diff --git a/backport-add-testing-cases.patch b/backport-add-testing-cases.patch new file mode 100644 index 0000000000000000000000000000000000000000..7dd05cf32a7e21423bbf887707b1b9d51fb1d59d --- /dev/null +++ b/backport-add-testing-cases.patch @@ -0,0 +1,4389 @@ +From c810b17993f65ae71f734ca077aed58e5bceb967 Mon Sep 17 00:00:00 2001 +From: root +Date: Thu, 1 Sep 2022 12:03:04 +0800 +Subject: [PATCH] add testing cases + +--- + 0001-add-testing-cases.patch | 3289 ++++++++++++++++++++++++++++++++++ + Makefile.am | 2 +- + configure.ac | 3 +- + tests/Makefile.am | 79 + + tests/runtests.py | 425 +++++ + tests/t_common.c | 77 + + tests/t_common.h | 19 + + tests/t_gssapi_cli.c | 206 +++ + tests/t_gssapi_srv.c | 198 ++ + 9 files changed, 4296 insertions(+), 2 deletions(-) + create mode 100644 0001-add-testing-cases.patch + 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/0001-add-testing-cases.patch b/0001-add-testing-cases.patch +new file mode 100644 +index 0000000..06dfa37 +--- /dev/null ++++ b/0001-add-testing-cases.patch +@@ -0,0 +1,3289 @@ ++From 2f34ca083ac5c1dbc5a84b8e7dd1ca2194b4a081 Mon Sep 17 00:00:00 2001 ++From: root ++Date: Thu, 1 Sep 2022 11:56:10 +0800 ++Subject: [PATCH] add testing cases ++ ++--- ++ 0001-add-testing-cases.patch | 2190 ++++++++++++++++++++++++++++++++++ ++ Makefile.am | 2 +- ++ configure.ac | 3 +- ++ tests/Makefile.am | 79 ++ ++ tests/runtests.py | 424 +++++++ ++ tests/t_common.c | 77 ++ ++ tests/t_common.h | 19 + ++ tests/t_gssapi_cli.c | 206 ++++ ++ tests/t_gssapi_srv.c | 198 +++ ++ 9 files changed, 3196 insertions(+), 2 deletions(-) ++ create mode 100644 0001-add-testing-cases.patch ++ 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/0001-add-testing-cases.patch b/0001-add-testing-cases.patch ++new file mode 100644 ++index 0000000..3cd3d14 ++--- /dev/null +++++ b/0001-add-testing-cases.patch ++@@ -0,0 +1,2190 @@ +++From 562d3ce28df4ee6b8df4a5b3c84c1eec4f5c4568 Mon Sep 17 00:00:00 2001 +++From: root +++Date: Thu, 1 Sep 2022 11:52:38 +0800 +++Subject: [PATCH] add testing cases +++ +++--- +++ 0001-add-testing-cases.patch | 1091 ++++++++++++++++++++++++++++++++++ +++ Makefile.am | 2 +- +++ configure.ac | 3 +- +++ tests/Makefile.am | 79 +++ +++ tests/runtests.py | 424 +++++++++++++ +++ tests/t_common.c | 77 +++ +++ tests/t_common.h | 19 + +++ tests/t_gssapi_cli.c | 206 +++++++ +++ tests/t_gssapi_srv.c | 198 ++++++ +++ 9 files changed, 2097 insertions(+), 2 deletions(-) +++ create mode 100644 0001-add-testing-cases.patch +++ 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/0001-add-testing-cases.patch b/0001-add-testing-cases.patch +++new file mode 100644 +++index 0000000..e3b7b96 +++--- /dev/null ++++++ b/0001-add-testing-cases.patch +++@@ -0,0 +1,1091 @@ ++++From b8809a59e17e5fc7484be0cf7cb99c6c91641325 Mon Sep 17 00:00:00 2001 ++++From: root ++++Date: Thu, 1 Sep 2022 11:45:09 +0800 ++++Subject: [PATCH] add testing cases ++++ ++++--- ++++ Makefile.am | 2 +- ++++ configure.ac | 3 +- ++++ tests/Makefile.am | 79 ++++++++ ++++ tests/runtests.py | 424 +++++++++++++++++++++++++++++++++++++++++++ ++++ tests/t_common.c | 77 ++++++++ ++++ tests/t_common.h | 19 ++ ++++ tests/t_gssapi_cli.c | 206 +++++++++++++++++++++ ++++ tests/t_gssapi_srv.c | 198 ++++++++++++++++++++ ++++ 8 files changed, 1006 insertions(+), 2 deletions(-) ++++ create mode 100644 tests/Makefile.am ++++ create mode 100644 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 251e43b..18aec4b 100644 ++++--- a/configure.ac +++++++ b/configure.ac ++++@@ -1577,7 +1577,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 100644 ++++index 0000000..7be6074 ++++--- /dev/null +++++++ b/tests/runtests.py ++++@@ -0,0 +1,424 @@ +++++#!/usr/bin/python3 +++++ +++++import argparse +++++import base64 +++++import os +++++import shutil +++++import signal +++++import subprocess +++++import sys +++++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_basic_test(kenv): +++++ 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)) +++++ 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: +++++ 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 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" +++++ 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 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 """ +++++ 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') +++++ +++++ err = 0 +++++ +++++ print('GSSAPI BASIC:') +++++ print(' ', end='') +++++ err += gssapi_basic_test(kenv) +++++ +++++ print('GSSAPI CHANNEL BINDING:') +++++ print(' ', end='') +++++ err += gssapi_channel_binding_test(kenv) +++++ +++++ print('GSSAPI CHANNEL BINDING MISMTACH:') +++++ print(' ', end='') +++++ 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 +++++ +++++def setup_plain(testdir): +++++ """ Create sasldb file """ +++++ sasldbfile = os.path.join(testdir, 'testsasldb.db') +++++ +++++ sasldbenv = {'SASL_PATH': os.path.join(testdir, '../../plugins/.libs'), +++++ 'LD_LIBRARY_PATH' : os.path.join(testdir, '../../lib/.libs')} +++++ +++++ passwdprog = os.path.join(testdir, '../../utils/saslpasswd2') +++++ +++++ echo = subprocess.Popen(('echo', '1234567'), stdout=subprocess.PIPE) +++++ subprocess.check_call([ +++++ passwdprog, "-f", sasldbfile, "-c", "test", +++++ "-u", "host.realm.test", "-p" +++++ ], stdin=echo.stdout, env=sasldbenv, timeout=5) +++++ +++++ return (sasldbfile, sasldbenv) +++++ +++++def plain_test(sasldbfile, sasldbenv): +++++ try: +++++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-P", sasldbfile], +++++ stdout=subprocess.PIPE, +++++ stderr=subprocess.PIPE, env=sasldbenv) +++++ srv.stdout.readline() # Wait for srv to say it is ready +++++ cli = subprocess.Popen(["../tests/t_gssapi_cli", "-P", "1234567"], +++++ stdout=subprocess.PIPE, +++++ stderr=subprocess.PIPE, env=sasldbenv) +++++ 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: PLAIN CLI({}) SRV({})".format( +++++ cli.stdout.read().decode('utf-8').strip(), +++++ srv.stdout.read().decode('utf-8').strip())) +++++ return +++++ +++++def plain_mismatch_test(sasldbfile, sasldbenv): +++++ result = "FAIL" +++++ try: +++++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-P", sasldbfile], +++++ stdout=subprocess.PIPE, +++++ stderr=subprocess.PIPE, env=sasldbenv) +++++ 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", "-P", "12345678"], +++++ stdout=subprocess.PIPE, +++++ stderr=subprocess.PIPE, env=sasldbenv) +++++ 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())) +++++ return +++++ +++++def plain_tests(testdir): +++++ sasldbfile, sasldbenv = setup_plain(testdir) +++++ #print("DB file: {}, ENV: {}".format(sasldbfile, sasldbenv)) +++++ print('SASLDB PLAIN:') +++++ print(' ', end='') +++++ plain_test(sasldbfile, sasldbenv) +++++ +++++ print('SASLDB PLAIN PASSWORD MISMATCH:') +++++ print(' ', end='') +++++ plain_mismatch_test(sasldbfile, sasldbenv) +++++ +++++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) +++++ +++++ plain_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 ++++new file mode 100644 ++++index 0000000..f56098e ++++--- /dev/null +++++++ b/tests/t_common.c ++++@@ -0,0 +1,77 @@ +++++/* Copyright (C) Simo Sorce +++++ * See COPYING file for License */ +++++ +++++#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; +++++ +++++ 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, 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) return; +++++ +++++ 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); +++++ +++++ *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; +++++} +++++ +++++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 ++++new file mode 100644 ++++index 0000000..be24a53 ++++--- /dev/null +++++++ b/tests/t_common.h ++++@@ -0,0 +1,19 @@ +++++/* Copyright (C) Simo Sorce +++++ * See COPYING file for License */ +++++ +++++#include "config.h" +++++ +++++#include +++++#include +++++#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, 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 ++++new file mode 100644 ++++index 0000000..b1dd6ce ++++--- /dev/null +++++++ b/tests/t_gssapi_cli.c ++++@@ -0,0 +1,206 @@ +++++/* Copyright (C) Simo Sorce +++++ * See COPYING file for License */ +++++ +++++#include "t_common.h" +++++ +++++#include +++++#include +++++#include +++++#include +++++ +++++#ifdef HAVE_UNISTD_H +++++#include +++++#endif +++++ +++++#include +++++#include +++++#include +++++ +++++const char *testpass = NULL; +++++ +++++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; +++++} +++++ +++++static int get_user(void *context __attribute__((unused)), +++++ int id, +++++ const char **result, +++++ unsigned *len) +++++{ +++++ const char *testuser = "test@host.realm.test"; +++++ +++++ if (! result) +++++ return SASL_BADPARAM; +++++ +++++ switch (id) { +++++ case SASL_CB_USER: +++++ case SASL_CB_AUTHNAME: +++++ *result = testuser; +++++ break; +++++ default: +++++ return SASL_BADPARAM; +++++ } +++++ +++++ if (len) *len = strlen(*result); +++++ +++++ return SASL_OK; +++++} +++++ +++++static int get_pass(sasl_conn_t *conn __attribute__((unused)), +++++ void *context __attribute__((unused)), +++++ int id, +++++ sasl_secret_t **psecret) +++++{ +++++ size_t len; +++++ static sasl_secret_t *x; +++++ +++++ /* paranoia check */ +++++ if (! conn || ! psecret || id != SASL_CB_PASS) +++++ return SASL_BADPARAM; +++++ +++++ len = strlen(testpass); +++++ +++++ x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); +++++ +++++ if (!x) { +++++ return SASL_NOMEM; +++++ } +++++ +++++ x->len = len; +++++ strcpy((char *)x->data, testpass); +++++ +++++ *psecret = x; +++++ return SASL_OK; +++++} +++++ +++++int main(int argc, char *argv[]) +++++{ +++++ sasl_callback_t callbacks[4] = {}; +++++ char buf[8192]; +++++ const char *chosenmech; +++++ sasl_conn_t *conn; +++++ const char *data; +++++ unsigned int len; +++++ sasl_channel_binding_t cb = {0}; +++++ char cb_buf[256]; +++++ int sd; +++++ int c, r; +++++ const char *sasl_mech = "GSSAPI"; +++++ int plain = 0; +++++ bool spnego = false; +++++ bool zeromaxssf = false; +++++ +++++ while ((c = getopt(argc, argv, "c:P:zN")) != EOF) { +++++ switch (c) { +++++ case 'c': +++++ parse_cb(&cb, cb_buf, 256, optarg); +++++ break; +++++ case 'P': +++++ plain = 1; +++++ testpass = optarg; +++++ break; +++++ case 'z': +++++ zeromaxssf = true; +++++ break; +++++ case 'N': +++++ spnego = true; +++++ break; +++++ default: +++++ break; +++++ } +++++ } +++++ +++++ /* 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; +++++ callbacks[2].id = SASL_CB_LIST_END; +++++ callbacks[2].proc = NULL; +++++ callbacks[2].context = NULL; +++++ callbacks[3].id = SASL_CB_LIST_END; +++++ callbacks[3].proc = NULL; +++++ callbacks[3].context = NULL; +++++ +++++ if (plain) { +++++ sasl_mech = "PLAIN"; +++++ +++++ callbacks[1].id = SASL_CB_AUTHNAME; +++++ callbacks[1].proc = (sasl_callback_ft)&get_user; +++++ +++++ callbacks[2].id = SASL_CB_PASS; +++++ callbacks[2].proc = (sasl_callback_ft)&get_pass; +++++ } +++++ +++++ 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); +++++ } +++++ +++++ if (cb.name) { +++++ 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); +++++ } +++++ +++++ 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)); +++++ exit(-1); +++++ } +++++ +++++ sd = setup_socket(); +++++ +++++ while (r == SASL_CONTINUE) { +++++ send_string(sd, data, len); +++++ len = 8192; +++++ recv_string(sd, buf, &len, false); +++++ +++++ 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..1989362 ++++--- /dev/null +++++++ b/tests/t_gssapi_srv.c ++++@@ -0,0 +1,198 @@ +++++/* Copyright (C) Simo Sorce , +++++ * Dmitry Belyavskiy +++++ * See COPYING file for License */ +++++ +++++#include "t_common.h" +++++ +++++#include +++++#include +++++#include +++++#include +++++ +++++#ifdef HAVE_UNISTD_H +++++#include +++++#endif +++++ +++++#include +++++#include +++++ +++++const char *sasldb_path = NULL, +++++ *auxprop_plugin = "sasldb", +++++ *pwcheck_method = +++++#if 0 /* totally undocumented. See issue#374 */ +++++ "auxprop-hashed" +++++#else +++++ "auxprop" +++++#endif +++++ ; +++++ +++++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; +++++} +++++ +++++static int test_getopt(void *context __attribute__((unused)), +++++ const char *plugin_name __attribute__((unused)), +++++ const char *option, +++++ const char **result, +++++ unsigned *len) +++++{ +++++ if (sasldb_path && !strcmp(option, "sasldb_path")) { +++++ *result = sasldb_path; +++++ if (len) +++++ *len = (unsigned) strlen(sasldb_path); +++++ return SASL_OK; +++++ } +++++ +++++ if (sasldb_path && !strcmp(option, "auxprop_plugin")) { +++++ *result = auxprop_plugin; +++++ if (len) +++++ *len = (unsigned) strlen(auxprop_plugin); +++++ return SASL_OK; +++++ } +++++ +++++ if (sasldb_path && !strcmp(option, "pwcheck_method")) { +++++ *result = pwcheck_method; +++++ if (len) +++++ *len = (unsigned) strlen(pwcheck_method); +++++ return SASL_OK; +++++ } +++++ return SASL_FAIL; +++++} +++++ +++++int main(int argc, char *argv[]) +++++{ +++++ sasl_callback_t callbacks[3] = {}; +++++ 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 c, r; +++++ const char *sasl_mech = "GSSAPI"; +++++ int plain = 0; +++++ bool spnego = false; +++++ bool zeromaxssf = false; +++++ +++++ while ((c = getopt(argc, argv, "c:P:zN")) != EOF) { +++++ switch (c) { +++++ case 'c': +++++ parse_cb(&cb, cb_buf, 256, optarg); +++++ break; +++++ case 'P': +++++ plain = 1; +++++ sasldb_path = optarg; +++++ break; +++++ case 'z': +++++ zeromaxssf = true; +++++ break; +++++ case 'N': +++++ spnego = true; +++++ break; +++++ default: +++++ break; +++++ } +++++ } +++++ +++++ /* 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_GETOPT; +++++ callbacks[1].proc = (sasl_callback_ft)&test_getopt; +++++ callbacks[1].context = NULL; +++++ callbacks[2].id = SASL_CB_LIST_END; +++++ callbacks[2].proc = NULL; +++++ callbacks[2].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); +++++ } +++++ +++++ if (cb.name) { +++++ sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); +++++ } +++++ +++++ if (plain) { +++++ sasl_mech = "PLAIN"; +++++ } +++++ +++++ 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, false); +++++ +++++ 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)); +++++ exit(-1); +++++ } +++++ +++++ while (r == SASL_CONTINUE) { +++++ send_string(sd, data, len); +++++ len = 8192; +++++ recv_string(sd, buf, &len, true); +++++ +++++ 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.33.0 ++++ +++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 251e43b..18aec4b 100644 +++--- a/configure.ac ++++++ b/configure.ac +++@@ -1577,7 +1577,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..7be6074 +++--- /dev/null ++++++ b/tests/runtests.py +++@@ -0,0 +1,424 @@ ++++#!/usr/bin/python3 ++++ ++++import argparse ++++import base64 ++++import os ++++import shutil ++++import signal ++++import subprocess ++++import sys ++++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_basic_test(kenv): ++++ 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)) ++++ 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: ++++ 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 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" ++++ 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 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 """ ++++ 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') ++++ ++++ err = 0 ++++ ++++ print('GSSAPI BASIC:') ++++ print(' ', end='') ++++ err += gssapi_basic_test(kenv) ++++ ++++ print('GSSAPI CHANNEL BINDING:') ++++ print(' ', end='') ++++ err += gssapi_channel_binding_test(kenv) ++++ ++++ print('GSSAPI CHANNEL BINDING MISMTACH:') ++++ print(' ', end='') ++++ 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 ++++ ++++def setup_plain(testdir): ++++ """ Create sasldb file """ ++++ sasldbfile = os.path.join(testdir, 'testsasldb.db') ++++ ++++ sasldbenv = {'SASL_PATH': os.path.join(testdir, '../../plugins/.libs'), ++++ 'LD_LIBRARY_PATH' : os.path.join(testdir, '../../lib/.libs')} ++++ ++++ passwdprog = os.path.join(testdir, '../../utils/saslpasswd2') ++++ ++++ echo = subprocess.Popen(('echo', '1234567'), stdout=subprocess.PIPE) ++++ subprocess.check_call([ ++++ passwdprog, "-f", sasldbfile, "-c", "test", ++++ "-u", "host.realm.test", "-p" ++++ ], stdin=echo.stdout, env=sasldbenv, timeout=5) ++++ ++++ return (sasldbfile, sasldbenv) ++++ ++++def plain_test(sasldbfile, sasldbenv): ++++ try: ++++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-P", sasldbfile], ++++ stdout=subprocess.PIPE, ++++ stderr=subprocess.PIPE, env=sasldbenv) ++++ srv.stdout.readline() # Wait for srv to say it is ready ++++ cli = subprocess.Popen(["../tests/t_gssapi_cli", "-P", "1234567"], ++++ stdout=subprocess.PIPE, ++++ stderr=subprocess.PIPE, env=sasldbenv) ++++ 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: PLAIN CLI({}) SRV({})".format( ++++ cli.stdout.read().decode('utf-8').strip(), ++++ srv.stdout.read().decode('utf-8').strip())) ++++ return ++++ ++++def plain_mismatch_test(sasldbfile, sasldbenv): ++++ result = "FAIL" ++++ try: ++++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-P", sasldbfile], ++++ stdout=subprocess.PIPE, ++++ stderr=subprocess.PIPE, env=sasldbenv) ++++ 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", "-P", "12345678"], ++++ stdout=subprocess.PIPE, ++++ stderr=subprocess.PIPE, env=sasldbenv) ++++ 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())) ++++ return ++++ ++++def plain_tests(testdir): ++++ sasldbfile, sasldbenv = setup_plain(testdir) ++++ #print("DB file: {}, ENV: {}".format(sasldbfile, sasldbenv)) ++++ print('SASLDB PLAIN:') ++++ print(' ', end='') ++++ plain_test(sasldbfile, sasldbenv) ++++ ++++ print('SASLDB PLAIN PASSWORD MISMATCH:') ++++ print(' ', end='') ++++ plain_mismatch_test(sasldbfile, sasldbenv) ++++ ++++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) ++++ ++++ plain_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 +++new file mode 100644 +++index 0000000..f56098e +++--- /dev/null ++++++ b/tests/t_common.c +++@@ -0,0 +1,77 @@ ++++/* Copyright (C) Simo Sorce ++++ * See COPYING file for License */ ++++ ++++#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; ++++ ++++ 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, 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) return; ++++ ++++ 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); ++++ ++++ *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; ++++} ++++ ++++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 +++new file mode 100644 +++index 0000000..be24a53 +++--- /dev/null ++++++ b/tests/t_common.h +++@@ -0,0 +1,19 @@ ++++/* Copyright (C) Simo Sorce ++++ * See COPYING file for License */ ++++ ++++#include "config.h" ++++ ++++#include ++++#include ++++#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, 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 +++new file mode 100644 +++index 0000000..b1dd6ce +++--- /dev/null ++++++ b/tests/t_gssapi_cli.c +++@@ -0,0 +1,206 @@ ++++/* Copyright (C) Simo Sorce ++++ * See COPYING file for License */ ++++ ++++#include "t_common.h" ++++ ++++#include ++++#include ++++#include ++++#include ++++ ++++#ifdef HAVE_UNISTD_H ++++#include ++++#endif ++++ ++++#include ++++#include ++++#include ++++ ++++const char *testpass = NULL; ++++ ++++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; ++++} ++++ ++++static int get_user(void *context __attribute__((unused)), ++++ int id, ++++ const char **result, ++++ unsigned *len) ++++{ ++++ const char *testuser = "test@host.realm.test"; ++++ ++++ if (! result) ++++ return SASL_BADPARAM; ++++ ++++ switch (id) { ++++ case SASL_CB_USER: ++++ case SASL_CB_AUTHNAME: ++++ *result = testuser; ++++ break; ++++ default: ++++ return SASL_BADPARAM; ++++ } ++++ ++++ if (len) *len = strlen(*result); ++++ ++++ return SASL_OK; ++++} ++++ ++++static int get_pass(sasl_conn_t *conn __attribute__((unused)), ++++ void *context __attribute__((unused)), ++++ int id, ++++ sasl_secret_t **psecret) ++++{ ++++ size_t len; ++++ static sasl_secret_t *x; ++++ ++++ /* paranoia check */ ++++ if (! conn || ! psecret || id != SASL_CB_PASS) ++++ return SASL_BADPARAM; ++++ ++++ len = strlen(testpass); ++++ ++++ x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); ++++ ++++ if (!x) { ++++ return SASL_NOMEM; ++++ } ++++ ++++ x->len = len; ++++ strcpy((char *)x->data, testpass); ++++ ++++ *psecret = x; ++++ return SASL_OK; ++++} ++++ ++++int main(int argc, char *argv[]) ++++{ ++++ sasl_callback_t callbacks[4] = {}; ++++ char buf[8192]; ++++ const char *chosenmech; ++++ sasl_conn_t *conn; ++++ const char *data; ++++ unsigned int len; ++++ sasl_channel_binding_t cb = {0}; ++++ char cb_buf[256]; ++++ int sd; ++++ int c, r; ++++ const char *sasl_mech = "GSSAPI"; ++++ int plain = 0; ++++ bool spnego = false; ++++ bool zeromaxssf = false; ++++ ++++ while ((c = getopt(argc, argv, "c:P:zN")) != EOF) { ++++ switch (c) { ++++ case 'c': ++++ parse_cb(&cb, cb_buf, 256, optarg); ++++ break; ++++ case 'P': ++++ plain = 1; ++++ testpass = optarg; ++++ break; ++++ case 'z': ++++ zeromaxssf = true; ++++ break; ++++ case 'N': ++++ spnego = true; ++++ break; ++++ default: ++++ break; ++++ } ++++ } ++++ ++++ /* 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; ++++ callbacks[2].id = SASL_CB_LIST_END; ++++ callbacks[2].proc = NULL; ++++ callbacks[2].context = NULL; ++++ callbacks[3].id = SASL_CB_LIST_END; ++++ callbacks[3].proc = NULL; ++++ callbacks[3].context = NULL; ++++ ++++ if (plain) { ++++ sasl_mech = "PLAIN"; ++++ ++++ callbacks[1].id = SASL_CB_AUTHNAME; ++++ callbacks[1].proc = (sasl_callback_ft)&get_user; ++++ ++++ callbacks[2].id = SASL_CB_PASS; ++++ callbacks[2].proc = (sasl_callback_ft)&get_pass; ++++ } ++++ ++++ 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); ++++ } ++++ ++++ if (cb.name) { ++++ 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); ++++ } ++++ ++++ 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)); ++++ exit(-1); ++++ } ++++ ++++ sd = setup_socket(); ++++ ++++ while (r == SASL_CONTINUE) { ++++ send_string(sd, data, len); ++++ len = 8192; ++++ recv_string(sd, buf, &len, false); ++++ ++++ 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..1989362 +++--- /dev/null ++++++ b/tests/t_gssapi_srv.c +++@@ -0,0 +1,198 @@ ++++/* Copyright (C) Simo Sorce , ++++ * Dmitry Belyavskiy ++++ * See COPYING file for License */ ++++ ++++#include "t_common.h" ++++ ++++#include ++++#include ++++#include ++++#include ++++ ++++#ifdef HAVE_UNISTD_H ++++#include ++++#endif ++++ ++++#include ++++#include ++++ ++++const char *sasldb_path = NULL, ++++ *auxprop_plugin = "sasldb", ++++ *pwcheck_method = ++++#if 0 /* totally undocumented. See issue#374 */ ++++ "auxprop-hashed" ++++#else ++++ "auxprop" ++++#endif ++++ ; ++++ ++++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; ++++} ++++ ++++static int test_getopt(void *context __attribute__((unused)), ++++ const char *plugin_name __attribute__((unused)), ++++ const char *option, ++++ const char **result, ++++ unsigned *len) ++++{ ++++ if (sasldb_path && !strcmp(option, "sasldb_path")) { ++++ *result = sasldb_path; ++++ if (len) ++++ *len = (unsigned) strlen(sasldb_path); ++++ return SASL_OK; ++++ } ++++ ++++ if (sasldb_path && !strcmp(option, "auxprop_plugin")) { ++++ *result = auxprop_plugin; ++++ if (len) ++++ *len = (unsigned) strlen(auxprop_plugin); ++++ return SASL_OK; ++++ } ++++ ++++ if (sasldb_path && !strcmp(option, "pwcheck_method")) { ++++ *result = pwcheck_method; ++++ if (len) ++++ *len = (unsigned) strlen(pwcheck_method); ++++ return SASL_OK; ++++ } ++++ return SASL_FAIL; ++++} ++++ ++++int main(int argc, char *argv[]) ++++{ ++++ sasl_callback_t callbacks[3] = {}; ++++ 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 c, r; ++++ const char *sasl_mech = "GSSAPI"; ++++ int plain = 0; ++++ bool spnego = false; ++++ bool zeromaxssf = false; ++++ ++++ while ((c = getopt(argc, argv, "c:P:zN")) != EOF) { ++++ switch (c) { ++++ case 'c': ++++ parse_cb(&cb, cb_buf, 256, optarg); ++++ break; ++++ case 'P': ++++ plain = 1; ++++ sasldb_path = optarg; ++++ break; ++++ case 'z': ++++ zeromaxssf = true; ++++ break; ++++ case 'N': ++++ spnego = true; ++++ break; ++++ default: ++++ break; ++++ } ++++ } ++++ ++++ /* 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_GETOPT; ++++ callbacks[1].proc = (sasl_callback_ft)&test_getopt; ++++ callbacks[1].context = NULL; ++++ callbacks[2].id = SASL_CB_LIST_END; ++++ callbacks[2].proc = NULL; ++++ callbacks[2].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); ++++ } ++++ ++++ if (cb.name) { ++++ sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); ++++ } ++++ ++++ if (plain) { ++++ sasl_mech = "PLAIN"; ++++ } ++++ ++++ 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, false); ++++ ++++ 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)); ++++ exit(-1); ++++ } ++++ ++++ while (r == SASL_CONTINUE) { ++++ send_string(sd, data, len); ++++ len = 8192; ++++ recv_string(sd, buf, &len, true); ++++ ++++ 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.33.0 +++ ++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 251e43b..18aec4b 100644 ++--- a/configure.ac +++++ b/configure.ac ++@@ -1577,7 +1577,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..6de56d4 ++--- /dev/null +++++ b/tests/runtests.py ++@@ -0,0 +1,424 @@ +++#!/usr/bin/python3 +++ +++import argparse +++import base64 +++import os +++import shutil +++import signal +++import subprocess +++import sys +++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_basic_test(kenv): +++ 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)) +++ 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: +++ 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 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" +++ 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 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 """ +++ 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') +++ +++ err = 0 +++ +++ print('GSSAPI BASIC:') +++ print(' ', end='') +++ err += gssapi_basic_test(kenv) +++ +++ print('GSSAPI CHANNEL BINDING:') +++ print(' ', end='') +++ err += gssapi_channel_binding_test(kenv) +++ +++ print('GSSAPI CHANNEL BINDING MISMTACH:') +++ print(' ', end='') +++ 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 +++ +++def setup_plain(testdir): +++ """ Create sasldb file """ +++ sasldbfile = os.path.join(testdir, 'testsasldb.db') +++ +++ sasldbenv = {'SASL_PATH': os.path.join(testdir, '../../plugins/.libs'), +++ 'LD_LIBRARY_PATH' : os.path.join(testdir, '../../lib/.libs')} +++ +++ passwdprog = os.path.join(testdir, '../../utils/saslpasswd2') +++ +++ echo = subprocess.Popen(('echo', '1234567'), stdout=subprocess.PIPE) +++ subprocess.check_call([ +++ passwdprog, "-f", sasldbfile, "-c", "test", +++ "-u", "host.realm.test", "-p" +++ ], stdin=echo.stdout, env=sasldbenv, timeout=5) +++ +++ return (sasldbfile, sasldbenv) +++ +++def plain_test(sasldbfile, sasldbenv): +++ try: +++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-P", sasldbfile], +++ stdout=subprocess.PIPE, +++ stderr=subprocess.PIPE, env=sasldbenv) +++ srv.stdout.readline() # Wait for srv to say it is ready +++ cli = subprocess.Popen(["../tests/t_gssapi_cli", "-P", "1234567"], +++ stdout=subprocess.PIPE, +++ stderr=subprocess.PIPE, env=sasldbenv) +++ 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: PLAIN CLI({}) SRV({})".format( +++ cli.stdout.read().decode('utf-8').strip(), +++ srv.stdout.read().decode('utf-8').strip())) +++ return +++ +++def plain_mismatch_test(sasldbfile, sasldbenv): +++ result = "FAIL" +++ try: +++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-P", sasldbfile], +++ stdout=subprocess.PIPE, +++ stderr=subprocess.PIPE, env=sasldbenv) +++ 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", "-P", "12345678"], +++ stdout=subprocess.PIPE, +++ stderr=subprocess.PIPE, env=sasldbenv) +++ 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())) +++ return +++ +++def plain_tests(testdir): +++ sasldbfile, sasldbenv = setup_plain(testdir) +++ #print("DB file: {}, ENV: {}".format(sasldbfile, sasldbenv)) +++ print('SASLDB PLAIN:') +++ print(' ', end='') +++ plain_test(sasldbfile, sasldbenv) +++ +++ print('SASLDB PLAIN PASSWORD MISMATCH:') +++ print(' ', end='') +++ plain_mismatch_test(sasldbfile, sasldbenv) +++ +++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) +++ +++ plain_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 ++new file mode 100644 ++index 0000000..f56098e ++--- /dev/null +++++ b/tests/t_common.c ++@@ -0,0 +1,77 @@ +++/* Copyright (C) Simo Sorce +++ * See COPYING file for License */ +++ +++#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; +++ +++ 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, 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) return; +++ +++ 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); +++ +++ *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; +++} +++ +++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 ++new file mode 100644 ++index 0000000..be24a53 ++--- /dev/null +++++ b/tests/t_common.h ++@@ -0,0 +1,19 @@ +++/* Copyright (C) Simo Sorce +++ * See COPYING file for License */ +++ +++#include "config.h" +++ +++#include +++#include +++#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, 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 ++new file mode 100644 ++index 0000000..b1dd6ce ++--- /dev/null +++++ b/tests/t_gssapi_cli.c ++@@ -0,0 +1,206 @@ +++/* Copyright (C) Simo Sorce +++ * See COPYING file for License */ +++ +++#include "t_common.h" +++ +++#include +++#include +++#include +++#include +++ +++#ifdef HAVE_UNISTD_H +++#include +++#endif +++ +++#include +++#include +++#include +++ +++const char *testpass = NULL; +++ +++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; +++} +++ +++static int get_user(void *context __attribute__((unused)), +++ int id, +++ const char **result, +++ unsigned *len) +++{ +++ const char *testuser = "test@host.realm.test"; +++ +++ if (! result) +++ return SASL_BADPARAM; +++ +++ switch (id) { +++ case SASL_CB_USER: +++ case SASL_CB_AUTHNAME: +++ *result = testuser; +++ break; +++ default: +++ return SASL_BADPARAM; +++ } +++ +++ if (len) *len = strlen(*result); +++ +++ return SASL_OK; +++} +++ +++static int get_pass(sasl_conn_t *conn __attribute__((unused)), +++ void *context __attribute__((unused)), +++ int id, +++ sasl_secret_t **psecret) +++{ +++ size_t len; +++ static sasl_secret_t *x; +++ +++ /* paranoia check */ +++ if (! conn || ! psecret || id != SASL_CB_PASS) +++ return SASL_BADPARAM; +++ +++ len = strlen(testpass); +++ +++ x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); +++ +++ if (!x) { +++ return SASL_NOMEM; +++ } +++ +++ x->len = len; +++ strcpy((char *)x->data, testpass); +++ +++ *psecret = x; +++ return SASL_OK; +++} +++ +++int main(int argc, char *argv[]) +++{ +++ sasl_callback_t callbacks[4] = {}; +++ char buf[8192]; +++ const char *chosenmech; +++ sasl_conn_t *conn; +++ const char *data; +++ unsigned int len; +++ sasl_channel_binding_t cb = {0}; +++ char cb_buf[256]; +++ int sd; +++ int c, r; +++ const char *sasl_mech = "GSSAPI"; +++ int plain = 0; +++ bool spnego = false; +++ bool zeromaxssf = false; +++ +++ while ((c = getopt(argc, argv, "c:P:zN")) != EOF) { +++ switch (c) { +++ case 'c': +++ parse_cb(&cb, cb_buf, 256, optarg); +++ break; +++ case 'P': +++ plain = 1; +++ testpass = optarg; +++ break; +++ case 'z': +++ zeromaxssf = true; +++ break; +++ case 'N': +++ spnego = true; +++ break; +++ default: +++ break; +++ } +++ } +++ +++ /* 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; +++ callbacks[2].id = SASL_CB_LIST_END; +++ callbacks[2].proc = NULL; +++ callbacks[2].context = NULL; +++ callbacks[3].id = SASL_CB_LIST_END; +++ callbacks[3].proc = NULL; +++ callbacks[3].context = NULL; +++ +++ if (plain) { +++ sasl_mech = "PLAIN"; +++ +++ callbacks[1].id = SASL_CB_AUTHNAME; +++ callbacks[1].proc = (sasl_callback_ft)&get_user; +++ +++ callbacks[2].id = SASL_CB_PASS; +++ callbacks[2].proc = (sasl_callback_ft)&get_pass; +++ } +++ +++ 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); +++ } +++ +++ if (cb.name) { +++ 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); +++ } +++ +++ 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)); +++ exit(-1); +++ } +++ +++ sd = setup_socket(); +++ +++ while (r == SASL_CONTINUE) { +++ send_string(sd, data, len); +++ len = 8192; +++ recv_string(sd, buf, &len, false); +++ +++ 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..1989362 ++--- /dev/null +++++ b/tests/t_gssapi_srv.c ++@@ -0,0 +1,198 @@ +++/* Copyright (C) Simo Sorce , +++ * Dmitry Belyavskiy +++ * See COPYING file for License */ +++ +++#include "t_common.h" +++ +++#include +++#include +++#include +++#include +++ +++#ifdef HAVE_UNISTD_H +++#include +++#endif +++ +++#include +++#include +++ +++const char *sasldb_path = NULL, +++ *auxprop_plugin = "sasldb", +++ *pwcheck_method = +++#if 0 /* totally undocumented. See issue#374 */ +++ "auxprop-hashed" +++#else +++ "auxprop" +++#endif +++ ; +++ +++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; +++} +++ +++static int test_getopt(void *context __attribute__((unused)), +++ const char *plugin_name __attribute__((unused)), +++ const char *option, +++ const char **result, +++ unsigned *len) +++{ +++ if (sasldb_path && !strcmp(option, "sasldb_path")) { +++ *result = sasldb_path; +++ if (len) +++ *len = (unsigned) strlen(sasldb_path); +++ return SASL_OK; +++ } +++ +++ if (sasldb_path && !strcmp(option, "auxprop_plugin")) { +++ *result = auxprop_plugin; +++ if (len) +++ *len = (unsigned) strlen(auxprop_plugin); +++ return SASL_OK; +++ } +++ +++ if (sasldb_path && !strcmp(option, "pwcheck_method")) { +++ *result = pwcheck_method; +++ if (len) +++ *len = (unsigned) strlen(pwcheck_method); +++ return SASL_OK; +++ } +++ return SASL_FAIL; +++} +++ +++int main(int argc, char *argv[]) +++{ +++ sasl_callback_t callbacks[3] = {}; +++ 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 c, r; +++ const char *sasl_mech = "GSSAPI"; +++ int plain = 0; +++ bool spnego = false; +++ bool zeromaxssf = false; +++ +++ while ((c = getopt(argc, argv, "c:P:zN")) != EOF) { +++ switch (c) { +++ case 'c': +++ parse_cb(&cb, cb_buf, 256, optarg); +++ break; +++ case 'P': +++ plain = 1; +++ sasldb_path = optarg; +++ break; +++ case 'z': +++ zeromaxssf = true; +++ break; +++ case 'N': +++ spnego = true; +++ break; +++ default: +++ break; +++ } +++ } +++ +++ /* 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_GETOPT; +++ callbacks[1].proc = (sasl_callback_ft)&test_getopt; +++ callbacks[1].context = NULL; +++ callbacks[2].id = SASL_CB_LIST_END; +++ callbacks[2].proc = NULL; +++ callbacks[2].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); +++ } +++ +++ if (cb.name) { +++ sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); +++ } +++ +++ if (plain) { +++ sasl_mech = "PLAIN"; +++ } +++ +++ 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, false); +++ +++ 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)); +++ exit(-1); +++ } +++ +++ while (r == SASL_CONTINUE) { +++ send_string(sd, data, len); +++ len = 8192; +++ recv_string(sd, buf, &len, true); +++ +++ 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.33.0 ++ +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 251e43b..18aec4b 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1577,7 +1577,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..83a90b3 +--- /dev/null ++++ b/tests/runtests.py +@@ -0,0 +1,425 @@ ++#!/usr/bin/python3 ++ ++import argparse ++import base64 ++import os ++import shutil ++import signal ++import subprocess ++import sys ++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_basic_test(kenv): ++ 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)) ++ 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: ++ 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 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" ++ 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)) ++ return 0 ++ except Exception as e: ++ print("{}: {}".format(result, e)) ++ return 1 ++ ++ 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 """ ++ 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') ++ ++ err = 0 ++ ++ print('GSSAPI BASIC:') ++ print(' ', end='') ++ err += gssapi_basic_test(kenv) ++ ++ print('GSSAPI CHANNEL BINDING:') ++ print(' ', end='') ++ err += gssapi_channel_binding_test(kenv) ++ ++ print('GSSAPI CHANNEL BINDING MISMTACH:') ++ print(' ', end='') ++ 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 ++ ++def setup_plain(testdir): ++ """ Create sasldb file """ ++ sasldbfile = os.path.join(testdir, 'testsasldb.db') ++ ++ sasldbenv = {'SASL_PATH': os.path.join(testdir, '../../plugins/.libs'), ++ 'LD_LIBRARY_PATH' : os.path.join(testdir, '../../lib/.libs')} ++ ++ passwdprog = os.path.join(testdir, '../../utils/saslpasswd2') ++ ++ echo = subprocess.Popen(('echo', '1234567'), stdout=subprocess.PIPE) ++ subprocess.check_call([ ++ passwdprog, "-f", sasldbfile, "-c", "test", ++ "-u", "host.realm.test", "-p" ++ ], stdin=echo.stdout, env=sasldbenv, timeout=5) ++ ++ return (sasldbfile, sasldbenv) ++ ++def plain_test(sasldbfile, sasldbenv): ++ try: ++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-P", sasldbfile], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=sasldbenv) ++ srv.stdout.readline() # Wait for srv to say it is ready ++ cli = subprocess.Popen(["../tests/t_gssapi_cli", "-P", "1234567"], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=sasldbenv) ++ 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: PLAIN CLI({}) SRV({})".format( ++ cli.stdout.read().decode('utf-8').strip(), ++ srv.stdout.read().decode('utf-8').strip())) ++ return ++ ++def plain_mismatch_test(sasldbfile, sasldbenv): ++ result = "FAIL" ++ try: ++ srv = subprocess.Popen(["../tests/t_gssapi_srv", "-P", sasldbfile], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=sasldbenv) ++ 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", "-P", "12345678"], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, env=sasldbenv) ++ 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())) ++ return ++ ++def plain_tests(testdir): ++ sasldbfile, sasldbenv = setup_plain(testdir) ++ #print("DB file: {}, ENV: {}".format(sasldbfile, sasldbenv)) ++ print('SASLDB PLAIN:') ++ print(' ', end='') ++ plain_test(sasldbfile, sasldbenv) ++ ++ print('SASLDB PLAIN PASSWORD MISMATCH:') ++ print(' ', end='') ++ plain_mismatch_test(sasldbfile, sasldbenv) ++ ++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) ++ ++ plain_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 +new file mode 100644 +index 0000000..f56098e +--- /dev/null ++++ b/tests/t_common.c +@@ -0,0 +1,77 @@ ++/* Copyright (C) Simo Sorce ++ * See COPYING file for License */ ++ ++#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; ++ ++ 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, 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) return; ++ ++ 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); ++ ++ *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; ++} ++ ++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 +new file mode 100644 +index 0000000..be24a53 +--- /dev/null ++++ b/tests/t_common.h +@@ -0,0 +1,19 @@ ++/* Copyright (C) Simo Sorce ++ * See COPYING file for License */ ++ ++#include "config.h" ++ ++#include ++#include ++#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, 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 +new file mode 100644 +index 0000000..b1dd6ce +--- /dev/null ++++ b/tests/t_gssapi_cli.c +@@ -0,0 +1,206 @@ ++/* Copyright (C) Simo Sorce ++ * See COPYING file for License */ ++ ++#include "t_common.h" ++ ++#include ++#include ++#include ++#include ++ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++ ++#include ++#include ++#include ++ ++const char *testpass = NULL; ++ ++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; ++} ++ ++static int get_user(void *context __attribute__((unused)), ++ int id, ++ const char **result, ++ unsigned *len) ++{ ++ const char *testuser = "test@host.realm.test"; ++ ++ if (! result) ++ return SASL_BADPARAM; ++ ++ switch (id) { ++ case SASL_CB_USER: ++ case SASL_CB_AUTHNAME: ++ *result = testuser; ++ break; ++ default: ++ return SASL_BADPARAM; ++ } ++ ++ if (len) *len = strlen(*result); ++ ++ return SASL_OK; ++} ++ ++static int get_pass(sasl_conn_t *conn __attribute__((unused)), ++ void *context __attribute__((unused)), ++ int id, ++ sasl_secret_t **psecret) ++{ ++ size_t len; ++ static sasl_secret_t *x; ++ ++ /* paranoia check */ ++ if (! conn || ! psecret || id != SASL_CB_PASS) ++ return SASL_BADPARAM; ++ ++ len = strlen(testpass); ++ ++ x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); ++ ++ if (!x) { ++ return SASL_NOMEM; ++ } ++ ++ x->len = len; ++ strcpy((char *)x->data, testpass); ++ ++ *psecret = x; ++ return SASL_OK; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ sasl_callback_t callbacks[4] = {}; ++ char buf[8192]; ++ const char *chosenmech; ++ sasl_conn_t *conn; ++ const char *data; ++ unsigned int len; ++ sasl_channel_binding_t cb = {0}; ++ char cb_buf[256]; ++ int sd; ++ int c, r; ++ const char *sasl_mech = "GSSAPI"; ++ int plain = 0; ++ bool spnego = false; ++ bool zeromaxssf = false; ++ ++ while ((c = getopt(argc, argv, "c:P:zN")) != EOF) { ++ switch (c) { ++ case 'c': ++ parse_cb(&cb, cb_buf, 256, optarg); ++ break; ++ case 'P': ++ plain = 1; ++ testpass = optarg; ++ break; ++ case 'z': ++ zeromaxssf = true; ++ break; ++ case 'N': ++ spnego = true; ++ break; ++ default: ++ break; ++ } ++ } ++ ++ /* 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; ++ callbacks[2].id = SASL_CB_LIST_END; ++ callbacks[2].proc = NULL; ++ callbacks[2].context = NULL; ++ callbacks[3].id = SASL_CB_LIST_END; ++ callbacks[3].proc = NULL; ++ callbacks[3].context = NULL; ++ ++ if (plain) { ++ sasl_mech = "PLAIN"; ++ ++ callbacks[1].id = SASL_CB_AUTHNAME; ++ callbacks[1].proc = (sasl_callback_ft)&get_user; ++ ++ callbacks[2].id = SASL_CB_PASS; ++ callbacks[2].proc = (sasl_callback_ft)&get_pass; ++ } ++ ++ 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); ++ } ++ ++ if (cb.name) { ++ 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); ++ } ++ ++ 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)); ++ exit(-1); ++ } ++ ++ sd = setup_socket(); ++ ++ while (r == SASL_CONTINUE) { ++ send_string(sd, data, len); ++ len = 8192; ++ recv_string(sd, buf, &len, false); ++ ++ 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..1989362 +--- /dev/null ++++ b/tests/t_gssapi_srv.c +@@ -0,0 +1,198 @@ ++/* Copyright (C) Simo Sorce , ++ * Dmitry Belyavskiy ++ * See COPYING file for License */ ++ ++#include "t_common.h" ++ ++#include ++#include ++#include ++#include ++ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++ ++#include ++#include ++ ++const char *sasldb_path = NULL, ++ *auxprop_plugin = "sasldb", ++ *pwcheck_method = ++#if 0 /* totally undocumented. See issue#374 */ ++ "auxprop-hashed" ++#else ++ "auxprop" ++#endif ++ ; ++ ++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; ++} ++ ++static int test_getopt(void *context __attribute__((unused)), ++ const char *plugin_name __attribute__((unused)), ++ const char *option, ++ const char **result, ++ unsigned *len) ++{ ++ if (sasldb_path && !strcmp(option, "sasldb_path")) { ++ *result = sasldb_path; ++ if (len) ++ *len = (unsigned) strlen(sasldb_path); ++ return SASL_OK; ++ } ++ ++ if (sasldb_path && !strcmp(option, "auxprop_plugin")) { ++ *result = auxprop_plugin; ++ if (len) ++ *len = (unsigned) strlen(auxprop_plugin); ++ return SASL_OK; ++ } ++ ++ if (sasldb_path && !strcmp(option, "pwcheck_method")) { ++ *result = pwcheck_method; ++ if (len) ++ *len = (unsigned) strlen(pwcheck_method); ++ return SASL_OK; ++ } ++ return SASL_FAIL; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ sasl_callback_t callbacks[3] = {}; ++ 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 c, r; ++ const char *sasl_mech = "GSSAPI"; ++ int plain = 0; ++ bool spnego = false; ++ bool zeromaxssf = false; ++ ++ while ((c = getopt(argc, argv, "c:P:zN")) != EOF) { ++ switch (c) { ++ case 'c': ++ parse_cb(&cb, cb_buf, 256, optarg); ++ break; ++ case 'P': ++ plain = 1; ++ sasldb_path = optarg; ++ break; ++ case 'z': ++ zeromaxssf = true; ++ break; ++ case 'N': ++ spnego = true; ++ break; ++ default: ++ break; ++ } ++ } ++ ++ /* 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_GETOPT; ++ callbacks[1].proc = (sasl_callback_ft)&test_getopt; ++ callbacks[1].context = NULL; ++ callbacks[2].id = SASL_CB_LIST_END; ++ callbacks[2].proc = NULL; ++ callbacks[2].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); ++ } ++ ++ if (cb.name) { ++ sasl_setprop(conn, SASL_CHANNEL_BINDING, &cb); ++ } ++ ++ if (plain) { ++ sasl_mech = "PLAIN"; ++ } ++ ++ 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, false); ++ ++ 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)); ++ exit(-1); ++ } ++ ++ while (r == SASL_CONTINUE) { ++ send_string(sd, data, len); ++ len = 8192; ++ recv_string(sd, buf, &len, true); ++ ++ 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.33.0 + diff --git a/cyrus-sasl.spec b/cyrus-sasl.spec index f0f6f0c42b7fe6f8f2943ecc419afbbe3a40c45c..09406a0bff3619381549fe12804533e98e711485 100644 --- a/cyrus-sasl.spec +++ b/cyrus-sasl.spec @@ -6,7 +6,7 @@ Name: cyrus-sasl Version: 2.1.27 -Release: 14 +Release: 15 Summary: The Cyrus SASL API Implementation License: BSD with advertising @@ -22,6 +22,7 @@ Patch3: backport-CVE-2022-24407-Escape-password-for-SQL-insert-update.patch Patch4: backport-configure-fix-check-for-dlsym-underscore.patch Patch5: backport-configure.ac-avoid-side-effects-in-AC_CACHE_VAL.patch Patch6: backport-configure.ac-properly-quote-macro-arguments.patch +Patch7: backport-add-testing-cases.patch BuildRequires: autoconf, automake, libtool, gdbm-devel, groff BuildRequires: krb5-devel >= 1.2.2, openssl-devel, pam-devel, pkgconfig @@ -264,6 +265,9 @@ getent passwd %{username} >/dev/null || useradd -r -g %{username} -d %{homedir} %changelog +* Thu Sep 1 2022 xuwenlong -2.1.27-15 +- add testing cases + * Fri May 20 2022 yixiangzhike - 2.1.27-14 - compatible with autoconf-2.71