Update version number and copyright year.
[exim.git] / src / src / transport.c
index 54252575440f0ed5e2e6137e7b58bc8b9cad6b33..3e63052e15d582447e67e5d76d10fec9cb309ae9 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/transport.c,v 1.3 2005/01/04 10:00:42 ph10 Exp $ */
+/* $Cambridge: exim/src/src/transport.c,v 1.19 2007/01/08 10:50:18 ph10 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. */
@@ -199,26 +202,42 @@ BOOL
 transport_write_block(int fd, uschar *block, int len)
 {
 int i, rc, save_errno;
+int local_timeout = transport_write_timeout;
+
+/* This loop is for handling incomplete writes and other retries. In most
+normal cases, it is only ever executed once. */
 
 for (i = 0; i < 100; i++)
   {
   DEBUG(D_transport)
     debug_printf("writing data block fd=%d size=%d timeout=%d\n",
-      fd, len, transport_write_timeout);
-  if (transport_write_timeout > 0) alarm(transport_write_timeout);
+      fd, len, local_timeout);
 
-  #ifdef SUPPORT_TLS
-  if (tls_active == fd) rc = tls_write(block, len); else
-  #endif
+  /* This code makes use of alarm() in order to implement the timeout. This
+  isn't a very tidy way of doing things. Using non-blocking I/O with select()
+  provides a neater approach. However, I don't know how to do this when TLS is
+  in use. */
 
-  rc = write(fd, block, len);
-  save_errno = errno;
+  if (transport_write_timeout <= 0)   /* No timeout wanted */
+    {
+    #ifdef SUPPORT_TLS
+    if (tls_active == fd) rc = tls_write(block, len); else
+    #endif
+    rc = write(fd, block, len);
+    save_errno = errno;
+    }
 
-  /* Cancel the alarm and deal with a timeout */
+  /* Timeout wanted. */
 
-  if (transport_write_timeout > 0)
+  else
     {
-    alarm(0);
+    alarm(local_timeout);
+    #ifdef SUPPORT_TLS
+    if (tls_active == fd) rc = tls_write(block, len); else
+    #endif
+    rc = write(fd, block, len);
+    save_errno = errno;
+    local_timeout = alarm(0);
     if (sigalrm_seen)
       {
       errno = ETIMEDOUT;
@@ -230,7 +249,8 @@ for (i = 0; i < 100; i++)
 
   if (rc == len) { transport_count += len; return TRUE; }
 
-  /* A non-negative return code is an incomplete write. Try again. */
+  /* A non-negative return code is an incomplete write. Try again for the rest
+  of the block. If we have exactly hit the timeout, give up. */
 
   if (rc >= 0)
     {
@@ -238,7 +258,7 @@ for (i = 0; i < 100; i++)
     block += rc;
     transport_count += rc;
     DEBUG(D_transport) debug_printf("write incomplete (%d)\n", rc);
-    continue;
+    goto CHECK_TIMEOUT;   /* A few lines below */
     }
 
   /* A negative return code with an EINTR error is another form of
@@ -248,7 +268,7 @@ for (i = 0; i < 100; i++)
     {
     DEBUG(D_transport)
       debug_printf("write interrupted before anything written\n");
-    continue;
+    goto CHECK_TIMEOUT;   /* A few lines below */
     }
 
   /* A response of EAGAIN from write() is likely only in the case of writing
@@ -259,6 +279,16 @@ for (i = 0; i < 100; i++)
     DEBUG(D_transport)
       debug_printf("write temporarily locked out, waiting 1 sec\n");
     sleep(1);
+
+    /* Before continuing to try another write, check that we haven't run out of
+    time. */
+
+    CHECK_TIMEOUT:
+    if (transport_write_timeout > 0 && local_timeout <= 0)
+      {
+      errno = ETIMEDOUT;
+      return FALSE;
+      }
     continue;
     }
 
@@ -797,7 +827,10 @@ if ((options & topt_no_headers) == 0)
   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. */
+  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)
     {
@@ -824,7 +857,8 @@ if ((options & topt_no_headers) == 0)
   /* 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. */
+  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)
     {
@@ -846,7 +880,11 @@ if ((options & topt_no_headers) == 0)
         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---\n", s);
+          {
+          debug_printf("added header line(s):\n%s", s);
+          if (s[len-1] != '\n') debug_printf("\n");
+          debug_printf("---\n");
+          }
         }
       }
     }
@@ -903,6 +941,190 @@ return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
 }
 
 
