Final documentation updates for 4.67.
[exim.git] / src / src / exim.c
index 8c5c23eff9f9ac34cec0d293bc26240ce85bc850..49830c0de94c82292a11ec99a4987d50531ea62f 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/exim.c,v 1.42 2006/07/27 10:13:52 ph10 Exp $ */
+/* $Cambridge: exim/src/src/exim.c,v 1.55 2007/01/30 15:10:59 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -980,6 +980,9 @@ fprintf(f, "Authenticators:");
 #ifdef AUTH_CYRUS_SASL
   fprintf(f, " cyrus_sasl");
 #endif
+#ifdef AUTH_DOVECOT
+  fprintf(f, " dovecot");
+#endif
 #ifdef AUTH_PLAINTEXT
   fprintf(f, " plaintext");
 #endif
@@ -1174,7 +1177,7 @@ int size = 0;
 int ptr = 0;
 uschar *yield = NULL;
 
-if (fn_readline == NULL) printf("> ");
+if (fn_readline == NULL) { printf("> "); fflush(stdout); }
 
 for (i = 0;; i++)
   {
@@ -1297,6 +1300,7 @@ uschar *alias_arg = NULL;
 uschar *called_as = US"";
 uschar *start_queue_run_id = NULL;
 uschar *stop_queue_run_id = NULL;
+uschar *expansion_test_message = NULL;
 uschar *ftest_domain = NULL;
 uschar *ftest_localpart = NULL;
 uschar *ftest_prefix = NULL;
@@ -1494,7 +1498,8 @@ regex_ismsgid =
   regex_must_compile(US"^(?:[^\\W_]{6}-){2}[^\\W_]{2}$", FALSE, TRUE);
 
 /* Precompile the regular expression that is used for matching an SMTP error
-code, possibly extended, at the start of an error message. */
+code, possibly extended, at the start of an error message. Note that the
+terminating whitespace character is included. */
 
 regex_smtp_code =
   regex_must_compile(US"^\\d\\d\\d\\s(?:\\d\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\s)?",
@@ -1677,10 +1682,21 @@ for (i = 1; i < argc; i++)
         else if (*argrest != 0) { badarg = TRUE; break; }
       }
 
-    /* -be: Run in expansion test mode */
+    /* -be:  Run in expansion test mode
+       -bem: Ditto, but read a message from a file first
+    */
 
     else if (*argrest == 'e')
+      {
       expansion_test = checking = TRUE;
+      if (argrest[1] == 'm')
+        {
+        if (++i >= argc) { badarg = TRUE; break; }
+        expansion_test_message = argv[i];
+        argrest++;
+        }
+      if (argrest[1] != 0) { badarg = TRUE; break; }
+      }
 
     /* -bF:  Run system filter test */
 
@@ -2149,6 +2165,9 @@ for (i = 1; i < argc; i++)
 
     if (Ustrcmp(argrest, "C") == 0)
       {
+      union sockaddr_46 interface_sock;
+      EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
       if (argc != i + 6)
         {
         fprintf(stderr, "exim: too many or too few arguments after -MC\n");
@@ -2178,6 +2197,19 @@ for (i = 1; i < argc; i++)
         return EXIT_FAILURE;
         }
 
+      /* Set up $sending_ip_address and $sending_port */
+
+      if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
+          &size) == 0)
+        sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
+          &sending_port);
+      else
+        {
+        fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
+          strerror(errno));
+        return EXIT_FAILURE;
+        }
+
       if (running_in_test_harness) millisleep(500);
       break;
       }
@@ -2249,6 +2281,7 @@ for (i = 1; i < argc; i++)
        -Mmad mark all recipients delivered
        -Mmd  mark recipients(s) delivered
        -Mes  edit sender
+       -Mset load a message for use with -be
        -Mvb  show body
        -Mvh  show header
        -Mvl  show log
@@ -2286,6 +2319,11 @@ for (i = 1; i < argc; i++)
       one_msg_action = TRUE;
       }
     else if (Ustrcmp(argrest, "rm") == 0) msg_action = MSG_REMOVE;
