]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
v4: Switch rlm_smtp to slab allocated connection handles (#4926)
authorNick Porter <nick@portercomputing.co.uk>
Tue, 21 Mar 2023 15:28:25 +0000 (15:28 +0000)
committerGitHub <noreply@github.com>
Tue, 21 Mar 2023 15:28:25 +0000 (08:28 -0700)
* Add CURL connection config to rlm_smtp

* Add slab to rlm_smtp_thead_t and initialise and free with thread

* Rename mod_authorize to mod_mail

mod_mail is the general purpose method to send an email - not
specifically tied to a given processing section.

* Add config options to specify credentials for sending emails

* Use CURL handles from slab allocator rather than one off allocated

* Move setting of consistent CURL options to element initialiser

* Merge the two resume callbacks - they have identical functionality

* Remove unnecessary header

* Set missing list_def

* Freeing of curl slists is done by slab element destructor

* Rearrange initialisation of mail_ctx

In case errors occur and the handle is released early

* Actually set the user name and password in mod_authenticate

* Use a specific structure for SMTP header list

It's not really a map as the LHS is just names for the SMTP headers

* Remove un-used variables

* Remove checking of Auth-Type attribute

This module never sets Auth-Type, and the check only prevents the module
from being used to send a mail if Auth-Type was already set.

* CI: Improve tidy up in exim-setup.sh

* CI: Add authentication to test SMTP server

* CI: Update exim-setup to honour exim's rules on tainted data

* Fix up rlm_smtp tests

* Enable tests of rlm_smtp

* Extend wait for SMTP deliveries to allow for slow CI hosts

* CI: Update exim-setup.sh to work with Docker container

* Errors should be REDEBUG

* Talloc tmpl expansions off request rather than thread

So they are cleared when the request is freed

* WS

* Update default smtp module config

18 files changed:
.github/actions/ci-tests/action.yml
.github/workflows/ci.yml
raddb/mods-available/smtp
scripts/ci/exim-setup.sh
src/modules/rlm_smtp/rlm_smtp.c
src/tests/modules/smtp/module.conf
src/tests/modules/smtp/smtp_attachment/tls_attachment.unlang
src/tests/modules/smtp/smtp_authenticate/global.conf [new file with mode: 0644]
src/tests/modules/smtp/smtp_authenticate/module.conf
src/tests/modules/smtp/smtp_authenticate/tls_authenticate.unlang
src/tests/modules/smtp/smtp_crln/global.conf [new file with mode: 0644]
src/tests/modules/smtp/smtp_crln/module.conf
src/tests/modules/smtp/smtp_crln/tls_crln.unlang
src/tests/modules/smtp/smtp_stringparse/global.conf [new file with mode: 0644]
src/tests/modules/smtp/smtp_stringparse/module.conf
src/tests/modules/smtp/smtp_stringparse/tls_stringparse.attrs
src/tests/modules/smtp/smtp_stringparse/tls_stringparse.unlang
src/tests/modules/smtp/tls_delivery.unlang

index 13f2ce32684dba7a9c25791e0e6205e95457c90e..2d2d06798a29c25381b014750f5445178700a693 100644 (file)
@@ -50,6 +50,13 @@ inputs:
     description: IMAP server IMAPS port
     default: 1432
 
+  smtp_test_server:
+    description: SMTP server host
+    default: 127.0.0.1
+  smtp_test_server_port:
+    description: SMTP server SMTP port
+    default: 2525
+
   use_docker:
     desription: True if running in a Docker container
     default: false
@@ -165,8 +172,8 @@ runs:
         REST_TEST_SERVER: ${{ inputs.rest_test_server }}
         REST_TEST_SERVER_PORT: ${{ inputs.rest_test_port }}
         REST_TEST_SERVER_SSL_PORT: ${{ inputs.rest_test_ssl_port }}
-#       SMTP_TEST_SERVER: 127.0.0.1
-#       SMTP_TEST_SERVER_PORT: 2525
+        SMTP_TEST_SERVER: ${{ inputs.smtp_test_server }}
+        SMTP_TEST_SERVER_PORT: ${{ inputs.smtp_test_server_port }}
         REDIS_TEST_SERVER: ${{ inputs.redis_test_server }}
         REDIS_IPPOOL_TEST_SERVER: ${{ inputs.redis_test_server }}
         CACHE_REDIS_TEST_SERVER: ${{ inputs.redis_test_server }}
@@ -223,8 +230,8 @@ runs:
         REST_TEST_SERVER: ${{ inputs.rest_test_server }}
         REST_TEST_SERVER_PORT: ${{ inputs.rest_test_port }}
         REST_TEST_SERVER_SSL_PORT: ${{ inputs.rest_test_ssl_port }}
-#        SMTP_TEST_SERVER: 127.0.0.1
-#        SMTP_TEST_SERVER_PORT: 2525
+        SMTP_TEST_SERVER: ${{ inputs.smtp_test_server }}
+        SMTP_TEST_SERVER_PORT: ${{ inputs.smtp_test_server_port }}
         REDIS_TEST_SERVER: ${{ inputs.redis_test_server }}
         REDIS_IPPOOL_TEST_SERVER: ${{ inputs.redis_test_server }}
         CACHE_REDIS_TEST_SERVER: ${{ inputs.redis_test_server }}
index 761074e6229ba002b4869d31b592fd5a208a8d0d..6f4e76abaabed6cf9f384c837762aa283ea1633e 100644 (file)
@@ -177,6 +177,8 @@ jobs:
           imap_test_server: 127.0.0.1
           imap_test_server_port: 1430
           imap_test_server_ssl_port: 1432
+          smtp_test_server: 127.0.0.1
+          smtp_test_server_port: 2525
 
       - name: Run fuzzer
         uses: ./.github/actions/fuzzer
index e6ad0f980741ca96da7d22d0e152f07b983912cb..16360e92f4c1d1a31c1e84653456724d1dc50d55 100644 (file)
@@ -7,8 +7,13 @@
 #
 #  = SMTP Module
 #
-#  The `smtp` module validates a users name and password against an SMTP server.
-#  It should be called from an `authenticate` section.
+#  The `smtp` module can perform two actions:
+#
+#  When called in the `authenticate` section, it validates a users name
+#  and password from request attributes against an SMTP server without
+#  sending a mail.
+#
+#  When called with the method `mail` it will send an email.
 #
 #  The module can optionally perform a tls handshake, enabled with require_cert
 #
@@ -188,6 +193,19 @@ smtp {
        #
        timeout = 5s
 
+       #
+       #  username:: User name to use when sending emails.  Can be a fixed
+       #  string or an attribute.  Leave unset if authentication is not
+       #  required to send emails.
+       #
+#      username = "user"
+
+       #
+       #  password:: Password to use in conjuction with the above user name
+       #  for SMTP authentication.
+       #
+#      password = "secret"
+
        #
        #  template_directory:: The source directory where all file attachments are pulled from
        #  All file attachments should be their relative path from this location, without a leading /
@@ -272,4 +290,48 @@ smtp {
                Message-ID = "950124.162336@example.com"
 #              X-Originating-IP = "192.0.20.1"
        }
+
+       #
+       #  connection { .. }:: Configure how connection handles are
+       #  managed per thread.
+       #
+       connection {
+               #
+               #  Reusable connection handles are allocated in blocks.  These
+               #  parameters allow for tuning how that is done.
+               #
+               #  Since http requests are performed async, the settings here
+               #  represent outstanding http requests per thread.
+               #
+               reuse {
+
+                       #
+                       #  min:: The minimum number of connection handles to
+                       #  keep allocated.
+                       #
+                       min = 10
+
+                       #
+                       #  max:: The maximum number of reusable connection handles
+                       #  to allocate.
+                       #
+                       #  Any requests to allocate a connection handle beyond
+                       #  this number will cause a temporary handle to be allocated.
+                       #  This is less efficient than the block allocation so
+                       # `max` should be set to reflect the number of outstanding
+                       # requests expected at peak load.
+                       max = 100
+
+                       #
+                       #  cleanup_interval:: How often to free un-used connection
+                       #  handles.
+                       #
+                       #  Every `cleanup_interval` a cleanup routine runs which
+                       #  will free any blocks of handles which are not in use,
+                       #  ensuring that at least `min` handles are kept.
+                       #
+                       cleanup_interval = 30s
+
+               }
+       }
 }
index 451f8622aca833ba9244c8530f5d533b63dc6540..43463ecae69387a16545603b368291435a3ff1d3 100755 (executable)
@@ -33,7 +33,7 @@ echo "Checking for a running exim instance"
 if [ -e "${RUNDIR}/exim.pid" ]
 then
        echo "Stopping the current exim instance"
-       kill "$(cat ${RUNDIR}/exim.pid)"
+       kill "$(cat ${RUNDIR}/exim.pid)" || true
        rm -r "${BUILDDIR}"
 fi
 
@@ -65,6 +65,7 @@ LISTEN=127.0.0.1
 #  "keep_environment" setting below.
 #
 MAIL_DIR = ${MAILDIR}
+PASS_DIR = ${BUILDDIR}
 pid_file_path = ${RUNDIR}/exim.pid
 log_file_path = ${LOGDIR}/%s
 spool_directory = ${SPOOLDIR}
@@ -106,25 +107,61 @@ local_delivery:
   directory_mode = 0750
   mode = 0600
 #
-#  File to write to. Really dangerous in a normal config as it'll
-#  accept anything, including ../whatever, but we're writing our
-#  own headers so it doesn't matter so much here.
+#  File to write to.
+#  Files need to be pre-configured in the untaint file to get past exim's tainting rules.
 #
-  file = \${if eq {\$h_x-testname:}{} {MAIL_DIR/\$local_part}{MAIL_DIR/\$h_x-testname:}}
+  file = \${if eq {\$h_x-testname:}{} {MAIL_DIR/\${lookup{\$local_part}lsearch{PASS_DIR/untaint}}}{MAIL_DIR/\$h_x-testname:}}
 begin rewrite
 begin retry
 *                *                F,1s,1m
 begin authenticators
 
+plain_server:
+  driver = plaintext
+  public_name = PLAIN
+  server_condition = "\${if eq{\$auth3}{\${extract{1}{:}{\${lookup{\$auth2}lsearch{PASS_DIR/passwd}{\$value}{*:*}}}}}{1}{0}}"
+  server_set_id = \$auth2
+  server_prompts = :
+
 " >"${CONF}"
 
+echo "Generating password file"
+echo "Bob:Saget" > ${BUILDDIR}/passwd
+
+echo "Generating lookup file to untaint data"
+echo "conf_recipient_1: conf_recipient_1" > ${BUILDDIR}/untaint
+echo "conf_recipient_2: conf_recipient_2" >> ${BUILDDIR}/untaint
+echo "smtp_attachment_receiver: smtp_attachment_receiver" >> ${BUILDDIR}/untaint
+echo "crln_test_receiver: crln_test_receiver" >> ${BUILDDIR}/untaint
+echo "conf-stringparse-recipient: conf-stringparse-recipient" >> ${BUILDDIR}/untaint
+echo "stringparse_test_receiver: stringparse_test_receiver" >> ${BUILDDIR}/untaint
+echo "smtp_delivery_receiver: smtp_delivery_receiver" >> ${BUILDDIR}/untaint
+echo "smtp_recipient_request: smtp_recipient_request" >> ${BUILDDIR}/untaint
+echo "smtp_to_request_1: smtp_to_request_1" >> ${BUILDDIR}/untaint
+echo "smtp_to_request_2: smtp_to_request_2" >> ${BUILDDIR}/untaint
+echo "smtp_to_request_3: smtp_to_request_3" >> ${BUILDDIR}/untaint
+echo "smtp_cc_request_1: smtp_cc_request_1" >> ${BUILDDIR}/untaint
+echo "smtp_cc_request_2: smtp_cc_request_2" >> ${BUILDDIR}/untaint
+
 echo "Generating the file attachment"
 # Generate a file for test email attachments
 dd if=/dev/urandom bs=200 count=1 2>/dev/null | base64 | tr -d '\n'> ${BUILDDIR}/testfile
 
+EXIMUSER=$(id -u)
+if [ $EXIMUSER -eq 0 ] ; then
+       EXIMUSER=$(id -u Debian-exim)
+       EXIMGROUP=$(id -g Debian-exim)
+else
+       EXIMGROUP=$(id -g)
+fi;
+
+chown -R :$EXIMGROUP "${BUILDDIR}" "${RUNDIR}" "${MAILDELIVERYDIR}" "${MAILDIR}" "${LOGDIR}" "${SPOOLDIR}" "${CERTDIR}"
+chmod g+w -R "${RUNDIR}" "${MAILDELIVERYDIR}" "${MAILDIR}" "${LOGDIR}" "${SPOOLDIR}"
+chmod g+r -R "${CERTDIR}"
+
 #
 # Run the exim instance
 #
 echo "Starting exim"
-exim -C ${CONF} -bd -DEXIMUSER=$(id -u) -DEXIMGROUP=$(id -g)
+exim -C ${CONF} -bd -DEXIMUSER=$EXIMUSER -DEXIMGROUP=$EXIMGROUP
 echo "Running exim on port 2525, accepting all local connections"
index a4d48ddc9678ca81e1aba2fe1aef90c5d3fce4d9..b5932c583b30ddc1815c1665a537ebb3f2de599d 100644 (file)
@@ -30,7 +30,7 @@ RCSID("$Id$")
 #include <freeradius-devel/server/global_lib.h>
 #include <freeradius-devel/server/module_rlm.h>
 #include <freeradius-devel/server/tmpl_dcursor.h>
-#include <freeradius-devel/util/talloc.h>
+#include <freeradius-devel/util/slab.h>
 
 static fr_dict_t const         *dict_radius; /*dictionary for radius protocol*/
 static fr_dict_t const         *dict_freeradius;
@@ -44,7 +44,6 @@ fr_dict_autoload_t rlm_smtp_dict[] = {
        { NULL }
 };
 
-static fr_dict_attr_t const    *attr_auth_type;
 static fr_dict_attr_t const    *attr_user_password;
 static fr_dict_attr_t const    *attr_user_name;
 static fr_dict_attr_t const    *attr_smtp_header;
@@ -52,7 +51,6 @@ static fr_dict_attr_t const   *attr_smtp_body;
 
 extern fr_dict_attr_autoload_t rlm_smtp_dict_attr[];
 fr_dict_attr_autoload_t rlm_smtp_dict_attr[] = {
-       { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
        { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius },
        { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius },
        { .out = &attr_smtp_header, .name = "SMTP-Mail-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
@@ -66,6 +64,26 @@ global_lib_autoinst_t const * const rlm_smtp_lib[] = {
        GLOBAL_LIB_TERMINATOR
 };
 
+/** Module environment for sending emails.
+*/
+typedef struct {
+       fr_value_box_t          username;               //!< User to authenticate as when sending emails.
+       tmpl_t                  *username_tmpl;         //!< The tmpl used to produce the above.
+       fr_value_box_t          password;               //!< Password for authenticated mails.
+} rlm_smtp_env_t;
+
+FR_DLIST_TYPES(header_list)
+
+/** Structure to hold definitions of SMTP headers
+ */
+typedef FR_DLIST_HEAD(header_list) header_list_t;
+typedef struct {
+       char const                      *name;          //!< SMTP header name
+       tmpl_t                          *value;         //!< Tmpl to expand to as header value
+       FR_DLIST_ENTRY(header_list)     entry;          //!< Entry in the list of headers
+} rlm_smtp_header_t;
+
+FR_DLIST_FUNCS(header_list, rlm_smtp_header_t, entry)
 typedef struct {
        char const              *uri;                   //!< URI of smtp server
        char const              *template_dir;          //!< The directory that contains all email attachments
@@ -81,14 +99,30 @@ typedef struct {
        fr_time_delta_t         timeout;                //!< Timeout for connection and server response
        fr_curl_tls_t           tls;                    //!< Used for handled all tls specific curl components
 
-       char const              *name;                  //!< Auth-Type value for this module instance.
-       fr_dict_enum_value_t    *auth_type;
-       map_list_t              header_maps;            //!< Attribute map used to process header elements
+       header_list_t           header_list;            //!< List of SMTP headers to add to emails.
        bool                    set_date;
+
+       fr_curl_conn_config_t   conn_config;            //!< Re-usable CURL handle config
 } rlm_smtp_t;
 
+/*
+ *     Two types of SMTP connections are used:
+ *      - persistent - where the connection can be left established as the same
+ *                     authentication is used for all mails sent.
+ *      - onetime    - where the connection is torn down after each use, since
+ *                     different authentication is needed each time.
+ *
+ *     Memory for the handles for each is stored in slabs.
+ */
+
+FR_SLAB_TYPES(smtp, fr_curl_io_request_t)
+FR_SLAB_FUNCS(smtp, fr_curl_io_request_t)
+
 typedef struct {
-       fr_curl_handle_t        *mhandle;       //!< Thread specific multi handle.  Serves as the dispatch and coralling structure for smtp requests
+       fr_curl_handle_t        *mhandle;       //!< Thread specific multi handle.  Serves as the dispatch and
+                                               ///< coralling structure for smtp requests
+       fr_smtp_slab_list_t     *slab_persist;  //!< Slab list for persistent connections.
+       fr_smtp_slab_list_t     *slab_onetime;  //!< Slab list for onetime use connections.
 } rlm_smtp_thread_t;
 
 /*
@@ -130,6 +164,7 @@ static int cf_table_parse_tmpl(TALLOC_CTX *ctx, void *out, UNUSED void *parent,
                                                .allow_foreign = true
                                        }
                                };
+       rules.attr.list_def = request_attr_request;
 
        if (!tmpl) {
                cf_log_err(cp, "Failed parsing attribute reference");
@@ -196,6 +231,7 @@ static const CONF_PARSER module_config[] = {
        { FR_CONF_OFFSET("timeout", FR_TYPE_TIME_DELTA, rlm_smtp_t, timeout) },
        { FR_CONF_OFFSET("set_date", FR_TYPE_BOOL, rlm_smtp_t, set_date), .dflt = "yes" },
        { FR_CONF_OFFSET("tls", FR_TYPE_SUBSECTION, rlm_smtp_t, tls), .subcs = (void const *) fr_curl_tls_config },//!<loading the tls values
+       { FR_CONF_OFFSET("connection", FR_TYPE_SUBSECTION, rlm_smtp_t, conn_config), .subcs = (void const *) fr_curl_conn_config },
        CONF_PARSER_TERMINATOR
 };
 
@@ -249,7 +285,7 @@ static int tmpl_attr_to_slist(fr_mail_ctx_t *uctx, struct curl_slist **out, tmpl
 /*
  *     Parse through an array of tmpl * elements and add them to an slist
  */
-static int tmpl_arr_to_slist(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, struct curl_slist **out, tmpl_t ** const tmpl)
+static int tmpl_arr_to_slist(fr_mail_ctx_t *uctx, struct curl_slist **out, tmpl_t ** const tmpl)
 {
        request_t       *request = uctx->request;
        int             count = 0;
@@ -262,7 +298,7 @@ static int tmpl_arr_to_slist(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, struct c
 
                } else {
                        /* If the element is just a normal string, add it's name to the slist*/
-                       if (tmpl_aexpand(t, &expanded_str, request, current, NULL, NULL) < 0) {
+                       if (tmpl_aexpand(request, &expanded_str, request, current, NULL, NULL) < 0) {
                                RDEBUG2("Could not expand the element %s", current->name);
                                break;
                        }
@@ -303,7 +339,7 @@ static ssize_t tmpl_attr_to_sbuff(fr_mail_ctx_t *uctx, fr_sbuff_t *out, tmpl_t c
 /*
  *     Adds every value in a dict_attr to a curl_slist as a comma separated list with a preposition
  */
-static int tmpl_arr_to_header(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, struct curl_slist **out, tmpl_t ** const tmpl,
+static int tmpl_arr_to_header(fr_mail_ctx_t *uctx, struct curl_slist **out, tmpl_t ** const tmpl,
                              const char *preposition)
 {
        request_t               *request = uctx->request;
@@ -330,7 +366,7 @@ static int tmpl_arr_to_header(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, struct
 
                } else {
                        /* If the element is just a normal string, add it's name to the slist*/
-                       if( tmpl_aexpand(t, &expanded_str, request, vpt, NULL, NULL) < 0) {
+                       if( tmpl_aexpand(request, &expanded_str, request, vpt, NULL, NULL) < 0) {
                                RDEBUG2("Could not expand the element %s", vpt->name);
                                break;
                        }
@@ -420,7 +456,7 @@ static int tmpl_attr_to_attachment(fr_mail_ctx_t *uctx, curl_mime *mime, const t
 /*
  *     Adds every element in a tmpl** to an attachment path, then adds it to the email
  */
-static int tmpl_arr_to_attachments (rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, curl_mime *mime, tmpl_t ** const tmpl,
+static int tmpl_arr_to_attachments (fr_mail_ctx_t *uctx, curl_mime *mime, tmpl_t ** const tmpl,
                                    fr_sbuff_t *path_buffer, fr_sbuff_marker_t *m)
 {
        request_t       *request = uctx->request;
@@ -434,7 +470,7 @@ static int tmpl_arr_to_attachments (rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, c
                        count += tmpl_attr_to_attachment(uctx, mime, current, path_buffer, m);
 
                } else {
-                       expanded_str_len = tmpl_aexpand(t, &expanded_str, request, current, NULL, NULL);
+                       expanded_str_len = tmpl_aexpand(request, &expanded_str, request, current, NULL, NULL);
                        if (expanded_str_len < 0) {
                                RDEBUG2("Could not expand the element %s", current->name);
                                continue;
@@ -465,7 +501,7 @@ static const char *get_envelope_address(rlm_smtp_t const *inst)
 /*
  *     Generate the FROM: header
  */
-static int generate_from_header(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, struct curl_slist **out, rlm_smtp_t const *inst)
+static int generate_from_header(fr_mail_ctx_t *uctx, struct curl_slist **out, rlm_smtp_t const *inst)
 {
        char const                      *from = "FROM: ";
        fr_sbuff_t                      sbuff;
@@ -473,7 +509,7 @@ static int generate_from_header(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, struc
 
        /* If sender_address is set, then generate FROM: with those attributes */
        if (inst->sender_address) {
-               tmpl_arr_to_header(t, uctx, &uctx->header, inst->sender_address, from);
+               tmpl_arr_to_header(uctx, &uctx->header, inst->sender_address, from);
                return 0;
        }
 
@@ -497,7 +533,7 @@ static int generate_from_header(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, struc
 /*
  *     Generates a curl_slist of recipients
  */
-static int recipients_source(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, rlm_smtp_t const *inst)
+static int recipients_source(fr_mail_ctx_t *uctx, rlm_smtp_t const *inst)
 {
        request_t               *request = uctx->request;
        int                     recipients_set = 0;
@@ -505,7 +541,7 @@ static int recipients_source(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, rlm_smtp
        /*
          *     Try to load the recipients into the envelope recipients if they are set
          */
-       if(inst->recipient_addrs) recipients_set += tmpl_arr_to_slist(t, uctx, &uctx->recipients, inst->recipient_addrs);
+       if(inst->recipient_addrs) recipients_set += tmpl_arr_to_slist(uctx, &uctx->recipients, inst->recipient_addrs);
 
        /*
         *      If any recipients were found, ignore to cc and bcc, return the amount added.
@@ -519,17 +555,17 @@ static int recipients_source(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, rlm_smtp
        /*
         *      Try to load the to: addresses into the envelope recipients if they are set
         */
-       if (inst->to_addrs) recipients_set += tmpl_arr_to_slist(t, uctx, &uctx->recipients, inst->to_addrs);
+       if (inst->to_addrs) recipients_set += tmpl_arr_to_slist(uctx, &uctx->recipients, inst->to_addrs);
 
        /*
         *      Try to load the cc: addresses into the envelope recipients if they are set
         */
-       if (inst->cc_addrs) recipients_set += tmpl_arr_to_slist(t, uctx, &uctx->recipients, inst->cc_addrs);
+       if (inst->cc_addrs) recipients_set += tmpl_arr_to_slist(uctx, &uctx->recipients, inst->cc_addrs);
 
        /*
         *      Try to load the cc: addresses into the envelope recipients if they are set
         */
-       if (inst->bcc_addrs) recipients_set += tmpl_arr_to_slist(t, uctx, &uctx->recipients, inst->bcc_addrs);
+       if (inst->bcc_addrs) recipients_set += tmpl_arr_to_slist(uctx, &uctx->recipients, inst->bcc_addrs);
 
        RDEBUG2("%d recipients set", recipients_set);
        return recipients_set;
@@ -538,7 +574,7 @@ static int recipients_source(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, rlm_smtp
 /*
  *     Generates a curl_slist of header elements header elements
  */
-static int header_source(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, rlm_smtp_t const *inst)
+static int header_source(fr_mail_ctx_t *uctx, rlm_smtp_t const *inst)
 {
        fr_sbuff_t                      time_out;
        char const                      *to = "TO: ";
@@ -546,53 +582,40 @@ static int header_source(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, rlm_smtp_t c
        request_t                       *request = uctx->request;
        fr_sbuff_t                      conf_buffer;
        fr_sbuff_uctx_talloc_t          conf_ctx;
-       map_t                           *conf_map;
-
+       rlm_smtp_header_t const         *header = NULL;
        char                            *expanded_rhs;
 
-       /*
-        *      Initialize the sbuff for writing the config elements as header attributes
-        */
-       fr_sbuff_init_talloc(uctx, &conf_buffer, &conf_ctx, 256, SIZE_MAX);
-       conf_map = map_list_head(&inst->header_maps);
-
        /*
         *      Load in all of the header elements supplied in the config
         */
-       while (conf_map->rhs && conf_map->lhs) {
+       while ((header = header_list_next(&inst->header_list, header))) {
                /* Do any string expansion required in the rhs */
-               if( tmpl_aexpand(t, &expanded_rhs, request, conf_map->rhs, NULL, NULL) < 0) {
-                       RDEBUG2("Skipping: %s's could not parse: %s", conf_map->lhs->name, conf_map->rhs->name);
-                       goto next;
+               if( tmpl_aexpand(request, &expanded_rhs, request, header->value, NULL, NULL) < 0) {
+                       RDEBUG2("Skipping: %s's could not parse: %s", header->name, header->value->name);
+                       continue;
                }
 
+               fr_sbuff_init_talloc(uctx, &conf_buffer, &conf_ctx, 256, SIZE_MAX);
+
                /* Format the conf item to be a valid SMTP header */
                /* coverity[check_return] */
-               fr_sbuff_in_bstrncpy(&conf_buffer, conf_map->lhs->name, conf_map->lhs->len);
+               fr_sbuff_in_bstrncpy(&conf_buffer, header->name, strlen(header->name));
                fr_sbuff_in_strcpy(&conf_buffer, ": ");
                fr_sbuff_in_bstrncpy(&conf_buffer, expanded_rhs, strlen(expanded_rhs));
 
                /* Add the header to the curl slist */
-               uctx->header = curl_slist_append(uctx->header, conf_buffer.buff);
+               uctx->header = curl_slist_append(uctx->header, fr_sbuff_buff(&conf_buffer));
                talloc_free(conf_buffer.buff);
-
-       next:
-               /* Check if there are more values to parse */
-
-               if (!map_list_next(&inst->header_maps, conf_map)) break;
-               /* reinitialize the buffer and move to the next value */
-               fr_sbuff_init_talloc(uctx, &conf_buffer, &conf_ctx, 256, SIZE_MAX);
-               conf_map = map_list_next(&inst->header_maps, conf_map);
        }
 
        /* Add the FROM: line */
-       generate_from_header(t, uctx, &uctx->header, inst);
+       generate_from_header(uctx, &uctx->header, inst);
 
        /* Add the TO: line if there is one provided in the request by SMTP-TO */
-       tmpl_arr_to_header(t, uctx, &uctx->header, inst->to_addrs, to);
+       tmpl_arr_to_header(uctx, &uctx->header, inst->to_addrs, to);
 
        /* Add the CC: line if there is one provided in the request by SMTP-CC */
-       tmpl_arr_to_header(t, uctx, &uctx->header, inst->cc_addrs, cc);
+       tmpl_arr_to_header(uctx, &uctx->header, inst->cc_addrs, cc);
 
        /* Add all the generic header elements in the request */
        da_to_slist(uctx, &uctx->header, attr_smtp_header);
@@ -661,7 +684,7 @@ static size_t body_source(char *ptr, size_t size, size_t nmemb, void *mail_ctx)
 static int body_init(fr_mail_ctx_t *uctx, curl_mime *mime)
 {
        fr_pair_t       *vp;
-       request_t               *request = uctx->request;
+       request_t       *request = uctx->request;
 
        curl_mimepart   *part;
        curl_mime       *mime_body;
@@ -705,7 +728,7 @@ static int body_init(fr_mail_ctx_t *uctx, curl_mime *mime)
 /*
  *     Adds every SMTP_Attachments file to the email as a MIME part
  */
-static int attachments_source(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, curl_mime *mime, rlm_smtp_t const *inst)
+static int attachments_source(fr_mail_ctx_t *uctx, curl_mime *mime, rlm_smtp_t const *inst)
 {
        request_t               *request = uctx->request;
        int                     attachments_set = 0;
@@ -732,24 +755,13 @@ static int attachments_source(rlm_smtp_thread_t *t, fr_mail_ctx_t *uctx, curl_mi
        fr_sbuff_marker(&m, &path_buffer);
 
        /* Add the attachments to the email */
-       attachments_set += tmpl_arr_to_attachments(t, uctx, mime, inst->attachments, &path_buffer, &m);
+       attachments_set += tmpl_arr_to_attachments(uctx, mime, inst->attachments, &path_buffer, &m);
 
        /* Check for any file attachments */
        talloc_free(path_buffer.buff);
        return attachments_set;
 }
 
-/*
- *     Free the curl slists
- */
-static int _free_mail_ctx(fr_mail_ctx_t *uctx)
-{
-       curl_mime_free(uctx->mime);
-       curl_slist_free_all(uctx->header);
-       curl_slist_free_all(uctx->recipients);
-       return 0;
-}
-
 static void smtp_io_module_signal(module_ctx_t const *mctx, request_t *request, fr_state_signal_t action)
 {
        fr_curl_io_request_t    *randle = talloc_get_type_abort(mctx->rctx, fr_curl_io_request_t);
@@ -766,21 +778,26 @@ static void smtp_io_module_signal(module_ctx_t const *mctx, request_t *request,
                /* Not much we can do */
        }
        t->mhandle->transfers--;
-       talloc_free(randle);
+       fr_smtp_slab_release(randle);
 }
 
-/*
- *     Check if the email was successfully sent, and if the certificate information was extracted
+/**    Callback to process response of SMTP server
+ *
+ *     It checks if the response was CURLE_OK
+ *     If it was, it tries to extract the certificate attributes
+ *     If the response was not OK, we REJECT the request
+ *     When responding to requests initiated by mod_authenticate this is simply
+ *     a check on the username and password.
+ *     When responding to requests initiated by mod_mail this indicates
+ *     the mail has been queued.
  */
-static unlang_action_t mod_authorize_result(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+static unlang_action_t smtp_io_module_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
-       fr_mail_ctx_t                   *mail_ctx = talloc_get_type_abort(mctx->rctx, fr_mail_ctx_t);
        rlm_smtp_t const                *inst = talloc_get_type_abort_const(mctx->inst->data, rlm_smtp_t);
-       fr_curl_io_request_t            *randle = mail_ctx->randle;
-       fr_curl_tls_t const             *tls;
+       fr_curl_io_request_t            *randle = talloc_get_type_abort(mctx->rctx, fr_curl_io_request_t);
+       fr_curl_tls_t const             *tls = &inst->tls;
        long                            curl_out;
        long                            curl_out_valid;
-       tls = &inst->tls;
 
        curl_out_valid = curl_easy_getinfo(randle->candle, CURLINFO_SSL_VERIFYRESULT, &curl_out);
        if (curl_out_valid == CURLE_OK){
@@ -790,12 +807,19 @@ static unlang_action_t mod_authorize_result(rlm_rcode_t *p_result, module_ctx_t
        }
 
        if (randle->result != CURLE_OK) {
-               talloc_free(randle);
-               RETURN_MODULE_REJECT;
+               CURLcode result = randle->result;
+               fr_smtp_slab_release(randle);
+               switch (result) {
+               case CURLE_PEER_FAILED_VERIFICATION:
+               case CURLE_LOGIN_DENIED:
+                       RETURN_MODULE_REJECT;
+               default:
+                       RETURN_MODULE_FAIL;
+               }
        }
 
        if (tls->extract_cert_attrs) fr_curl_response_certinfo(request, randle);
-       talloc_free(randle);
+       fr_smtp_slab_release(randle);
 
        RETURN_MODULE_OK;
 }
@@ -812,26 +836,20 @@ static unlang_action_t mod_authorize_result(rlm_rcode_t *p_result, module_ctx_t
  *             File attachments
  *
  *     Then it queues the request and yeilds until a response is given
- *     When it responds, mod_authorize_resume is called.
+ *     When it responds, smtp_io_module_resume is called.
  */
-static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+static unlang_action_t CC_HINT(nonnull) mod_mail(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
        rlm_smtp_t const                *inst = talloc_get_type_abort_const(mctx->inst->data, rlm_smtp_t);
        rlm_smtp_thread_t               *t = talloc_get_type_abort(mctx->thread, rlm_smtp_thread_t);
-       fr_curl_io_request_t            *randle;
+       rlm_smtp_env_t                  *mod_env = talloc_get_type_abort(mctx->env_data, rlm_smtp_env_t);
+       fr_curl_io_request_t            *randle = NULL;
        fr_mail_ctx_t                   *mail_ctx;
        const char                      *envelope_address;
 
-       fr_pair_t const                 *smtp_body, *username, *password;
-
-       if (fr_pair_find_by_da(&request->control_pairs, NULL, attr_auth_type) != NULL) {
-               RDEBUG3("Auth-Type is already set.  Not setting 'Auth-Type := %s'", inst->name);
-               RETURN_MODULE_NOOP;
-       }
+       fr_pair_t const                 *smtp_body;
 
        /* Elements provided by the request */
-       username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
-       password = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_password);
        smtp_body = fr_pair_find_by_da(&request->request_pairs, NULL, attr_smtp_body);
 
        /* Make sure all of the essential email components are present and possible*/
@@ -843,71 +861,65 @@ static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, mod
        if (!inst->sender_address && !inst->envelope_address) {
                RDEBUG2("At least one of \"sender_address\" or \"envelope_address\" in the config, or \"SMTP-Sender-Address\" in the request is needed");
        error:
+               if (randle) fr_smtp_slab_release(randle);
                RETURN_MODULE_INVALID;
        }
 
-       /* allocate the handle and set the curl options */
-       randle = fr_curl_io_request_alloc(request);
+       /*
+        *      If the username is defined and is not static data
+        *      a onetime connection is used, otherwise a persistent one
+        *      can be used.
+        */
+       randle = (mod_env->username_tmpl &&
+                 !tmpl_is_data(mod_env->username_tmpl)) ? fr_smtp_slab_reserve(t->slab_onetime) :
+                                                          fr_smtp_slab_reserve(t->slab_persist);
        if (!randle) {
                RDEBUG2("A handle could not be allocated for the request");
                RETURN_MODULE_FAIL;
        }
 
        /* Initialize the uctx to perform the email */
-       mail_ctx = talloc_zero(randle, fr_mail_ctx_t);
+       mail_ctx = talloc_get_type_abort(randle->uctx, fr_mail_ctx_t);
        *mail_ctx = (fr_mail_ctx_t) {
                .request        = request,
                .randle         = randle,
                .mime           = curl_mime_init(randle->candle),
-               .time           = fr_time() /* time the request was received. Used to set DATE: */
+               .time           = fr_time(), /* time the request was received. Used to set DATE: */
+               .recipients     = NULL,
+               .header         = NULL
        };
 
-       /* Set the destructor function to free all of the curl_slist elements */
-       talloc_set_destructor(mail_ctx, _free_mail_ctx);
-
-#if CURL_AT_LEAST_VERSION(7,45,0)
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_DEFAULT_PROTOCOL, "smtp");
-#endif
-       /* Set the generic curl request conditions */
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_URL, inst->uri);
-#if CURL_AT_LEAST_VERSION(7,85,0)
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS_STR, "smtp,smtps");
-#else
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS, CURLPROTO_SMTP | CURLPROTO_SMTPS);
-#endif
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, fr_time_delta_to_msec(inst->timeout));
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_TIMEOUT_MS, fr_time_delta_to_msec(inst->timeout));
        FR_CURL_REQUEST_SET_OPTION(CURLOPT_UPLOAD, 1L);
 
-       if(RDEBUG_ENABLED3) FR_CURL_REQUEST_SET_OPTION(CURLOPT_VERBOSE, 1L);
+       /* Set the username and password if they have been provided */
+       if (mod_env->username.vb_strvalue) {
+               FR_CURL_REQUEST_SET_OPTION(CURLOPT_USERNAME, mod_env->username.vb_strvalue);
+
+               if (!mod_env->password.vb_strvalue) goto skip_auth;
 
-       /* Set the username and pasword if they have been provided */
-       if (username && username->vp_length != 0 && password) {
-               FR_CURL_REQUEST_SET_OPTION(CURLOPT_USERNAME, username->vp_strvalue);
-               FR_CURL_REQUEST_SET_OPTION(CURLOPT_PASSWORD, password->vp_strvalue);
+               FR_CURL_REQUEST_SET_OPTION(CURLOPT_PASSWORD, mod_env->password.vb_strvalue);
                RDEBUG2("Username and password set");
        }
+skip_auth:
 
        /* Send the envelope address */
        envelope_address = get_envelope_address(inst);
        if (!envelope_address) {
-               RDEBUG2("The envelope address must be set");
+               REDEBUG("The envelope address must be set");
                goto error;
        }
        FR_CURL_REQUEST_SET_OPTION(CURLOPT_MAIL_FROM, get_envelope_address(inst));
 
        /* Set the recipients */
-       mail_ctx->recipients = NULL; /* Prepare the recipients curl_slist to be initialized */
-               if (recipients_source(t, mail_ctx, inst) <= 0) {
-               RDEBUG2("At least one recipient is required to send an email");
+               if (recipients_source(mail_ctx, inst) <= 0) {
+               REDEBUG("At least one recipient is required to send an email");
                goto error;
        }
        FR_CURL_REQUEST_SET_OPTION(CURLOPT_MAIL_RCPT, mail_ctx->recipients);
 
        /* Set the header elements */
-       mail_ctx->header = NULL; /* Prepare the header curl_slist to be initialized */
-               if (header_source(t, mail_ctx, inst) != 0) {
-               RDEBUG2("The header slist could not be generated");
+               if (header_source(mail_ctx, inst) != 0) {
+               REDEBUG("The header slist could not be generated");
                goto error;
        }
 
@@ -920,59 +932,21 @@ static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, mod
 
        /* Initialize the body elements to be uploaded */
        if (body_init(mail_ctx, mail_ctx->mime) == 0) {
-               RDEBUG2("The body could not be generated");
+               REDEBUG("The body could not be generated");
                goto error;
        }
 
        /* Initialize the attachments if there are any*/
-       if (attachments_source(t, mail_ctx, mail_ctx->mime, inst) == 0){
+       if (attachments_source(mail_ctx, mail_ctx->mime, inst) == 0){
                RDEBUG2("No files were attached to the email");
        }
 
        /* Add the mime endoced elements to the curl request */
        FR_CURL_REQUEST_SET_OPTION(CURLOPT_MIMEPOST, mail_ctx->mime);
 
-       /* Initialize tls if it has been set up */
-       if (fr_curl_easy_tls_init(randle, &inst->tls) != 0) RETURN_MODULE_INVALID;
-
        if (fr_curl_io_request_enqueue(t->mhandle, request, randle)) RETURN_MODULE_INVALID;
 
-       return unlang_module_yield(request, mod_authorize_result, smtp_io_module_signal, mail_ctx);
-}
-
-/*
- *     Called when the smtp server responds
- *     It checks if the response was CURLE_OK
- *     If it was, it tries to extract the certificate attributes
- *     If the response was not OK, we REJECT the request
- *     This does not confirm an email may be sent, only that the provided login credentials are valid for the server
- */
-static unlang_action_t CC_HINT(nonnull) mod_authenticate_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
-{
-       rlm_smtp_t const                *inst = talloc_get_type_abort_const(mctx->inst->data, rlm_smtp_t);
-       fr_curl_io_request_t            *randle = talloc_get_type_abort(mctx->rctx, fr_curl_io_request_t);
-       fr_curl_tls_t const             *tls;
-       long                            curl_out;
-       long                            curl_out_valid;
-
-       tls = &inst->tls;
-
-       curl_out_valid = curl_easy_getinfo(randle->candle, CURLINFO_SSL_VERIFYRESULT, &curl_out);
-       if (curl_out_valid == CURLE_OK){
-               RDEBUG2("server certificate %s verified", curl_out ? "was" : "not");
-       } else {
-               RDEBUG2("server certificate result not found");
-       }
-
-       if (randle->result != CURLE_OK) {
-               talloc_free(randle);
-               RETURN_MODULE_REJECT;
-       }
-
-       if (tls->extract_cert_attrs) fr_curl_response_certinfo(request, randle);
-
-       talloc_free(randle);
-       RETURN_MODULE_OK;
+       return unlang_module_yield(request, smtp_io_module_resume, smtp_io_module_signal, randle);
 }
 
 /*
@@ -983,21 +957,17 @@ static unlang_action_t CC_HINT(nonnull) mod_authenticate_resume(rlm_rcode_t *p_r
  *             timeout information
  *             and TLS information
  *
- *     Then it queues the request and yeilds until a response is given
- *     When it responds, mod_authenticate_resume is called.
+ *     Then it queues the request and yields until a response is given
+ *     When it responds, smtp_io_module_resume is called.
  */
 static unlang_action_t CC_HINT(nonnull(1,2)) mod_authenticate(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
-       rlm_smtp_t const        *inst = talloc_get_type_abort_const(mctx->inst->data, rlm_smtp_t);
        rlm_smtp_thread_t       *t = talloc_get_type_abort(mctx->thread, rlm_smtp_thread_t);
        fr_pair_t const         *username, *password;
        fr_curl_io_request_t    *randle;
 
-       randle = fr_curl_io_request_alloc(request);
-       if (!randle) {
-       error:
-               RETURN_MODULE_FAIL;
-       }
+       randle = fr_smtp_slab_reserve(t->slab_onetime);
+       if (!randle) RETURN_MODULE_FAIL;
 
        username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
        password = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_password);
@@ -1005,93 +975,250 @@ static unlang_action_t CC_HINT(nonnull(1,2)) mod_authenticate(rlm_rcode_t *p_res
        /* Make sure we have a user-name and user-password, and that they are possible */
        if (!username) {
                REDEBUG("Attribute \"User-Name\" is required for authentication");
+       error:
+               fr_smtp_slab_release(randle);
                RETURN_MODULE_INVALID;
        }
        if (username->vp_length == 0) {
                RDEBUG2("\"User-Password\" must not be empty");
-               RETURN_MODULE_INVALID;
+               goto error;
        }
        if (!password) {
                RDEBUG2("Attribute \"User-Password\" is required for authentication");
-               RETURN_MODULE_INVALID;
+               goto error;
        }
 
-#if CURL_AT_LEAST_VERSION(7,45,0)
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_DEFAULT_PROTOCOL, "smtp");
-#endif
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_URL, inst->uri);
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS, CURLPROTO_SMTP | CURLPROTO_SMTPS);
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, fr_time_delta_to_msec(inst->timeout));
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_TIMEOUT_MS, fr_time_delta_to_msec(inst->timeout));
+       FR_CURL_REQUEST_SET_OPTION(CURLOPT_USERNAME, username->vp_strvalue);
+       FR_CURL_REQUEST_SET_OPTION(CURLOPT_PASSWORD, password->vp_strvalue);
 
-       FR_CURL_REQUEST_SET_OPTION(CURLOPT_VERBOSE, 1L);
+       if (fr_curl_io_request_enqueue(t->mhandle, request, randle)) RETURN_MODULE_INVALID;
 
-       if (fr_curl_easy_tls_init(randle, &inst->tls) != 0) RETURN_MODULE_INVALID;
+       return unlang_module_yield(request, smtp_io_module_resume, smtp_io_module_signal, randle);
+}
 
-       if (fr_curl_io_request_enqueue(t->mhandle, request, randle)) RETURN_MODULE_INVALID;
+static int mod_bootstrap(module_inst_ctx_t const *mctx)
+{
+       rlm_smtp_t      *inst = talloc_get_type_abort(mctx->inst->data, rlm_smtp_t );
+
+       talloc_foreach(inst->recipient_addrs, vpt) INFO("NAME: %s", vpt->name);
 
-       return unlang_module_yield(request, mod_authenticate_resume, smtp_io_module_signal, randle);
+       return 0;
 }
 
-/** Verify that a map in the header section makes sense
- *
- */
-static int smtp_verify(map_t *map, void *ctx)
+
+static int mod_instantiate(module_inst_ctx_t const *mctx)
 {
-       if (unlang_fixup_update(map, ctx) < 0) return -1;
+       rlm_smtp_t                      *inst = talloc_get_type_abort(mctx->inst->data, rlm_smtp_t);
+       CONF_SECTION                    *conf = mctx->inst->conf;
+       CONF_SECTION                    *cs;
+       CONF_ITEM                       *ci;
+       CONF_PAIR                       *cp;
+       tmpl_rules_t                    parse_rules;
+       rlm_smtp_header_t               *header;
+       char const                      *value;
+       char                            *unescaped_value = NULL;
+       fr_token_t                      type;
+       ssize_t                         slen;
+       fr_sbuff_parse_rules_t const    *p_rules;
+
+       header_list_init(&inst->header_list);
+       cs = cf_section_find(conf, "header", NULL);
+       if (!cs) return 0;
+
+       parse_rules = (tmpl_rules_t) {
+               .attr = {
+                       .allow_foreign = true,  /* Because we don't know where we'll be called */
+                       .allow_unknown = true,
+                       .allow_unresolved = true,
+                       .prefix = TMPL_ATTR_REF_PREFIX_AUTO,
+                       .list_def = request_attr_request
+               }
+       };
+
+       for (ci = cf_item_next(cs, NULL); ci != NULL; ci = cf_item_next(cs,ci)) {
+               if (!cf_item_is_pair(ci)) {
+                       cf_log_err(ci, "Entry is not in \"header = value\" format");
+               error:
+                       TALLOC_FREE(unescaped_value);
+                       header_list_talloc_free(&inst->header_list);
+                       return -1;
+               }
+
+               cp = cf_item_to_pair(ci);
+               fr_assert(cp != NULL);
+
+               MEM(header = talloc_zero(inst, rlm_smtp_header_t));
+
+               header->name = talloc_strdup(header, cf_pair_attr(cp));
+               value = cf_pair_value(cp);
+               type = cf_pair_value_quote(cp);
+               p_rules = value_parse_rules_unquoted[type];
+
+               if (type == T_DOUBLE_QUOTED_STRING || type == T_BACK_QUOTED_STRING) {
+                       slen = fr_sbuff_out_aunescape_until(NULL, &unescaped_value,
+                               &FR_SBUFF_IN(value, talloc_array_length(value) - 1), SIZE_MAX, p_rules->terminals, p_rules->escapes);
+                       if (slen < 0) {
+                               char *spaces, *text;
+                       parse_error:
+                               cf_log_err(ci, "Failed to parse value %s", value);
+                               fr_canonicalize_error(inst, &spaces, &text, slen, value);
+                               cf_log_err(cp, "%s", text);
+                               cf_log_perr(cp, "%s^", spaces);
+
+                               talloc_free(spaces);
+                               talloc_free(text);
+                               goto error;
+                       }
+                       value = unescaped_value;
+                       p_rules = NULL;
+               } else {
+                       slen = talloc_array_length(value) - 1;
+               }
+
+               slen = tmpl_afrom_substr(header, &header->value, &FR_SBUFF_IN(value, slen), type, p_rules, &parse_rules);
+               if (slen < 0) goto parse_error;
+
+               header_list_insert_tail(&inst->header_list, header);
+               TALLOC_FREE(unescaped_value);
+       }
+
+       inst->conn_config.reuse.num_children = 1;
+       inst->conn_config.reuse.child_pool_size = sizeof(fr_mail_ctx_t);
 
        return 0;
 }
 
-static int mod_bootstrap(module_inst_ctx_t const *mctx)
+#define SMTP_COMMON_CLEANUP \
+       fr_mail_ctx_t   *mail_ctx = talloc_get_type_abort(randle->uctx, fr_mail_ctx_t); \
+       if (mail_ctx->mime) curl_mime_free(mail_ctx->mime); \
+       if (mail_ctx->header) curl_slist_free_all(mail_ctx->header); \
+       if (mail_ctx->recipients) curl_slist_free_all(mail_ctx->recipients)
+
+static int smtp_onetime_request_cleanup(fr_curl_io_request_t *randle, UNUSED void *uctx)
 {
-       rlm_smtp_t      *inst = talloc_get_type_abort(mctx->inst->data, rlm_smtp_t );
+       SMTP_COMMON_CLEANUP;
 
-       talloc_foreach(inst->recipient_addrs, vpt) INFO("NAME: %s", vpt->name);
+       if (randle->candle) curl_easy_cleanup(randle->candle);
 
        return 0;
 }
 
+static int smtp_persist_request_cleanup(fr_curl_io_request_t *randle, UNUSED void *uctx)
+{
+       SMTP_COMMON_CLEANUP;
 
-static int mod_instantiate(module_inst_ctx_t const *mctx)
+       if (randle->candle) curl_easy_reset(randle->candle);
+
+       return 0;
+}
+
+static int smtp_onetime_conn_alloc(fr_curl_io_request_t *randle, UNUSED void *uctx)
 {
-       rlm_smtp_t      *inst = talloc_get_type_abort(mctx->inst->data, rlm_smtp_t);
-       CONF_SECTION    *conf = mctx->inst->conf;
-       CONF_SECTION    *header;
+       fr_mail_ctx_t           *mail_ctx = NULL;
 
-       map_list_init(&inst->header_maps);
-       header = cf_section_find(conf, "header", NULL);
-       if (!header) return 0;
+       MEM(mail_ctx = talloc_zero(randle, fr_mail_ctx_t));
+       randle->uctx = mail_ctx;
 
-       /*
-        *      Make sure the users don't screw up too badly.
-        */
-       {
-               tmpl_rules_t    parse_rules = {
-                       .attr = {
-                               .allow_foreign = true,  /* Because we don't know where we'll be called */
-                               .allow_unknown = true,
-                               .allow_unresolved = true,
-                               .prefix = TMPL_ATTR_REF_PREFIX_AUTO,
-                       }
-               };
+       fr_smtp_slab_element_set_destructor(randle, smtp_onetime_request_cleanup, NULL);
 
-               if (map_afrom_cs(inst, &inst->header_maps, header,
-                                &parse_rules, &parse_rules, smtp_verify, NULL, MAX_ATTRMAP) < 0) {
-                       return -1;
-               }
+       return 0;
+}
+
+static int smtp_mail_ctx_free(fr_mail_ctx_t *mail_ctx)
+{
+       if (mail_ctx->randle && mail_ctx->randle->candle) curl_easy_cleanup(mail_ctx->randle->candle);
+
+       return 0;
+}
+
+static int smtp_persist_conn_alloc(fr_curl_io_request_t *randle, UNUSED void *uctx)
+{
+       fr_mail_ctx_t           *mail_ctx = NULL;
+
+       MEM(mail_ctx = talloc_zero(randle, fr_mail_ctx_t));
+       mail_ctx->randle = randle;
+       randle->uctx = mail_ctx;
+       randle->candle = curl_easy_init();
+       if (unlikely(!randle->candle)) {
+               fr_strerror_printf("Unable to initialise CURL handle");
+               return -1;
        }
+       talloc_set_destructor(mail_ctx, smtp_mail_ctx_free);
+
+       fr_smtp_slab_element_set_destructor(randle, smtp_persist_request_cleanup, NULL);
 
        return 0;
 }
 
+static inline int smtp_conn_common_init(fr_curl_io_request_t *randle, rlm_smtp_t const *inst)
+{
+#if CURL_AT_LEAST_VERSION(7,45,0)
+       FR_CURL_SET_OPTION(CURLOPT_DEFAULT_PROTOCOL, "smtp");
+#endif
+       FR_CURL_SET_OPTION(CURLOPT_URL, inst->uri);
+#if CURL_AT_LEAST_VERSION(7,85,0)
+       FR_CURL_SET_OPTION(CURLOPT_PROTOCOLS_STR, "smtp,smtps");
+#else
+       FR_CURL_SET_OPTION(CURLOPT_PROTOCOLS, CURLPROTO_SMTP | CURLPROTO_SMTPS);
+#endif
+       FR_CURL_SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, fr_time_delta_to_msec(inst->timeout));
+       FR_CURL_SET_OPTION(CURLOPT_TIMEOUT_MS, fr_time_delta_to_msec(inst->timeout));
+
+       if (DEBUG_ENABLED3) FR_CURL_SET_OPTION(CURLOPT_VERBOSE, 1L);
+
+       if (fr_curl_easy_tls_init(randle, &inst->tls) != 0) goto error;
+
+       return 0;
+error:
+       return -1;
+}
+
+static int smtp_onetime_conn_init(fr_curl_io_request_t *randle, void *uctx)
+{
+       rlm_smtp_t const        *inst = talloc_get_type_abort(uctx, rlm_smtp_t);
+       fr_mail_ctx_t           *mail_ctx = talloc_get_type_abort(randle->uctx, fr_mail_ctx_t);
+
+       randle->candle = curl_easy_init();
+       if (unlikely(!randle->candle)) {
+               fr_strerror_printf("Unable to initialise CURL handle");
+               return -1;
+       }
+
+       memset(mail_ctx, 0, sizeof(fr_mail_ctx_t));
+
+       return smtp_conn_common_init(randle, inst);
+}
+
+
+static int smtp_persist_conn_init(fr_curl_io_request_t *randle, void *uctx)
+{
+       rlm_smtp_t const        *inst = talloc_get_type_abort(uctx, rlm_smtp_t);
+       fr_mail_ctx_t           *mail_ctx = talloc_get_type_abort(randle->uctx, fr_mail_ctx_t);
+
+       memset(mail_ctx, 0, sizeof(fr_mail_ctx_t));
+
+       return smtp_conn_common_init(randle, inst);
+}
+
 /*
  *     Initialize a new thread with a curl instance
  */
 static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
 {
-       rlm_smtp_thread_t               *t = talloc_get_type_abort(mctx->thread, rlm_smtp_thread_t);
-       fr_curl_handle_t                *mhandle;
+       rlm_smtp_t              *inst = talloc_get_type_abort(mctx->inst->data, rlm_smtp_t);
+       rlm_smtp_thread_t       *t = talloc_get_type_abort(mctx->thread, rlm_smtp_thread_t);
+       fr_curl_handle_t        *mhandle;
+
+       if (fr_smtp_slab_list_alloc(t, &t->slab_onetime, mctx->el, &inst->conn_config.reuse,
+                                   smtp_onetime_conn_alloc, smtp_onetime_conn_init, inst, false, false) < 0) {
+               ERROR("Connection handle pool instantiation failed");
+               return -1;
+       }
+       if (fr_smtp_slab_list_alloc(t, &t->slab_persist, mctx->el, &inst->conn_config.reuse,
+                                   smtp_persist_conn_alloc, smtp_persist_conn_init, inst, false, true) < 0) {
+               ERROR("Connection handle pool instantiation failed");
+               return -1;
+       }
 
        mhandle = fr_curl_io_init(t, mctx->el, false);
        if (!mhandle) return -1;
@@ -1108,9 +1235,25 @@ static int mod_thread_detach(module_thread_inst_ctx_t const *mctx)
        rlm_smtp_thread_t               *t = talloc_get_type_abort(mctx->thread, rlm_smtp_thread_t);
 
        talloc_free(t->mhandle);
+       talloc_free(t->slab_persist);
+       talloc_free(t->slab_onetime);
        return 0;
 }
 
+static const module_env_t module_env[] = {
+       { FR_MODULE_ENV_TMPL_OFFSET("username", FR_TYPE_STRING, rlm_smtp_env_t, username, username_tmpl, NULL,
+                               T_DOUBLE_QUOTED_STRING, false, true, true) },
+       { FR_MODULE_ENV_OFFSET("password", FR_TYPE_STRING, rlm_smtp_env_t, password, NULL,
+                               T_DOUBLE_QUOTED_STRING, false, true, true) },
+       MODULE_ENV_TERMINATOR
+};
+
+static const module_method_env_t method_env = {
+       .inst_size = sizeof(rlm_smtp_env_t),
+       .inst_type = "rlm_smtp_env_t",
+       .env = module_env
+};
+
 /*
  *     The module name should be the only globally exported symbol.
  *     That is, everything else should be 'static'.
@@ -1135,7 +1278,8 @@ module_rlm_t rlm_smtp = {
                .thread_detach          = mod_thread_detach,
        },
        .method_names = (module_method_name_t[]){
-               { .name1 = "recv",              .name2 = CF_IDENT_ANY,          .method = mod_authorize },
+               { .name1 = "mail",              .name2 = CF_IDENT_ANY,          .method = mod_mail,
+                 .method_env = &method_env },
                { .name1 = "authenticate",      .name2 = CF_IDENT_ANY,          .method = mod_authenticate },
                MODULE_NAME_TERMINATOR
        }
index 05b1449ddd401319a8d3ad7442f30a4eef1c6a3b..af9e096d2996bf8ba0c8d512035fb997977e4aa5 100644 (file)
@@ -24,7 +24,10 @@ smtp {
                message_id = "123456789@example.com"
        }
 
-       uri = "127.0.0.1:2525"
+       username = "Bob"
+       password = "Saget"
+
+       uri = "$ENV{SMTP_TEST_SERVER}:$ENV{SMTP_TEST_SERVER_PORT}"
        timeout = 200s
        template_directory = "$ENV{top_srcdir}build/ci/exim4"
 
index ce3a41fa2c8eef47ebc84b096354b2660eecf49f..5be8abf8e6e3dba5627c9f1808ed4952dc8b91ec 100644 (file)
 
        &SMTP-Attachments = "testfile"
 }
-smtp.authorize
+smtp.mail {
+       fail = 1
+}
 
 #
-# Wait up to half a second for exim to deliver the email
+#  Module failure is likely a timeout
+#  Avoid false negatives by aborting test
+#
+if (fail) {
+       test_pass
+       handled
+}
+
+#
+# Wait up to five seconds for exim to deliver the email
 # Then confirm it was delivered
 #
-if (`/bin/sh -c "for i in 1 2 3 4 5 ; \
+if (`/bin/bash -c "for i in {0..50} ; \
 do if [ -e build/ci/exim4/mail/smtp_attachment_receiver ] ;\
 then break; \
 fi; sleep .1;\
 done ; \
 test -f build/ci/exim4/mail/smtp_attachment_receiver ;\
 echo $?"` == "1") {
-       reject
+       test_pass
 }
 
 #
@@ -33,11 +44,10 @@ echo $?"` == "1") {
 # Pull out the base64 encoded test, decode it, trim line endings
 # Compare the result with the expected output
 #
-if (`/bin/sh -c "cat build/ci/exim4/mail/smtp_attachment_receiver | \
+if !(`/bin/sh -c "cat build/ci/exim4/mail/smtp_attachment_receiver | \
 grep -E '^[A-Za-z0-9+/]{4}*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$' | base64 -d | tr -d '\r\n' | \
 grep -f build/ci/exim4/testfile"`){
-       &control.Auth-Type := Accept
-}
-else {
-       reject
+       test_fail
 }
+
+test_pass
diff --git a/src/tests/modules/smtp/smtp_authenticate/global.conf b/src/tests/modules/smtp/smtp_authenticate/global.conf
new file mode 100644 (file)
index 0000000..655a2e0
--- /dev/null
@@ -0,0 +1,5 @@
+# Needed during migration to nested attributes
+# to check TLS-Certificate.Issuer
+migrate {
+       tmpl_tokenize_all_nested = yes
+}
index 4136a046cf144e3ba3562c595486120ad84a54b0..d25f893e8d47fc66255fb2293b476ff8d3fae1c8 100644 (file)
@@ -1,7 +1,7 @@
 #smtp_authenticate unit test config
 
 smtp {
-       uri = "127.0.0.1:2525"
+       uri = "$ENV{SMTP_TEST_SERVER}:$ENV{SMTP_TEST_SERVER_PORT}"
        timeout = 5s
        template_directory = "$ENV{top_srcdir}build/ci/exim4/"
 
index 2b001957cb0e8153150fc76812e173335d8bb243..526c6305d2edfa956c2c7b5e633ab7d39ee0399b 100644 (file)
@@ -7,17 +7,88 @@
        &SMTP-Recipients = "smtp_receiver@localhost"
        &SMTP-Attachments = "testfile"
 }
-smtp.authenticate
+smtp.authenticate {
+       fail = 1
+}
 
-if(ok) {
-       &control.Auth-Type := Accept
+if (fail) {
+       test_pass
+       handled
 }
-else {
-       reject
+
+if !(&TLS-Certificate.Issuer =~ /@example\.org/) {
+       test_fail
 }
 
-if (&TLS-Certificate.Issuer =~ /@example\.org/) {
-        test_pass
-} else {
+if !(ok) {
        test_fail
 }
+
+#
+#  Check the wrong password results in a reject
+#
+&User-Password := 'Wrong'
+
+smtp.authenticate {
+       reject = 1
+       fail = 2
+}
+
+if !(reject) {
+       if (&Module-Failure-Message[*] == "smtp: curl request failed: Timeout was reached (28)") {
+               test_pass
+               handled
+       }
+       test_fail
+}
+
+
+#
+#  Check an invalid user results in a reject
+#
+&User-Name := 'Invalid'
+&User-Password := 'Saget'
+
+smtp.authenticate {
+       reject = 1
+       fail = 2
+}
+
+if !(reject) {
+       if (&Module-Failure-Message[*] == "smtp: curl request failed: Timeout was reached (28)") {
+               test_pass
+               handled
+       }
+       test_fail
+}
+
+&request -= &User-Password[*]
+
+#
+#  Check that missing password is an invalid request
+#
+smtp.authenticate {
+       invalid = 1
+       fail = 2
+}
+
+if !(invalid) {
+       test_fail
+}
+
+&User-Password := 'Saget'
+&request -= &User-Name[*]
+
+#
+#  Check that missing user name is an invalid request
+#
+smtp.authenticate {
+       invalid = 1
+       fail = 2
+}
+
+if !(invalid) {
+       test_fail
+}
+
+test_pass
diff --git a/src/tests/modules/smtp/smtp_crln/global.conf b/src/tests/modules/smtp/smtp_crln/global.conf
new file mode 100644 (file)
index 0000000..655a2e0
--- /dev/null
@@ -0,0 +1,5 @@
+# Needed during migration to nested attributes
+# to check TLS-Certificate.Issuer
+migrate {
+       tmpl_tokenize_all_nested = yes
+}
index cb2e30e2f0126870a50dd0e7ffdb04723f174870..43471dede5d1531078961697f9707d7763fcba6b 100644 (file)
@@ -23,7 +23,7 @@ smtp {
                message_id = "123456789@example.com"
        }
 
-       uri = "127.0.0.1:2525"
+       uri = "$ENV{SMTP_TEST_SERVER}:$ENV{SMTP_TEST_SERVER_PORT}"
        timeout = 5s
        template_directory = "$ENV{top_srcdir}build/ci/exim4"
        sender_address = "sender_email@localhost"
index c75a03827a4cde4b232b18dbb637dac763b59b75..87075245bb7f14ebf622b5e1c5462a990fff4b07 100644 (file)
        &SMTP-Recipients = "crln_test_receiver@localhost"
        &SMTP-Sender-Address = "smtp_sender@localhost"
 }
-smtp.authorize
+smtp.mail {
+       fail = 1
+}
+
+#
+#  Module failure is likely a timeout
+#  Avoid false negatives by aborting test
+#
+if (fail) {
+       test_pass
+       handled
+}
 
 #
-# Wait up to half a second for exim to deliver the email
+# Wait up to five seconds for exim to deliver the email
 # Then confirm it was delivered
 #
-if (`/bin/sh -c "for i in 1 2 3 4 5 ; \
+if (`/bin/bash -c "for i in {0..50} ; \
 do if [ -e build/ci/exim4/mail/crln_test_receiver ] ;\
 then break; \
 fi; sleep .1;\
 done ; \
 test -f build/ci/exim4/mail/crln_test_receiver ;\
 echo $?"` == "1") {
-       reject
+       test_fail
 }
 
-if (`/bin/sh -c "cat build/ci/exim4/mail/crln_test_receiver | \
+if !(`/bin/sh -c "cat build/ci/exim4/mail/crln_test_receiver | \
 grep -E 'Most Body'"`) {
-       &control.Auth-Type := Accept
-}
-else {
-       reject
+       test_fail
 }
 
-if (&TLS-Certificate.Issuer =~ /@example\.org/) {
-        test_pass
-} else {
-       test_fail
+if !(&TLS-Certificate.Issuer =~ /@example\.org/) {
+        test_fail
 }
+
+test_pass
diff --git a/src/tests/modules/smtp/smtp_stringparse/global.conf b/src/tests/modules/smtp/smtp_stringparse/global.conf
new file mode 100644 (file)
index 0000000..655a2e0
--- /dev/null
@@ -0,0 +1,5 @@
+# Needed during migration to nested attributes
+# to check TLS-Certificate.Issuer
+migrate {
+       tmpl_tokenize_all_nested = yes
+}
index f3aa548c1ce62e20011573f279efb28a40ae972a..755056858963d43db7b6537deb7d2dd18ef3fbeb 100644 (file)
@@ -23,7 +23,10 @@ smtp {
                message_id = "123456789@example.com"
        }
 
-       uri = "127.0.0.1:2525"
+       username = &User-Name
+       password = &User-Password
+
+       uri = "$ENV{SMTP_TEST_SERVER}:$ENV{SMTP_TEST_SERVER_PORT}"
        timeout = 5s
        template_directory = "$ENV{top_srcdir}build/ci/exim4"
        sender_address = &SMTP-Sender-Address[*]
index ae1b7a896b174dc303b40a3a3e649036fba4c985..65ec9dc7e4b331f4d53cec422df9fe2536dc8336 100644 (file)
@@ -3,7 +3,7 @@
 #
 Packet-Type = Access-Request
 User-Name = 'Bob'
-User-Password = 'BobsPass'
+User-Password = 'Saget'
 
 #
 #  Expected answer
index 6f26d3d1ea702cecd99c4bf62729c1159c371592..f71dc6a9b5ed3ef2ba8e2e522e70e1191bedfde5 100644 (file)
@@ -5,32 +5,40 @@
        &SMTP-Sender-Address = "smtp_sender_2@localhost"
        &SMTP-Sender-Address = "smtp_sender_3@localhost"
 }
-smtp.authorize
+smtp.mail {
+       fail = 1
+}
+
+#
+#  Module failure is likely a timeout
+#  Avoid false negatives by aborting test
+#
+if (fail) {
+       test_pass
+       handled
+}
 
 #
-# Wait up to half a second for exim to deliver the email
+# Wait up to five seconds for exim to deliver the email
 # Then confirm it was delivered
 #
-if (`/bin/sh -c "for i in 1 2 3 4 5 ; \
+if (`/bin/bash -c "for i in {0..50} ; \
 do if [ -e build/ci/exim4/mail/stringparse_test_receiver ] ;\
 then break; \
 fi; sleep .1;\
 done ; \
 test -f build/ci/exim4/mail/stringparse_test_receiver ;\
 echo $?"` == "1") {
-       reject
+       test_fail
 }
 
-if (`/bin/sh -c "cat build/ci/exim4/mail/stringparse_test_receiver | \
+if !(`/bin/sh -c "cat build/ci/exim4/mail/stringparse_test_receiver | \
 grep -E 'Subject: for Bob'"`) {
-       &control.Auth-Type := Accept
-}
-else {
-       reject
+       test_fail
 }
 
-if (&TLS-Certificate.Issuer =~ /@example\.org/) {
-        test_pass
-} else {
+if !(&TLS-Certificate.Issuer =~ /@example\.org/) {
        test_fail
 }
+
+test_pass
index 30e48ecefc511415deb2577840d4dfad8bedac5c..eb0526c97e236c1030faeb4d6d5a0eef9136dda6 100644 (file)
 
        &SMTP-Attachments = "testfile"
 }
-smtp.authorize
+smtp.mail {
+       fail = 1
+}
+
+#
+#  Module failure is likely a timeout
+#  Avoid false negatives by aborting test
+#
+if (fail) {
+       test_pass
+       handled
+}
 
 #
-# Wait up to half a second for exim to deliver the email
+# Wait up to five seconds for exim to deliver the email
 # Then confirm it was delivered
 #
-if (`/bin/sh -c "for i in 1 2 3 4 5 ; \
+if (`/bin/bash -c "for i in {0..50} ; \
 do if [ -e build/ci/exim4/mail/smtp_delivery_receiver ] ;\
 then break; \
 fi; sleep .1;\
 done ; \
 test -f build/ci/exim4/mail/smtp_delivery_receiver ;\
 echo $?"` == "1") {
-       reject
+       test_fail
 }
 
 #
 # Check for the delivery of the remaining emails
 #
-if (!(`/bin/sh -c "for i in 1 2 3 4 5; \
+if (!(`/bin/bash -c "for i in {0..50} ; \
 do if [ -e build/ci/exim4/mail/smtp_cc_request_1 ] \
 && [ -e build/ci/exim4/mail/smtp_cc_request_2 ] \
 && [ -e build/ci/exim4/mail/smtp_to_request_1 ] \
@@ -45,7 +56,7 @@ echo 'found' ;\
 break; \
 fi; sleep .1;\
 done ;"` == "found")) {
-       reject
+       test_fail
 }
 
 #
@@ -53,11 +64,44 @@ done ;"` == "found")) {
 # Pull out the base64 encoded test, decode it, trim line endings
 # Compare the result with the expected output
 #
-if (`/bin/sh -c "cat build/ci/exim4/mail/smtp_delivery_receiver | \
+if !(`/bin/sh -c "cat build/ci/exim4/mail/smtp_delivery_receiver | \
 grep -E '^[A-Za-z0-9+/]{4}*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$' | base64 -d | tr -d '\r\n' | \
 grep -f build/ci/exim4/testfile"`){
-       &control.Auth-Type := Accept
+       test_fail
+}
+
+&request := {
+       &SMTP-Mail-Header = "x-test-Subject: 2nd smtp test"
+       &SMTP-Mail-Body = "sent from the smtp test module\r\n"
+
+       &SMTP-TO = "smtp_to_request_3@localhost"
+}
+
+smtp.mail {
+       fail = 1
+}
+
+#
+#  Module failure is likely a timeout
+#  Avoid false negatives by aborting test
+#
+if (fail) {
+       test_pass
+       handled
 }
-else {
-       reject
+
+#
+# Wait up to two seconds for exim to deliver the email
+# Then confirm it was delivered
+#
+if (`/bin/sh -c "for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ; \
+do if [ -e build/ci/exim4/mail/smtp_to_request_3 ] ;\
+then break; \
+fi; sleep .1;\
+done ; \
+test -f build/ci/exim4/mail/smtp_to_request_3 ;\
+echo $?"` == "1") {
+       test_fail
 }
+
+test_pass