Defend against symlink attack by another process running as exim
[exim.git] / src / src / transport.c
index e55e81f2f893d6ed2985d315fb27f68032bc2dbf..6ec5f372096ec7f940541029c3b291ffbaecdeda 100644 (file)
@@ -139,14 +139,11 @@ readconf_driver_init(US"transport",
 /* Now scan the configured transports and check inconsistencies. A shadow
 transport is permitted only for local transports. */
 
 /* 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 (t->body_only && t->headers_only)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
@@ -416,18 +413,24 @@ for (ptr = start; ptr < end; ptr++)
   room for the next uschar, plus a possible extra CR for an LF, plus the escape
   string. */
 
   room for the next uschar, plus a possible extra CR for an LF, plus the escape
   string. */
 
-  /*XXX CHUNKING: probably want to increase DELIVER_OUT_BUFFER_SIZE */
   if ((len = chunk_ptr - deliver_out_buffer) > mlen)
     {
   if ((len = chunk_ptr - deliver_out_buffer) > mlen)
     {
+    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 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, tc_reap_prev|tc_reap_one) != OK)
+      {
+      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;
        return FALSE;
-
-    if (!transport_write_block(fd, deliver_out_buffer, len))
-      return FALSE;
     chunk_ptr = deliver_out_buffer;
     }
 
     chunk_ptr = deliver_out_buffer;
     }
 
@@ -617,13 +620,8 @@ Arguments:
   addr                  (chain of) addresses (for extra headers), or NULL;
                           only the first address is used
   fd                    file descriptor to write the message to
   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)
   sendfn               function for output (transport or verify)
-  wck_flags
-    use_crlf           turn NL into CR LF
-    use_bdat           callback before chunk flush
-  rewrite_rules         chain of header rewriting rules
-  rewrite_existflags    flags for the rewriting rules
-  chunk_cb             transport callback function for data-chunk commands
 
 Returns:                TRUE on success; FALSE on failure.
 */
 
 Returns:                TRUE on success; FALSE on failure.
 */
@@ -827,12 +825,12 @@ Arguments:
       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
       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;
+    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
                             it is used when returning messages to their senders,
                             and is approximate rather than exact, owing to chunk
                             buffering
-    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
 
 Returns:                TRUE on success; FALSE (with errno) on failure.
                         In addition, the global variable transport_count
 
 Returns:                TRUE on success; FALSE (with errno) on failure.
                         In addition, the global variable transport_count
@@ -928,11 +926,11 @@ if (!(tctx->options & topt_no_headers))
     return FALSE;
   }
 
     return FALSE;
   }
 
-/* When doing RFC3030 CHUNKING output, work out how much data will be in the
-last BDAT, consisting of the current write_chunk() output buffer fill
+/* 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
 (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 the BDAT, and ensure
+(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.
 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.
@@ -943,7 +941,7 @@ suboptimal. */
 if (tctx->options & topt_use_bdat)
   {
   off_t fsize;
 if (tctx->options & topt_use_bdat)
   {
   off_t fsize;
-  int hsize, size;
+  int hsize, size = 0;
 
   if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
     hsize = 0;
 
   if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
     hsize = 0;
@@ -966,6 +964,8 @@ if (tctx->options & topt_use_bdat)
 
   if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
     {
 
   if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
     {
+    DEBUG(D_transport)
+      debug_printf("sending small initial BDAT; hssize=%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
     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
@@ -1058,8 +1058,9 @@ uschar * dkim_spool_name;
 int sread = 0;
 int wwritten = 0;
 uschar *dkim_signature = NULL;
 int sread = 0;
 int wwritten = 0;
 uschar *dkim_signature = NULL;
-int siglen;
+int siglen = 0;
 off_t k_file_size;
 off_t k_file_size;
+int options;
 
 /* If we can't sign, just call the original function. */
 
 
 /* If we can't sign, just call the original function. */
 
@@ -1077,10 +1078,13 @@ if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
   goto CLEANUP;
   }
 
   goto CLEANUP;
   }
 
-/* Call original function to write the -K file; does the CRLF expansion */
+/* Call original function to write the -K file; does the CRLF expansion
+(but, in the CHUNKING case, not dot-stuffing and dot-termination). */
 
 
+options = tctx->options;
 tctx->options &= ~topt_use_bdat;
 rc = transport_write_message(dkim_fd, tctx, 0);
 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 error state. We must clean up before returning. */
 if (!rc)
@@ -1089,62 +1093,56 @@ if (!rc)
   goto CLEANUP;
   }
 
   goto CLEANUP;
   }
 
-if (dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
+/* 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)))
+  siglen = Ustrlen(dkim_signature);
+else if (dkim->dkim_strict)
   {
   {
-  /* 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->dkim_private_key,
-                                 dkim->dkim_domain,
-                                 dkim->dkim_selector,
-                                 dkim->dkim_canon,
-                                 dkim->dkim_sign_headers);
-  if (!dkim_signature)
-    {
-    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) )
       {
       {
-      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.");
-         rc = FALSE;
-         goto CLEANUP;
-         }
+      /* 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;
       }
       }
-    }
-
-  siglen = 0;
   }
 
   }
 
-if (dkim_signature)
+#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)
   {
   {
-  siglen = Ustrlen(dkim_signature);
-  while(siglen > 0)
+
+  /* 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)
     {
     {
-#ifdef SUPPORT_TLS
-    wwritten = tls_out.active == out_fd
-      ? tls_write(FALSE, dkim_signature, siglen)
-      : write(out_fd, dkim_signature, siglen);
-#else
-    wwritten = write(out_fd, dkim_signature, siglen);
-#endif
-    if (wwritten == -1)
-      {
-      /* error, bail out */
-      save_errno = errno;
-      rc = FALSE;
-      goto CLEANUP;
-      }
-    siglen -= wwritten;
-    dkim_signature += wwritten;
+    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;
   }
 
   }
 
