authenticator dynamic modules
authorJeremy Harris <jgh146exb@wizmail.org>
Fri, 16 Aug 2024 18:33:48 +0000 (19:33 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Fri, 16 Aug 2024 22:03:55 +0000 (23:03 +0100)
32 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/OS/Makefile-Base
src/scripts/Configure-Makefile
src/scripts/MakeLinks
src/scripts/drivers-Makefile
src/src/EDITME
src/src/auths/Makefile
src/src/auths/cram_md5.c
src/src/auths/cyrus_sasl.c
src/src/auths/dovecot.c
src/src/auths/external.c
src/src/auths/gsasl.c [new file with mode: 0644]
src/src/auths/gsasl.h [new file with mode: 0644]
src/src/auths/gsasl_exim.c [deleted file]
src/src/auths/gsasl_exim.h [deleted file]
src/src/auths/heimdal_gssapi.c
src/src/auths/heimdal_gssapi.h
src/src/auths/plaintext.c
src/src/auths/spa.c
src/src/auths/tls.c
src/src/drtables.c
src/src/exim.c
src/src/globals.h
src/src/readconf.c
src/src/transports/Makefile
test/confs/0000
test/runtest
test/stderr/0402
test/stderr/0544
test/stderr/5410

index 9fbf7a2db31bf9b29a21d872a97c5d9790928cc8..f73792ac5f848c5cba8d615846a87a275f2fccdd 100644 (file)
@@ -2073,6 +2073,7 @@ withdrawn.
 .cindex "lookup modules"
 .cindex "router modules"
 .cindex "transport modules"
 .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
 .cindex "dynamic modules"
 .cindex ".so building"
 On some platforms, Exim supports not compiling all lookup types directly into
@@ -2084,7 +2085,8 @@ dependencies.
 Most, but not all, lookup types can be built this way.
 
 .new
 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
 This permits a smaller exim binary, growing only as needed for the
 runtime cofiguration.
 .wen
index 3a0e3efc504f5915f37cba24662e6a6b9dee17cf..adfd5cb8ffe57784b8479eca290717fd9c71bfe1 100644 (file)
@@ -40,6 +40,11 @@ JH/07 Bug 3106: Fix coding in SPA authenticator. A macro argument was not
 JH/08 The output of "exim -bV" now includes lookup types built as dynamic-load
       modules.
 
 JH/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
 -----------------
 
 Exim version 4.98
 -----------------
 
index be0f0c679643daa6745039c77ad88e437deaf59b..1910ad00213a7cd8e3720fadc5d581a7a7e90142 100644 (file)
@@ -14,8 +14,9 @@ Version 4.98
 
  3. Events smtp:fail:protocol and smtp:fail:syntax
 
 
  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
 ------------
 
 Version 4.98
 ------------
index d45524561286e69e3f603857d7971f5a398ff29e..3f2d4d8839d096a29b46ba23a2598aea8d8782cd 100644 (file)
@@ -119,7 +119,7 @@ OBJ_MACRO = macro_predef.o \
        macro-appendfile.o macro-autoreply.o macro-lmtp.o macro-pipe.o macro-queuefile.o \
        macro-smtp.o macro-accept.o macro-dnslookup.o macro-ipliteral.o macro-iplookup.o \
        macro-manualroute.o macro-queryprogram.o macro-redirect.o \
        macro-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-heimdal_gssapi.o macro-plaintext.o macro-spa.o macro-authtls.o macro-external.o \
        macro-dkim.o macro-malware.o macro-signing.o
 
@@ -206,9 +206,9 @@ macro-dovecot.o:    auths/dovecot.c
 macro-external.o:      auths/external.c
        @echo "$(CC) -DMACRO_PREDEF auths/external.c"
        $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/external.c
 macro-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
 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
index 79ab19dccf3b6acd39d73672f8a122db5ec8a7ad..9179392f3123233925bffdc4834773ec3a0f1974 100755 (executable)
@@ -298,8 +298,9 @@ do
   mv $class/Makefile.postdynamic $class/Makefile
   rm $class/Makefile.predynamic
 done <<-END
   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
  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.
 END
 
 # See if there is a definition of EXIM_PERL in what we have built so far.
index 998b73bf9f7e199c5c97fb20a19125337bdb6ae9..76859ce9abf2bca8f06875813111be91268e1fd7 100755 (executable)
@@ -77,11 +77,12 @@ cd ..
 d="auths"
 mkdir $d
 cd $d
 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 \
   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
   spa.h tls.c tls.h external.c external.h
 do
   ln -s ../../src/$d/$f $f
index 0c3da19cbe9a19e255a29154f585f1cd9188a244..2dd9580438342c42d8349b5d77c7de53813307ae 100755 (executable)
@@ -3,7 +3,6 @@
 # Copyright (c) The Exim Maintainers 1995 - 2024
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 # Copyright (c) The Exim Maintainers 1995 - 2024
 # SPDX-License-Identifier: GPL-2.0-or-later
 
-set -e
 class=${CLASS:?}
 classdef=${CLASSDEF:?}
 drnames="${DRNAMES:?}"
 class=${CLASS:?}
 classdef=${CLASSDEF:?}
 drnames="${DRNAMES:?}"
@@ -137,16 +136,17 @@ emit_module_rule() {
       exit 1
     fi
     MODS="${MODS} ${mod_name}.so"
       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"
   elif want_at_all "$name"
   then
     OBJ="${OBJ} ${mod_name}.o"
index 820793032de4fb087dc8ad3c41a167cfa5502b7c..b930f00a735a0ec0d5ec7e7eac2a88c2c9e1783f 100644 (file)
@@ -827,6 +827,20 @@ FIXED_NEVER_USERS=root
 # you must uncomment at least one of the following, so that appropriate code is
 # included in the Exim binary. You will then need to set up the run time
 # configuration to make use of the mechanism(s) selected.
 # 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
 
 # AUTH_CRAM_MD5=yes
 # AUTH_CYRUS_SASL=yes
index ac5cf865b7fccc13450f82196dedfd78bf9312c5..fa60acba9b7d6c2e3a819b6fc1c8206650fc055e 100644 (file)
@@ -4,40 +4,61 @@
 # linked in only when needed. This Makefile is called from the main make file,
 # after cd'ing to the auths subdirectory. When the relevant AUTH_ macros are
 # defined, the equivalent modules herein is not included in the final binary.
 # 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
 
 # End
index a64177f2a024c0b11ff01ac2b1169f1824672e0c..7b41ee06506f67ce49854e9c4f9f99dfecf04ca2 100644 (file)
@@ -334,10 +334,34 @@ if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS big_buffer,
 return smtp_read_response(sx, US buffer, buffsize, '2', timeout)
   ? OK : FAIL;
 }
 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*/
 
 #  endif  /*AUTH_CRAM_MD5*/
 # endif  /*!STAND_ALONE*/
 
-
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
 /*************************************************
 **************************************************
 *             Stand-alone test program           *
index 8266e23190a7f0e652ea432ad1b1124851bacc0a..ed0995637bde81b8b8d5f240d86f8efadcc9444a 100644 (file)
@@ -508,6 +508,29 @@ auth_cyrus_sasl_client(
 return FAIL;
 }
 
 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 */
 
 #endif   /*!MACRO_PREDEF*/
 #endif  /* AUTH_CYRUS_SASL */
 
index fdfdbc749234e40b10b36777c11cf7a0a5395aa9..ee69436becc92cdfb956827fe70da5316e8deddf 100644 (file)
@@ -1,13 +1,13 @@
 /*
 /*
- * 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
 
 /* A number of modifications have been made to the original code. Originally I
 commented them specially, but now they are getting quite extensive, so I have
@@ -541,6 +541,28 @@ return ret;
 }
 
 
 }
 
 
+# ifdef DYNLOOKUP
+#  define dovecot_auth_info _auth_info
+# endif
+
+auth_info dovecot_auth_info = {
+.drinfo = {
+  .driver_name =       US"dovecot",                   /* lookup name */
+  .options =           auth_dovecot_options,
+  .options_count =     &auth_dovecot_options_count,
+  .options_block =     &auth_dovecot_option_defaults,
+  .options_len =       sizeof(auth_dovecot_options_block),
+  .init =              auth_dovecot_init,
+# ifdef DYNLOOKUP
+  .dyn_magic =         AUTH_MAGIC,
+# endif
+  },
+.servercode =          auth_dovecot_server,
+.clientcode =          NULL,
+.version_report =      NULL,
+.macros_create =       NULL,
+};
+
 #endif /*!MACRO_PREDEF*/
 #endif /*AUTH_DOVECOT*/
 /* end of auths/dovecot.c */
 #endif /*!MACRO_PREDEF*/
 #endif /*AUTH_DOVECOT*/
 /* end of auths/dovecot.c */
