* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
/* See the file NOTICE for conditions of use and distribution. */
/* General functions concerned with transportation, and generic options for all
(void *)offsetof(transport_instance, driver_name) },
{ "envelope_to_add", opt_bool|opt_public,
(void *)(offsetof(transport_instance, envelope_to_add)) },
+#ifndef DISABLE_EVENT
+ { "event_action", opt_stringptr | opt_public,
+ (void *)offsetof(transport_instance, event_action) },
+#endif
{ "group", opt_expand_gid|opt_public,
(void *)offsetof(transport_instance, gid) },
- { "headers_add", opt_stringptr|opt_public,
+ { "headers_add", opt_stringptr|opt_public|opt_rep_str,
(void *)offsetof(transport_instance, add_headers) },
{ "headers_only", opt_bool|opt_public,
(void *)offsetof(transport_instance, headers_only) },
- { "headers_remove", opt_stringptr|opt_public,
+ { "headers_remove", opt_stringptr|opt_public|opt_rep_str,
(void *)offsetof(transport_instance, remove_headers) },
{ "headers_rewrite", opt_rewrite|opt_public,
(void *)offsetof(transport_instance, headers_rewrite) },
(void *)offsetof(transport_instance, home_dir) },
{ "initgroups", opt_bool|opt_public,
(void *)offsetof(transport_instance, initgroups) },
+ { "max_parallel", opt_stringptr|opt_public,
+ (void *)offsetof(transport_instance, max_parallel) },
{ "message_size_limit", opt_stringptr|opt_public,
(void *)offsetof(transport_instance, message_size_limit) },
{ "rcpt_include_affixes", opt_bool|opt_public,
(void *)offsetof(transport_instance, uid) }
};
-int optionlist_transports_size =
- sizeof(optionlist_transports)/sizeof(optionlist);
+int optionlist_transports_size = nelem(optionlist_transports);
+
+
+void
+readconf_options_transports(void)
+{
+struct transport_info * ti;
+readconf_options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
+
+for (ti = transports_available; ti->driver_name[0]; ti++)
+ {
+ macro_create(string_sprintf("_DRIVER_TRANSPORT_%T", ti->driver_name), US"y", FALSE, TRUE);
+ readconf_options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
+ }
+}
/*************************************************
* Initialize transport list *
/* Now scan the configured transports and check inconsistencies. A shadow
transport is permitted only for local transports. */
-for (t = transports; t != NULL; t = t->next)
+for (t = transports; t; t = t->next)
{
- if (!t->info->local)
- {
- if (t->shadow != NULL)
- log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
- "shadow transport not allowed on non-local transport %s", t->name);
- }
+ if (!t->info->local && t->shadow)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+ "shadow transport not allowed on non-local transport %s", t->name);
if (t->body_only && t->headers_only)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
if (transport_write_timeout <= 0) /* No timeout wanted */
{
#ifdef SUPPORT_TLS
- if (tls_active == fd) rc = tls_write(block, len); else
+ if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
#endif
rc = write(fd, block, len);
save_errno = errno;
else
{
alarm(local_timeout);
- #ifdef SUPPORT_TLS
- if (tls_active == fd) rc = tls_write(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;
-register uschar *ptr;
+uschar *ptr;
int mlen = DELIVER_OUT_BUFFER_SIZE - nl_escape_length - 2;
/* The assumption is made that the check string will never stretch over move
for (ptr = start; ptr < end; ptr++)
{
- register 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)
+ if ((len = chunk_ptr - deliver_out_buffer) > mlen)
{
- if (!transport_write_block(fd, deliver_out_buffer,
- chunk_ptr - deliver_out_buffer))
- return FALSE;
+ DEBUG(D_transport) debug_printf("flushing headers buffer\n");
+
+ /* 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, 0) != OK
+ || !transport_write_block(fd, deliver_out_buffer, len)
+ || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+ )
+ return FALSE;
+ }
+ else
+ 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;
/* Do nothing if we have already handled this address. If not, remember it
so that we don't handle it again. */
-for (ppp = *pdlist; ppp != NULL; ppp = ppp->next)
- { if (p == ppp->ptr) return TRUE; }
+for (ppp = *pdlist; ppp; ppp = ppp->next) if (p == ppp->ptr) return TRUE;
ppp = store_get(sizeof(struct aci));
ppp->next = *pdlist;
for (pp = p;; pp = pp->parent)
{
address_item *dup;
- for (dup = addr_duplicate; dup != NULL; dup = dup->next)
- {
- if (dup->dupof != pp) continue; /* Not a dup of our address */
- if (!write_env_to(dup, pplist, pdlist, first, fd, use_crlf)) return FALSE;
- }
- if (pp->parent == NULL) break;
+ 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, tctx))
+ return FALSE;
+ if (!pp->parent) break;
}
/* Check to see if we have already output the progenitor. */
-for (ppp = *pplist; ppp != NULL; ppp = ppp->next)
- { if (pp == ppp->ptr) break; }
-if (ppp != NULL) return TRUE;
+for (ppp = *pplist; ppp; ppp = ppp->next) if (pp == ppp->ptr) break;
+if (ppp) return TRUE;
/* Remember what we have output, and output it. */
*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));
}
+/* Add/remove/rewrite headers, and send them plus the empty-line separator.
+
+Globals:
+ header_list
+
+Arguments:
+ addr (chain of) addresses (for extra headers), or NULL;
+ only the first address is used
+ fd file descriptor to write the message to
+ tctx transport context
+ sendfn function for output (transport or verify)
+
+Returns: TRUE on success; FALSE on failure.
+*/
+BOOL
+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
+were removed (e.g. Bcc). If remove_headers is not null, skip any headers that
+match any entries therein. It is a colon-sep list; expand the items
+separately and squash any empty ones.
+Then check addr->prop.remove_headers too, provided that addr is not NULL. */
+
+for (h = header_list; h; h = h->next) if (h->type != htype_old)
+ {
+ int i;
+ 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)
+ {
+ int sep = ':'; /* This is specified as a colon-separated list */
+ uschar *s, *ss;
+ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+ {
+ int len;
+
+ if (i == 0)
+ if (!(s = expand_string(s)) && !expand_string_forcedfail)
+ {
+ errno = ERRNO_CHHEADER_FAIL;
+ return FALSE;
+ }
+ len = s ? Ustrlen(s) : 0;
+ if (strncmpic(h->text, s, len) != 0) continue;
+ ss = h->text + len;
+ while (*ss == ' ' || *ss == '\t') ss++;
+ if (*ss == ':') break;
+ }
+ if (s) { include_header = FALSE; break; }
+ }
+ if (addr) list = addr->prop.remove_headers;
+ }
+
+ /* If this header is to be output, try to rewrite it if there are rewriting
+ rules. */
+
+ if (include_header)
+ {
+ if (tblock && tblock->rewrite_rules)
+ {
+ void *reset_point = store_get(0);
+ header_line *hh;
+
+ if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
+ tblock->rewrite_existflags, 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, tctx, h->text, h->slen)) return FALSE;
+ }
+
+ /* Header removed */
+
+ else
+ {
+ DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text);
+ }
+ }
+
+/* Add on any address-specific headers. If there are multiple addresses,
+they will all have the same headers in order to be batched. The headers
+are chained in reverse order of adding (so several addresses from the
+same alias might share some of them) but we want to output them in the
+opposite order. This is a bit tedious, but there shouldn't be very many
+of them. We just walk the list twice, reversing the pointers each time,
+but on the second time, write out the items.
+
+Headers added to an address by a router are guaranteed to end with a newline.
+*/
+
+if (addr)
+ {
+ int i;
+ header_line *hprev = addr->prop.extra_headers;
+ header_line *hnext;
+ for (i = 0; i < 2; i++)
+ for (h = hprev, hprev = NULL; h; h = hnext)
+ {
+ hnext = h->next;
+ h->next = hprev;
+ hprev = h;
+ if (i == 1)
+ {
+ if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
+ DEBUG(D_transport)
+ debug_printf("added header line(s):\n%s---\n", h->text);
+ }
+ }
+ }
+
+/* If a string containing additional headers exists it is a newline-sep
+list. Expand each item and write out the result. This is done last so that
+if it (deliberately or accidentally) isn't in header format, it won't mess
+up any other headers. An empty string or a forced expansion failure are
+noops. An added header string from a transport may not end with a newline;
+add one if it does not. */
+
+if (tblock && (list = CUS tblock->add_headers))
+ {
+ int sep = '\n';
+ uschar * s;
+
+ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+ if ((s = expand_string(s)))
+ {
+ int len = Ustrlen(s);
+ if (len > 0)
+ {
+ if (!sendfn(fd, tctx, s, len)) return FALSE;
+ if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1))
+ return FALSE;
+ DEBUG(D_transport)
+ {
+ debug_printf("added header line:\n%s", s);
+ if (s[len-1] != '\n') debug_printf("\n");
+ debug_printf("---\n");
+ }
+ }
+ }
+ else if (!expand_string_forcedfail)
+ { errno = ERRNO_CHHEADER_FAIL; return FALSE; }
+ }
+
+/* Separate headers from body with a blank line */
+
+return sendfn(fd, tctx, US"\n", 1);
+}
+
+
/*************************************************
* Write the message *
*************************************************/
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
+ 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
+ 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
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;
-header_line *h;
-BOOL use_crlf = (options & topt_use_crlf) != 0;
/* Initialize pointer in output buffer. */
/* Set up the data for start-of-line data checking and escaping */
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;
+else
+ nl_check_length = nl_escape_length = 0;
/* Whether the escaping mechanism is applied to headers or not is controlled by
an option (set for SMTP, not otherwise). Negate the length if not wanted till
after the headers. */
-if ((options & topt_escape_headers) == 0) nl_check_length = -nl_check_length;
+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) == 0)
+if (!(tctx->options & topt_no_headers))
{
/* Add return-path: if requested. */
- if ((options & topt_add_return_path) != 0)
+ if (tctx->options & topt_add_return_path)
{
uschar buffer[ADDRESS_MAXLENGTH + 20];
- sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
+ int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
return_path);
- if (!write_chunk(fd, buffer, Ustrlen(buffer), use_crlf)) return FALSE;
+ if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
}
/* Add envelope-to: if requested */
- if ((options & topt_add_envelope_to) != 0)
+ 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. */
+ this level because write_env_to() calls itself recursively. */
- for (p = addr; p != NULL; p = p->next)
- {
- if (!write_env_to(p, &plist, &dlist, &first, fd, use_crlf)) return FALSE;
- }
+ 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];
- sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
- if (!write_chunk(fd, buffer, Ustrlen(buffer), use_crlf)) return FALSE;
+ int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
+ if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
}
/* 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
were removed (e.g. Bcc). If remove_headers is not null, skip any headers that
- match any entries therein. Then check addr->p.remove_headers too, provided that
+ match any entries therein. Then check addr->prop.remove_headers too, provided that
addr is not NULL. */
- if (remove_headers != NULL)
- {
- uschar *s = expand_string(remove_headers);
- if (s == NULL && !expand_string_forcedfail)
- {
- errno = ERRNO_CHHEADER_FAIL;
- return FALSE;
- }
- remove_headers = s;
- }
-
- for (h = header_list; h != NULL; h = h->next)
- {
- int i;
- uschar *list = NULL;
- BOOL include_header;
-
- if (h->type == htype_old) continue;
-
- include_header = TRUE;
- list = remove_headers;
-
- for (i = 0; i < 2; i++) /* For remove_headers && addr->p.remove_headers */
- {
- if (list != NULL)
- {
- int sep = ':'; /* This is specified as a colon-separated list */
- uschar *s, *ss;
- uschar buffer[128];
- while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
- != NULL)
- {
- int len = Ustrlen(s);
- if (strncmpic(h->text, s, len) != 0) continue;
- ss = h->text + len;
- while (*ss == ' ' || *ss == '\t') ss++;
- if (*ss == ':') break;
- }
- if (s != NULL) { include_header = FALSE; break; }
- }
- if (addr != NULL) list = addr->p.remove_headers;
- }
-
- /* If this header is to be output, try to rewrite it if there are rewriting
- rules. */
-
- if (include_header)
- {
- if (rewrite_rules != NULL)
- {
- void *reset_point = store_get(0);
- header_line *hh =
- rewrite_header(h, NULL, NULL, rewrite_rules, rewrite_existflags,
- FALSE);
- if (hh != NULL)
- {
- if (!write_chunk(fd, hh->text, hh->slen, use_crlf)) return FALSE;
- store_reset(reset_point);
- continue; /* With the next header line */
- }
- }
-
- /* Either no rewriting rules, or it didn't get rewritten */
-
- if (!write_chunk(fd, h->text, h->slen, use_crlf)) return FALSE;
- }
-
- /* Header removed */
-
- else
- {
- DEBUG(D_transport) debug_printf("removed header line:\n%s---\n",
- h->text);
- }
- }
-
- /* Add on any address-specific headers. If there are multiple addresses,
- they will all have the same headers in order to be batched. The headers
- are chained in reverse order of adding (so several addresses from the
- same alias might share some of them) but we want to output them in the
- opposite order. This is a bit tedious, but there shouldn't be very many
- of them. We just walk the list twice, reversing the pointers each time,
- but on the second time, write out the items.
+ if (!transport_headers_send(fd, tctx, &write_chunk))
+ return FALSE;
+ }
- Headers added to an address by a router are guaranteed to end with a newline.
- */
+/* When doing RFC3030 CHUNKING output, work out how much data would be in a
+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 BDAT(s), and ensure
+that further use of write_chunk() will not prepend BDATs.
+The first BDAT written will also first flush any outstanding MAIL and RCPT
+commands which were buffered thans to PIPELINING.
+Commands go out (using a send()) from a different buffer to data (using a
+write()). They might not end up in the same TCP segment, which is
+suboptimal. */
+
+if (tctx->options & topt_use_bdat)
+ {
+ off_t fsize;
+ int hsize, size = 0;
- if (addr != NULL)
+ if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
+ hsize = 0;
+ if (!(tctx->options & topt_no_body))
{
- int i;
- header_line *hprev = addr->p.extra_headers;
- header_line *hnext;
- for (i = 0; i < 2; i++)
- {
- for (h = hprev, hprev = NULL; h != NULL; h = hnext)
- {
- hnext = h->next;
- h->next = hprev;
- hprev = h;
- if (i == 1)
- {
- if (!write_chunk(fd, h->text, h->slen, use_crlf)) return FALSE;
- DEBUG(D_transport)
- debug_printf("added header line(s):\n%s---\n", h->text);
- }
- }
- }
+ 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 = hsize + fsize;
+ if (tctx->options & topt_use_crlf)
+ size += body_linecount; /* account for CRLF-expansion */
}
- /* If a string containing additional headers exists, expand it and write
- out the result. This is done last so that if it (deliberately or accidentally)
- isn't in header format, it won't mess up any other headers. An empty string
- or a forced expansion failure are noops. An added header string from a
- transport may not end with a newline; add one if it does not. */
+ /* If the message is large, emit first a non-LAST chunk with just the
+ headers, and reap the command responses. This lets us error out early
+ on RCPT rejects rather than sending megabytes of data. Include headers
+ on the assumption they are cheap enough and some clever implementations
+ might errorcheck them too, on-the-fly, and reject that chunk. */
- if (add_headers != NULL)
+ if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
{
- uschar *s = expand_string(add_headers);
- if (s == NULL)
- {
- if (!expand_string_forcedfail)
- {
- errno = ERRNO_CHHEADER_FAIL;
- return FALSE;
- }
- }
- else
- {
- int len = Ustrlen(s);
- if (len > 0)
- {
- if (!write_chunk(fd, s, len, use_crlf)) return FALSE;
- if (s[len-1] != '\n' && !write_chunk(fd, US"\n", 1, use_crlf))
- return FALSE;
- DEBUG(D_transport)
- {
- debug_printf("added header line(s):\n%s", s);
- if (s[len-1] != '\n') debug_printf("\n");
- debug_printf("---\n");
- }
- }
- }
+ DEBUG(D_transport)
+ debug_printf("sending small initial BDAT; hsize=%d\n", hsize);
+ if ( tctx->chunk_cb(fd, tctx, hsize, 0) != OK
+ || !transport_write_block(fd, deliver_out_buffer, hsize)
+ || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+ )
+ return FALSE;
+ chunk_ptr = deliver_out_buffer;
+ size -= hsize;
}
- /* Separate headers from body with a blank line */
+ /* Emit a LAST datachunk command. */
+
+ if (tctx->chunk_cb(fd, tctx, size, tc_chunk_last) != OK)
+ return FALSE;
- if (!write_chunk(fd, US"\n", 1, use_crlf)) return FALSE;
+ tctx->options &= ~topt_use_bdat;
}
/* If the body is required, ensure that the data for check strings (formerly
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) == 0)
+if (!(tctx->options & topt_no_body))
{
+ int size = size_limit;
+
nl_check_length = abs(nl_check_length);
nl_partial_match = 0;
- lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET);
- while ((len = read(deliver_datafile, deliver_in_buffer,
- DELIVER_IN_BUFFER_SIZE)) > 0)
+ if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
+ 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, use_crlf)) return FALSE;
- if (size_limit > 0)
- {
- written += len;
- if (written > size_limit)
- {
- len = 0; /* Pretend EOF */
- break;
- }
- }
+ if (!write_chunk(fd, tctx, deliver_in_buffer, len))
+ return FALSE;
+ size -= len;
}
/* 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) != 0 && !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. */
* External interface to write the message, while signing it with DKIM and/or Domainkeys *
***************************************************************************************************/
-/* This function is a wrapper around transport_write_message(). It is only called
- from the smtp transport if DKIM or Domainkeys support is compiled in.
- The function sets up a replacement fd into a -K file, then calls the normal
- function. This way, the exact bits that exim would have put "on the wire" will
- end up in the file (except for TLS encapsulation, which is the very
- very last thing). When we are done signing the file, send the
- signed message down the original fd (or TLS fd).
-
-Arguments: as for internal_transport_write_message() above, with additional
- arguments:
- uschar *dkim_private_key DKIM: The private key to use (filename or plain data)
- uschar *dkim_domain DKIM: The domain to use
- uschar *dkim_selector DKIM: The selector to use.
- uschar *dkim_canon DKIM: The canonalization scheme to use, "simple" or "relaxed"
- uschar *dkim_strict DKIM: What to do if signing fails: 1/true => throw error
- 0/false => send anyway
- uschar *dkim_sign_headers DKIM: List of headers that should be included in signature
- generation
+/* This function is a wrapper around transport_write_message().
+ It is only called from the smtp transport if DKIM or Domainkeys support
+ is compiled in. The function sets up a replacement fd into a -K file,
+ then calls the normal function. This way, the exact bits that exim would
+ have put "on the wire" will end up in the file (except for TLS
+ encapsulation, which is the very very last thing). When we are done
+ signing the file, send the signed message down the original fd (or TLS fd).
+
+Arguments:
+ as for internal_transport_write_message() above, with additional arguments
+ for DKIM.
Returns: TRUE on success; FALSE (with errno) for any failure
*/
BOOL
-dkim_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, uschar *dkim_private_key, uschar *dkim_domain,
- uschar *dkim_selector, uschar *dkim_canon, uschar *dkim_strict, uschar *dkim_sign_headers
- )
+dkim_transport_write_message(int out_fd, transport_ctx * tctx,
+ struct ob_dkim * dkim, const uschar ** err)
{
- int dkim_fd;
- int save_errno = 0;
- BOOL rc;
- uschar dkim_spool_name[256];
- char sbuf[2048];
- int sread = 0;
- int wwritten = 0;
- uschar *dkim_signature = NULL;
- off_t size = 0;
-
- if (!( ((dkim_private_key != NULL) && (dkim_domain != NULL) && (dkim_selector != NULL)) )) {
- /* If we can't sign, just call the original function. */
- return transport_write_message(addr, fd, options,
- size_limit, add_headers, remove_headers,
- check_string, escape_string, rewrite_rules,
- rewrite_existflags);
+int dkim_fd;
+int save_errno = 0;
+BOOL rc;
+uschar * dkim_spool_name;
+uschar * dkim_signature = NULL;
+int sread = 0, wwritten = 0, siglen = 0, options;
+off_t k_file_size;
+const uschar * errstr;
+
+/* 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(out_fd, tctx, 0);
+
+dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
+ string_sprintf("-%d-K", (int)getpid()));
+
+if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
+ {
+ /* Can't create spool file. Ugh. */
+ rc = FALSE;
+ save_errno = errno;
+ *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
+ goto CLEANUP;
}
- (void)string_format(dkim_spool_name, 256, "%s/input/%s/%s-%d-K",
- spool_directory, message_subdir, message_id, (int)getpid());
- dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE);
- if (dkim_fd < 0)
- {
- /* Can't create spool file. Ugh. */
- rc = FALSE;
- save_errno = errno;
- goto CLEANUP;
- }
+/* Call original function to write the -K file; does the CRLF expansion
+(but, in the CHUNKING case, not dot-stuffing and dot-termination). */
- /* Call original function */
- rc = transport_write_message(addr, dkim_fd, options,
- size_limit, add_headers, remove_headers,
- check_string, escape_string, rewrite_rules,
- rewrite_existflags);
+options = tctx->options;
+tctx->options &= ~topt_use_bdat;
+rc = transport_write_message(dkim_fd, tctx, 0);
+tctx->options = options;
- /* Save error state. We must clean up before returning. */
- if (!rc)
- {
- save_errno = errno;
- goto CLEANUP;
- }
+/* Save error state. We must clean up before returning. */
+if (!rc)
+ {
+ save_errno = errno;
+ goto CLEANUP;
+ }
- if ( (dkim_private_key != NULL) && (dkim_domain != NULL) && (dkim_selector != NULL) ) {
- /* Rewind file and feed it to the goats^W DKIM lib */
- lseek(dkim_fd, 0, SEEK_SET);
- dkim_signature = dkim_exim_sign(dkim_fd,
- dkim_private_key,
- dkim_domain,
- dkim_selector,
- dkim_canon,
- dkim_sign_headers);
- if (dkim_signature == NULL) {
- if (dkim_strict != NULL) {
- uschar *dkim_strict_result = expand_string(dkim_strict);
- if (dkim_strict_result != NULL) {
- if ( (strcmpic(dkim_strict,US"1") == 0) ||
- (strcmpic(dkim_strict,US"true") == 0) ) {
- /* Set errno to something halfway meaningful */
- save_errno = EACCES;
- log_write(0, LOG_MAIN, "DKIM: message could not be signed, and dkim_strict is set. Deferring message delivery.");
- rc = FALSE;
- goto CLEANUP;
- }
- }
- }
- }
- else {
- int siglen = Ustrlen(dkim_signature);
- while(siglen > 0) {
- #ifdef SUPPORT_TLS
- if (tls_active == fd) wwritten = tls_write(dkim_signature, siglen); else
- #endif
- wwritten = write(fd,dkim_signature,siglen);
- if (wwritten == -1) {
- /* error, bail out */
- save_errno = errno;
- rc = FALSE;
- goto CLEANUP;
- }
- siglen -= wwritten;
- dkim_signature += wwritten;
+/* Rewind file and feed it to the goats^W DKIM lib */
+dkim->dot_stuffed = !!(options & topt_end_dot);
+lseek(dkim_fd, 0, SEEK_SET);
+if ((dkim_signature = dkim_exim_sign(dkim_fd, dkim, &errstr)))
+ siglen = Ustrlen(dkim_signature);
+else if (dkim->dkim_strict)
+ {
+ uschar *dkim_strict_result = expand_string(dkim->dkim_strict);
+ if (dkim_strict_result)
+ if ( (strcmpic(dkim->dkim_strict,US"1") == 0) ||
+ (strcmpic(dkim->dkim_strict,US"true") == 0) )
+ {
+ /* Set errno to something halfway meaningful */
+ save_errno = EACCES;
+ log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
+ " and dkim_strict is set. Deferring message delivery.");
+ *err = errstr;
+ rc = FALSE;
+ goto CLEANUP;
}
+ }
+
+#ifndef HAVE_LINUX_SENDFILE
+if (options & topt_use_bdat)
+#endif
+ k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
+
+if (options & topt_use_bdat)
+ {
+
+ /* On big messages output a precursor chunk to get any pipelined
+ MAIL & RCPT commands flushed, then reap the responses so we can
+ error out on RCPT rejects before sending megabytes. */
+
+ if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0)
+ {
+ if ( tctx->chunk_cb(out_fd, tctx, siglen, 0) != OK
+ || !transport_write_block(out_fd, dkim_signature, siglen)
+ || tctx->chunk_cb(out_fd, tctx, 0, tc_reap_prev) != OK
+ )
+ goto err;
+ siglen = 0;
}
+
+ if (tctx->chunk_cb(out_fd, tctx, siglen + k_file_size, tc_chunk_last) != OK)
+ goto err;
}
- /* Fetch file positition (the size) */
- size = lseek(dkim_fd,0,SEEK_CUR);
+if(siglen > 0 && !transport_write_block(out_fd, dkim_signature, siglen))
+ goto err;
+
+#ifdef HAVE_LINUX_SENDFILE
+/* We can use sendfile() to shove the file contents
+ to the socket. However only if we don't use TLS,
+ as then there's another layer of indirection
+ before the data finally hits the socket. */
+if (tls_out.active != out_fd)
+ {
+ ssize_t copied = 0;
+ off_t offset = 0;
/* Rewind file */
lseek(dkim_fd, 0, SEEK_SET);
-#ifdef HAVE_LINUX_SENDFILE
- /* We can use sendfile() to shove the file contents
- to the socket. However only if we don't use TLS,
- in which case theres another layer of indirection
- before the data finally hits the socket. */
- if (tls_active != fd)
- {
- ssize_t copied = 0;
- off_t offset = 0;
- while((copied >= 0) && (offset<size))
- {
- copied = sendfile(fd, dkim_fd, &offset, (size - offset));
- }
- if (copied < 0)
- {
- save_errno = errno;
- rc = FALSE;
- }
- goto CLEANUP;
- }
+ while(copied >= 0 && offset < k_file_size)
+ copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset);
+ if (copied < 0)
+ goto err;
+ }
+else
+
#endif
+ {
+ /* Rewind file */
+ lseek(dkim_fd, 0, SEEK_SET);
+
/* Send file down the original fd */
- while((sread = read(dkim_fd,sbuf,2048)) > 0)
+ while((sread = read(dkim_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
{
- char *p = sbuf;
+ uschar * p = deliver_out_buffer;
/* write the chunk */
- DKIM_WRITE:
- #ifdef SUPPORT_TLS
- if (tls_active == fd) wwritten = tls_write(US p, sread); else
- #endif
- wwritten = write(fd,p,sread);
- if (wwritten == -1)
- {
- /* error, bail out */
- save_errno = errno;
- rc = FALSE;
- goto CLEANUP;
- }
- if (wwritten < sread)
+
+ while (sread)
{
- /* short write, try again */
+#ifdef SUPPORT_TLS
+ wwritten = tls_out.active == out_fd
+ ? tls_write(FALSE, p, sread)
+ : write(out_fd, CS p, sread);
+#else
+ wwritten = write(out_fd, CS p, sread);
+#endif
+ if (wwritten == -1)
+ goto err;
p += wwritten;
sread -= wwritten;
- goto DKIM_WRITE;
}
}
{
save_errno = errno;
rc = FALSE;
- goto CLEANUP;
}
+ }
- CLEANUP:
+CLEANUP:
/* unlink -K file */
(void)close(dkim_fd);
Uunlink(dkim_spool_name);
errno = save_errno;
return rc;
+
+err:
+ save_errno = errno;
+ rc = FALSE;
+ goto CLEANUP;
}
#endif
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;
BOOL last_filter_was_NL = TRUE;
int rc, len, yield, fd_read, fd_write, save_errno;
-int pfd[2];
+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;
/* If there is no filter command set up, call the internal function that does
the actual work, passing it the incoming fd, and return its result. */
-if (transport_filter_argv == NULL)
- return internal_transport_write_message(addr, fd, options, size_limit,
- add_headers, remove_headers, check_string, escape_string,
- rewrite_rules, rewrite_existflags);
+if ( !transport_filter_argv
+ || !*transport_filter_argv
+ || !**transport_filter_argv
+ )
+ 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;
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;
yield = FALSE;
write_pid = (pid_t)(-1);
-(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
-filter_pid = child_open(transport_filter_argv, NULL, 077, &fd_write, &fd_read,
- FALSE);
-(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
+ {
+ int bits = fcntl(fd, F_GETFD);
+ (void)fcntl(fd, F_SETFD, bits | FD_CLOEXEC);
+ filter_pid = child_open(USS transport_filter_argv, NULL, 077,
+ &fd_write, &fd_read, FALSE);
+ (void)fcntl(fd, F_SETFD, bits & ~FD_CLOEXEC);
+ }
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;
- (void)write(pfd[pipe_write], (void *)&rc, sizeof(BOOL));
- (void)write(pfd[pipe_write], (void *)&save_errno, sizeof(int));
- (void)write(pfd[pipe_write], (void *)&(addr->more_errno), sizeof(int));
+ 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 *)&tctx->addr->more_errno, sizeof(int))
+ != sizeof(int)
+ )
+ rc = FALSE; /* compiler quietening */
_exit(0);
}
save_errno = errno;
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);
}
{
rc = child_close(write_pid, 30);
if (yield)
- {
if (rc == 0)
{
BOOL ok;
- (void)read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
- if (!ok)
+ if (read(pfd[pipe_read], (void *)&ok, sizeof(BOOL)) != sizeof(BOOL))
+ {
+ DEBUG(D_transport)
+ debug_printf("pipe read from writing process: %s\n", strerror(errno));
+ save_errno = ERRNO_FILTER_FAIL;
+ yield = FALSE;
+ }
+ else if (!ok)
{
- (void)read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
- (void)read(pfd[pipe_read], (void *)&(addr->more_errno), sizeof(int));
+ int dummy = read(pfd[pipe_read], (void *)&save_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);
}
- }
}
(void)close(pfd[pipe_read]);
if (yield)
{
nl_check_length = nl_escape_length = 0;
- if ((options & topt_end_dot) != 0 && (last_filter_was_NL?
- !write_chunk(fd, US".\n", 2, use_crlf) :
- !write_chunk(fd, US"\n.\n", 3, use_crlf)))
- {
+ if ( tctx->options & topt_end_dot
+ && ( last_filter_was_NL
+ ? !write_chunk(fd, tctx, US".\n", 2)
+ : !write_chunk(fd, tctx, US"\n.\n", 3)
+ ) )
yield = FALSE;
- }
/* Write out any remaining data in the buffer. */
else
- {
- yield = (len = chunk_ptr - deliver_out_buffer) <= 0 ||
- transport_write_block(fd, deliver_out_buffer, len);
- }
+ yield = (len = chunk_ptr - deliver_out_buffer) <= 0
+ || transport_write_block(fd, deliver_out_buffer, len);
}
-else errno = save_errno; /* From some earlier error */
+else
+ errno = save_errno; /* From some earlier error */
DEBUG(D_transport)
{
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;
transport_update_waiting(host_item *hostlist, uschar *tpname)
{
uschar buffer[256];
-uschar *prevname = US"";
+const uschar *prevname = US"";
host_item *host;
open_db dbblock;
open_db *dbm_file;
as set by the caller transport
new_message_id set to the message id of a waiting message
more set TRUE if there are yet more messages waiting
+ oicf_func function to call to validate if it is ok to send
+ to this message_id from the current instance.
+ oicf_data opaque data for oicf_func
Returns: TRUE if new_message_id set; FALSE otherwise
*/
+typedef struct msgq_s
+{
+ uschar message_id [MESSAGE_ID_LENGTH + 1];
+ BOOL bKeep;
+} msgq_t;
+
BOOL
-transport_check_waiting(uschar *transport_name, uschar *hostname,
- int local_message_max, uschar *new_message_id, BOOL *more)
+transport_check_waiting(const uschar *transport_name, const uschar *hostname,
+ int local_message_max, uschar *new_message_id, BOOL *more, oicf oicf_func, void *oicf_data)
{
dbdata_wait *host_record;
-int host_length, path_len;
+int host_length;
open_db dbblock;
open_db *dbm_file;
uschar buffer[256];
+int i;
+struct stat statbuf;
+
*more = FALSE;
DEBUG(D_transport)
/* See if there is a record for this host; if not, there's nothing to do. */
-host_record = dbfn_read(dbm_file, hostname);
-if (host_record == NULL)
+if (!(host_record = dbfn_read(dbm_file, hostname)))
{
dbfn_close(dbm_file);
DEBUG(D_transport) debug_printf("no messages waiting for %s\n", hostname);
emptied, delete it and continue with any continuation records that may exist.
*/
-host_length = host_record->count * MESSAGE_ID_LENGTH;
+/* For Bug 1141, I refactored this major portion of the routine, it is risky
+but the 1 off will remain without it. This code now allows me to SKIP over
+a message I do not want to send out on this run. */
-/* Loop to handle continuation host records in the database */
+host_length = host_record->count * MESSAGE_ID_LENGTH;
-for (;;)
+while (1)
{
- BOOL found = FALSE;
+ msgq_t *msgq;
+ int msgq_count = 0;
+ int msgq_actual = 0;
+ BOOL bFound = FALSE;
+ BOOL bContinuation = FALSE;
- sprintf(CS buffer, "%s/input/", spool_directory);
- path_len = Ustrlen(buffer);
+ /* create an array to read entire message queue into memory for processing */
- for (host_length -= MESSAGE_ID_LENGTH; host_length >= 0;
- host_length -= MESSAGE_ID_LENGTH)
+ msgq = store_malloc(sizeof(msgq_t) * host_record->count);
+ msgq_count = host_record->count;
+ msgq_actual = msgq_count;
+
+ for (i = 0; i < host_record->count; ++i)
{
- struct stat statbuf;
- Ustrncpy(new_message_id, host_record->text + host_length,
+ msgq[i].bKeep = TRUE;
+
+ Ustrncpy(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH),
MESSAGE_ID_LENGTH);
- new_message_id[MESSAGE_ID_LENGTH] = 0;
+ msgq[i].message_id[MESSAGE_ID_LENGTH] = 0;
+ }
- if (split_spool_directory)
- sprintf(CS(buffer + path_len), "%c/%s-D", new_message_id[5], new_message_id);
- else
- sprintf(CS(buffer + path_len), "%s-D", new_message_id);
+ /* first thing remove current message id if it exists */
+
+ for (i = 0; i < msgq_count; ++i)
+ if (Ustrcmp(msgq[i].message_id, message_id) == 0)
+ {
+ msgq[i].bKeep = FALSE;
+ break;
+ }
+
+ /* now find the next acceptable message_id */
+
+ for (i = msgq_count - 1; i >= 0; --i) if (msgq[i].bKeep)
+ {
+ uschar subdir[2];
- /* The listed message may be the one we are currently processing. If
- so, we want to remove it from the list without doing anything else.
- If not, do a stat to see if it is an existing message. If it is, break
- the loop to handle it. No need to bother about locks; as this is all
- "hint" processing, it won't matter if it doesn't exist by the time exim
- actually tries to deliver it. */
+ subdir[0] = split_spool_directory ? msgq[i].message_id[5] : 0;
+ subdir[1] = 0;
- if (Ustrcmp(new_message_id, message_id) != 0 &&
- Ustat(buffer, &statbuf) == 0)
+ if (Ustat(spool_fname(US"input", subdir, msgq[i].message_id, US"-D"),
+ &statbuf) != 0)
+ msgq[i].bKeep = FALSE;
+ else if (!oicf_func || oicf_func(msgq[i].message_id, oicf_data))
{
- found = TRUE;
+ Ustrcpy(new_message_id, msgq[i].message_id);
+ msgq[i].bKeep = FALSE;
+ bFound = TRUE;
break;
}
}
- /* If we have removed all the message ids from the record delete the record.
- If there is a continuation record, fetch it and remove it from the file,
- as it will be rewritten as the main record. Repeat in the case of an
- empty continuation. */
+ /* re-count */
+ for (msgq_actual = 0, i = 0; i < msgq_count; ++i)
+ if (msgq[i].bKeep)
+ msgq_actual++;
+
+ /* reassemble the host record, based on removed message ids, from in
+ memory queue */
+
+ if (msgq_actual <= 0)
+ {
+ host_length = 0;
+ host_record->count = 0;
+ }
+ else
+ {
+ host_length = msgq_actual * MESSAGE_ID_LENGTH;
+ host_record->count = msgq_actual;
+
+ if (msgq_actual < msgq_count)
+ {
+ int new_count;
+ for (new_count = 0, i = 0; i < msgq_count; ++i)
+ if (msgq[i].bKeep)
+ Ustrncpy(&host_record->text[new_count++ * MESSAGE_ID_LENGTH],
+ msgq[i].message_id, MESSAGE_ID_LENGTH);
+
+ host_record->text[new_count * MESSAGE_ID_LENGTH] = 0;
+ }
+ }
+
+/* Jeremy: check for a continuation record, this code I do not know how to
+test but the code should work */
while (host_length <= 0)
{
int i;
- dbdata_wait *newr = NULL;
+ dbdata_wait * newr = NULL;
/* Search for a continuation */
- for (i = host_record->sequence - 1; i >= 0 && newr == NULL; i--)
+ for (i = host_record->sequence - 1; i >= 0 && !newr; i--)
{
sprintf(CS buffer, "%.200s:%d", hostname, i);
newr = dbfn_read(dbm_file, buffer);
/* If no continuation, delete the current and break the loop */
- if (newr == NULL)
+ if (!newr)
{
dbfn_delete(dbm_file, hostname);
break;
dbfn_delete(dbm_file, buffer);
host_record = newr;
host_length = host_record->count * MESSAGE_ID_LENGTH;
- }
- /* If we found an existing message, break the continuation loop. */
+ bContinuation = TRUE;
+ }
- if (found) break;
+ if (bFound) /* Usual exit from main loop */
+ {
+ store_free (msgq);
+ break;
+ }
/* If host_length <= 0 we have emptied a record and not found a good message,
and there are no continuation records. Otherwise there is a continuation
DEBUG(D_transport) debug_printf("waiting messages already delivered\n");
return FALSE;
}
- }
+
+ /* we were not able to find an acceptable message, nor was there a
+ * continuation record. So bug out, outer logic will clean this up.
+ */
+
+ if (!bContinuation)
+ {
+ Ustrcpy(new_message_id, message_id);
+ dbfn_close(dbm_file);
+ return FALSE;
+ }
+
+ store_free(msgq);
+ } /* we need to process a continuation record */
/* Control gets here when an existing message has been encountered; its
id is in new_message_id, and host_length is the revised length of the
if (host_length > 0)
{
host_record->count = host_length/MESSAGE_ID_LENGTH;
+
dbfn_write(dbm_file, hostname, host_record, (int)sizeof(dbdata_wait) + host_length);
*more = TRUE;
}
return TRUE;
}
-
-
/*************************************************
* Deliver waiting message down same socket *
*************************************************/
*/
BOOL
-transport_pass_socket(uschar *transport_name, uschar *hostname,
- uschar *hostaddress, uschar *id, int socket_fd)
+transport_pass_socket(const uschar *transport_name, const uschar *hostname,
+ const uschar *hostaddress, uschar *id, int socket_fd)
{
pid_t pid;
int status;
if ((pid = fork()) == 0)
{
- int i = 16;
- uschar **argv;
+ int i = 20;
+ const uschar **argv;
/* Disconnect entirely from the parent process. If we are running in the
test harness, wait for a bit to allow the previous process time to finish,
/* Set up the calling arguments; use the standard function for the basics,
but we have a number of extras that may be added. */
- argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
+ argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
if (smtp_authenticated) argv[i++] = US"-MCA";
- #ifdef SUPPORT_TLS
- if (tls_offered) argv[i++] = US"-MCT";
- #endif
-
- if (smtp_use_size) argv[i++] = US"-MCS";
- if (smtp_use_pipelining) argv[i++] = US"-MCP";
+ if (smtp_peer_options & PEER_OFFERED_CHUNKING) argv[i++] = US"-MCK";
+ if (smtp_peer_options & PEER_OFFERED_DSN) argv[i++] = US"-MCD";
+ if (smtp_peer_options & PEER_OFFERED_PIPE) argv[i++] = US"-MCP";
+ if (smtp_peer_options & PEER_OFFERED_SIZE) argv[i++] = US"-MCS";
+#ifdef SUPPORT_TLS
+ if (smtp_peer_options & PEER_OFFERED_TLS)
+ if (tls_out.active >= 0 || continue_proxy_cipher)
+ {
+ argv[i++] = US"-MCt";
+ argv[i++] = sending_ip_address;
+ argv[i++] = string_sprintf("%d", sending_port);
+ argv[i++] = tls_out.active >= 0 ? tls_out.cipher : continue_proxy_cipher;
+ }
+ else
+ argv[i++] = US"-MCT";
+#endif
if (queue_run_pid != (pid_t)0)
{
}
argv[i++] = US"-MC";
- argv[i++] = transport_name;
- argv[i++] = hostname;
- argv[i++] = hostaddress;
+ argv[i++] = US transport_name;
+ argv[i++] = US hostname;
+ argv[i++] = US hostaddress;
argv[i++] = string_sprintf("%d", continue_sequence + 1);
argv[i++] = id;
argv[i++] = NULL;
Arguments:
argvptr pointer to anchor for argv vector
- cmd points to the command string
+ cmd points to the command string (modified IN PLACE)
expand_arguments true if expansion is to occur
expand_failed error value to set if expansion fails; not relevant if
addr == NULL
*/
BOOL
-transport_set_up_command(uschar ***argvptr, uschar *cmd, BOOL expand_arguments,
- int expand_failed, address_item *addr, uschar *etext, uschar **errptr)
+transport_set_up_command(const uschar ***argvptr, uschar *cmd,
+ BOOL expand_arguments, int expand_failed, address_item *addr,
+ uschar *etext, uschar **errptr)
{
address_item *ad;
-uschar **argv;
+const uschar **argv;
uschar *s, *ss;
int address_count = 0;
int argcount = 0;
if (*s != 0) s++;
*ss++ = 0;
}
- else argv[argcount++] = string_dequote(&s);
+ else argv[argcount++] = string_copy(string_dequote(CUSS &s));
while (isspace(*s)) s++;
}
memmove(argv + i + 1 + additional, argv + i + 1,
(argcount - i)*sizeof(uschar *));
- for (ad = addr; ad != NULL; ad = ad->next) argv[i++] = ad->address;
+ for (ad = addr; ad != NULL; ad = ad->next) {
+ argv[i++] = ad->address;
+ argcount++;
+ }
+
+ /* Subtract one since we replace $pipe_addresses */
+ argcount--;
+ i--;
+ }
+
+ /* Handle special case of $address_pipe when af_force_command is set */
+
+ else if (addr != NULL && testflag(addr,af_force_command) &&
+ (Ustrcmp(argv[i], "$address_pipe") == 0 ||
+ Ustrcmp(argv[i], "${address_pipe}") == 0))
+ {
+ int address_pipe_i;
+ int address_pipe_argcount = 0;
+ int address_pipe_max_args;
+ uschar **address_pipe_argv;
+
+ /* We can never have more then the argv we will be loading into */
+ address_pipe_max_args = max_args - argcount + 1;
+
+ DEBUG(D_transport)
+ debug_printf("address_pipe_max_args=%d\n", address_pipe_max_args);
+
+ /* We allocate an additional for (uschar *)0 */
+ address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *));
+
+ /* +1 because addr->local_part[0] == '|' since af_force_command is set */
+ s = expand_string(addr->local_part + 1);
+
+ if (s == NULL || *s == '\0')
+ {
+ addr->transport_return = FAIL;
+ addr->message = string_sprintf("Expansion of \"%s\" "
+ "from command \"%s\" in %s failed: %s",
+ (addr->local_part + 1), cmd, etext, expand_string_message);
+ return FALSE;
+ }
+
+ while (isspace(*s)) s++; /* strip leading space */
+
+ while (*s != 0 && address_pipe_argcount < address_pipe_max_args)
+ {
+ if (*s == '\'')
+ {
+ ss = s + 1;
+ while (*ss != 0 && *ss != '\'') ss++;
+ address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++);
+ while (*s != 0 && *s != '\'') *ss++ = *s++;
+ if (*s != 0) s++;
+ *ss++ = 0;
+ }
+ else address_pipe_argv[address_pipe_argcount++] =
+ string_copy(string_dequote(CUSS &s));
+ while (isspace(*s)) s++; /* strip space after arg */
+ }
+
+ address_pipe_argv[address_pipe_argcount] = (uschar *)0;
+
+ /* If *s != 0 we have run out of argument slots. */
+ if (*s != 0)
+ {
+ uschar *msg = string_sprintf("Too many arguments in $address_pipe "
+ "\"%s\" in %s", addr->local_part + 1, etext);
+ if (addr != NULL)
+ {
+ addr->transport_return = FAIL;
+ addr->message = msg;
+ }
+ else *errptr = msg;
+ return FALSE;
+ }
+
+ /* address_pipe_argcount - 1
+ * because we are replacing $address_pipe in the argument list
+ * with the first thing it expands to */
+ if (argcount + address_pipe_argcount - 1 > max_args)
+ {
+ addr->transport_return = FAIL;
+ addr->message = string_sprintf("Too many arguments to command "
+ "\"%s\" after expanding $address_pipe in %s", cmd, etext);
+ return FALSE;
+ }
+
+ /* If we are not just able to replace the slot that contained
+ * $address_pipe (address_pipe_argcount == 1)
+ * We have to move the existing argv by address_pipe_argcount - 1
+ * Visually if address_pipe_argcount == 2:
+ * [argv 0][argv 1][argv 2($address_pipe)][argv 3][0]
+ * [argv 0][argv 1][ap_arg0][ap_arg1][old argv 3][0]
+ */
+ if (address_pipe_argcount > 1)
+ memmove(
+ /* current position + additional args */
+ argv + i + address_pipe_argcount,
+ /* current position + 1 (for the (uschar *)0 at the end) */
+ argv + i + 1,
+ /* -1 for the (uschar *)0 at the end)*/
+ (argcount - i)*sizeof(uschar *)
+ );
+
+ /* Now we fill in the slots we just moved argv out of
+ * [argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0]
+ */
+ for (address_pipe_i = 0;
+ address_pipe_argv[address_pipe_i] != (uschar *)0;
+ address_pipe_i++)
+ {
+ argv[i++] = address_pipe_argv[address_pipe_i];
+ argcount++;
+ }
+
+ /* Subtract one since we replace $address_pipe */
+ argcount--;
i--;
}
else
{
- uschar *expanded_arg;
+ const uschar *expanded_arg;
enable_dollar_recipients = allow_dollar_recipients;
- expanded_arg = expand_string(argv[i]);
+ expanded_arg = expand_cstring(argv[i]);
enable_dollar_recipients = FALSE;
if (expanded_arg == NULL)
return TRUE;
}
+/* vi: aw ai sw=2
+*/
/* End of transport.c */