+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,
 #ifdef HAVE_LINUX_SENDFILE
 /* We can use sendfile() to shove the file contents
    to the socket. However only if we don't use TLS,
@@ -1155,18 +1153,13 @@ if (tls_out.active != out_fd)
   ssize_t copied = 0;
   off_t offset = 0;
 
   ssize_t copied = 0;
   off_t offset = 0;
 
-  k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
-
   /* Rewind file */
   lseek(dkim_fd, 0, SEEK_SET);
 
   while(copied >= 0 && offset < k_file_size)
     copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset);
   if (copied < 0)
   /* Rewind file */
   lseek(dkim_fd, 0, SEEK_SET);
 
   while(copied >= 0 && offset < k_file_size)
     copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset);
   if (copied < 0)
-    {
-    save_errno = errno;
-    rc = FALSE;
-    }
+    goto err;
   }
 else
 
   }
 else
 
@@ -1179,25 +1172,20 @@ else
   /* Send file down the original fd */
   while((sread = read(dkim_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
     {
   /* Send file down the original fd */
   while((sread = read(dkim_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
     {
-    char *p = deliver_out_buffer;
+    uschar * p = deliver_out_buffer;
     /* write the chunk */
 
     while (sread)
       {
 #ifdef SUPPORT_TLS
       wwritten = tls_out.active == out_fd
     /* write the chunk */
 
     while (sread)
       {
 #ifdef SUPPORT_TLS
       wwritten = tls_out.active == out_fd
-       ? tls_write(FALSE, US p, sread)
-       : write(out_fd, p, sread);
+       ? tls_write(FALSE, p, sread)
+       : write(out_fd, CS p, sread);
 #else
 #else
-      wwritten = write(out_fd, p, sread);
+      wwritten = write(out_fd, CS p, sread);
 #endif
       if (wwritten == -1)
 #endif
       if (wwritten == -1)
-       {
-       /* error, bail out */
-       save_errno = errno;
-       rc = FALSE;
-       goto CLEANUP;
-       }
+       goto err;
       p += wwritten;
       sread -= wwritten;
       }
       p += wwritten;
       sread -= wwritten;
       }
@@ -1211,11 +1199,16 @@ else
   }
 
 CLEANUP:
   }
 
 CLEANUP:
-/* unlink -K file */
-(void)close(dkim_fd);
-Uunlink(dkim_spool_name);
-errno = save_errno;
-return rc;
+  /* 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
 }
 
 #endif
@@ -1242,7 +1235,6 @@ Returns:       TRUE on success; FALSE (with errno) for any failure
 BOOL
 transport_write_message(int fd, transport_ctx * tctx, int size_limit)
 {
 BOOL
 transport_write_message(int fd, transport_ctx * tctx, int size_limit)
 {
-unsigned wck_flags;
 BOOL last_filter_was_NL = TRUE;
 int rc, len, yield, fd_read, fd_write, save_errno;
 int pfd[2] = {-1, -1};
 BOOL last_filter_was_NL = TRUE;
 int rc, len, yield, fd_read, fd_write, save_errno;
 int pfd[2] = {-1, -1};
@@ -1266,7 +1258,6 @@ if (  !transport_filter_argv
 before being written to the incoming fd. First set up the special processing to
 be done during the copying. */
 
 before being written to the incoming fd. First set up the special processing to
 be done during the copying. */
 
-wck_flags = tctx->options & topt_use_crlf;
 nl_partial_match = -1;
 
 if (tctx->check_string && tctx->escape_string)
 nl_partial_match = -1;
 
 if (tctx->check_string && tctx->escape_string)
@@ -1290,10 +1281,13 @@ save_errno = 0;
 yield = FALSE;
 write_pid = (pid_t)(-1);
 
 yield = FALSE;
 write_pid = (pid_t)(-1);
 
-(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
-filter_pid = child_open(USS 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)
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
 DEBUG(D_transport)
@@ -1432,14 +1426,19 @@ if (write_pid > 0)
   {
   rc = child_close(write_pid, 30);
   if (yield)
   {
   rc = child_close(write_pid, 30);
   if (yield)
-    {
     if (rc == 0)
       {
       BOOL ok;
     if (rc == 0)
       {
       BOOL ok;
-      int dummy = 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)
         {
         {
-        dummy = read(pfd[pipe_read], (void *)&save_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;
         }
         dummy = read(pfd[pipe_read], (void *)&(tctx->addr->more_errno), sizeof(int));
         yield = FALSE;
         }
@@ -1451,7 +1450,6 @@ if (write_pid > 0)
       tctx->addr->more_errno = rc;
       DEBUG(D_transport) debug_printf("writing process returned %d\n", rc);
       }
       tctx->addr->more_errno = rc;
       DEBUG(D_transport) debug_printf("writing process returned %d\n", rc);
       }
-    }
   }
 (void)close(pfd[pipe_read]);
 
   }
 (void)close(pfd[pipe_read]);
 
@@ -1765,7 +1763,7 @@ while (1)
 
   /* create an array to read entire message queue into memory for processing  */
 
 
   /* create an array to read entire message queue into memory for processing  */
 
-  msgq = (msgq_t*) malloc(sizeof(msgq_t) * host_record->count);
+  msgq = store_malloc(sizeof(msgq_t) * host_record->count);
   msgq_count = host_record->count;
   msgq_actual = msgq_count;
 
   msgq_count = host_record->count;
   msgq_actual = msgq_count;
 
@@ -1873,7 +1871,7 @@ test but the code should work */
 
   if (bFound)          /* Usual exit from main loop */
     {
 
   if (bFound)          /* Usual exit from main loop */
     {
-    free (msgq);
+    store_free (msgq);
     break;
     }
 
     break;
     }
 
@@ -1899,7 +1897,7 @@ test but the code should work */
     return FALSE;
     }
 
     return FALSE;
     }
 
-  free(msgq);
+  store_free(msgq);
   }            /* we need to process a continuation record */
 
 /* Control gets here when an existing message has been encountered; its
   }            /* we need to process a continuation record */
 
 /* Control gets here when an existing message has been encountered; its
@@ -1948,7 +1946,7 @@ DEBUG(D_transport) debug_printf("transport_pass_socket entered\n");
 
 if ((pid = fork()) == 0)
   {
 
 if ((pid = fork()) == 0)
   {
-  int i = 16;
+  int i = 17;
   const uschar **argv;
 
   /* Disconnect entirely from the parent process. If we are running in the
   const uschar **argv;
 
   /* Disconnect entirely from the parent process. If we are running in the
@@ -1964,16 +1962,15 @@ if ((pid = fork()) == 0)
 
   argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
 
 
   argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
 
-  if (smtp_use_dsn) argv[i++] = US"-MCD";
-
   if (smtp_authenticated) argv[i++] = US"-MCA";
 
   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) argv[i++] = US"-MCT";
+#endif
 
   if (queue_run_pid != (pid_t)0)
     {
 
   if (queue_run_pid != (pid_t)0)
     {