index 137d1e04387bd273323e91c3f2f64cbe706d719b..de0d07f86b66fb3484f579aa98652b61e3e0b2cc 100644 (file)
@@ -159,6 +159,28 @@ return OK;
 
 
 
 
 
 
+# ifdef DYNLOOKUP
+#  define external_auth_info _auth_info
+# endif
+
+auth_info external_auth_info = {
+.drinfo = {
+  .driver_name =       US"external",                   /* lookup name */
+  .options =           auth_external_options,
+  .options_count =     &auth_external_options_count,
+  .options_block =     &auth_external_option_defaults,
+  .options_len =       sizeof(auth_external_options_block),
+  .init =              auth_external_init,
+# ifdef DYNLOOKUP
+  .dyn_magic =         AUTH_MAGIC,
+# endif
+  },
+.servercode =          auth_external_server,
+.clientcode =          auth_external_client,
+.version_report =      NULL,
+.macros_create =       NULL,
+};
+
 #endif /*!MACRO_PREDEF*/
 #endif /*AUTH_EXTERNAL*/
 /* End of external.c */
 #endif /*!MACRO_PREDEF*/
 #endif /*AUTH_EXTERNAL*/
 /* End of external.c */
diff --git a/src/src/auths/gsasl.c b/src/src/auths/gsasl.c
new file mode 100644 (file)
index 0000000..e128dac
--- /dev/null
@@ -0,0 +1,1088 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2019 - 2024 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (c) Twitter Inc 2012
+   Author: Phil Pennock <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 */
diff --git a/src/src/auths/gsasl.h b/src/src/auths/gsasl.h
new file mode 100644 (file)
index 0000000..180d4c8
--- /dev/null
@@ -0,0 +1,54 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2019 - 2022 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (c) Twitter Inc 2012 */
+
+/* Interface to GNU SASL library for generic authentication. */
+
+/* Authenticator-specific options. */
+
+typedef struct {
+  uschar *server_service;
+  uschar *server_hostname;
+  uschar *server_realm;
+  uschar *server_mech;
+  uschar *server_password;
+  uschar *server_key;
+  uschar *server_s_key;
+  uschar *server_scram_iter;
+  uschar *server_scram_salt;
+
+  uschar *client_username;
+  uschar *client_password;
+  uschar *client_authz;
+  uschar *client_spassword;
+
+  BOOL    server_channelbinding;
+  BOOL   client_channelbinding;
+} auth_gsasl_options_block;
+
+/* Data for reading the authenticator-specific options. */
+
+extern optionlist auth_gsasl_options[];
+extern int auth_gsasl_options_count;
+
+/* Defaults for the authenticator-specific options. */
+
+extern auth_gsasl_options_block auth_gsasl_option_defaults;
+
+/* The entry points for the mechanism */
+
+extern void auth_gsasl_init(driver_instance *);
+extern int auth_gsasl_server(auth_instance *, uschar *);
+extern int auth_gsasl_client(auth_instance *, void *,
+                               int, uschar *, int);
+extern gstring * auth_gsasl_version_report(gstring *);
+extern void auth_gsasl_macros(void);
+
+/* End of gsasl_exim.h */
diff --git a/src/src/auths/gsasl_exim.c b/src/src/auths/gsasl_exim.c
deleted file mode 100644 (file)
index 55ac15b..0000000
+++ /dev/null
@@ -1,1066 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) The Exim Maintainers 2019 - 2023 */
-/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Copyright (c) Twitter Inc 2012
-   Author: Phil Pennock <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 */
diff --git a/src/src/auths/gsasl_exim.h b/src/src/auths/gsasl_exim.h
deleted file mode 100644 (file)
index 180d4c8..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) The Exim Maintainers 2019 - 2022 */
-/* Copyright (c) University of Cambridge 1995 - 2012 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Copyright (c) Twitter Inc 2012 */
-
-/* Interface to GNU SASL library for generic authentication. */
-
-/* Authenticator-specific options. */
-
-typedef struct {
-  uschar *server_service;
-  uschar *server_hostname;
-  uschar *server_realm;
-  uschar *server_mech;
-  uschar *server_password;
-  uschar *server_key;
-  uschar *server_s_key;
-  uschar *server_scram_iter;
-  uschar *server_scram_salt;
-
-  uschar *client_username;
-  uschar *client_password;
-  uschar *client_authz;
-  uschar *client_spassword;
-
-  BOOL    server_channelbinding;
-  BOOL   client_channelbinding;
-} auth_gsasl_options_block;
-
-/* Data for reading the authenticator-specific options. */
-
-extern optionlist auth_gsasl_options[];
-extern int auth_gsasl_options_count;
-
-/* Defaults for the authenticator-specific options. */
-
-extern auth_gsasl_options_block auth_gsasl_option_defaults;
-
-/* The entry points for the mechanism */
-
-extern void auth_gsasl_init(driver_instance *);
-extern int auth_gsasl_server(auth_instance *, uschar *);
-extern int auth_gsasl_client(auth_instance *, void *,
-                               int, uschar *, int);
-extern gstring * auth_gsasl_version_report(gstring *);
-extern void auth_gsasl_macros(void);
-
-/* End of gsasl_exim.h */
index a5eef8d954a21440fb910aa1f56063b28af13202..8e9e3866089df9faa7deb34566543086a8595d46 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -228,7 +228,7 @@ int
 auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
 {
 auth_heimdal_gssapi_options_block * ob =
 auth_heimdal_gssapi_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;
 gss_name_t gclient = GSS_C_NO_NAME;
 gss_name_t gserver = GSS_C_NO_NAME;
 gss_cred_id_t gcred = GSS_C_NO_CREDENTIAL;
@@ -250,7 +250,7 @@ uschar requested_qop;
 store_reset_point = store_mark();
 
 HDEBUG(D_auth)
 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);
 
 /* Construct our gss_name_t gserver describing ourselves */
 tmp1 = expand_string(ob->server_service);
