* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
/* 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)) },
+#ifdef EXPERIMENTAL_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|opt_rep_str,
+/* Add/remove/rewwrite headers, and send them plus the empty-line sparator.
+
+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
+ sendfn function for output
+ use_crlf turn NL into CR LF
+ rewrite_rules chain of header rewriting rules
+ rewrite_existflags flags for the rewriting rules
+
+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)
+{
+header_line *h;
+
+/* 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->p.remove_headers too, provided that addr is not NULL. */
+
+for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
+ {
+ int i;
+ uschar *list = remove_headers;
+
+ BOOL include_header = TRUE;
+
+ for (i = 0; i < 2; i++) /* For remove_headers && addr->p.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 = 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)
+ {
+ void *reset_point = store_get(0);
+ header_line *hh;
+
+ if ((hh = rewrite_header(h, NULL, NULL, rewrite_rules, rewrite_existflags, FALSE)))
+ {
+ if (!sendfn(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 (!sendfn(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.
+
+Headers added to an address by a router are guaranteed to end with a newline.
+*/
+
+if (addr)
+ {
+ 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 (!sendfn(fd, h->text, h->slen, use_crlf)) 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 (add_headers)
+ {
+ int sep = '\n';
+ uschar * s;
+
+ while ((s = string_nextinlist(&add_headers, &sep, NULL, 0)))
+ if (!(s = expand_string(s)))
+ {
+ if (!expand_string_forcedfail)
+ { errno = ERRNO_CHHEADER_FAIL; return FALSE; }
+ }
+ else
+ {
+ 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))
+ return FALSE;
+ DEBUG(D_transport)
+ {
+ debug_printf("added header line:\n%s", s);
+ if (s[len-1] != '\n') debug_printf("\n");
+ debug_printf("---\n");
+ }
+ }
+ }
+ }
+
+/* Separate headers from body with a blank line */
+
+return sendfn(fd, US"\n", 1, use_crlf);
+}
+
+
/*************************************************
* Write the message *
*************************************************/
{
int written = 0;
int len;
-header_line *h;
BOOL use_crlf = (options & topt_use_crlf) != 0;
/* Initialize pointer in output buffer. */
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
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.
-
- Headers added to an address by a router are guaranteed to end with a newline.
- */
-
- if (addr != NULL)
- {
- 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 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 (add_headers != NULL)
- {
- 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");
- }
- }
- }
- }
-
- /* Separate headers from body with a blank line */
-
- if (!write_chunk(fd, US"\n", 1, use_crlf)) return FALSE;
+ if (!transport_headers_send(addr, fd, add_headers, remove_headers, &write_chunk,
+ use_crlf, rewrite_rules, rewrite_existflags))
+ return FALSE;
}
/* If the body is required, ensure that the data for check strings (formerly
* 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:
+ 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
Returns: TRUE on success; FALSE (with errno) for any failure
*/
uschar *dkim_selector, uschar *dkim_canon, uschar *dkim_strict, uschar *dkim_sign_headers
)
{
- 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[256];
+char sbuf[2048];
+int sread = 0;
+int wwritten = 0;
+uschar *dkim_signature = NULL;
+
+/* If we can't sign, just call the original function. */
+
+if (!(dkim_private_key && dkim_domain && dkim_selector))
+ return transport_write_message(addr, fd, options,
+ size_limit, add_headers, remove_headers,
+ check_string, escape_string, rewrite_rules,
+ rewrite_existflags);
+
+(void)string_format(dkim_spool_name, 256, "%s/input/%s/%s-%d-K",
+ spool_directory, message_subdir, message_id, (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;
+ 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 */
- /* 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);
+rc = transport_write_message(addr, dkim_fd, options,
+ size_limit, add_headers, remove_headers,
+ check_string, escape_string, rewrite_rules,
+ rewrite_existflags);
- /* 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;
- }
- }
+if (dkim_private_key && dkim_domain && dkim_selector)
+ {
+ /* 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)
+ {
+ if (dkim_strict)
+ {
+ uschar *dkim_strict_result = expand_string(dkim_strict);
+ if (dkim_strict_result)
+ 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_out.active == fd) wwritten = tls_write(FALSE, 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;
+ else
+ {
+ int siglen = Ustrlen(dkim_signature);
+ while(siglen > 0)
+ {
+#ifdef SUPPORT_TLS
+ wwritten = tls_out.active == fd
+ ? tls_write(FALSE, dkim_signature, siglen)
+ : write(fd, dkim_signature, siglen);
+#else
+ wwritten = write(fd, dkim_signature, siglen);
+#endif
+ if (wwritten == -1)
+ {
+ /* error, bail out */
+ save_errno = errno;
+ rc = FALSE;
+ goto CLEANUP;
+ }
+ siglen -= wwritten;
+ dkim_signature += wwritten;
}
}
}
- /* Fetch file positition (the size) */
- size = lseek(dkim_fd,0,SEEK_CUR);
+#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 != fd)
+ {
+ off_t size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
+ 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_out.active != fd)
+ while(copied >= 0 && offset < size)
+ copied = sendfile(fd, dkim_fd, &offset, size - offset);
+ if (copied < 0)
{
- 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;
+ save_errno = errno;
+ rc = FALSE;
}
+ }
+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, sbuf, 2048)) > 0)
{
char *p = sbuf;
/* write the chunk */
- DKIM_WRITE:
- #ifdef SUPPORT_TLS
- if (tls_out.active == fd) wwritten = tls_write(FALSE, 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 == fd
+ ? tls_write(FALSE, US p, sread)
+ : write(fd, p, sread);
+#else
+ wwritten = write(fd, p, sread);
+#endif
+ if (wwritten == -1)
+ {
+ /* error, bail out */
+ save_errno = errno;
+ rc = FALSE;
+ goto CLEANUP;
+ }
p += wwritten;
sread -= wwritten;
- goto DKIM_WRITE;
}
}
{
save_errno = errno;
rc = FALSE;
- goto CLEANUP;
}
+ }
- CLEANUP:
- /* unlink -K file */
- (void)close(dkim_fd);
- Uunlink(dkim_spool_name);
- errno = save_errno;
- return rc;
+CLEANUP:
+/* unlink -K file */
+(void)close(dkim_fd);
+Uunlink(dkim_spool_name);
+errno = save_errno;
+return rc;
}
#endif
argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
+ #ifdef EXPERIMENTAL_DSN
+ /* Call with the dsn flag */
+ if (smtp_use_dsn) argv[i++] = US"-MCD";
+ #endif
+
if (smtp_authenticated) argv[i++] = US"-MCA";
#ifdef SUPPORT_TLS
return TRUE;
}
+/* vi: aw ai sw=2
+*/
/* End of transport.c */