Acl expansions: tests and documentation
[exim.git] / src / src / expand.c
index 913a808b88d52be279c96ee0a9592d4a2dcf5f39..767e4771a2c1129751f2b358866e085a9533f973 100644 (file)
@@ -249,6 +249,7 @@ static uschar *cond_table[] = {
   US"==",     /* Backward compatibility */
   US">",
   US">=",
+  US"acl",
   US"and",
   US"bool",
   US"bool_lax",
@@ -294,6 +295,7 @@ enum {
   ECOND_NUM_EE,
   ECOND_NUM_G,
   ECOND_NUM_GE,
+  ECOND_ACL,
   ECOND_AND,
   ECOND_BOOL,
   ECOND_BOOL_LAX,
@@ -1834,6 +1836,40 @@ if (Ustrncmp(name, "acl_", 4) == 0)
 
 
 
+/*
+Load args from sub array to globals, and call acl_check().
+
+Returns:       OK         access is granted by an ACCEPT verb
+               DISCARD    access is granted by a DISCARD verb
+              FAIL       access is denied
+              FAIL_DROP  access is denied; drop the connection
+              DEFER      can't tell at the moment
+              ERROR      disaster
+*/
+static int
+eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
+{
+int i;
+uschar *dummy_log_msg;
+
+for (i = 1; i < nsub && sub[i]; i++)
+  acl_arg[i-1] = sub[i];
+acl_narg = i-1;
+while (i < nsub)
+  acl_arg[i++ - 1] = NULL;
+
+DEBUG(D_expand)
+  debug_printf("expanding: acl: %s  arg: %s%s\n",
+    sub[0],
+    acl_narg>0 ? sub[1]   : US"<none>",
+    acl_narg>1 ? " +more" : "");
+
+return acl_check(ACL_WHERE_EXPANSION, NULL, sub[0], user_msgp, &dummy_log_msg);
+}
+
+
+
+
 /*************************************************
 *        Read and evaluate a condition           *
 *************************************************/
@@ -1861,7 +1897,7 @@ int i, rc, cond_type, roffset;
 int_eximarith_t num[2];
 struct stat statbuf;
 uschar name[256];
-uschar *sub[4];
+uschar *sub[10];
 
 const pcre *re;
 const uschar *rerror;
@@ -2066,12 +2102,65 @@ switch(cond_type)
   return s;
 
 
+  /* call ACL (in a conditional context).  Accept true, deny false.
+  Defer is a forced-fail.  Anything set by message= goes to $value.
+  Up to ten parameters are used; we use the braces round the name+args
+  like the saslauthd condition does, to permit a variable number of args.
+  See also the expansion-item version EITEM_ACL and the traditional
+  acl modifier ACLC_ACL.
+  */
+
+  case ECOND_ACL:
+    /* ${if acl {{name}{arg1}{arg2}...}  {yes}{no}} */
+    {
+    uschar *nameargs;
+    uschar *user_msg;
+    BOOL cond = FALSE;
+    int size = 0;
+    int ptr = 0;
+
+    while (isspace(*s)) s++;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;
+
+    switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
+      &s, yield == NULL, TRUE, US"acl"))
+      {
+      case 1: expand_string_message = US"too few arguments or bracketing "
+        "error for acl";
+      case 2:
+      case 3: return NULL;
+      }
+
+    if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+       {
+       case OK:
+         cond = TRUE;
+       case FAIL:
+          lookup_value = NULL;
+         if (user_msg)
+           {
+            lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
+            lookup_value[ptr] = '\0';
+           }
+         *yield = cond;
+         break;
+
+       case DEFER:
+          expand_string_forcedfail = TRUE;
+       default:
+          expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+         return NULL;
+       }
+    return s;
+    }
+
+
   /* saslauthd: does Cyrus saslauthd authentication. Four parameters are used:
 
      ${if saslauthd {{username}{password}{service}{realm}}  {yes}[no}}
 
   However, the last two are optional. That is why the whole set is enclosed
-  in their own set or braces. */
+  in their own set of braces. */
 
   case ECOND_SASLAUTHD:
   #ifndef CYRUS_SASLAUTHD_SOCKET
@@ -3654,18 +3743,19 @@ while (*s != 0)
   switch(item_type)
     {
     /* Call an ACL from an expansion.  We feed data in via $acl_arg1 - $acl_arg9.
-    If the ACL returns acceptance we return content set by "message ="
+    If the ACL returns accept or reject we return content set by "message ="
     There is currently no limit on recursion; this would have us call
     acl_check_internal() directly and get a current level from somewhere.
+    See also the acl expansion condition ECOND_ACL and the traditional
+    acl modifier ACLC_ACL.
     */
 
     case EITEM_ACL:
+      /* ${acl {name} {arg1}{arg2}...} */
       {
-      int rc;
-      uschar *sub[10]; /* name + arg1-arg9, must match number of acl_arg[] */
-      uschar *new_yield;
+      uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
       uschar *user_msg;
-      uschar *log_msg;
+
       switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl"))
         {
         case 1: goto EXPAND_FAILED_CURLY;
@@ -3674,28 +3764,18 @@ while (*s != 0)
         }
       if (skipping) continue;
 
-      for (rc = 1; rc < sizeof(sub)/sizeof(*sub) && sub[rc]; rc++)
-        acl_arg[rc-1] = sub[rc];
-      acl_narg = rc-1;
-      while (rc < sizeof(sub)/sizeof(*sub))
-        acl_arg[rc++ - 1] = NULL;
-
-      DEBUG(D_expand)
-        debug_printf("expanding: acl: %s  arg: %s%s\n",
-         sub[0],
-         acl_narg>0 ? sub[1]   : US"<none>",
-         acl_narg>1 ? " +more" : "");
-
-      switch(rc = acl_check(ACL_WHERE_EXPANSION, NULL, sub[0], &user_msg, &log_msg))
+      switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
        {
        case OK:
+       case FAIL:
          if (user_msg)
             yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
          continue;
+
        case DEFER:
-         continue;
+          expand_string_forcedfail = TRUE;
        default:
-          expand_string_message = string_sprintf("acl \"%s\" did not accept", sub[0]);
+          expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          goto EXPAND_FAILED;
        }
       }