.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
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
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
-----------------
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
------------
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
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
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.
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
# Copyright (c) The Exim Maintainers 1995 - 2024
# SPDX-License-Identifier: GPL-2.0-or-later
-set -e
class=${CLASS:?}
classdef=${CLASSDEF:?}
drnames="${DRNAMES:?}"
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"
# 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
# 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
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 *
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 */
/*
- * Copyright (c) The Exim Maintainers 2006 - 2024
- * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
- * 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 <pazke@donpac.ru>
+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
}
+# 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 */
+# 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 */
--- /dev/null
+/*************************************************
+* 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 <pdp@exim.org> */
+/* Copyright (c) Phil Pennock 2012 */
+
+/* Interface to GNU SASL library for generic authentication. */
+
+/* Trade-offs:
+
+GNU SASL does not provide authentication data itself, so we have to expose
+that decision to configuration. For some mechanisms, we need to act much
+like plaintext. For others, we only need to be able to provide some
+evaluated data on demand. There's no abstracted way (ie, without hardcoding
+knowledge of authenticators here) to know which need what properties; we
+can't query a session or the library for "we will need these for mechanism X".
+
+So: we always require server_condition, even if sometimes it will just be
+set as "yes". We do provide a number of other hooks, which might not make
+sense in all contexts. For some, we can do checks at init time.
+*/
+
+#include "../exim.h"
+
+#ifndef AUTH_GSASL
+/* dummy function to satisfy compilers when we link in an "empty" file. */
+static void dummy(int x);
+static void dummy2(int x) { dummy(x-1); }
+static void dummy(int x) { dummy2(x-1); }
+#else
+
+#include <gsasl.h>
+#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 */
--- /dev/null
+/*************************************************
+* 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 */
+++ /dev/null
-/*************************************************
-* 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 <pdp@exim.org> */
-/* Copyright (c) Phil Pennock 2012 */
-
-/* Interface to GNU SASL library for generic authentication. */
-
-/* Trade-offs:
-
-GNU SASL does not provide authentication data itself, so we have to expose
-that decision to configuration. For some mechanisms, we need to act much
-like plaintext. For others, we only need to be able to provide some
-evaluated data on demand. There's no abstracted way (ie, without hardcoding
-knowledge of authenticators here) to know which need what properties; we
-can't query a session or the library for "we will need these for mechanism X".
-
-So: we always require server_condition, even if sometimes it will just be
-set as "yes". We do provide a number of other hooks, which might not make
-sense in all contexts. For some, we can do checks at init time.
-*/
-
-#include "../exim.h"
-
-#ifndef AUTH_GSASL
-/* dummy function to satisfy compilers when we link in an "empty" file. */
-static void dummy(int x);
-static void dummy2(int x) { dummy(x-1); }
-static void dummy(int x) { dummy2(x-1); }
-#else
-
-#include <gsasl.h>
-#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 */
+++ /dev/null
-/*************************************************
-* 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 */
* 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 */
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;
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);
OM_uint32 maj_stat, min_stat;
OM_uint32 msgcontext = 0;
gss_buffer_desc status_string;
-gstring * g;
+gstring * g = NULL;
HDEBUG(D_auth)
{
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 */
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 */
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 */
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 */
}
+# 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 */
* 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 */
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 *
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);
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 */
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;
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)
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
*/
#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)
{
#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
}
.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)
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
begin routers
begin transports
+begin authenticators
# End
(?: .*\sBerkeley\ DB
| \sProbably\ (?:Berkeley\ DB|ndbm|GDBM)
| \sUsing\ (?:tdb|sqlite3)
- | Authenticators:
+ | Authenticators\ \((?:built-in|dynamic)\):
| Lookups(?:\(built-in\))?:
| Support\ for:
| Routers\ \((?:built-in|dynamic)\):
@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)\): ?(.*)/)
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
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
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
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
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
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
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 ------------
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
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 ------------
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
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 ------------
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