X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/fd98a5c6771f3a5a686e54370b0525dcc3dca2f9..993e3ef1b0190835fbb69a537c30990db232b4ae:/src/src/transports/smtp.c diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index ee260a129..ac24f5f09 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -35,6 +35,10 @@ optionlist smtp_transport_options[] = { (void *)offsetof(transport_instance, connection_max_messages) }, { "data_timeout", opt_time, (void *)offsetof(smtp_transport_options_block, data_timeout) }, +#ifdef EXPERIMENTAL_DBL + { "dbl_host_defer_query", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, dbl_host_defer_query) }, +#endif { "delay_after_cutoff", opt_bool, (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) }, #ifndef DISABLE_DKIM @@ -101,6 +105,10 @@ optionlist smtp_transport_options[] = { { "hosts_require_auth", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_require_auth) }, #ifdef SUPPORT_TLS +# if defined EXPERIMENTAL_OCSP + { "hosts_require_ocsp", opt_stringptr, + (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) }, +# endif { "hosts_require_tls", opt_stringptr, (void *)offsetof(smtp_transport_options_block, hosts_require_tls) }, #endif @@ -178,6 +186,9 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* hosts_require_auth */ #ifdef EXPERIMENTAL_PRDR NULL, /* hosts_try_prdr */ +#endif +#ifdef EXPERIMENTAL_OCSP + NULL, /* hosts_require_ocsp */ #endif NULL, /* hosts_require_tls */ NULL, /* hosts_avoid_tls */ @@ -226,6 +237,9 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* dkim_sign_headers */ NULL /* dkim_strict */ #endif +#ifdef EXPERIMENTAL_DBL + ,NULL /* dbl_host_defer_query */ +#endif }; @@ -570,6 +584,56 @@ else +#ifdef EXPERIMENTAL_DBL +/************************************************* +* Write error message to database log * +*************************************************/ + +/* This expands an arbitrary per-transport string. + It might, for example, be used to write to the database log. + +Arguments: + dbl_host_defer_query dbl_host_defer_query from the transport options block + addr the address item containing error information + host the current host + +Returns: nothing +*/ + +static void +dbl_write_defer_log(uschar *dbl_host_defer_query, address_item *addr, host_item *host) +{ +if (dbl_host_defer_query == NULL) + return; + +dbl_delivery_ip = string_copy(host->address); +dbl_delivery_port = (host->port == PORT_NONE)? 25 : host->port; +dbl_delivery_fqdn = string_copy(host->name); +dbl_delivery_local_part = string_copy(addr->local_part); +dbl_delivery_domain = string_copy(addr->domain); +dbl_defer_errno = addr->basic_errno; + +dbl_defer_errstr = (addr->message != NULL) + ? (addr->basic_errno > 0) + ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno)) + : string_copy(addr->message) + : (addr->basic_errno > 0) + ? string_copy(strerror(addr->basic_errno)) + : NULL; + +DEBUG(D_transport) { + debug_printf(" DBL(host defer): dbl_host_defer_query=|%s| dbl_delivery_IP=%s\n", dbl_host_defer_query, dbl_delivery_ip); +} + +router_name = addr->router->name; +transport_name = addr->transport->name; +expand_string(dbl_host_defer_query); +router_name = transport_name = NULL; +} +#endif + + + /************************************************* * Synchronize SMTP responses * *************************************************/ @@ -807,6 +871,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 * *************************************************/ @@ -886,7 +1173,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]; @@ -1147,13 +1433,15 @@ if (tls_offered && !suppress_tls && int rc = tls_client_start(inblock.sock, host, addrlist, - NULL, /* No DH param */ ob->tls_certificate, ob->tls_privatekey, ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, ob->tls_require_ciphers, +#ifdef EXPERIMENTAL_OCSP + ob->hosts_require_ocsp, +#endif ob->tls_dh_min_bits, ob->command_timeout); @@ -1258,9 +1546,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. */ @@ -1304,139 +1589,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)) - { - 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) + switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host, + ob, esmtp, &inblock, &outblock)) { - 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; } } @@ -1529,32 +1688,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 @@ -1834,7 +1969,12 @@ if (!ok) ok = TRUE; else /* Set up confirmation if needed - applies only to SMTP */ - if ((log_extra_selector & LX_smtp_confirmation) != 0 && !lmtp) + if ( + #ifndef EXPERIMENTAL_DBL + (log_extra_selector & LX_smtp_confirmation) != 0 && + #endif + !lmtp + ) { uschar *s = string_printing(buffer); conf = (s == buffer)? (uschar *)string_copy(s) : s; @@ -2973,6 +3113,11 @@ for (cutoff_retry = 0; expired && first_addr->basic_errno != ERRNO_TLSFAILURE) write_logs(first_addr, host); + #ifdef EXPERIMENTAL_DBL + if (rc == DEFER) + dbl_write_defer_log(ob->dbl_host_defer_query, first_addr, host); + #endif + /* If STARTTLS was accepted, but there was a failure in setting up the TLS session (usually a certificate screwup), and the host is not in hosts_require_tls, and tls_tempfail_tryclear is true, try again, with @@ -2995,6 +3140,10 @@ for (cutoff_retry = 0; expired && expanded_hosts != NULL, &message_defer, TRUE); if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL) write_logs(first_addr, host); + #ifdef EXPERIMENTAL_DBL + if (rc == DEFER) + dbl_write_defer_log(ob->dbl_host_defer_query, first_addr, host); + #endif } #endif }