+    else if (Ustrcmp(argrest, "set") == 0)
+      {
+      msg_action = MSG_LOAD;
+      one_msg_action = TRUE;
+      }
     else if (Ustrcmp(argrest, "t") == 0)  msg_action = MSG_THAW;
     else if (Ustrcmp(argrest, "vb") == 0)
       {
@@ -2625,6 +2663,11 @@ for (i = 1; i < argc; i++)
 
     case 'q':
     receiving_message = FALSE;
+    if (queue_interval >= 0)
+      {
+      fprintf(stderr, "exim: -q specified more than once\n");
+      exit(EXIT_FAILURE);
+      }
 
     /* -qq...: Do queue runs in a 2-stage manner */
 
@@ -2733,7 +2776,6 @@ for (i = 1; i < argc; i++)
         }
       }
     else deliver_selectstring = argrest;
-    if (queue_interval < 0) queue_interval = 0;
     break;
 
 
@@ -2781,7 +2823,6 @@ for (i = 1; i < argc; i++)
         }
       }
     else deliver_selectstring_sender = argrest;
-    if (queue_interval < 0) queue_interval = 0;
     break;
 
     /* -Tqt is an option that is exclusively for use by the testing suite.
@@ -2872,6 +2913,12 @@ for (i = 1; i < argc; i++)
   }
 
 
+/* If -R or -S have been specified without -q, assume a single queue run. */
+
+if ((deliver_selectstring != NULL || deliver_selectstring_sender != NULL) &&
+  queue_interval < 0) queue_interval = 0;
+
+
 /* Arguments have been processed. Check for incompatibilities. */
 
 END_ARG:
