diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55f1c10 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/mod_auth_gssapi-1.5.1.tar.gz diff --git a/.mod_auth_gssapi.metadata b/.mod_auth_gssapi.metadata new file mode 100644 index 0000000..7e4341b --- /dev/null +++ b/.mod_auth_gssapi.metadata @@ -0,0 +1 @@ +4cea95bd22f676cd49fca290e3dc93eb1ea7182a SOURCES/mod_auth_gssapi-1.5.1.tar.gz diff --git a/SOURCES/Allow-admins-to-selectively-suppress-negotiation.patch b/SOURCES/Allow-admins-to-selectively-suppress-negotiation.patch new file mode 100644 index 0000000..74ce671 --- /dev/null +++ b/SOURCES/Allow-admins-to-selectively-suppress-negotiation.patch @@ -0,0 +1,163 @@ +From 96a8f82b0b9a84a7ba7ba84965978ab27674b802 Mon Sep 17 00:00:00 2001 +From: Simo Sorce +Date: Mon, 24 Apr 2017 15:40:33 -0400 +Subject: [PATCH] Allow admins to selectively suppress negotiation + +If the admin sets the gssapi-no-negotiate requets enironemnt variable, +then we suppress the ability to send Negotiate headers. +This is useful to slectively send negotiate only to specific whielisted +or blacklisted browsers, clients, IP Addresses, etc... based on +directives like BrowserMatch or SetEnvIf. + +Signed-off-by: Simo Sorce +Resolves #135 +(cherry picked from commit 114e4408523ca4d06da32c265680b9faa74ad882) +--- + src/mod_auth_gssapi.c | 13 ++++++++++--- + tests/httpd.conf | 13 +++++++++++++ + tests/magtests.py | 19 +++++++++++++++++++ + tests/t_nonego.py | 29 +++++++++++++++++++++++++++++ + 4 files changed, 71 insertions(+), 3 deletions(-) + create mode 100755 tests/t_nonego.py + +diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c +index 755654d..59120d1 100644 +--- a/src/mod_auth_gssapi.c ++++ b/src/mod_auth_gssapi.c +@@ -833,7 +833,7 @@ static int mag_auth(request_rec *req) + gss_OID_set desired_mechs = GSS_C_NO_OID_SET; + struct mag_conn *mc = NULL; + int i; +- bool send_auth_header = true; ++ bool send_nego_header = true; + + type = ap_auth_type(req); + if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) { +@@ -907,6 +907,11 @@ static int mag_auth(request_rec *req) + } + } + ++ /* check if admin wants to disable negotiate with this client */ ++ if (apr_table_get(req->subprocess_env, "gssapi-no-negotiate")) { ++ send_nego_header = false; ++ } ++ + if (cfg->ssl_only) { + if (!mag_conn_is_https(req->connection)) { + mag_post_error(req, cfg, MAG_AUTH_NOT_ALLOWED, 0, 0, +@@ -965,7 +970,9 @@ static int mag_auth(request_rec *req) + } + + /* We got auth header, sending auth header would mean re-auth */ +- send_auth_header = !cfg->negotiate_once; ++ if (cfg->negotiate_once) { ++ send_nego_header = false; ++ } + + for (i = 0; auth_types[i] != NULL; i++) { + if (strcasecmp(auth_header_type, auth_types[i]) == 0) { +@@ -1126,7 +1133,7 @@ done: + apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply); + } + } else if (ret == HTTP_UNAUTHORIZED) { +- if (send_auth_header) { ++ if (send_nego_header) { + apr_table_add(req->err_headers_out, + req_cfg->rep_proto, "Negotiate"); + if (is_mech_allowed(desired_mechs, gss_mech_ntlmssp, +diff --git a/tests/httpd.conf b/tests/httpd.conf +index 7879727..e17cf0a 100644 +--- a/tests/httpd.conf ++++ b/tests/httpd.conf +@@ -211,6 +211,19 @@ CoreDumpDirectory "${HTTPROOT}" + Require valid-user + + ++ ++ BrowserMatch NONEGO gssapi-no-negotiate ++ AuthType GSSAPI ++ AuthName "Login" ++ GssapiSSLonly Off ++ GssapiCredStore ccache:${HTTPROOT}/tmp/httpd_krb5_ccache ++ GssapiCredStore client_keytab:${HTTPROOT}/http.keytab ++ GssapiCredStore keytab:${HTTPROOT}/http.keytab ++ GssapiBasicAuth On ++ GssapiAllowedMech krb5 ++ Require valid-user ++ ++ + + ProxyRequests On + ProxyVia On +diff --git a/tests/magtests.py b/tests/magtests.py +index a008d81..4d276df 100755 +--- a/tests/magtests.py ++++ b/tests/magtests.py +@@ -410,6 +410,23 @@ def test_bad_acceptor_name(testdir, testenv, testlog): + sys.stderr.write('BAD ACCEPTOR: FAILED\n') + + ++def test_no_negotiate(testdir, testenv, testlog): ++ ++ nonego_dir = os.path.join(testdir, 'httpd', 'html', 'nonego') ++ os.mkdir(nonego_dir) ++ shutil.copy('tests/index.html', nonego_dir) ++ ++ with (open(testlog, 'a')) as logfile: ++ spnego = subprocess.Popen(["tests/t_nonego.py"], ++ stdout=logfile, stderr=logfile, ++ env=testenv, preexec_fn=os.setsid) ++ spnego.wait() ++ if spnego.returncode != 0: ++ sys.stderr.write('NO Negotiate: FAILED\n') ++ else: ++ sys.stderr.write('NO Negotiate: SUCCESS\n') ++ ++ + if __name__ == '__main__': + + args = parse_args() +@@ -454,6 +471,8 @@ if __name__ == '__main__': + testenv.update(kdcenv) + test_basic_auth_krb5(testdir, testenv, testlog) + ++ test_no_negotiate(testdir, testenv, testlog) ++ + finally: + with (open(testlog, 'a')) as logfile: + for name in processes: +diff --git a/tests/t_nonego.py b/tests/t_nonego.py +new file mode 100755 +index 0000000..c4f2bdd +--- /dev/null ++++ b/tests/t_nonego.py +@@ -0,0 +1,29 @@ ++#!/usr/bin/python ++# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license. ++ ++import os ++import requests ++ ++ ++if __name__ == '__main__': ++ url = 'http://%s/nonego/' % (os.environ['NSS_WRAPPER_HOSTNAME']) ++ ++ # ensure a 401 with the appropriate WWW-Authenticate header is returned ++ # when no auth is provided ++ r = requests.get(url) ++ if r.status_code != 401: ++ raise ValueError('NO Negotiate failed - 401 expected') ++ if not (r.headers.get("WWW-Authenticate") and ++ r.headers.get("WWW-Authenticate").startswith("Negotiate")): ++ raise ValueError('NO Negotiate failed - WWW-Authenticate ' ++ 'Negotiate header is absent') ++ ++ # ensure a 401 with the WWW-Authenticate Negotiate header is absent ++ # when the special User-Agent is sent ++ r = requests.get(url, headers={'User-Agent': 'NONEGO'}) ++ if r.status_code != 401: ++ raise ValueError('NO Negotiate failed - 401 expected') ++ if (r.headers.get("WWW-Authenticate") and ++ r.headers.get("WWW-Authenticate").startswith("Negotiate")): ++ raise ValueError('NO Negotiate failed - WWW-Authenticate ' ++ 'Negotiate header is present, should be absent') diff --git a/SOURCES/Document-gssapi-no-negotiate.patch b/SOURCES/Document-gssapi-no-negotiate.patch new file mode 100644 index 0000000..92eeaca --- /dev/null +++ b/SOURCES/Document-gssapi-no-negotiate.patch @@ -0,0 +1,38 @@ +From 703a06e6c370973e2f3c6694abddbdd380f0bf31 Mon Sep 17 00:00:00 2001 +From: Robbie Harwood +Date: Thu, 19 Oct 2017 16:35:29 -0400 +Subject: [PATCH] Document gssapi-no-negotiate + +Adds new section for apache env vars. + +(cherry picked from commit 5330fa4959ca9317bf5943decfaaf0bbe86f1853) +--- + README | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +diff --git a/README b/README +index 1fdfe98..1393760 100644 +--- a/README ++++ b/README +@@ -418,3 +418,21 @@ Note: The GSS_C_NT_HOSTBASED_SERVICE format is used for names (see example). + #### Example + GssapiAcceptorName HTTP@www.example.com + ++Environment Variables ++--------------------- ++ ++(Note: these are not process environment variables, but rather Apache ++environment variables, as described ++[in the apache docs](https://httpd.apache.org/docs/2.4/env.html).) ++ ++### gssapi-no-negotiate ++ ++This environment variable is used to suppress setting Negotiate headers. Not ++sending these headers is useful to work around browsers that do not handle ++them properly (and incorrectly show authentication popups to users). ++ ++#### Example ++ ++For instance, to suppress negotiation on Windows browsers, one could set: ++ ++ BrowserMatch Windows gssapi-no-negotiate diff --git a/SOURCES/Fix-strtol-error-checking.patch b/SOURCES/Fix-strtol-error-checking.patch new file mode 100644 index 0000000..d1a91eb --- /dev/null +++ b/SOURCES/Fix-strtol-error-checking.patch @@ -0,0 +1,34 @@ +From 26a82531170a046c3f8d04e85276e8bd118a36ce Mon Sep 17 00:00:00 2001 +From: Simo Sorce +Date: Tue, 3 Oct 2017 12:33:38 -0400 +Subject: [PATCH] Fix strtol error checking + +Signed-off-by: Simo Sorce +Reviewed-by: Robbie Harwood +(cherry picked from commit 7cddf31b6ea7d0c67ac8a086d6b61d3f3631f47c) +--- + src/mod_auth_gssapi.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c +index 59120d1..74962d1 100644 +--- a/src/mod_auth_gssapi.c ++++ b/src/mod_auth_gssapi.c +@@ -1502,7 +1502,7 @@ static const char *mag_deleg_ccache_perms(cmd_parms *parms, void *mconfig, + if (isdigit(*p)) { + char *endptr; + cfg->deleg_ccache_uid = strtol(p, &endptr, 0); +- if (errno != 0 || endptr != '\0') { ++ if (errno != 0 || (endptr && *endptr != '\0')) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server, + "Invalid GssapiDelegCcachePerms uid value [%s]", + p); +@@ -1527,7 +1527,7 @@ static const char *mag_deleg_ccache_perms(cmd_parms *parms, void *mconfig, + if (isdigit(*p)) { + char *endptr; + cfg->deleg_ccache_gid = strtol(p, &endptr, 0); +- if (errno != 0 || endptr != '\0') { ++ if (errno != 0 || (endptr && *endptr != '\0')) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server, + "Invalid GssapiDelegCcachePerms gid value [%s]", + p); diff --git a/SOURCES/Handle-extra-large-NSS-entries.patch b/SOURCES/Handle-extra-large-NSS-entries.patch new file mode 100644 index 0000000..90d2124 --- /dev/null +++ b/SOURCES/Handle-extra-large-NSS-entries.patch @@ -0,0 +1,168 @@ +From 43992c5d7b138a11339a725242863b5d001d21bb Mon Sep 17 00:00:00 2001 +From: Simo Sorce +Date: Tue, 3 Oct 2017 12:31:02 -0400 +Subject: [PATCH] Handle extra large NSS entries + +Signed-off-by: Simo Sorce +Reviewed-by: Robbie Harwood + +Closes #152 + +(cherry picked from commit 9813eb46c6c415a933c5edf5076fd0a8e39c86b2) +[rharwood@redhat.com: conflicts due to no lex/yacc stuff yet] +--- + src/Makefile.am | 2 +- + src/mod_auth_gssapi.c | 26 +++++++------------- + src/mod_auth_gssapi.h | 2 ++ + src/util.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 78 insertions(+), 18 deletions(-) + create mode 100644 src/util.c + +diff --git a/src/Makefile.am b/src/Makefile.am +index 0605261..d8dd3ec 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -8,7 +8,7 @@ dist_noinst_HEADERS = \ + mod_auth_gssapi.h crypto.h sessions.h environ.h + + mod_auth_gssapi_la_SOURCES = \ +- mod_auth_gssapi.c crypto.c sessions.c environ.c ++ mod_auth_gssapi.c crypto.c sessions.c environ.c util.c + mod_auth_gssapi_la_CFLAGS = \ + $(MAG_CFLAGS) + mod_auth_gssapi_la_LIBADD = \ +diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c +index 74962d1..c2dd039 100644 +--- a/src/mod_auth_gssapi.c ++++ b/src/mod_auth_gssapi.c +@@ -1476,7 +1476,7 @@ static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig, + #define CCMODE "mode:" + #define CCUID "uid:" + #define CCGID "gid:" +-#define NSS_BUF_LEN 2048 /* just use a uid/gid number if not big enough */ ++ + static const char *mag_deleg_ccache_perms(cmd_parms *parms, void *mconfig, + const char *w) + { +@@ -1510,15 +1510,11 @@ static const char *mag_deleg_ccache_perms(cmd_parms *parms, void *mconfig, + cfg->deleg_ccache_uid = 0; + } + } else { +- struct passwd pwd, *user; +- char buf[NSS_BUF_LEN]; +- int ret = getpwnam_r(p, &pwd, buf, NSS_BUF_LEN, &user); +- if ((ret != 0) || user != &pwd) { ++ int ret = mag_get_user_uid(p, &cfg->deleg_ccache_uid); ++ if (ret != 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server, +- "Invalid GssapiDelegCcachePerms uid value [%s]", +- p); +- } else { +- cfg->deleg_ccache_uid = user->pw_uid; ++ "Invalid GssapiDelegCcachePerms uid value [%s](%s)", ++ p, strerror(ret)); + } + } + } else if (strncmp(w, CCGID, sizeof(CCGID) - 1) == 0) { +@@ -1535,15 +1531,11 @@ static const char *mag_deleg_ccache_perms(cmd_parms *parms, void *mconfig, + cfg->deleg_ccache_gid = 0; + } + } else { +- struct group grp, *group; +- char buf[NSS_BUF_LEN]; +- int ret = getgrnam_r(p, &grp, buf, NSS_BUF_LEN, &group); +- if ((ret != 0) || group != &grp) { ++ int ret = mag_get_group_gid(p, &cfg->deleg_ccache_gid); ++ if (ret != 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server, +- "Invalid GssapiDelegCcachePerms gid value [%s]", +- p); +- } else { +- cfg->deleg_ccache_gid = group->gr_gid; ++ "Invalid GssapiDelegCcachePerms gid value [%s](%s)", ++ p, strerror(ret)); + } + } + } else { +diff --git a/src/mod_auth_gssapi.h b/src/mod_auth_gssapi.h +index 159d6b7..2ea52a3 100644 +--- a/src/mod_auth_gssapi.h ++++ b/src/mod_auth_gssapi.h +@@ -138,3 +138,5 @@ struct mag_conn { + struct mag_conn *mag_new_conn_ctx(apr_pool_t *pool); + const char *mag_str_auth_type(int auth_type); + char *mag_error(apr_pool_t *pool, const char *msg, uint32_t maj, uint32_t min); ++int mag_get_user_uid(const char *name, uid_t *uid); ++int mag_get_group_gid(const char *name, gid_t *gid); +diff --git a/src/util.c b/src/util.c +new file mode 100644 +index 0000000..6b2a4a0 +--- /dev/null ++++ b/src/util.c +@@ -0,0 +1,66 @@ ++/* Copyright (C) 2017 mod_auth_gssapi contributors - See COPYING for (C) terms */ ++ ++#include "mod_auth_gssapi.h" ++ ++#define NSS_BUF_MIN 1024 ++#define NSS_BUF_MAX 1024*1024 ++static char *get_buf(char *cur, size_t *len) ++{ ++ if (*len == 0) { ++ *len = NSS_BUF_MIN; ++ } else { ++ *len *= 2; ++ } ++ if (*len > NSS_BUF_MAX) { ++ *len = 0; /* will free the buf and return NULL */ ++ } ++ return realloc(cur, *len); ++} ++ ++int mag_get_user_uid(const char *name, uid_t *uid) ++{ ++ struct passwd pwd, *user; ++ size_t buflen = 0; ++ char *buf = NULL; ++ int ret; ++ ++ do { ++ buf = get_buf(buf, &buflen); ++ if (buf == NULL || buflen == 0) { ++ ret = ENOMEM; ++ break; ++ } ++ ret = getpwnam_r(name, &pwd, buf, buflen, &user); ++ } while (ret == ERANGE); ++ if (ret != 0 || user != &pwd) { ++ ret = (ret == 0) ? EINVAL : ret; ++ } else { ++ *uid = user->pw_uid; ++ } ++ free(buf); ++ return ret; ++} ++ ++int mag_get_group_gid(const char *name, gid_t *gid) ++{ ++ struct group grp, *group; ++ size_t buflen = 0; ++ char *buf = NULL; ++ int ret; ++ ++ do { ++ buf = get_buf(buf, &buflen); ++ if (buf == NULL || buflen == 0) { ++ ret = ENOMEM; ++ break; ++ } ++ ret = getgrnam_r(name, &grp, buf, buflen, &group); ++ } while (ret == ERANGE); ++ if (ret != 0 || group != &grp) { ++ ret = (ret == 0) ? EINVAL : ret; ++ } else { ++ *gid = group->gr_gid; ++ } ++ free(buf); ++ return ret; ++} diff --git a/SOURCES/report-file-operation-errors-as-warnings.patch b/SOURCES/report-file-operation-errors-as-warnings.patch new file mode 100644 index 0000000..99ffe3f --- /dev/null +++ b/SOURCES/report-file-operation-errors-as-warnings.patch @@ -0,0 +1,58 @@ +From d4256f0aa908b7b828bbefc89e712b09c6e87968 Mon Sep 17 00:00:00 2001 +From: Alexander Bokovoy +Date: Fri, 17 Mar 2017 14:17:43 +0200 +Subject: [PATCH] report file operation errors as warnings + +The failures to change permissions and chown the ccache do not prevent +us from continuing to use this ccache. Log messages are confusing to +mod_auth_gssapi users as they leave an impression things are completely +broken. + +Change log level to warning to avoid filling up logs with them on error +log level. + +Signed-off-by: Alexander Bokovoy +Reviewed-by: Simo Sorce +(cherry picked from commit 12ebe14ee6636801c750eb6e982b30635788e4ba) +--- + src/environ.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/environ.c b/src/environ.c +index 5c80b35..66fbbe2 100644 +--- a/src/environ.c ++++ b/src/environ.c +@@ -329,7 +329,7 @@ static void mag_set_ccname_envvar(request_rec *req, struct mag_config *cfg, + (finfo.protection != cfg->deleg_ccache_mode)) { + status = apr_file_perms_set(path, cfg->deleg_ccache_mode); + if (status != APR_SUCCESS) +- ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, ++ ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, req, + "failed to set perms (%o) on file (%s)!", + cfg->deleg_ccache_mode, path); + } +@@ -337,7 +337,7 @@ static void mag_set_ccname_envvar(request_rec *req, struct mag_config *cfg, + (finfo.user != cfg->deleg_ccache_uid)) { + status = lchown(path, cfg->deleg_ccache_uid, -1); + if (status != 0) +- ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, ++ ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, req, + "failed to set user (%u) on file (%s)!", + cfg->deleg_ccache_uid, path); + } +@@ -345,13 +345,13 @@ static void mag_set_ccname_envvar(request_rec *req, struct mag_config *cfg, + (finfo.group != cfg->deleg_ccache_gid)) { + status = lchown(path, -1, cfg->deleg_ccache_gid); + if (status != 0) +- ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, ++ ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, req, + "failed to set group (%u) on file (%s)!", + cfg->deleg_ccache_gid, path); + } + } else { + /* set the file cache anyway, but warn */ +- ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, ++ ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, req, + "KRB5CCNAME file (%s) lookup failed!", path); + } + diff --git a/SPECS/mod_auth_gssapi.spec b/SPECS/mod_auth_gssapi.spec new file mode 100644 index 0000000..d2701ad --- /dev/null +++ b/SPECS/mod_auth_gssapi.spec @@ -0,0 +1,91 @@ +Name: mod_auth_gssapi +Version: 1.5.1 +Release: 5%{?dist} +Summary: A GSSAPI Authentication module for Apache + +Group: System Environment/Daemons +License: MIT +URL: https://github.com/modauthgssapi/mod_auth_gssapi +Source0: https://github.com/modauthgssapi/%{name}/releases/download/v%{version}/%name-%{version}.tar.gz + +Patch0: report-file-operation-errors-as-warnings.patch +Patch1: Allow-admins-to-selectively-suppress-negotiation.patch +Patch2: Fix-strtol-error-checking.patch +Patch3: Handle-extra-large-NSS-entries.patch +Patch4: Document-gssapi-no-negotiate.patch + +BuildRequires: httpd-devel, krb5-devel, openssl-devel, autoconf, automake, libtool +Requires: httpd-mmn = %{_httpd_mmn} +Requires: krb5-libs >= 1.11.5 + +%description +The mod_auth_gssapi module is an authentication service that implements the +SPNEGO based HTTP Authentication protocol defined in RFC4559. + +%prep +%setup -q +%patch0 -p1 -b .report-file-operation-errors-as-warnings +%patch1 -p1 -b .Allow-admins-to-selectively-suppress-negotiation +%patch2 -p1 -b .Fix-strtol-error-checking +%patch3 -p1 -b .Handle-extra-large-NSS-entries +%patch4 -p1 -b .Document-gssapi-no-negotiate + +%build +export APXS=%{_httpd_apxs} +autoreconf -fi +%configure +make %{?_smp_mflags} + + +%install +mkdir -p %{buildroot}%{_httpd_moddir} +install -m 755 src/.libs/%{name}.so %{buildroot}%{_httpd_moddir} + +# Apache configuration for the module +echo "LoadModule auth_gssapi_module modules/mod_auth_gssapi.so" > 10-auth_gssapi.conf +mkdir -p %{buildroot}%{_httpd_modconfdir} +install -m 644 10-auth_gssapi.conf %{buildroot}%{_httpd_modconfdir} + +%files +%doc +%defattr(-,root,root) +%doc README COPYING +%config(noreplace) %{_httpd_modconfdir}/10-auth_gssapi.conf +%{_httpd_moddir}/mod_auth_gssapi.so + +%changelog +* Fri Oct 27 2017 Robbie Harwood - 1.5.1-5 +- Document gssapi-no-negotiate +- Resolves: #1309041 + +* Wed Oct 04 2017 Robbie Harwood - 1.5.1-4 +- Handle large NSS entries (>1024) +- Resolves: #1498176 + +* Mon Oct 02 2017 Robbie Harwood - 1.5.1-3 +- Allow admins to suppress negotiation selectively +- Resolves: #1309041 + +* Mon Mar 27 2017 Simo Sorce - 1.5.1-2 +- Fix log level on some messages +- resolves: #1433362 + +* Thu Mar 9 2017 Simo Sorce - 1.5.1-1 +- Korabl-Sputnik 4 launch (1.5.1) +- resolves: #1403194 + +* Tue Jun 21 2016 Simo Sorce 1.4.0-1 +- Lunar Reconnaissance Orbiter (2009) release (1.4.0) +- resolves: #1346883 +- resolves: #1343422 + +* Thu Sep 3 2015 Simo Sorce 1.3.1-1 +- Various bugfixes and minor new features +- resolves: #1258168 +- resolves: #1258171 +- resolves: #1258172 +- resolves: #1258456 + +* Wed Apr 29 2015 Simo Sorce 1.2.0-1 +- First RHEL release +- resolves: #1205367