[Buzilla 376] Preliminary DKIM support
[exim.git] / src / src / transport.c
index 6630f36ee87ba3f3b51af6d094395e02f5772978..4843d5250d9d6607ae6bdbccf37b6e8a0999c62e 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/transport.c,v 1.10 2005/06/20 11:20:41 ph10 Exp $ */
+/* $Cambridge: exim/src/src/transport.c,v 1.20 2007/09/28 12:21:57 tom Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* General functions concerned with transportation, and generic options for all
@@ -13,6 +13,9 @@ transports. */
 
 #include "exim.h"
 
+#ifdef HAVE_LINUX_SENDFILE
+#include <sys/sendfile.h>
+#endif
 
 /* Structure for keeping list of addresses that have been added to
 Envelope-To:, in order to avoid duplication. */
@@ -983,10 +986,11 @@ dk_transport_write_message(address_item *addr, int fd, int options,
   int sread = 0;
   int wwritten = 0;
   uschar *dk_signature = NULL;
+  off_t size = 0;
 
-  snprintf(CS dk_spool_name, 256, "%s/input/%s/%s-K",
-          spool_directory, message_subdir, message_id);
-  dk_fd = Uopen(dk_spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
+  (void)string_format(dk_spool_name, 256, "%s/input/%s/%s-%d-K",
+          spool_directory, message_subdir, message_id, (int)getpid());
+  dk_fd = Uopen(dk_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE);
   if (dk_fd < 0)
     {
     /* Can't create spool file. Ugh. */
@@ -1052,9 +1056,35 @@ dk_transport_write_message(address_item *addr, int fd, int options,
       }
     }
 
-  /* Rewind file and send it down the original fd. */
+  /* Fetch file positition (the size) */
+  size = lseek(dk_fd,0,SEEK_CUR);
+
+  /* Rewind file */
   lseek(dk_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, dk_fd, &offset, (size - offset));
+      }
+    if (copied < 0)
+      {
+      save_errno = errno;
+      rc = FALSE;
+      }
+    goto CLEANUP;
+    }
+#endif
+
+  /* Send file down the original fd */
   while((sread = read(dk_fd,sbuf,2048)) > 0)
     {
     char *p = sbuf;
@@ -1087,10 +1117,9 @@ dk_transport_write_message(address_item *addr, int fd, int options,
     goto CLEANUP;
     }
 
-
   CLEANUP:
   /* unlink -K file */
-  close(dk_fd);
+  (void)close(dk_fd);
   Uunlink(dk_spool_name);
   errno = save_errno;
   return rc;
@@ -1098,6 +1127,193 @@ dk_transport_write_message(address_item *addr, int fd, int options,
 #endif
 
 
+
+#ifdef EXPERIMENTAL_DKIM
+
+/**********************************************************************************
+*    External interface to write the message, while signing it with DKIM          *
+**********************************************************************************/
+
+/* This function is a wrapper around transport_write_message(). It is only called
+   from the smtp transport if
+   (1) DKIM support is compiled in.
+   (2) The dkim_private_key and dkim_domain option on the smtp transport is set.
+   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         The private key to use (filename or plain data)
+               uschar *dkim_domain              The domain to use
+               uschar *dkim_selector            The selector to use.
+               uschar *dkim_canon               The canonalization scheme to use, "simple" or "relaxed"
+               uschar *dkim_strict              What to do if signing fails: 1/true  => throw error
+                                                                             0/false => send anyway
+
+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;
+
+  (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;
+    }
+
+  /* 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)
+    {
+    /* Send the signature first */
+    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;
+      }
+    }
+  else 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) )
+        {
+        save_errno = errno;
+        rc = FALSE;
+        goto CLEANUP;
+        }
+      }
+    }
+
+  /* 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_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_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)
+      {
+      /* 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
+
+
+
 /*************************************************
 *    External interface to write the message     *
 *************************************************/
@@ -1164,10 +1380,10 @@ save_errno = 0;
 yield = FALSE;
 write_pid = (pid_t)(-1);
 
-fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+(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);
-fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
+(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
 DEBUG(D_transport)
@@ -1182,25 +1398,25 @@ if (pipe(pfd) != 0) goto TIDY_UP;      /* errno set */
 if ((write_pid = fork()) == 0)
   {
   BOOL rc;
-  close(fd_read);
-  close(pfd[pipe_read]);
+  (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);
   save_errno = errno;
-  write(pfd[pipe_write], (void *)&rc, sizeof(BOOL));
-  write(pfd[pipe_write], (void *)&save_errno, sizeof(int));
-  write(pfd[pipe_write], (void *)&(addr->more_errno), sizeof(int));
+  (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));
   _exit(0);
   }
 save_errno = errno;
 
 /* Parent process: close our copy of the writing subprocess' pipes. */
 
