From 6d5c916cc5720591335fea53242dd6b97ea56fe3 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Thu, 28 Jul 2016 22:41:17 +0100 Subject: [PATCH] Callback into smtp transport for BDAT commands --- src/src/deliver.c | 21 ++--- src/src/functions.h | 5 +- src/src/smtp_in.c | 10 ++- src/src/smtp_out.c | 74 +++++++++--------- src/src/structs.h | 27 +++++-- src/src/transport.c | 104 ++++++++++++------------- src/src/transports/autoreply.c | 3 +- src/src/transports/lmtp.c | 3 +- src/src/transports/pipe.c | 2 +- src/src/transports/smtp.c | 137 ++++++++++++++++++++++++++++----- src/src/verify.c | 16 ++-- test/confs/0611 | 1 + 12 files changed, 262 insertions(+), 141 deletions(-) diff --git a/src/src/deliver.c b/src/src/deliver.c index 81f9a9aa2..419dee92c 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -6907,7 +6907,7 @@ if (addr_senddsn) FILE *f = fdopen(fd, "wb"); /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ uschar * bound; - transport_ctx tctx; + transport_ctx tctx = {0}; DEBUG(D_deliver) debug_printf("sending error message to: %s\n", sender_address); @@ -6987,7 +6987,6 @@ if (addr_senddsn) /* Write the original email out */ - bzero(&tctx, sizeof(tctx)); tctx.options = topt_add_return_path | topt_no_body; transport_write_message(fileno(f), &tctx, 0); fflush(f); @@ -7445,16 +7444,14 @@ wording. */ transport_filter_argv = NULL; /* Just in case */ return_path = sender_address; /* In case not previously set */ { /* Dummy transport for headers add */ - transport_ctx * tctx = - store_get(sizeof(*tctx) + sizeof(transport_instance)); - transport_instance * tb = (transport_instance *)(tctx+1); + transport_ctx tctx = {0}; + transport_instance tb = {0}; - bzero(tctx, sizeof(*tctx)+sizeof(*tb)); - tctx->tblock = tb; - tctx->options = topt; - tb->add_headers = dsnnotifyhdr; + tctx.tblock = &tb; + tctx.options = topt; + tb.add_headers = dsnnotifyhdr; - transport_write_message(fileno(f), tctx, 0); + transport_write_message(fileno(f), &tctx, 0); } fflush(f); @@ -7771,9 +7768,7 @@ else if (addr_defer != (address_item *)(+1)) FILE *wmf = NULL; FILE *f = fdopen(fd, "wb"); uschar * bound; - transport_ctx tctx; - - bzero(&tctx, sizeof(tctx)); + transport_ctx tctx = {0}; if (warn_message_file) if (!(wmf = Ufopen(warn_message_file, "rb"))) diff --git a/src/src/functions.h b/src/src/functions.h index 27e9ff821..260b365df 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -468,9 +468,8 @@ extern BOOL transport_set_up_command(const uschar ***, uschar *, extern void transport_update_waiting(host_item *, uschar *); extern BOOL transport_write_block(int, uschar *, int); extern BOOL transport_write_string(int, const char *, ...); -extern BOOL transport_headers_send(address_item *, int, transport_instance *, - BOOL (*)(int, uschar *, int, unsigned), - BOOL); +extern BOOL transport_headers_send(int, transport_ctx *, + BOOL (*)(int, transport_ctx *, uschar *, int)); extern BOOL transport_write_message(int, transport_ctx *, int); extern void tree_add_duplicate(uschar *, address_item *); extern void tree_add_nonrecipient(uschar *); diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index d4b3e565a..3144b39ad 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -73,7 +73,6 @@ enum { ETRN_CMD, /* This by analogy with TURN from the RFC */ STARTTLS_CMD, /* Required by the STARTTLS RFC */ TLS_AUTH_CMD, /* auto-command at start of SSL */ - BDAT_CMD, /* Implied by RFC3030 "After all MAIL and..." */ /* This is a dummy to identify the non-sync commands when pipelining */ @@ -83,6 +82,15 @@ enum { MAIL_CMD, RCPT_CMD, RSET_CMD, + /* RFC3030 section 2: "After all MAIL and RCPT responses are collected and + processed the message is sent using a series of BDAT commands" + implies that BDAT should be synchronized. However, we see Google, at least, + sending MAIL,RCPT,BDAT-LAST in a single packet, clearly not waiting for + processing of the RPCT response(s). We shall do the same, and not require + synch for BDAT. */ + + BDAT_CMD, + /* This is a dummy to identify the non-sync commands when not pipelining */ NON_SYNC_CMD_NON_PIPELINING, diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 6b4843175..76181b5f1 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -357,6 +357,7 @@ Arguments: noflush if TRUE, save the command in the output buffer, for pipelining format a format, starting with one of of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT. + If NULL, flush pipeline buffer only. ... data for the format Returns: 0 if command added to pipelining buffer, with nothing transmitted @@ -371,48 +372,51 @@ int count; int rc = 0; va_list ap; -va_start(ap, format); -if (!string_vformat(big_buffer, big_buffer_size, CS format, ap)) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " - "SMTP"); -va_end(ap); -count = Ustrlen(big_buffer); - -if (count > outblock->buffersize) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " - "SMTP"); - -if (count > outblock->buffersize - (outblock->ptr - outblock->buffer)) +if (format) { - rc = outblock->cmd_count; /* flush resets */ - if (!flush_buffer(outblock)) return -1; - } + va_start(ap, format); + if (!string_vformat(big_buffer, big_buffer_size, CS format, ap)) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " + "SMTP"); + va_end(ap); + count = Ustrlen(big_buffer); + + if (count > outblock->buffersize) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " + "SMTP"); + + if (count > outblock->buffersize - (outblock->ptr - outblock->buffer)) + { + rc = outblock->cmd_count; /* flush resets */ + if (!flush_buffer(outblock)) return -1; + } -Ustrncpy(CS outblock->ptr, big_buffer, count); -outblock->ptr += count; -outblock->cmd_count++; -count -= 2; -big_buffer[count] = 0; /* remove \r\n for error message */ + Ustrncpy(CS outblock->ptr, big_buffer, count); + outblock->ptr += count; + outblock->cmd_count++; + count -= 2; + big_buffer[count] = 0; /* remove \r\n for error message */ -/* We want to hide the actual data sent in AUTH transactions from reflections -and logs. While authenticating, a flag is set in the outblock to enable this. -The AUTH command itself gets any data flattened. Other lines are flattened -completely. */ + /* We want to hide the actual data sent in AUTH transactions from reflections + and logs. While authenticating, a flag is set in the outblock to enable this. + The AUTH command itself gets any data flattened. Other lines are flattened + completely. */ -if (outblock->authenticating) - { - uschar *p = big_buffer; - if (Ustrncmp(big_buffer, "AUTH ", 5) == 0) + if (outblock->authenticating) { - p += 5; - while (isspace(*p)) p++; - while (!isspace(*p)) p++; - while (isspace(*p)) p++; + uschar *p = big_buffer; + if (Ustrncmp(big_buffer, "AUTH ", 5) == 0) + { + p += 5; + while (isspace(*p)) p++; + while (!isspace(*p)) p++; + while (isspace(*p)) p++; + } + while (*p != 0) *p++ = '*'; } - while (*p != 0) *p++ = '*'; - } -HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> %s\n", big_buffer); + HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> %s\n", big_buffer); + } if (!noflush) { diff --git a/src/src/structs.h b/src/src/structs.h index 61f8a4169..2a6ca68ab 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -223,14 +223,31 @@ typedef struct transport_info { } transport_info; +/* smtp transport datachunk callback */ + +struct transport_context; +typedef int (*tpt_chunk_cmd_cb)(int fd, struct transport_context * tctx, + unsigned len, BOOL last); + /* Structure for information about a delivery-in-progress */ typedef struct transport_context { - transport_instance * tblock; - struct address_item * addr; - uschar * check_string; - uschar * escape_string; - int options; /* topt_* */ + transport_instance * tblock; /* transport */ + struct address_item * addr; + uschar * check_string; /* string replacement */ + uschar * escape_string; + int options; /* output processing topt_* */ + + /* 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 * completed_address; + int cmd_count; } transport_ctx; diff --git a/src/src/transport.c b/src/src/transport.c index 148c1955f..8c81e8a9e 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -359,9 +359,7 @@ Arguments: fd file descript to write to chunk pointer to data to write len length of data to write - flags bitmap of topt_ flags for processing options - use_crlf terminate lines with CRLF - use_bdat prepend chunks with RFC3030 BDAT header + tctx transport context - processing to be done during output In addition, the static nl_xxx variables must be set as required. @@ -369,7 +367,7 @@ Returns: TRUE on success, FALSE on failure (with errno preserved) */ static BOOL -write_chunk(int fd, uschar *chunk, int len, unsigned flags) +write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len) { uschar *start = chunk; uschar *end = chunk + len; @@ -412,19 +410,23 @@ possible. */ for (ptr = start; ptr < end; ptr++) { - int ch; + int ch, len; /* Flush the buffer if it has reached the threshold - we want to leave enough room for the next uschar, plus a possible extra CR for an LF, plus the escape string. */ -/*XXX CHUNKING: need to prefix write_block with a BDAT cmd. Also possibly -reap a response from a previous BDAT first. NEED a callback into the tpt -for that */ - if (chunk_ptr - deliver_out_buffer > mlen) + /*XXX CHUNKING: probably want to increase DELIVER_OUT_BUFFER_SIZE */ + if ((len = chunk_ptr - deliver_out_buffer) > mlen) { - if (!transport_write_block(fd, deliver_out_buffer, - chunk_ptr - deliver_out_buffer)) + /* If CHUNKING, prefix with BDAT (size) NON-LAST. Also, reap responses + from previous SMTP commands. */ + + if (tctx && tctx->options & topt_use_bdat && tctx->chunk_cb) + if (tctx->chunk_cb(fd, tctx, (unsigned)len, FALSE) != OK) + return FALSE; + + if (!transport_write_block(fd, deliver_out_buffer, len)) return FALSE; chunk_ptr = deliver_out_buffer; } @@ -435,7 +437,7 @@ for that */ /* Insert CR before NL if required */ - if (flags & topt_use_crlf) *chunk_ptr++ = '\r'; + if (tctx && tctx->options & topt_use_crlf) *chunk_ptr++ = '\r'; *chunk_ptr++ = '\n'; transport_newlines++; @@ -552,14 +554,14 @@ Arguments: pdlist address of anchor of the list of processed addresses first TRUE if this is the first address; set it FALSE afterwards fd the file descriptor to write to - flags to be passed on to write_chunk() + tctx transport context - processing to be done during output Returns: FALSE if writing failed */ static BOOL write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist, - BOOL *first, int fd, unsigned flags) + BOOL *first, int fd, transport_ctx * tctx) { address_item *pp; struct aci *ppp; @@ -581,7 +583,7 @@ for (pp = p;; pp = pp->parent) address_item *dup; for (dup = addr_duplicate; dup; dup = dup->next) if (dup->dupof == pp) /* a dup of our address */ - if (!write_env_to(dup, pplist, pdlist, first, fd, flags)) + if (!write_env_to(dup, pplist, pdlist, first, fd, tctx)) return FALSE; if (!pp->parent) break; } @@ -598,9 +600,9 @@ ppp->next = *pplist; *pplist = ppp; ppp->ptr = pp; -if (!*first && !write_chunk(fd, US",\n ", 3, flags)) return FALSE; +if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE; *first = FALSE; -return write_chunk(fd, pp->address, Ustrlen(pp->address), flags); +return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address)); } @@ -616,19 +618,23 @@ Arguments: only the first address is used fd file descriptor to write the message to sendfn function for output (transport or verify) - use_crlf turn NL into CR LF + wck_flags + use_crlf turn NL into CR LF + use_bdat callback before chunk flush rewrite_rules chain of header rewriting rules rewrite_existflags flags for the rewriting rules + chunk_cb transport callback function for data-chunk commands Returns: TRUE on success; FALSE on failure. */ BOOL -transport_headers_send(address_item *addr, int fd, transport_instance * tblock, - BOOL (*sendfn)(int fd, uschar * s, int len, unsigned options), - BOOL use_crlf) +transport_headers_send(int fd, transport_ctx * tctx, + BOOL (*sendfn)(int fd, transport_ctx * tctx, uschar * s, int len)) { header_line *h; const uschar *list; +transport_instance * tblock = tctx ? tctx->tblock : NULL; +address_item * addr = tctx ? tctx->addr : NULL; /* Then the message's headers. Don't write any that are flagged as "old"; that means they were rewritten, or are a record of envelope rewriting, or @@ -683,7 +689,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old) if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules, tblock->rewrite_existflags, FALSE))) { - if (!sendfn(fd, hh->text, hh->slen, use_crlf)) return FALSE; + if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE; store_reset(reset_point); continue; /* With the next header line */ } @@ -691,7 +697,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old) /* Either no rewriting rules, or it didn't get rewritten */ - if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE; + if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE; } /* Header removed */ @@ -726,7 +732,7 @@ if (addr) hprev = h; if (i == 1) { - if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE; + if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE; DEBUG(D_transport) debug_printf("added header line(s):\n%s---\n", h->text); } @@ -751,8 +757,8 @@ if (tblock && (list = CUS tblock->add_headers)) int len = Ustrlen(s); if (len > 0) { - if (!sendfn(fd, s, len, use_crlf)) return FALSE; - if (s[len-1] != '\n' && !sendfn(fd, US"\n", 1, use_crlf)) + if (!sendfn(fd, tctx, s, len)) return FALSE; + if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1)) return FALSE; DEBUG(D_transport) { @@ -768,7 +774,7 @@ if (tblock && (list = CUS tblock->add_headers)) /* Separate headers from body with a blank line */ -return sendfn(fd, US"\n", 1, use_crlf); +return sendfn(fd, tctx, US"\n", 1); } @@ -837,7 +843,6 @@ static BOOL internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit) { int len; -unsigned wck_flags = (unsigned) tctx->options; off_t fsize; int size; @@ -877,7 +882,7 @@ if (!(tctx->options & topt_no_headers)) uschar buffer[ADDRESS_MAXLENGTH + 20]; int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH, return_path); - if (!write_chunk(fd, buffer, n, wck_flags)) return FALSE; + if (!write_chunk(fd, tctx, buffer, n)) return FALSE; } /* Add envelope-to: if requested */ @@ -890,19 +895,19 @@ if (!(tctx->options & topt_no_headers)) struct aci *dlist = NULL; void *reset_point = store_get(0); - if (!write_chunk(fd, US"Envelope-to: ", 13, wck_flags)) return FALSE; + if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE; /* Pick up from all the addresses. The plist and dlist variables are anchors for lists of addresses already handled; they have to be defined at this level becuase write_env_to() calls itself recursively. */ for (p = tctx->addr; p; p = p->next) - if (!write_env_to(p, &plist, &dlist, &first, fd, wck_flags)) + if (!write_env_to(p, &plist, &dlist, &first, fd, tctx)) return FALSE; /* Add a final newline and reset the store used for tracking duplicates */ - if (!write_chunk(fd, US"\n", 1, wck_flags)) return FALSE; + if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE; store_reset(reset_point); } @@ -912,7 +917,7 @@ if (!(tctx->options & topt_no_headers)) { uschar buffer[100]; int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full)); - if (!write_chunk(fd, buffer, n, wck_flags)) return FALSE; + if (!write_chunk(fd, tctx, buffer, n)) return FALSE; } /* Then the message's headers. Don't write any that are flagged as "old"; @@ -921,7 +926,7 @@ if (!(tctx->options & topt_no_headers)) match any entries therein. Then check addr->prop.remove_headers too, provided that addr is not NULL. */ - if (!transport_headers_send(tctx->addr, fd, tctx->tblock, &write_chunk, wck_flags)) + if (!transport_headers_send(fd, tctx, &write_chunk)) return FALSE; } @@ -947,22 +952,13 @@ if (tctx->options & topt_use_bdat) size += body_linecount; /* account for CRLF-expansion */ } - /*XXX need an smtp_outblock here; can't really use the smtp - tpts one. so that had better have been flushed. - - WORRY: smtp cmd response sync, needs an inblock and a LOT - of tpt info. NEED a callback into the tpt. + /*XXX CHUNKING: + Emit a LAST datachunk command. */ -#ifdef notdef - smtp_write_command(&outblock, FALSE, "BDAT %d LAST\r\n", size); - if (count < 0) return FALSE; - if (count > 0) - { - } -#endif - */ + if (tctx->chunk_cb(fd, tctx, size, TRUE) != OK) + return FALSE; - wck_flags &= ~topt_use_bdat; + tctx->options &= ~topt_use_bdat; } /* If the body is required, ensure that the data for check strings (formerly @@ -979,7 +975,7 @@ if (!(tctx->options & topt_no_body)) return FALSE; while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0 && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0) - if (!write_chunk(fd, deliver_in_buffer, len, wck_flags)) + if (!write_chunk(fd, tctx, deliver_in_buffer, len)) return FALSE; /* A read error on the body will have left len == -1 and errno set. */ @@ -993,7 +989,7 @@ nl_check_length = nl_escape_length = 0; /* If requested, add a terminating "." line (SMTP output). */ -if (tctx->options & topt_end_dot && !write_chunk(fd, US".\n", 2, wck_flags)) +if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2)) return FALSE; /* Write out any remaining data in the buffer before returning. */ @@ -1224,7 +1220,7 @@ BOOL last_filter_was_NL = TRUE; int rc, len, yield, fd_read, fd_write, save_errno; int pfd[2] = {-1, -1}; pid_t filter_pid, write_pid; -static transport_ctx dummy_tctx = { NULL, NULL, NULL, NULL, 0 }; +static transport_ctx dummy_tctx = {0}; if (!tctx) tctx = &dummy_tctx; @@ -1359,7 +1355,7 @@ for (;;) if (len > 0) { - if (!write_chunk(fd, deliver_in_buffer, len, wck_flags)) goto TIDY_UP; + if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP; last_filter_was_NL = (deliver_in_buffer[len-1] == '\n'); } @@ -1441,8 +1437,8 @@ if (yield) nl_check_length = nl_escape_length = 0; if ( tctx->options & topt_end_dot && ( last_filter_was_NL - ? !write_chunk(fd, US".\n", 2, wck_flags) - : !write_chunk(fd, US"\n.\n", 3, wck_flags) + ? !write_chunk(fd, tctx, US".\n", 2) + : !write_chunk(fd, tctx, US"\n.\n", 3) ) ) yield = FALSE; diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c index 36a68b92c..f07cd83cf 100644 --- a/src/src/transports/autoreply.c +++ b/src/src/transports/autoreply.c @@ -694,8 +694,7 @@ if (return_message) transport_ctx tctx = { tblock, addr, - NULL, - NULL, + NULL, NULL, (tblock->body_only ? topt_no_headers : 0) | (tblock->headers_only ? topt_no_body : 0) | (tblock->return_path_add ? topt_add_return_path : 0) | diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c index 9624ece6a..0cc981064 100644 --- a/src/src/transports/lmtp.c +++ b/src/src/transports/lmtp.c @@ -612,8 +612,7 @@ if (send_data) transport_ctx tctx = { tblock, addrlist, - US".", - US"..", + US".", US"..", ob->options }; diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c index e3d01974a..d3841e050 100644 --- a/src/src/transports/pipe.c +++ b/src/src/transports/pipe.c @@ -554,7 +554,7 @@ const uschar **argv; uschar *envp[50]; const uschar *envlist = ob->environment; uschar *cmd, *ss; -uschar *eol = (ob->use_crlf)? US"\r\n" : US"\n"; +uschar *eol = ob->use_crlf ? US"\r\n" : US"\n"; transport_ctx tctx = { tblock, addr, diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 33de45cd7..58a59433d 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -1361,6 +1361,63 @@ return checks; } + +/* Callback for emitting a BDAT data chunk header. +Flush any buffered SMTP commands first. +Reap SMTP command responses if not the BDAT LAST. + +A nonlast request that is size zero is special-cased to only flush the +command buffer and reap all outstanding responses. + +Returns: OK or ERROR +*/ + +static int +smtp_chunk_cmd_callback(int fd, transport_ctx * tctx, + unsigned chunk_size, BOOL chunk_last) +{ +smtp_transport_options_block * ob = + (smtp_transport_options_block *)(tctx->tblock->options_block); +uschar buffer[128]; + +if ( (tctx->cmd_count = chunk_size == 0 && !chunk_last + + /* Handle flush request */ + ? smtp_write_command(tctx->outblock, FALSE, NULL) + + /* Write SMTP chunk header command */ + : smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n", + chunk_size, chunk_last ? " LAST" : "") + ) + < 0) + return ERROR; + +if (chunk_last) + return OK; + +/* Reap responses for this and any previous, and error out on failure */ +debug_printf("(look for %d responses)\n", tctx->cmd_count); + +switch(sync_responses(tctx->first_addr, tctx->tblock->rcpt_include_affixes, + tctx->sync_addr, tctx->host, tctx->cmd_count, + ob->address_retry_include_sender, + tctx->pending_MAIL, 0, + tctx->inblock, + ob->command_timeout, + buffer, sizeof(buffer))) + { + case 1: /* 2xx (only) => OK */ + case 3: /* 2xx & 5xx => OK & progress made */ + case 2: *tctx->completed_address = TRUE; /* 5xx (only) => progress made */ + case 0: return OK; /* No 2xx or 5xx, but no probs */ + + case -1: /* Timeout on RCPT */ + default: return ERROR; /* I/O error, or any MAIL/DATA error */ + } +} + + + /************************************************* * Deliver address list to given host * *************************************************/ @@ -2107,14 +2164,11 @@ for (dsn_all_lasthop = TRUE, addr = first_addr; if (smtp_use_dsn && !dsn_all_lasthop) { if (dsn_ret == dsn_ret_hdrs) - { - Ustrcpy(p, " RET=HDRS"); p += 9; - } + { Ustrcpy(p, " RET=HDRS"); p += 9; } else if (dsn_ret == dsn_ret_full) - { - Ustrcpy(p, " RET=FULL"); p += 9; - } - if (dsn_envid != NULL) + { Ustrcpy(p, " RET=FULL"); p += 9; } + + if (dsn_envid) { string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid); while (*p) p++; @@ -2320,15 +2374,19 @@ if (mua_wrapper) send DATA, but if it is FALSE (in the normal, non-wrapper case), we may still have a good recipient buffered up if we are pipelining. We don't want to waste time sending DATA needlessly, so we only send it if either ok is TRUE or if we -are pipelining. The responses are all handled by sync_responses(). */ +are pipelining. The responses are all handled by sync_responses(). +If using CHUNKING, do not send a BDAT until we know how big a chunk we want +to send is. */ -if (ok || (smtp_use_pipelining && !mua_wrapper)) +if ( !(peer_offered & PEER_OFFERED_CHUNKING) + && (ok || (smtp_use_pipelining && !mua_wrapper))) { int count = smtp_write_command(&outblock, FALSE, "DATA\r\n"); + if (count < 0) goto SEND_FAILED; switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr, host, count, ob->address_retry_include_sender, pending_MAIL, - ok? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer))) + ok ? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer))) { case 3: ok = TRUE; /* 2xx & 5xx => OK & progress made */ case 2: completed_address = TRUE; /* 5xx (only) => progress made */ @@ -2343,10 +2401,6 @@ if (ok || (smtp_use_pipelining && !mua_wrapper)) } } -/* Save the first address of the next batch. */ - -first_addr = addr; - /* If there were no good recipients (but otherwise there have been no problems), just set ok TRUE, since we have handled address-specific errors already. Otherwise, it's OK to send the message. Use the check/escape mechanism @@ -2354,8 +2408,13 @@ for handling the SMTP dot-handling protocol, flagging to apply to headers as well as body. Set the appropriate timeout value to be used for each chunk. (Haven't been able to make it work using select() for writing yet.) */ -if (!ok) +if (!(peer_offered & PEER_OFFERED_CHUNKING) && !ok) + { + /* Save the first address of the next batch. */ + first_addr = addr; + ok = TRUE; + } else { transport_ctx tctx = { @@ -2367,24 +2426,41 @@ else | (tblock->headers_only ? topt_no_body : 0) | (tblock->return_path_add ? topt_add_return_path : 0) | (tblock->delivery_date_add ? topt_add_delivery_date : 0) - | (tblock->envelope_to_add ? topt_add_envelope_to : 0) + | (tblock->envelope_to_add ? topt_add_envelope_to : 0), }; + /* If using CHUNKING we need a callback from the generic transport + support to us, for the sending of BDAT smtp commands and the reaping + of responses. The callback needs a whole bunch of state so set up + a transport-context structure to be passed around. */ + if (peer_offered & PEER_OFFERED_CHUNKING) { - tctx.options |= topt_use_bdat; tctx.check_string = tctx.escape_string = NULL; + tctx.options |= topt_use_bdat; + tctx.chunk_cb = smtp_chunk_cmd_callback; + tctx.inblock = &inblock; + tctx.outblock = &outblock; + tctx.host = host; + tctx.first_addr = first_addr; + tctx.sync_addr = &sync_addr; + tctx.pending_MAIL = pending_MAIL; + tctx.completed_address = &completed_address; } else tctx.options |= topt_end_dot; + /* Save the first address of the next batch. */ + first_addr = addr; + sigalrm_seen = FALSE; transport_write_timeout = ob->data_timeout; smtp_command = US"sending data block"; /* For error messages */ DEBUG(D_transport|D_v) - debug_printf(" SMTP>> writing message %s\n", - peer_offered & PEER_OFFERED_CHUNKING - ? "using CHUNKING" : "and terminating \".\""); + if (peer_offered & PEER_OFFERED_CHUNKING) + debug_printf(" will write message using CHUNKING\n"); + else + debug_printf(" SMTP>> writing message and terminating \".\"\n"); transport_count = 0; #ifndef DISABLE_DKIM @@ -2417,6 +2493,27 @@ else smtp_command = US"end of data"; + if (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, + host, tctx.cmd_count-1, ob->address_retry_include_sender, + pending_MAIL, 0, + &inblock, ob->command_timeout, buffer, sizeof(buffer))) + { + case 3: ok = TRUE; /* 2xx & 5xx => OK & progress made */ + case 2: completed_address = TRUE; /* 5xx (only) => progress made */ + break; + + case 1: ok = TRUE; /* 2xx (only) => OK, but if LMTP, */ + if (!lmtp) completed_address = 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 */ + default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */ + } + } + #ifndef DISABLE_PRDR /* For PRDR we optionally get a partial-responses warning * followed by the individual responses, before going on with diff --git a/src/src/verify.c b/src/src/verify.c index f5f478edd..4c4dfc599 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1559,9 +1559,9 @@ return cutthrough_response('3', NULL) == '3'; } -/* fd and options args only to match write_chunk() */ +/* fd and tctx args only to match write_chunk() */ static BOOL -cutthrough_write_chunk(int fd, uschar * s, int len, unsigned options) +cutthrough_write_chunk(int fd, transport_ctx * tctx, uschar * s, int len) { uschar * s2; while(s && (s2 = Ustrchr(s, '\n'))) @@ -1580,6 +1580,8 @@ return TRUE; BOOL cutthrough_headers_send(void) { +transport_ctx tctx; + if(cutthrough.fd < 0) return FALSE; @@ -1588,9 +1590,13 @@ if(cutthrough.fd < 0) */ HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n"); -if (!transport_headers_send(&cutthrough.addr, cutthrough.fd, - cutthrough.addr.transport, - &cutthrough_write_chunk, topt_use_crlf)) +tctx.tblock = cutthrough.addr.transport; +tctx.addr = &cutthrough.addr; +tctx.check_string = US"."; +tctx.escape_string = US".."; +tctx.options = topt_use_crlf; + +if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk)) return FALSE; HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n"); diff --git a/test/confs/0611 b/test/confs/0611 index e8b4c51d4..4f6f490d3 100644 --- a/test/confs/0611 +++ b/test/confs/0611 @@ -11,6 +11,7 @@ spool_directory = DIR/spool gecos_pattern = "" gecos_name = CALLER_NAME tls_advertise_hosts = +chunking_advertise_hosts = # ----- Main settings ----- -- 2.25.1