From 73b857921ade31832351cebe918b9316a4bc7e2f Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: May 07 2020 17:36:39 +0000 Subject: BAckport GSSAPI related fixes and features from master Backport GSSAPI Channel Bindings support Add support for setting maxssf=0 in GSS-SPNEGO Reduce excessive GSSAPI plugin logging Signed-off-by: Simo Sorce --- diff --git a/cyrus-sasl-2.1.27-Add-Channel-Binding-support-for-GSSAPI-GSS-SPNEGO.patch b/cyrus-sasl-2.1.27-Add-Channel-Binding-support-for-GSSAPI-GSS-SPNEGO.patch new file mode 100644 index 0000000..242b436 --- /dev/null +++ b/cyrus-sasl-2.1.27-Add-Channel-Binding-support-for-GSSAPI-GSS-SPNEGO.patch @@ -0,0 +1,444 @@ +From aa8b6b2275fd14ba2cca3d2339ae61c7e7ddfa70 Mon Sep 17 00:00:00 2001 +From: Simo Sorce +Date: Tue, 5 May 2020 14:08:48 -0400 +Subject: [PATCH] Add Channel Binding support for GSSAPI/GSS-SPNEGO + +Backport of commit ids: +829a6ed086432e26dafa9d1dcf892aef4c42cfbd +944bd8a6205f840b105206ef83e8f6b9dff0138e + +Signed-off-by: Simo Sorce +--- + plugins/gssapi.c | 30 +++++++++++--- + tests/runtests.py | 93 ++++++++++++++++++++++++++++++++++++++++---- + tests/t_common.c | 24 ++++++++---- + tests/t_common.h | 5 ++- + tests/t_gssapi_cli.c | 24 ++++++++++-- + tests/t_gssapi_srv.c | 24 ++++++++++-- + 6 files changed, 172 insertions(+), 28 deletions(-) + +diff --git a/plugins/gssapi.c b/plugins/gssapi.c +index ff663da..5d900c5 100644 +--- a/plugins/gssapi.c ++++ b/plugins/gssapi.c +@@ -830,7 +830,9 @@ gssapi_server_mech_authneg(context_t *text, + gss_buffer_desc name_without_realm; + gss_name_t client_name_MN = NULL, without = NULL; + gss_OID mech_type; +- ++ gss_channel_bindings_t bindings = GSS_C_NO_CHANNEL_BINDINGS; ++ struct gss_channel_bindings_struct cb = {0}; ++ + input_token = &real_input_token; + output_token = &real_output_token; + output_token->value = NULL; output_token->length = 0; +@@ -902,6 +904,12 @@ gssapi_server_mech_authneg(context_t *text, + real_input_token.length = clientinlen; + } + ++ if (params->cbinding != NULL) { ++ cb.application_data.length = params->cbinding->len; ++ cb.application_data.value = params->cbinding->data; ++ bindings = &cb; ++ } ++ + + GSS_LOCK_MUTEX_CTX(params->utils, text); + maj_stat = +@@ -909,7 +917,7 @@ gssapi_server_mech_authneg(context_t *text, + &(text->gss_ctx), + server_creds, + input_token, +- GSS_C_NO_CHANNEL_BINDINGS, ++ bindings, + &text->client_name, + &mech_type, + output_token, +@@ -1505,7 +1513,8 @@ static sasl_server_plug_t gssapi_server_plugins[] = + | SASL_SEC_PASS_CREDENTIALS, + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY +- | SASL_FEAT_DONTUSE_USERPASSWD, /* features */ ++ | SASL_FEAT_DONTUSE_USERPASSWD ++ | SASL_FEAT_CHANNEL_BINDING, /* features */ + NULL, /* glob_context */ + &gssapi_server_mech_new, /* mech_new */ + &gssapi_server_mech_step, /* mech_step */ +@@ -1529,6 +1538,7 @@ static sasl_server_plug_t gssapi_server_plugins[] = + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_DONTUSE_USERPASSWD ++ | SASL_FEAT_CHANNEL_BINDING + | SASL_FEAT_SUPPORTS_HTTP, /* features */ + &gss_spnego_oid, /* glob_context */ + &gssapi_server_mech_new, /* mech_new */ +@@ -1662,6 +1672,8 @@ static int gssapi_client_mech_step(void *conn_context, + input_token->value = NULL; + input_token->length = 0; + gss_cred_id_t client_creds = (gss_cred_id_t)params->gss_creds; ++ gss_channel_bindings_t bindings = GSS_C_NO_CHANNEL_BINDINGS; ++ struct gss_channel_bindings_struct cb = {0}; + + if (clientout) + *clientout = NULL; +@@ -1777,6 +1789,12 @@ static int gssapi_client_mech_step(void *conn_context, + req_flags = req_flags | GSS_C_DELEG_FLAG; + } + ++ if (params->cbinding != NULL) { ++ cb.application_data.length = params->cbinding->len; ++ cb.application_data.value = params->cbinding->data; ++ bindings = &cb; ++ } ++ + GSS_LOCK_MUTEX_CTX(params->utils, text); + maj_stat = gss_init_sec_context(&min_stat, + client_creds, /* GSS_C_NO_CREDENTIAL */ +@@ -1785,7 +1803,7 @@ static int gssapi_client_mech_step(void *conn_context, + text->mech_type, + req_flags, + 0, +- GSS_C_NO_CHANNEL_BINDINGS, ++ bindings, + input_token, + NULL, + output_token, +@@ -2190,7 +2208,8 @@ static sasl_client_plug_t gssapi_client_plugins[] = + | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ + SASL_FEAT_NEEDSERVERFQDN + | SASL_FEAT_WANT_CLIENT_FIRST +- | SASL_FEAT_ALLOWS_PROXY, /* features */ ++ | SASL_FEAT_ALLOWS_PROXY ++ | SASL_FEAT_CHANNEL_BINDING, /* features */ + gssapi_required_prompts, /* required_prompts */ + GSS_C_NO_OID, /* glob_context */ + &gssapi_client_mech_new, /* mech_new */ +@@ -2213,6 +2232,7 @@ static sasl_client_plug_t gssapi_client_plugins[] = + SASL_FEAT_NEEDSERVERFQDN + | SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY ++ | SASL_FEAT_CHANNEL_BINDING + | SASL_FEAT_SUPPORTS_HTTP, /* features */ + gssapi_required_prompts, /* required_prompts */ + &gss_spnego_oid, /* glob_context */ +diff --git a/tests/runtests.py b/tests/runtests.py +index f645adf..fc9cf24 100755 +--- a/tests/runtests.py ++++ b/tests/runtests.py +@@ -1,6 +1,7 @@ + #!/usr/bin/python3 + + import argparse ++import base64 + import os + import shutil + import signal +@@ -126,14 +127,7 @@ def setup_kdc(testdir, env): + + return kdc, env + +- +-def gssapi_tests(testdir): +- """ SASL/GSSAPI Tests """ +- env = setup_socket_wrappers(testdir) +- kdc, kenv = setup_kdc(testdir, env) +- #print("KDC: {}, ENV: {}".format(kdc, kenv)) +- kenv['KRB5_TRACE'] = os.path.join(testdir, 'trace.log') +- ++def gssapi_basic_test(kenv): + try: + srv = subprocess.Popen(["../tests/t_gssapi_srv"], + stdout=subprocess.PIPE, +@@ -155,11 +149,94 @@ def gssapi_tests(testdir): + srv.returncode, srv.stderr.read().decode('utf-8'))) + except Exception as e: + print("FAIL: {}".format(e)) ++ return ++ ++ print("PASS: CLI({}) SRV({})".format( ++ cli.stdout.read().decode('utf-8').strip(), ++ srv.stdout.read().decode('utf-8').strip())) ++ ++def gssapi_channel_binding_test(kenv): ++ try: ++ bindings = base64.b64encode("MATCHING CBS".encode('utf-8')) ++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-c", bindings], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ srv.stdout.readline() # Wait for srv to say it is ready ++ cli = subprocess.Popen(["../tests/t_gssapi_cli", "-c", bindings], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ try: ++ cli.wait(timeout=5) ++ srv.wait(timeout=5) ++ except Exception as e: ++ print("Failed on {}".format(e)); ++ cli.kill() ++ srv.kill() ++ if cli.returncode != 0 or srv.returncode != 0: ++ raise Exception("CLI ({}): {} --> SRV ({}): {}".format( ++ cli.returncode, cli.stderr.read().decode('utf-8'), ++ srv.returncode, srv.stderr.read().decode('utf-8'))) ++ except Exception as e: ++ print("FAIL: {}".format(e)) ++ return + + print("PASS: CLI({}) SRV({})".format( + cli.stdout.read().decode('utf-8').strip(), + srv.stdout.read().decode('utf-8').strip())) + ++def gssapi_channel_binding_mismatch_test(kenv): ++ result = "FAIL" ++ try: ++ bindings = base64.b64encode("SRV CBS".encode('utf-8')) ++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-c", bindings], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ srv.stdout.readline() # Wait for srv to say it is ready ++ bindings = base64.b64encode("CLI CBS".encode('utf-8')) ++ cli = subprocess.Popen(["../tests/t_gssapi_cli", "-c", bindings], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ try: ++ cli.wait(timeout=5) ++ srv.wait(timeout=5) ++ except Exception as e: ++ print("Failed on {}".format(e)); ++ cli.kill() ++ srv.kill() ++ if cli.returncode != 0 or srv.returncode != 0: ++ cli_err = cli.stderr.read().decode('utf-8').strip() ++ srv_err = srv.stderr.read().decode('utf-8').strip() ++ if "authentication failure" in srv_err: ++ result = "PASS" ++ raise Exception("CLI ({}): {} --> SRV ({}): {}".format( ++ cli.returncode, cli_err, srv.returncode, srv_err)) ++ except Exception as e: ++ print("{}: {}".format(result, e)) ++ return ++ ++ print("FAIL: This test should fail [CLI({}) SRV({})]".format( ++ cli.stdout.read().decode('utf-8').strip(), ++ srv.stdout.read().decode('utf-8').strip())) ++ ++def gssapi_tests(testdir): ++ """ SASL/GSSAPI Tests """ ++ env = setup_socket_wrappers(testdir) ++ kdc, kenv = setup_kdc(testdir, env) ++ #print("KDC: {}, ENV: {}".format(kdc, kenv)) ++ kenv['KRB5_TRACE'] = os.path.join(testdir, 'trace.log') ++ ++ print('GSSAPI BASIC:') ++ print(' ', end='') ++ gssapi_basic_test(kenv) ++ ++ print('GSSAPI CHANNEL BINDING:') ++ print(' ', end='') ++ gssapi_channel_binding_test(kenv) ++ ++ print('GSSAPI CHANNEL BINDING MISMTACH:') ++ print(' ', end='') ++ gssapi_channel_binding_mismatch_test(kenv) ++ + os.killpg(kdc.pid, signal.SIGTERM) + + +diff --git a/tests/t_common.c b/tests/t_common.c +index 7168b2f..478e6a1 100644 +--- a/tests/t_common.c ++++ b/tests/t_common.c +@@ -1,4 +1,5 @@ +-/* TBD, add (C) */ ++/* Copyright (C) Simo Sorce ++ * See COPYING file for License */ + + #include + +@@ -13,9 +14,6 @@ void send_string(int sd, const char *s, unsigned int l) + { + ssize_t ret; + +-fprintf(stderr, "s:%u ", l); +-fflush(stderr); +- + ret = send(sd, &l, sizeof(l), 0); + if (ret != sizeof(l)) s_error("send size", ret, sizeof(l), errno); + +@@ -34,8 +32,6 @@ void recv_string(int sd, char *buf, unsigned int *buflen) + if (ret != sizeof(l)) s_error("recv size", ret, sizeof(l), errno); + + if (l == 0) { +-fprintf(stderr, "r:0 "); +-fflush(stderr); + *buflen = 0; + return; + } +@@ -45,8 +41,6 @@ fflush(stderr); + ret = recv(sd, buf, l, 0); + if (ret != l) s_error("recv data", ret, l, errno); + +-fprintf(stderr, "r:%ld ", ret); +-fflush(stderr); + *buflen = ret; + } + +@@ -65,4 +59,18 @@ int getpath(void *context __attribute__((unused)), const char **path) + return SASL_OK; + } + ++void parse_cb(sasl_channel_binding_t *cb, char *buf, unsigned max, char *in) ++{ ++ unsigned len; ++ int r; + ++ r = sasl_decode64(in, strlen(in), buf, max, &len); ++ if (r != SASL_OK) { ++ saslerr(r, "failed to parse channel bindings"); ++ exit(-1); ++ } ++ cb->name = "TEST BINDINGS"; ++ cb->critical = 0; ++ cb->data = (unsigned char *)buf; ++ cb->len = len; ++} +diff --git a/tests/t_common.h b/tests/t_common.h +index 4ee1976..a10def1 100644 +--- a/tests/t_common.h ++++ b/tests/t_common.h +@@ -1,4 +1,5 @@ +-/* TBD, add (C) */ ++/* Copyright (C) Simo Sorce ++ * See COPYING file for License */ + + #include "config.h" + +@@ -7,9 +8,11 @@ + #include + + #include ++#include + + void s_error(const char *hdr, ssize_t ret, ssize_t len, int err); + void send_string(int sd, const char *s, unsigned int l); + void recv_string(int sd, char *buf, unsigned int *buflen); + void saslerr(int why, const char *what); + int getpath(void *context __attribute__((unused)), const char **path); ++void parse_cb(sasl_channel_binding_t *cb, char *buf, unsigned max, char *in); +diff --git a/tests/t_gssapi_cli.c b/tests/t_gssapi_cli.c +index c833c05..a44a3f5 100644 +--- a/tests/t_gssapi_cli.c ++++ b/tests/t_gssapi_cli.c +@@ -1,4 +1,5 @@ +-/* TBD, add (C) */ ++/* Copyright (C) Simo Sorce ++ * See COPYING file for License */ + + #include "t_common.h" + +@@ -13,6 +14,7 @@ + + #include + #include ++#include + + static int setup_socket(void) + { +@@ -32,7 +34,7 @@ static int setup_socket(void) + return sock; + } + +-int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) ++int main(int argc, char *argv[]) + { + sasl_callback_t callbacks[2] = {}; + char buf[8192]; +@@ -40,8 +42,20 @@ int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) + sasl_conn_t *conn; + const char *data; + unsigned int len; ++ sasl_channel_binding_t cb = {0}; ++ char cb_buf[256]; + int sd; +- int r; ++ int c, r; ++ ++ while ((c = getopt(argc, argv, "c:")) != EOF) { ++ switch (c) { ++ case 'c': ++ parse_cb(&cb, cb_buf, 256, optarg); ++ break; ++ default: ++ break; ++ } ++ } + + /* initialize the sasl library */ + callbacks[0].id = SASL_CB_GETPATH; +@@ -60,6 +74,10 @@ int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) + exit(-1); + } + ++ if (cb.name) { ++ sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); ++ } ++ + r = sasl_client_start(conn, "GSSAPI", NULL, &data, &len, &chosenmech); + if (r != SASL_OK && r != SASL_CONTINUE) { + saslerr(r, "starting SASL negotiation"); +diff --git a/tests/t_gssapi_srv.c b/tests/t_gssapi_srv.c +index 29f538d..ef1217f 100644 +--- a/tests/t_gssapi_srv.c ++++ b/tests/t_gssapi_srv.c +@@ -1,4 +1,5 @@ +-/* TBD, add (C) */ ++/* Copyright (C) Simo Sorce ++ * See COPYING file for License */ + + #include "t_common.h" + +@@ -44,15 +45,28 @@ static int setup_socket(void) + return sd; + } + +-int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) ++int main(int argc, char *argv[]) + { + sasl_callback_t callbacks[2] = {}; + char buf[8192]; + sasl_conn_t *conn; + const char *data; + unsigned int len; ++ sasl_channel_binding_t cb = {0}; ++ unsigned char cb_buf[256]; + int sd; +- int r; ++ int c, r; ++ ++ while ((c = getopt(argc, argv, "c:")) != EOF) { ++ switch (c) { ++ case 'c': ++ parse_cb(&cb, cb_buf, 256, optarg); ++ break; ++ default: ++ break; ++ } ++ } ++ + + /* initialize the sasl library */ + callbacks[0].id = SASL_CB_GETPATH; +@@ -72,6 +86,10 @@ int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) + exit(-1); + } + ++ if (cb.name) { ++ sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); ++ } ++ + sd = setup_socket(); + + len = 8192; +-- +2.18.2 + diff --git a/cyrus-sasl-2.1.27-Add-basic-test-infrastructure.patch b/cyrus-sasl-2.1.27-Add-basic-test-infrastructure.patch new file mode 100644 index 0000000..2f6a35c --- /dev/null +++ b/cyrus-sasl-2.1.27-Add-basic-test-infrastructure.patch @@ -0,0 +1,641 @@ +From 82e299e970461c153a036bb1fbc84e808f926e12 Mon Sep 17 00:00:00 2001 +From: Simo Sorce +Date: Tue, 5 May 2020 14:06:57 -0400 +Subject: [PATCH] Add basic test infrastructure + +First test is for SASL/GSSAPI + +Backport of upstream commit id: +18ff41d5d18f61c2ded7235dad1d9618aa84784b + +Signed-off-by: Simo Sorce +--- + Makefile.am | 2 +- + configure.ac | 3 +- + tests/Makefile.am | 79 +++++++++++++++++++ + tests/runtests.py | 179 +++++++++++++++++++++++++++++++++++++++++++ + tests/t_common.c | 68 ++++++++++++++++ + tests/t_common.h | 15 ++++ + tests/t_gssapi_cli.c | 95 +++++++++++++++++++++++ + tests/t_gssapi_srv.c | 111 +++++++++++++++++++++++++++ + 8 files changed, 550 insertions(+), 2 deletions(-) + create mode 100644 tests/Makefile.am + create mode 100755 tests/runtests.py + create mode 100644 tests/t_common.c + create mode 100644 tests/t_common.h + create mode 100644 tests/t_gssapi_cli.c + create mode 100644 tests/t_gssapi_srv.c + +diff --git a/Makefile.am b/Makefile.am +index 83dae6f..fc24509 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -70,7 +70,7 @@ else + INSTALLOSX = + endif + +-SUBDIRS=include sasldb common lib plugins utils $(PWC) $(SAM) $(JAV) $(SAD) ++SUBDIRS=include sasldb common lib plugins utils $(PWC) $(SAM) $(JAV) $(SAD) tests + EXTRA_DIST=config doc docsrc win32 mac dlcompat-20010505 NTMakefile \ + INSTALL.TXT libsasl2.pc.in + +diff --git a/configure.ac b/configure.ac +index ca5936a..c1d2182 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1575,7 +1575,8 @@ java/javax/Makefile + java/javax/security/Makefile + java/javax/security/auth/Makefile + java/javax/security/auth/callback/Makefile +-pwcheck/Makefile) ++pwcheck/Makefile ++tests/Makefile) + + AC_MSG_NOTICE([ + +diff --git a/tests/Makefile.am b/tests/Makefile.am +new file mode 100644 +index 0000000..1edf010 +--- /dev/null ++++ b/tests/Makefile.am +@@ -0,0 +1,79 @@ ++# Makefile.am -- automake input for cyrus-sasl tests ++# Simo Sorce ++# ++################################################################ ++# Copyright (c) 2000 Carnegie Mellon University. All rights reserved. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions ++# are met: ++# ++# 1. Redistributions of source code must retain the above copyright ++# notice, this list of conditions and the following disclaimer. ++# ++# 2. Redistributions in binary form must reproduce the above copyright ++# notice, this list of conditions and the following disclaimer in ++# the documentation and/or other materials provided with the ++# distribution. ++# ++# 3. The name "Carnegie Mellon University" must not be used to ++# endorse or promote products derived from this software without ++# prior written permission. For permission or any other legal ++# details, please contact ++# Office of Technology Transfer ++# Carnegie Mellon University ++# 5000 Forbes Avenue ++# Pittsburgh, PA 15213-3890 ++# (412) 268-4387, fax: (412) 268-7395 ++# tech-transfer@andrew.cmu.edu ++# ++# 4. Redistributions of any form whatsoever must retain the following ++# acknowledgment: ++# "This product includes software developed by Computing Services ++# at Carnegie Mellon University (http://www.cmu.edu/computing/)." ++# ++# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO ++# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY ++# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE ++# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN ++# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING ++# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++# ++################################################################ ++ ++AM_CPPFLAGS=-I$(top_srcdir)/include -DPLUGINDIR='"${top_srcdir}/plugins/.libs"' ++ ++COMMON_LDADD = ../lib/libsasl2.la $(GSSAPIBASE_LIBS) $(GSSAPI_LIBS) $(LIB_SOCKET) ++ ++t_gssapi_cli_SOURCES = \ ++ t_common.c \ ++ t_gssapi_cli.c ++ ++t_gssapi_cli_LDADD = $(COMMON_LDADD) ++ ++t_gssapi_srv_SOURCES = \ ++ t_common.c \ ++ t_gssapi_srv.c ++ ++t_gssapi_srv_LDADD = $(COMMON_LDADD) ++ ++check_PROGRAMS = \ ++ t_gssapi_cli \ ++ t_gssapi_srv \ ++ $(NULL) ++ ++noinst_PROGRAMS = $(check_PROGRAMS) ++ ++EXTRA_DIST = \ ++ runtests.py \ ++ $(NULL) ++ ++all: $(check_PROGRAMS) ++ ++check: ++if MACOSX ++# skip Mac OSX for now ++else ++ $(srcdir)/runtests.py $(CHECKARGS) ++endif +diff --git a/tests/runtests.py b/tests/runtests.py +new file mode 100755 +index 0000000..f645adf +--- /dev/null ++++ b/tests/runtests.py +@@ -0,0 +1,179 @@ ++#!/usr/bin/python3 ++ ++import argparse ++import os ++import shutil ++import signal ++import subprocess ++import time ++from string import Template ++ ++ ++def setup_socket_wrappers(testdir): ++ """ Try to set up socket wrappers """ ++ wrapdir = os.path.join(testdir, 'w') ++ os.makedirs(wrapdir) ++ ++ wrappers = subprocess.Popen(['pkg-config', '--exists', 'socket_wrapper']) ++ wrappers.wait() ++ if wrappers.returncode != 0: ++ raise Exception('Socket Wrappers not available') ++ ++ wrappers = subprocess.Popen(['pkg-config', '--exists', 'nss_wrapper']) ++ wrappers.wait() ++ if wrappers.returncode != 0: ++ raise Exception('NSS Wrappers not available') ++ ++ hosts = os.path.join(wrapdir, 'hosts') ++ with open(hosts, 'w+') as conffile: ++ conffile.write('127.0.0.9 host.realm.test') ++ ++ return {'LD_PRELOAD': 'libsocket_wrapper.so libnss_wrapper.so', ++ 'SOCKET_WRAPPER_DIR': wrapdir, ++ 'SOCKET_WRAPPER_DEFAULT_IFACE': '9', ++ 'NSS_WRAPPER_HOSTNAME': 'host.realm.test', ++ 'NSS_WRAPPER_HOSTS': hosts} ++ ++ ++KERBEROS_CONF = ''' ++[libdefaults] ++ default_realm = REALM.TEST ++ dns_lookup_realm = false ++ dns_lookup_kdc = false ++ rdns = false ++ ticket_lifetime = 24h ++ forwardable = yes ++ default_ccache_name = FILE://${TESTDIR}/ccache ++ udp_preference_limit = 1 ++ ++[domain_realm] ++ .realm.test = REALM.TEST ++ realm.test = REALM.TEST ++ ++[realms] ++ REALM.TEST = { ++ kdc = 127.0.0.9 ++ admin_server = 127.0.0.9 ++ acl_file = ${TESTDIR}/kadm.acl ++ dict_file = /usr/share/dict/words ++ admin_keytab = ${TESTDIR}/kadm.keytab ++ database_name = ${TESTDIR}/kdc.db ++ key_stash_file = ${TESTDIR}/kdc.stash ++ } ++ ++[kdcdefaults] ++ kdc_ports = 88 ++ kdc_tcp_ports = 88 ++ ++[logging] ++ kdc = FILE:${TESTDIR}/kdc.log ++ admin_server = FILE:${TESTDIR}/kadm.log ++ default = FILE:${TESTDIR}/krb5.log ++''' ++ ++ ++def setup_kdc(testdir, env): ++ """ Setup KDC and start process """ ++ krbconf = os.path.join(testdir, 'krb.conf') ++ env['KRB5_CONFIG'] = krbconf ++ ++ kenv = {'KRB5_KDC_PROFILE': krbconf, ++ 'PATH': '/sbin:/bin:/usr/sbin:/usr/bin'} ++ kenv.update(env) ++ ++ # KDC/KRB5 CONFIG ++ templ = Template(KERBEROS_CONF) ++ text = templ.substitute({'TESTDIR': testdir}) ++ with open(krbconf, 'w+') as conffile: ++ conffile.write(text) ++ ++ testlog = os.path.join(testdir, 'kdc.log') ++ log = open(testlog, 'a') ++ ++ subprocess.check_call([ ++ "kdb5_util", "create", ++ "-r", "REALM.TEST", "-s", "-P", "password" ++ ], stdout=log, stderr=log, env=kenv, timeout=5) ++ ++ kdc = subprocess.Popen(['krb5kdc', '-n'], env=kenv, preexec_fn=os.setsid) ++ time.sleep(5) ++ ++ # Add a user and genrate a keytab ++ keytab = os.path.join(testdir, "user.keytab") ++ subprocess.check_call([ ++ "kadmin.local", "-q", ++ "addprinc -randkey user" ++ ], stdout=log, stderr=log, env=kenv, timeout=5) ++ ++ subprocess.check_call([ ++ "kadmin.local", "-q", ++ "ktadd -k {} user".format(keytab) ++ ], stdout=log, stderr=log, env=kenv, timeout=5) ++ env['KRB5_CLIENT_KTNAME'] = keytab ++ ++ # Add a service and genrate a keytab ++ keytab = os.path.join(testdir, "test.keytab") ++ subprocess.check_call([ ++ "kadmin.local", "-q", ++ "addprinc -randkey test/host.realm.test" ++ ], stdout=log, stderr=log, env=kenv, timeout=5) ++ ++ subprocess.check_call([ ++ "kadmin.local", "-q", ++ "ktadd -k {} test/host.realm.test".format(keytab) ++ ], stdout=log, stderr=log, env=kenv, timeout=5) ++ env['KRB5_KTNAME'] = keytab ++ ++ return kdc, env ++ ++ ++def gssapi_tests(testdir): ++ """ SASL/GSSAPI Tests """ ++ env = setup_socket_wrappers(testdir) ++ kdc, kenv = setup_kdc(testdir, env) ++ #print("KDC: {}, ENV: {}".format(kdc, kenv)) ++ kenv['KRB5_TRACE'] = os.path.join(testdir, 'trace.log') ++ ++ try: ++ srv = subprocess.Popen(["../tests/t_gssapi_srv"], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ srv.stdout.readline() # Wait for srv to say it is ready ++ cli = subprocess.Popen(["../tests/t_gssapi_cli"], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ try: ++ cli.wait(timeout=5) ++ srv.wait(timeout=5) ++ except Exception as e: ++ print("Failed on {}".format(e)); ++ cli.kill() ++ srv.kill() ++ if cli.returncode != 0 or srv.returncode != 0: ++ raise Exception("CLI ({}): {} --> SRV ({}): {}".format( ++ cli.returncode, cli.stderr.read().decode('utf-8'), ++ srv.returncode, srv.stderr.read().decode('utf-8'))) ++ except Exception as e: ++ print("FAIL: {}".format(e)) ++ ++ print("PASS: CLI({}) SRV({})".format( ++ cli.stdout.read().decode('utf-8').strip(), ++ srv.stdout.read().decode('utf-8').strip())) ++ ++ os.killpg(kdc.pid, signal.SIGTERM) ++ ++ ++if __name__ == "__main__": ++ ++ P = argparse.ArgumentParser(description='Cyrus SASL Tests') ++ P.add_argument('--testdir', default=os.path.join(os.getcwd(), '.tests'), ++ help="Directory for running tests") ++ A = vars(P.parse_args()) ++ ++ T = A['testdir'] ++ ++ if os.path.exists(T): ++ shutil.rmtree(T) ++ os.makedirs(T) ++ ++ gssapi_tests(T) +diff --git a/tests/t_common.c b/tests/t_common.c +new file mode 100644 +index 0000000..7168b2f +--- /dev/null ++++ b/tests/t_common.c +@@ -0,0 +1,68 @@ ++/* TBD, add (C) */ ++ ++#include ++ ++void s_error(const char *hdr, ssize_t ret, ssize_t len, int err) ++{ ++ fprintf(stderr, "%s l:%ld/%ld [%d] %s", ++ hdr, ret, len, err, strerror(err)); ++ exit(-1); ++} ++ ++void send_string(int sd, const char *s, unsigned int l) ++{ ++ ssize_t ret; ++ ++fprintf(stderr, "s:%u ", l); ++fflush(stderr); ++ ++ ret = send(sd, &l, sizeof(l), 0); ++ if (ret != sizeof(l)) s_error("send size", ret, sizeof(l), errno); ++ ++ if (l == 0) return; ++ ++ ret = send(sd, s, l, 0); ++ if (ret != l) s_error("send data", ret, l, errno); ++} ++ ++void recv_string(int sd, char *buf, unsigned int *buflen) ++{ ++ unsigned int l; ++ ssize_t ret; ++ ++ ret = recv(sd, &l, sizeof(l), MSG_WAITALL); ++ if (ret != sizeof(l)) s_error("recv size", ret, sizeof(l), errno); ++ ++ if (l == 0) { ++fprintf(stderr, "r:0 "); ++fflush(stderr); ++ *buflen = 0; ++ return; ++ } ++ ++ if (*buflen < l) s_error("recv len", l, *buflen, E2BIG); ++ ++ ret = recv(sd, buf, l, 0); ++ if (ret != l) s_error("recv data", ret, l, errno); ++ ++fprintf(stderr, "r:%ld ", ret); ++fflush(stderr); ++ *buflen = ret; ++} ++ ++void saslerr(int why, const char *what) ++{ ++ fprintf(stderr, "%s: %s", what, sasl_errstring(why, NULL, NULL)); ++} ++ ++int getpath(void *context __attribute__((unused)), const char **path) ++{ ++ if (! path) { ++ return SASL_BADPARAM; ++ } ++ ++ *path = PLUGINDIR; ++ return SASL_OK; ++} ++ ++ +diff --git a/tests/t_common.h b/tests/t_common.h +new file mode 100644 +index 0000000..4ee1976 +--- /dev/null ++++ b/tests/t_common.h +@@ -0,0 +1,15 @@ ++/* TBD, add (C) */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++ ++#include ++ ++void s_error(const char *hdr, ssize_t ret, ssize_t len, int err); ++void send_string(int sd, const char *s, unsigned int l); ++void recv_string(int sd, char *buf, unsigned int *buflen); ++void saslerr(int why, const char *what); ++int getpath(void *context __attribute__((unused)), const char **path); +diff --git a/tests/t_gssapi_cli.c b/tests/t_gssapi_cli.c +new file mode 100644 +index 0000000..c833c05 +--- /dev/null ++++ b/tests/t_gssapi_cli.c +@@ -0,0 +1,95 @@ ++/* TBD, add (C) */ ++ ++#include "t_common.h" ++ ++#include ++#include ++#include ++#include ++ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++ ++#include ++#include ++ ++static int setup_socket(void) ++{ ++ struct sockaddr_in addr; ++ int sock, ret; ++ ++ sock = socket(AF_INET, SOCK_STREAM, 0); ++ if (sock < 0) s_error("socket", 0, 0, errno); ++ ++ addr.sin_family = AF_INET; ++ addr.sin_addr.s_addr = inet_addr("127.0.0.9"); ++ addr.sin_port = htons(9000); ++ ++ ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr)); ++ if (ret != 0) s_error("connect", 0, 0, errno); ++ ++ return sock; ++} ++ ++int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) ++{ ++ sasl_callback_t callbacks[2] = {}; ++ char buf[8192]; ++ const char *chosenmech; ++ sasl_conn_t *conn; ++ const char *data; ++ unsigned int len; ++ int sd; ++ int r; ++ ++ /* initialize the sasl library */ ++ callbacks[0].id = SASL_CB_GETPATH; ++ callbacks[0].proc = (sasl_callback_ft)&getpath; ++ callbacks[0].context = NULL; ++ callbacks[1].id = SASL_CB_LIST_END; ++ callbacks[1].proc = NULL; ++ callbacks[1].context = NULL; ++ ++ r = sasl_client_init(callbacks); ++ if (r != SASL_OK) exit(-1); ++ ++ r = sasl_client_new("test", "host.realm.test", NULL, NULL, NULL, 0, &conn); ++ if (r != SASL_OK) { ++ saslerr(r, "allocating connection state"); ++ exit(-1); ++ } ++ ++ r = sasl_client_start(conn, "GSSAPI", NULL, &data, &len, &chosenmech); ++ if (r != SASL_OK && r != SASL_CONTINUE) { ++ saslerr(r, "starting SASL negotiation"); ++ printf("\n%s\n", sasl_errdetail(conn)); ++ exit(-1); ++ } ++ ++ sd = setup_socket(); ++ ++ while (r == SASL_CONTINUE) { ++ send_string(sd, data, len); ++ len = 8192; ++ recv_string(sd, buf, &len); ++ ++ r = sasl_client_step(conn, buf, len, NULL, &data, &len); ++ if (r != SASL_OK && r != SASL_CONTINUE) { ++ saslerr(r, "performing SASL negotiation"); ++ printf("\n%s\n", sasl_errdetail(conn)); ++ exit(-1); ++ } ++ } ++ ++ if (r != SASL_OK) exit(-1); ++ ++ if (len > 0) { ++ send_string(sd, data, len); ++ } ++ ++ fprintf(stdout, "DONE\n"); ++ fflush(stdout); ++ return 0; ++} ++ +diff --git a/tests/t_gssapi_srv.c b/tests/t_gssapi_srv.c +new file mode 100644 +index 0000000..29f538d +--- /dev/null ++++ b/tests/t_gssapi_srv.c +@@ -0,0 +1,111 @@ ++/* TBD, add (C) */ ++ ++#include "t_common.h" ++ ++#include ++#include ++#include ++#include ++ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++ ++#include ++#include ++ ++static int setup_socket(void) ++{ ++ struct sockaddr_in addr; ++ int sock, ret, sd; ++ ++ sock = socket(AF_INET, SOCK_STREAM, 0); ++ if (sock < 0) s_error("socket", 0, 0, errno); ++ ++ addr.sin_family = AF_INET; ++ addr.sin_addr.s_addr = inet_addr("127.0.0.9"); ++ addr.sin_port = htons(9000); ++ ++ ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); ++ if (ret != 0) s_error("bind", 0, 0, errno); ++ ++ ret = listen(sock, 1); ++ if (ret != 0) s_error("listen", 0, 0, errno); ++ ++ /* signal we are ready */ ++ fprintf(stdout, "READY\n"); ++ fflush(stdout); ++ ++ /* block until the client connects */ ++ sd = accept(sock, NULL, NULL); ++ if (sd < 0) s_error("accept", 0, 0, errno); ++ ++ close(sock); ++ return sd; ++} ++ ++int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) ++{ ++ sasl_callback_t callbacks[2] = {}; ++ char buf[8192]; ++ sasl_conn_t *conn; ++ const char *data; ++ unsigned int len; ++ int sd; ++ int r; ++ ++ /* initialize the sasl library */ ++ callbacks[0].id = SASL_CB_GETPATH; ++ callbacks[0].proc = (sasl_callback_ft)&getpath; ++ callbacks[0].context = NULL; ++ callbacks[1].id = SASL_CB_LIST_END; ++ callbacks[1].proc = NULL; ++ callbacks[1].context = NULL; ++ ++ r = sasl_server_init(callbacks, "t_gssapi_srv"); ++ if (r != SASL_OK) exit(-1); ++ ++ r = sasl_server_new("test", "host.realm.test", NULL, NULL, NULL, ++ callbacks, 0, &conn); ++ if (r != SASL_OK) { ++ saslerr(r, "allocating connection state"); ++ exit(-1); ++ } ++ ++ sd = setup_socket(); ++ ++ len = 8192; ++ recv_string(sd, buf, &len); ++ ++ r = sasl_server_start(conn, "GSSAPI", buf, len, &data, &len); ++ if (r != SASL_OK && r != SASL_CONTINUE) { ++ saslerr(r, "starting SASL negotiation"); ++ printf("\n%s\n", sasl_errdetail(conn)); ++ exit(-1); ++ } ++ ++ while (r == SASL_CONTINUE) { ++ send_string(sd, data, len); ++ len = 8192; ++ recv_string(sd, buf, &len); ++ ++ r = sasl_server_step(conn, buf, len, &data, &len); ++ if (r != SASL_OK && r != SASL_CONTINUE) { ++ saslerr(r, "performing SASL negotiation"); ++ printf("\n%s\n", sasl_errdetail(conn)); ++ exit(-1); ++ } ++ ++ } ++ ++ if (r != SASL_OK) exit(-1); ++ ++ if (len > 0) { ++ send_string(sd, data, len); ++ } ++ ++ fprintf(stdout, "DONE\n"); ++ fflush(stdout); ++ return 0; ++} ++ +-- +2.18.2 + diff --git a/cyrus-sasl-2.1.27-Add-support-for-setting-max-ssf-0-to-GSS-SPNEGO.patch b/cyrus-sasl-2.1.27-Add-support-for-setting-max-ssf-0-to-GSS-SPNEGO.patch new file mode 100644 index 0000000..c8c4a79 --- /dev/null +++ b/cyrus-sasl-2.1.27-Add-support-for-setting-max-ssf-0-to-GSS-SPNEGO.patch @@ -0,0 +1,435 @@ +From 49e965f41257a0ed299c58a7cf1c120ddf944aaa Mon Sep 17 00:00:00 2001 +From: Simo Sorce +Date: Tue, 5 May 2020 14:51:36 -0400 +Subject: [PATCH] Add support for setting max ssf 0 to GSS-SPNEGO + +Bacport form this proposed PR (still open at bacport time): +https://github.com/cyrusimap/cyrus-sasl/pull/603 + +Signed-off-by: Simo Sorce +--- + m4/sasl2.m4 | 13 +++++++ + plugins/gssapi.c | 44 ++++++++++++++++++++- + tests/runtests.py | 91 ++++++++++++++++++++++++++++++++++++++++---- + tests/t_common.c | 13 ++++--- + tests/t_common.h | 3 +- + tests/t_gssapi_cli.c | 25 ++++++++++-- + tests/t_gssapi_srv.c | 28 +++++++++++--- + 7 files changed, 194 insertions(+), 23 deletions(-) + +diff --git a/m4/sasl2.m4 b/m4/sasl2.m4 +index 56e0504..6effe99 100644 +--- a/m4/sasl2.m4 ++++ b/m4/sasl2.m4 +@@ -287,6 +287,19 @@ if test "$gssapi" != no; then + AC_CHECK_FUNCS(gss_oid_equal) + LIBS="$cmu_save_LIBS" + ++ cmu_save_LIBS="$LIBS" ++ LIBS="$LIBS $GSSAPIBASE_LIBS" ++ if test "$ac_cv_header_gssapi_gssapi_krb5_h" = "yes"; then ++ AC_CHECK_DECL(GSS_KRB5_CRED_NO_CI_FLAGS_X, ++ [AC_DEFINE(HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X,1, ++ [Define if your GSSAPI implementation supports GSS_KRB5_CRED_NO_CI_FLAGS_X])],, ++ [ ++ AC_INCLUDES_DEFAULT ++ #include ++ ]) ++ fi ++ LIBS="$cmu_save_LIBS" ++ + cmu_save_LIBS="$LIBS" + LIBS="$LIBS $GSSAPIBASE_LIBS" + AC_CHECK_FUNCS(gss_get_name_attribute) +diff --git a/plugins/gssapi.c b/plugins/gssapi.c +index 5d900c5..7480316 100644 +--- a/plugins/gssapi.c ++++ b/plugins/gssapi.c +@@ -1783,7 +1783,49 @@ static int gssapi_client_mech_step(void *conn_context, + /* We want to try for privacy */ + req_flags |= GSS_C_CONF_FLAG; + } +- } ++#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X ++ /* The krb5 mechanism automatically adds INTEG and CONF flags even when ++ * not specified, this has the effect of rendering explicit requests ++ * of no confidentiality and integrity via setting maxssf 0 moot. ++ * However to interoperate with Windows machines it needs to be ++ * possible to unset these flags as Windows machines refuse to allow ++ * two layers (say TLS and GSSAPI) to both provide these services. ++ * So if we do not suppress these flags a SASL/GSS-SPNEGO negotiation ++ * over, say, LDAPS will fail against Windows Servers */ ++ } else if (params->props.max_ssf == 0) { ++ gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER; ++ if (client_creds == GSS_C_NO_CREDENTIAL) { ++ gss_OID_set_desc mechs = { 0 }; ++ gss_OID_set desired_mechs = GSS_C_NO_OID_SET; ++ if (text->mech_type != GSS_C_NO_OID) { ++ mechs.count = 1; ++ mechs.elements = text->mech_type; ++ desired_mechs = &mechs; ++ } ++ ++ maj_stat = gss_acquire_cred(&min_stat, GSS_C_NO_NAME, ++ GSS_C_INDEFINITE, desired_mechs, ++ GSS_C_INITIATE, ++ &text->client_creds, NULL, NULL); ++ if (GSS_ERROR(maj_stat)) { ++ sasl_gss_seterror(text->utils, maj_stat, min_stat); ++ sasl_gss_free_context_contents(text); ++ return SASL_FAIL; ++ } ++ client_creds = text->client_creds; ++ } ++ ++ maj_stat = gss_set_cred_option(&min_stat, &client_creds, ++ (gss_OID)GSS_KRB5_CRED_NO_CI_FLAGS_X, ++ &empty_buffer); ++ if (GSS_ERROR(maj_stat)) { ++ sasl_gss_seterror(text->utils, maj_stat, min_stat); ++ sasl_gss_free_context_contents(text); ++ return SASL_FAIL; ++ } ++#endif ++ } ++ + + if (params->props.security_flags & SASL_SEC_PASS_CREDENTIALS) { + req_flags = req_flags | GSS_C_DELEG_FLAG; +diff --git a/tests/runtests.py b/tests/runtests.py +index fc9cf24..4106401 100755 +--- a/tests/runtests.py ++++ b/tests/runtests.py +@@ -6,6 +6,7 @@ import os + import shutil + import signal + import subprocess ++import sys + import time + from string import Template + +@@ -149,11 +150,12 @@ def gssapi_basic_test(kenv): + srv.returncode, srv.stderr.read().decode('utf-8'))) + except Exception as e: + print("FAIL: {}".format(e)) +- return ++ return 1 + + print("PASS: CLI({}) SRV({})".format( + cli.stdout.read().decode('utf-8').strip(), + srv.stdout.read().decode('utf-8').strip())) ++ return 0 + + def gssapi_channel_binding_test(kenv): + try: +@@ -178,11 +180,12 @@ def gssapi_channel_binding_test(kenv): + srv.returncode, srv.stderr.read().decode('utf-8'))) + except Exception as e: + print("FAIL: {}".format(e)) +- return ++ return 1 + + print("PASS: CLI({}) SRV({})".format( + cli.stdout.read().decode('utf-8').strip(), + srv.stdout.read().decode('utf-8').strip())) ++ return 0 + + def gssapi_channel_binding_mismatch_test(kenv): + result = "FAIL" +@@ -212,11 +215,70 @@ def gssapi_channel_binding_mismatch_test(kenv): + cli.returncode, cli_err, srv.returncode, srv_err)) + except Exception as e: + print("{}: {}".format(result, e)) +- return ++ return 0 + + print("FAIL: This test should fail [CLI({}) SRV({})]".format( + cli.stdout.read().decode('utf-8').strip(), + srv.stdout.read().decode('utf-8').strip())) ++ return 1 ++ ++def gss_spnego_basic_test(kenv): ++ try: ++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-N"], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ srv.stdout.readline() # Wait for srv to say it is ready ++ cli = subprocess.Popen(["../tests/t_gssapi_cli", "-N"], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ try: ++ cli.wait(timeout=5) ++ srv.wait(timeout=5) ++ except Exception as e: ++ print("Failed on {}".format(e)); ++ cli.kill() ++ srv.kill() ++ if cli.returncode != 0 or srv.returncode != 0: ++ raise Exception("CLI ({}): {} --> SRV ({}): {}".format( ++ cli.returncode, cli.stderr.read().decode('utf-8'), ++ srv.returncode, srv.stderr.read().decode('utf-8'))) ++ except Exception as e: ++ print("FAIL: {}".format(e)) ++ return 1 ++ ++ print("PASS: CLI({}) SRV({})".format( ++ cli.stdout.read().decode('utf-8').strip(), ++ srv.stdout.read().decode('utf-8').strip())) ++ return 0 ++ ++def gss_spnego_zeromaxssf_test(kenv): ++ try: ++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-N", "-z"], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ srv.stdout.readline() # Wait for srv to say it is ready ++ cli = subprocess.Popen(["../tests/t_gssapi_cli", "-N", "-z"], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=kenv) ++ try: ++ cli.wait(timeout=5) ++ srv.wait(timeout=5) ++ except Exception as e: ++ print("Failed on {}".format(e)); ++ cli.kill() ++ srv.kill() ++ if cli.returncode != 0 or srv.returncode != 0: ++ raise Exception("CLI ({}): {} --> SRV ({}): {}".format( ++ cli.returncode, cli.stderr.read().decode('utf-8'), ++ srv.returncode, srv.stderr.read().decode('utf-8'))) ++ except Exception as e: ++ print("FAIL: {}".format(e)) ++ return 1 ++ ++ print("PASS: CLI({}) SRV({})".format( ++ cli.stdout.read().decode('utf-8').strip(), ++ srv.stdout.read().decode('utf-8').strip())) ++ return 0 + + def gssapi_tests(testdir): + """ SASL/GSSAPI Tests """ +@@ -225,20 +287,32 @@ def gssapi_tests(testdir): + #print("KDC: {}, ENV: {}".format(kdc, kenv)) + kenv['KRB5_TRACE'] = os.path.join(testdir, 'trace.log') + ++ err = 0 ++ + print('GSSAPI BASIC:') + print(' ', end='') +- gssapi_basic_test(kenv) ++ err += gssapi_basic_test(kenv) + + print('GSSAPI CHANNEL BINDING:') + print(' ', end='') +- gssapi_channel_binding_test(kenv) ++ err += gssapi_channel_binding_test(kenv) + + print('GSSAPI CHANNEL BINDING MISMTACH:') + print(' ', end='') +- gssapi_channel_binding_mismatch_test(kenv) ++ err += gssapi_channel_binding_mismatch_test(kenv) ++ ++ print('GSS-SPNEGO BASIC:') ++ print(' ', end='') ++ err += gss_spnego_basic_test(kenv) ++ ++ print('GSS-SPNEGO 0 MAXSSF:') ++ print(' ', end='') ++ err += gss_spnego_zeromaxssf_test(kenv) + + os.killpg(kdc.pid, signal.SIGTERM) + ++ return err ++ + + if __name__ == "__main__": + +@@ -253,4 +327,7 @@ if __name__ == "__main__": + shutil.rmtree(T) + os.makedirs(T) + +- gssapi_tests(T) ++ err = gssapi_tests(T) ++ if err != 0: ++ print('{} test(s) FAILED'.format(err)) ++ sys.exit(-1) +diff --git a/tests/t_common.c b/tests/t_common.c +index 478e6a1..f56098e 100644 +--- a/tests/t_common.c ++++ b/tests/t_common.c +@@ -23,20 +23,21 @@ void send_string(int sd, const char *s, unsigned int l) + if (ret != l) s_error("send data", ret, l, errno); + } + +-void recv_string(int sd, char *buf, unsigned int *buflen) ++void recv_string(int sd, char *buf, unsigned int *buflen, bool allow_eof) + { ++ unsigned int bufsize = *buflen; + unsigned int l; + ssize_t ret; + ++ *buflen = 0; ++ + ret = recv(sd, &l, sizeof(l), MSG_WAITALL); ++ if (allow_eof && ret == 0) return; + if (ret != sizeof(l)) s_error("recv size", ret, sizeof(l), errno); + +- if (l == 0) { +- *buflen = 0; +- return; +- } ++ if (l == 0) return; + +- if (*buflen < l) s_error("recv len", l, *buflen, E2BIG); ++ if (bufsize < l) s_error("recv len", l, bufsize, E2BIG); + + ret = recv(sd, buf, l, 0); + if (ret != l) s_error("recv data", ret, l, errno); +diff --git a/tests/t_common.h b/tests/t_common.h +index a10def1..be24a53 100644 +--- a/tests/t_common.h ++++ b/tests/t_common.h +@@ -4,6 +4,7 @@ + #include "config.h" + + #include ++#include + #include + #include + +@@ -12,7 +13,7 @@ + + void s_error(const char *hdr, ssize_t ret, ssize_t len, int err); + void send_string(int sd, const char *s, unsigned int l); +-void recv_string(int sd, char *buf, unsigned int *buflen); ++void recv_string(int sd, char *buf, unsigned int *buflen, bool allow_eof); + void saslerr(int why, const char *what); + int getpath(void *context __attribute__((unused)), const char **path); + void parse_cb(sasl_channel_binding_t *cb, char *buf, unsigned max, char *in); +diff --git a/tests/t_gssapi_cli.c b/tests/t_gssapi_cli.c +index a44a3f5..d9eafe1 100644 +--- a/tests/t_gssapi_cli.c ++++ b/tests/t_gssapi_cli.c +@@ -46,12 +46,21 @@ int main(int argc, char *argv[]) + char cb_buf[256]; + int sd; + int c, r; ++ const char *sasl_mech = "GSSAPI"; ++ bool spnego = false; ++ bool zeromaxssf = false; + +- while ((c = getopt(argc, argv, "c:")) != EOF) { ++ while ((c = getopt(argc, argv, "c:zN")) != EOF) { + switch (c) { + case 'c': + parse_cb(&cb, cb_buf, 256, optarg); + break; ++ case 'z': ++ zeromaxssf = true; ++ break; ++ case 'N': ++ spnego = true; ++ break; + default: + break; + } +@@ -78,7 +87,17 @@ int main(int argc, char *argv[]) + sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); + } + +- r = sasl_client_start(conn, "GSSAPI", NULL, &data, &len, &chosenmech); ++ if (spnego) { ++ sasl_mech = "GSS-SPNEGO"; ++ } ++ ++ if (zeromaxssf) { ++ /* set all security properties to 0 including maxssf */ ++ sasl_security_properties_t secprops = { 0 }; ++ sasl_setprop(conn, SASL_SEC_PROPS, &secprops); ++ } ++ ++ r = sasl_client_start(conn, sasl_mech, NULL, &data, &len, &chosenmech); + if (r != SASL_OK && r != SASL_CONTINUE) { + saslerr(r, "starting SASL negotiation"); + printf("\n%s\n", sasl_errdetail(conn)); +@@ -90,7 +109,7 @@ int main(int argc, char *argv[]) + while (r == SASL_CONTINUE) { + send_string(sd, data, len); + len = 8192; +- recv_string(sd, buf, &len); ++ recv_string(sd, buf, &len, false); + + r = sasl_client_step(conn, buf, len, NULL, &data, &len); + if (r != SASL_OK && r != SASL_CONTINUE) { +diff --git a/tests/t_gssapi_srv.c b/tests/t_gssapi_srv.c +index ef1217f..448a218 100644 +--- a/tests/t_gssapi_srv.c ++++ b/tests/t_gssapi_srv.c +@@ -56,12 +56,21 @@ int main(int argc, char *argv[]) + unsigned char cb_buf[256]; + int sd; + int c, r; ++ const char *sasl_mech = "GSSAPI"; ++ bool spnego = false; ++ bool zeromaxssf = false; + +- while ((c = getopt(argc, argv, "c:")) != EOF) { ++ while ((c = getopt(argc, argv, "c:zN")) != EOF) { + switch (c) { + case 'c': + parse_cb(&cb, cb_buf, 256, optarg); + break; ++ case 'z': ++ zeromaxssf = true; ++ break; ++ case 'N': ++ spnego = true; ++ break; + default: + break; + } +@@ -90,12 +99,22 @@ int main(int argc, char *argv[]) + sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); + } + ++ if (spnego) { ++ sasl_mech = "GSS-SPNEGO"; ++ } ++ ++ if (zeromaxssf) { ++ /* set all security properties to 0 including maxssf */ ++ sasl_security_properties_t secprops = { 0 }; ++ sasl_setprop(conn, SASL_SEC_PROPS, &secprops); ++ } ++ + sd = setup_socket(); + + len = 8192; +- recv_string(sd, buf, &len); ++ recv_string(sd, buf, &len, false); + +- r = sasl_server_start(conn, "GSSAPI", buf, len, &data, &len); ++ r = sasl_server_start(conn, sasl_mech, buf, len, &data, &len); + if (r != SASL_OK && r != SASL_CONTINUE) { + saslerr(r, "starting SASL negotiation"); + printf("\n%s\n", sasl_errdetail(conn)); +@@ -105,7 +124,7 @@ int main(int argc, char *argv[]) + while (r == SASL_CONTINUE) { + send_string(sd, data, len); + len = 8192; +- recv_string(sd, buf, &len); ++ recv_string(sd, buf, &len, true); + + r = sasl_server_step(conn, buf, len, &data, &len); + if (r != SASL_OK && r != SASL_CONTINUE) { +@@ -113,7 +132,6 @@ int main(int argc, char *argv[]) + printf("\n%s\n", sasl_errdetail(conn)); + exit(-1); + } +- + } + + if (r != SASL_OK) exit(-1); +-- +2.18.2 + diff --git a/cyrus-sasl-2.1.27-Emit-debug-log-only-in-case-of-errors.patch b/cyrus-sasl-2.1.27-Emit-debug-log-only-in-case-of-errors.patch new file mode 100644 index 0000000..d5e1334 --- /dev/null +++ b/cyrus-sasl-2.1.27-Emit-debug-log-only-in-case-of-errors.patch @@ -0,0 +1,42 @@ +From ec070b2e83a4ee698c08d6d68c205aea4d90b0bb Mon Sep 17 00:00:00 2001 +From: Simo Sorce +Date: Tue, 5 May 2020 14:31:10 -0400 +Subject: [PATCH] Emit debug log only in case of errors + +Backport of commit id: +ccc5e547d4b40ee2b182a9945f8f6cc10b4fdf48 + +Signed-off-by: Simo Sorce +--- + plugins/gssapi.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/plugins/gssapi.c b/plugins/gssapi.c +index 7480316..6bcd78e 100644 +--- a/plugins/gssapi.c ++++ b/plugins/gssapi.c +@@ -1444,9 +1444,6 @@ gssapi_server_mech_step(void *conn_context, + + if (text == NULL) return SASL_BADPROT; + +- params->utils->log(params->utils->conn, SASL_LOG_DEBUG, +- "GSSAPI server step %d\n", text->state); +- + switch (text->state) { + + case SASL_GSSAPI_STATE_AUTHNEG: +@@ -1496,8 +1493,10 @@ gssapi_server_mech_step(void *conn_context, + } + + oparams->doneflag = 1; ++ } else { ++ params->utils->log(params->utils->conn, SASL_LOG_DEBUG, ++ "GSSAPI server step failed: %d\n", text->state); + } +- + return ret; + } + +-- +2.18.2 + diff --git a/cyrus-sasl.spec b/cyrus-sasl.spec index e33533a..cbf7e6f 100644 --- a/cyrus-sasl.spec +++ b/cyrus-sasl.spec @@ -8,7 +8,7 @@ Summary: The Cyrus SASL library Name: cyrus-sasl Version: 2.1.27 -Release: 4%{?dist} +Release: 5%{?dist} License: BSD with advertising URL: https://www.cyrusimap.org/sasl/ @@ -34,6 +34,10 @@ Patch49: cyrus-sasl-2.1.26-md5global.patch Patch60: cyrus-sasl-pr559-RC4-openssl.patch Patch100: cyrus-sasl-cve-2019-19906.patch +Patch101: cyrus-sasl-2.1.27-Add-basic-test-infrastructure.patch +Patch102: cyrus-sasl-2.1.27-Add-Channel-Binding-support-for-GSSAPI-GSS-SPNEGO.patch +Patch103: cyrus-sasl-2.1.27-Add-support-for-setting-max-ssf-0-to-GSS-SPNEGO.patch +Patch104: cyrus-sasl-2.1.27-Emit-debug-log-only-in-case-of-errors.patch BuildRequires: autoconf, automake, libtool, gdbm-devel, groff BuildRequires: krb5-devel >= 1.2.2, openssl-devel, pam-devel, pkgconfig @@ -42,6 +46,8 @@ BuildRequires: libdb-devel %if ! %{bootstrap_cyrus_sasl} BuildRequires: openldap-devel %endif +#build reqs for make check +BuildRequires: python36 nss_wrapper socket_wrapper krb5-server %{?systemd_requires} Requires(pre): /usr/sbin/useradd /usr/sbin/groupadd Requires(postun): /usr/sbin/userdel /usr/sbin/groupdel @@ -154,6 +160,10 @@ the GS2 authentication scheme. %patch49 -p1 -b .md5global.h %patch60 -p1 -b .openssl_rc4 %patch100 -p1 -b .cve_2019_19906 +%patch101 -p1 -b .tests +%patch102 -p1 -b .gssapi_cbs +%patch103 -p1 -b .maxssf0 +%patch104 -p1 -b .nolog %build # reconfigure @@ -285,6 +295,8 @@ rm -f $RPM_BUILD_ROOT%{_libdir}/sasl2/*.la rm -f $RPM_BUILD_ROOT%{_libdir}/*.la rm -f $RPM_BUILD_ROOT%{_mandir}/cat8/saslauthd.8 +%check +make check %{?_smp_mflags} %pre getent group %{username} >/dev/null || groupadd -g 76 -r %{username} @@ -360,6 +372,11 @@ getent passwd %{username} >/dev/null || useradd -r -g %{username} -d %{homedir} %{_sbindir}/sasl2-shared-mechlist %changelog +* Thu May 5 2020 Simo Sorce - 2.1.27-5 +- Backport GSSAPI Channel Bindings support +- Add support for setting maxssf=0 in GSS-SPNEGO +- Reduce excessive GSSAPI plugin logging + * Thu Mar 19 2020 Simo Sorce - 2.1.27-4 - Fix CVE 2019 19906