@@ -548,7 +548,7 @@ va_list ap;
 OM_uint32 maj_stat, min_stat;
 OM_uint32 msgcontext = 0;
 gss_buffer_desc status_string;
 OM_uint32 maj_stat, min_stat;
 OM_uint32 msgcontext = 0;
 gss_buffer_desc status_string;
-gstring * g;
+gstring * g = NULL;
 
 HDEBUG(D_auth)
   {
 
 HDEBUG(D_auth)
   {
@@ -610,9 +610,31 @@ build-time and export the result as a string into a header ourselves. */
 
 return string_fmt_append(g, "Library version: Heimdal: Runtime: %s\n"
                            " Build Info: %s\n",
 
 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 */
 
 #endif   /*!MACRO_PREDEF*/
 #endif  /* AUTH_HEIMDAL_GSSAPI */
 
index c438aae91fa0f39f27f1c4051d0130715cbc4a20..e1d308ac9b3024c34dfb1dda2d07b842cf015a75 100644 (file)
@@ -35,6 +35,6 @@ extern auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults;
 extern void auth_heimdal_gssapi_init(driver_instance *);
 extern int auth_heimdal_gssapi_server(auth_instance *, uschar *);
 extern int auth_heimdal_gssapi_client(auth_instance *, void *, int, uschar *, int);
 extern void auth_heimdal_gssapi_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 */
 
 /* End of heimdal_gssapi.h */
index 26ac4aeffad5c36ec12067c3fbf5a8305dbcebf3..13d05d1e5eac23c8e34f078532d6fa5be65627fd 100644 (file)
@@ -180,6 +180,29 @@ while ((s = string_nextinlist(&text, &sep, NULL, 0)))
 return FAIL;
 }
 
 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 */
 #endif /*!MACRO_PREDEF*/
 #endif /*AUTH_PLAINTEST*/
 /* End of plaintext.c */
index 09d4e43a62ac5c1b3e984067927300ff593cffac..e76e0fc16d2a7707bfaa76114048047d92010126 100644 (file)
@@ -375,6 +375,28 @@ if (errno != 0 || buffer[0] != '3')
 return FAIL;
 }
 
 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 */
 #endif /*!MACRO_PREDEF*/
 #endif /*AUTH_SPA*/
 /* End of spa.c */
index 0bcb675f7180f6beb82db24c92bd4da9f298039f..534b536391a7ec4b189148dc5a457ca6e4b41d26 100644 (file)
@@ -95,6 +95,28 @@ return auth_check_serv_cond(ablock);
 }
 
 
 }
 
 