-close(pfd[pipe_write]);
-close(fd_write);
+(void)close(pfd[pipe_write]);
+(void)close(fd_write);
 fd_write = -1;
 
 /* Writing process creation failed */
@@ -1270,8 +1486,8 @@ sure. Also apply a paranoia timeout. */
 TIDY_UP:
 save_errno = errno;
 
-close(fd_read);
-if (fd_write > 0) close(fd_write);
+(void)close(fd_read);
+if (fd_write > 0) (void)close(fd_write);
 
 if (!yield)
   {
@@ -1303,11 +1519,11 @@ if (write_pid > 0)
     if (rc == 0)
       {
       BOOL ok;
-      read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
+      (void)read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
       if (!ok)
         {
-        read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
-        read(pfd[pipe_read], (void *)&(addr->more_errno), sizeof(int));
+        (void)read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
+        (void)read(pfd[pipe_read], (void *)&(addr->more_errno), sizeof(int));
         yield = FALSE;
         }
       }
@@ -1320,7 +1536,7 @@ if (write_pid > 0)
       }
     }
   }
-close(pfd[pipe_read]);
+(void)close(pfd[pipe_read]);
 
 /* If there have been no problems we can now add the terminating "." if this is
 SMTP output, turning off escaping beforehand. If the last character from the
@@ -1389,8 +1605,7 @@ better.
 Old records should eventually get swept up by the exim_tidydb utility.
 
 Arguments:
-  hostlist  list of hosts that this message could be sent to;
-              the update_waiting flag is set if a host is to be noted
+  hostlist  list of hosts that this message could be sent to
   tpname    name of the transport
 
 Returns:    nothing
@@ -1405,6 +1620,8 @@ host_item *host;
 open_db dbblock;
 open_db *dbm_file;
 
+DEBUG(D_transport) debug_printf("updating wait-%s database\n", tpname);
+
 /* Open the database for this transport */
 
 sprintf(CS buffer, "wait-%.200s", tpname);
@@ -1412,8 +1629,7 @@ dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
 if (dbm_file == NULL) return;
 
 /* Scan the list of hosts for which this message is waiting, and ensure
-that the message id is in each host record for those that have the
-update_waiting flag set. */
+that the message id is in each host record. */
 
 for (host = hostlist; host!= NULL; host = host->next)
   {
@@ -1422,10 +1638,6 @@ for (host = hostlist; host!= NULL; host = host->next)
   uschar *s;
   int i, host_length;
 
-  /* Skip if the update_waiting flag is not set. */
-
-  if (!host->update_waiting) continue;
-
   /* Skip if this is the same host as we just processed; otherwise remember
   the name for next time. */
 
@@ -1475,7 +1687,11 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   /* If this message is already in a record, no need to update. */
 
-  if (already) continue;
+  if (already)
+    {
+    DEBUG(D_transport) debug_printf("already listed for %s\n", host->name);
+    continue;
+    }
 
 
   /* If this record is full, write it out with a new name constructed
@@ -1511,6 +1727,7 @@ for (host = hostlist; host!= NULL; host = host->next)
   /* Update the database */
 
   dbfn_write(dbm_file, host->name, host_record, sizeof(dbdata_wait) + host_length);
+  DEBUG(D_transport) debug_printf("added to list for %s\n", host->name);
   }
 
 /* All now done */
@@ -1749,7 +1966,7 @@ if ((pid = fork()) == 0)
   automatic comparison. */
 
   if ((pid = fork()) != 0) _exit(EXIT_SUCCESS);
-  if (running_in_test_harness) millisleep(500);
+  if (running_in_test_harness) sleep(1);
 
   /* Set up the calling arguments; use the standard function for the basics,
   but we have a number of extras that may be added. */
@@ -1784,8 +2001,8 @@ if ((pid = fork()) == 0)
 
   if (socket_fd != 0)
     {
-    dup2(socket_fd, 0);
-    close(socket_fd);
+    (void)dup2(socket_fd, 0);
+    (void)close(socket_fd);
     }
 
   DEBUG(D_exec) debug_print_argv(argv);