Doc: Improve clarity on -be
[exim.git] / src / src / filter.c
index 0e8e790a4799638d6a5630cfb8b4f400fa20b64c..bba1a520e7c2e18094d0e46905aa3a04eb46efa1 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/filter.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -76,8 +74,8 @@ permitted to include \n followed by white space) first, and then the body text
 one (it can have \n anywhere). Then the file names and once_repeat, which may
 not contain \n. */
 
-static char *mailargs[] = {  /* "to" must be first, and */
-  "to",                      /* "cc" and "bcc" must follow */
+static const char *mailargs[] = {  /* "to" must be first, and */
+  "to",                            /* "cc" and "bcc" must follow */
   "cc",
   "bcc",
   "from",
@@ -146,14 +144,14 @@ enum { cond_and, cond_or, cond_personal, cond_begins, cond_BEGINS,
        cond_above, cond_below, cond_errormsg, cond_firsttime,
        cond_manualthaw, cond_foranyaddress };
 
-static char *cond_names[] = {
+static const char *cond_names[] = {
   "and", "or", "personal",
   "begins", "BEGINS", "ends", "ENDS",
   "is", "IS", "matches", "MATCHES", "contains",
   "CONTAINS", "delivered", "above", "below", "error_message",
   "first_delivery", "manually_thawed", "foranyaddress" };
 
-static char *cond_not_names[] = {
+static const char *cond_not_names[] = {
   "", "", "not personal",
   "does not begin", "does not BEGIN",
   "does not end", "does not END",
@@ -165,7 +163,7 @@ static char *cond_not_names[] = {
 /* Tables of binary condition words and their corresponding types. Not easy
 to amalgamate with the above because of the different variants. */
 
-static char *cond_words[] = {
+static const char *cond_words[] = {
    "BEGIN",
    "BEGINS",
    "CONTAIN",
@@ -203,7 +201,7 @@ enum { add_command, defer_command, deliver_command, elif_command, else_command,
        mail_command, noerror_command, pipe_command, save_command, seen_command,
        testprint_command, unseen_command, vacation_command };
 
-static char *command_list[] = {
+static const char *command_list[] = {
   "add",     "defer",   "deliver", "elif", "else",      "endif",    "finish",
   "fail",    "freeze",  "headers", "if",   "logfile",   "logwrite", "mail",
   "noerror", "pipe",    "save",    "seen", "testprint", "unseen",   "vacation"
@@ -357,7 +355,7 @@ while (*(++ptr) != 0 && *ptr != '\"' && *ptr != '\n')
         }
       }
 
-    *bp++ = string_interpret_escape(&ptr);
+    *bp++ = string_interpret_escape(CUSS &ptr);
     }
   }
 
@@ -777,7 +775,7 @@ Returns:      nothing
 static void
 print_condition(condition_block *c, BOOL toplevel)
 {
-char *name = (c->testfor)? cond_names[c->type] : cond_not_names[c->type];
+const char *name = (c->testfor)? cond_names[c->type] : cond_not_names[c->type];
 switch(c->type)
   {
   case cond_personal:
@@ -800,7 +798,7 @@ switch(c->type)
   case cond_ENDS:
   case cond_above:
   case cond_below:
-  debug_printf("%s %s %s", c->left.u, (char *)name, c->right.u);
+  debug_printf("%s %s %s", c->left.u, name, c->right.u);
   break;
 
   case cond_and:
@@ -1044,6 +1042,13 @@ switch (command)
   case elif_command:
   case else_command:
   case endif_command:
+  if (seen_force || noerror_force)
+    {
+    *error_pointer = string_sprintf("\"seen\", \"unseen\", or \"noerror\" "
+      "near line %d is not followed by a command", line_number);
+    yield = FALSE;
+    }
+
   if (expect_endif > 0)
     had_else_endif = (command == elif_command)? had_elif :
                      (command == else_command)? had_else : had_endif;
@@ -1316,6 +1321,12 @@ switch (command)
 
   case seen_command:
   case unseen_command:
+  if (*ptr == 0)
+    {
+    *error_pointer = string_sprintf("\"seen\" or \"unseen\" "
+      "near line %d is not followed by a command", line_number);
+    yield = FALSE;
+    }
   if (seen_force)
     {
     *error_pointer = string_sprintf("\"seen\" or \"unseen\" repeated "
@@ -1331,6 +1342,12 @@ switch (command)
   /* So does noerror */
 
   case noerror_command:
+  if (*ptr == 0)
+    {
+    *error_pointer = string_sprintf("\"noerror\" "
+      "near line %d is not followed by a command", line_number);
+    yield = FALSE;
+    }
   noerror_force = TRUE;
   was_noerror = TRUE;
   break;
@@ -1414,7 +1431,7 @@ Returns:         TRUE if the condition is met
 static BOOL
 test_condition(condition_block *c, BOOL toplevel)
 {
-BOOL yield;
+BOOL yield = FALSE;
 const pcre *re;
 uschar *exp[2], *p, *pp;
 const uschar *regcomp_error = NULL;
@@ -1462,7 +1479,7 @@ switch (c->type)
   and filter testing and verification. */
 
   case cond_firsttime:
-  yield = filter_test != NULL || message_id[0] == 0 || deliver_firsttime;
+  yield = filter_test != FTEST_NONE || message_id[0] == 0 || deliver_firsttime;
   break;
 
   /* Only TRUE if a message is actually being processed; FALSE for address
@@ -1503,7 +1520,7 @@ switch (c->type)
 
     if (filter_thisaddress != NULL)
       {
-      if ((filter_test != NULL && debug_selector != 0) ||
+      if ((filter_test != FTEST_NONE && debug_selector != 0) ||
           (debug_selector & D_filter) != 0)
         {
         indent();
@@ -1578,7 +1595,7 @@ switch (c->type)
 
     case cond_matches:
     case cond_MATCHES:
-    if ((filter_test != NULL && debug_selector != 0) ||
+    if ((filter_test != FTEST_NONE && debug_selector != 0) ||
         (debug_selector & D_filter) != 0)
       {
       debug_printf("Match expanded arguments:\n");
@@ -1621,7 +1638,7 @@ switch (c->type)
   break;
   }
 
-if ((filter_test != NULL && debug_selector != 0) ||
+if ((filter_test != FTEST_NONE && debug_selector != 0) ||
     (debug_selector & D_filter) != 0)
   {
   indent();
@@ -1729,7 +1746,7 @@ while (commands != NULL)
       }
 
     filter_n[n[1]] += n[0];
-    if (filter_test != NULL) printf("Add %d to n%d\n", n[0], n[1]);
+    if (filter_test != FTEST_NONE) printf("Add %d to n%d\n", n[0], n[1]);
     break;
 
     /* A deliver command's argument must be a valid address. Its optional
@@ -1777,7 +1794,7 @@ while (commands != NULL)
 
     /* Test case: report what would happen */
 
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       indent();
       printf("%seliver message to: %s%s%s%s\n",
@@ -1804,7 +1821,7 @@ while (commands != NULL)
       set in a system filter and to the local address in user filters. */
 
       addr = deliver_make_addr(expargs[0], TRUE);  /* TRUE => copy s */
-      addr->p.errors_address = (s == NULL)?
+      addr->prop.errors_address = (s == NULL)?
         s : string_copy(s);                        /* Default is NULL */
       if (commands->noerror) setflag(addr, af_ignore_error);
       addr->next = *generated;
@@ -1818,7 +1835,7 @@ while (commands != NULL)
 
     /* Test case: report what would happen */
 
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       indent();
       if (mode < 0)
@@ -1834,11 +1851,12 @@ while (commands != NULL)
 
     else
       {
+      if (s[0] != '/' && (filter_options & RDO_PREPEND_HOME) != 0 &&
+          deliver_home != NULL && deliver_home[0] != 0)
+        s = string_sprintf("%s/%s", deliver_home, s);
       DEBUG(D_filter) debug_printf("Filter: %ssave message to: %s%s\n",
         (commands->seen)? "" : "unseen ", s,
         commands->noerror? " (noerror)" : "");
-      if (s[0] != '/' && deliver_home != NULL && deliver_home[0] != 0)
-        s = string_sprintf("%s/%s", deliver_home, s);
 
       /* Create the new address and add it to the chain, setting the
       af_pfr and af_file flags, the af_ignore_error flag if necessary, and the
@@ -1855,7 +1873,7 @@ while (commands != NULL)
 
     case pipe_command:
     s = string_copy(commands->args[0].u);
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       indent();
       printf("%sipe message to: %s%s\n", (commands->seen)?
@@ -1907,11 +1925,11 @@ while (commands != NULL)
     if (log_mode == -1) log_mode = 0600;
     if (log_fd >= 0)
       {
-      close(log_fd);
+      (void)close(log_fd);
       log_fd = -1;
       }
     log_filename = expargs[0];
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       indent();
       printf("%sogfile %s\n", (commands->seen)? "Seen l" : "L", log_filename);
@@ -1921,7 +1939,7 @@ while (commands != NULL)
     case logwrite_command:
     s = expargs[0];
 
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       indent();
       printf("%sogwrite \"%s\"\n", (commands->seen)? "Seen l" : "L",
@@ -1983,7 +2001,7 @@ while (commands != NULL)
       int subtype = commands->args[1].i;
       s = expargs[0];
 
-      if (filter_test != NULL)
+      if (filter_test != FTEST_NONE)
         printf("Headers %s \"%s\"\n", (subtype == TRUE)? "add" :
           (subtype == FALSE)? "remove" : "charset", string_printing(s));
 
@@ -2003,7 +2021,7 @@ while (commands != NULL)
         {
         int sep = 0;
         uschar *ss;
-        uschar *list = s;
+        const uschar *list = s;
         uschar buffer[128];
         while ((ss = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
                != NULL)
@@ -2039,10 +2057,10 @@ while (commands != NULL)
     DEFERFREEZEFAIL:
     fmsg = expargs[0];
     if (Ustrlen(fmsg) > 1024) Ustrcpy(fmsg + 1000, " ... (truncated)");
-    fmsg = string_printing(fmsg);
+    fmsg = US string_printing(fmsg);
     *error_pointer = fmsg;
 
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       indent();
       printf("%c%s text \"%s\"\n", toupper(ff_name[0]), ff_name+1, fmsg);
@@ -2051,7 +2069,7 @@ while (commands != NULL)
     return ff_ret;
 
     case finish_command:
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       indent();
       printf("%sinish\n", (commands->seen)? "Seen f" : "F");
@@ -2091,7 +2109,7 @@ while (commands != NULL)
     case vacation_command:
     if (return_path == NULL || return_path[0] == 0)
       {
-      if (filter_test != NULL)
+      if (filter_test != FTEST_NONE)
         printf("%s command ignored because return_path is empty\n",
           command_list[commands->command]);
       else DEBUG(D_filter) debug_printf("%s command ignored because return_path "
@@ -2166,7 +2184,6 @@ while (commands != NULL)
                   string_printing(s), command_list[commands->command]);
                 return FF_ERROR;
                 }
-              pp++;
               }
             p = pp;
             }
@@ -2180,7 +2197,7 @@ while (commands != NULL)
 
     /* Proceed with mail or vacation command */
 
-    if (filter_test != NULL)
+    if (filter_test != FTEST_NONE)
       {
       uschar *to = commands->args[mailarg_index_to].u;
       indent();
@@ -2207,12 +2224,17 @@ while (commands != NULL)
     else
       {
       uschar *tt;
+      uschar *log_addr = NULL;
       uschar *to = commands->args[mailarg_index_to].u;
+      int size = 0;
+      int ptr = 0;
+      int badflag = 0;
+
       if (to == NULL) to = expand_string(US"$reply_address");
       while (isspace(*to)) to++;
 
-      for (tt = to; *tt != 0; tt++)     /* Get rid of newlines so that */
-        if (*tt == '\n') *tt = ' ';     /* the eventual log line is OK */
+      for (tt = to; *tt != 0; tt++)     /* Get rid of newlines */
+        if (*tt == '\n') *tt = ' ';
 
       DEBUG(D_filter)
         {
@@ -2235,10 +2257,59 @@ while (commands != NULL)
           }
         }
 
-      /* Create the "address" for the autoreply */
+      /* Create the "address" for the autoreply. This is used only for logging,
+      as the actual recipients are extracted from the To: line by -t. We use the
+      same logic here to extract the working addresses (there may be more than
+      one). Just in case there are a vast number of addresses, stop when the
+      string gets too long. */
+
+      tt = to;
+      while (*tt != 0)
+        {
+        uschar *ss = parse_find_address_end(tt, FALSE);
+        uschar *recipient, *errmess;
+        int start, end, domain;
+        int temp = *ss;
+
+        *ss = 0;
+        recipient = parse_extract_address(tt, &errmess, &start, &end, &domain,
+          FALSE);
+        *ss = temp;
 
-      addr = deliver_make_addr(string_sprintf(">%.256s", to), FALSE);
-      setflag(addr, af_pfr);
+        /* Ignore empty addresses and errors; an error will occur later if
+        there's something really bad. */
+
+        if (recipient != NULL)
+          {
+          log_addr = string_cat(log_addr, &size, &ptr,
+            (log_addr == NULL)? US">" : US",", 1);
+          log_addr = string_cat(log_addr, &size, &ptr, recipient,
+            Ustrlen(recipient));
+          }
+
+        /* Check size */
+
+        if (ptr > 256)
+          {
+          log_addr = string_cat(log_addr, &size, &ptr, US", ...", 5);
+          break;
+          }
+
+        /* Move on past this address */
+
+        tt = ss + (*ss? 1:0);
+        while (isspace(*tt)) tt++;
+        }
+
+      if (log_addr == NULL)
+        {
+        log_addr = string_sprintf(">**bad-reply**");
+        badflag = af_bad_reply;
+        }
+      else log_addr[ptr] = 0;
+
+      addr = deliver_make_addr(log_addr, FALSE);
+      setflag(addr, (af_pfr|badflag));
       if (commands->noerror) setflag(addr, af_ignore_error);
       addr->next = *generated;
       *generated = addr;
@@ -2278,10 +2349,10 @@ while (commands != NULL)
     break;
 
     case testprint_command:
-    if (filter_test != NULL || (debug_selector & D_filter) != 0)
+    if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
       {
-      uschar *s = string_printing(expargs[0]);
-      if (filter_test == NULL)
+      const uschar *s = string_printing(expargs[0]);
+      if (filter_test == FTEST_NONE)
         debug_printf("Filter: testprint: %s\n", s);
       else
         printf("Testprint: %s\n", s);
@@ -2321,14 +2392,42 @@ header_line *h;
 int to_count = 2;
 int from_count = 9;
 
-/* If any header line in the message starts with "List-", it is not
-a personal message. */
+/* If any header line in the message is a defined "List-" header field, it is
+not a personal message. We used to check for any header line that started with
+"List-", but this was tightened up for release 4.54. The check is now for
+"List-Id", defined in RFC 2929, or "List-Help", "List-Subscribe", "List-
+Unsubscribe", "List-Post", "List-Owner" or "List-Archive", all of which are
+defined in RFC 2369. We also scan for "Auto-Submitted"; if it is found to
+contain any value other than "no", the message is not personal (RFC 3834).
+Previously the test was for "auto-". */
 
 for (h = header_list; h != NULL; h = h->next)
   {
-  if (h->type != htype_old && h->slen > 5 &&
-      strncmpic(h->text, US"List-", 5) == 0)
-    return FALSE;
+  uschar *s;
+  if (h->type == htype_old) continue;
+
+  if (strncmpic(h->text, US"List-", 5) == 0)
+    {
+    s = h->text + 5;
+    if (strncmpic(s, US"Id:", 3) == 0 ||
+        strncmpic(s, US"Help:", 5) == 0 ||
+        strncmpic(s, US"Subscribe:", 10) == 0 ||
+        strncmpic(s, US"Unsubscribe:", 12) == 0 ||
+        strncmpic(s, US"Post:", 5) == 0 ||
+        strncmpic(s, US"Owner:", 6) == 0 ||
+        strncmpic(s, US"Archive:", 8) == 0)
+      return FALSE;
+    }
+
+  else if (strncmpic(h->text, US"Auto-submitted:", 15) == 0)
+    {
+    s = h->text + 15;
+    while (isspace(*s)) s++;
+    if (strncmpic(s, US"no", 2) != 0) return FALSE;
+    s += 2;
+    while (isspace(*s)) s++;
+    if (*s != 0) return FALSE;
+    }
   }
 
 /* Set up "my" address */
@@ -2384,7 +2483,6 @@ yield =
     "^daemon@", "^root@", "^listserv@", "^majordomo@", "^.*?-request@",
     "^owner-[^@]+@", self, self_from, psself, psself_from) &&
 
-  header_match(US"auto-submitted:", FALSE, FALSE, NULL, 1, "auto-") &&
   header_match(US"precedence:", FALSE, FALSE, NULL, 3, "bulk","list","junk") &&
 
   (sender_address == NULL || sender_address[0] != 0);
@@ -2460,7 +2558,7 @@ ptr = nextsigchar(ptr, TRUE);
 if (read_command_list(&ptr, &lastcmdptr, FALSE))
   yield = interpret_commands(commands, generated);
 
-if (filter_test != NULL || (debug_selector & D_filter) != 0)
+if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
   {
   uschar *s = US"";
   switch(yield)
@@ -2493,14 +2591,14 @@ if (filter_test != NULL || (debug_selector & D_filter) != 0)
     break;
     }
 
-  if (filter_test != NULL) printf("%s\n", CS s);
+  if (filter_test != FTEST_NONE) printf("%s\n", CS s);
     else debug_printf("%s\n", s);
   }
 
 /* Close the log file if it was opened, and kill off any numerical variables
 before returning. Reset the header decoding charset. */
 
-if (log_fd >= 0) close(log_fd);
+if (log_fd >= 0) (void)close(log_fd);
 expand_nmax = -1;
 filter_running = FALSE;
 headers_charset = save_headers_charset;