+# ifdef DYNLOOKUP
+#  define tls_auth_info _auth_info
+# endif
+
+auth_info tls_auth_info = {
+.drinfo = {
+  .driver_name =       US"tls",                   /* lookup name */
+  .options =           auth_tls_options,
+  .options_count =     &auth_tls_options_count,
+  .options_block =     &auth_tls_option_defaults,
+  .options_len =       sizeof(auth_tls_options_block),
+  .init =              auth_tls_init,
+# ifdef DYNLOOKUP
+  .dyn_magic =         AUTH_MAGIC,
+# endif
+  },
+.servercode =          auth_tls_server,
+.clientcode =          NULL,
+.version_report =      NULL,
+.macros_create =       NULL,
+};
+
 #endif /*!MACRO_PREDEF*/
 #endif /*AUTH_TLS*/
 /* End of tls.c */
 #endif /*!MACRO_PREDEF*/
 #endif /*AUTH_TLS*/
 /* End of tls.c */
index 026f2ff97b712b7d71b5a682747e155d57fffe56..c490e7f86809b7c28cb4026ebe64a0a89a59de29 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     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 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -21,230 +21,82 @@ all described in src/EDITME. */
 lookup_info **lookup_list;
 int lookup_list_count = 0;
 
 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
 #endif
-
-#ifdef AUTH_HEIMDAL_GSSAPI
-#include "auths/heimdal_gssapi.h"
+#if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL!=2
+  " cyrus_sasl"
 #endif
 #endif