+#ifdef EXPERIMENTAL_DOMAINKEYS
+
+/**********************************************************************************
+*    External interface to write the message, while signing it with domainkeys    *
+**********************************************************************************/
+
+/* This function is a wrapper around transport_write_message(). It is only called
+   from the smtp transport if
+   (1) Domainkeys support is compiled in.
+   (2) The dk_private_key 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 *dk_private_key         The private key to use (filename or plain data)
+               uschar *dk_domain              Override domain (normally NULL)
+               uschar *dk_selector            The selector to use.
+               uschar *dk_canon               The canonalization scheme to use, "simple" or "nofws"
+               uschar *dk_headers             Colon-separated header list to include in the signing
+                                              process.
+               uschar *dk_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
+dk_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 *dk_private_key, uschar *dk_domain,
+  uschar *dk_selector, uschar *dk_canon, uschar *dk_headers, uschar *dk_strict)
+{
+  int dk_fd;
+  int save_errno = 0;
+  BOOL rc;
+  uschar dk_spool_name[256];
+  char sbuf[2048];
+  int sread = 0;
+  int wwritten = 0;
+  uschar *dk_signature = NULL;
+  off_t size = 0;
+
+  (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. */
+    rc = FALSE;
+    save_errno = errno;
+    goto CLEANUP;
+    }
+
+  /* Call original function */
+  rc = transport_write_message(addr, dk_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 DK lib */
+  lseek(dk_fd, 0, SEEK_SET);
+  dk_signature = dk_exim_sign(dk_fd,
+                              dk_private_key,
+                              dk_domain,
+                              dk_selector,
+                              dk_canon);
+
+  if (dk_signature != NULL)
+    {
+    /* Send the signature first */
+    int siglen = Ustrlen(dk_signature);
+    while(siglen > 0)
+      {
+      #ifdef SUPPORT_TLS
+      if (tls_active == fd) wwritten = tls_write(dk_signature, siglen); else
+      #endif
+      wwritten = write(fd,dk_signature,siglen);
+      if (wwritten == -1)
+        {
+        /* error, bail out */
+        save_errno = errno;
+        rc = FALSE;
+        goto CLEANUP;
+        }
+      siglen -= wwritten;
+      dk_signature += wwritten;
+      }
+    }
+  else if (dk_strict != NULL)
+    {
+    uschar *dk_strict_result = expand_string(dk_strict);
+    if (dk_strict_result != NULL)
+      {
+      if ( (strcmpic(dk_strict,US"1") == 0) ||
+           (strcmpic(dk_strict,US"true") == 0) )
+        {
+        save_errno = errno;
+        rc = FALSE;
+        goto CLEANUP;
+        }
+      }
+    }
+
+  /* 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;
+    /* write the chunk */
+    DK_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 DK_WRITE;
+      }
+    }
+
+  if (sread == -1)
+    {
+    save_errno = errno;
+    rc = FALSE;
+    goto CLEANUP;
+    }
+
+  CLEANUP:
+  /* unlink -K file */
+  (void)close(dk_fd);
+  Uunlink(dk_spool_name);
+  errno = save_errno;
+  return rc;
+}
+#endif
 
 
 /*************************************************
@@ -933,6 +1155,8 @@ int rc, len, yield, fd_read, fd_write, save_errno;
 int pfd[2];
 pid_t filter_pid, write_pid;
 
+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. */
 
@@ -969,10 +1193,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)
@@ -987,25 +1211,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 */
@@ -1045,6 +1269,7 @@ for (;;)
   if (sigalrm_seen)
     {
     errno = ETIMEDOUT;
+    transport_filter_timed_out = TRUE;
     goto TIDY_UP;
     }
 
@@ -1074,8 +1299,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)
   {
@@ -1095,7 +1320,7 @@ if (filter_pid > 0 && (rc = child_close(filter_pid, 30)) != 0 && yield)
   }
 
 /* Wait for the writing process to complete. If it ends successfully,
-read the results from its pipe, provided we haven't already had a filter 
+read the results from its pipe, provided we haven't already had a filter
 process failure. */
 
 DEBUG(D_transport) debug_printf("waiting for writing process\n");
@@ -1104,14 +1329,14 @@ if (write_pid > 0)
   rc = child_close(write_pid, 30);
   if (yield)
     {
-    if (rc == 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;
         }
       }
@@ -1122,9 +1347,9 @@ if (write_pid > 0)
       addr->more_errno = rc;
       DEBUG(D_transport) debug_printf("writing process returned %d\n", rc);
       }
-    }   
+    }
   }
-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
@@ -1193,8 +1418,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
@@ -1209,6 +1433,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);
@@ -1216,8 +1442,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)
   {
@@ -1226,10 +1451,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. */
 
@@ -1279,7 +1500,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
@@ -1315,6 +1540,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 */
@@ -1553,7 +1779,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. */
@@ -1588,8 +1814,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);