From: Jeremy Harris Date: Fri, 16 Aug 2024 18:33:48 +0000 (+0100) Subject: authenticator dynamic modules X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/51a87a4dd1f2999485da7fe50c313e821dcc8dba?hp=62ccb00d5ca29f962c3691201e143667ff80bc6c authenticator dynamic modules --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 9fbf7a2db..f73792ac5 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -2073,6 +2073,7 @@ withdrawn. .cindex "lookup modules" .cindex "router modules" .cindex "transport modules" +.cindex "authenticator modules" .cindex "dynamic modules" .cindex ".so building" On some platforms, Exim supports not compiling all lookup types directly into @@ -2084,7 +2085,8 @@ dependencies. Most, but not all, lookup types can be built this way. .new -Similarly, router and transport drivers can be built as external modules. +Similarly, authenticator, router and transport drivers can be built +as external modules. This permits a smaller exim binary, growing only as needed for the runtime cofiguration. .wen diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 3a0e3efc5..adfd5cb8f 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -40,6 +40,11 @@ JH/07 Bug 3106: Fix coding in SPA authenticator. A macro argument was not JH/08 The output of "exim -bV" now includes lookup types built as dynamic-load modules. +JH/09 Not a change, but worthy of note: There is no test coverage of the + heimdall-gssapi authenticator driver. It does build, though with (on at + least one platform) library version conflicts with the gsasl auth + driver). Confidence in its operation is lacking. + Exim version 4.98 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index be0f0c679..1910ad002 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -14,8 +14,9 @@ Version 4.98 3. Events smtp:fail:protocol and smtp:fail:syntax - 4. JSON lookup support, all the router drivers except manualroute, and all the transport - drivers except smtp can now be built as lodable modules + 4. JSON lookup support, all the router drivers except manualroute, all the + transport drivers except smtp, and all the authenticator drivers except + plaintext, gsasl and spa can now be built as loadable modules Version 4.98 ------------ diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index d45524561..3f2d4d883 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -119,7 +119,7 @@ OBJ_MACRO = macro_predef.o \ macro-appendfile.o macro-autoreply.o macro-lmtp.o macro-pipe.o macro-queuefile.o \ macro-smtp.o macro-accept.o macro-dnslookup.o macro-ipliteral.o macro-iplookup.o \ macro-manualroute.o macro-queryprogram.o macro-redirect.o \ - macro-auth-spa.o macro-cram_md5.o macro-cyrus_sasl.o macro-dovecot.o macro-gsasl_exim.o \ + macro-auth-spa.o macro-cram_md5.o macro-cyrus_sasl.o macro-dovecot.o macro-gsasl.o \ macro-heimdal_gssapi.o macro-plaintext.o macro-spa.o macro-authtls.o macro-external.o \ macro-dkim.o macro-malware.o macro-signing.o @@ -206,9 +206,9 @@ macro-dovecot.o: auths/dovecot.c macro-external.o: auths/external.c @echo "$(CC) -DMACRO_PREDEF auths/external.c" $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/external.c -macro-gsasl_exim.o : auths/gsasl_exim.c - @echo "$(CC) -DMACRO_PREDEF auths/gsasl_exim.c" - $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/gsasl_exim.c +macro-gsasl.o : auths/gsasl.c + @echo "$(CC) -DMACRO_PREDEF auths/gsasl.c" + $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/gsasl.c macro-heimdal_gssapi.o: auths/heimdal_gssapi.c @echo "$(CC) -DMACRO_PREDEF auths/heimdal_gssapi.c" $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/heimdal_gssapi.c diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 79ab19dcc..9179392f3 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -298,8 +298,9 @@ do mv $class/Makefile.postdynamic $class/Makefile rm $class/Makefile.predynamic done <<-END - routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT + routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP + auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS END # See if there is a definition of EXIM_PERL in what we have built so far. diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 998b73bf9..76859ce9a 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -77,11 +77,12 @@ cd .. d="auths" mkdir $d cd $d -for f in README Makefile call_pam.c call_pwcheck.c \ - call_radius.c check_serv_cond.c cyrus_sasl.c cyrus_sasl.h gsasl_exim.c \ - gsasl_exim.h get_data.c get_no64_data.c heimdal_gssapi.c heimdal_gssapi.h \ +# Makefile is generated +for f in README call_pam.c call_pwcheck.c \ + call_radius.c check_serv_cond.c cyrus_sasl.c cyrus_sasl.h gsasl.c \ + gsasl.h get_data.c get_no64_data.c heimdal_gssapi.c heimdal_gssapi.h \ cram_md5.c cram_md5.h plaintext.c plaintext.h \ - pwcheck.c pwcheck.h auth-spa.c auth-spa.h dovecot.c dovecot.h sha1.c spa.c \ + pwcheck.c pwcheck.h auth-spa.c auth-spa.h dovecot.c dovecot.h spa.c \ spa.h tls.c tls.h external.c external.h do ln -s ../../src/$d/$f $f diff --git a/src/scripts/drivers-Makefile b/src/scripts/drivers-Makefile index 0c3da19cb..2dd958043 100755 --- a/src/scripts/drivers-Makefile +++ b/src/scripts/drivers-Makefile @@ -3,7 +3,6 @@ # Copyright (c) The Exim Maintainers 1995 - 2024 # SPDX-License-Identifier: GPL-2.0-or-later -set -e class=${CLASS:?} classdef=${CLASSDEF:?} drnames="${DRNAMES:?}" @@ -137,16 +136,17 @@ emit_module_rule() { exit 1 fi MODS="${MODS} ${mod_name}.so" -# pkgconf=$(grep "^${classdef}_${name}_PC" "$defs_source") -# if [ $? -eq 0 ]; then -# pkgconf=$(echo $pkgconf | sed 's/^.*= *//') -# echo "${classdef}_${mod_name}_INCLUDE = $(pkg-config --cflags $pkgconf)" -# echo "${classdef}_${mod_name}_LIBS = $(pkg-config --libs $pkgconf)" -# else -# grep "^${classdef}_${name}_" "$defs_source" -# echo "${classdef}_${mod_name}_INCLUDE = \$(${classdef}_${name}_INCLUDE)" -# echo "${classdef}_${mod_name}_LIBS = \$(${classdef}_${name}_LIBS)" -# fi + grep "^${classdef}_${name}_PC" "$defs_source" 1>&2 + pkgconf=$(grep "^${classdef}_${name}_PC" "$defs_source") + if [ $? -eq 0 ]; then + pkgconf=$(echo $pkgconf | sed 's/^.*= *//') + echo "${classdef}_${mod_name}_INCLUDE = $(pkg-config --cflags $pkgconf)" + echo "${classdef}_${mod_name}_LIBS = $(pkg-config --libs $pkgconf)" + else + grep "^${classdef}_${name}_" "$defs_source" + echo "${classdef}_${mod_name}_INCLUDE = \$(${classdef}_${name}_INCLUDE)" + echo "${classdef}_${mod_name}_LIBS = \$(${classdef}_${name}_LIBS)" + fi elif want_at_all "$name" then OBJ="${OBJ} ${mod_name}.o" diff --git a/src/src/EDITME b/src/src/EDITME index 820793032..b930f00a7 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -827,6 +827,20 @@ FIXED_NEVER_USERS=root # you must uncomment at least one of the following, so that appropriate code is # included in the Exim binary. You will then need to set up the run time # configuration to make use of the mechanism(s) selected. +# +# If set to "2" instead of "yes" then the corresponding driver will be +# built as a module and must be installed into LOOKUP_MODULE_DIR (the name +# is historic). +# You need to add -export-dynamic -rdynamic to EXTRALIBS. You may also need to +# add -ldl to EXTRALIBS so that dlopen() is available to Exim. You need to +# define CFLAGS_DYNAIC and LOOKUP_MODULE_DIR below so the builds are done right, +# and so the exim binary actually loads dynamic lookup modules. +# +# Libraries being built as modules should be added to respective +# LOOKUP_*_INCLUDE and LOOKUP_*_LIBS rather than the the ones for the +# core exim build. This gets them linked with the module instead +# Only the cram_md5, cyrus_sasl, dovecot, external and tls builds for modules +# are known to work. The heimdal does build, but we have no test coverage. # AUTH_CRAM_MD5=yes # AUTH_CYRUS_SASL=yes diff --git a/src/src/auths/Makefile b/src/src/auths/Makefile index ac5cf865b..fa60acba9 100644 --- a/src/src/auths/Makefile +++ b/src/src/auths/Makefile @@ -4,40 +4,61 @@ # linked in only when needed. This Makefile is called from the main make file, # after cd'ing to the auths subdirectory. When the relevant AUTH_ macros are # defined, the equivalent modules herein is not included in the final binary. +# +# Copyright (c) The Exim Maintainers 2024 -OBJ = auth-spa.o call_pam.o call_pwcheck.o \ - call_radius.o check_serv_cond.o cram_md5.o cyrus_sasl.o dovecot.o \ - external.o get_data.o get_no64_data.o gsasl_exim.o heimdal_gssapi.o \ - plaintext.o pwcheck.o \ - spa.o tls.o - -auths.a: $(OBJ) - @$(RM_COMMAND) -f auths.a - @echo "$(AR) auths.a" - $(FE)$(AR) auths.a $(OBJ) - $(RANLIB) $@ - -.SUFFIXES: .o .c -.c.o:; @echo "$(CC) $*.c" - $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c - -auth-spa.o: $(HDRS) auth-spa.c -call_pam.o: $(HDRS) call_pam.c -call_pwcheck.o: $(HDRS) call_pwcheck.c pwcheck.h -call_radius.o: $(HDRS) call_radius.c -check_serv_cond.o: $(HDRS) check_serv_cond.c -get_data.o: $(HDRS) get_data.c -get_no64_data.o: $(HDRS) get_no64_data.c -pwcheck.o: $(HDRS) pwcheck.c pwcheck.h - -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 -external.o: $(HDRS) external.c external.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 -tls.o: $(HDRS) tls.c tls.h +# nb: at build time, the version of this file used will have had some +# extra variable definitions and prepended to it and module build rules +# interpolated below. This is done by scripts/drivers-Makefile with +# definitions from scripts/Configure-Makefile + +# MAGIC-TAG-MODS-OBJ-RULES-GO-HERE + +OBJ += auth-spa.o call_pam.o call_pwcheck.o call_radius.o check_serv_cond.o \ + get_data.o get_no64_data.o pwcheck.o + +all: auths.a $(MODS) + +auths.a: $(OBJ) + @$(RM_COMMAND) -f auths.a + @echo "$(AR) auths.a" + $(FE)$(AR) auths.a $(OBJ) + $(RANLIB) $@ + +.SUFFIXES: .o .c .so +.c.o:; @echo "$(CC) $*.c" + $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c + +SO_FLAGS = -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) +.c.so:; @echo "$(CC) -shared $*.c" + $(FE)$(CC) $(SO_FLAGS) $(AUTH_$*_INCLUDE) $(AUTH_$*_LIBS) \ + $*.c -o $@ + + +$(OBJ) $(MOD): $(HDRS) + +auth-spa.o: auth-spa.c +call_pam.o: call_pam.c +call_pwcheck.o: call_pwcheck.c pwcheck.h +call_radius.o: call_radius.c +check_serv_cond.o: check_serv_cond.c +get_data.o: get_data.c +get_no64_data.o: get_no64_data.c +pwcheck.o: pwcheck.c pwcheck.h + +cram_md5.o: cram_md5.c cram_md5.h +cyrus_sasl.o: cyrus_sasl.c cyrus_sasl.h +dovecot.o: dovecot.c dovecot.h +external.o: external.c external.h +gsasl.o: gsasl.c gsasl.h +heimdal_gssapi.o: heimdal_gssapi.c heimdal_gssapi.h +plaintext.o: plaintext.c plaintext.h +spa.o: spa.c spa.h +tls.o: tls.c tls.h + +# These depend on more than one .c source + +spa.so: spa.c auth-spa.c spa.h + $(FE)$(CC) $(SO_FLAGS) spa.c auth-spa.c -o $@ # End diff --git a/src/src/auths/cram_md5.c b/src/src/auths/cram_md5.c index a64177f2a..7b41ee065 100644 --- a/src/src/auths/cram_md5.c +++ b/src/src/auths/cram_md5.c @@ -334,10 +334,34 @@ if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS big_buffer, return smtp_read_response(sx, US buffer, buffsize, '2', timeout) ? OK : FAIL; } + + +# ifdef DYNLOOKUP +# define cram_md5_auth_info _auth_info +# endif + +auth_info cram_md5_auth_info = { +.drinfo = { + .driver_name = US"cram_md5", /* lookup name */ + .options = auth_cram_md5_options, + .options_count = &auth_cram_md5_options_count, + .options_block = &auth_cram_md5_option_defaults, + .options_len = sizeof(auth_cram_md5_options_block), + .init = auth_cram_md5_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_cram_md5_server, +.clientcode = auth_cram_md5_client, +.version_report = NULL, +.macros_create = NULL, +}; + + # endif /*AUTH_CRAM_MD5*/ # endif /*!STAND_ALONE*/ - /************************************************* ************************************************** * Stand-alone test program * diff --git a/src/src/auths/cyrus_sasl.c b/src/src/auths/cyrus_sasl.c index 8266e2319..ed0995637 100644 --- a/src/src/auths/cyrus_sasl.c +++ b/src/src/auths/cyrus_sasl.c @@ -508,6 +508,29 @@ auth_cyrus_sasl_client( return FAIL; } + +# ifdef DYNLOOKUP +# define cyrus_sasl_auth_info _auth_info +# endif + +auth_info cyrus_sasl_auth_info = { +.drinfo = { + .driver_name = US"cyrus_sasl", /* lookup name */ + .options = auth_cyrus_sasl_options, + .options_count = &auth_cyrus_sasl_options_count, + .options_block = &auth_cyrus_sasl_option_defaults, + .options_len = sizeof(auth_cyrus_sasl_options_block), + .init = auth_cyrus_sasl_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_cyrus_sasl_server, +.clientcode = NULL, +.version_report = auth_cyrus_sasl_version_report, +.macros_create = NULL, +}; + #endif /*!MACRO_PREDEF*/ #endif /* AUTH_CYRUS_SASL */ diff --git a/src/src/auths/dovecot.c b/src/src/auths/dovecot.c index fdfdbc749..ee69436be 100644 --- a/src/src/auths/dovecot.c +++ b/src/src/auths/dovecot.c @@ -1,13 +1,13 @@ /* - * Copyright (c) The Exim Maintainers 2006 - 2024 - * Copyright (c) 2004 Andrey Panin - * SPDX-License-Identifier: GPL-2.0-or-later - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - */ +Copyright (c) The Exim Maintainers 2006 - 2024 +Copyright (c) 2004 Andrey Panin +SPDX-License-Identifier: GPL-2.0-or-later + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +*/ /* A number of modifications have been made to the original code. Originally I commented them specially, but now they are getting quite extensive, so I have @@ -541,6 +541,28 @@ return ret; } +# ifdef DYNLOOKUP +# define dovecot_auth_info _auth_info +# endif + +auth_info dovecot_auth_info = { +.drinfo = { + .driver_name = US"dovecot", /* lookup name */ + .options = auth_dovecot_options, + .options_count = &auth_dovecot_options_count, + .options_block = &auth_dovecot_option_defaults, + .options_len = sizeof(auth_dovecot_options_block), + .init = auth_dovecot_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_dovecot_server, +.clientcode = NULL, +.version_report = NULL, +.macros_create = NULL, +}; + #endif /*!MACRO_PREDEF*/ #endif /*AUTH_DOVECOT*/ /* end of auths/dovecot.c */ diff --git a/src/src/auths/external.c b/src/src/auths/external.c index 137d1e043..de0d07f86 100644 --- a/src/src/auths/external.c +++ b/src/src/auths/external.c @@ -159,6 +159,28 @@ return OK; +# ifdef DYNLOOKUP +# define external_auth_info _auth_info +# endif + +auth_info external_auth_info = { +.drinfo = { + .driver_name = US"external", /* lookup name */ + .options = auth_external_options, + .options_count = &auth_external_options_count, + .options_block = &auth_external_option_defaults, + .options_len = sizeof(auth_external_options_block), + .init = auth_external_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_external_server, +.clientcode = auth_external_client, +.version_report = NULL, +.macros_create = NULL, +}; + #endif /*!MACRO_PREDEF*/ #endif /*AUTH_EXTERNAL*/ /* End of external.c */ diff --git a/src/src/auths/gsasl.c b/src/src/auths/gsasl.c new file mode 100644 index 000000000..e128dac69 --- /dev/null +++ b/src/src/auths/gsasl.c @@ -0,0 +1,1088 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2019 - 2024 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock */ +/* 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); +static void dummy2(int x) { dummy(x-1); } +static void dummy(int x) { dummy2(x-1); } +#else + +#include +#include "gsasl.h" + + +#if GSASL_VERSION_MAJOR == 2 + +# define EXIM_GSASL_HAVE_SCRAM_SHA_256 +# define EXIM_GSASL_SCRAM_S_KEY +# if GSASL_VERSION_MINOR >= 1 +# define EXIM_GSASL_HAVE_EXPORTER +# elif GSASL_VERSION_PATCH >= 1 +# define EXIM_GSASL_HAVE_EXPORTER +# endif + +#elif GSASL_VERSION_MAJOR == 1 +# if GSASL_VERSION_MINOR >= 10 +# define EXIM_GSASL_HAVE_SCRAM_SHA_256 +# define EXIM_GSASL_SCRAM_S_KEY + +# elif GSASL_VERSION_MINOR == 9 +# define EXIM_GSASL_HAVE_SCRAM_SHA_256 + +# if GSASL_VERSION_PATCH >= 1 +# define EXIM_GSASL_SCRAM_S_KEY +# endif +# if GSASL_VERSION_PATCH < 2 +# define CHANNELBIND_HACK +# endif + +# else +# define CHANNELBIND_HACK +# endif +#endif + +/* Convenience for testing strings */ + +#define STREQIC(Foo, Bar) (strcmpic((Foo), (Bar)) == 0) + + +/* 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. */ +#define LOFF(field) OPT_OFF(auth_gsasl_options_block, field) + +optionlist auth_gsasl_options[] = { + { "client_authz", opt_stringptr, LOFF(client_authz) }, + { "client_channelbinding", opt_bool, LOFF(client_channelbinding) }, + { "client_password", opt_stringptr, LOFF(client_password) }, + { "client_spassword", opt_stringptr, LOFF(client_spassword) }, + { "client_username", opt_stringptr, LOFF(client_username) }, + + { "server_channelbinding", opt_bool, LOFF(server_channelbinding) }, + { "server_hostname", opt_stringptr, LOFF(server_hostname) }, +#ifdef EXIM_GSASL_SCRAM_S_KEY + { "server_key", opt_stringptr, LOFF(server_key) }, +#endif + { "server_mech", opt_stringptr, LOFF(server_mech) }, + { "server_password", opt_stringptr, LOFF(server_password) }, + { "server_realm", opt_stringptr, LOFF(server_realm) }, + { "server_scram_iter", opt_stringptr, LOFF(server_scram_iter) }, + { "server_scram_salt", opt_stringptr, LOFF(server_scram_salt) }, +#ifdef EXIM_GSASL_SCRAM_S_KEY + { "server_skey", opt_stringptr, LOFF(server_s_key) }, +#endif + { "server_service", opt_stringptr, LOFF(server_service) } +}; + +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 = { + .server_service = US"smtp", + .server_hostname = US"$primary_hostname", + .server_scram_iter = US"4096", + /* all others zero/null */ +}; + + +#ifdef MACRO_PREDEF +# include "../macro_predef.h" + +/* Dummy values */ +void auth_gsasl_init(driver_instance *ablock) {} +int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;} +int auth_gsasl_client(auth_instance *ablock, void * sx, + int timeout, uschar *buffer, int buffsize) {return 0;} +gstring * auth_gsasl_version_report(gstring * g) {return NULL;} + +void +auth_gsasl_macros(void) +{ +# ifdef EXIM_GSASL_HAVE_SCRAM_SHA_256 + builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_SHA_256"); +# endif +# ifdef EXIM_GSASL_SCRAM_S_KEY + builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_S_KEY"); +# endif +} + +#else /*!MACRO_PREDEF*/ + + + +/* "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(driver_instance * a) +{ +auth_instance * ablock = (auth_instance *)a; +auth_gsasl_options_block * ob = a->options_block; +static char * once = NULL; +int rc; + +/* 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) + 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) + { + if ((rc = gsasl_init(&gsasl_ctx)) != GSASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't initialise GNU SASL library: %s (%s)", + a->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. */ + +HDEBUG(D_auth) if (!once) + { + if ((rc = gsasl_server_mechlist(gsasl_ctx, &once)) != GSASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "failed to retrieve list of mechanisms: %s (%s)", + a->name, gsasl_strerror_name(rc), gsasl_strerror(rc)); + + debug_printf("GNU SASL supports: %s\n", once); + } + +if (!gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech)) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "GNU SASL does not support mechanism \"%s\"", + a->name, ob->server_mech); + +if (ablock->server_condition) + ablock->server = TRUE; +else if( ob->server_mech + && !STREQIC(ob->server_mech, US"EXTERNAL") + && !STREQIC(ob->server_mech, US"ANONYMOUS") + && !STREQIC(ob->server_mech, US"PLAIN") + && !STREQIC(ob->server_mech, US"LOGIN") + ) + { + /* 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. + */ + + ablock->server = FALSE; + HDEBUG(D_auth) debug_printf("%s authenticator: " + "Need server_condition for %s mechanism\n", + a->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 + && STREQIC(ob->server_mech, US"DIGEST-MD5")) + { + ablock->server = FALSE; + HDEBUG(D_auth) debug_printf("%s authenticator: " + "Need server_realm for %s mechanism\n", + a->name, ob->server_mech); + } + +ablock->client = ob->client_username && ob->client_password; +} + + +/* 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); + +if (!cb_state) + { + HDEBUG(D_auth) debug_printf("gsasl callback (%d) not from our server/client processing\n", prop); +#ifdef CHANNELBIND_HACK + if (prop == GSASL_CB_TLS_UNIQUE) + { + uschar * s; + if ((s = gsasl_callback_hook_get(ctx))) /* Gross hack for early lib vers */ + { + HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n"); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CS s); + } + else + { + HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE! dummy for now\n"); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, ""); + } + return GSASL_OK; + } +#endif + return GSASL_NO_CALLBACK; + } + +HDEBUG(D_auth) + debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n", + prop, callback_loop); + +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->drinst.name); + /* NOTREACHED */ + +callback_loop = 0; +return rc; +} + + +/************************************************* +* Debug service function * +*************************************************/ +static const uschar * +gsasl_prop_code_to_name(Gsasl_property prop) +{ +switch (prop) + { + case GSASL_AUTHID: return US"AUTHID"; + case GSASL_AUTHZID: return US"AUTHZID"; + case GSASL_PASSWORD: return US"PASSWORD"; + case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN"; + case GSASL_SERVICE: return US"SERVICE"; + case GSASL_HOSTNAME: return US"HOSTNAME"; + case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME"; + case GSASL_PASSCODE: return US"PASSCODE"; + case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN"; + case GSASL_PIN: return US"PIN"; + case GSASL_REALM: return US"REALM"; + case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD"; + case GSASL_QOPS: return US"QOPS"; + case GSASL_QOP: return US"QOP"; + case GSASL_SCRAM_ITER: return US"SCRAM_ITER"; + case GSASL_SCRAM_SALT: return US"SCRAM_SALT"; + case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD"; +#ifdef EXIM_GSASL_SCRAM_S_KEY + case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY"; + case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY"; +#endif +#ifdef EXIM_GSASL_HAVE_EXPORTER /* v. 2.1.0 */ + case GSASL_CB_TLS_EXPORTER: return US"CB_TLS_EXPORTER"; +#endif + case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE"; + case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER"; + case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL"; + case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL"; + case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA"; + case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER"; + case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER"; + case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE"; + case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL"; + case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS"; + case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI"; + case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID"; + case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20"; + case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20"; + } +return CUS string_sprintf("(unknown prop: %d)", (int)prop); +} + +static void +preload_prop(Gsasl_session * sctx, Gsasl_property propcode, const uschar * val) +{ +DEBUG(D_auth) debug_printf("preloading prop %s val %s\n", + gsasl_prop_code_to_name(propcode), val); +gsasl_property_set(sctx, propcode, CCS val); +} + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_server(auth_instance * ablock, uschar * initial_data) +{ +auth_gsasl_options_block * ob = ablock->drinst.options_block; +const uschar * auname = ablock->drinst.name; +uschar * tmps; +char * to_send, * received; +Gsasl_session * sctx = NULL; +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", + auname, ob->server_mech); + +#ifndef DISABLE_TLS +if (tls_in.channelbinding && ob->server_channelbinding) + { +# ifndef DISABLE_TLS_RESUME + if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED) + { /* per RFC 7677 section 4 */ + HDEBUG(D_auth) debug_printf( + "channel binding not usable on resumed TLS without extended-master-secret"); + return FAIL; + } +# endif +# ifdef CHANNELBIND_HACK +/* This is a gross hack to get around the library before 1.9.2 +a) requiring that c-b was already set, at the _start() call, and +b) caching a b64'd version of the binding then which it never updates. */ + + gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding); +# endif + } +#endif + +if ((rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != 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 */ + +cb_state.ablock = ablock; +cb_state.currently = CURRENTLY_SERVER; +gsasl_session_hook_set(sctx, &cb_state); + +tmps = expand_string(ob->server_service); +preload_prop(sctx, GSASL_SERVICE, tmps); +tmps = expand_string(ob->server_hostname); +preload_prop(sctx, GSASL_HOSTNAME, tmps); +if (ob->server_realm) + { + tmps = expand_string(ob->server_realm); + if (tmps && *tmps) + preload_prop(sctx, GSASL_REALM, tmps); + } +/* We don't support protection layers. */ +preload_prop(sctx, GSASL_QOPS, US "qop-auth"); + +#ifndef DISABLE_TLS +if (tls_in.channelbinding) + { + /* 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 session 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* + + Earlier library versions need this set early, during the _start() call, + so we had to misuse gsasl_callback_hook_set/get() as a data transfer + mech for the callback done at that time to get the bind-data. More recently + the callback is done (if needed) during the first gsasl_stop(). We know + the bind-data here so can set it (and should not get a callback). + */ + if (ob->server_channelbinding) + { + HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", + auname); +# ifndef CHANNELBIND_HACK + preload_prop(sctx, +# ifdef EXIM_GSASL_HAVE_EXPORTER + tls_in.channelbind_exporter ? GSASL_CB_TLS_EXPORTER : +# endif + GSASL_CB_TLS_UNIQUE, + tls_in.channelbinding); +# endif + } + else + HDEBUG(D_auth) + debug_printf("Auth %s: Not enabling channel-binding (data available)\n", + auname); + } +else + HDEBUG(D_auth) + debug_printf("Auth %s: no channel-binding data available\n", + auname); +#endif + +checked_server_condition = FALSE; + +received = CS initial_data; +to_send = NULL; +exim_error = exim_error_override = OK; + +do { + switch (rc = gsasl_step64(sctx, received, &to_send)) + { + 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)", + auname, 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; + } + + /*XXX having our caller send the final smtp "235" is unfortunate; wastes a roundtrip */ + if ((rc == GSASL_NEEDS_MORE) || (to_send && *to_send)) + exim_error = auth_get_no64_data(USS &received, US 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; + +HDEBUG(D_auth) + { + const uschar * s; + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_ITER))) + debug_printf(" - itercnt: '%s'\n", s); + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALT))) + debug_printf(" - salt: '%s'\n", s); +#ifdef EXIM_GSASL_SCRAM_S_KEY + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SERVERKEY))) + debug_printf(" - ServerKey: '%s'\n", s); + if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_STOREDKEY))) + debug_printf(" - StoredKey: '%s'\n", s); +#endif + } + +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 = auth_check_some_cond(ablock, label, condition_string, FAIL); +switch (exim_rc) + { + case OK: return GSASL_OK; + case DEFER: sasl_error_should_defer = TRUE; + return GSASL_AUTHENTICATION_ERROR; + case FAIL: return GSASL_AUTHENTICATION_ERROR; + default: log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Unhandled return from checking %s: %d", + ablock->drinst.name, label, exim_rc); + } + +/* NOTREACHED */ +return GSASL_AUTHENTICATION_ERROR; +} + + +/* Set the "next" $auth[n] and increment expand_nmax */ + +static void +set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop) +{ +uschar * propval = US gsasl_property_fast(sctx, prop); +int i = expand_nmax, j = i + 1; +propval = propval ? string_copy(propval) : US""; +HDEBUG(D_auth) debug_printf("auth[%d] <= %s'%s'\n", + j, gsasl_prop_code_to_name(prop), propval); +expand_nstring[j] = propval; +expand_nlength[j] = Ustrlen(propval); +if (i < AUTH_VARS) auth_vars[i] = propval; +expand_nmax = j; +} + +static void +set_exim_authvars_from_a_az_r_props(Gsasl_session * sctx) +{ +if (expand_nmax > 0 ) return; + +/* 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. */ + +set_exim_authvar_from_prop(sctx, GSASL_AUTHID); +set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); +set_exim_authvar_from_prop(sctx, GSASL_REALM); +} + + +static int +prop_from_option(Gsasl_session * sctx, Gsasl_property prop, + const uschar * option) +{ +HDEBUG(D_auth) debug_printf(" %s\n", gsasl_prop_code_to_name(prop)); +if (option) + { + set_exim_authvars_from_a_az_r_props(sctx); + option = expand_cstring(option); + HDEBUG(D_auth) debug_printf(" '%s'\n", option); + if (*option) + gsasl_property_set(sctx, prop, CCS option); + return GSASL_OK; + } +HDEBUG(D_auth) debug_printf(" option not set\n"); +return GSASL_NO_CALLBACK; +} + +static int +server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, + auth_instance *ablock) +{ +auth_gsasl_options_block * ob = ablock->drinst.options_block; +char * tmps; +uschar * s; +int cbrc = GSASL_NO_CALLBACK; + +HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n", + gsasl_prop_code_to_name(prop), ablock->drinst.name, ablock->public_name); + +for (int 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 */ + set_exim_authvar_from_prop(sctx, GSASL_AUTHID); + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); + set_exim_authvar_from_prop(sctx, GSASL_PASSWORD); + + cbrc = condition_check(ablock, US"server_condition", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_EXTERNAL: + if (!ablock->server_condition) + { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); + + cbrc = condition_check(ablock, + US"server_condition (EXTERNAL)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_ANONYMOUS: + if (!ablock->server_condition) + { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + set_exim_authvar_from_prop(sctx, GSASL_ANONYMOUS_TOKEN); + + 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. */ + + set_exim_authvar_from_prop(sctx, GSASL_GSSAPI_DISPLAY_NAME); + set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); + + /* 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_SCRAM_ITER: + cbrc = prop_from_option(sctx, prop, ob->server_scram_iter); + break; + + case GSASL_SCRAM_SALT: + cbrc = prop_from_option(sctx, prop, ob->server_scram_salt); + break; + +#ifdef EXIM_GSASL_SCRAM_S_KEY + case GSASL_SCRAM_STOREDKEY: + cbrc = prop_from_option(sctx, prop, ob->server_s_key); + break; + + case GSASL_SCRAM_SERVERKEY: + cbrc = prop_from_option(sctx, prop, ob->server_key); + break; +#endif + + case GSASL_PASSWORD: + /* SCRAM-*: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + CRAM-MD5: GSASL_AUTHID + PLAIN: GSASL_AUTHID and GSASL_AUTHZID + LOGIN: GSASL_AUTHID + */ + set_exim_authvars_from_a_az_r_props(sctx); + + if (!(s = ob->server_password)) + { + HDEBUG(D_auth) debug_printf("option not set\n"); + break; + } + if (!(tmps = CS expand_string(s))) + { + sasl_error_should_defer = !f.expand_string_forcedfail; + 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; + } + HDEBUG(D_auth) debug_printf(" set\n"); + 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. */ + + if (US tmps != s) 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; +} + + +/******************************************************************************/ + +#define PROP_OPTIONAL BIT(0) + +static BOOL +set_client_prop(Gsasl_session * sctx, Gsasl_property prop, uschar * val, + unsigned flags, uschar * buffer, int buffsize) +{ +uschar * s; + +if (!val) return !!(flags & PROP_OPTIONAL); +if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s) + { + string_format(buffer, buffsize, "%s", expand_string_message); + return FALSE; + } +if (*s) + { + HDEBUG(D_auth) debug_printf("%s: set %s = '%s'\n", __FUNCTION__, + gsasl_prop_code_to_name(prop), s); + gsasl_property_set(sctx, prop, CS s); + } + +return TRUE; +} + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_client( + auth_instance * ablock, /* authenticator block */ + void * sx, /* connection */ + int timeout, /* command timeout */ + uschar * buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ +auth_gsasl_options_block * ob = ablock->drinst.options_block; +const uschar * auname = ablock->drinst.name; +Gsasl_session * sctx = NULL; +struct callback_exim_state cb_state; +uschar * s; +BOOL initial = TRUE; +int rc, yield = FAIL; + +HDEBUG(D_auth) + debug_printf("GNU SASL: initialising session for %s, mechanism %s\n", + auname, ob->server_mech); + +*buffer = 0; + +#ifndef DISABLE_TLS +if (tls_out.channelbinding && ob->client_channelbinding) + { +# ifndef DISABLE_TLS_RESUME + if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED) + { /* Per RFC 7677 section 4. See also RFC 7627, "Triple Handshake" + vulnerability, and https://www.mitls.org/pages/attacks/3SHAKE */ + string_format(buffer, buffsize, "%s", + "channel binding not usable on resumed TLS without extended-master-secret"); + return FAIL; + } +# endif +# ifdef CHANNELBIND_HACK + /* This is a gross hack to get around the library before 1.9.2 + a) requiring that c-b was already set, at the _start() call, and + b) caching a b64'd version of the binding then which it never updates. */ + + gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding); +# endif + } +#endif + +if ((rc = gsasl_client_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK) + { + string_format(buffer, buffsize, "GNU SASL: session start failure: %s (%s)", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("%s\n", buffer); + return ERROR; + } + +cb_state.ablock = ablock; +cb_state.currently = CURRENTLY_CLIENT; +gsasl_session_hook_set(sctx, &cb_state); + +/* Set properties */ + +if ( !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password, + 0, buffer, buffsize) + || !set_client_prop(sctx, GSASL_AUTHID, ob->client_username, + 0, buffer, buffsize) + || !set_client_prop(sctx, GSASL_AUTHZID, ob->client_authz, + PROP_OPTIONAL, buffer, buffsize) + ) + return ERROR; + +#ifndef DISABLE_TLS +if (tls_out.channelbinding) + if (ob->client_channelbinding) + { + HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", + auname); +# ifndef CHANNELBIND_HACK + preload_prop(sctx, +# ifdef EXIM_GSASL_HAVE_EXPORTER + tls_out.channelbind_exporter ? GSASL_CB_TLS_EXPORTER : +# endif + GSASL_CB_TLS_UNIQUE, + tls_out.channelbinding); +# endif + } + else + HDEBUG(D_auth) + debug_printf("Auth %s: Not enabling channel-binding (data available)\n", + auname); +#endif + +/* Run the SASL conversation with the server */ + +for(s = NULL; ;) + { + uschar * outstr; + BOOL fail = TRUE; + + rc = gsasl_step64(sctx, CS s, CSS &outstr); + + if (rc == GSASL_NEEDS_MORE || rc == GSASL_OK) + { + fail = initial + ? smtp_write_command(sx, SCMD_FLUSH, + outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n", + ablock->public_name, outstr) <= 0 + : outstr + ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0 + : FALSE; + free(outstr); + if (fail) + { + yield = FAIL_SEND; + goto done; + } + initial = FALSE; + } + + if (rc != GSASL_NEEDS_MORE) + { + if (rc != GSASL_OK) + { + string_format(buffer, buffsize, "gsasl: %s", gsasl_strerror(rc)); + break; + } + + /* expecting a final 2xx from the server, accepting the AUTH */ + + if (smtp_read_response(sx, buffer, buffsize, '2', timeout)) + yield = OK; + break; /* from SASL sequence loop */ + } + + /* 2xx or 3xx response is acceptable. If 2xx, no further input */ + + if (!smtp_read_response(sx, buffer, buffsize, '3', timeout)) + if (errno == 0 && buffer[0] == '2') + buffer[4] = '\0'; + else + { + yield = FAIL; + goto done; + } + s = buffer + 4; + } + +done: +if (yield == OK) + { + expand_nmax = 0; + set_exim_authvar_from_prop(sctx, GSASL_AUTHID); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD); + } + +gsasl_finish(sctx); +return yield; +} + +static int +client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) +{ +HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as client\n", + gsasl_prop_code_to_name(prop), ablock->drinst.name, ablock->public_name); +switch (prop) + { +#ifdef EXIM_GSASL_HAVE_EXPORTER + case GSASL_CB_TLS_EXPORTER: /* Should never get called for this, as pre-set */ + if (!tls_out.channelbind_exporter) break; + HDEBUG(D_auth) debug_printf(" filling in\n"); + gsasl_property_set(sctx, GSASL_CB_TLS_EXPORTER, CCS tls_out.channelbinding); + return GSASL_OK; +#endif + case GSASL_CB_TLS_UNIQUE: /* Should never get called for this, as pre-set */ +#ifdef EXIM_GSASL_HAVE_EXPORTER + if (tls_out.channelbind_exporter) break; +#endif + HDEBUG(D_auth) debug_printf(" filling in\n"); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding); + return GSASL_OK; + case GSASL_SCRAM_SALTED_PASSWORD: + { + uschar * client_spassword = + ((auth_gsasl_options_block *) ablock->drinst.options_block)->client_spassword; + uschar dummy[4]; + HDEBUG(D_auth) if (!client_spassword) + debug_printf(" client_spassword option unset\n"); + if (client_spassword) + { + expand_nmax = 0; + set_exim_authvar_from_prop(sctx, GSASL_AUTHID); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER); + set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT); + set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, client_spassword, + 0, dummy, sizeof(dummy)); + for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; + expand_nmax = 0; + } + break; + } + default: + HDEBUG(D_auth) + debug_printf(" not providing one\n"); + break; + } +return GSASL_NO_CALLBACK; +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +gstring * +auth_gsasl_version_report(gstring * g) +{ +return string_fmt_append(g, "Library version: GNU SASL: Compile: %s\n" + " Runtime: %s\n", + GSASL_VERSION, gsasl_check_version(NULL)); +} + + + +/* Dummy */ +void auth_gsasl_macros(void) {} + +# ifdef DYNLOOKUP +# define gsasl_auth_info _auth_info +# endif + +auth_info gsasl_auth_info = { +.drinfo = { + .driver_name = US"gsasl", /* lookup name */ + .options = auth_gsasl_options, + .options_count = &auth_gsasl_options_count, + .options_block = &auth_gsasl_option_defaults, + .options_len = sizeof(auth_gsasl_options_block), + .init = auth_gsasl_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_gsasl_server, +.clientcode = auth_gsasl_client, +.version_report = auth_gsasl_version_report, +.macros_create = auth_gsasl_macros, +}; + +#endif /*!MACRO_PREDEF*/ +#endif /* AUTH_GSASL */ + +/* End of gsasl_exim.c */ diff --git a/src/src/auths/gsasl.h b/src/src/auths/gsasl.h new file mode 100644 index 000000000..180d4c848 --- /dev/null +++ b/src/src/auths/gsasl.h @@ -0,0 +1,54 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2019 - 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* 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_key; + uschar *server_s_key; + uschar *server_scram_iter; + uschar *server_scram_salt; + + uschar *client_username; + uschar *client_password; + uschar *client_authz; + uschar *client_spassword; + + BOOL server_channelbinding; + BOOL client_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(driver_instance *); +extern int auth_gsasl_server(auth_instance *, uschar *); +extern int auth_gsasl_client(auth_instance *, void *, + int, uschar *, int); +extern gstring * auth_gsasl_version_report(gstring *); +extern void auth_gsasl_macros(void); + +/* End of gsasl_exim.h */ diff --git a/src/src/auths/gsasl_exim.c b/src/src/auths/gsasl_exim.c deleted file mode 100644 index 55ac15b4b..000000000 --- a/src/src/auths/gsasl_exim.c +++ /dev/null @@ -1,1066 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* Copyright (c) The Exim Maintainers 2019 - 2023 */ -/* Copyright (c) University of Cambridge 1995 - 2018 */ -/* See the file NOTICE for conditions of use and distribution. */ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/* Copyright (c) Twitter Inc 2012 - Author: Phil Pennock */ -/* 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); -static void dummy2(int x) { dummy(x-1); } -static void dummy(int x) { dummy2(x-1); } -#else - -#include -#include "gsasl_exim.h" - - -#if GSASL_VERSION_MAJOR == 2 - -# define EXIM_GSASL_HAVE_SCRAM_SHA_256 -# define EXIM_GSASL_SCRAM_S_KEY -# if GSASL_VERSION_MINOR >= 1 -# define EXIM_GSASL_HAVE_EXPORTER -# elif GSASL_VERSION_PATCH >= 1 -# define EXIM_GSASL_HAVE_EXPORTER -# endif - -#elif GSASL_VERSION_MAJOR == 1 -# if GSASL_VERSION_MINOR >= 10 -# define EXIM_GSASL_HAVE_SCRAM_SHA_256 -# define EXIM_GSASL_SCRAM_S_KEY - -# elif GSASL_VERSION_MINOR == 9 -# define EXIM_GSASL_HAVE_SCRAM_SHA_256 - -# if GSASL_VERSION_PATCH >= 1 -# define EXIM_GSASL_SCRAM_S_KEY -# endif -# if GSASL_VERSION_PATCH < 2 -# define CHANNELBIND_HACK -# endif - -# else -# define CHANNELBIND_HACK -# endif -#endif - -/* Convenience for testing strings */ - -#define STREQIC(Foo, Bar) (strcmpic((Foo), (Bar)) == 0) - - -/* 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. */ -#define LOFF(field) OPT_OFF(auth_gsasl_options_block, field) - -optionlist auth_gsasl_options[] = { - { "client_authz", opt_stringptr, LOFF(client_authz) }, - { "client_channelbinding", opt_bool, LOFF(client_channelbinding) }, - { "client_password", opt_stringptr, LOFF(client_password) }, - { "client_spassword", opt_stringptr, LOFF(client_spassword) }, - { "client_username", opt_stringptr, LOFF(client_username) }, - - { "server_channelbinding", opt_bool, LOFF(server_channelbinding) }, - { "server_hostname", opt_stringptr, LOFF(server_hostname) }, -#ifdef EXIM_GSASL_SCRAM_S_KEY - { "server_key", opt_stringptr, LOFF(server_key) }, -#endif - { "server_mech", opt_stringptr, LOFF(server_mech) }, - { "server_password", opt_stringptr, LOFF(server_password) }, - { "server_realm", opt_stringptr, LOFF(server_realm) }, - { "server_scram_iter", opt_stringptr, LOFF(server_scram_iter) }, - { "server_scram_salt", opt_stringptr, LOFF(server_scram_salt) }, -#ifdef EXIM_GSASL_SCRAM_S_KEY - { "server_skey", opt_stringptr, LOFF(server_s_key) }, -#endif - { "server_service", opt_stringptr, LOFF(server_service) } -}; - -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 = { - .server_service = US"smtp", - .server_hostname = US"$primary_hostname", - .server_scram_iter = US"4096", - /* all others zero/null */ -}; - - -#ifdef MACRO_PREDEF -# include "../macro_predef.h" - -/* Dummy values */ -void auth_gsasl_init(driver_instance *ablock) {} -int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;} -int auth_gsasl_client(auth_instance *ablock, void * sx, - int timeout, uschar *buffer, int buffsize) {return 0;} -gstring * auth_gsasl_version_report(gstring * g) {return NULL;} - -void -auth_gsasl_macros(void) -{ -# ifdef EXIM_GSASL_HAVE_SCRAM_SHA_256 - builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_SHA_256"); -# endif -# ifdef EXIM_GSASL_SCRAM_S_KEY - builtin_macro_create(US"_HAVE_AUTH_GSASL_SCRAM_S_KEY"); -# endif -} - -#else /*!MACRO_PREDEF*/ - - - -/* "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(driver_instance * a) -{ -auth_instance * ablock = (auth_instance *)a; -auth_gsasl_options_block * ob = a->options_block; -static char * once = NULL; -int rc; - -/* 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) - 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) - { - if ((rc = gsasl_init(&gsasl_ctx)) != GSASL_OK) - log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " - "couldn't initialise GNU SASL library: %s (%s)", - a->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. */ - -HDEBUG(D_auth) if (!once) - { - if ((rc = gsasl_server_mechlist(gsasl_ctx, &once)) != GSASL_OK) - log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " - "failed to retrieve list of mechanisms: %s (%s)", - a->name, gsasl_strerror_name(rc), gsasl_strerror(rc)); - - debug_printf("GNU SASL supports: %s\n", once); - } - -if (!gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech)) - log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " - "GNU SASL does not support mechanism \"%s\"", - a->name, ob->server_mech); - -if (ablock->server_condition) - ablock->server = TRUE; -else if( ob->server_mech - && !STREQIC(ob->server_mech, US"EXTERNAL") - && !STREQIC(ob->server_mech, US"ANONYMOUS") - && !STREQIC(ob->server_mech, US"PLAIN") - && !STREQIC(ob->server_mech, US"LOGIN") - ) - { - /* 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. - */ - - ablock->server = FALSE; - HDEBUG(D_auth) debug_printf("%s authenticator: " - "Need server_condition for %s mechanism\n", - a->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 - && STREQIC(ob->server_mech, US"DIGEST-MD5")) - { - ablock->server = FALSE; - HDEBUG(D_auth) debug_printf("%s authenticator: " - "Need server_realm for %s mechanism\n", - a->name, ob->server_mech); - } - -ablock->client = ob->client_username && ob->client_password; -} - - -/* 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); - -if (!cb_state) - { - HDEBUG(D_auth) debug_printf("gsasl callback (%d) not from our server/client processing\n", prop); -#ifdef CHANNELBIND_HACK - if (prop == GSASL_CB_TLS_UNIQUE) - { - uschar * s; - if ((s = gsasl_callback_hook_get(ctx))) /* Gross hack for early lib vers */ - { - HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE from ctx hook\n"); - gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CS s); - } - else - { - HDEBUG(D_auth) debug_printf("GSASL_CB_TLS_UNIQUE! dummy for now\n"); - gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, ""); - } - return GSASL_OK; - } -#endif - return GSASL_NO_CALLBACK; - } - -HDEBUG(D_auth) - debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n", - prop, callback_loop); - -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->drinst.name); - /* NOTREACHED */ - -callback_loop = 0; -return rc; -} - - -/************************************************* -* Debug service function * -*************************************************/ -static const uschar * -gsasl_prop_code_to_name(Gsasl_property prop) -{ -switch (prop) - { - case GSASL_AUTHID: return US"AUTHID"; - case GSASL_AUTHZID: return US"AUTHZID"; - case GSASL_PASSWORD: return US"PASSWORD"; - case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN"; - case GSASL_SERVICE: return US"SERVICE"; - case GSASL_HOSTNAME: return US"HOSTNAME"; - case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME"; - case GSASL_PASSCODE: return US"PASSCODE"; - case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN"; - case GSASL_PIN: return US"PIN"; - case GSASL_REALM: return US"REALM"; - case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD"; - case GSASL_QOPS: return US"QOPS"; - case GSASL_QOP: return US"QOP"; - case GSASL_SCRAM_ITER: return US"SCRAM_ITER"; - case GSASL_SCRAM_SALT: return US"SCRAM_SALT"; - case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD"; -#ifdef EXIM_GSASL_SCRAM_S_KEY - case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY"; - case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY"; -#endif -#ifdef EXIM_GSASL_HAVE_EXPORTER /* v. 2.1.0 */ - case GSASL_CB_TLS_EXPORTER: return US"CB_TLS_EXPORTER"; -#endif - case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE"; - case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER"; - case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL"; - case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL"; - case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA"; - case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER"; - case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER"; - case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE"; - case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL"; - case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS"; - case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI"; - case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID"; - case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20"; - case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20"; - } -return CUS string_sprintf("(unknown prop: %d)", (int)prop); -} - -static void -preload_prop(Gsasl_session * sctx, Gsasl_property propcode, const uschar * val) -{ -DEBUG(D_auth) debug_printf("preloading prop %s val %s\n", - gsasl_prop_code_to_name(propcode), val); -gsasl_property_set(sctx, propcode, CCS val); -} - -/************************************************* -* Server entry point * -*************************************************/ - -/* For interface, see auths/README */ - -int -auth_gsasl_server(auth_instance * ablock, uschar * initial_data) -{ -auth_gsasl_options_block * ob = ablock->drinst.options_block; -const uschar * auname = ablock->drinst.name; -uschar * tmps; -char * to_send, * received; -Gsasl_session * sctx = NULL; -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", - auname, ob->server_mech); - -#ifndef DISABLE_TLS -if (tls_in.channelbinding && ob->server_channelbinding) - { -# ifndef DISABLE_TLS_RESUME - if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED) - { /* per RFC 7677 section 4 */ - HDEBUG(D_auth) debug_printf( - "channel binding not usable on resumed TLS without extended-master-secret"); - return FAIL; - } -# endif -# ifdef CHANNELBIND_HACK -/* This is a gross hack to get around the library before 1.9.2 -a) requiring that c-b was already set, at the _start() call, and -b) caching a b64'd version of the binding then which it never updates. */ - - gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding); -# endif - } -#endif - -if ((rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != 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 */ - -cb_state.ablock = ablock; -cb_state.currently = CURRENTLY_SERVER; -gsasl_session_hook_set(sctx, &cb_state); - -tmps = expand_string(ob->server_service); -preload_prop(sctx, GSASL_SERVICE, tmps); -tmps = expand_string(ob->server_hostname); -preload_prop(sctx, GSASL_HOSTNAME, tmps); -if (ob->server_realm) - { - tmps = expand_string(ob->server_realm); - if (tmps && *tmps) - preload_prop(sctx, GSASL_REALM, tmps); - } -/* We don't support protection layers. */ -preload_prop(sctx, GSASL_QOPS, US "qop-auth"); - -#ifndef DISABLE_TLS -if (tls_in.channelbinding) - { - /* 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 session 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* - - Earlier library versions need this set early, during the _start() call, - so we had to misuse gsasl_callback_hook_set/get() as a data transfer - mech for the callback done at that time to get the bind-data. More recently - the callback is done (if needed) during the first gsasl_stop(). We know - the bind-data here so can set it (and should not get a callback). - */ - if (ob->server_channelbinding) - { - HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", - auname); -# ifndef CHANNELBIND_HACK - preload_prop(sctx, -# ifdef EXIM_GSASL_HAVE_EXPORTER - tls_in.channelbind_exporter ? GSASL_CB_TLS_EXPORTER : -# endif - GSASL_CB_TLS_UNIQUE, - tls_in.channelbinding); -# endif - } - else - HDEBUG(D_auth) - debug_printf("Auth %s: Not enabling channel-binding (data available)\n", - auname); - } -else - HDEBUG(D_auth) - debug_printf("Auth %s: no channel-binding data available\n", - auname); -#endif - -checked_server_condition = FALSE; - -received = CS initial_data; -to_send = NULL; -exim_error = exim_error_override = OK; - -do { - switch (rc = gsasl_step64(sctx, received, &to_send)) - { - 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)", - auname, 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; - } - - /*XXX having our caller send the final smtp "235" is unfortunate; wastes a roundtrip */ - if ((rc == GSASL_NEEDS_MORE) || (to_send && *to_send)) - exim_error = auth_get_no64_data(USS &received, US 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; - -HDEBUG(D_auth) - { - const uschar * s; - if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_ITER))) - debug_printf(" - itercnt: '%s'\n", s); - if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALT))) - debug_printf(" - salt: '%s'\n", s); -#ifdef EXIM_GSASL_SCRAM_S_KEY - if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SERVERKEY))) - debug_printf(" - ServerKey: '%s'\n", s); - if ((s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_STOREDKEY))) - debug_printf(" - StoredKey: '%s'\n", s); -#endif - } - -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 = auth_check_some_cond(ablock, label, condition_string, FAIL); -switch (exim_rc) - { - case OK: return GSASL_OK; - case DEFER: sasl_error_should_defer = TRUE; - return GSASL_AUTHENTICATION_ERROR; - case FAIL: return GSASL_AUTHENTICATION_ERROR; - default: log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " - "Unhandled return from checking %s: %d", - ablock->drinst.name, label, exim_rc); - } - -/* NOTREACHED */ -return GSASL_AUTHENTICATION_ERROR; -} - - -/* Set the "next" $auth[n] and increment expand_nmax */ - -static void -set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop) -{ -uschar * propval = US gsasl_property_fast(sctx, prop); -int i = expand_nmax, j = i + 1; -propval = propval ? string_copy(propval) : US""; -HDEBUG(D_auth) debug_printf("auth[%d] <= %s'%s'\n", - j, gsasl_prop_code_to_name(prop), propval); -expand_nstring[j] = propval; -expand_nlength[j] = Ustrlen(propval); -if (i < AUTH_VARS) auth_vars[i] = propval; -expand_nmax = j; -} - -static void -set_exim_authvars_from_a_az_r_props(Gsasl_session * sctx) -{ -if (expand_nmax > 0 ) return; - -/* 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. */ - -set_exim_authvar_from_prop(sctx, GSASL_AUTHID); -set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); -set_exim_authvar_from_prop(sctx, GSASL_REALM); -} - - -static int -prop_from_option(Gsasl_session * sctx, Gsasl_property prop, - const uschar * option) -{ -HDEBUG(D_auth) debug_printf(" %s\n", gsasl_prop_code_to_name(prop)); -if (option) - { - set_exim_authvars_from_a_az_r_props(sctx); - option = expand_cstring(option); - HDEBUG(D_auth) debug_printf(" '%s'\n", option); - if (*option) - gsasl_property_set(sctx, prop, CCS option); - return GSASL_OK; - } -HDEBUG(D_auth) debug_printf(" option not set\n"); -return GSASL_NO_CALLBACK; -} - -static int -server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, - auth_instance *ablock) -{ -auth_gsasl_options_block * ob = ablock->drinst.options_block; -char * tmps; -uschar * s; -int cbrc = GSASL_NO_CALLBACK; - -HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as server\n", - gsasl_prop_code_to_name(prop), ablock->drinst.name, ablock->public_name); - -for (int 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 */ - set_exim_authvar_from_prop(sctx, GSASL_AUTHID); - set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); - set_exim_authvar_from_prop(sctx, GSASL_PASSWORD); - - cbrc = condition_check(ablock, US"server_condition", ablock->server_condition); - checked_server_condition = TRUE; - break; - - case GSASL_VALIDATE_EXTERNAL: - if (!ablock->server_condition) - { - HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL\n"); - cbrc = GSASL_AUTHENTICATION_ERROR; - break; - } - set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); - - cbrc = condition_check(ablock, - US"server_condition (EXTERNAL)", ablock->server_condition); - checked_server_condition = TRUE; - break; - - case GSASL_VALIDATE_ANONYMOUS: - if (!ablock->server_condition) - { - HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS\n"); - cbrc = GSASL_AUTHENTICATION_ERROR; - break; - } - set_exim_authvar_from_prop(sctx, GSASL_ANONYMOUS_TOKEN); - - 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. */ - - set_exim_authvar_from_prop(sctx, GSASL_GSSAPI_DISPLAY_NAME); - set_exim_authvar_from_prop(sctx, GSASL_AUTHZID); - - /* 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_SCRAM_ITER: - cbrc = prop_from_option(sctx, prop, ob->server_scram_iter); - break; - - case GSASL_SCRAM_SALT: - cbrc = prop_from_option(sctx, prop, ob->server_scram_salt); - break; - -#ifdef EXIM_GSASL_SCRAM_S_KEY - case GSASL_SCRAM_STOREDKEY: - cbrc = prop_from_option(sctx, prop, ob->server_s_key); - break; - - case GSASL_SCRAM_SERVERKEY: - cbrc = prop_from_option(sctx, prop, ob->server_key); - break; -#endif - - case GSASL_PASSWORD: - /* SCRAM-*: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM - DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM - CRAM-MD5: GSASL_AUTHID - PLAIN: GSASL_AUTHID and GSASL_AUTHZID - LOGIN: GSASL_AUTHID - */ - set_exim_authvars_from_a_az_r_props(sctx); - - if (!(s = ob->server_password)) - { - HDEBUG(D_auth) debug_printf("option not set\n"); - break; - } - if (!(tmps = CS expand_string(s))) - { - sasl_error_should_defer = !f.expand_string_forcedfail; - 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; - } - HDEBUG(D_auth) debug_printf(" set\n"); - 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. */ - - if (US tmps != s) 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; -} - - -/******************************************************************************/ - -#define PROP_OPTIONAL BIT(0) - -static BOOL -set_client_prop(Gsasl_session * sctx, Gsasl_property prop, uschar * val, - unsigned flags, uschar * buffer, int buffsize) -{ -uschar * s; - -if (!val) return !!(flags & PROP_OPTIONAL); -if (!(s = expand_string(val)) || !(flags & PROP_OPTIONAL) && !*s) - { - string_format(buffer, buffsize, "%s", expand_string_message); - return FALSE; - } -if (*s) - { - HDEBUG(D_auth) debug_printf("%s: set %s = '%s'\n", __FUNCTION__, - gsasl_prop_code_to_name(prop), s); - gsasl_property_set(sctx, prop, CS s); - } - -return TRUE; -} - -/************************************************* -* Client entry point * -*************************************************/ - -/* For interface, see auths/README */ - -int -auth_gsasl_client( - auth_instance * ablock, /* authenticator block */ - void * sx, /* connection */ - int timeout, /* command timeout */ - uschar * buffer, /* buffer for reading response */ - int buffsize) /* size of buffer */ -{ -auth_gsasl_options_block * ob = ablock->drinst.options_block; -const uschar * auname = ablock->drinst.name; -Gsasl_session * sctx = NULL; -struct callback_exim_state cb_state; -uschar * s; -BOOL initial = TRUE; -int rc, yield = FAIL; - -HDEBUG(D_auth) - debug_printf("GNU SASL: initialising session for %s, mechanism %s\n", - auname, ob->server_mech); - -*buffer = 0; - -#ifndef DISABLE_TLS -if (tls_out.channelbinding && ob->client_channelbinding) - { -# ifndef DISABLE_TLS_RESUME - if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED) - { /* Per RFC 7677 section 4. See also RFC 7627, "Triple Handshake" - vulnerability, and https://www.mitls.org/pages/attacks/3SHAKE */ - string_format(buffer, buffsize, "%s", - "channel binding not usable on resumed TLS without extended-master-secret"); - return FAIL; - } -# endif -# ifdef CHANNELBIND_HACK - /* This is a gross hack to get around the library before 1.9.2 - a) requiring that c-b was already set, at the _start() call, and - b) caching a b64'd version of the binding then which it never updates. */ - - gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding); -# endif - } -#endif - -if ((rc = gsasl_client_start(gsasl_ctx, CCS ob->server_mech, &sctx)) != GSASL_OK) - { - string_format(buffer, buffsize, "GNU SASL: session start failure: %s (%s)", - gsasl_strerror_name(rc), gsasl_strerror(rc)); - HDEBUG(D_auth) debug_printf("%s\n", buffer); - return ERROR; - } - -cb_state.ablock = ablock; -cb_state.currently = CURRENTLY_CLIENT; -gsasl_session_hook_set(sctx, &cb_state); - -/* Set properties */ - -if ( !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password, - 0, buffer, buffsize) - || !set_client_prop(sctx, GSASL_AUTHID, ob->client_username, - 0, buffer, buffsize) - || !set_client_prop(sctx, GSASL_AUTHZID, ob->client_authz, - PROP_OPTIONAL, buffer, buffsize) - ) - return ERROR; - -#ifndef DISABLE_TLS -if (tls_out.channelbinding) - if (ob->client_channelbinding) - { - HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", - auname); -# ifndef CHANNELBIND_HACK - preload_prop(sctx, -# ifdef EXIM_GSASL_HAVE_EXPORTER - tls_out.channelbind_exporter ? GSASL_CB_TLS_EXPORTER : -# endif - GSASL_CB_TLS_UNIQUE, - tls_out.channelbinding); -# endif - } - else - HDEBUG(D_auth) - debug_printf("Auth %s: Not enabling channel-binding (data available)\n", - auname); -#endif - -/* Run the SASL conversation with the server */ - -for(s = NULL; ;) - { - uschar * outstr; - BOOL fail = TRUE; - - rc = gsasl_step64(sctx, CS s, CSS &outstr); - - if (rc == GSASL_NEEDS_MORE || rc == GSASL_OK) - { - fail = initial - ? smtp_write_command(sx, SCMD_FLUSH, - outstr ? "AUTH %s %s\r\n" : "AUTH %s\r\n", - ablock->public_name, outstr) <= 0 - : outstr - ? smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", outstr) <= 0 - : FALSE; - free(outstr); - if (fail) - { - yield = FAIL_SEND; - goto done; - } - initial = FALSE; - } - - if (rc != GSASL_NEEDS_MORE) - { - if (rc != GSASL_OK) - { - string_format(buffer, buffsize, "gsasl: %s", gsasl_strerror(rc)); - break; - } - - /* expecting a final 2xx from the server, accepting the AUTH */ - - if (smtp_read_response(sx, buffer, buffsize, '2', timeout)) - yield = OK; - break; /* from SASL sequence loop */ - } - - /* 2xx or 3xx response is acceptable. If 2xx, no further input */ - - if (!smtp_read_response(sx, buffer, buffsize, '3', timeout)) - if (errno == 0 && buffer[0] == '2') - buffer[4] = '\0'; - else - { - yield = FAIL; - goto done; - } - s = buffer + 4; - } - -done: -if (yield == OK) - { - expand_nmax = 0; - set_exim_authvar_from_prop(sctx, GSASL_AUTHID); - set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER); - set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT); - set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD); - } - -gsasl_finish(sctx); -return yield; -} - -static int -client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) -{ -HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as client\n", - gsasl_prop_code_to_name(prop), ablock->drinst.name, ablock->public_name); -switch (prop) - { -#ifdef EXIM_GSASL_HAVE_EXPORTER - case GSASL_CB_TLS_EXPORTER: /* Should never get called for this, as pre-set */ - if (!tls_out.channelbind_exporter) break; - HDEBUG(D_auth) debug_printf(" filling in\n"); - gsasl_property_set(sctx, GSASL_CB_TLS_EXPORTER, CCS tls_out.channelbinding); - return GSASL_OK; -#endif - case GSASL_CB_TLS_UNIQUE: /* Should never get called for this, as pre-set */ -#ifdef EXIM_GSASL_HAVE_EXPORTER - if (tls_out.channelbind_exporter) break; -#endif - HDEBUG(D_auth) debug_printf(" filling in\n"); - gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding); - return GSASL_OK; - case GSASL_SCRAM_SALTED_PASSWORD: - { - uschar * client_spassword = - ((auth_gsasl_options_block *) ablock->drinst.options_block)->client_spassword; - uschar dummy[4]; - HDEBUG(D_auth) if (!client_spassword) - debug_printf(" client_spassword option unset\n"); - if (client_spassword) - { - expand_nmax = 0; - set_exim_authvar_from_prop(sctx, GSASL_AUTHID); - set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER); - set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT); - set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, client_spassword, - 0, dummy, sizeof(dummy)); - for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; - expand_nmax = 0; - } - break; - } - default: - HDEBUG(D_auth) - debug_printf(" not providing one\n"); - break; - } -return GSASL_NO_CALLBACK; -} - -/************************************************* -* Diagnostic API * -*************************************************/ - -gstring * -auth_gsasl_version_report(gstring * g) -{ -return string_fmt_append(g, "Library version: GNU SASL: Compile: %s\n" - " Runtime: %s\n", - GSASL_VERSION, gsasl_check_version(NULL)); -} - - - -/* Dummy */ -void auth_gsasl_macros(void) {} - -#endif /*!MACRO_PREDEF*/ -#endif /* AUTH_GSASL */ - -/* End of gsasl_exim.c */ diff --git a/src/src/auths/gsasl_exim.h b/src/src/auths/gsasl_exim.h deleted file mode 100644 index 180d4c848..000000000 --- a/src/src/auths/gsasl_exim.h +++ /dev/null @@ -1,54 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* Copyright (c) The Exim Maintainers 2019 - 2022 */ -/* Copyright (c) University of Cambridge 1995 - 2012 */ -/* See the file NOTICE for conditions of use and distribution. */ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/* 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_key; - uschar *server_s_key; - uschar *server_scram_iter; - uschar *server_scram_salt; - - uschar *client_username; - uschar *client_password; - uschar *client_authz; - uschar *client_spassword; - - BOOL server_channelbinding; - BOOL client_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(driver_instance *); -extern int auth_gsasl_server(auth_instance *, uschar *); -extern int auth_gsasl_client(auth_instance *, void *, - int, uschar *, int); -extern gstring * auth_gsasl_version_report(gstring *); -extern void auth_gsasl_macros(void); - -/* End of gsasl_exim.h */ diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c index a5eef8d95..8e9e38660 100644 --- a/src/src/auths/heimdal_gssapi.c +++ b/src/src/auths/heimdal_gssapi.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2023 */ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -228,7 +228,7 @@ int auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data) { auth_heimdal_gssapi_options_block * ob = - (auth_heimdal_gssapi_options_block *)(ablock->drinfo.options_block); + (auth_heimdal_gssapi_options_block *)(ablock->drinst.options_block); 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; @@ -250,7 +250,7 @@ uschar requested_qop; store_reset_point = store_mark(); HDEBUG(D_auth) - debug_printf("heimdal: initialising auth context for %s\n", ablock->name); + debug_printf("heimdal: initialising auth context for %s\n", ablock->drinst.name); /* Construct our gss_name_t gserver describing ourselves */ tmp1 = expand_string(ob->server_service); @@ -548,7 +548,7 @@ va_list ap; OM_uint32 maj_stat, min_stat; OM_uint32 msgcontext = 0; gss_buffer_desc status_string; -gstring * g; +gstring * g = NULL; HDEBUG(D_auth) { @@ -610,9 +610,31 @@ build-time and export the result as a string into a header ourselves. */ return string_fmt_append(g, "Library version: Heimdal: Runtime: %s\n" " Build Info: %s\n", - heimdal_version, heimdal_long_version)); + heimdal_version, heimdal_long_version); } +# ifdef DYNLOOKUP +# define heimdal_gssapi_auth_info _auth_info +# endif + +auth_info heimdal_gssapi_auth_info = { +.drinfo = { + .driver_name = US"heimdal_gssapi", /* lookup name */ + .options = auth_heimdal_gssapi_options, + .options_count = &auth_heimdal_gssapi_options_count, + .options_block = &auth_heimdal_gssapi_option_defaults, + .options_len = sizeof(auth_heimdal_gssapi_options_block), + .init = auth_heimdal_gssapi_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_heimdal_gssapi_server, +.clientcode = NULL, +.version_report = auth_heimdal_gssapi_version_report, +.macros_create = NULL, +}; + #endif /*!MACRO_PREDEF*/ #endif /* AUTH_HEIMDAL_GSSAPI */ diff --git a/src/src/auths/heimdal_gssapi.h b/src/src/auths/heimdal_gssapi.h index c438aae91..e1d308ac9 100644 --- a/src/src/auths/heimdal_gssapi.h +++ b/src/src/auths/heimdal_gssapi.h @@ -35,6 +35,6 @@ extern auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults; extern void auth_heimdal_gssapi_init(driver_instance *); extern int auth_heimdal_gssapi_server(auth_instance *, uschar *); extern int auth_heimdal_gssapi_client(auth_instance *, void *, int, uschar *, int); -extern void auth_heimdal_gssapi_version_report(BOOL); +extern gstring * auth_heimdal_gssapi_version_report(gstring *); /* End of heimdal_gssapi.h */ diff --git a/src/src/auths/plaintext.c b/src/src/auths/plaintext.c index 26ac4aeff..13d05d1e5 100644 --- a/src/src/auths/plaintext.c +++ b/src/src/auths/plaintext.c @@ -180,6 +180,29 @@ while ((s = string_nextinlist(&text, &sep, NULL, 0))) return FAIL; } + +# ifdef DYNLOOKUP +# define plaintext_auth_info _auth_info +# endif + +auth_info plaintext_auth_info = { +.drinfo = { + .driver_name = US"plaintext", /* lookup name */ + .options = auth_plaintext_options, + .options_count = &auth_plaintext_options_count, + .options_block = &auth_plaintext_option_defaults, + .options_len = sizeof(auth_plaintext_options_block), + .init = auth_plaintext_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_plaintext_server, +.clientcode = auth_plaintext_client, +.version_report = NULL, +.macros_create = NULL, +}; + #endif /*!MACRO_PREDEF*/ #endif /*AUTH_PLAINTEST*/ /* End of plaintext.c */ diff --git a/src/src/auths/spa.c b/src/src/auths/spa.c index 09d4e43a6..e76e0fc16 100644 --- a/src/src/auths/spa.c +++ b/src/src/auths/spa.c @@ -375,6 +375,28 @@ if (errno != 0 || buffer[0] != '3') return FAIL; } +# ifdef DYNLOOKUP +# define spa_auth_info _auth_info +# endif + +auth_info spa_auth_info = { +.drinfo = { + .driver_name = US"spa", /* lookup name */ + .options = auth_spa_options, + .options_count = &auth_spa_options_count, + .options_block = &auth_spa_option_defaults, + .options_len = sizeof(auth_spa_options_block), + .init = auth_spa_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_spa_server, +.clientcode = auth_spa_client, +.version_report = NULL, +.macros_create = NULL, +}; + #endif /*!MACRO_PREDEF*/ #endif /*AUTH_SPA*/ /* End of spa.c */ diff --git a/src/src/auths/tls.c b/src/src/auths/tls.c index 0bcb675f7..534b53639 100644 --- a/src/src/auths/tls.c +++ b/src/src/auths/tls.c @@ -95,6 +95,28 @@ return auth_check_serv_cond(ablock); } +# ifdef DYNLOOKUP +# define tls_auth_info _auth_info +# endif + +auth_info tls_auth_info = { +.drinfo = { + .driver_name = US"tls", /* lookup name */ + .options = auth_tls_options, + .options_count = &auth_tls_options_count, + .options_block = &auth_tls_option_defaults, + .options_len = sizeof(auth_tls_options_block), + .init = auth_tls_init, +# ifdef DYNLOOKUP + .dyn_magic = AUTH_MAGIC, +# endif + }, +.servercode = auth_tls_server, +.clientcode = NULL, +.version_report = NULL, +.macros_create = NULL, +}; + #endif /*!MACRO_PREDEF*/ #endif /*AUTH_TLS*/ /* End of tls.c */ diff --git a/src/src/drtables.c b/src/src/drtables.c index 026f2ff97..c490e7f86 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2023 */ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -21,230 +21,82 @@ all described in src/EDITME. */ lookup_info **lookup_list; int lookup_list_count = 0; -/* Table of information about all possible authentication mechanisms. All -entries are always present if any mechanism is declared, but the functions are -set to NULL for those that are not compiled into the binary. */ +/* Lists of information about which drivers are included in the exim binary. */ -#ifdef AUTH_CRAM_MD5 -#include "auths/cram_md5.h" -#endif +auth_info * auths_available= NULL; +router_info * routers_available = NULL; +transport_info * transports_available = NULL; -#ifdef AUTH_CYRUS_SASL -#include "auths/cyrus_sasl.h" -#endif -#ifdef AUTH_DOVECOT -#include "auths/dovecot.h" -#endif -#ifdef AUTH_EXTERNAL -#include "auths/external.h" -#endif +#ifndef MACRO_PREDEF -#ifdef AUTH_GSASL -#include "auths/gsasl_exim.h" +gstring * +auth_show_supported(gstring * g) +{ +uschar * b = US"" /* static-build authenticatornames */ +#if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5!=2 + " cram_md5" #endif - -#ifdef AUTH_HEIMDAL_GSSAPI -#include "auths/heimdal_gssapi.h" +#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL!=2 + " cyrus_sasl" #endif - -#ifdef AUTH_PLAINTEXT -#include "auths/plaintext.h" +#if defined(AUTH_DOVECOT) && AUTH_DOVECOT!=2 + " dovecot" #endif - -#ifdef AUTH_SPA -#include "auths/spa.h" +#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL!=2 + " external" #endif - -#ifdef AUTH_TLS -#include "auths/tls.h" +#if defined(AUTH_GSASL) && AUTH_GSASL!=2 + " gsasl" #endif +#if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI!=2 + " heimdal_gssapi" +#endif +#if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT!=2 + " plaintext" +#endif +#if defined(AUTH_SPA) && AUTH_SPA!=2 + " spa" +#endif +#if defined(AUTH_TLS) && AUTH_TLS!=2 + " tls" +#endif + ; -auth_info * auths_available_newlist = NULL; -auth_info auths_available_oldarray[] = { - -/* Checking by an expansion condition on plain text */ - -#ifdef AUTH_CRAM_MD5 - { - .drinfo = { - .driver_name = US"cram_md5", /* lookup name */ - .options = auth_cram_md5_options, - .options_count = &auth_cram_md5_options_count, - .options_block = &auth_cram_md5_option_defaults, - .options_len = sizeof(auth_cram_md5_options_block), - .init = auth_cram_md5_init, - }, - .servercode = auth_cram_md5_server, - .clientcode = auth_cram_md5_client, - .version_report = NULL, - .macros_create = NULL, - }, -#endif - -#ifdef AUTH_CYRUS_SASL - { - .drinfo = { - .driver_name = US"cyrus_sasl", - .options = auth_cyrus_sasl_options, - .options_count = &auth_cyrus_sasl_options_count, - .options_block = &auth_cyrus_sasl_option_defaults, - .options_len = sizeof(auth_cyrus_sasl_options_block), - .init = auth_cyrus_sasl_init, - }, - .servercode = auth_cyrus_sasl_server, - .clientcode = NULL, - .version_report = auth_cyrus_sasl_version_report, - .macros_create = NULL, - }, -#endif - -#ifdef AUTH_DOVECOT - { - .drinfo = { - .driver_name = US"dovecot", - .options = auth_dovecot_options, - .options_count = &auth_dovecot_options_count, - .options_block = &auth_dovecot_option_defaults, - .options_len = sizeof(auth_dovecot_options_block), - .init = auth_dovecot_init, - }, - .servercode = auth_dovecot_server, - .clientcode = NULL, - .version_report = NULL, - .macros_create = NULL, - }, -#endif - -#ifdef AUTH_EXTERNAL - { - .drinfo = { - .driver_name = US"external", - .options = auth_external_options, - .options_count = &auth_external_options_count, - .options_block = &auth_external_option_defaults, - .options_len = sizeof(auth_external_options_block), - .init = auth_external_init, - }, - .servercode = auth_external_server, - .clientcode = auth_external_client, - .version_report = NULL, - .macros_create = NULL, - }, -#endif - -#ifdef AUTH_GSASL - { - .drinfo = { - .driver_name = US"gsasl", - .options = auth_gsasl_options, - .options_count = &auth_gsasl_options_count, - .options_block = &auth_gsasl_option_defaults, - .options_len = sizeof(auth_gsasl_options_block), - .init = auth_gsasl_init, - }, - .servercode = auth_gsasl_server, - .clientcode = auth_gsasl_client, - .version_report = auth_gsasl_version_report, - .macros_create = auth_gsasl_macros, - }, -#endif - -#ifdef AUTH_HEIMDAL_GSSAPI - { - .drinfo = { - .driver_name = US"heimdal_gssapi", - .options = auth_heimdal_gssapi_options, - .options_count = &auth_heimdal_gssapi_options_count, - .options_block = &auth_heimdal_gssapi_option_defaults, - .options_len = sizeof(auth_heimdal_gssapi_options_block), - .init = auth_heimdal_gssapi_init, - }, - .servercode = auth_heimdal_gssapi_server, - .clientcode = NULL, - .version_report = auth_heimdal_gssapi_version_report, - .macros_create = NULL, - }, -#endif - -#ifdef AUTH_PLAINTEXT - { - .drinfo = { - .driver_name = US"plaintext", - .options = auth_plaintext_options, - .options_count = &auth_plaintext_options_count, - .options_block = &auth_plaintext_option_defaults, - .options_len = sizeof(auth_plaintext_options_block), - .init = auth_plaintext_init, - }, - .servercode = auth_plaintext_server, - .clientcode = auth_plaintext_client, - .version_report = NULL, - .macros_create = NULL, - }, -#endif - -#ifdef AUTH_SPA - { - .drinfo = { - .driver_name = US"spa", - .options = auth_spa_options, - .options_count = &auth_spa_options_count, - .options_block = &auth_spa_option_defaults, - .options_len = sizeof(auth_spa_options_block), - .init = auth_spa_init, - }, - .servercode = auth_spa_server, - .clientcode = auth_spa_client, - .version_report = NULL, - .macros_create = NULL, - }, -#endif - -#ifdef AUTH_TLS - { - .drinfo = { - .driver_name = US"tls", - .options = auth_tls_options, - .options_count = &auth_tls_options_count, - .options_block = &auth_tls_option_defaults, - .options_len = sizeof(auth_tls_options_block), - .init = auth_tls_init, - }, - .servercode = auth_tls_server, - .clientcode = NULL, - .version_report = NULL, - .macros_create = NULL, - }, -#endif - - { .drinfo = { .driver_name = US"" }} /* end marker */ -}; - - -/* Tables of information about which routers and transports are included in the -exim binary. */ - -/* Pull in the necessary header files */ - -#include "routers/rf_functions.h" - - -router_info * routers_available = NULL; -transport_info * transports_available = NULL; - - - -#ifndef MACRO_PREDEF +uschar * d = US"" /* dynamic-module authenticator names */ +#if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5==2 + " cram_md5" +#endif +#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL==2 + " cyrus_sasl" +#endif +#if defined(AUTH_DOVECOT) && AUTH_DOVECOT==2 + " dovecot" +#endif +#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL==2 + " external" +#endif +#if defined(AUTH_GSASL) && AUTH_GSASL==2 + " gsasl" +#endif +#if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI==2 + " heimdal_gssapi" +#endif +#if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT==2 + " plaintext" +#endif +#if defined(AUTH_SPA) && AUTH_SPA==2 + " spa" +#endif +#if defined(AUTH_TLS) && AUTH_TLS==2 + " tls" +#endif + ; -gstring * -auth_show_supported(gstring * g) -{ -g = string_cat(g, US"Authenticators:"); -for (auth_info * ai = auths_available_oldarray; ai->drinfo.driver_name[0]; ai++) - g = string_fmt_append(g, " %s", ai->drinfo.driver_name); -return string_cat(g, US"\n"); +if (*b) g = string_fmt_append(g, "Authenticators (built-in):%s\n", b); +if (*d) g = string_fmt_append(g, "Authenticators (dynamic): %s\n", d); +return g; } gstring * diff --git a/src/src/exim.c b/src/src/exim.c index 8eb602a17..66746e6bb 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1382,8 +1382,7 @@ g = show_db_version(g); show_string(is_stdout, g); g = NULL; -//for (auth_info * ai= auths_available; *ai->drinfo.driver_name != '\0'; ai++) -for (auth_info * ai = auths_available_newlist; ai; ai = (auth_info *)ai->drinfo.next) +for (auth_info * ai = auths_available; ai; ai = (auth_info *)ai->drinfo.next) if (ai->version_report) g = (*ai->version_report)(g); diff --git a/src/src/globals.h b/src/src/globals.h index 7fc888f5b..9731b7b58 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -374,8 +374,7 @@ extern uschar *authenticated_sender; /* From AUTH on MAIL */ extern BOOL authentication_failed; /* TRUE if AUTH was tried and failed */ extern uschar *authenticator_name; /* for debug and error messages */ extern uschar *auth_advertise_hosts; /* Only advertise to these */ -extern auth_info auths_available_oldarray[]; /* Vector of available auth mechanisms */ -extern auth_info * auths_available_newlist; +extern auth_info * auths_available; /* List of available auth mechanisms */ extern auth_instance *auths; /* Chain of instantiated auths */ extern auth_instance auth_defaults; /* Default values */ extern uschar *auth_defer_msg; /* Error message for log */ diff --git a/src/src/readconf.c b/src/src/readconf.c index 57788afc3..532e0774c 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -435,10 +435,7 @@ uschar buf[EXIM_DRIVERNAME_MAX]; options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL); -#ifdef old -for (struct auth_info * ai = auths_available; ai->drinfo.driver_name[0]; ai++) -#endif -for (driver_info * di = (driver_info *)auths_available_newlist; di; di = di->next) +for (driver_info * di = (driver_info *)auths_available; di; di = di->next) { auth_info * ai = (auth_info *)di; @@ -3738,7 +3735,7 @@ driver_info * di; int len; DIR * dd; -/* First scan the list of statically-built drivers. */ +/* First scan the list of driver seen so far. */ for (di = *info_anchor; di; di = di->next) if (Ustrcmp(d->driver_name, di->driver_name) == 0) @@ -3859,8 +3856,8 @@ Arguments: instance_size size of instance block driver_optionlist generic option list driver_optionlist_count count of generic option list - class "router", "transport", or "authenticator" - for error message + class "router", "transport", or "auth" + for filename component (and error message) Returns: nothing */ @@ -4328,25 +4325,65 @@ auths_init(void) #ifndef DISABLE_PIPE_CONNECT int nauths = 0; #endif - -for (auth_info * tblent = auths_available_oldarray; - *tblent->drinfo.driver_name; tblent++) +int old_pool = store_pool; +store_pool = POOL_PERM; { - driver_info * listent = store_get(sizeof(auth_info), tblent); - memcpy(listent, tblent, sizeof(auth_info)); - listent->next = (driver_info *)auths_available_newlist; - auths_available_newlist = (auth_info *)listent; + driver_info ** anchor = (driver_info **) &auths_available; + extern auth_info cram_md5_auth_info; + extern auth_info cyrus_sasl_auth_info; + extern auth_info dovecot_auth_info; + extern auth_info external_auth_info; + extern auth_info gsasl_auth_info; + extern auth_info heimdal_gssapi_auth_info; + extern auth_info plaintext_auth_info; + extern auth_info spa_auth_info; + extern auth_info tls_auth_info; + + /* Add the transport drivers that are built for static linkage to the + list of availables. */ + +#if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5!=2 + add_driver_info(anchor, &cram_md5_auth_info.drinfo, sizeof(auth_info)); +#endif +#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL!=2 + add_driver_info(anchor, &cyrus_sasl_auth_info.drinfo, sizeof(auth_info)); +#endif +#if defined(AUTH_DOVECOT) && AUTH_DOVECOT!=2 + add_driver_info(anchor, &dovecot_auth_info.drinfo, sizeof(auth_info)); +#endif +#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL!=2 + add_driver_info(anchor, &external_auth_info.drinfo, sizeof(auth_info)); +#endif +#if defined(AUTH_GSASL) && AUTH_GSASL!=2 + add_driver_info(anchor, &gsasl_auth_info.drinfo, sizeof(auth_info)); +#endif +#if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI!=2 + add_driver_info(anchor, &heimdal_gssapi_auth_info.drinfo, sizeof(auth_info)); +#endif +#if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT!=2 + add_driver_info(anchor, &plaintext_auth_info.drinfo, sizeof(auth_info)); +#endif +#if defined(AUTH_SPA) && AUTH_SPA!=2 + add_driver_info(anchor, &spa_auth_info.drinfo, sizeof(auth_info)); +#endif +#if defined(AUTH_TLS) && AUTH_TLS!=2 + add_driver_info(anchor, &tls_auth_info.drinfo, sizeof(auth_info)); +#endif } +store_pool = old_pool; +/* Read the config file "authenticators" section, creating an auth instance list. +For any yet-undiscovered driver, check for a loadable module and add it to +those available. */ readconf_driver_init((driver_instance **)&auths, /* chain anchor */ - (driver_info **)&auths_available_newlist, /* available drivers */ + (driver_info **)&auths_available, /* available drivers */ sizeof(auth_info), /* size of info block */ &auth_defaults, /* default values for generic options */ sizeof(auth_instance), /* size of instance block */ optionlist_auths, /* generic options */ optionlist_auths_size, - US"authenticator"); + US"auth"); for (auth_instance * au = auths; au; au = au->drinst.next) { @@ -4367,7 +4404,7 @@ for (auth_instance * au = auths; au; au = au->drinst.next) #endif } #ifndef DISABLE_PIPE_CONNECT -f.smtp_in_early_pipe_no_auth = nauths > 16; +f.smtp_in_early_pipe_no_auth = nauths > 16; /* bits in bitmap limit */ #endif } diff --git a/src/src/transports/Makefile b/src/src/transports/Makefile index 43418fd22..895dad57f 100644 --- a/src/src/transports/Makefile +++ b/src/src/transports/Makefile @@ -25,8 +25,9 @@ transports.a: $(OBJ) smtp_socks.o tf_maildir.o .c.o:; @echo "$(CC) $*.c" $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c +SO_FLAGS = -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) .c.so:; @echo "$(CC) -shared $*.c" - $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@ + $(FE)$(CC) $(SO_FLAGS) $*.c -o $@ $(OBJ) $(MOD): $(HDRS) @@ -47,7 +48,6 @@ tf_maildir.o: tf_maildir.c tf_maildir.h appendfile.h appendfile.so: appendfile.c appendfile.h tf_maildir.c tf_maildir.h @echo "$(CC) -shared appendfile.c tf_maildir.c" - $(FE)$(CC) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) \ - appendfile.c tf_maildir.c -o $@ + $(FE)$(CC) $(SO_FLAGS) appendfile.c tf_maildir.c -o $@ # End diff --git a/test/confs/0000 b/test/confs/0000 index 5db8ac8cf..e25ac4b4f 100644 --- a/test/confs/0000 +++ b/test/confs/0000 @@ -12,5 +12,6 @@ keep_environment = begin routers begin transports +begin authenticators # End diff --git a/test/runtest b/test/runtest index 632672324..0c9f2808b 100755 --- a/test/runtest +++ b/test/runtest @@ -1160,7 +1160,7 @@ RESET_AFTER_EXTRA_LINE_READ: (?: .*\sBerkeley\ DB | \sProbably\ (?:Berkeley\ DB|ndbm|GDBM) | \sUsing\ (?:tdb|sqlite3) - | Authenticators: + | Authenticators\ \((?:built-in|dynamic)\): | Lookups(?:\(built-in\))?: | Support\ for: | Routers\ \((?:built-in|dynamic)\): @@ -3741,12 +3741,13 @@ while () @parm_lookups{keys %temp_lookups} = values %temp_lookups; } - elsif (/^Authenticators: (.*)/) + elsif (/^Authenticators \((?:built-in|dynamic)\): ?(.*)/) { print; @temp = split /(\s+)/, $1; push(@temp, ' '); - %parm_authenticators = @temp; + my %temp_auths= @temp; + @parm_authenticators{keys %temp_auths} = values %temp_auths; } elsif (/^Routers \((?:built-in|dynamic)\): ?(.*)/) diff --git a/test/stderr/0402 b/test/stderr/0402 index 8c4dda874..d0906810d 100644 --- a/test/stderr/0402 +++ b/test/stderr/0402 @@ -546,6 +546,9 @@ expanded: 'TESTSUITE/test-mail/junk' file is not a filter file parse_forward_list: TESTSUITE/test-mail/junk extract item: TESTSUITE/test-mail/junk +try option errors_to +try option headers_add +try option headers_remove try option file_transport try option transport set transport ft1 @@ -604,6 +607,9 @@ expanded: 'TESTSUITE/test-mail/junk' file is not a filter file parse_forward_list: TESTSUITE/test-mail/junk extract item: TESTSUITE/test-mail/junk +try option errors_to +try option headers_add +try option headers_remove try option file_transport try option transport set transport ft1 @@ -650,6 +656,9 @@ try option set calling r3 router r3 router called for userz@test.ex domain = test.ex +try option errors_to +try option headers_add +try option headers_remove try option transport set transport t2 queued for t2 transport: local_part = userz @@ -688,6 +697,9 @@ try option set calling r2 router r2 router called for usery@test.ex domain = test.ex +try option errors_to +try option headers_add +try option headers_remove try option transport set transport t1 queued for t1 transport: local_part = usery @@ -719,6 +731,9 @@ try option set calling r1 router r1 router called for CALLER@test.ex domain = test.ex +try option errors_to +try option headers_add +try option headers_remove try option transport set transport t1 queued for t1 transport: local_part = CALLER diff --git a/test/stderr/0544 b/test/stderr/0544 index de449b123..84ecb04f3 100644 --- a/test/stderr/0544 +++ b/test/stderr/0544 @@ -369,10 +369,16 @@ admin user dropping to exim gid; retaining priv uid try option router_home_directory try option set +try option errors_to +try option headers_add +try option headers_remove try option transport try option unseen try option router_home_directory try option set +try option errors_to +try option headers_add +try option headers_remove try option transport try option unseen try option multi_domain diff --git a/test/stderr/5410 b/test/stderr/5410 index 4ee810ac3..471e04156 100644 --- a/test/stderr/5410 +++ b/test/stderr/5410 @@ -95,6 +95,9 @@ processing address_data domain.com in "*"? list element: * domain.com in "*"? yes (matched "*") +try option errors_to +try option headers_add +try option headers_remove try option transport try option unseen ----------- end verify ------------ @@ -122,6 +125,9 @@ processing address_data domain.com in "*"? list element: * domain.com in "*"? yes (matched "*") +try option errors_to +try option headers_add +try option headers_remove try option transport try option unseen try option interface @@ -766,6 +772,9 @@ processing address_data domain.com in "*"? list element: * domain.com in "*"? yes (matched "*") +try option errors_to +try option headers_add +try option headers_remove try option transport try option unseen ----------- end verify ------------ @@ -793,6 +802,9 @@ processing address_data domain.com in "*"? list element: * domain.com in "*"? yes (matched "*") +try option errors_to +try option headers_add +try option headers_remove try option transport try option unseen try option interface @@ -1386,6 +1398,9 @@ processing address_data domain.com in "*"? list element: * domain.com in "*"? yes (matched "*") +try option errors_to +try option headers_add +try option headers_remove try option transport try option unseen ----------- end verify ------------ @@ -1413,6 +1428,9 @@ processing address_data domain.com in "*"? list element: * domain.com in "*"? yes (matched "*") +try option errors_to +try option headers_add +try option headers_remove try option transport try option unseen try option interface