-
-#ifdef AUTH_PLAINTEXT
-#include "auths/plaintext.h"
+#if defined(AUTH_DOVECOT) && AUTH_DOVECOT!=2
+  " dovecot"
 #endif
 #endif
-
-#ifdef AUTH_SPA
-#include "auths/spa.h"
+#if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL!=2
+  " external"
 #endif
 #endif
-
-#ifdef AUTH_TLS
-#include "auths/tls.h"
+#if defined(AUTH_GSASL) && AUTH_GSASL!=2
+  " gsasl"
 #endif
 #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 *
 }
 
 gstring *
index 8eb602a171f96cab312c7e679f0ab9af46ca9714..66746e6bb9f2982bae50c6b3eebfdd561f273660 100644 (file)
@@ -1382,8 +1382,7 @@ g = show_db_version(g);
 show_string(is_stdout, g);
 g = NULL;
 
 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);
 
   if (ai->version_report)
     g = (*ai->version_report)(g);
 
index 7fc888f5baee4bf12cace4603418817eb5a1c5f2..9731b7b5851c98f8e04b4bbb0a338139d490dab6 100644 (file)
@@ -374,8 +374,7 @@ extern uschar *authenticated_sender;   /* From AUTH on MAIL */
 extern BOOL    authentication_failed;  /* TRUE if AUTH was tried and failed */
 extern uschar *authenticator_name;     /* for debug and error messages */
 extern uschar *auth_advertise_hosts;   /* Only advertise to these */
 extern 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 */
 extern auth_instance *auths;           /* Chain of instantiated auths */
 extern auth_instance auth_defaults;    /* Default values */
 extern uschar *auth_defer_msg;         /* Error message for log */
