diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/scripts/Configure-Makefile | 71 | ||||
-rwxr-xr-x | src/scripts/Configure-config.h | 3 | ||||
-rwxr-xr-x | src/scripts/MakeLinks | 4 | ||||
-rwxr-xr-x | src/scripts/lookups-Makefile | 15 | ||||
-rw-r--r-- | src/src/EDITME | 29 | ||||
-rw-r--r-- | src/src/auths/Makefile | 5 | ||||
-rw-r--r-- | src/src/auths/check_serv_cond.c | 43 | ||||
-rw-r--r-- | src/src/auths/cyrus_sasl.h | 1 | ||||
-rw-r--r-- | src/src/auths/get_no64_data.c | 4 | ||||
-rw-r--r-- | src/src/auths/gsasl_exim.c | 617 | ||||
-rw-r--r-- | src/src/auths/gsasl_exim.h | 42 | ||||
-rw-r--r-- | src/src/auths/heimdal_gssapi.c | 582 | ||||
-rw-r--r-- | src/src/auths/heimdal_gssapi.h | 39 | ||||
-rw-r--r-- | src/src/config.h.defaults | 2 | ||||
-rw-r--r-- | src/src/drtables.c | 53 | ||||
-rw-r--r-- | src/src/exim.c | 16 | ||||
-rw-r--r-- | src/src/exim.h | 8 | ||||
-rw-r--r-- | src/src/functions.h | 2 | ||||
-rw-r--r-- | src/src/globals.h | 1 | ||||
-rw-r--r-- | src/src/macros.h | 5 | ||||
-rw-r--r-- | src/src/structs.h | 2 | ||||
-rw-r--r-- | src/src/tls-gnu.c | 19 | ||||
-rw-r--r-- | src/src/tls.c | 1 |
23 files changed, 1531 insertions, 33 deletions
diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 8a4362a74..5ef0ff7f0 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -111,6 +111,76 @@ do if test -r ../$f fi done >> $mft || exit 1 +# handle pkg-config +# beware portability of extended regexps with sed. + +egrep "^[$st]*(AUTH|LOOKUP)_[A-Z0-9_]*[$st]*=[$st]*" $mft | \ + sed "s/[$st]*=/='/" | \ + sed "s/\$/'/" > $mftt +egrep "^[$st]*((USE_(OPENSSL|GNUTLS)_PC)|SUPPORT_TLS|USE_GNUTLS)[$st]*=[$st]*" $mft | \ + sed "s/[$st]*=/='/" | \ + sed "s/\$/'/" >> $mftt +if test -s $mftt +then + ( + echo "# pkg-config fixups" + . ./$mftt + for var in `cut -d = -f 1 < $mftt`; do + case $var in + + USE_*_PC) + eval "pc_value=\"\$$var\"" + need_this='' + if [ ".$SUPPORT_TLS" = "." ]; then + # no TLS, not referencing + true + elif [ ".$var" = ".USE_GNUTLS_PC" ] && [ ".$USE_GNUTLS" != "." ]; then + need_this=t + elif [ ".$var" = ".USE_OPENSSL_PC" ] && [ ".$USE_GNUTLS" = "." ]; then + need_this=t + fi + if [ ".$need_this" != "." ]; then + tls_include=`pkg-config --cflags $pc_value` + tls_libs=`pkg-config --libs $pc_value` + echo "TLS_INCLUDE=$tls_include" + echo "TLS_LIBS=$tls_libs" + fi + ;; + + *_PC) + eval "pc_value=\"\$$var\"" + base=`echo $var | sed 's/_PC$//'` + eval "basevalue=\"\$$base\"" + if [ ".$basevalue" = "." ]; then + # not pulling in this module, _PC defined as default? Ignore + true + elif [ $basevalue = 2 ]; then + # module; handled in scripts/lookups-Makefile + true + else + # main binary + cflags=`pkg-config --cflags $pc_value` + libs=`pkg-config --libs $pc_value` + if [ "$var" != "${var#LOOKUP_}" ]; then + echo "LOOKUP_INCLUDE += $cflags" + echo "LOOKUP_LIBS += $libs" + elif [ "$var" != "${var#AUTH_}" ]; then + echo "CFLAGS += $cflags" + echo "AUTH_LIBS += $libs" + else + echo >&2 "Don't know how to handle pkg-config for $var" + fi + fi + ;; + + esac + done + echo "# End of pkg-config fixups" + echo + ) >> $mft +fi +rm -f $mftt + # make the lookups Makefile with the definitions ## prepend stuff here; eg: grep LOOKUP_ $mft > $look_mft @@ -184,4 +254,5 @@ else echo " " exit 1; fi +# vim: set ft=sh : # End of Configure-Makefile diff --git a/src/scripts/Configure-config.h b/src/scripts/Configure-config.h index c23523995..75d366fca 100755 --- a/src/scripts/Configure-config.h +++ b/src/scripts/Configure-config.h @@ -34,7 +34,7 @@ $MAKE buildconfig || exit 1 st=' ' (sed -n \ - "/\\\$/d;s/#.*\$//;s/^[$st]*\\([A-Z][^:$st]*\\)[$st]*=[$st]*\\([^$st]*\\)[$st]*\$/\\1=\\2 export \\1/p" \ + "/\\\$/d;s/#.*\$//;s/^[$st]*\\([A-Z][^:!+$st]*\\)[$st]*=[$st]*\\([^$st]*\\)[$st]*\$/\\1=\\2 export \\1/p" \ < Makefile ; echo "./buildconfig") | /bin/sh # If buildconfig ends with an error code, it will have output an error @@ -56,4 +56,5 @@ fi echo ">>> config.h built" echo "" +# vim: set ft=sh : # End of Configure-config.h diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 082659c99..166a25f88 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -123,8 +123,12 @@ ln -s ../../src/auths/call_radius.c call_radius.c ln -s ../../src/auths/check_serv_cond.c check_serv_cond.c ln -s ../../src/auths/cyrus_sasl.c cyrus_sasl.c ln -s ../../src/auths/cyrus_sasl.h cyrus_sasl.h +ln -s ../../src/auths/gsasl_exim.c gsasl_exim.c +ln -s ../../src/auths/gsasl_exim.h gsasl_exim.h ln -s ../../src/auths/get_data.c get_data.c ln -s ../../src/auths/get_no64_data.c get_no64_data.c +ln -s ../../src/auths/heimdal_gssapi.c heimdal_gssapi.c +ln -s ../../src/auths/heimdal_gssapi.h heimdal_gssapi.h ln -s ../../src/auths/md5.c md5.c ln -s ../../src/auths/xtextencode.c xtextencode.c ln -s ../../src/auths/xtextdecode.c xtextdecode.c diff --git a/src/scripts/lookups-Makefile b/src/scripts/lookups-Makefile index cd0a51b34..14c15259e 100755 --- a/src/scripts/lookups-Makefile +++ b/src/scripts/lookups-Makefile @@ -84,7 +84,7 @@ OBJ="" emit_module_rule() { local lookup_name="$1" - local mod_name + local mod_name pkgconf if [ "${lookup_name%:*}" = "$lookup_name" ] then mod_name=$(echo $lookup_name | tr A-Z a-z) @@ -100,9 +100,16 @@ emit_module_rule() { exit 1 fi MODS="${MODS} ${mod_name}.so" - grep "^LOOKUP_${lookup_name}_" "$defs_source" - echo "LOOKUP_${mod_name}_INCLUDE = \$(LOOKUP_${lookup_name}_INCLUDE)" - echo "LOOKUP_${mod_name}_LIBS = \$(LOOKUP_${lookup_name}_LIBS)" + pkgconf=$(grep "^LOOKUP_${lookup_name}_PC" "$defs_source") + if [ $? -eq 0 ]; then + pkgconf=$(echo $pkgconf | sed 's/^.*= *//') + echo "LOOKUP_${mod_name}_INCLUDE = $(pkg-config --cflags $pkgconf)" + echo "LOOKUP_${mod_name}_LIBS = $(pkg-config --libs $pkgconf)" + else + grep "^LOOKUP_${lookup_name}_" "$defs_source" + echo "LOOKUP_${mod_name}_INCLUDE = \$(LOOKUP_${lookup_name}_INCLUDE)" + echo "LOOKUP_${mod_name}_LIBS = \$(LOOKUP_${lookup_name}_LIBS)" + fi elif want_at_all "$lookup_name" then OBJ="${OBJ} ${mod_name}.o" diff --git a/src/src/EDITME b/src/src/EDITME index a180cd5cd..fc57054bf 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -279,6 +279,10 @@ TRANSPORT_SMTP=yes # the dynamic library and not the exim binary will be linked against the # library. # NOTE: LDAP cannot be built as a module! +# +# If your system has pkg-config then the _INCLUDE/_LIBS setting can be +# handled for you automatically by also defining the _PC variable to reference +# the name of the pkg-config package, if such is available. LOOKUP_DBM=yes LOOKUP_LSEARCH=yes @@ -295,6 +299,7 @@ LOOKUP_DNSDB=yes # LOOKUP_PASSWD=yes # LOOKUP_PGSQL=yes # LOOKUP_SQLITE=yes +# LOOKUP_SQLITE_PC=sqlite3 # LOOKUP_WHOSON=yes # These two settings are obsolete; all three lookups are compiled when @@ -342,6 +347,8 @@ PCRE_LIBS=-lpcre # don't need to set LOOKUP_INCLUDE if the relevant directories are already # specified in INCLUDE. The settings below are just examples; -lpq is for # PostgreSQL, -lgds is for Interbase, -lsqlite3 is for SQLite. +# +# You do not need to use this for any lookup information added via pkg-config. # LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include -I /usr/local/pgsql/include # LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3 @@ -553,6 +560,10 @@ FIXED_NEVER_USERS=root # AUTH_CRAM_MD5=yes # AUTH_CYRUS_SASL=yes # AUTH_DOVECOT=yes +# AUTH_GSASL=yes +# AUTH_GSASL_PC=libgsasl +# AUTH_HEIMDAL_GSSAPI=yes +# AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi # AUTH_PLAINTEXT=yes # AUTH_SPA=yes @@ -560,9 +571,13 @@ FIXED_NEVER_USERS=root #------------------------------------------------------------------------------ # If you specified AUTH_CYRUS_SASL above, you should ensure that you have the # Cyrus SASL library installed before trying to build Exim, and you probably -# want to uncomment the following line: +# want to uncomment the first line below. +# Similarly for GNU SASL, unless pkg-config is used via AUTH_GSASL_PC. +# Ditto for AUTH_HEIMDAL_GSSAPI(_PC). # AUTH_LIBS=-lsasl2 +# AUTH_LIBS=-lgsasl +# AUTH_LIBS=-lgssapi -lheimntlm -lkrb5 -lhx509 -lcom_err -lhcrypto -lasn1 -lwind -lroken -lcrypt #------------------------------------------------------------------------------ @@ -655,11 +670,14 @@ HEADERS_CHARSET="ISO-8859-1" # This setting is required for any TLS support (either OpenSSL or GnuTLS) # SUPPORT_TLS=yes -# Uncomment this setting if you are using OpenSSL +# Uncomment one of these settings if you are using OpenSSL; pkg-config vs not +# USE_OPENSSL_PC=openssl # TLS_LIBS=-lssl -lcrypto -# Uncomment these settings if you are using GnuTLS +# Uncomment the first and either the second or the third of these if you +# are using GnuTLS. If you have pkg-config, then the second, else the third. # USE_GNUTLS=yes +# USE_GNUTLS_PC=gnutls # TLS_LIBS=-lgnutls -ltasn1 -lgcrypt # If you are running Exim as a server, note that just building it with TLS @@ -670,6 +688,11 @@ HEADERS_CHARSET="ISO-8859-1" # if you are running Exim only as a client, building it with TLS support # is all you need to do. +# If you are using pkg-config then you should not need to worry where the +# libraries and headers are installed, as the pkg-config .pc specification +# should include all -L/-I information necessary. If not using pkg-config +# then you might need to specify the locations too. + # Additional libraries and include files are required for both OpenSSL and # GnuTLS. The TLS_LIBS settings above assume that the libraries are installed # with all your other libraries. If they are in a special directory, you may diff --git a/src/src/auths/Makefile b/src/src/auths/Makefile index 3e0e1a2cd..c6ef218b2 100644 --- a/src/src/auths/Makefile +++ b/src/src/auths/Makefile @@ -7,7 +7,8 @@ OBJ = auth-spa.o b64decode.o b64encode.o call_pam.o call_pwcheck.o \ call_radius.o check_serv_cond.o cram_md5.o cyrus_sasl.o dovecot.o \ - get_data.o get_no64_data.o md5.o plaintext.o pwcheck.o sha1.o \ + get_data.o get_no64_data.o gsasl_exim.o heimdal_gssapi.o \ + md5.o plaintext.o pwcheck.o sha1.o \ spa.o xtextdecode.o xtextencode.o auths.a: $(OBJ) @@ -38,6 +39,8 @@ xtextencode.o: $(HDRS) xtextencode.c cram_md5.o: $(HDRS) cram_md5.c cram_md5.h cyrus_sasl.o: $(HDRS) cyrus_sasl.c cyrus_sasl.h dovecot.o: $(HDRS) dovecot.c dovecot.h +gsasl_exim.o: $(HDRS) gsasl_exim.c gsasl_exim.h +heimdal_gssapi.o: $(HDRS) heimdal_gssapi.c heimdal_gssapi.h plaintext.o: $(HDRS) plaintext.c plaintext.h spa.o: $(HDRS) spa.c spa.h diff --git a/src/src/auths/check_serv_cond.c b/src/src/auths/check_serv_cond.c index 476d112ae..c10ff1be4 100644 --- a/src/src/auths/check_serv_cond.c +++ b/src/src/auths/check_serv_cond.c @@ -16,8 +16,8 @@ by all authenticators. */ *************************************************/ /* This function is called from the server code of all authenticators. For -plaintext, it is always called: the argument cannot be empty, because for -plaintext, setting server_condition is what enables it as a server +plaintext and gsasl, it is always called: the argument cannot be empty, because +for those, setting server_condition is what enables it as a server authenticator. For all the other authenticators, this function is called after they have authenticated, to enable additional authorization to be done. @@ -32,12 +32,40 @@ Returns: int auth_check_serv_cond(auth_instance *ablock) { + return auth_check_some_cond(ablock, + US"server_condition", ablock->server_condition, OK); +} + + +/************************************************* +* Check some server condition * +*************************************************/ + +/* This underlies server_condition, but is also used for some more generic + checks. + +Arguments: + ablock the authenticator's instance block + label debugging label naming the string checked + condition the condition string to be expanded and checked + unset value to return on NULL condition + +Returns: + OK success (or unset=OK) + DEFER couldn't complete the check + FAIL authentication failed +*/ + +int +auth_check_some_cond(auth_instance *ablock, + uschar *label, uschar *condition, int unset) +{ uschar *cond; HDEBUG(D_auth) { int i; - debug_printf("%s authenticator:\n", ablock->name); + debug_printf("%s authenticator %s:\n", ablock->name, label); for (i = 0; i < AUTH_VARS; i++) { if (auth_vars[i] != NULL) @@ -51,8 +79,13 @@ HDEBUG(D_auth) /* For the plaintext authenticator, server_condition is never NULL. For the rest, an unset condition lets everything through. */ -if (ablock->server_condition == NULL) return OK; -cond = expand_string(ablock->server_condition); +/* For server_condition, an unset condition lets everything through. +For plaintext/gsasl authenticators, it will have been pre-checked to prevent +this. We return the unset scenario value given to us, which for +server_condition will be OK and otherwise will typically be FAIL. */ + +if (condition == NULL) return unset; +cond = expand_string(condition); HDEBUG(D_auth) { diff --git a/src/src/auths/cyrus_sasl.h b/src/src/auths/cyrus_sasl.h index 7e62e63aa..031e783ed 100644 --- a/src/src/auths/cyrus_sasl.h +++ b/src/src/auths/cyrus_sasl.h @@ -31,5 +31,6 @@ extern void auth_cyrus_sasl_init(auth_instance *); extern int auth_cyrus_sasl_server(auth_instance *, uschar *); extern int auth_cyrus_sasl_client(auth_instance *, smtp_inblock *, smtp_outblock *, int, uschar *, int); +extern void auth_cyrus_sasl_version_report(FILE *f); /* End of cyrus_sasl.h */ diff --git a/src/src/auths/get_no64_data.c b/src/src/auths/get_no64_data.c index 4055c0411..ea5fd6f6d 100644 --- a/src/src/auths/get_no64_data.c +++ b/src/src/auths/get_no64_data.c @@ -14,8 +14,8 @@ /* This function is used by authentication drivers to output a challenge to the SMTP client and read the response line. This version does not use base -64 encoding for the text on the 334 line. It is used by the SPA and dovecot -authenticators. +64 encoding for the text on the 334 line. It is used by the SPA, dovecot +and gsasl authenticators. Arguments: aptr set to point to the response (which is in big_buffer) diff --git a/src/src/auths/gsasl_exim.c b/src/src/auths/gsasl_exim.c new file mode 100644 index 000000000..aef337c44 --- /dev/null +++ b/src/src/auths/gsasl_exim.c @@ -0,0 +1,617 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock <pdp@exim.org> */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to GNU SASL library for generic authentication. */ + +/* Trade-offs: + +GNU SASL does not provide authentication data itself, so we have to expose +that decision to configuration. For some mechanisms, we need to act much +like plaintext. For others, we only need to be able to provide some +evaluated data on demand. There's no abstracted way (ie, without hardcoding +knowledge of authenticators here) to know which need what properties; we +can't query a session or the library for "we will need these for mechanism X". + +So: we always require server_condition, even if sometimes it will just be +set as "yes". We do provide a number of other hooks, which might not make +sense in all contexts. For some, we can do checks at init time. +*/ + +#include "../exim.h" + +#ifndef AUTH_GSASL +/* dummy function to satisfy compilers when we link in an "empty" file. */ +static void dummy(int x) { dummy(x-1); } +#else + +#include <gsasl.h> +#include "gsasl_exim.h" + +/* Authenticator-specific options. */ +/* I did have server_*_condition options for various mechanisms, but since +we only ever handle one mechanism at a time, I didn't see the point in keeping +that. In case someone sees a point, I've left the condition_check() API +alone. */ +optionlist auth_gsasl_options[] = { + { "server_channelbinding", opt_bool, + (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) }, + { "server_hostname", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_hostname)) }, + { "server_mech", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_mech)) }, + { "server_password", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_password)) }, + { "server_realm", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_realm)) }, + { "server_scram_iter", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) }, + { "server_scram_salt", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) }, + { "server_service", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_service)) } +}; +/* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing +hooks to avoid cleartext passwords in the Exim server. */ + +int auth_gsasl_options_count = + sizeof(auth_gsasl_options)/sizeof(optionlist); + +/* Defaults for the authenticator-specific options. */ +auth_gsasl_options_block auth_gsasl_option_defaults = { + US"smtp", /* server_service */ + US"$primary_hostname", /* server_hostname */ + NULL, /* server_realm */ + NULL, /* server_mech */ + NULL, /* server_password */ + NULL, /* server_scram_iter */ + NULL, /* server_scram_salt */ + FALSE /* server_channelbinding */ +}; + +/* "Globals" for managing the gsasl interface. */ + +static Gsasl *gsasl_ctx = NULL; +static int + main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop); +static int + server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock); +static int + client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock); + +static BOOL sasl_error_should_defer = FALSE; +static Gsasl_property callback_loop = 0; +static BOOL checked_server_condition = FALSE; + +enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 }; + +struct callback_exim_state { + auth_instance *ablock; + int currently; +}; + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +auth_gsasl_init(auth_instance *ablock) +{ + char *p; + int rc, supported; + auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); + + /* As per existing Cyrus glue, use the authenticator's public name as + the default for the mechanism name; we don't handle multiple mechanisms + in one authenticator, but the same driver can be used multiple times. */ + + if (ob->server_mech == NULL) + ob->server_mech = string_copy(ablock->public_name); + + /* Can get multiple session contexts from one library context, so just + initialise the once. */ + if (gsasl_ctx == NULL) { + rc = gsasl_init(&gsasl_ctx); + if (rc != GSASL_OK) { + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't initialise GNU SASL library: %s (%s)", + ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc)); + } + gsasl_callback_set(gsasl_ctx, main_callback); + } + + /* We don't need this except to log it for debugging. */ + rc = gsasl_server_mechlist(gsasl_ctx, &p); + if (rc != GSASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "failed to retrieve list of mechanisms: %s (%s)", + ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p); + + supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech); + if (!supported) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "GNU SASL does not support mechanism \"%s\"", + ablock->name, ob->server_mech); + + if ((ablock->server_condition == NULL) && + (streqic(ob->server_mech, US"EXTERNAL") || + streqic(ob->server_mech, US"ANONYMOUS") || + streqic(ob->server_mech, US"PLAIN") || + streqic(ob->server_mech, US"LOGIN"))) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Need server_condition for %s mechanism", + ablock->name, ob->server_mech); + + /* This does *not* scale to new SASL mechanisms. Need a better way to ask + which properties will be needed. */ + if ((ob->server_realm == NULL) && + streqic(ob->server_mech, US"DIGEST-MD5")) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Need server_realm for %s mechanism", + ablock->name, ob->server_mech); + + /* At present, for mechanisms we don't panic on absence of server_condition; + need to figure out the most generically correct approach to deciding when + it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism, + etc) it clearly is critical. + + So don't activate without server_condition, this might be relaxed in the future. + */ + if (ablock->server_condition != NULL) ablock->server = TRUE; + ablock->client = FALSE; +} + + +/* GNU SASL uses one top-level callback, registered at library level. +We dispatch to client and server functions instead. */ + +static int +main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) +{ + int rc = 0; + struct callback_exim_state *cb_state = + (struct callback_exim_state *)gsasl_session_hook_get(sctx); + + HDEBUG(D_auth) + debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n", + prop, callback_loop); + + if (cb_state == NULL) { + HDEBUG(D_auth) debug_printf(" not from our server/client processing.\n"); + return GSASL_NO_CALLBACK; + } + + if (callback_loop > 0) { + /* Most likely is that we were asked for property foo, and to + expand the string we asked for property bar to put into an auth + variable, but property bar is not supplied for this mechanism. */ + HDEBUG(D_auth) + debug_printf("Loop, asked for property %d while handling property %d\n", + prop, callback_loop); + return GSASL_NO_CALLBACK; + } + callback_loop = prop; + + if (cb_state->currently == CURRENTLY_CLIENT) + rc = client_callback(ctx, sctx, prop, cb_state->ablock); + else if (cb_state->currently == CURRENTLY_SERVER) + rc = server_callback(ctx, sctx, prop, cb_state->ablock); + else { + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "unhandled callback state, bug in Exim", cb_state->ablock->name); + /* NOTREACHED */ + } + + callback_loop = 0; + return rc; +} + + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_server(auth_instance *ablock, uschar *initial_data) +{ + char *tmps; + char *to_send, *received; + Gsasl_session *sctx = NULL; + auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); + struct callback_exim_state cb_state; + int rc, auth_result, exim_error, exim_error_override; + + HDEBUG(D_auth) + debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n", + ablock->name, ob->server_mech); + + rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx); + if (rc != GSASL_OK) { + auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg); + return DEFER; + } + /* Hereafter: gsasl_finish(sctx) please */ + + gsasl_session_hook_set(sctx, (void *)ablock); + cb_state.ablock = ablock; + cb_state.currently = CURRENTLY_SERVER; + gsasl_session_hook_set(sctx, (void *)&cb_state); + + tmps = CS expand_string(ob->server_service); + gsasl_property_set(sctx, GSASL_SERVICE, tmps); + tmps = CS expand_string(ob->server_hostname); + gsasl_property_set(sctx, GSASL_HOSTNAME, tmps); + if (ob->server_realm) { + tmps = CS expand_string(ob->server_realm); + if (tmps && *tmps) { + gsasl_property_set(sctx, GSASL_REALM, tmps); + } + } + /* We don't support protection layers. */ + gsasl_property_set(sctx, GSASL_QOPS, "qop-auth"); +#ifdef SUPPORT_TLS + if (tls_channelbinding_b64) { + /* Some auth mechanisms can ensure that both sides are talking withing the + same security context; for TLS, this means that even if a bad certificate + has been accepted, they remain MitM-proof because both sides must be within + the same negotiated session; if someone is terminating one sesson and + proxying data on within a second, authentication will fail. + + We might not have this available, depending upon TLS implementation, + ciphersuite, phase of moon ... + + If we do, it results in extra SASL mechanisms being available; here, + Exim's one-mechanism-per-authenticator potentially causes problems. + It depends upon how GNU SASL will implement the PLUS variants of GS2 + and whether it automatically mandates a switch to the bound PLUS + if the data is available. Since default-on, despite being more secure, + would then result in mechanism name changes on a library update, we + have little choice but to default it off and let the admin choose to + enable it. *sigh* + */ + if (ob->server_channelbinding) { + HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", + ablock->name); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, + (const char *) tls_channelbinding_b64); + } else { + HDEBUG(D_auth) + debug_printf("Auth %s: Not enabling channel-binding (data available)\n", + ablock->name); + } + } else { + HDEBUG(D_auth) + debug_printf("Auth %s: no channel-binding data available\n", + ablock->name); + } +#endif + + checked_server_condition = FALSE; + + received = CS initial_data; + to_send = NULL; + exim_error = exim_error_override = OK; + + do { + rc = gsasl_step64(sctx, received, &to_send); + + switch (rc) { + case GSASL_OK: + if (!to_send) + goto STOP_INTERACTION; + break; + + case GSASL_NEEDS_MORE: + break; + + case GSASL_AUTHENTICATION_ERROR: + case GSASL_INTEGRITY_ERROR: + case GSASL_NO_AUTHID: + case GSASL_NO_ANONYMOUS_TOKEN: + case GSASL_NO_AUTHZID: + case GSASL_NO_PASSWORD: + case GSASL_NO_PASSCODE: + case GSASL_NO_PIN: + case GSASL_BASE64_ERROR: + HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + log_write(0, LOG_REJECT, "%s authenticator (%s):\n " + "GNU SASL permanent failure: %s (%s)", + ablock->name, ob->server_mech, + gsasl_strerror_name(rc), gsasl_strerror(rc)); + if (rc == GSASL_BASE64_ERROR) + exim_error_override = BAD64; + goto STOP_INTERACTION; + + default: + auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg); + exim_error_override = DEFER; + goto STOP_INTERACTION; + } + + if ((rc == GSASL_NEEDS_MORE) || + (to_send && *to_send)) + exim_error = + auth_get_no64_data((uschar **)&received, (uschar *)to_send); + + if (to_send) { + free(to_send); + to_send = NULL; + } + + if (exim_error) + break; /* handles * cancelled check */ + + } while (rc == GSASL_NEEDS_MORE); + +STOP_INTERACTION: + auth_result = rc; + + gsasl_finish(sctx); + + /* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */ + + if (exim_error != OK) + return exim_error; + + if (auth_result != GSASL_OK) { + HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n", + gsasl_strerror_name(auth_result), gsasl_strerror(auth_result)); + if (exim_error_override != OK) + return exim_error_override; /* might be DEFER */ + if (sasl_error_should_defer) /* overriding auth failure SASL error */ + return DEFER; + return FAIL; + } + + /* Auth succeeded, check server_condition unless already done in callback */ + return checked_server_condition ? OK : auth_check_serv_cond(ablock); +} + +/* returns the GSASL status of expanding the Exim string given */ +static int +condition_check(auth_instance *ablock, uschar *label, uschar *condition_string) +{ + int exim_rc; + + exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL); + + if (exim_rc == OK) { + return GSASL_OK; + } else if (exim_rc == DEFER) { + sasl_error_should_defer = TRUE; + return GSASL_AUTHENTICATION_ERROR; + } else if (exim_rc == FAIL) { + return GSASL_AUTHENTICATION_ERROR; + } + + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Unhandled return from checking %s: %d", + ablock->name, label, exim_rc); + /* NOTREACHED */ + return GSASL_AUTHENTICATION_ERROR; +} + +static int +server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) +{ + char *tmps; + uschar *propval; + int cbrc = GSASL_NO_CALLBACK; + int i; + auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); + + HDEBUG(D_auth) + debug_printf("GNU SASL callback %d for %s/%s as server\n", + prop, ablock->name, ablock->public_name); + + for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; + expand_nmax = 0; + + switch (prop) { + case GSASL_VALIDATE_SIMPLE: + /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */ + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_PASSWORD); + auth_vars[2] = expand_nstring[3] = propval ? propval : US""; + expand_nmax = 3; + for (i = 1; i <= 3; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + cbrc = condition_check(ablock, US"server_condition", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_EXTERNAL: + if (ablock->server_condition == NULL) { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL.\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID); + /* We always set $auth1, even if only to empty string. */ + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + expand_nlength[1] = Ustrlen(expand_nstring[1]); + expand_nmax = 1; + + cbrc = condition_check(ablock, + US"server_condition (EXTERNAL)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_ANONYMOUS: + if (ablock->server_condition == NULL) { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS.\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + propval = (uschar *) gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN); + /* We always set $auth1, even if only to empty string. */ + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + expand_nlength[1] = Ustrlen(expand_nstring[1]); + expand_nmax = 1; + + cbrc = condition_check(ablock, + US"server_condition (ANONYMOUS)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_GSSAPI: + /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME + The display-name is authenticated as part of GSS, the authzid is claimed + by the SASL integration after authentication; protected against tampering + (if the SASL mechanism supports that, which Kerberos does) but is + unverified, same as normal for other mechanisms. + + First coding, we had these values swapped, but for consistency and prior + to the first release of Exim with this authenticator, they've been + switched to match the ordering of GSASL_VALIDATE_SIMPLE. */ + propval = (uschar *) gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + expand_nmax = 2; + for (i = 1; i <= 2; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + /* In this one case, it perhaps makes sense to default back open? + But for consistency, let's just mandate server_condition here too. */ + cbrc = condition_check(ablock, + US"server_condition (GSSAPI family)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_PASSWORD: + /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + CRAM-MD5: GSASL_AUTHID + PLAIN: GSASL_AUTHID and GSASL_AUTHZID + LOGIN: GSASL_AUTHID + */ + if (ob->server_scram_iter) { + tmps = CS expand_string(ob->server_scram_iter); + gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps); + } + if (ob->server_scram_salt) { + tmps = CS expand_string(ob->server_scram_salt); + gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps); + } + /* Asking for GSASL_AUTHZID calls back into us if we use + gsasl_property_get(), thus the use of gsasl_property_fast(). + Do we really want to hardcode limits per mechanism? What happens when + a new mechanism is added to the library. It *shouldn't* result in us + needing to add more glue, since avoiding that is a large part of the + point of SASL. */ + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_REALM); + auth_vars[2] = expand_nstring[3] = propval ? propval : US""; + expand_nmax = 3; + for (i = 1; i <= 3; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + tmps = CS expand_string(ob->server_password); + if (tmps == NULL) { + sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE; + HDEBUG(D_auth) debug_printf("server_password expansion failed, so " + "can't tell GNU SASL library the password for %s\n", auth_vars[0]); + return GSASL_AUTHENTICATION_ERROR; + } + gsasl_property_set(sctx, GSASL_PASSWORD, tmps); + /* This is inadequate; don't think Exim's store stacks are geared + for memory wiping, so expanding strings will leave stuff laying around. + But no need to compound the problem, so get rid of the one we can. */ + memset(tmps, '\0', strlen(tmps)); + cbrc = GSASL_OK; + break; + + default: + HDEBUG(D_auth) debug_printf("Unrecognised callback: %d\n", prop); + cbrc = GSASL_NO_CALLBACK; + } + + HDEBUG(D_auth) debug_printf("Returning %s (%s)\n", + gsasl_strerror_name(cbrc), gsasl_strerror(cbrc)); + + return cbrc; +} + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_client( + auth_instance *ablock, /* authenticator block */ + smtp_inblock *inblock, /* connection inblock */ + smtp_outblock *outblock, /* connection outblock */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ + HDEBUG(D_auth) + debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n"); + /* NOT IMPLEMENTED */ + return FAIL; +} + +static int +client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) +{ + int cbrc = GSASL_NO_CALLBACK; + HDEBUG(D_auth) + debug_printf("GNU SASL callback %d for %s/%s as client\n", + prop, ablock->name, ablock->public_name); + + HDEBUG(D_auth) + debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n"); + + return cbrc; +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +void +auth_gsasl_version_report(FILE *f) +{ + const char *runtime; + runtime = gsasl_check_version(NULL); + fprintf(f, "Library version: GNU SASL: Compile: %s\n" + " Runtime: %s\n", + GSASL_VERSION, runtime); +} + +#endif /* AUTH_GSASL */ + +/* End of gsasl_exim.c */ diff --git a/src/src/auths/gsasl_exim.h b/src/src/auths/gsasl_exim.h new file mode 100644 index 000000000..785b8538b --- /dev/null +++ b/src/src/auths/gsasl_exim.h @@ -0,0 +1,42 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 */ + +/* Interface to GNU SASL library for generic authentication. */ + +/* Authenticator-specific options. */ + +typedef struct { + uschar *server_service; + uschar *server_hostname; + uschar *server_realm; + uschar *server_mech; + uschar *server_password; + uschar *server_scram_iter; + uschar *server_scram_salt; + BOOL server_channelbinding; +} auth_gsasl_options_block; + +/* Data for reading the authenticator-specific options. */ + +extern optionlist auth_gsasl_options[]; +extern int auth_gsasl_options_count; + +/* Defaults for the authenticator-specific options. */ + +extern auth_gsasl_options_block auth_gsasl_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_gsasl_init(auth_instance *); +extern int auth_gsasl_server(auth_instance *, uschar *); +extern int auth_gsasl_client(auth_instance *, smtp_inblock *, + smtp_outblock *, int, uschar *, int); +extern void auth_gsasl_version_report(FILE *f); + +/* End of gsasl_exim.h */ diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c new file mode 100644 index 000000000..9021509dd --- /dev/null +++ b/src/src/auths/heimdal_gssapi.c @@ -0,0 +1,582 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock <pdp@exim.org> */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to Heimdal library for GSSAPI authentication. */ + +/* Naming and rationale + +Sensibly, this integration would be deferred to a SASL library, but none +of them appear to offer keytab file selection interfaces in their APIs. It +might be that this driver only requires minor modification to work with MIT +Kerberos. + +Heimdal provides a number of interfaces for various forms of authentication. +As GS2 does not appear to provide keytab control interfaces either, we may +end up supporting that too. It's possible that we could trivially expand to +support NTLM support via Heimdal, etc. Rather than try to be too generic +immediately, this driver is directly only supporting GSSAPI. + +Without rename, we could add an option for GS2 support in the future. +*/ + +/* Sources + +* mailcheck-imap (Perl, client-side, written by me years ago) +* gsasl driver (GPL, server-side) +* heimdal sources and man-pages, plus http://www.h5l.org/manual/ +* FreeBSD man-pages (very informative!) +* http://www.ggf.org/documents/GFD.24.pdf confirming GSS_KRB5_REGISTER_ACCEPTOR_IDENTITY_X + semantics, that found by browsing Heimdal source to find how to set the keytab; however, + after multiple attempts I failed to get that to work and instead switched to + gsskrb5_register_acceptor_identity(). +*/ + +#include "../exim.h" + +#ifndef AUTH_HEIMDAL_GSSAPI +/* dummy function to satisfy compilers when we link in an "empty" file. */ +static void dummy(int x) { dummy(x-1); } +#else + +#include <gssapi/gssapi.h> +#include <gssapi/gssapi_krb5.h> + +/* for the _init debugging */ +#include <krb5.h> + +#include "heimdal_gssapi.h" + +/* Authenticator-specific options. */ +optionlist auth_heimdal_gssapi_options[] = { + { "server_hostname", opt_stringptr, + (void *)(offsetof(auth_heimdal_gssapi_options_block, server_hostname)) }, + { "server_keytab", opt_stringptr, + (void *)(offsetof(auth_heimdal_gssapi_options_block, server_keytab)) }, + { "server_service", opt_stringptr, + (void *)(offsetof(auth_heimdal_gssapi_options_block, server_service)) } +}; + +int auth_heimdal_gssapi_options_count = + sizeof(auth_heimdal_gssapi_options)/sizeof(optionlist); + +/* Defaults for the authenticator-specific options. */ +auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults = { + US"$primary_hostname", /* server_hostname */ + NULL, /* server_keytab */ + US"smtp", /* server_service */ +}; + +/* "Globals" for managing the heimdal_gssapi interface. */ + +/* Utility functions */ +static void + exim_heimdal_error_debug(const char *, krb5_context, krb5_error_code); +static int + exim_gssapi_error_defer(uschar *, OM_uint32, OM_uint32, const char *, ...) + PRINTF_FUNCTION(4, 5); + +#define EmptyBuf(buf) do { buf.value = NULL; buf.length = 0; } while (0) + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +/* Heimdal provides a GSSAPI extension method for setting the keytab; +in the init, we mostly just use raw krb5 methods so that we can report +the keytab contents, for -D+auth debugging. */ + +void +auth_heimdal_gssapi_init(auth_instance *ablock) +{ + krb5_context context; + krb5_keytab keytab; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_error_code krc; + char *principal, *enctype_s; + const char *k_keytab_typed_name = NULL; + auth_heimdal_gssapi_options_block *ob = + (auth_heimdal_gssapi_options_block *)(ablock->options_block); + + ablock->server = FALSE; + ablock->client = FALSE; + + if (!ob->server_service || !*ob->server_service) { + HDEBUG(D_auth) debug_printf("heimdal: missing server_service\n"); + return; + } + + krc = krb5_init_context(&context); + if (krc != 0) { + int kerr = errno; + HDEBUG(D_auth) debug_printf("heimdal: failed to initialise krb5 context: %s\n", + strerror(kerr)); + return; + } + + if (ob->server_keytab) { + k_keytab_typed_name = CCS string_sprintf("file:%s", expand_string(ob->server_keytab)); + HDEBUG(D_auth) debug_printf("heimdal: using keytab %s\n", k_keytab_typed_name); + krc = krb5_kt_resolve(context, k_keytab_typed_name, &keytab); + if (krc) { + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_resolve", context, krc); + return; + } + } else { + HDEBUG(D_auth) debug_printf("heimdal: using system default keytab\n"); + krc = krb5_kt_default(context, &keytab); + if (krc) { + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_default", context, krc); + return; + } + } + + HDEBUG(D_auth) { + /* http://www.h5l.org/manual/HEAD/krb5/krb5_keytab_intro.html */ + krc = krb5_kt_start_seq_get(context, keytab, &cursor); + if (krc) + exim_heimdal_error_debug("krb5_kt_start_seq_get", context, krc); + else { + while ((krc = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) { + principal = enctype_s = NULL; + krb5_unparse_name(context, entry.principal, &principal); + krb5_enctype_to_string(context, entry.keyblock.keytype, &enctype_s); + debug_printf("heimdal: keytab principal: %s vno=%d type=%s\n", + principal ? principal : "??", + entry.vno, + enctype_s ? enctype_s : "??"); + free(principal); + free(enctype_s); + krb5_kt_free_entry(context, &entry); + } + krc = krb5_kt_end_seq_get(context, keytab, &cursor); + if (krc) + exim_heimdal_error_debug("krb5_kt_end_seq_get", context, krc); + } + } + + krc = krb5_kt_close(context, keytab); + if (krc) + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_close", context, krc); + + krb5_free_context(context); + + /* RFC 4121 section 5.2, SHOULD support 64K input buffers */ + if (big_buffer_size < (64 * 1024)) { + uschar *newbuf; + big_buffer_size = 64 * 1024; + newbuf = store_malloc(big_buffer_size); + store_free(big_buffer); + big_buffer = newbuf; + } + + ablock->server = TRUE; +} + + +static void +exim_heimdal_error_debug(const char *label, + krb5_context context, krb5_error_code err) +{ + const char *kerrsc; + kerrsc = krb5_get_error_message(context, err); + debug_printf("heimdal %s: %s\n", label, kerrsc ? kerrsc : "unknown error"); + krb5_free_error_message(context, kerrsc); +} + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +/* GSSAPI notes: +OM_uint32: portable type for unsigned int32 +gss_buffer_desc / *gss_buffer_t: hold/point-to size_t .length & void *value + -- all strings/etc passed in should go through one of these + -- when allocated by gssapi, release with gss_release_buffer() +*/ + +int +auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data) +{ + gss_name_t gclient = GSS_C_NO_NAME; + gss_name_t gserver = GSS_C_NO_NAME; + gss_cred_id_t gcred = GSS_C_NO_CREDENTIAL; + gss_ctx_id_t gcontext = GSS_C_NO_CONTEXT; + uschar *ex_server_str; + gss_buffer_desc gbufdesc = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gbufdesc_in = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gbufdesc_out = GSS_C_EMPTY_BUFFER; + gss_OID mech_type; + OM_uint32 maj_stat, min_stat; + int step, error_out, i; + uschar *tmp1, *tmp2, *from_client; + auth_heimdal_gssapi_options_block *ob = + (auth_heimdal_gssapi_options_block *)(ablock->options_block); + BOOL handled_empty_ir; + uschar *store_reset_point; + uschar *keytab; + uschar sasl_config[4]; + uschar requested_qop; + + store_reset_point = store_get(0); + + HDEBUG(D_auth) + debug_printf("heimdal: initialising auth context for %s\n", ablock->name); + + /* Construct our gss_name_t gserver describing ourselves */ + tmp1 = expand_string(ob->server_service); + tmp2 = expand_string(ob->server_hostname); + ex_server_str = string_sprintf("%s@%s", tmp1, tmp2); + gbufdesc.value = (void *) ex_server_str; + gbufdesc.length = Ustrlen(ex_server_str); + maj_stat = gss_import_name(&min_stat, + &gbufdesc, GSS_C_NT_HOSTBASED_SERVICE, &gserver); + if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "gss_import_name(%s)", CS gbufdesc.value); + + /* Use a specific keytab, if specified */ + if (ob->server_keytab) { + keytab = expand_string(ob->server_keytab); + maj_stat = gsskrb5_register_acceptor_identity(CCS keytab); + if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "registering keytab \"%s\"", keytab); + HDEBUG(D_auth) + debug_printf("heimdal: using keytab \"%s\"\n", keytab); + } + + /* Acquire our credentials */ + maj_stat = gss_acquire_cred(&min_stat, + gserver, /* desired name */ + 0, /* time */ + GSS_C_NULL_OID_SET, /* desired mechs */ + GSS_C_ACCEPT, /* cred usage */ + &gcred, /* handle */ + NULL /* actual mechs */, + NULL /* time rec */); + if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "gss_acquire_cred(%s)", ex_server_str); + + maj_stat = gss_release_name(&min_stat, &gserver); + + HDEBUG(D_auth) debug_printf("heimdal: have server credentials.\n"); + + /* Loop talking to client */ + step = 0; + from_client = initial_data; + handled_empty_ir = FALSE; + error_out = OK; + + /* buffer sizes: auth_get_data() uses big_buffer, which we grow per + GSSAPI RFC in _init, if needed, to meet the SHOULD size of 64KB. + (big_buffer starts life at the MUST size of 16KB). */ + + /* step values + 0: getting initial data from client to feed into GSSAPI + 1: iterating for as long as GSS_S_CONTINUE_NEEDED + 2: GSS_S_COMPLETE, SASL wrapping for authz and qop to send to client + 3: unpick final auth message from client + 4: break/finish (non-step) + */ + while (step < 4) { + switch (step) { + case 0: + if (!from_client || *from_client == '\0') { + if (handled_empty_ir) { + HDEBUG(D_auth) debug_printf("gssapi: repeated empty input, grr.\n"); + error_out = BAD64; + goto ERROR_OUT; + } else { + HDEBUG(D_auth) debug_printf("gssapi: missing initial response, nudging.\n"); + error_out = auth_get_data(&from_client, US"", 0); + if (error_out != OK) + goto ERROR_OUT; + handled_empty_ir = TRUE; + continue; + } + } + /* We should now have the opening data from the client, base64-encoded. */ + step += 1; + HDEBUG(D_auth) debug_printf("heimdal: have initial client data\n"); + break; + + case 1: + gbufdesc_in.length = auth_b64decode(from_client, USS &gbufdesc_in.value); + if (gclient) { + maj_stat = gss_release_name(&min_stat, &gclient); + gclient = GSS_C_NO_NAME; + } + maj_stat = gss_accept_sec_context(&min_stat, + &gcontext, /* context handle */ + gcred, /* acceptor cred handle */ + &gbufdesc_in, /* input from client */ + GSS_C_NO_CHANNEL_BINDINGS, /* XXX fixme: use the channel bindings from GnuTLS */ + &gclient, /* client identifier */ + &mech_type, /* mechanism in use */ + &gbufdesc_out, /* output to send to client */ + NULL, /* return flags */ + NULL, /* time rec */ + NULL /* delegated cred_handle */ + ); + if (GSS_ERROR(maj_stat)) { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_accept_sec_context()"); + error_out = FAIL; + goto ERROR_OUT; + } + if (&gbufdesc_out.length != 0) { + error_out = auth_get_data(&from_client, + gbufdesc_out.value, gbufdesc_out.length); + if (error_out != OK) + goto ERROR_OUT; + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + } + if (maj_stat == GSS_S_COMPLETE) { + step += 1; + HDEBUG(D_auth) debug_printf("heimdal: GSS complete\n"); + } else { + HDEBUG(D_auth) debug_printf("heimdal: need more data\n"); + } + break; + + case 2: + memset(sasl_config, 0xFF, 4); + /* draft-ietf-sasl-gssapi-06.txt defines bitmasks for first octet + 0x01 No security layer + 0x02 Integrity protection + 0x04 Confidentiality protection + + The remaining three octets are the maximum buffer size for wrapped + content. */ + sasl_config[0] = 0x01; /* Exim does not wrap/unwrap SASL layers after auth */ + gbufdesc.value = (void *) sasl_config; + gbufdesc.length = 4; + maj_stat = gss_wrap(&min_stat, + gcontext, + 0, /* conf_req_flag: integrity only */ + GSS_C_QOP_DEFAULT, /* qop requested */ + &gbufdesc, /* message to protect */ + NULL, /* conf_state: no confidentiality applied */ + &gbufdesc_out /* output buffer */ + ); + if (GSS_ERROR(maj_stat)) { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_wrap(SASL state after auth)"); + error_out = FAIL; + goto ERROR_OUT; + } + + HDEBUG(D_auth) debug_printf("heimdal SASL: requesting QOP with no security layers\n"); + + error_out = auth_get_data(&from_client, + gbufdesc_out.value, gbufdesc_out.length); + if (error_out != OK) + goto ERROR_OUT; + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + step += 1; + break; + + case 3: + gbufdesc_in.length = auth_b64decode(from_client, USS &gbufdesc_in.value); + maj_stat = gss_unwrap(&min_stat, + gcontext, + &gbufdesc_in, /* data from client */ + &gbufdesc_out, /* results */ + NULL, /* conf state */ + NULL /* qop state */ + ); + if (GSS_ERROR(maj_stat)) { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_unwrap(final SASL message from client)"); + error_out = FAIL; + goto ERROR_OUT; + } + if (gbufdesc_out.length < 5) { + HDEBUG(D_auth) + debug_printf("gssapi: final message too short; " + "need flags, buf sizes and authzid\n"); + error_out = FAIL; + goto ERROR_OUT; + } + + requested_qop = (CS gbufdesc_out.value)[0]; + if ((requested_qop & 0x01) == 0) { + HDEBUG(D_auth) + debug_printf("gssapi: client requested security layers (%x)\n", + (unsigned int) requested_qop); + error_out = FAIL; + goto ERROR_OUT; + } + + for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; + expand_nmax = 0; + + /* Identifiers: + The SASL provided identifier is an unverified authzid. + GSSAPI provides us with a verified identifier. + */ + + /* $auth2 is authzid requested at SASL layer */ + expand_nlength[2] = gbufdesc_out.length - 4; + auth_vars[1] = expand_nstring[2] = + string_copyn((US gbufdesc_out.value) + 4, expand_nlength[2]); + expand_nmax = 2; + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + + /* $auth1 is GSSAPI display name */ + maj_stat = gss_display_name(&min_stat, + gclient, + &gbufdesc_out, + &mech_type); + if (GSS_ERROR(maj_stat)) { + auth_vars[1] = expand_nstring[2] = NULL; + expand_nmax = 0; + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_display_name(client identifier)"); + error_out = FAIL; + goto ERROR_OUT; + } + + expand_nlength[1] = gbufdesc_out.length; + auth_vars[0] = expand_nstring[1] = + string_copyn(gbufdesc_out.value, gbufdesc_out.length); + + HDEBUG(D_auth) + debug_printf("heimdal SASL: happy with client request\n" + " auth1 (verified GSSAPI display-name): \"%s\"\n" + " auth2 (unverified SASL requested authzid): \"%s\"\n", + auth_vars[0], auth_vars[1]); + + step += 1; + break; + + } /* switch */ + } /* while step */ + + +ERROR_OUT: + maj_stat = gss_release_cred(&min_stat, &gcred); + if (gclient) { + gss_release_name(&min_stat, &gclient); + gclient = GSS_C_NO_NAME; + } + if (gbufdesc_out.length) { + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + } + if (gcontext != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&min_stat, &gcontext, GSS_C_NO_BUFFER); + } + + store_reset(store_reset_point); + + if (error_out != OK) + return error_out; + + /* Auth succeeded, check server_condition */ + return auth_check_serv_cond(ablock); +} + + +static int +exim_gssapi_error_defer(uschar *store_reset_point, + OM_uint32 major, OM_uint32 minor, + const char *format, ...) +{ + va_list ap; + uschar buffer[STRING_SPRINTF_BUFFER_SIZE]; + OM_uint32 maj_stat, min_stat; + OM_uint32 msgcontext = 0; + gss_buffer_desc status_string; + + va_start(ap, format); + if (!string_vformat(buffer, sizeof(buffer), format, ap)) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "exim_gssapi_error_defer expansion larger than %d", + sizeof(buffer)); + va_end(ap); + + auth_defer_msg = NULL; + + do { + maj_stat = gss_display_status(&min_stat, + major, GSS_C_GSS_CODE, GSS_C_NO_OID, + &msgcontext, &status_string); + + if (auth_defer_msg == NULL) { + auth_defer_msg = string_copy(US status_string.value); + } + + HDEBUG(D_auth) debug_printf("heimdal %s: %.*s\n", + buffer, (int)status_string.length, CS status_string.value); + gss_release_buffer(&min_stat, &status_string); + + } while (msgcontext != 0); + + if (store_reset_point) + store_reset(store_reset_point); + return DEFER; +} + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_heimdal_gssapi_client( + auth_instance *ablock, /* authenticator block */ + smtp_inblock *inblock, /* connection inblock */ + smtp_outblock *outblock, /* connection outblock */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ + HDEBUG(D_auth) + debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n"); + /* NOT IMPLEMENTED */ + return FAIL; +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +void +auth_heimdal_gssapi_version_report(FILE *f) +{ + /* No build-time constants available unless we link against libraries at + build-time and export the result as a string into a header ourselves. */ + fprintf(f, "Library version: Heimdal: Runtime: %s\n" + " Build Info: %s\n", + heimdal_version, heimdal_long_version); +} + +#endif /* AUTH_HEIMDAL_GSSAPI */ + +/* End of heimdal_gssapi.c */ diff --git a/src/src/auths/heimdal_gssapi.h b/src/src/auths/heimdal_gssapi.h new file mode 100644 index 000000000..a606a5c26 --- /dev/null +++ b/src/src/auths/heimdal_gssapi.h @@ -0,0 +1,39 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock <pdp@exim.org> */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to Heimdal library for GSSAPI authentication. */ + +/* Authenticator-specific options. */ + +typedef struct { + uschar *server_hostname; + uschar *server_keytab; + uschar *server_service; +} auth_heimdal_gssapi_options_block; + +/* Data for reading the authenticator-specific options. */ + +extern optionlist auth_heimdal_gssapi_options[]; +extern int auth_heimdal_gssapi_options_count; + +/* Defaults for the authenticator-specific options. */ + +extern auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_heimdal_gssapi_init(auth_instance *); +extern int auth_heimdal_gssapi_server(auth_instance *, uschar *); +extern int auth_heimdal_gssapi_client(auth_instance *, smtp_inblock *, + smtp_outblock *, int, uschar *, int); +extern void auth_heimdal_gssapi_version_report(FILE *f); + +/* End of heimdal_gssapi.h */ diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index bc983c444..f3e3d880a 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -20,6 +20,8 @@ it's a default value. */ #define AUTH_CRAM_MD5 #define AUTH_CYRUS_SASL #define AUTH_DOVECOT +#define AUTH_GSASL +#define AUTH_HEIMDAL_GSSAPI #define AUTH_PLAINTEXT #define AUTH_SPA diff --git a/src/src/drtables.c b/src/src/drtables.c index 37ecf4f4b..c87e9c23b 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -37,6 +37,14 @@ set to NULL for those that are not compiled into the binary. */ #include "auths/dovecot.h" #endif +#ifdef AUTH_GSASL +#include "auths/gsasl_exim.h" +#endif + +#ifdef AUTH_HEIMDAL_GSSAPI +#include "auths/heimdal_gssapi.h" +#endif + #ifdef AUTH_PLAINTEXT #include "auths/plaintext.h" #endif @@ -58,7 +66,8 @@ auth_info auths_available[] = { sizeof(auth_cram_md5_options_block), auth_cram_md5_init, /* init function */ auth_cram_md5_server, /* server function */ - auth_cram_md5_client /* client function */ + auth_cram_md5_client, /* client function */ + NULL /* diagnostic function */ }, #endif @@ -71,7 +80,8 @@ auth_info auths_available[] = { sizeof(auth_cyrus_sasl_options_block), auth_cyrus_sasl_init, /* init function */ auth_cyrus_sasl_server, /* server function */ - NULL /* client function */ + NULL, /* client function */ + auth_cyrus_sasl_version_report /* diagnostic function */ }, #endif @@ -84,7 +94,36 @@ auth_info auths_available[] = { sizeof(auth_dovecot_options_block), auth_dovecot_init, /* init function */ auth_dovecot_server, /* server function */ - NULL /* client function */ + NULL, /* client function */ + NULL /* diagnostic function */ + }, +#endif + +#ifdef AUTH_GSASL + { + US"gsasl", /* lookup name */ + auth_gsasl_options, + &auth_gsasl_options_count, + &auth_gsasl_option_defaults, + sizeof(auth_gsasl_options_block), + auth_gsasl_init, /* init function */ + auth_gsasl_server, /* server function */ + NULL, /* client function */ + auth_gsasl_version_report /* diagnostic function */ + }, +#endif + +#ifdef AUTH_HEIMDAL_GSSAPI + { + US"heimdal_gssapi", /* lookup name */ + auth_heimdal_gssapi_options, + &auth_heimdal_gssapi_options_count, + &auth_heimdal_gssapi_option_defaults, + sizeof(auth_heimdal_gssapi_options_block), + auth_heimdal_gssapi_init, /* init function */ + auth_heimdal_gssapi_server, /* server function */ + NULL, /* client function */ + auth_heimdal_gssapi_version_report /* diagnostic function */ }, #endif @@ -97,7 +136,8 @@ auth_info auths_available[] = { sizeof(auth_plaintext_options_block), auth_plaintext_init, /* init function */ auth_plaintext_server, /* server function */ - auth_plaintext_client /* client function */ + auth_plaintext_client, /* client function */ + NULL /* diagnostic function */ }, #endif @@ -110,11 +150,12 @@ auth_info auths_available[] = { sizeof(auth_spa_options_block), auth_spa_init, /* init function */ auth_spa_server, /* server function */ - auth_spa_client /* client function */ + auth_spa_client, /* client function */ + NULL /* diagnostic function */ }, #endif -{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL } +{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL } }; diff --git a/src/src/exim.c b/src/src/exim.c index a6c0d7832..4a690a783 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -725,6 +725,8 @@ Returns: nothing static void show_whats_supported(FILE *f) { + auth_info *authi; + #ifdef DB_VERSION_STRING fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING); #elif defined(BTREEVERSION) && defined(HASHVERSION) @@ -867,6 +869,12 @@ fprintf(f, "Authenticators:"); #ifdef AUTH_DOVECOT fprintf(f, " dovecot"); #endif +#ifdef AUTH_GSASL + fprintf(f, " gsasl"); +#endif +#ifdef AUTH_HEIMDAL_GSSAPI + fprintf(f, " heimdal_gssapi"); +#endif #ifdef AUTH_PLAINTEXT fprintf(f, " plaintext"); #endif @@ -962,9 +970,11 @@ DEBUG(D_any) do { tls_version_report(f); #endif -#ifdef AUTH_CYRUS_SASL - auth_cyrus_sasl_version_report(f); -#endif + for (authi = auths_available; *authi->driver_name != '\0'; ++authi) { + if (authi->version_report) { + (*authi->version_report)(f); + } + } fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n" " Runtime: %s\n", diff --git a/src/src/exim.h b/src/src/exim.h index a45ea0b30..626d33dae 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -538,12 +538,4 @@ default to EDQUOT if it exists, otherwise ENOSPC. */ #endif #endif -/* These are for reporting version information from various componenents, to -figure out what's actually happening. They need to be available to the main -function, so we declare them here. Unfortunate. */ - -#ifdef AUTH_CYRUS_SASL -extern void auth_cyrus_sasl_version_report(FILE *); -#endif - /* End of exim.h */ diff --git a/src/src/functions.h b/src/src/functions.h index 5efcbbb04..d2575946b 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -54,6 +54,8 @@ extern int auth_call_radius(uschar *, uschar **); extern int auth_call_saslauthd(uschar *, uschar *, uschar *, uschar *, uschar **); extern int auth_check_serv_cond(auth_instance *); +extern int auth_check_some_cond(auth_instance *, uschar *, uschar *, int); + extern int auth_get_data(uschar **, uschar *, int); extern int auth_get_no64_data(uschar **, uschar *); extern uschar *auth_xtextencode(uschar *, int); diff --git a/src/src/globals.h b/src/src/globals.h index 869a23e63..a51e3bc50 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -91,6 +91,7 @@ extern uschar *openssl_options; /* OpenSSL compatibility options */ extern const pcre *regex_STARTTLS; /* For recognizing STARTTLS settings */ extern uschar *tls_advertise_hosts; /* host for which TLS is advertised */ extern uschar *tls_certificate; /* Certificate file */ +extern uschar *tls_channelbinding_b64; /* string of base64 channel binding */ extern uschar *tls_crl; /* CRL File */ extern uschar *tls_dhparam; /* DH param file */ extern BOOL tls_offered; /* Server offered TLS */ diff --git a/src/src/macros.h b/src/src/macros.h index 451bc56fd..c1c4cc33f 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -72,6 +72,11 @@ as unsigned. */ ((uschar)(c) > 127 && print_topbitchars)) +/* Convenience for testing strings */ + +#define streqic(Foo, Bar) (strcmpic(Foo, Bar) == 0) + + /* When built with TLS support, the act of flushing SMTP output becomes a no-op once an SSL session is in progress. */ diff --git a/src/src/structs.h b/src/src/structs.h index 3790c7fe8..9b51d0b7c 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -367,6 +367,8 @@ typedef struct auth_info { int, /* command timeout */ uschar *, /* buffer for reading response */ int); /* sizeof buffer */ + void (*version_report)( /* diagnostic version reporting */ + FILE *); /* I/O stream to print to */ } auth_info; diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index dc09d4720..2f952e47b 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -855,6 +855,10 @@ construct_cipher_name(gnutls_session session) static uschar cipherbuf[256]; uschar *ver; int c, kx, mac; +#ifdef GNUTLS_CB_TLS_UNIQUE +int rc; +gnutls_datum_t channel; +#endif ver = string_copy( US gnutls_protocol_get_name(gnutls_protocol_get_version(session))); @@ -872,6 +876,21 @@ string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver, tls_cipher = cipherbuf; DEBUG(D_tls) debug_printf("cipher: %s\n", cipherbuf); + +if (tls_channelbinding_b64) + free(tls_channelbinding_b64); +tls_channelbinding_b64 = NULL; + +#ifdef GNUTLS_CB_TLS_UNIQUE +channel = { NULL, 0 }; +rc = gnutls_session_channel_binding(session, GNUTLS_CB_TLS_UNIQUE, &channel); +if (rc) { + DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); +} else { + tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size); + DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n"); +} +#endif } diff --git a/src/src/tls.c b/src/src/tls.c index 7cb1550f3..d975a2c89 100644 --- a/src/src/tls.c +++ b/src/src/tls.c @@ -40,6 +40,7 @@ static int ssl_xfer_buffer_hwm = 0; static int ssl_xfer_eof = 0; static int ssl_xfer_error = 0; +uschar *tls_channelbinding_b64 = NULL; /************************************************* |