From: Jeremy Harris Date: Sun, 19 May 2013 17:14:50 +0000 (+0100) Subject: Support AUTH for verify-callout and cutthrough-delivery. X-Git-Tag: exim-4_82_RC1~28^2~1 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/fcc8e04755fd6f211fd636e6c077ac41963ab0b9 Support AUTH for verify-callout and cutthrough-delivery. Refactored smtp transport to pull out AUTH-related routines so they could be also called from the verify code. Bugs 321, 823. --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index b024f7227..842dd8ffd 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -28844,6 +28844,9 @@ following SMTP commands are sent: LHLO is used instead of HELO if the transport's &%protocol%& option is set to &"lmtp"&. +The callout may use EHLO, AUTH and/or STARTTLS given appropriate option +settings. + A recipient callout check is similar. By default, it also uses an empty address for the sender. This default is chosen because most hosts do not make use of the sender address when verifying a recipient. Using the same address means diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index f06946a73..11079a298 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -193,6 +193,9 @@ PP/19 Renamed DNSSEC-enabling option to "dns_dnssec_ok", to make it PP/20 Added force_command boolean option to pipe transport. Patch from Nick Koston, of cPanel Inc. +JH/15 AUTH support on callouts (and hence cutthrough-deliveries). + Bugzilla 321, 823. + Exim version 4.80.1 ------------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index e349fc855..d1d0d7279 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -83,7 +83,7 @@ Version 4.82 for specific access to the information for each connection. The old names are present for now but deprecated. - Not yet supported: IGNOREQUOTA, SIZE, PIPELINING, AUTH. + Not yet supported: IGNOREQUOTA, SIZE, PIPELINING. 8. New expansion operators ${listnamed:name} to get the content of a named list and ${listcount:string} to count the items in a list. @@ -134,6 +134,9 @@ Version 4.82 decorating commands from user .forward pipe aliases with prefix wrappers, for instance. +20. Callout connections can now AUTH; the same controls as normal delivery + connections apply. + Version 4.80 ------------ diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 4b5529fd8..25cc5490a 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -814,6 +814,229 @@ return yield; +/* Do the client side of smtp-level authentication */ +/* +Arguments: + buffer EHLO response from server (gets overwritten) + addrlist chain of potential addresses to deliver + host host to deliver to + ob transport options + ibp, obp comms channel control blocks + +Returns: + OK Success, or failed (but not required): global "smtp_authenticated" set + DEFER Failed authentication (and was required) + ERROR Internal problem + + FAIL_SEND Failed communications - transmit + FAIL - response +*/ + +int +smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *host, + smtp_transport_options_block *ob, BOOL is_esmtp, + smtp_inblock *ibp, smtp_outblock *obp) +{ + int require_auth; + uschar *fail_reason = US"server did not advertise AUTH support"; + + smtp_authenticated = FALSE; + client_authenticator = client_authenticated_id = client_authenticated_sender = NULL; + require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL, + host->name, host->address, NULL); + + if (is_esmtp && !regex_AUTH) regex_AUTH = + regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)", + FALSE, TRUE); + + if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1)) + { + uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]); + expand_nmax = -1; /* reset */ + + /* Must not do this check until after we have saved the result of the + regex match above. */ + + if (require_auth == OK || + verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name, + host->address, NULL) == OK) + { + auth_instance *au; + fail_reason = US"no common mechanisms were found"; + + DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n"); + + /* Scan the configured authenticators looking for one which is configured + for use as a client, which is not suppressed by client_condition, and + whose name matches an authentication mechanism supported by the server. + If one is found, attempt to authenticate by calling its client function. + */ + + for (au = auths; !smtp_authenticated && au != NULL; au = au->next) + { + uschar *p = names; + if (!au->client || + (au->client_condition != NULL && + !expand_check_condition(au->client_condition, au->name, + US"client authenticator"))) + { + DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n", + au->name, + (au->client)? "client_condition is false" : + "not configured as a client"); + continue; + } + + /* Loop to scan supported server mechanisms */ + + while (*p != 0) + { + int rc; + int len = Ustrlen(au->public_name); + while (isspace(*p)) p++; + + if (strncmpic(au->public_name, p, len) != 0 || + (p[len] != 0 && !isspace(p[len]))) + { + while (*p != 0 && !isspace(*p)) p++; + continue; + } + + /* Found data for a listed mechanism. Call its client entry. Set + a flag in the outblock so that data is overwritten after sending so + that reflections don't show it. */ + + fail_reason = US"authentication attempt(s) failed"; + obp->authenticating = TRUE; + rc = (au->info->clientcode)(au, ibp, obp, + ob->command_timeout, buffer, bufsize); + obp->authenticating = FALSE; + DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", + au->name, rc); + + /* A temporary authentication failure must hold up delivery to + this host. After a permanent authentication failure, we carry on + to try other authentication methods. If all fail hard, try to + deliver the message unauthenticated unless require_auth was set. */ + + switch(rc) + { + case OK: + smtp_authenticated = TRUE; /* stops the outer loop */ + client_authenticator = au->name; + if (au->set_client_id != NULL) + client_authenticated_id = expand_string(au->set_client_id); + break; + + /* Failure after writing a command */ + + case FAIL_SEND: + return FAIL_SEND; + + /* Failure after reading a response */ + + case FAIL: + if (errno != 0 || buffer[0] != '5') return FAIL; + log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s", + au->name, host->name, host->address, buffer); + break; + + /* Failure by some other means. In effect, the authenticator + decided it wasn't prepared to handle this case. Typically this + is the result of "fail" in an expansion string. Do we need to + log anything here? Feb 2006: a message is now put in the buffer + if logging is required. */ + + case CANCELLED: + if (*buffer != 0) + log_write(0, LOG_MAIN, "%s authenticator cancelled " + "authentication H=%s [%s] %s", au->name, host->name, + host->address, buffer); + break; + + /* Internal problem, message in buffer. */ + + case ERROR: + set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE); + return ERROR; + } + + break; /* If not authenticated, try next authenticator */ + } /* Loop for scanning supported server mechanisms */ + } /* Loop for further authenticators */ + } + } + + /* If we haven't authenticated, but are required to, give up. */ + + if (require_auth == OK && !smtp_authenticated) + { + set_errno(addrlist, ERRNO_AUTHFAIL, + string_sprintf("authentication required but %s", fail_reason), DEFER, + FALSE); + return DEFER; + } + + return OK; +} + + +/* Construct AUTH appendix string for MAIL TO */ +/* +Arguments + buffer to build string + addrlist chain of potential addresses to deliver + ob transport options + +Globals smtp_authenticated + client_authenticated_sender +Return True on error, otherwise buffer has (possibly empty) terminated string +*/ + +BOOL +smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist, + smtp_transport_options_block *ob) +{ +uschar *local_authenticated_sender = authenticated_sender; + +#ifdef notdef + debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, smtp_authenticated?"Y":"N"); +#endif + +if (ob->authenticated_sender != NULL) + { + uschar *new = expand_string(ob->authenticated_sender); + if (new == NULL) + { + if (!expand_string_forcedfail) + { + uschar *message = string_sprintf("failed to expand " + "authenticated_sender: %s", expand_string_message); + set_errno(addrlist, 0, message, DEFER, FALSE); + return TRUE; + } + } + else if (new[0] != 0) local_authenticated_sender = new; + } + +/* Add the authenticated sender address if present */ + +if ((smtp_authenticated || ob->authenticated_sender_force) && + local_authenticated_sender != NULL) + { + string_format(buffer, bufsize, " AUTH=%s", + auth_xtextencode(local_authenticated_sender, + Ustrlen(local_authenticated_sender))); + client_authenticated_sender = string_copy(local_authenticated_sender); + } +else + *buffer= 0; + +return FALSE; +} + + + /************************************************* * Deliver address list to given host * *************************************************/ @@ -893,7 +1116,6 @@ smtp_inblock inblock; smtp_outblock outblock; int max_rcpt = tblock->max_addresses; uschar *igquotstr = US""; -uschar *local_authenticated_sender = authenticated_sender; uschar *helo_data = NULL; uschar *message = NULL; uschar new_message_id[MESSAGE_ID_LENGTH + 1]; @@ -1267,9 +1489,6 @@ if (continue_hostname == NULL #endif ) { - int require_auth; - uschar *fail_reason = US"server did not advertise AUTH support"; - /* Set for IGNOREQUOTA if the response to LHLO specifies support and the lmtp_ignore_quota option was set. */ @@ -1313,139 +1532,13 @@ if (continue_hostname == NULL the business. The host name and address must be available when the authenticator's client driver is running. */ - smtp_authenticated = FALSE; - client_authenticator = client_authenticated_id = client_authenticated_sender = NULL; - require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL, - host->name, host->address, NULL); - - if (esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1)) + switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host, + ob, esmtp, &inblock, &outblock)) { - uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]); - expand_nmax = -1; /* reset */ - - /* Must not do this check until after we have saved the result of the - regex match above. */ - - if (require_auth == OK || - verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name, - host->address, NULL) == OK) - { - auth_instance *au; - fail_reason = US"no common mechanisms were found"; - - DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n"); - - /* Scan the configured authenticators looking for one which is configured - for use as a client, which is not suppressed by client_condition, and - whose name matches an authentication mechanism supported by the server. - If one is found, attempt to authenticate by calling its client function. - */ - - for (au = auths; !smtp_authenticated && au != NULL; au = au->next) - { - uschar *p = names; - if (!au->client || - (au->client_condition != NULL && - !expand_check_condition(au->client_condition, au->name, - US"client authenticator"))) - { - DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n", - au->name, - (au->client)? "client_condition is false" : - "not configured as a client"); - continue; - } - - /* Loop to scan supported server mechanisms */ - - while (*p != 0) - { - int rc; - int len = Ustrlen(au->public_name); - while (isspace(*p)) p++; - - if (strncmpic(au->public_name, p, len) != 0 || - (p[len] != 0 && !isspace(p[len]))) - { - while (*p != 0 && !isspace(*p)) p++; - continue; - } - - /* Found data for a listed mechanism. Call its client entry. Set - a flag in the outblock so that data is overwritten after sending so - that reflections don't show it. */ - - fail_reason = US"authentication attempt(s) failed"; - outblock.authenticating = TRUE; - rc = (au->info->clientcode)(au, &inblock, &outblock, - ob->command_timeout, buffer, sizeof(buffer)); - outblock.authenticating = FALSE; - DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", - au->name, rc); - - /* A temporary authentication failure must hold up delivery to - this host. After a permanent authentication failure, we carry on - to try other authentication methods. If all fail hard, try to - deliver the message unauthenticated unless require_auth was set. */ - - switch(rc) - { - case OK: - smtp_authenticated = TRUE; /* stops the outer loop */ - client_authenticator = au->name; - if (au->set_client_id != NULL) - client_authenticated_id = expand_string(au->set_client_id); - break; - - /* Failure after writing a command */ - - case FAIL_SEND: - goto SEND_FAILED; - - /* Failure after reading a response */ - - case FAIL: - if (errno != 0 || buffer[0] != '5') goto RESPONSE_FAILED; - log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s", - au->name, host->name, host->address, buffer); - break; - - /* Failure by some other means. In effect, the authenticator - decided it wasn't prepared to handle this case. Typically this - is the result of "fail" in an expansion string. Do we need to - log anything here? Feb 2006: a message is now put in the buffer - if logging is required. */ - - case CANCELLED: - if (*buffer != 0) - log_write(0, LOG_MAIN, "%s authenticator cancelled " - "authentication H=%s [%s] %s", au->name, host->name, - host->address, buffer); - break; - - /* Internal problem, message in buffer. */ - - case ERROR: - yield = ERROR; - set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE); - goto SEND_QUIT; - } - - break; /* If not authenticated, try next authenticator */ - } /* Loop for scanning supported server mechanisms */ - } /* Loop for further authenticators */ - } - } - - /* If we haven't authenticated, but are required to, give up. */ - - if (require_auth == OK && !smtp_authenticated) - { - yield = DEFER; - set_errno(addrlist, ERRNO_AUTHFAIL, - string_sprintf("authentication required but %s", fail_reason), DEFER, - FALSE); - goto SEND_QUIT; + default: goto SEND_QUIT; + case OK: break; + case FAIL_SEND: goto SEND_FAILED; + case FAIL: goto RESPONSE_FAILED; } } @@ -1538,32 +1631,8 @@ Other expansion failures are serious. An empty result is ignored, but there is otherwise no check - this feature is expected to be used with LMTP and other cases where non-standard addresses (e.g. without domains) might be required. */ -if (ob->authenticated_sender != NULL) - { - uschar *new = expand_string(ob->authenticated_sender); - if (new == NULL) - { - if (!expand_string_forcedfail) - { - uschar *message = string_sprintf("failed to expand " - "authenticated_sender: %s", expand_string_message); - set_errno(addrlist, 0, message, DEFER, FALSE); - return ERROR; - } - } - else if (new[0] != 0) local_authenticated_sender = new; - } - -/* Add the authenticated sender address if present */ - -if ((smtp_authenticated || ob->authenticated_sender_force) && - local_authenticated_sender != NULL) - { - string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s", - auth_xtextencode(local_authenticated_sender, - Ustrlen(local_authenticated_sender))); - client_authenticated_sender = string_copy(local_authenticated_sender); - } +if (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob)) + return ERROR; /* From here until we send the DATA command, we can make use of PIPELINING if the server host supports it. The code has to be able to check the responses diff --git a/src/src/verify.c b/src/src/verify.c index 52404575f..ea7869d25 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -722,11 +722,18 @@ else } } + /* Try to AUTH */ + + else done = smtp_auth(responsebuffer, sizeof(responsebuffer), + addr, host, ob, esmtp, &inblock, &outblock) == OK && + + /* Build a mail-AUTH string (re-using responsebuffer for convenience */ + !smtp_mail_auth_str(responsebuffer, sizeof(responsebuffer), addr, ob) && + /* Send the MAIL command */ - else done = - smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n", - from_address) >= 0 && + smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>%s\r\n", + from_address, responsebuffer) >= 0 && smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout); diff --git a/test/confs/0568 b/test/confs/0568 new file mode 100644 index 000000000..dec5b0dbc --- /dev/null +++ b/test/confs/0568 @@ -0,0 +1,70 @@ +# Exim test configuration 0568 +# Recipient callout with AUTH + +exim_path = EXIM_PATH +host_lookup_order = bydns +primary_hostname = myhost.test.ex +rfc1413_query_timeout = 0s +spool_directory = DIR/spool +log_file_path = DIR/spool/log/%slog +gecos_pattern = "" +gecos_name = CALLER_NAME + +# ----- Main settings ----- + +acl_smtp_rcpt = check_rcpt + +queue_only + + +# ----- Authentication ----- + +begin authenticators + +plain: + driver = plaintext + public_name = PLAIN + client_send = ^userx^secret + server_advertise_condition = yes + server_prompts = : + server_condition = yes + server_set_id = $auth2 + + +# ----- ACLs ----- + +begin acl + +check_rcpt: + accept verify = recipient/callout + + +# ----- Routers ----- + +begin routers + +r1: + driver = accept + transport = ${if eq{force}{$domain} {t2}{t1}} + + +# ----- Transports ----- + +begin transports + +t1: + driver = smtp + hosts = 127.0.0.1 + port = PORT_S + allow_localhost + hosts_try_auth = * + +t2: + driver = smtp + hosts = 127.0.0.1 + port = PORT_S + allow_localhost + hosts_try_auth = * + authenticated_sender= brian + +# End diff --git a/test/scripts/0000-Basic/0568 b/test/scripts/0000-Basic/0568 new file mode 100644 index 000000000..2aa86f45d --- /dev/null +++ b/test/scripts/0000-Basic/0568 @@ -0,0 +1,76 @@ +# Recipient callout with AUTH +need_ipv4 +# +# Variant 1: using authenticated_sender on the transport. +server PORT_S 1 +220 Welcome +EHLO +250-wotcher mate +250-AUTH PLAIN +250 Hi +AUTH +250 Oh alright then +MAIL FROM +250 OK +RCPT TO +250 OK +QUIT +250 OK +**** +exim -odq -bs +EHLO the.client +mail from:<> +RCPT TO: +quit +**** +# +# +# Variant 2: Passing through an authenticated_sender from the MAIL FROM: +server PORT_S 1 +220 Welcome +EHLO +250-wotcher mate +250-AUTH PLAIN +250 Hi +AUTH +250 Oh alright then +MAIL FROM +250 OK +RCPT TO +250 OK +QUIT +250 OK +**** +exim -odq -bs +EHLO the.client +AUTH PLAIN AHVzZXJ4AHNlY3JldA== +mail from:<> AUTH=freddy +RCPT TO: +quit +**** +# +# +# Variant 3: An authenticated_sender option on the transport should override +# a value set by the MAIL FROM: +server PORT_S 1 +220 Welcome +EHLO +250-wotcher mate +250-AUTH PLAIN +250 Hi +AUTH +250 Oh alright then +MAIL FROM +250 OK +RCPT TO +250 OK +QUIT +250 OK +**** +exim -odq -bs +EHLO the.client +AUTH PLAIN AHVzZXJ4AHNlY3JldA== +mail from:<> AUTH=freddy +RCPT TO: +quit +**** diff --git a/test/stderr/0398 b/test/stderr/0398 index 4e97f4e00..0ad911345 100644 --- a/test/stderr/0398 +++ b/test/stderr/0398 @@ -129,6 +129,7 @@ Connecting to 127.0.0.1 [127.0.0.1]:1224 ... connected 127.0.0.1 in hosts_avoid_esmtp? no (option unset) SMTP>> EHLO mail.test.ex SMTP<< 250 OK +127.0.0.1 in hosts_require_auth? no (option unset) SMTP>> MAIL FROM:<> SMTP<< 250 OK SMTP>> RCPT TO: diff --git a/test/stderr/0432 b/test/stderr/0432 index 33e1b9892..759c9a819 100644 --- a/test/stderr/0432 +++ b/test/stderr/0432 @@ -92,6 +92,7 @@ Connecting to 127.0.0.1 [127.0.0.1]:1224 ... connected 127.0.0.1 in hosts_avoid_esmtp? no (option unset) SMTP>> EHLO myhost.test.ex SMTP<< 250 OK +127.0.0.1 in hosts_require_auth? no (option unset) SMTP>> MAIL FROM:<> SMTP<< 250 OK SMTP>> RCPT TO: @@ -246,6 +247,7 @@ MUNGED: ::1 will be omitted in what follows >>> 127.0.0.1 in hosts_avoid_esmtp? no (option unset) >>> SMTP>> EHLO myhost.test.ex >>> SMTP<< 250 OK +>>> 127.0.0.1 in hosts_require_auth? no (option unset) >>> SMTP>> MAIL FROM:<> >>> SMTP<< 250 OK >>> SMTP>> RCPT TO: diff --git a/test/stderr/5410 b/test/stderr/5410 index f8b31a750..40ef77c4a 100644 --- a/test/stderr/5410 +++ b/test/stderr/5410 @@ -86,6 +86,7 @@ expanding: ${if eq {$address_data}{userz}{*}{:}} 250-8BITMIME 250-PIPELINING 250 HELP +127.0.0.1 in hosts_require_auth? no (option unset) SMTP>> MAIL FROM: SMTP<< 250 OK SMTP>> RCPT TO: @@ -218,6 +219,7 @@ skipping: result is not used expanding: ${if eq {$address_data}{usery}{*}{:}} result: * 127.0.0.1 in hosts_avoid_tls? yes (matched "*") +127.0.0.1 in hosts_require_auth? no (option unset) SMTP>> MAIL FROM: SMTP<< 250 OK SMTP>> RCPT TO: @@ -350,6 +352,7 @@ skipping: result is not used expanding: ${if eq {$address_data}{usery}{*}{:}} result: * 127.0.0.1 in hosts_avoid_tls? yes (matched "*") +127.0.0.1 in hosts_require_auth? no (option unset) SMTP>> MAIL FROM: SMTP<< 250 OK SMTP>> RCPT TO: diff --git a/test/stdout/0568 b/test/stdout/0568 new file mode 100644 index 000000000..671998a86 --- /dev/null +++ b/test/stdout/0568 @@ -0,0 +1,82 @@ +220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 +250-myhost.test.ex Hello CALLER at the.client +250-SIZE 52428800 +250-8BITMIME +250-PIPELINING +250-AUTH PLAIN +250 HELP +250 OK +250 Accepted +221 myhost.test.ex closing connection +220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 +250-myhost.test.ex Hello CALLER at the.client +250-SIZE 52428800 +250-8BITMIME +250-PIPELINING +250-AUTH PLAIN +250 HELP +235 Authentication succeeded +250 OK +250 Accepted +221 myhost.test.ex closing connection +220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 +250-myhost.test.ex Hello CALLER at the.client +250-SIZE 52428800 +250-8BITMIME +250-PIPELINING +250-AUTH PLAIN +250 HELP +235 Authentication succeeded +250 OK +250 Accepted +221 myhost.test.ex closing connection + +******** SERVER ******** +Listening on port 1224 ... +Connection request from [127.0.0.1] +220 Welcome +EHLO myhost.test.ex +250-wotcher mate +250-AUTH PLAIN +250 Hi +AUTH PLAIN AHVzZXJ4AHNlY3JldA== +250 Oh alright then +MAIL FROM:<> AUTH=brian +250 OK +RCPT TO: +250 OK +QUIT +250 OK +End of script +Listening on port 1224 ... +Connection request from [127.0.0.1] +220 Welcome +EHLO myhost.test.ex +250-wotcher mate +250-AUTH PLAIN +250 Hi +AUTH PLAIN AHVzZXJ4AHNlY3JldA== +250 Oh alright then +MAIL FROM:<> AUTH=freddy +250 OK +RCPT TO: +250 OK +QUIT +250 OK +End of script +Listening on port 1224 ... +Connection request from [127.0.0.1] +220 Welcome +EHLO myhost.test.ex +250-wotcher mate +250-AUTH PLAIN +250 Hi +AUTH PLAIN AHVzZXJ4AHNlY3JldA== +250 Oh alright then +MAIL FROM:<> AUTH=brian +250 OK +RCPT TO: +250 OK +QUIT +250 OK +End of script