index 57788afc39c28b561dbf5cdb7978e623a75df8fc..532e0774c6fdfc934222090eabdf8282ee5b6514 100644 (file)
@@ -435,10 +435,7 @@ uschar buf[EXIM_DRIVERNAME_MAX];
 options_from_list(optionlist_auths, optionlist_auths_size,
   US"AUTHENTICATORS", NULL);
 
 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;
 
   {
   auth_info * ai = (auth_info *)di;
 
@@ -3738,7 +3735,7 @@ driver_info * di;
 int len;
 DIR * dd;
 
 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)
 
 for (di = *info_anchor; di; di = di->next)
   if (Ustrcmp(d->driver_name, di->driver_name) == 0)
@@ -3859,8 +3856,8 @@ Arguments:
   instance_size              size of instance block
   driver_optionlist          generic option list
   driver_optionlist_count    count of generic option list
   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
 */
 
 Returns:                     nothing
 */
@@ -4328,25 +4325,65 @@ auths_init(void)
 #ifndef DISABLE_PIPE_CONNECT
 int nauths = 0;
 #endif
 #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 */
 
 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,
   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)
   {
 
 for (auth_instance * au = auths; au; au = au->drinst.next)
   {
@@ -4367,7 +4404,7 @@ for (auth_instance * au = auths; au; au = au->drinst.next)
 #endif
   }
 #ifndef DISABLE_PIPE_CONNECT
 #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
 }
 
 #endif
 }
 
index 43418fd22effd6757f245b8c43cbc9dcfcfcb86d..895dad57fb5821a2ae49ae7994749238e6853967 100644 (file)
@@ -25,8 +25,9 @@ transports.a:    $(OBJ) smtp_socks.o tf_maildir.o
 .c.o:;           @echo "$(CC) $*.c"
                 $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
 
 .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"
 .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)
 
 
 $(OBJ) $(MOD): $(HDRS)
@@ -47,7 +48,6 @@ tf_maildir.o:                 tf_maildir.c tf_maildir.h appendfile.h
 
 appendfile.so: appendfile.c appendfile.h tf_maildir.c tf_maildir.h
        @echo "$(CC) -shared appendfile.c tf_maildir.c"
 
 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
 
 # End
index 5db8ac8cfbeab242298742fe0be8c0a2701482b4..e25ac4b4fe0c491982255999cc27275a84da6bb8 100644 (file)
@@ -12,5 +12,6 @@ keep_environment =
 
 begin routers
 begin transports
 
 begin routers
 begin transports
+begin authenticators
 
 # End
 
 # End
index 6326723249c1da38e2a8b01c4deb44fc39bf5d83..0c9f2808b94432f4a3ab636107edf79236ceeb01 100755 (executable)
@@ -1160,7 +1160,7 @@ RESET_AFTER_EXTRA_LINE_READ:
                (?: .*\sBerkeley\ DB
                  | \sProbably\ (?:Berkeley\ DB|ndbm|GDBM)
                  | \sUsing\ (?:tdb|sqlite3)
                (?: .*\sBerkeley\ DB
                  | \sProbably\ (?:Berkeley\ DB|ndbm|GDBM)
                  | \sUsing\ (?:tdb|sqlite3)
-                 | Authenticators:
+                 | Authenticators\ \((?:built-in|dynamic)\):
                  | Lookups(?:\(built-in\))?:
                  | Support\ for:
                  | Routers\ \((?:built-in|dynamic)\):
                  | Lookups(?:\(built-in\))?:
                  | Support\ for:
                  | Routers\ \((?:built-in|dynamic)\):
@@ -3741,12 +3741,13 @@ while (<EXIMINFO>)
     @parm_lookups{keys %temp_lookups} = values %temp_lookups;
     }
 
     @parm_lookups{keys %temp_lookups} = values %temp_lookups;
     }
 
