else
{
alarm(local_timeout);
- #ifdef SUPPORT_TLS
- if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
- #endif
- rc = write(fd, block, len);
+#ifdef SUPPORT_TLS
+ if (tls_out.active == fd)
+ rc = tls_write(FALSE, block, len);
+ else
+#endif
+ rc = write(fd, block, len);
save_errno = errno;
local_timeout = alarm(0);
if (sigalrm_seen)
fd file descript to write to
chunk pointer to data to write
len length of data to write
- usr_crlf TRUE if CR LF is wanted at the end of each line
+ tctx transport context - processing to be done during output
In addition, the static nl_xxx variables must be set as required.
*/
static BOOL
-write_chunk(int fd, uschar *chunk, int len, BOOL use_crlf)
+write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len)
{
uschar *start = chunk;
uschar *end = chunk + len;
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. */
- 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;
}
/* Insert CR before NL if required */
- if (use_crlf) *chunk_ptr++ = '\r';
+ if (tctx && tctx->options & topt_use_crlf) *chunk_ptr++ = '\r';
*chunk_ptr++ = '\n';
transport_newlines++;
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
- use_crlf 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, BOOL use_crlf)
+ BOOL *first, int fd, transport_ctx * tctx)
{
address_item *pp;
struct aci *ppp;
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, use_crlf))
+ if (!write_env_to(dup, pplist, pdlist, first, fd, tctx))
return FALSE;
if (!pp->parent) break;
}
*pplist = ppp;
ppp->ptr = pp;
-if (!(*first) && !write_chunk(fd, US",\n ", 3, use_crlf)) return FALSE;
+if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE;
*first = FALSE;
-return write_chunk(fd, pp->address, Ustrlen(pp->address), use_crlf);
+return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address));
}
addr (chain of) addresses (for extra headers), or NULL;
only the first address is used
fd file descriptor to write the message to
- sendfn function for output
- use_crlf turn NL into CR LF
+ sendfn function for output (transport or verify)
+ 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, uschar *add_headers, uschar *remove_headers,
- BOOL (*sendfn)(int fd, uschar * s, int len, BOOL use_crlf),
- BOOL use_crlf, rewrite_rule *rewrite_rules, int rewrite_existflags)
+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
for (h = header_list; h; h = h->next) if (h->type != htype_old)
{
int i;
- const uschar *list = remove_headers;
-
BOOL include_header = TRUE;
+ list = tblock ? tblock->remove_headers : NULL;
for (i = 0; i < 2; i++) /* For remove_headers && addr->prop.remove_headers */
{
if (list)
while (*ss == ' ' || *ss == '\t') ss++;
if (*ss == ':') break;
}
- if (s != NULL) { include_header = FALSE; break; }
+ if (s) { include_header = FALSE; break; }
}
- if (addr != NULL) list = addr->prop.remove_headers;
+ if (addr) list = addr->prop.remove_headers;
}
/* If this header is to be output, try to rewrite it if there are rewriting
if (include_header)
{
- if (rewrite_rules)
+ if (tblock && tblock->rewrite_rules)
{
void *reset_point = store_get(0);
header_line *hh;
- if ((hh = rewrite_header(h, NULL, NULL, rewrite_rules, rewrite_existflags, FALSE)))
+ 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 */
}
/* 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 */
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);
}
noops. An added header string from a transport may not end with a newline;
add one if it does not. */
-if (add_headers)
+if (tblock && (list = CUS tblock->add_headers))
{
int sep = '\n';
uschar * s;
- while ((s = string_nextinlist(CUSS &add_headers, &sep, NULL, 0)))
+ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
if ((s = expand_string(s)))
{
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)
{
/* Separate headers from body with a blank line */
-return sendfn(fd, US"\n", 1, use_crlf);
+return sendfn(fd, tctx, US"\n", 1);
}
transport_write_timeout non-zero.
Arguments:
- addr (chain of) addresses (for extra headers), or NULL;
- only the first address is used
fd file descriptor to write the message to
- options bit-wise options:
- add_return_path if TRUE, add a "return-path" header
- add_envelope_to if TRUE, add a "envelope-to" header
- add_delivery_date if TRUE, add a "delivery-date" header
- use_crlf if TRUE, turn NL into CR LF
- end_dot if TRUE, send a terminating "." line at the end
- no_headers if TRUE, omit the headers
- no_body if TRUE, omit the body
- size_limit if > 0, this is a limit to the size of message written;
- it is used when returning messages to their senders,
- and is approximate rather than exact, owing to chunk
- buffering
- add_headers a string containing one or more headers to add; it is
- expanded, and must be in correct RFC 822 format as
- it is transmitted verbatim; NULL => no additions,
- and so does empty string or forced expansion fail
- remove_headers a colon-separated list of headers to remove, or NULL
- check_string a string to check for at the start of lines, or NULL
- escape_string a string to insert in front of any check string
- rewrite_rules chain of header rewriting rules
- rewrite_existflags flags for the rewriting rules
+ tctx
+ addr (chain of) addresses (for extra headers), or NULL;
+ only the first address is used
+ tblock optional transport instance block (NULL signifies NULL/0):
+ add_headers a string containing one or more headers to add; it is
+ expanded, and must be in correct RFC 822 format as
+ it is transmitted verbatim; NULL => no additions,
+ and so does empty string or forced expansion fail
+ remove_headers a colon-separated list of headers to remove, or NULL
+ rewrite_rules chain of header rewriting rules
+ rewrite_existflags flags for the rewriting rules
+ options bit-wise options:
+ add_return_path if TRUE, add a "return-path" header
+ add_envelope_to if TRUE, add a "envelope-to" header
+ add_delivery_date if TRUE, add a "delivery-date" header
+ use_crlf if TRUE, turn NL into CR LF
+ end_dot if TRUE, send a terminating "." line at the end
+ no_headers if TRUE, omit the headers
+ no_body if TRUE, omit the body
+ size_limit if > 0, this is a limit to the size of message written;
+ it is used when returning messages to their senders,
+ and is approximate rather than exact, owing to chunk
+ buffering
+ check_string a string to check for at the start of lines, or NULL
+ escape_string a string to insert in front of any check string
Returns: TRUE on success; FALSE (with errno) on failure.
In addition, the global variable transport_count
*/
static BOOL
-internal_transport_write_message(address_item *addr, int fd, int options,
- int size_limit, uschar *add_headers, uschar *remove_headers, uschar *check_string,
- uschar *escape_string, rewrite_rule *rewrite_rules, int rewrite_existflags)
+internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
{
-int written = 0;
int len;
-BOOL use_crlf = (options & topt_use_crlf) != 0;
+off_t fsize;
+int size;
/* Initialize pointer in output buffer. */
/* Set up the data for start-of-line data checking and escaping */
nl_partial_match = -1;
-if (check_string && escape_string)
+if (tctx->check_string && tctx->escape_string)
{
- nl_check = check_string;
+ nl_check = tctx->check_string;
nl_check_length = Ustrlen(nl_check);
- nl_escape = escape_string;
+ nl_escape = tctx->escape_string;
nl_escape_length = Ustrlen(nl_escape);
}
else
an option (set for SMTP, not otherwise). Negate the length if not wanted till
after the headers. */
-if (!(options & topt_escape_headers))
+if (!(tctx->options & topt_escape_headers))
nl_check_length = -nl_check_length;
/* Write the headers if required, including any that have to be added. If there
are header rewriting rules, apply them. */
-if (!(options & topt_no_headers))
+if (!(tctx->options & topt_no_headers))
{
/* Add return-path: if requested. */
- if (options & topt_add_return_path)
+ if (tctx->options & topt_add_return_path)
{
uschar buffer[ADDRESS_MAXLENGTH + 20];
int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
return_path);
- if (!write_chunk(fd, buffer, n, use_crlf)) return FALSE;
+ if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
}
/* Add envelope-to: if requested */
- if (options & topt_add_envelope_to)
+ if (tctx->options & topt_add_envelope_to)
{
BOOL first = TRUE;
address_item *p;
struct aci *dlist = NULL;
void *reset_point = store_get(0);
- if (!write_chunk(fd, US"Envelope-to: ", 13, use_crlf)) 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 = addr; p; p = p->next)
- if (!write_env_to(p, &plist, &dlist, &first, fd, use_crlf))
+ for (p = tctx->addr; p; p = p->next)
+ 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, use_crlf)) return FALSE;
+ if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE;
store_reset(reset_point);
}
/* Add delivery-date: if requested. */
- if ((options & topt_add_delivery_date) != 0)
+ if (tctx->options & topt_add_delivery_date)
{
uschar buffer[100];
int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
- if (!write_chunk(fd, buffer, n, use_crlf)) 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";
were removed (e.g. Bcc). If remove_headers is not null, skip any headers that
match any entries therein. Then check addr->prop.remove_headers too, provided that
addr is not NULL. */
- if (!transport_headers_send(addr, fd, add_headers, remove_headers, &write_chunk,
- use_crlf, rewrite_rules, rewrite_existflags))
+
+ if (!transport_headers_send(fd, tctx, &write_chunk))
return FALSE;
}
+/* When doing RFC3030 CHUNKING output, work out how much data will be in the
+last BDAT, consisting of the current write_chunk() output buffer fill
+(optimally, all of the headers - but it does not matter if we already had to
+flush that buffer with non-last BDAT prependix) plus the amount of body data
+(as expanded for CRLF lines). Then create and write the BDAT, and ensure
+that further use of write_chunk() will not prepend BDATs. */
+
+if (tctx->options & topt_use_bdat)
+ {
+ if ((size = chunk_ptr - deliver_out_buffer) < 0)
+ size = 0;
+ if (!(tctx->options & topt_no_body))
+ {
+ if ((fsize = lseek(deliver_datafile, 0, SEEK_END)) < 0) return FALSE;
+ fsize -= SPOOL_DATA_START_OFFSET;
+ if (size_limit > 0 && fsize > size_limit)
+ fsize = size_limit;
+ size += fsize;
+ if (tctx->options & topt_use_crlf)
+ size += body_linecount; /* account for CRLF-expansion */
+ }
+
+ /*XXX CHUNKING:
+ Emit a LAST datachunk command. */
+
+ if (tctx->chunk_cb(fd, tctx, size, TRUE) != OK)
+ return FALSE;
+
+ tctx->options &= ~topt_use_bdat;
+ }
+
/* If the body is required, ensure that the data for check strings (formerly
the "from hack") is enabled by negating the length if necessary. (It will be
negative in cases where it isn't to apply to the headers). Then ensure the body
is positioned at the start of its file (following the message id), then write
it, applying the size limit if required. */
-if (!(options & topt_no_body))
+if (!(tctx->options & topt_no_body))
{
nl_check_length = abs(nl_check_length);
nl_partial_match = 0;
if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
return FALSE;
- while ((len = read(deliver_datafile, deliver_in_buffer,
- DELIVER_IN_BUFFER_SIZE)) > 0)
- {
- if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) return FALSE;
- if (size_limit > 0)
- {
- written += len;
- if (written > size_limit)
- {
- len = 0; /* Pretend EOF */
- break;
- }
- }
- }
+ while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
+ && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
+ 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. */
/* If requested, add a terminating "." line (SMTP output). */
-if (options & topt_end_dot && !write_chunk(fd, US".\n", 2, use_crlf))
+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. */
*/
BOOL
-dkim_transport_write_message(address_item *addr, int out_fd, int options,
- uschar *add_headers, uschar *remove_headers,
- uschar *check_string, uschar *escape_string, rewrite_rule *rewrite_rules,
- int rewrite_existflags, struct ob_dkim * dkim)
+dkim_transport_write_message(int out_fd, transport_ctx * tctx,
+ struct ob_dkim * dkim)
{
int dkim_fd;
int save_errno = 0;
int sread = 0;
int wwritten = 0;
uschar *dkim_signature = NULL;
+int siglen;
off_t k_file_size;
/* If we can't sign, just call the original function. */
if (!(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector))
- return transport_write_message(addr, out_fd, options,
- 0, add_headers, remove_headers,
- check_string, escape_string, rewrite_rules,
- rewrite_existflags);
+ return transport_write_message(out_fd, tctx, 0);
dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
string_sprintf("-%d-K", (int)getpid()));
/* Call original function to write the -K file; does the CRLF expansion */
-rc = transport_write_message(addr, dkim_fd, options,
- 0, add_headers, remove_headers,
- check_string, escape_string, rewrite_rules,
- rewrite_existflags);
+tctx->options &= ~topt_use_bdat;
+rc = transport_write_message(dkim_fd, tctx, 0);
/* Save error state. We must clean up before returning. */
if (!rc)
}
}
- if (dkim_signature)
+ siglen = 0;
+ }
+
+if (dkim_signature)
+ {
+ siglen = Ustrlen(dkim_signature);
+ while(siglen > 0)
{
- int siglen = Ustrlen(dkim_signature);
- while(siglen > 0)
- {
#ifdef SUPPORT_TLS
- wwritten = tls_out.active == out_fd
- ? tls_write(FALSE, dkim_signature, siglen)
- : write(out_fd, dkim_signature, siglen);
+ wwritten = tls_out.active == out_fd
+ ? tls_write(FALSE, dkim_signature, siglen)
+ : write(out_fd, dkim_signature, siglen);
#else
- wwritten = write(out_fd, dkim_signature, siglen);
+ wwritten = write(out_fd, dkim_signature, siglen);
#endif
- if (wwritten == -1)
- {
- /* error, bail out */
- save_errno = errno;
- rc = FALSE;
- goto CLEANUP;
- }
- siglen -= wwritten;
- dkim_signature += wwritten;
+ if (wwritten == -1)
+ {
+ /* error, bail out */
+ save_errno = errno;
+ rc = FALSE;
+ goto CLEANUP;
}
+ siglen -= wwritten;
+ dkim_signature += wwritten;
}
}
to write to the filter, and in this process just suck from the filter and write
down the given fd. At the end, tidy up the pipes and the processes.
+XXX
Arguments: as for internal_transport_write_message() above
Returns: TRUE on success; FALSE (with errno) for any failure
*/
BOOL
-transport_write_message(address_item *addr, int fd, int options,
- int size_limit, uschar *add_headers, uschar *remove_headers,
- uschar *check_string, uschar *escape_string, rewrite_rule *rewrite_rules,
- int rewrite_existflags)
+transport_write_message(int fd, transport_ctx * tctx, int size_limit)
{
-BOOL use_crlf;
+unsigned wck_flags;
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 = {0};
+
+if (!tctx) tctx = &dummy_tctx;
transport_filter_timed_out = FALSE;
|| !*transport_filter_argv
|| !**transport_filter_argv
)
- return internal_transport_write_message(addr, fd, options, size_limit,
- add_headers, remove_headers, check_string, escape_string,
- rewrite_rules, rewrite_existflags);
+ return internal_transport_write_message(fd, tctx, size_limit);
/* Otherwise the message must be written to a filter process and read back
before being written to the incoming fd. First set up the special processing to
be done during the copying. */
-use_crlf = (options & topt_use_crlf) != 0;
+wck_flags = tctx->options & topt_use_crlf;
nl_partial_match = -1;
-if (check_string != NULL && escape_string != NULL)
+if (tctx->check_string && tctx->escape_string)
{
- nl_check = check_string;
+ nl_check = tctx->check_string;
nl_check_length = Ustrlen(nl_check);
- nl_escape = escape_string;
+ nl_escape = tctx->escape_string;
nl_escape_length = Ustrlen(nl_escape);
}
else nl_check_length = nl_escape_length = 0;
if (filter_pid < 0) goto TIDY_UP; /* errno set */
DEBUG(D_transport)
- debug_printf("process %d running as transport filter: write=%d read=%d\n",
+ debug_printf("process %d running as transport filter: fd_write=%d fd_read=%d\n",
(int)filter_pid, fd_write, fd_read);
/* Fork subprocess to write the message to the filter, and return the result
(void)close(fd_read);
(void)close(pfd[pipe_read]);
nl_check_length = nl_escape_length = 0;
- rc = internal_transport_write_message(addr, fd_write,
- (options & ~(topt_use_crlf | topt_end_dot)),
- size_limit, add_headers, remove_headers, NULL, NULL,
- rewrite_rules, rewrite_existflags);
+
+ tctx->check_string = tctx->escape_string = NULL;
+ tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat);
+
+ rc = internal_transport_write_message(fd_write, tctx, size_limit);
+
save_errno = errno;
if ( write(pfd[pipe_write], (void *)&rc, sizeof(BOOL))
!= sizeof(BOOL)
|| write(pfd[pipe_write], (void *)&save_errno, sizeof(int))
!= sizeof(int)
- || write(pfd[pipe_write], (void *)&(addr->more_errno), sizeof(int))
+ || write(pfd[pipe_write], (void *)&tctx->addr->more_errno, sizeof(int))
!= sizeof(int)
)
rc = FALSE; /* compiler quietening */
if (len > 0)
{
- if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) 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');
}
{
yield = FALSE;
save_errno = ERRNO_FILTER_FAIL;
- addr->more_errno = rc;
+ tctx->addr->more_errno = rc;
DEBUG(D_transport) debug_printf("filter process returned %d\n", rc);
}
if (!ok)
{
dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
- dummy = read(pfd[pipe_read], (void *)&(addr->more_errno), sizeof(int));
+ dummy = read(pfd[pipe_read], (void *)&(tctx->addr->more_errno), sizeof(int));
yield = FALSE;
}
}
{
yield = FALSE;
save_errno = ERRNO_FILTER_FAIL;
- addr->more_errno = rc;
+ tctx->addr->more_errno = rc;
DEBUG(D_transport) debug_printf("writing process returned %d\n", rc);
}
}
if (yield)
{
nl_check_length = nl_escape_length = 0;
- if ( options & topt_end_dot
+ if ( tctx->options & topt_end_dot
&& ( last_filter_was_NL
- ? !write_chunk(fd, US".\n", 2, options)
- : !write_chunk(fd, US"\n.\n", 3, options)
+ ? !write_chunk(fd, tctx, US".\n", 2)
+ : !write_chunk(fd, tctx, US"\n.\n", 3)
) )
yield = FALSE;
{
debug_printf("end of filtering transport writing: yield=%d\n", yield);
if (!yield)
- debug_printf("errno=%d more_errno=%d\n", errno, addr->more_errno);
+ debug_printf("errno=%d more_errno=%d\n", errno, tctx->addr->more_errno);
}
return yield;