@@ -2883,13 +2930,14 @@ if ((
     ) ||
     (
     msg_action_arg > 0 &&
-    (daemon_listen || queue_interval >= 0 || list_options || checking ||
-     bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0)
+    (daemon_listen || queue_interval >= 0 || list_options ||
+      (checking && msg_action != MSG_LOAD) ||
+      bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0)
     ) ||
     (
     (daemon_listen || queue_interval >= 0) &&
     (sender_address != NULL || list_options || list_queue || checking ||
-     bi_option)
+      bi_option)
     ) ||
     (
     daemon_listen && queue_interval == 0
@@ -2914,6 +2962,10 @@ if ((
     ) ||
     (
     deliver_selectstring != NULL && queue_interval < 0
+    ) ||
+    (
+    msg_action == MSG_LOAD &&
+      (!expansion_test || expansion_test_message != NULL)
     )
    )
   {
@@ -3652,12 +3704,12 @@ if (count_queue)
   exit(EXIT_SUCCESS);
   }
 
-/* Handle actions on specific messages, except for the force delivery action,
-which is done below. Some actions take a whole list of message ids, which
-are known to continue up to the end of the arguments. Others take a single
-message id and then operate on the recipients list. */
+/* Handle actions on specific messages, except for the force delivery and
+message load actions, which are done below. Some actions take a whole list of
+message ids, which are known to continue up to the end of the arguments. Others
+take a single message id and then operate on the recipients list. */
 
-if (msg_action_arg > 0 && msg_action != MSG_DELIVER)
+if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD)
   {
   int yield = EXIT_SUCCESS;
   set_process_info("acting on specified messages");
@@ -3837,16 +3889,19 @@ if (list_options)
 
 
 /* Handle a request to deliver one or more messages that are already on the
-queue. Values of msg_action other than MSG_DELIVER are dealt with above. This
-is typically used for a small number when prodding by hand (when the option
-forced_delivery will be set) or when re-execing to regain root privilege.
-Each message delivery must happen in a separate process, so we fork a process
-for each one, and run them sequentially so that debugging output doesn't get
-intertwined, and to avoid spawning too many processes if a long list is given.
-However, don't fork for the last one; this saves a process in the common case
-when Exim is called to deliver just one message. */
-
-if (msg_action_arg > 0)
+queue. Values of msg_action other than MSG_DELIVER and MSG_LOAD are dealt with
+above. MSG_LOAD is handled with -be (which is the only time it applies) below.
+
+Delivery of specific messages is typically used for a small number when
+prodding by hand (when the option forced_delivery will be set) or when
+re-execing to regain root privilege. Each message delivery must happen in a
+separate process, so we fork a process for each one, and run them sequentially
+so that debugging output doesn't get intertwined, and to avoid spawning too
+many processes if a long list is given. However, don't fork for the last one;
+this saves a process in the common case when Exim is called to deliver just one
+message. */
+
+if (msg_action_arg > 0 && msg_action != MSG_LOAD)
   {
   if (prod_requires_admin && !admin_user)
     {
@@ -4166,18 +4221,65 @@ if (verify_address_mode || address_test_mode)
   exim_exit(exit_value);
   }
 
-/* Handle expansion checking */
+/* Handle expansion checking. Either expand items on the command line, or read
+from stdin if there aren't any. If -Mset was specified, load the message so
+that its variables can be used, but restrict this facility to admin users.
+Otherwise, if -bem was used, read a message from stdin. */
 
 if (expansion_test)
   {
+  if (msg_action_arg > 0 && msg_action == MSG_LOAD)
+    {
+    uschar spoolname[256];  /* Not big_buffer; used in spool_read_header() */
+    if (!admin_user)
+      {
+      fprintf(stderr, "exim: permission denied\n");
+      exit(EXIT_FAILURE);
+      }
+    message_id = argv[msg_action_arg];
+    (void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id);
+    if (!spool_open_datafile(message_id))
+      printf ("Failed to load message datafile %s\n", message_id);
+    if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK)
+      printf ("Failed to load message %s\n", message_id);
+    }
+
+  /* Read a test message from a file. We fudge it up to be on stdin, saving
+  stdin itself for later reading of expansion strings. */
+
+  else if (expansion_test_message != NULL)
+    {
+    int save_stdin = dup(0);
+    int fd = Uopen(expansion_test_message, O_RDONLY, 0);
+    if (fd < 0)
+      {
+      fprintf(stderr, "exim: failed to open %s: %s\n", expansion_test_message,
+        strerror(errno));
+      return EXIT_FAILURE;
+      }
+    (void) dup2(fd, 0);
+    filter_test = FTEST_USER;      /* Fudge to make it look like filter test */
+    message_ended = END_NOTENDED;
+    read_message_body(receive_msg(extract_recipients));
+    message_linecount += body_linecount;
+    (void)dup2(save_stdin, 0);
+    (void)close(save_stdin);
+    clearerr(stdin);               /* Required by Darwin */
+    }
+
+  /* Allow $recipients for this testing */
+
+  enable_dollar_recipients = TRUE;
+
+  /* Expand command line items */
+
   if (recipients_arg < argc)
     {
     while (recipients_arg < argc)
       {
       uschar *s = argv[recipients_arg++];
       uschar *ss = expand_string(s);
-      if (ss == NULL)
-        printf ("Failed: %s\n", expand_string_message);
+      if (ss == NULL) printf ("Failed: %s\n", expand_string_message);
       else printf("%s\n", CS ss);
       }
     }
@@ -4209,6 +4311,14 @@ if (expansion_test)
     #endif
     }
 
+  /* The data file will be open after -Mset */
+
+  if (deliver_datafile >= 0)
+    {
+    (void)close(deliver_datafile);
+    deliver_datafile = -1;
+    }
+
   exim_exit(EXIT_SUCCESS);
   }
 
@@ -4277,6 +4387,11 @@ if (host_checking)
     log_write_selector &= ~L_smtp_connection;
   log_write(L_smtp_connection, LOG_MAIN, "%s", smtp_get_connection_info());
 
+  /* NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
+  because a log line has already been written for all its failure exists
+  (usually "connection refused: <reason>") and writing another one is
+  unnecessary clutter. */
+
   if (smtp_start_session())
     {
     reset_point = store_get(0);
@@ -4286,6 +4401,7 @@ if (host_checking)
       if (smtp_setup_msg() <= 0) break;
       if (!receive_msg(FALSE)) break;
       }
+    smtp_log_no_mail();
     }
   exim_exit(EXIT_SUCCESS);
   }
@@ -4387,16 +4503,17 @@ but fd 1 will not be set. This also happens for passed SMTP channels. */
 
 if (fstat(1, &statbuf) < 0) (void)dup2(0, 1);
 
