Typo in debug output.
[exim.git] / src / src / expand.c
index 913a808b88d52be279c96ee0a9592d4a2dcf5f39..7803862738722ac63bff8210155aff94eb865342 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;
@@ -1928,6 +1964,7 @@ switch(cond_type)
       Ustrncmp(name, "bheader_", 8) == 0)
     {
     s = read_header_name(name, 256, s);
+    /* {-for-text-editors */
     if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
     if (yield != NULL) *yield =
       (find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
@@ -1987,10 +2024,11 @@ switch(cond_type)
   case ECOND_PWCHECK:
 
   while (isspace(*s)) s++;
-  if (*s != '{') goto COND_FAILED_CURLY_START;
+  if (*s != '{') goto COND_FAILED_CURLY_START;         /* }-for-text-editors */
 
   sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE);
   if (sub[0] == NULL) return NULL;
+  /* {-for-text-editors */
   if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
   if (yield == NULL) return s;   /* No need to run the test if skipping */
@@ -2066,19 +2104,72 @@ 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 == testfor;
+         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
   goto COND_FAILED_NOT_COMPILED;
   #else
   while (isspace(*s)) s++;
-  if (*s++ != '{') goto COND_FAILED_CURLY_START;
+  if (*s++ != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
   switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd"))
     {
     case 1: expand_string_message = US"too few arguments or bracketing "
@@ -2197,63 +2288,63 @@ switch(cond_type)
     {
     case ECOND_NUM_E:
     case ECOND_NUM_EE:
-    *yield = (num[0] == num[1]) == testfor;
+    tempcond = (num[0] == num[1]);
     break;
 
     case ECOND_NUM_G:
-    *yield = (num[0] > num[1]) == testfor;
+    tempcond = (num[0] > num[1]);
     break;
 
     case ECOND_NUM_GE:
-    *yield = (num[0] >= num[1]) == testfor;
+    tempcond = (num[0] >= num[1]);
     break;
 
     case ECOND_NUM_L:
-    *yield = (num[0] < num[1]) == testfor;
+    tempcond = (num[0] < num[1]);
     break;
 
     case ECOND_NUM_LE:
-    *yield = (num[0] <= num[1]) == testfor;
+    tempcond = (num[0] <= num[1]);
     break;
 
     case ECOND_STR_LT:
-    *yield = (Ustrcmp(sub[0], sub[1]) < 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) < 0);
     break;
 
     case ECOND_STR_LTI:
-    *yield = (strcmpic(sub[0], sub[1]) < 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) < 0);
     break;
 
     case ECOND_STR_LE:
-    *yield = (Ustrcmp(sub[0], sub[1]) <= 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) <= 0);
     break;
 
     case ECOND_STR_LEI:
-    *yield = (strcmpic(sub[0], sub[1]) <= 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) <= 0);
     break;
 
     case ECOND_STR_EQ:
-    *yield = (Ustrcmp(sub[0], sub[1]) == 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) == 0);
     break;
 
     case ECOND_STR_EQI:
-    *yield = (strcmpic(sub[0], sub[1]) == 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) == 0);
     break;
 
     case ECOND_STR_GT:
-    *yield = (Ustrcmp(sub[0], sub[1]) > 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) > 0);
     break;
 
     case ECOND_STR_GTI:
-    *yield = (strcmpic(sub[0], sub[1]) > 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) > 0);
     break;
 
     case ECOND_STR_GE:
-    *yield = (Ustrcmp(sub[0], sub[1]) >= 0) == testfor;
+    tempcond = (Ustrcmp(sub[0], sub[1]) >= 0);
     break;
 
     case ECOND_STR_GEI:
-    *yield = (strcmpic(sub[0], sub[1]) >= 0) == testfor;
+    tempcond = (strcmpic(sub[0], sub[1]) >= 0);
     break;
 
     case ECOND_MATCH:   /* Regular expression match */
@@ -2265,7 +2356,7 @@ switch(cond_type)
         "\"%s\": %s at offset %d", sub[1], rerror, roffset);
       return NULL;
       }
-    *yield = regex_match_and_setup(re, sub[0], 0, -1) == testfor;
+    tempcond = regex_match_and_setup(re, sub[0], 0, -1);
     break;
 
     case ECOND_MATCH_ADDRESS:  /* Match in an address list */
@@ -2321,11 +2412,11 @@ switch(cond_type)
     switch(rc)
       {
       case OK:
-      *yield = testfor;
+      tempcond = TRUE;
       break;
 
       case FAIL:
-      *yield = !testfor;
+      tempcond = FALSE;
       break;
 
       case DEFER:
@@ -2339,6 +2430,7 @@ switch(cond_type)
     /* Various "encrypted" comparisons. If the second string starts with
     "{" then an encryption type is given. Default to crypt() or crypt16()
     (build-time choice). */
+    /* }-for-text-editors */
 
     case ECOND_CRYPTEQ:
     #ifndef SUPPORT_CRYPTEQ
@@ -2363,7 +2455,7 @@ switch(cond_type)
         uschar *coded = auth_b64encode((uschar *)digest, 16);
         DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
-        *yield = (Ustrcmp(coded, sub[1]+5) == 0) == testfor;
+        tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
         }
       else if (sublen == 32)
         {
@@ -2373,13 +2465,13 @@ switch(cond_type)
         coded[32] = 0;
         DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+5);
