PRDR support, if compiled with EXPERIMENTAL_PRDR
[exim.git] / src / src / acl.c
index a721665d445abb26bbc2520cea4240228ef836e0..f61d2dfdfe560ec04eab4711533a1375ee4c4e16 100644 (file)
@@ -88,6 +88,7 @@ enum { ACLC_ACL,
 #ifdef WITH_CONTENT_SCAN
        ACLC_REGEX,
 #endif
+       ACLC_REMOVE_HEADER,
        ACLC_SENDER_DOMAINS,
        ACLC_SENDERS,
        ACLC_SET,
@@ -150,6 +151,7 @@ static uschar *conditions[] = {
 #ifdef WITH_CONTENT_SCAN
   US"regex",
 #endif
+  US"remove_header",
   US"sender_domains", US"senders", US"set",
 #ifdef WITH_CONTENT_SCAN
   US"spam",
@@ -236,7 +238,7 @@ at the outer level. In the other cases, expansion already occurs in the
 checking functions. */
 
 static uschar cond_expand_at_top[] = {
-  TRUE,    /* acl */
+  FALSE,   /* acl */
   TRUE,    /* add_header */
   FALSE,   /* authenticated */
 #ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -280,6 +282,7 @@ static uschar cond_expand_at_top[] = {
 #ifdef WITH_CONTENT_SCAN
   TRUE,    /* regex */
 #endif
+  TRUE,    /* remove_header */
   FALSE,   /* sender_domains */
   FALSE,   /* senders */
   TRUE,    /* set */
@@ -340,6 +343,7 @@ static uschar cond_modifiers[] = {
 #ifdef WITH_CONTENT_SCAN
   FALSE,   /* regex */
 #endif
+  TRUE,    /* remove_header */
   FALSE,   /* sender_domains */
   FALSE,   /* senders */
   TRUE,    /* set */
@@ -364,6 +368,9 @@ static unsigned int cond_forbids[] = {
   (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* add_header */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif
     (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
     (1<<ACL_WHERE_DKIM)|
     (1<<ACL_WHERE_NOTSMTP_START)),
@@ -376,6 +383,9 @@ static unsigned int cond_forbids[] = {
   (1<<ACL_WHERE_AUTH)|                             /* bmi_optin */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
@@ -394,7 +404,11 @@ static unsigned int cond_forbids[] = {
 
   #ifdef EXPERIMENTAL_DCC
   (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* dcc */
+  ~((1<<ACL_WHERE_DATA)|                           /* dcc */
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif /* EXPERIMENTAL_PRDR */
+    (1<<ACL_WHERE_NOTSMTP)),
   #endif
 
   #ifdef WITH_CONTENT_SCAN
@@ -406,7 +420,11 @@ static unsigned int cond_forbids[] = {
 
   #ifdef WITH_OLD_DEMIME
   (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* demime */
+  ~((1<<ACL_WHERE_DATA)|                           /* demime */
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif /* EXPERIMENTAL_PRDR */
+    (1<<ACL_WHERE_NOTSMTP)),
   #endif
 
   #ifndef DISABLE_DKIM
@@ -421,7 +439,11 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_NOTSMTP_START),
 
   (unsigned int)
-  ~(1<<ACL_WHERE_RCPT),                            /* domains */
+  ~((1<<ACL_WHERE_RCPT)                            /* domains */
+  #ifdef EXPERIMENTAL_PRDR
+    |(1<<ACL_WHERE_PRDR)
+  #endif
+    ),
 
   (1<<ACL_WHERE_NOTSMTP)|                          /* encrypted */
     (1<<ACL_WHERE_CONNECT)|
@@ -434,7 +456,11 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_NOTSMTP_START),
 
   (unsigned int)
-  ~(1<<ACL_WHERE_RCPT),                            /* local_parts */
+  ~((1<<ACL_WHERE_RCPT)                             /* local_parts */
+  #ifdef EXPERIMENTAL_PRDR
+    |(1<<ACL_WHERE_PRDR)
+  #endif
+    ),
 
   0,                                               /* log_message */
 
@@ -444,7 +470,11 @@ static unsigned int cond_forbids[] = {
 
   #ifdef WITH_CONTENT_SCAN
   (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* malware */
+  ~((1<<ACL_WHERE_DATA)|                           /* malware */
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif /* EXPERIMENTAL_PRDR */
+    (1<<ACL_WHERE_NOTSMTP)),
   #endif
 
   0,                                               /* message */
@@ -461,10 +491,23 @@ static unsigned int cond_forbids[] = {
 
   #ifdef WITH_CONTENT_SCAN
   (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|    /* regex */
+  ~((1<<ACL_WHERE_DATA)|                           /* regex */
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif /* EXPERIMENTAL_PRDR */
+    (1<<ACL_WHERE_NOTSMTP)|
     (1<<ACL_WHERE_MIME)),
   #endif
 
+  (unsigned int)
+  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* remove_header */
+    (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif
+    (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
+    (1<<ACL_WHERE_NOTSMTP_START)),
+
   (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|      /* sender_domains */
     (1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
@@ -481,7 +524,11 @@ static unsigned int cond_forbids[] = {
 
   #ifdef WITH_CONTENT_SCAN
   (unsigned int)
-  ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)),   /* spam */
+  ~((1<<ACL_WHERE_DATA)|                           /* spam */
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif /* EXPERIMENTAL_PRDR */
+    (1<<ACL_WHERE_NOTSMTP)),
   #endif
 
   #ifdef EXPERIMENTAL_SPF
@@ -525,6 +572,9 @@ static unsigned int control_forbids[] = {
 
   #ifndef DISABLE_DKIM
   (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|      /* dkim_disable_verify */
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif /* EXPERIMENTAL_PRDR */
     (1<<ACL_WHERE_NOTSMTP_START),
   #endif
 
@@ -552,11 +602,13 @@ static unsigned int control_forbids[] = {
   (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* freeze */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+    // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
     (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
 
   (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* queue_only */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+    // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
     (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
 
   (unsigned int)
@@ -572,17 +624,24 @@ static unsigned int control_forbids[] = {
   (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* no_mbox_unspool */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+    // (1<<ACL_WHERE_PRDR)|    /* Not allow one user to freeze for all */
     (1<<ACL_WHERE_MIME)),
   #endif
 
   (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* fakedefer */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif /* EXPERIMENTAL_PRDR */
     (1<<ACL_WHERE_MIME)),
 
   (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* fakereject */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+  #ifdef EXPERIMENTAL_PRDR
+    (1<<ACL_WHERE_PRDR)|
+  #endif /* EXPERIMENTAL_PRDR */
     (1<<ACL_WHERE_MIME)),
 
   (1<<ACL_WHERE_NOTSMTP)|                          /* no_multiline */
@@ -696,8 +755,8 @@ static uschar *ratelimit_option_string[] = {
 
 /* Enable recursion between acl_check_internal() and acl_check_condition() */
 
-static int acl_check_internal(int, address_item *, uschar *, int, uschar **,
-         uschar **);
+static int acl_check_wargs(int, address_item *, uschar *, int, uschar **,
+    uschar **);
 
 
 /*************************************************
@@ -948,10 +1007,13 @@ setup_header(uschar *hstring)
 uschar *p, *q;
 int hlen = Ustrlen(hstring);
 
-/* An empty string does nothing; otherwise add a final newline if necessary. */
+/* Ignore any leading newlines */
+while (*hstring == '\n') hstring++, hlen--;
 
+/* An empty string does nothing; ensure exactly one final newline. */
 if (hlen <= 0) return;
-if (hstring[hlen-1] != '\n') hstring = string_sprintf("%s\n", hstring);
+if (hstring[--hlen] != '\n') hstring = string_sprintf("%s\n", hstring);
+else while(hstring[--hlen] == '\n') hstring[hlen+1] = '\0';
 
 /* Loop for multiple header lines, taking care about continuations */
 
@@ -1037,6 +1099,69 @@ for (p = q = hstring; *p != 0; )
 
 
 
+/*************************************************
+*        List the added header lines            *
+*************************************************/
+uschar *
+fn_hdrs_added(void)
+{
+uschar * ret = NULL;
+header_line * h = acl_added_headers;
+uschar * s;
+uschar * cp;
+int size = 0;
+int ptr = 0;
+
+if (!h) return NULL;
+
+do
+  {
+  s = h->text;
+  while ((cp = Ustrchr(s, '\n')) != NULL)
+    {
+    if (cp[1] == '\0') break;
+
+    /* contains embedded newline; needs doubling */
+    ret = string_cat(ret, &size, &ptr, s, cp-s+1);
+    ret = string_cat(ret, &size, &ptr, US"\n", 1);
+    s = cp+1;
+    }
+  /* last bit of header */
+
+  ret = string_cat(ret, &size, &ptr, s, cp-s+1);       /* newline-sep list */
+  }
+while((h = h->next));
+
+ret[ptr-1] = '\0';     /* overwrite last newline */
+return ret;
+}
+
+
+/*************************************************
+*        Set up removed header line(s)           *
+*************************************************/
+
+/* This function is called by the remove_header modifier.  The argument is
+treated as a sequence of header names which are added to a colon separated
+list, provided there isn't an identical one already there.
+
+Argument:   string of header names
+Returns:    nothing
+*/
+
+static void
+setup_remove_header(uschar *hnames)
+{
+if (*hnames != 0)
+  {
+  if (acl_removed_headers == NULL)
+    acl_removed_headers = hnames;
+  else
+    acl_removed_headers = string_sprintf("%s : %s", acl_removed_headers, hnames);
+  }
+}
+
+
 
 /*************************************************
 *               Handle warnings                  *
@@ -2785,14 +2910,14 @@ for (; cb != NULL; cb = cb->next)
     "discard" verb. */
 
     case ACLC_ACL:
-    rc = acl_check_internal(where, addr, arg, level+1, user_msgptr, log_msgptr);
-    if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD)
-      {
-      *log_msgptr = string_sprintf("nested ACL returned \"discard\" for "
-        "\"%s\" command (only allowed with \"accept\" or \"discard\")",
-        verbs[verb]);
-      return ERROR;
-      }
+      rc = acl_check_wargs(where, addr, arg, level+1, user_msgptr, log_msgptr);
+      if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD)
+        {
+        *log_msgptr = string_sprintf("nested ACL returned \"discard\" for "
+          "\"%s\" command (only allowed with \"accept\" or \"discard\")",
+          verbs[verb]);
+        return ERROR;
+        }
     break;
 
     case ACLC_AUTHENTICATED:
@@ -3294,6 +3419,10 @@ for (; cb != NULL; cb = cb->next)
     break;
     #endif
 
+    case ACLC_REMOVE_HEADER:
+    setup_remove_header(arg);
+    break;
+
     case ACLC_SENDER_DOMAINS:
       {
       uschar *sdomain;
@@ -3863,10 +3992,97 @@ return FAIL;
 }
 
 
+
+
+/* Same args as acl_check_internal() above, but the string s is
+the name of an ACL followed optionally by up to 9 space-separated arguments.
+The name and args are separately expanded.  Args go into $acl_arg globals. */
+static int
+acl_check_wargs(int where, address_item *addr, uschar *s, int level,
+  uschar **user_msgptr, uschar **log_msgptr)
+{
+uschar * tmp;
+uschar * tmp_arg[9];   /* must match acl_arg[] */
+uschar * sav_arg[9];   /* must match acl_arg[] */
+int sav_narg;
+uschar * name;
+int i;
+int ret;
+
+if (!(tmp = string_dequote(&s)) || !(name = expand_string(tmp)))
+  goto bad;
+
+for (i = 0; i < 9; i++)
+  {
+  while (*s && isspace(*s)) s++;
+  if (!*s) break;
+  if (!(tmp = string_dequote(&s)) || !(tmp_arg[i] = expand_string(tmp)))
+    {
+    tmp = name;
+    goto bad;
+    }
+  }
+
+sav_narg = acl_narg;
+acl_narg = i;
+for (i = 0; i < acl_narg; i++)
+  {
+  sav_arg[i] = acl_arg[i];
+  acl_arg[i] = tmp_arg[i];
+  }
+while (i < 9)
+  {
+  sav_arg[i] = acl_arg[i];
+  acl_arg[i++] = NULL;
+  }
+
+ret = acl_check_internal(where, addr, name, level, user_msgptr, log_msgptr);
+
+acl_narg = sav_narg;
+for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i];
+return ret;
+
+bad:
+if (expand_string_forcedfail) return ERROR;
+*log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
+  tmp, expand_string_message);
+return search_find_defer?DEFER:ERROR;
+}
+
+
+
 /*************************************************
 *        Check access using an ACL               *
 *************************************************/
 
+/* Alternate interface for ACL, used by expansions */
+int
+acl_eval(int where, uschar *s, uschar **user_msgptr, uschar **log_msgptr)
+{
+address_item adb;
+address_item *addr = NULL;
+
+*user_msgptr = *log_msgptr = NULL;
+sender_verified_failed = NULL;
+ratelimiters_cmd = NULL;
+log_reject_target = LOG_MAIN|LOG_REJECT;
+
+if (where == ACL_WHERE_RCPT)
+  {
+  adb = address_defaults;
+  addr = &adb;
+  addr->address = expand_string(US"$local_part@$domain");
+  addr->domain = deliver_domain;
+  addr->local_part = deliver_localpart;
+  addr->cc_local_part = deliver_localpart;
+  addr->lc_local_part = deliver_localpart;
+  }
+
+return acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
+}
+
+
+
 /* This is the external interface for ACL checks. It sets up an address and the
 expansions for $domain and $local_part when called after RCPT, then calls
 acl_check_internal() to do the actual work.
@@ -3885,6 +4101,7 @@ Returns:       OK         access is granted by an ACCEPT verb
                DEFER      can't tell at the moment
                ERROR      disaster
 */
+int acl_where = ACL_WHERE_UNKNOWN;
 
 int
 acl_check(int where, uschar *recipient, uschar *s, uschar **user_msgptr,
@@ -3899,7 +4116,11 @@ sender_verified_failed = NULL;
 ratelimiters_cmd = NULL;
 log_reject_target = LOG_MAIN|LOG_REJECT;
 
-if (where == ACL_WHERE_RCPT)
+#ifdef EXPERIMENTAL_PRDR
+if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR )
+#else
+if (where == ACL_WHERE_RCPT )
+#endif
   {
   adb = address_defaults;
   addr = &adb;
@@ -3913,7 +4134,9 @@ if (where == ACL_WHERE_RCPT)
   deliver_localpart = addr->local_part;
   }
 
+acl_where = where;
 rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
+acl_where = ACL_WHERE_UNKNOWN;
 
 /* Cutthrough - if requested,
 and WHERE_RCPT and not yet opened conn as result of recipient-verify,
@@ -3937,6 +4160,9 @@ If conn-failure, no action (and keep the spooled copy).
 switch (where)
 {
 case ACL_WHERE_RCPT:
+#ifdef EXPERIMENTAL_PRDR
+case ACL_WHERE_PRDR:
+#endif
   if( rcpt_count > 1 )
     cancel_cutthrough_connection("more than one recipient");
   else if (rc == OK  &&  cutthrough_delivery  &&  cutthrough_fd < 0)
@@ -3996,7 +4222,6 @@ return rc;
 }
 
 
-
 /*************************************************
 *             Create ACL variable                *
 *************************************************/