From d9c3c8ed8b17e693befc72912bbb1ef737027d5a Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Tue, 10 Jan 2017 19:22:04 +0000 Subject: [PATCH] Split out smtp_write_mail_and_rcpt_cmds() from smtp_deliver() --- src/src/transports/smtp.c | 356 ++++++++++++++++++++------------------ src/src/transports/smtp.h | 6 + 2 files changed, 196 insertions(+), 166 deletions(-) diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 1ce0cbe11..61e2cc4ab 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -2363,6 +2363,166 @@ 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 + */ + +int +smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield) +{ +address_item * addr; +int address_count; +int rc; + +if (build_mailcmd_options(sx, sx->first_addr) != OK) + { + *yield = ERROR; + return -3; + } + +/* 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 +at any point, for when the buffer fills up, so we write it totally generally. +When PIPELINING is off, each command written reports that it has flushed the +buffer. */ + +sx->pending_MAIL = TRUE; /* The block starts with MAIL */ + + { + uschar * s = sx->from_addr; +#ifdef SUPPORT_I18N + uschar * errstr = NULL; + + /* If we must downconvert, do the from-address here. Remember we had to + for the to-addresses (done below), and also (ugly) for re-doing when building + the delivery log line. */ + + if ( sx->addrlist->prop.utf8_msg + && (sx->addrlist->prop.utf8_downcvt || !(sx->peer_offered & PEER_OFFERED_UTF8)) + ) + { + if (s = string_address_utf8_to_alabel(s, &errstr), errstr) + { + set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE); + *yield = ERROR; + return -3; + } + setflag(sx->addrlist, af_utf8_downcvt); + } +#endif + + rc = smtp_write_command(&sx->outblock, pipelining_active, + "MAIL FROM:<%s>%s\r\n", s, sx->buffer); + } + +mail_command = string_copy(big_buffer); /* Save for later error message */ + +switch(rc) + { + case -1: /* Transmission error */ + return -4; + + case +1: /* Block was sent */ + if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2', + sx->ob->command_timeout)) + { + if (errno == 0 && sx->buffer[0] == '4') + { + errno = ERRNO_MAIL4XX; + sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8; + } + return -1; + } + sx->pending_MAIL = FALSE; + break; + } + +/* Pass over all the relevant recipient addresses for this host, which are the +ones that have status PENDING_DEFER. If we are using PIPELINING, we can send +several before we have to read the responses for those seen so far. This +checking is done by a subroutine because it also needs to be done at the end. +Send only up to max_rcpt addresses at a time, leaving next_addr pointing to +the next one if not all are sent. + +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. */ + +for (addr = sx->first_addr, address_count = 0; + addr && address_count < sx->max_rcpt; + addr = addr->next) if (addr->transport_return == PENDING_DEFER) + { + int count; + BOOL no_flush; + uschar * rcpt_addr; + + addr->dsn_aware = sx->peer_offered & PEER_OFFERED_DSN + ? dsn_support_yes : dsn_support_no; + + address_count++; + no_flush = pipelining_active && (!mua_wrapper || addr->next); + + build_rcptcmd_options(sx, addr); + + /* Now send the RCPT command, and process outstanding responses when + necessary. After a timeout on RCPT, we just end the function, leaving the + yield as OK, because this error can often mean that there is a problem with + just one address, so we don't want to delay the host. */ + + rcpt_addr = transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes); + +#ifdef SUPPORT_I18N + if ( testflag(sx->addrlist, af_utf8_downcvt) + && !(rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, NULL)) + ) + { + /*XXX could we use a per-address errstr here? Not fail the whole send? */ + errno = ERRNO_EXPANDFAIL; + return -4; /*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) + { + switch(sync_responses(sx->first_addr, sx->tblock->rcpt_include_affixes, + &sx->sync_addr, sx->host, count, sx->ob->address_retry_include_sender, + sx->pending_MAIL, 0, &sx->inblock, sx->ob->command_timeout, sx->buffer, + sizeof(sx->buffer))) + { + case 3: sx->ok = TRUE; /* 2xx & 5xx => OK & progress made */ + case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */ + break; + + case 1: sx->ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ + if (!sx->lmtp) /* can't tell about progress yet */ + sx->completed_addr = TRUE; + 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 */ + } + sx->pending_MAIL = FALSE; /* Dealt with MAIL */ + } + } /* Loop for next address */ + +sx->next_addr = addr; +return 0; +} + + /************************************************* * Deliver address list to given host * *************************************************/ @@ -2413,17 +2573,11 @@ smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port, BOOL *message_defer, BOOL suppress_tls) { address_item *addr; -address_item *sync_addr; -address_item *first_addr = addrlist; int yield = OK; -int address_count; int save_errno; int rc; time_t start_delivery_time = time(NULL); -BOOL completed_address = FALSE; - - BOOL pass_message = FALSE; uschar *message = NULL; uschar new_message_id[MESSAGE_ID_LENGTH + 1]; @@ -2492,154 +2646,24 @@ code was to use a goto to jump back to this point when there is another transaction to handle. */ SEND_MESSAGE: -sync_addr = first_addr; +sx.from_addr = return_path; +sx.first_addr = sx.sync_addr = addrlist; sx.ok = FALSE; sx.send_rset = TRUE; -completed_address = FALSE; +sx.completed_addr = FALSE; /* Initiate a message transfer. */ -if (build_mailcmd_options(&sx, first_addr) != OK) - { - yield = ERROR; - goto SEND_QUIT; - } - -/* 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 -at any point, for when the buffer fills up, so we write it totally generally. -When PIPELINING is off, each command written reports that it has flushed the -buffer. */ - -sx.pending_MAIL = TRUE; /* The block starts with MAIL */ - - { - uschar * s = return_path; -#ifdef SUPPORT_I18N - uschar * errstr = NULL; - - /* If we must downconvert, do the from-address here. Remember we had to - for the to-addresses (done below), and also (ugly) for re-doing when building - the delivery log line. */ - - if ( addrlist->prop.utf8_msg - && (addrlist->prop.utf8_downcvt || !(sx.peer_offered & PEER_OFFERED_UTF8)) - ) - { - if (s = string_address_utf8_to_alabel(return_path, &errstr), errstr) - { - set_errno_nohost(addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE); - yield = ERROR; - goto SEND_QUIT; - } - setflag(addrlist, af_utf8_downcvt); - } -#endif - - rc = smtp_write_command(&sx.outblock, pipelining_active, - "MAIL FROM:<%s>%s\r\n", s, sx.buffer); - } - -mail_command = string_copy(big_buffer); /* Save for later error message */ - -switch(rc) +switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield)) { - case -1: /* Transmission error */ - goto SEND_FAILED; - - case +1: /* Block was sent */ - if (!smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2', - sx.ob->command_timeout)) - { - if (errno == 0 && sx.buffer[0] == '4') - { - errno = ERRNO_MAIL4XX; - addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8; - } - goto RESPONSE_FAILED; - } - sx.pending_MAIL = FALSE; - break; + case 0: break; + case -1: goto RESPONSE_FAILED; + case -2: goto END_OFF; + case -3: goto SEND_QUIT; + default: goto SEND_FAILED; } -/* Pass over all the relevant recipient addresses for this host, which are the -ones that have status PENDING_DEFER. If we are using PIPELINING, we can send -several before we have to read the responses for those seen so far. This -checking is done by a subroutine because it also needs to be done at the end. -Send only up to max_rcpt addresses at a time, leaving first_addr pointing to -the next one if not all are sent. - -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. */ - -for (addr = first_addr, address_count = 0; - addr && address_count < sx.max_rcpt; - addr = addr->next) if (addr->transport_return == PENDING_DEFER) - { - int count; - BOOL no_flush; - uschar * rcpt_addr; - - addr->dsn_aware = sx.peer_offered & PEER_OFFERED_DSN - ? dsn_support_yes : dsn_support_no; - - address_count++; - no_flush = pipelining_active && (!mua_wrapper || addr->next); - - build_rcptcmd_options(&sx, addr); - - /* Now send the RCPT command, and process outstanding responses when - necessary. After a timeout on RCPT, we just end the function, leaving the - yield as OK, because this error can often mean that there is a problem with - just one address, so we don't want to delay the host. */ - - rcpt_addr = transport_rcpt_address(addr, tblock->rcpt_include_affixes); - -#ifdef SUPPORT_I18N - if ( testflag(addrlist, af_utf8_downcvt) - && !(rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, NULL)) - ) - { - /*XXX could we use a per-address errstr here? Not fail the whole send? */ - errno = ERRNO_EXPANDFAIL; - goto SEND_FAILED; - } -#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) goto SEND_FAILED; - if (count > 0) - { - switch(sync_responses(first_addr, tblock->rcpt_include_affixes, - &sync_addr, host, count, sx.ob->address_retry_include_sender, - sx.pending_MAIL, 0, &sx.inblock, sx.ob->command_timeout, sx.buffer, - sizeof(sx.buffer))) - { - case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */ - case 2: completed_address = TRUE; /* 5xx (only) => progress made */ - break; - - case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ - if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */ - case 0: /* No 2xx or 5xx, but no probs */ - break; - - case -1: goto END_OFF; /* Timeout on RCPT */ - default: goto RESPONSE_FAILED; /* I/O error, or any MAIL error */ - } - sx.pending_MAIL = FALSE; /* Dealt with MAIL */ - } - } /* Loop for next address */ - -/*XXX potential break point for verify-callouts here. The MAIL and -RCPT handling is relevant there */ - /* If we are an MUA wrapper, abort if any RCPTs were rejected, either permanently or temporarily. We should have flushed and synced after the last RCPT. */ @@ -2647,7 +2671,7 @@ RCPT. */ if (mua_wrapper) { address_item *badaddr; - for (badaddr = first_addr; badaddr; badaddr = badaddr->next) + for (badaddr = sx.first_addr; badaddr; badaddr = badaddr->next) if (badaddr->transport_return != PENDING_OK) { /*XXX could we find a better errno than 0 here? */ @@ -2672,16 +2696,16 @@ if ( !(sx.peer_offered & PEER_OFFERED_CHUNKING) int count = smtp_write_command(&sx.outblock, FALSE, "DATA\r\n"); if (count < 0) goto SEND_FAILED; - switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr, + switch(sync_responses(sx.first_addr, tblock->rcpt_include_affixes, &sx.sync_addr, host, count, sx.ob->address_retry_include_sender, sx.pending_MAIL, sx.ok ? +1 : -1, &sx.inblock, sx.ob->command_timeout, sx.buffer, sizeof(sx.buffer))) { case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */ - case 2: completed_address = TRUE; /* 5xx (only) => progress made */ + case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */ break; case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ - if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */ + if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */ case 0: break; /* No 2xx or 5xx, but no probs */ case -1: goto END_OFF; /* Timeout on RCPT */ @@ -2701,7 +2725,7 @@ well as body. Set the appropriate timeout value to be used for each chunk. if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok) { /* Save the first address of the next batch. */ - first_addr = addr; + sx.first_addr = sx.next_addr; sx.ok = TRUE; } @@ -2732,12 +2756,12 @@ else tctx.inblock = &sx.inblock; tctx.outblock = &sx.outblock; tctx.host = host; - tctx.first_addr = first_addr; - tctx.sync_addr = &sync_addr; + 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 = &completed_address; + tctx.completed_address = &sx.completed_addr; tctx.cmd_count = 0; tctx.buffer = sx.buffer; } @@ -2745,7 +2769,7 @@ else tctx.options |= topt_end_dot; /* Save the first address of the next batch. */ - first_addr = addr; + sx.first_addr = sx.next_addr; /* Responses from CHUNKING commands go in buffer. Otherwise, there has not been a response. */ @@ -2793,17 +2817,17 @@ else if (sx.peer_offered & PEER_OFFERED_CHUNKING && tctx.cmd_count > 1) { /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */ - switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr, + switch(sync_responses(sx.first_addr, tblock->rcpt_include_affixes, &sx.sync_addr, host, tctx.cmd_count-1, sx.ob->address_retry_include_sender, sx.pending_MAIL, 0, &sx.inblock, sx.ob->command_timeout, sx.buffer, sizeof(sx.buffer))) { case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */ - case 2: completed_address = TRUE; /* 5xx (only) => progress made */ + case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */ break; case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ - if (!sx.lmtp) completed_address = TRUE; /* can't tell about progress yet */ + if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */ case 0: break; /* No 2xx or 5xx, but no probs */ case -1: goto END_OFF; /* Timeout on RCPT */ @@ -2887,7 +2911,7 @@ else /* Process all transported addresses - for LMTP or PRDR, read a status for each one. */ - for (addr = addrlist; addr != first_addr; addr = addr->next) + for (addr = addrlist; addr != sx.first_addr; addr = addr->next) { if (addr->transport_return != PENDING_OK) continue; @@ -2928,7 +2952,7 @@ else } continue; } - completed_address = TRUE; /* NOW we can set this flag */ + sx.completed_addr = TRUE; /* NOW we can set this flag */ if (LOGGING(smtp_confirmation)) { const uschar *s = string_printing(sx.buffer); @@ -2987,14 +3011,14 @@ else errno = ERRNO_DATA4XX; addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8; } - for (addr = addrlist; addr != first_addr; addr = addr->next) + for (addr = addrlist; addr != sx.first_addr; addr = addr->next) if (sx.buffer[0] == '5' || addr->transport_return == OK) addr->transport_return = PENDING_OK; /* allow set_errno action */ goto RESPONSE_FAILED; } /* Update the journal, or setup retry. */ - for (addr = addrlist; addr != first_addr; addr = addr->next) + for (addr = addrlist; addr != sx.first_addr; addr = addr->next) if (addr->transport_return == OK) { if (testflag(addr, af_homonym)) @@ -3178,9 +3202,9 @@ hosts_nopass_tls. */ DEBUG(D_transport) debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d " "yield=%d first_address is %sNULL\n", sx.ok, sx.send_quit, - sx.send_rset, continue_more, yield, first_addr ? "not " : ""); + sx.send_rset, continue_more, yield, sx.first_addr ? "not " : ""); -if (completed_address && sx.ok && sx.send_quit) +if (sx.completed_addr && sx.ok && sx.send_quit) { BOOL more; smtp_compare_t t_compare; @@ -3188,7 +3212,7 @@ if (completed_address && sx.ok && sx.send_quit) t_compare.tblock = tblock; t_compare.current_sender_address = sender_address; - if ( first_addr != NULL + if ( sx.first_addr != NULL || continue_more || ( ( tls_out.active < 0 || verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK @@ -3226,7 +3250,7 @@ if (completed_address && sx.ok && sx.send_quit) if (sx.ok) { - if (first_addr != NULL) /* More addresses still to be sent */ + if (sx.first_addr != NULL) /* More addresses still to be sent */ { /* in this run of the transport */ continue_sequence++; /* Causes * in logging */ goto SEND_MESSAGE; @@ -3264,7 +3288,7 @@ propagate it from the initial /* If RSET failed and there are addresses left, they get deferred. */ - else set_errno(first_addr, errno, msg, DEFER, FALSE, host + else set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host #ifdef EXPERIMENTAL_DSN_INFO , sx.smtp_greeting, sx.helo_response #endif diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index e12c0ab94..f158e701c 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -91,6 +91,7 @@ typedef struct { /* smtp connect context */ typedef struct { + uschar * from_addr; address_item * addrlist; host_item * host; int host_af; @@ -117,6 +118,7 @@ typedef struct { BOOL dane:1; BOOL dane_required:1; #endif + BOOL completed_addr; /* pointer to this used by BDAT callback */ int max_rcpt; @@ -128,6 +130,10 @@ typedef struct { uschar * helo_response; #endif + address_item * first_addr; + address_item * next_addr; + address_item * sync_addr; + smtp_inblock inblock; smtp_outblock outblock; uschar buffer[DELIVER_BUFFER_SIZE]; -- 2.30.2