-        *yield = (strcmpic(coded, sub[1]+5) == 0) == testfor;
+        tempcond = (strcmpic(coded, sub[1]+5) == 0);
         }
       else
         {
         DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
           "fail\n  crypted=%s\n", sub[1]+5);
-        *yield = !testfor;
+        tempcond = FALSE;
         }
       }
 
@@ -2401,7 +2493,7 @@ switch(cond_type)
         uschar *coded = auth_b64encode((uschar *)digest, 20);
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
-        *yield = (Ustrcmp(coded, sub[1]+6) == 0) == testfor;
+        tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
         }
       else if (sublen == 40)
         {
@@ -2411,13 +2503,13 @@ switch(cond_type)
         coded[40] = 0;
         DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
           "  subject=%s\n  crypted=%s\n", coded, sub[1]+6);
-        *yield = (strcmpic(coded, sub[1]+6) == 0) == testfor;
+        tempcond = (strcmpic(coded, sub[1]+6) == 0);
         }
       else
         {
         DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
           "fail\n  crypted=%s\n", sub[1]+6);
-        *yield = !testfor;
+       tempcond = FALSE;
         }
       }
 
@@ -2437,7 +2529,7 @@ switch(cond_type)
         sub[1] += 9;
         which = 2;
         }
-      else if (sub[1][0] == '{')
+      else if (sub[1][0] == '{')               /* }-for-text-editors */
         {
         expand_string_message = string_sprintf("unknown encryption mechanism "
           "in \"%s\"", sub[1]);
@@ -2464,8 +2556,8 @@ switch(cond_type)
       salt), force failure. Otherwise we get false positives: with an empty
       string the yield of crypt() is an empty string! */
 
-      *yield = (Ustrlen(sub[1]) < 2)? !testfor :
-        (Ustrcmp(coded, sub[1]) == 0) == testfor;
+      tempcond = (Ustrlen(sub[1]) < 2)? FALSE :
+        (Ustrcmp(coded, sub[1]) == 0);
       }
     break;
     #endif  /* SUPPORT_CRYPTEQ */
@@ -2474,10 +2566,10 @@ switch(cond_type)
     case ECOND_INLISTI:
       {
       int sep = 0;
-      BOOL found = FALSE;
       uschar *save_iterate_item = iterate_item;
       int (*compare)(const uschar *, const uschar *);
 
+      tempcond = FALSE;
       if (cond_type == ECOND_INLISTI)
         compare = strcmpic;
       else
@@ -2486,15 +2578,15 @@ switch(cond_type)
       while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL)
         if (compare(sub[0], iterate_item) == 0)
           {
-          found = TRUE;
+          tempcond = TRUE;
           break;
           }
       iterate_item = save_iterate_item;
-      *yield = found;
       }
 
     }   /* Switch for comparison conditions */
 
+  *yield = tempcond == testfor;
   return s;    /* End of comparison conditions */
 
 
@@ -2506,13 +2598,14 @@ switch(cond_type)
   combined_cond = (cond_type == ECOND_AND);
 
   while (isspace(*s)) s++;
-  if (*s++ != '{') goto COND_FAILED_CURLY_START;
+  if (*s++ != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
 
   for (;;)
     {
     while (isspace(*s)) s++;
+    /* {-for-text-editors */
     if (*s == '}') break;
-    if (*s != '{')
+    if (*s != '{')                                     /* }-for-text-editors */
       {
       expand_string_message = string_sprintf("each subcondition "
         "inside an \"%s{...}\" condition must be in its own {}", name);
@@ -2528,8 +2621,10 @@ switch(cond_type)
       }
     while (isspace(*s)) s++;
 
+    /* {-for-text-editors */
     if (*s++ != '}')
       {
+      /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
         "inside \"%s\" group", name);
       return NULL;
@@ -2563,13 +2658,14 @@ switch(cond_type)
     uschar *save_iterate_item = iterate_item;
 
     while (isspace(*s)) s++;
-    if (*s++ != '{') goto COND_FAILED_CURLY_START;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
     sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE);
     if (sub[0] == NULL) return NULL;
+    /* {-for-text-editors */
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
 
     while (isspace(*s)) s++;
-    if (*s++ != '{') goto COND_FAILED_CURLY_START;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
 
     sub[1] = s;
 
@@ -2586,8 +2682,10 @@ switch(cond_type)
       }
     while (isspace(*s)) s++;
 
+    /* {-for-text-editors */
     if (*s++ != '}')
       {
+      /* {-for-text-editors */
       expand_string_message = string_sprintf("missing } at end of condition "
         "inside \"%s\"", name);
       return NULL;
@@ -2635,7 +2733,7 @@ switch(cond_type)
     size_t len;
     BOOL boolvalue = FALSE;
     while (isspace(*s)) s++;
-    if (*s != '{') goto COND_FAILED_CURLY_START;
+    if (*s != '{') goto COND_FAILED_CURLY_START;       /* }-for-text-editors */
     ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
     switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname))
       {
@@ -3654,18 +3752,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 +3773,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;
        }
       }