Apply Jakob Hirsch's patch for arbitrary ACL variable names, tidied up
[exim.git] / src / src / acl.c
index 9df6339a7cb1c838637d4faab9aeb3a87084c631..8c2ab699a11982f94af7412351d0a80603c62d36 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/acl.c,v 1.57 2006/03/06 16:05:12 ph10 Exp $ */
+/* $Cambridge: exim/src/src/acl.c,v 1.64 2006/09/19 11:28:45 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -202,8 +202,8 @@ at the outer level. In the other cases, expansion already occurs in the
 checking functions. */
 
 static uschar cond_expand_at_top[] = {
-  TRUE,    /* add_header */
   TRUE,    /* acl */
+  TRUE,    /* add_header */
   FALSE,   /* authenticated */
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   TRUE,    /* bmi_optin */
@@ -260,8 +260,8 @@ static uschar cond_expand_at_top[] = {
 /* Flags to identify the modifiers */
 
 static uschar cond_modifiers[] = {
-  TRUE,    /* add_header */
   FALSE,   /* acl */
+  TRUE,    /* add_header */
   FALSE,   /* authenticated */
 #ifdef EXPERIMENTAL_BRIGHTMAIL
   TRUE,    /* bmi_optin */
@@ -323,12 +323,14 @@ static unsigned int cond_forbids[] = {
   0,                                               /* acl */
 
   (unsigned int)
-  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|      /* add_header */
+  ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* add_header */
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
-    (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)),
+    (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
+    (1<<ACL_WHERE_NOTSMTP_START)),
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* authenticated */
-    (1<<ACL_WHERE_HELO),
+  (1<<ACL_WHERE_NOTSMTP)|                          /* authenticated */
+    (1<<ACL_WHERE_NOTSMTP_START)|
+    (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO),
 
   #ifdef EXPERIMENTAL_BRIGHTMAIL
   (1<<ACL_WHERE_AUTH)|                             /* bmi_optin */
@@ -337,7 +339,8 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_PREDATA),
+    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_NOTSMTP_START),
   #endif
 
   0,                                               /* condition */
@@ -366,7 +369,7 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY),
+    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_NOTSMTP_START),
 
   (1<<ACL_WHERE_AUTH)|                             /* dk_policy */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
@@ -374,7 +377,7 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY),
+    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_NOTSMTP_START),
 
   (1<<ACL_WHERE_AUTH)|                             /* dk_sender_domains */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
@@ -382,7 +385,7 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY),
+    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_NOTSMTP_START),
 
   (1<<ACL_WHERE_AUTH)|                             /* dk_sender_local_parts */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
@@ -390,7 +393,7 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY),
+    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_NOTSMTP_START),
 
   (1<<ACL_WHERE_AUTH)|                             /* dk_senders */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
