+#ifndef DISABLE_DKIM
+
+/***************************************************************************************************
+* 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
+
+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
+ )
+{
+ 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);
+ }
+
+ (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 */
+ 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;
+ }
+
+ 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_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;
+ }
+ }
+ }
+
+ /* Fetch file positition (the size) */
+ size = lseek(dkim_fd,0,SEEK_CUR);
+
+ /* 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)
+ {
+ 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;
+ }
+#endif
+
+ /* Send file down the original fd */
+ 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)
+ {
+ /* short write, try again */
+ p += wwritten;
+ sread -= wwritten;
+ goto DKIM_WRITE;
+ }
+ }
+
+ if (sread == -1)
+ {
+ save_errno = errno;
+ rc = FALSE;
+ goto CLEANUP;
+ }
+
+ CLEANUP:
+ /* unlink -K file */
+ (void)close(dkim_fd);
+ Uunlink(dkim_spool_name);
+ errno = save_errno;
+ return rc;
+}
+
+#endif
+