diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/src/functions.h | 1 | ||||
-rw-r--r-- | src/src/structs.h | 12 | ||||
-rw-r--r-- | src/src/transports/smtp.c | 310 | ||||
-rw-r--r-- | src/src/transports/smtp.h | 15 | ||||
-rw-r--r-- | src/src/verify.c | 408 |
5 files changed, 337 insertions, 409 deletions
diff --git a/src/src/functions.h b/src/src/functions.h index 04d941034..bba8c446b 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -169,7 +169,6 @@ extern BOOL enq_start(uschar *, unsigned); extern uschar *event_raise(uschar *, const uschar *, uschar *); extern void msg_event_raise(const uschar *, const address_item *); #endif -extern uschar ehlo_response(uschar *, size_t, uschar); extern const uschar * exim_errstr(int); extern void exim_exit(int); extern void exim_nullstd(void); diff --git a/src/src/structs.h b/src/src/structs.h index 12d714f28..d9d37f1c0 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -244,17 +244,7 @@ typedef struct transport_context { /* items below only used with option topt_use_bdat */ tpt_chunk_cmd_cb chunk_cb; /* per-datachunk callback */ - struct smtp_inblock * inblock; - struct smtp_outblock * outblock; - host_item * host; - struct address_item * first_addr; - struct address_item **sync_addr; - BOOL pending_MAIL; - BOOL pending_BDAT; - BOOL good_RCPT; - BOOL * completed_address; - int cmd_count; - uschar * buffer; + void * smtp_context; } transport_ctx; diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index af463d66a..95bee582d 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -510,82 +510,62 @@ check_response(host_item *host, int *errno_value, int more_errno, uschar *buffer, int *yield, uschar **message, BOOL *pass_message) { uschar * pl = pipelining_active ? US"pipelined " : US""; +const uschar * s; *yield = '4'; /* Default setting is to give a temporary error */ -/* Handle response timeout */ - -if (*errno_value == ETIMEDOUT) - { - *message = US string_sprintf("SMTP timeout after %s%s", - pl, smtp_command); - if (transport_count > 0) - *message = US string_sprintf("%s (%d bytes written)", *message, - transport_count); - return FALSE; - } - -/* Handle malformed SMTP response */ - -if (*errno_value == ERRNO_SMTPFORMAT) - { - const uschar *malfresp = string_printing(buffer); - while (isspace(*malfresp)) malfresp++; - *message = *malfresp == 0 - ? string_sprintf("Malformed SMTP reply (an empty line) " - "in response to %s%s", pl, smtp_command) - : string_sprintf("Malformed SMTP reply in response to %s%s: %s", - pl, smtp_command, malfresp); - return FALSE; - } - -/* Handle a failed filter process error; can't send QUIT as we mustn't -end the DATA. */ - -if (*errno_value == ERRNO_FILTER_FAIL) - { - *message = US string_sprintf("transport filter process failed (%d)%s", - more_errno, - (more_errno == EX_EXECFAILED)? ": unable to execute command" : ""); - return FALSE; - } - -/* Handle a failed add_headers expansion; can't send QUIT as we mustn't -end the DATA. */ - -if (*errno_value == ERRNO_CHHEADER_FAIL) - { - *message = - US string_sprintf("failed to expand headers_add or headers_remove: %s", - expand_string_message); - return FALSE; - } - -/* Handle failure to write a complete data block */ - -if (*errno_value == ERRNO_WRITEINCOMPLETE) +switch(*errno_value) { - *message = US string_sprintf("failed to write a data block"); - return FALSE; - } + case ETIMEDOUT: /* Handle response timeout */ + *message = US string_sprintf("SMTP timeout after %s%s", + pl, smtp_command); + if (transport_count > 0) + *message = US string_sprintf("%s (%d bytes written)", *message, + transport_count); + return FALSE; + + case ERRNO_SMTPFORMAT: /* Handle malformed SMTP response */ + s = string_printing(buffer); + while (isspace(*s)) s++; + *message = *s == 0 + ? string_sprintf("Malformed SMTP reply (an empty line) " + "in response to %s%s", pl, smtp_command) + : string_sprintf("Malformed SMTP reply in response to %s%s: %s", + pl, smtp_command, s); + return FALSE; + + case ERRNO_FILTER_FAIL: /* Handle a failed filter process error; + can't send QUIT as we mustn't end the DATA. */ + *message = string_sprintf("transport filter process failed (%d)%s", + more_errno, + more_errno == EX_EXECFAILED ? ": unable to execute command" : ""); + return FALSE; + + case ERRNO_CHHEADER_FAIL: /* Handle a failed add_headers expansion; + can't send QUIT as we mustn't end the DATA. */ + *message = + string_sprintf("failed to expand headers_add or headers_remove: %s", + expand_string_message); + return FALSE; + + case ERRNO_WRITEINCOMPLETE: /* failure to write a complete data block */ + *message = string_sprintf("failed to write a data block"); + return FALSE; #ifdef SUPPORT_I18N -/* Handle lack of advertised SMTPUTF8, for international message */ -if (*errno_value == ERRNO_UTF8_FWD) - { - *message = US"utf8 support required but not offered for forwarding"; - DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message); - return TRUE; - } + case ERRNO_UTF8_FWD: /* no advertised SMTPUTF8, for international message */ + *message = US"utf8 support required but not offered for forwarding"; + DEBUG(D_deliver|D_transport) debug_printf("%s\n", *message); + return TRUE; #endif + } /* Handle error responses from the remote mailer. */ if (buffer[0] != 0) { - const uschar *s = string_printing(buffer); - *message = US string_sprintf("SMTP error from remote mail server after %s%s: " - "%s", pl, smtp_command, s); + *message = string_sprintf("SMTP error from remote mail server after %s%s: " + "%s", pl, smtp_command, s = string_printing(buffer)); *pass_message = TRUE; *yield = buffer[0]; return TRUE; @@ -754,6 +734,8 @@ static int sync_responses(smtp_context * sx, int count, int pending_DATA) { address_item *addr = sx->sync_addr; +smtp_transport_options_block *ob = + (smtp_transport_options_block *)sx->tblock->options_block; int yield = 0; /* Handle the response for a MAIL command. On error, reinstate the original @@ -763,7 +745,8 @@ responses before returning, except after I/O errors and timeouts. */ if (sx->pending_MAIL) { count--; - if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout)) + if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), + '2', ob->command_timeout)) { DEBUG(D_transport) debug_printf("bad response for MAIL\n"); Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */ @@ -779,7 +762,7 @@ if (sx->pending_MAIL) while (count-- > 0) { if (!smtp_read_response(&sx->inblock, flushbuffer, sizeof(flushbuffer), - '2', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout) + '2', ob->command_timeout) && (errno != 0 || flushbuffer[0] == 0)) break; } @@ -810,7 +793,8 @@ while (count-- > 0) /* The address was accepted */ addr->host_used = sx->host; - if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout)) + if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), + '2', ob->command_timeout)) { yield |= 1; addr->transport_return = PENDING_OK; @@ -833,7 +817,7 @@ while (count-- > 0) else if (errno == ETIMEDOUT) { uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>", - transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes)); + transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes)); set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE); retry_add_item(addr, addr->address_retry_key, 0); update_waiting = FALSE; @@ -861,7 +845,8 @@ while (count-- > 0) "%s", transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes), string_printing(sx->buffer)); setflag(addr, af_pass_message); - msglog_line(sx->host, addr->message); + if (!sx->verify) + msglog_line(sx->host, addr->message); /* The response was 5xx */ @@ -879,38 +864,40 @@ while (count-- > 0) addr->basic_errno = ERRNO_RCPT4XX; addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8; + if (!sx->verify) + { #ifndef DISABLE_EVENT - event_defer_errno = addr->more_errno; - msg_event_raise(US"msg:rcpt:host:defer", addr); + event_defer_errno = addr->more_errno; + msg_event_raise(US"msg:rcpt:host:defer", addr); #endif - /* Log temporary errors if there are more hosts to be tried. - If not, log this last one in the == line. */ + /* Log temporary errors if there are more hosts to be tried. + If not, log this last one in the == line. */ - if (sx->host->next) - log_write(0, LOG_MAIN, "H=%s [%s]: %s", sx->host->name, sx->host->address, addr->message); + if (sx->host->next) + log_write(0, LOG_MAIN, "H=%s [%s]: %s", + sx->host->name, sx->host->address, addr->message); #ifndef DISABLE_EVENT - else - msg_event_raise(US"msg:rcpt:defer", addr); + else + msg_event_raise(US"msg:rcpt:defer", addr); #endif - /* Do not put this message on the list of those waiting for specific - hosts, as otherwise it is likely to be tried too often. */ + /* Do not put this message on the list of those waiting for specific + hosts, as otherwise it is likely to be tried too often. */ - update_waiting = FALSE; + update_waiting = FALSE; - /* Add a retry item for the address so that it doesn't get tried again - too soon. If address_retry_include_sender is true, add the sender address - to the retry key. */ + /* Add a retry item for the address so that it doesn't get tried again + too soon. If address_retry_include_sender is true, add the sender address + to the retry key. */ - if (((smtp_transport_options_block *)sx->tblock->options_block)->address_retry_include_sender) - { - uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key, - sender_address); - retry_add_item(addr, altkey, 0); - } - else retry_add_item(addr, addr->address_retry_key, 0); + retry_add_item(addr, + ob->address_retry_include_sender + ? string_sprintf("%s:<%s>", addr->address_retry_key, sender_address) + : addr->address_retry_key, + 0); + } } } } /* Loop for next RCPT response */ @@ -918,13 +905,14 @@ while (count-- > 0) /* Update where to start at for the next block of responses, unless we have already handled all the addresses. */ -if (addr != NULL) sx->sync_addr = addr->next; +if (addr) sx->sync_addr = addr->next; /* Handle a response to DATA. If we have not had any good recipients, either previously or in this block, the response is ignored. */ if (pending_DATA != 0 && - !smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '3', ((smtp_transport_options_block *)sx->tblock->options_block)->command_timeout)) + !smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), + '3', ob->command_timeout)) { int code; uschar *msg; @@ -1296,9 +1284,11 @@ return Ustrcmp(current_local_identity, message_local_identity) == 0; -uschar -ehlo_response(uschar * buf, size_t bsize, uschar checks) +static uschar +ehlo_response(uschar * buf, uschar checks) { +size_t bsize = Ustrlen(buf); + #ifdef SUPPORT_TLS if ( checks & PEER_OFFERED_TLS && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0) @@ -1361,17 +1351,15 @@ smtp_chunk_cmd_callback(int fd, transport_ctx * tctx, { smtp_transport_options_block * ob = (smtp_transport_options_block *)(tctx->tblock->options_block); +smtp_context * sx = tctx->smtp_context; int cmd_count = 0; int prev_cmd_count; -uschar * buffer = tctx->buffer; -smtp_context sx; - /* Write SMTP chunk header command */ if (chunk_size > 0) { - if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n", + if((cmd_count = smtp_write_command(&sx->outblock, FALSE, "BDAT %u%s\r\n", chunk_size, flags & tc_chunk_last ? " LAST" : "") ) < 0) return ERROR; @@ -1379,69 +1367,61 @@ if (chunk_size > 0) data_command = string_copy(big_buffer); /* Save for later error message */ } -prev_cmd_count = cmd_count += tctx->cmd_count; +prev_cmd_count = cmd_count += sx->cmd_count; /* Reap responses for any previous, but not one we just emitted */ if (chunk_size > 0) prev_cmd_count--; -if (tctx->pending_BDAT) +if (sx->pending_BDAT) prev_cmd_count--; if (flags & tc_reap_prev && prev_cmd_count > 0) { - sx.first_addr = tctx->first_addr; - sx.tblock = tctx->tblock; - sx.sync_addr = *tctx->sync_addr; - sx.host = tctx->host; - sx.pending_MAIL = tctx->pending_MAIL; - sx.inblock = *tctx->inblock; - DEBUG(D_transport) debug_printf("look for %d responses" " for previous pipelined cmds\n", prev_cmd_count); - switch(sync_responses(&sx, prev_cmd_count, 0)) + switch(sync_responses(sx, prev_cmd_count, 0)) { case 1: /* 2xx (only) => OK */ - case 3: tctx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */ - case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */ + case 3: sx->good_RCPT = TRUE; /* 2xx & 5xx => OK & progress made */ + case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ case 0: break; /* No 2xx or 5xx, but no probs */ case -1: /* Timeout on RCPT */ default: return ERROR; /* I/O error, or any MAIL/DATA error */ } - *tctx->sync_addr = sx.sync_addr; cmd_count = 1; - if (!tctx->pending_BDAT) + if (!sx->pending_BDAT) pipelining_active = FALSE; } /* Reap response for an outstanding BDAT */ -if (tctx->pending_BDAT) +if (sx->pending_BDAT) { DEBUG(D_transport) debug_printf("look for one response for BDAT\n"); - if (!smtp_read_response(tctx->inblock, sx.buffer, sizeof(sx.buffer), '2', + if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2', ob->command_timeout)) { - if (errno == 0 && sx.buffer[0] == '4') + if (errno == 0 && sx->buffer[0] == '4') { errno = ERRNO_DATA4XX; /*XXX does this actually get used? */ - tctx->first_addr->more_errno |= - ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8; + sx->first_addr->more_errno |= + ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8; } return ERROR; } cmd_count--; - tctx->pending_BDAT = FALSE; + sx->pending_BDAT = FALSE; pipelining_active = FALSE; } else if (chunk_size > 0) - tctx->pending_BDAT = TRUE; + sx->pending_BDAT = TRUE; -tctx->cmd_count = cmd_count; +sx->cmd_count = cmd_count; return OK; } @@ -1456,8 +1436,6 @@ Arguments: ctx connection context suppress_tls if TRUE, don't attempt a TLS connection - this is set for a second attempt after TLS initialization fails - verify TRUE if connection is for a verify callout, FALSE for - a delivery attempt Returns: OK - the connection was made and the delivery attempted; fd is set in the conn context, tls_out set up. @@ -1469,7 +1447,7 @@ Returns: OK - the connection was made and the delivery attempted; to expand */ int -smtp_setup_conn(smtp_context * sx, BOOL suppress_tls, BOOL verify) +smtp_setup_conn(smtp_context * sx, BOOL suppress_tls) { #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) dns_answer tlsa_dnsa; @@ -1561,7 +1539,7 @@ specially so they can be identified for retries. */ if (continue_hostname == NULL) { - if (verify) + if (sx->verify) HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port); /* This puts port into host->port */ @@ -1573,14 +1551,14 @@ if (continue_hostname == NULL) { uschar * msg = NULL; int save_errno = errno; - if (verify) + if (sx->verify) { msg = strerror(errno); HDEBUG(D_verify) debug_printf("connect: %s\n", msg); } set_errno_nohost(sx->addrlist, save_errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : save_errno, - verify ? string_sprintf("could not connect: %s", msg) + sx->verify ? string_sprintf("could not connect: %s", msg) : NULL, DEFER, FALSE); sx->send_quit = FALSE; @@ -1627,7 +1605,7 @@ if (continue_hostname == NULL) if (sx->helo_data) if (!(sx->helo_data = expand_string(sx->helo_data))) - if (verify) + if (sx->verify) log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's helo_data value for callout: %s", sx->addrlist->address, expand_string_message); @@ -1639,7 +1617,7 @@ if (continue_hostname == NULL) if ((sx->helo_data = string_domain_utf8_to_alabel(sx->helo_data, &expand_string_message)), expand_string_message) - if (verify) + if (sx->verify) log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's helo_data value for callout: %s", sx->addrlist->address, expand_string_message); @@ -1812,7 +1790,7 @@ goto SEND_QUIT; if (sx->esmtp || sx->lmtp) { - sx->peer_offered = ehlo_response(sx->buffer, Ustrlen(sx->buffer), + sx->peer_offered = ehlo_response(sx->buffer, PEER_OFFERED_TLS /* others checked later */ ); @@ -1856,7 +1834,7 @@ for error analysis. */ if ( smtp_peer_options & PEER_OFFERED_TLS && !suppress_tls && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK - && ( !verify + && ( !sx->verify || verify_check_given_host(&sx->ob->hosts_verify_avoid_tls, sx->host) != OK ) ) { @@ -2016,7 +1994,7 @@ if (continue_hostname == NULL { if (sx->esmtp || sx->lmtp) { - sx->peer_offered = ehlo_response(sx->buffer, Ustrlen(sx->buffer), + sx->peer_offered = ehlo_response(sx->buffer, 0 /* no TLS */ | (sx->lmtp && sx->ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0) | PEER_OFFERED_CHUNKING @@ -2355,11 +2333,12 @@ if (sx->peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop)) /* Return: - 0 good - -1 MAIL response error, any read i/o error - -2 non-MAIL response timeout - -3 internal error; channel still usable - -4 transmit failed + 0 good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL) + -1 MAIL response error + -2 any non-MAIL read i/o error + -3 non-MAIL response timeout + -4 internal error; channel still usable + -5 transmit failed */ int @@ -2372,7 +2351,7 @@ int rc; if (build_mailcmd_options(sx, sx->first_addr) != OK) { *yield = ERROR; - return -3; + return -4; } /* From here until we send the DATA command, we can make use of PIPELINING @@ -2400,7 +2379,7 @@ sx->pending_MAIL = TRUE; /* The block starts with MAIL */ { set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE); *yield = ERROR; - return -3; + return -4; } setflag(sx->addrlist, af_utf8_downcvt); } @@ -2415,7 +2394,7 @@ mail_command = string_copy(big_buffer); /* Save for later error message */ switch(rc) { case -1: /* Transmission error */ - return -4; + return -5; case +1: /* Cmd was sent */ if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2', @@ -2430,6 +2409,8 @@ switch(rc) } sx->pending_MAIL = FALSE; break; + + /* otherwise zero: command queued for pipeline */ } /* Pass over all the relevant recipient addresses for this host, which are the @@ -2443,7 +2424,9 @@ In the MUA wrapper situation, we want to flush the PIPELINING buffer for the last address because we want to abort if any recipients have any kind of problem, temporary or permanent. We know that all recipient addresses will have the PENDING_DEFER status, because only one attempt is ever made, and we know -that max_rcpt will be large, so all addresses will be done at once. */ +that max_rcpt will be large, so all addresses will be done at once. + +For verify we flush the pipeline after any (the only) rcpt address. */ for (addr = sx->first_addr, address_count = 0; addr && address_count < sx->max_rcpt; @@ -2457,7 +2440,7 @@ for (addr = sx->first_addr, address_count = 0; ? dsn_support_yes : dsn_support_no; address_count++; - no_flush = pipelining_active && (!mua_wrapper || addr->next); + no_flush = pipelining_active && !sx->verify && (!mua_wrapper || addr->next); build_rcptcmd_options(sx, addr); @@ -2475,21 +2458,21 @@ for (addr = sx->first_addr, address_count = 0; { /*XXX could we use a per-address errstr here? Not fail the whole send? */ errno = ERRNO_EXPANDFAIL; - return -4; /*XXX too harsh? */ + return -5; /*XXX too harsh? */ } #endif count = smtp_write_command(&sx->outblock, no_flush, "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer); - if (count < 0) return -4; + if (count < 0) return -5; if (count > 0) { switch(sync_responses(sx, count, 0)) { case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */ case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ - break; + break; case 1: sx->ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ if (!sx->lmtp) /* can't tell about progress yet */ @@ -2497,8 +2480,9 @@ for (addr = sx->first_addr, address_count = 0; case 0: /* No 2xx or 5xx, but no probs */ break; - case -1: return -2; /* Timeout on RCPT */ - default: return -1; /* I/O error, or any MAIL error */ + case -1: return -3; /* Timeout on RCPT */ + case -2: return -2; /* non-MAIL read i/o error */ + default: return -1; /* any MAIL error */ } sx->pending_MAIL = FALSE; /* Dealt with MAIL */ } @@ -2581,17 +2565,18 @@ sx.port = port; sx.interface = interface; sx.helo_data = NULL; sx.tblock = tblock; +sx.verify = FALSE; /* Get the channel set up ready for a message (MAIL FROM being the next SMTP command to send */ -if ((rc = smtp_setup_conn(&sx, suppress_tls, FALSE)) != OK) +if ((rc = smtp_setup_conn(&sx, suppress_tls)) != OK) return rc; /* If there is a filter command specified for this transport, we can now set it up. This cannot be done until the identify of the host is known. */ -if (tblock->filter_command != NULL) +if (tblock->filter_command) { BOOL rc; uschar fbuf[64]; @@ -2643,11 +2628,11 @@ sx.completed_addr = FALSE; switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield)) { - case 0: break; - case -1: goto RESPONSE_FAILED; - case -2: goto END_OFF; - case -3: goto SEND_QUIT; - default: goto SEND_FAILED; + case 0: break; + case -1: case -2: goto RESPONSE_FAILED; + case -3: goto END_OFF; + case -4: goto SEND_QUIT; + default: goto SEND_FAILED; } /* If we are an MUA wrapper, abort if any RCPTs were rejected, either @@ -2737,17 +2722,10 @@ else tctx.check_string = tctx.escape_string = NULL; tctx.options |= topt_use_bdat; tctx.chunk_cb = smtp_chunk_cmd_callback; - tctx.inblock = &sx.inblock; - tctx.outblock = &sx.outblock; - tctx.host = host; - tctx.first_addr = sx.first_addr; - tctx.sync_addr = &sx.sync_addr; - tctx.pending_MAIL = sx.pending_MAIL; - tctx.pending_BDAT = FALSE; - tctx.good_RCPT = sx.ok; - tctx.completed_address = &sx.completed_addr; - tctx.cmd_count = 0; - tctx.buffer = sx.buffer; + sx.pending_BDAT = FALSE; + sx.good_RCPT = sx.ok; + sx.cmd_count = 0; + tctx.smtp_context = &sx; } else tctx.options |= topt_end_dot; @@ -2798,10 +2776,10 @@ else smtp_command = US"end of data"; - if (sx.peer_offered & PEER_OFFERED_CHUNKING && tctx.cmd_count > 1) + if (sx.peer_offered & PEER_OFFERED_CHUNKING && sx.cmd_count > 1) { /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */ - switch(sync_responses(&sx, tctx.cmd_count-1, 0)) + switch(sync_responses(&sx, sx.cmd_count-1, 0)) { case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */ case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */ diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index f158e701c..d89537b40 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -98,15 +98,13 @@ typedef struct { int port; uschar * interface; + BOOL verify:1; BOOL lmtp:1; BOOL smtps:1; BOOL ok:1; - BOOL send_rset:1; - BOOL send_quit:1; BOOL setting_up:1; BOOL esmtp:1; BOOL esmtp_sent:1; - BOOL pending_MAIL:1; #ifndef DISABLE_PRDR BOOL prdr_active:1; #endif @@ -118,9 +116,15 @@ typedef struct { BOOL dane:1; BOOL dane_required:1; #endif - BOOL completed_addr; /* pointer to this used by BDAT callback */ + BOOL pending_MAIL:1; + BOOL pending_BDAT:1; + BOOL good_RCPT:1; + BOOL completed_addr:1; + BOOL send_rset:1; + BOOL send_quit:1; int max_rcpt; + int cmd_count; uschar peer_offered; uschar * igquotstr; @@ -144,7 +148,8 @@ typedef struct { smtp_transport_options_block * ob; } smtp_context; -extern int smtp_setup_conn(smtp_context *, BOOL, BOOL); +extern int smtp_setup_conn(smtp_context *, BOOL); +extern int smtp_write_mail_and_rcpt_cmds(smtp_context *, int *); /* Data for reading the private options. */ diff --git a/src/src/verify.c b/src/src/verify.c index e1f81dd5b..f263060be 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -440,9 +440,8 @@ if (addr->transport == cutthrough.addr.transport) Ustrcpy(resp, US"connection dropped"); addr->message = - string_sprintf("response to \"%s\" from %s [%s] was: %s", - big_buffer, host->name, host->address, - string_printing(resp)); + string_sprintf("response to \"%s\" was: %s", + big_buffer, string_printing(resp)); addr->user_message = string_sprintf("Callout verification failed:\n%s", resp); @@ -624,7 +623,6 @@ coding means skipping this whole loop and doing the append separately. */ int port = 25; uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ smtp_context sx; - uschar responsebuffer[4096]; if (!host->address) { @@ -643,7 +641,7 @@ coding means skipping this whole loop and doing the append separately. */ /* Set IPv4 or IPv6 */ - host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6; + host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET; /* Expand and interpret the interface and port strings. The latter will not be used if there is a host-specific port (e.g. from a manualroute router). @@ -671,6 +669,7 @@ coding means skipping this whole loop and doing the append separately. */ sx.interface = interface; sx.helo_data = tf->helo_data; sx.tblock = addr->transport; + sx.verify = TRUE; tls_retry_connection: /* Set the address state so that errors are recorded in it */ @@ -683,7 +682,7 @@ tls_retry_connection: SMTP command to send. If we tried TLS but it failed, try again without if permitted */ - if ( (yield = smtp_setup_conn(&sx, FALSE, TRUE)) == DEFER + if ( (yield = smtp_setup_conn(&sx, FALSE)) == DEFER && addr->basic_errno == ERRNO_TLSFAILURE && ob->tls_tempfail_tryclear && verify_check_given_host(&ob->hosts_require_tls, host) != OK @@ -692,12 +691,11 @@ tls_retry_connection: log_write(0, LOG_MAIN, "TLS session failure:" " callout unencrypted to %s [%s] (not in hosts_require_tls)", host->name, host->address); - yield = smtp_setup_conn(&sx, TRUE, TRUE); + addr->transport_return = PENDING_DEFER; + yield = smtp_setup_conn(&sx, TRUE); } if (yield != OK) { - if (addr->message) addr->message = string_sprintf("%s [%s] %s", - host->name, host->address, addr->message); errno = addr->basic_errno; transport_name = NULL; deliver_host = deliver_host_address = NULL; @@ -723,68 +721,22 @@ tls_retry_connection: addr->authenticator = client_authenticator; addr->auth_id = client_authenticated_id; - /* Build a mail-AUTH string (re-using responsebuffer for convenience */ + sx.from_addr = from_address; + sx.first_addr = sx.sync_addr = addr; + sx.ok = FALSE; /*XXX these 3 last might not be needed for verify? */ + sx.send_rset = TRUE; + sx.completed_addr = FALSE; - done = - !smtp_mail_auth_str(responsebuffer, sizeof(responsebuffer), addr, ob); + new_domain_record.result = + old_domain_cache_result == ccache_reject_mfnull + ? ccache_reject_mfnull : ccache_accept; - if (done) - { - addr->auth_sndr = client_authenticated_sender; - - /* Send the MAIL command */ - - done = - (smtp_write_command(&sx.outblock, FALSE, -#ifdef SUPPORT_I18N - addr->prop.utf8_msg && !addr->prop.utf8_downcvt - ? "MAIL FROM:<%s>%s%s SMTPUTF8\r\n" - : -#endif - "MAIL FROM:<%s>%s%s\r\n", - from_address, - responsebuffer, - options & vopt_is_recipient && sx.peer_offered & PEER_OFFERED_SIZE - ? string_sprintf(" SIZE=%d", message_size + ob->size_addition) - : US"" - ) >= 0) - - && smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); - } - - deliver_host = deliver_host_address = NULL; - deliver_domain = save_deliver_domain; + /* Do the random local part check first. Temporarily replace the recipient + with the "random" value */ - /* If the host does not accept MAIL FROM:<>, arrange to cache this - information, but again, don't record anything for an I/O error or a defer. Do - not cache rejections of MAIL when a non-empty sender has been used, because - that blocks the whole domain for all senders. */ - - if (!done) - { - *failure_ptr = US"mail"; /* At or before MAIL */ - if (errno == 0 && responsebuffer[0] == '5') - { - setflag(addr, af_verify_nsfail); - if (from_address[0] == 0) - new_domain_record.result = ccache_reject_mfnull; - } - } - - /* Otherwise, proceed to check a "random" address (if required), then the - given address, and the postmaster address (if required). Between each check, - issue RSET, because some servers accept only one recipient after MAIL - FROM:<>. - - Before doing this, set the result in the domain cache record to "accept", - unless its previous value was ccache_reject_mfnull. In that case, the domain - rejects MAIL FROM:<> and we want to continue to remember that. When that is - the case, we have got here only in the case of a recipient verification with - a non-null sender. */ - - else + if (random_local_part) { + uschar * main_address = addr->address; const uschar * rcpt_domain = addr->domain; #ifdef SUPPORT_I18N @@ -802,64 +754,31 @@ tls_retry_connection: } #endif - new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull - ? ccache_reject_mfnull : ccache_accept; - - /* Do the random local part check first */ + /* This would be ok for 1st rcpt of a cutthrough (XXX do we have a count?) , but no way to + handle a subsequent because of the RSET. So refuse to support any. */ + cancel_cutthrough_connection("random-recipient"); - if (random_local_part) - { - uschar randombuffer[1024]; - BOOL random_ok = - smtp_write_command(&sx.outblock, FALSE, - "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part, - rcpt_domain) >= 0 && - smtp_read_response(&sx.inblock, randombuffer, - sizeof(randombuffer), '2', callout); - - /* Remember when we last did a random test */ - - new_domain_record.random_stamp = time(NULL); - - /* If accepted, we aren't going to do any further tests below. */ - - if (random_ok) - new_domain_record.random_result = ccache_accept; - - /* Otherwise, cache a real negative response, and get back to the right - state to send RCPT. Unless there's some problem such as a dropped - connection, we expect to succeed, because the commands succeeded above. - However, some servers drop the connection after responding to an - invalid recipient, so on (any) error we drop and remake the connection. - */ - - else if (errno == 0) - { - /* This would be ok for 1st rcpt a cutthrough, but no way to - handle a subsequent. So refuse to support any */ - cancel_cutthrough_connection("random-recipient"); - - if (randombuffer[0] == '5') - new_domain_record.random_result = ccache_reject; + addr->address = string_sprintf("%s@%.1000s", + random_local_part, rcpt_domain); + done = FALSE; + if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0) + switch(addr->transport_return) + { + case PENDING_OK: + new_domain_record.random_result = ccache_accept; + break; + case FAIL: + new_domain_record.random_result = ccache_reject; - done = - smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), - '2', callout) && + /* Between each check, issue RSET, because some servers accept only + one recipient after MAIL FROM:<>. */ - smtp_write_command(&sx.outblock, FALSE, -#ifdef SUPPORT_I18N - addr->prop.utf8_msg && !addr->prop.utf8_downcvt - ? "MAIL FROM:<%s> SMTPUTF8\r\n" - : -#endif - "MAIL FROM:<%s>\r\n", - from_address) >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); + if ((done = + smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 && + smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), + '2', callout))) + break; - if (!done) - { HDEBUG(D_acl|D_v) debug_printf("problem after random/rset/mfrom; reopen conn\n"); random_local_part = NULL; @@ -868,113 +787,144 @@ tls_retry_connection: #endif HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP(close)>>\n"); (void)close(sx.inblock.sock); + sx.inblock.sock = sx.outblock.sock = -1; #ifndef DISABLE_EVENT (void) event_raise(addr->transport->event_action, US"tcp:close", NULL); #endif goto tls_retry_connection; - } - } - else done = FALSE; /* Some timeout/connection problem */ - } /* Random check */ + } - /* If the host is accepting all local parts, as determined by the "random" - check, we don't need to waste time doing any further checking. */ + /* If accepted, we aren't going to do any further tests below. + Otherwise, cache a real negative response, and get back to the right + state to send RCPT. Unless there's some problem such as a dropped + connection, we expect to succeed, because the commands succeeded above. + However, some servers drop the connection after responding to an + invalid recipient, so on (any) error we drop and remake the connection. + + XXX could we add another flag to the context, and have the common + code emit the RSET too? Even pipelined after the RCPT... + Then the main-verify call could use it if there's to be a subsequent + postmaster-verify. + The sync_responses() would need to be taught about it and we'd + need another return code filtering out to here. + + Remember when we last did a random test + */ - if (new_domain_record.random_result != ccache_accept && done) - { - /* Get the rcpt_include_affixes flag from the transport if there is one, - but assume FALSE if there is not. */ + new_domain_record.random_stamp = time(NULL); - uschar * rcpt = transport_rcpt_address(addr, - addr->transport ? addr->transport->rcpt_include_affixes : FALSE); + /* Re-setup for main verify, or for the error message when failing */ + addr->address = main_address; + addr->transport_return = PENDING_DEFER; + sx.first_addr = sx.sync_addr = addr; + sx.ok = FALSE; + sx.send_rset = TRUE; + sx.completed_addr = FALSE; + } + else + done = TRUE; -#ifdef SUPPORT_I18N - /*XXX should the conversion be moved into transport_rcpt_address() ? */ - if ( testflag(addr, af_utf8_downcvt) - && !(rcpt = string_address_utf8_to_alabel(rcpt, NULL)) - ) - { - errno = ERRNO_EXPANDFAIL; - *failure_ptr = US"recipient"; - done = FALSE; - } - else -#endif + /* Main verify. If the host is accepting all local parts, as determined + by the "random" check, we don't need to waste time doing any further + checking. */ - done = - smtp_write_command(&sx.outblock, FALSE, "RCPT TO:<%.1000s>\r\n", - rcpt) >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), - '2', callout); + if (done) + { + done = FALSE; + switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield)) + { + case 0: switch(addr->transport_return) /* ok so far */ + { + case PENDING_OK: done = TRUE; + new_address_record.result = ccache_accept; + break; + case FAIL: done = TRUE; + yield = FAIL; + *failure_ptr = US"recipient"; + new_address_record.result = ccache_reject; + break; + default: break; + } + break; + + case -1: /* MAIL response error */ + *failure_ptr = US"mail"; + if (errno == 0 && sx.buffer[0] == '5') + { + setflag(addr, af_verify_nsfail); + if (from_address[0] == 0) + new_domain_record.result = ccache_reject_mfnull; + } + break; + /* non-MAIL read i/o error */ + /* non-MAIL response timeout */ + /* internal error; channel still usable */ + default: break; /* transmit failed */ + } + } - if (done) - new_address_record.result = ccache_accept; - else if (errno == 0 && responsebuffer[0] == '5') - { - *failure_ptr = US"recipient"; - new_address_record.result = ccache_reject; - } + addr->auth_sndr = client_authenticated_sender; - /* Do postmaster check if requested; if a full check is required, we - check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */ + deliver_host = deliver_host_address = NULL; + deliver_domain = save_deliver_domain; - if (done && pm_mailfrom) - { - /* Could possibly shift before main verify, just above, and be ok - for cutthrough. But no way to handle a subsequent rcpt, so just - refuse any */ - cancel_cutthrough_connection("postmaster verify"); - HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n"); - - done = - smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) && - - smtp_write_command(&sx.outblock, FALSE, - "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) && - - /* First try using the current domain */ - - (( - smtp_write_command(&sx.outblock, FALSE, - "RCPT TO:<postmaster@%.1000s>\r\n", rcpt_domain) >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) - ) - - || - - /* If that doesn't work, and a full check is requested, - try without the domain. */ - - ( - (options & vopt_callout_fullpm) != 0 && - smtp_write_command(&sx.outblock, FALSE, - "RCPT TO:<postmaster>\r\n") >= 0 && - smtp_read_response(&sx.inblock, responsebuffer, - sizeof(responsebuffer), '2', callout) - )); - - /* Sort out the cache record */ - - new_domain_record.postmaster_stamp = time(NULL); - - if (done) - new_domain_record.postmaster_result = ccache_accept; - else if (errno == 0 && responsebuffer[0] == '5') - { - *failure_ptr = US"postmaster"; - setflag(addr, af_verify_pmfail); - new_domain_record.postmaster_result = ccache_reject; - } - } - } /* Random not accepted */ - } /* MAIL FROM: accepted */ + /* Do postmaster check if requested; if a full check is required, we + check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */ + if (done && pm_mailfrom) + { + /* Could possibly shift before main verify, just above, and be ok + for cutthrough. But no way to handle a subsequent rcpt, so just + refuse any */ + cancel_cutthrough_connection("postmaster verify"); + HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n"); + + done = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 + && smtp_read_response(&sx.inblock, sx.buffer, + sizeof(sx.buffer), '2', callout); + + if (done) + { + uschar * main_address = addr->address; + + /*XXX oops, affixes */ + addr->address = string_sprintf("postmaster@%.1000s", addr->domain); + addr->transport_return = PENDING_DEFER; + + sx.from_addr = pm_mailfrom; + sx.first_addr = sx.sync_addr = addr; + sx.ok = FALSE; + sx.send_rset = TRUE; + sx.completed_addr = FALSE; + + if( smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0 + && addr->transport_return == PENDING_OK + ) + done = TRUE; + else + done = (options & vopt_callout_fullpm) != 0 + && smtp_write_command(&sx.outblock, FALSE, + "RCPT TO:<postmaster>\r\n") >= 0 + && smtp_read_response(&sx.inblock, sx.buffer, + sizeof(sx.buffer), '2', callout); + + /* Sort out the cache record */ + + new_domain_record.postmaster_stamp = time(NULL); + + if (done) + new_domain_record.postmaster_result = ccache_accept; + else if (errno == 0 && sx.buffer[0] == '5') + { + *failure_ptr = US"postmaster"; + setflag(addr, af_verify_pmfail); + new_domain_record.postmaster_result = ccache_reject; + } + + addr->address = main_address; + } + } /* For any failure of the main check, other than a negative response, we just close the connection and carry on. We can identify a negative response by the fact that errno is zero. For I/O errors it will be non-zero @@ -986,7 +936,7 @@ tls_retry_connection: is not to be widely broadcast. */ no_conn: - if (!done) switch(errno) + switch(errno) { case ETIMEDOUT: HDEBUG(D_verify) debug_printf("SMTP timeout\n"); @@ -999,8 +949,7 @@ no_conn: extern int acl_where; /* src/acl.c */ errno = 0; addr->message = string_sprintf( - "response to \"EHLO\" from %s [%s] did not include SMTPUTF8", - host->name, host->address); + "response to \"EHLO\" did not include SMTPUTF8"); addr->user_message = acl_where == ACL_WHERE_RCPT ? US"533 no support for internationalised mailbox name" : US"550 mailbox unavailable"; @@ -1014,21 +963,25 @@ no_conn: break; case 0: - if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped"); + if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped"); - addr->message = - string_sprintf("response to \"%s\" from %s [%s] was: %s", - big_buffer, host->name, host->address, - string_printing(responsebuffer)); + /*XXX test here is ugly; seem to have a split of responsibility for + building this message. Need to reationalise. Where is it done + before here, and when not? + Not == 5xx resp to MAIL on main-verify + */ + if (!addr->message) addr->message = + string_sprintf("response to \"%s\" was: %s", + big_buffer, string_printing(sx.buffer)); addr->user_message = options & vopt_is_recipient - ? string_sprintf("Callout verification failed:\n%s", responsebuffer) + ? string_sprintf("Callout verification failed:\n%s", sx.buffer) : string_sprintf("Called: %s\nSent: %s\nResponse: %s", - host->address, big_buffer, responsebuffer); + host->address, big_buffer, sx.buffer); /* Hard rejection ends the process */ - if (responsebuffer[0] == '5') /* Address rejected */ + if (sx.buffer[0] == '5') /* Address rejected */ { yield = FAIL; done = TRUE; @@ -1081,7 +1034,7 @@ no_conn: (void) smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n"); /* Wait a short time for response, and discard it */ - smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer), + smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2', 1); } @@ -1099,6 +1052,9 @@ no_conn: } } + if (!done || yield != OK) + addr->message = string_sprintf("%s [%s] : %s", host->name, host->address, + addr->message); } /* Loop through all hosts, while !done */ } |