-  elsif (/^Authenticators(.*)/)
+  elsif (/^Authenticators \((?:built-in|dynamic)\):  ?(.*)/)
     {
     print;
     @temp = split /(\s+)/, $1;
     push(@temp, ' ');
     {
     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)\):  ?(.*)/)
     }
 
   elsif (/^Routers \((?:built-in|dynamic)\):  ?(.*)/)
index 8c4dda874a8a992c64f4c8af052ad3324e5ad60b..d0906810d0ac62eb205ec265f3c67af7ce180801 100644 (file)
@@ -546,6 +546,9 @@ expanded: 'TESTSUITE/test-mail/junk'
 file is not a filter file
 parse_forward_list: TESTSUITE/test-mail/junk
 extract item: TESTSUITE/test-mail/junk
 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
 try option file_transport
 try option transport
 set transport ft1
@@ -604,6 +607,9 @@ expanded: 'TESTSUITE/test-mail/junk'
 file is not a filter file
 parse_forward_list: TESTSUITE/test-mail/junk
 extract item: TESTSUITE/test-mail/junk
 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
 try option file_transport
 try option transport
 set transport ft1
@@ -650,6 +656,9 @@ try option set
 calling r3 router
 r3 router called for userz@test.ex
   domain = test.ex
 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
 try option transport
 set transport t2
 queued for t2 transport: local_part = userz
@@ -688,6 +697,9 @@ try option set
 calling r2 router
 r2 router called for usery@test.ex
   domain = test.ex
 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
 try option transport
 set transport t1
 queued for t1 transport: local_part = usery
@@ -719,6 +731,9 @@ try option set
 calling r1 router
 r1 router called for CALLER@test.ex
   domain = test.ex
 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
 try option transport
 set transport t1
 queued for t1 transport: local_part = CALLER
index de449b1239d5fefdae921bbe0d9126144b6d9c10..84ecb04f3efe72cf93e4ef6c24b9a3832f2fb512 100644 (file)
@@ -369,10 +369,16 @@ admin user
 dropping to exim gid; retaining priv uid
 try option router_home_directory
 try option set
 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 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
 try option transport
 try option unseen
 try option multi_domain
index 4ee810ac3da7aecd44b0eab41a39a1d45e367dd7..471e0415672cc2d987acd4538ca328c286a345e8 100644 (file)
@@ -95,6 +95,9 @@ processing address_data
 domain.com in "*"?
  list element: *
  domain.com in "*"? yes (matched "*")
 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 ------------
 try option transport
 try option unseen
 ----------- end verify ------------
@@ -122,6 +125,9 @@ processing address_data
 domain.com in "*"?
  list element: *
  domain.com in "*"? yes (matched "*")
 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
 try option transport
 try option unseen
 try option interface
@@ -766,6 +772,9 @@ processing address_data
 domain.com in "*"?
  list element: *
  domain.com in "*"? yes (matched "*")
 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 ------------
 try option transport
 try option unseen
 ----------- end verify ------------
@@ -793,6 +802,9 @@ processing address_data
 domain.com in "*"?
  list element: *
  domain.com in "*"? yes (matched "*")
 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
 try option transport
 try option unseen
 try option interface
@@ -1386,6 +1398,9 @@ processing address_data
 domain.com in "*"?
  list element: *
  domain.com in "*"? yes (matched "*")
 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 ------------
 try option transport
 try option unseen
 ----------- end verify ------------
@@ -1413,6 +1428,9 @@ processing address_data
 domain.com in "*"?
  list element: *
  domain.com in "*"? yes (matched "*")
 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
 try option transport
 try option unseen
 try option interface