-/* Set up the incoming protocol name and the state of the program. Root
-is allowed to force received protocol via the -oMr option above, and if we are
-in a non-local SMTP state it means we have come via inetd and the process info
-has already been set up. We don't set received_protocol here for smtp input,
-as it varies according to batch/HELO/EHLO/AUTH/TLS. */
+/* Set up the incoming protocol name and the state of the program. Root is
+allowed to force received protocol via the -oMr option above. If we have come
+via inetd, the process info has already been set up. We don't set
+received_protocol here for smtp input, as it varies according to
+batch/HELO/EHLO/AUTH/TLS. */
 
 if (smtp_input)
   {
-  if (sender_local) set_process_info("accepting a local SMTP message from <%s>",
-    sender_address);
+  if (!is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
+    smtp_batched_input? "batched " : "",
+    (sender_address!= NULL)? sender_address : originator_login);
   }
 else
   {
@@ -4423,8 +4540,13 @@ if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0))
   return EXIT_FAILURE;
   }
 
-/* If this is smtp input of any kind, handle the start of the SMTP
-session. */
+/* If this is smtp input of any kind, real or batched, handle the start of the
+SMTP session.
+
+NOTE: We do *not* call smtp_log_no_mail() if smtp_start_session() fails,
+because a log line has already been written for all its failure exists
+(usually "connection refused: <reason>") and writing another one is
+unnecessary clutter. */
 
 if (smtp_input)
   {
@@ -4444,8 +4566,8 @@ if (smtp_input)
 
 else
   {
-  thismessage_size_limit = expand_string_integer(message_size_limit);
-  if (thismessage_size_limit < 0)
+  thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
+  if (expand_string_message != NULL)
     {
     if (thismessage_size_limit == -1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
@@ -4512,20 +4634,13 @@ while (more)
   store_reset(reset_point);
   message_id[0] = 0;
 
-  /* In the SMTP case, we have to handle the initial SMTP input and build the
-  recipients list, before calling receive_msg() to read the message proper.
-  Whatever sender address is actually given in the SMTP transaction is
-  actually ignored for local senders - we use the actual sender, which is
-  normally either the underlying user running this process or a -f argument
-  provided by a trusted caller. It is saved in real_sender_address.
-
-  However, if this value is NULL, we are dealing with a trusted caller when
-  -f was not used; in this case, the SMTP sender is allowed to stand.
-
-  Also, if untrusted_set_sender is set, we permit sender addresses that match
-  anything in its list.
-
-  The variable raw_sender_address holds the sender address before rewriting. */
+  /* Handle the SMTP case; call smtp_setup_mst() to deal with the initial SMTP
+  input and build the recipients list, before calling receive_msg() to read the
+  message proper. Whatever sender address is given in the SMTP transaction is
+  often ignored for local senders - we use the actual sender, which is normally
+  either the underlying user running this process or a -f argument provided by
+  a trusted caller. It is saved in real_sender_address. The test for whether to
+  accept the SMTP sender is encapsulated in receive_check_set_sender(). */
 
   if (smtp_input)
     {
@@ -4538,14 +4653,36 @@ while (more)
         sender_address = raw_sender = real_sender_address;
         sender_address_unrewritten = NULL;
         }
+
+      /* For batched SMTP, we have to run the acl_not_smtp_start ACL, since it
+      isn't really SMTP, so no other ACL will run until the acl_not_smtp one at
+      the very end. The result of the ACL is ignored (as for other non-SMTP
+      messages). It is run for its potential side effects. */
+
+      if (smtp_batched_input && acl_not_smtp_start != NULL)
+        {
+        uschar *user_msg, *log_msg;
+        enable_dollar_recipients = TRUE;
+        (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+          &user_msg, &log_msg);
+        enable_dollar_recipients = FALSE;
+        }
+
+      /* Now get the data for the message */
+
       more = receive_msg(extract_recipients);
       if (message_id[0] == 0)
         {
         if (more) continue;
+        smtp_log_no_mail();               /* Log no mail if configured */
         exim_exit(EXIT_FAILURE);
         }
       }
-    else exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+    else
+      {
+      smtp_log_no_mail();               /* Log no mail if configured */
+      exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+      }
     }
 
   /* In the non-SMTP case, we have all the information from the command