@@ -398,7 +401,7 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY),
+    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_NOTSMTP_START),
 
   (1<<ACL_WHERE_AUTH)|                             /* dk_status */
     (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
@@ -406,20 +409,24 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
     (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
     (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
-    (1<<ACL_WHERE_VRFY),
+    (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_NOTSMTP_START),
   #endif
 
-  (1<<ACL_WHERE_NOTSMTP),                          /* dnslists */
+  (1<<ACL_WHERE_NOTSMTP)|                          /* dnslists */
+    (1<<ACL_WHERE_NOTSMTP_START),
 
   (unsigned int)
   ~(1<<ACL_WHERE_RCPT),                            /* domains */
 
-  (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)|   /* encrypted */
+  (1<<ACL_WHERE_NOTSMTP)|                          /* encrypted */
+    (1<<ACL_WHERE_CONNECT)|
+    (1<<ACL_WHERE_NOTSMTP_START)|
     (1<<ACL_WHERE_HELO),
 
   0,                                               /* endpass */
 
-  (1<<ACL_WHERE_NOTSMTP),                          /* hosts */
+  (1<<ACL_WHERE_NOTSMTP)|                          /* hosts */
+    (1<<ACL_WHERE_NOTSMTP_START),
 
   (unsigned int)
   ~(1<<ACL_WHERE_RCPT),                            /* local_parts */
@@ -475,7 +482,9 @@ static unsigned int cond_forbids[] = {
     (1<<ACL_WHERE_HELO)|
     (1<<ACL_WHERE_MAILAUTH)|
     (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
-    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+    (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
+    (1<<ACL_WHERE_NOTSMTP)|
+    (1<<ACL_WHERE_NOTSMTP_START),
   #endif
 
   /* Certain types of verify are always allowed, so we let it through
@@ -498,7 +507,8 @@ static unsigned int control_forbids[] = {
   #endif
 
   #ifdef EXPERIMENTAL_DOMAINKEYS
-  (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP),      /* dk_verify */
+  (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|      /* dk_verify */
+    (1<<ACL_WHERE_NOTSMTP_START),
   #endif
 
   0,                                               /* error */
@@ -509,9 +519,11 @@ static unsigned int control_forbids[] = {
   (unsigned int)
   ~(1<<ACL_WHERE_RCPT),                            /* caselower_local_part */
 
-  (1<<ACL_WHERE_NOTSMTP),                          /* enforce_sync */
+  (1<<ACL_WHERE_NOTSMTP)|                          /* enforce_sync */
+    (1<<ACL_WHERE_NOTSMTP_START),
 
-  (1<<ACL_WHERE_NOTSMTP),                          /* no_enforce_sync */
+  (1<<ACL_WHERE_NOTSMTP)|                          /* no_enforce_sync */
+    (1<<ACL_WHERE_NOTSMTP_START),
 
   (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* freeze */
@@ -529,7 +541,8 @@ static unsigned int control_forbids[] = {
 
   (unsigned int)
   ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|       /* suppress_local_fixups */
-    (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_PREDATA)),
+    (1<<ACL_WHERE_PREDATA)|
+    (1<<ACL_WHERE_NOTSMTP_START)),
 
   #ifdef WITH_CONTENT_SCAN
   (unsigned int)
@@ -548,7 +561,8 @@ static unsigned int control_forbids[] = {
     (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
     (1<<ACL_WHERE_MIME)),
 
-  (1<<ACL_WHERE_NOTSMTP)                           /* no_multiline */
+  (1<<ACL_WHERE_NOTSMTP)|                          /* no_multiline */
+    (1<<ACL_WHERE_NOTSMTP_START)
 };
 
 /* Structure listing various control arguments, with their characteristics. */
@@ -710,7 +724,7 @@ while ((s = (*func)()) != NULL)
   can be started by a name, or by a macro definition. */
 
   s = readconf_readname(name, sizeof(name), s);
-  if (*s == ':' || isupper(name[0] && *s == '=')) return yield;
+  if (*s == ':' || (isupper(name[0]) && *s == '=')) return yield;
 
   /* If a verb is unrecognized, it may be another condition or modifier that
   continues the previous verb. */
@@ -720,7 +734,8 @@ while ((s = (*func)()) != NULL)
     {
     if (this == NULL)
       {
-      *error = string_sprintf("unknown ACL verb in \"%s\"", saveline);
+      *error = string_sprintf("unknown ACL verb \"%s\" in \"%s\"", name,
+        saveline);
       return NULL;
       }
     }
@@ -790,36 +805,44 @@ while ((s = (*func)()) != NULL)
 
   /* The "set" modifier is different in that its argument is "name=value"
   rather than just a value, and we can check the validity of the name, which
-  gives us a variable number to insert into the data block. */
+  gives us a variable name to insert into the data block. The original ACL
+  variable names were acl_c0 ... acl_c9 and acl_m0 ... acl_m9. This was
+  extended to 20 of each type, but after that people successfully argued for
+  arbitrary names. For compatibility, however, the names must still start with
+  acl_c or acl_m. After that, we allow alphanumerics and underscores. */
 
   if (c == ACLC_SET)
     {
-    int offset, max, n;
     uschar *endptr;
 
-    if (Ustrncmp(s, "acl_", 4) != 0) goto BAD_ACL_VAR;
-    if (s[4] == 'c')
+    if (Ustrncmp(s, "acl_c", 5) != 0 &&
+        Ustrncmp(s, "acl_m", 5) != 0)
       {
-      offset = 0;
-      max = ACL_CVARS;
+      *error = string_sprintf("invalid variable name after \"set\" in ACL "
+        "modifier \"set %s\" (must start \"acl_c\" or \"acl_m\")", s);
+      return NULL;
       }
-    else if (s[4] == 'm')
+
+    endptr = s + 5;
+    while (*endptr != 0 && *endptr != '=' && !isspace(*endptr))
       {
-      offset = ACL_CVARS;
-      max = ACL_MVARS;
+      if (!isalnum(*endptr) && *endptr != '_')
+        {
+        *error = string_sprintf("invalid character \"%c\" in variable name "
+          "in ACL modifier \"set %s\"", *endptr, s);
+        return NULL;
+        }
+      endptr++;
       }
-    else goto BAD_ACL_VAR;
 
-    n = Ustrtoul(s + 5, &endptr, 10);
-    if ((*endptr != 0 && *endptr != '=' && !isspace(*endptr)) || n >= max)
+    if (endptr - s < 6)
       {
-      BAD_ACL_VAR:
-      *error = string_sprintf("syntax error or unrecognized name after "
-        "\"set\" in ACL modifier \"set %s\"", s);
+      *error = string_sprintf("invalid variable name after \"set\" in ACL "
+        "modifier \"set %s\" (must be at least 6 characters)", s);
       return NULL;
       }
 
-    cond->u.varnumber = n + offset;
+    cond->u.varname = string_copyn(s + 4, endptr - s - 4);
     s = endptr;
     while (isspace(*s)) s++;
     }
@@ -2035,6 +2058,7 @@ ACL clauses like: defer ratelimit = 15 / 1h
 
 Arguments:
   arg         the option string for ratelimit=
+  where       ACL_WHERE_xxxx indicating which ACL this is
   log_msgptr  for error messages
 
 Returns:       OK        - Sender's rate is above limit
@@ -2044,7 +2068,7 @@ Returns:       OK        - Sender's rate is above limit
 */
 
 static int
-acl_ratelimit(uschar *arg, uschar **log_msgptr)
+acl_ratelimit(uschar *arg, int where, uschar **log_msgptr)
 {
 double limit, period;
 uschar *ss, *key;
@@ -2263,6 +2287,9 @@ else
   if (per_byte)
     dbd->rate = (message_size < 0 ? 0.0 : (double)message_size)
               * (1 - a) / i_over_p + a * dbd->rate;
+  else if (per_cmd && where == ACL_WHERE_NOTSMTP)
+    dbd->rate = (double)recipients_count
+              * (1 - a) / i_over_p + a * dbd->rate;
   else
     dbd->rate = (1 - a) / i_over_p + a * dbd->rate;
   }
@@ -2344,7 +2371,7 @@ acl_check_condition(int verb, acl_condition_block *cb, int where,
 {
 uschar *user_message = NULL;
 uschar *log_message = NULL;
-uschar *p;
+uschar *p = NULL;
 int rc = OK;
 #ifdef WITH_CONTENT_SCAN
 int sep = '/';
@@ -2407,11 +2434,8 @@ for (; cb != NULL; cb = cb->next)
 
     if (cb->type == ACLC_SET)
       {
-      int n = cb->u.varnumber;
-      int t = (n < ACL_CVARS)? 'c' : 'm';
-      if (n >= ACL_CVARS) n -= ACL_CVARS;
-      debug_printf("acl_%c%d ", t, n);
-      lhswidth += 7;
+      debug_printf("acl_%s ", cb->u.varname);
+      lhswidth += 5 + Ustrlen(cb->u.varname);
       }
 
     debug_printf("= %s\n", cb->arg);
@@ -2873,7 +2897,7 @@ for (; cb != NULL; cb = cb->next)
     #endif
 
     case ACLC_RATELIMIT:
-    rc = acl_ratelimit(arg, log_msgptr);
+    rc = acl_ratelimit(arg, where, log_msgptr);
     break;
 
     case ACLC_RECIPIENTS:
@@ -2907,8 +2931,8 @@ for (; cb != NULL; cb = cb->next)
     case ACLC_SET:
       {
       int old_pool = store_pool;
-      if (cb->u.varnumber < ACL_CVARS) store_pool = POOL_PERM;
-      acl_var[cb->u.varnumber] = string_copy(arg);
+      if (cb->u.varname[0] == 'c') store_pool = POOL_PERM;
+      acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
       store_pool = old_pool;
       }
     break;
@@ -3578,4 +3602,64 @@ if (rc != OK && *user_msgptr != NULL && Ustrlen(*user_msgptr) > 75)
 return rc;
 }
 
+
+
+/*************************************************
+*             Create ACL variable                *
+*************************************************/
+
+/* Create an ACL variable or reuse an existing one. ACL variables are in a
+binary tree (see tree.c) with acl_var_c and acl_var_m as root nodes.
+
+Argument:
+  name    pointer to the variable's name, starting with c or m
+
+Returns   the pointer to variable's tree node
+*/
+
+tree_node *
+acl_var_create(uschar *name)
+{
+tree_node *node, **root;
+root = (name[0] == 'c')? &acl_var_c : &acl_var_m;
+node = tree_search(*root, name);
+if (node == NULL)
+  {
+  node = store_get(sizeof(tree_node) + Ustrlen(name));
+  Ustrcpy(node->name, name);
+  (void)tree_insertnode(root, node);
+  }
+node->data.ptr = NULL;
+return node;
+}
+
+
+
+/*************************************************
+*       Write an ACL variable in spool format    *
+*************************************************/
+
+/* This function is used as a callback for tree_walk when writing variables to
+the spool file. To retain spool file compatibility, what is written is -aclc or
+-aclm followed by the rest of the name and the data length, space separated,
+then the value itself, starting on a new line, and terminated by an additional
+newline. When we had only numbered ACL variables, the first line might look
+like this: "-aclc 5 20". Now it might be "-aclc foo 20" for the variable called
+acl_cfoo.
+
+Arguments:
+  name    of the variable
+  value   of the variable
+  ctx     FILE pointer (as a void pointer)
+
+Returns:  nothing
+*/
+
+void
+acl_var_write(uschar *name, uschar *value, void *ctx)
+{
+FILE *f = (FILE *)ctx;
+fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
+}
+
 /* End of acl.c */