Go to 20 ACL variables of each type, and make the numbers changeable at
[exim.git] / src / src / expand.c
index 80971cb263e4de99a2d8e4d709c7b3f6b06d8a39..048b87a096104c5106a701a300fd09100139b902 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/expand.c,v 1.34 2005/06/22 10:17:23 ph10 Exp $ */
+/* $Cambridge: exim/src/src/expand.c,v 1.51 2005/12/12 15:58:53 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -298,26 +298,6 @@ enum {
 /* This table must be kept in alphabetical order. */
 
 static var_entry var_table[] = {
 /* This table must be kept in alphabetical order. */
 
 static var_entry var_table[] = {
-  { "acl_c0",              vtype_stringptr,   &acl_var[0] },
-  { "acl_c1",              vtype_stringptr,   &acl_var[1] },
-  { "acl_c2",              vtype_stringptr,   &acl_var[2] },
-  { "acl_c3",              vtype_stringptr,   &acl_var[3] },
-  { "acl_c4",              vtype_stringptr,   &acl_var[4] },
-  { "acl_c5",              vtype_stringptr,   &acl_var[5] },
-  { "acl_c6",              vtype_stringptr,   &acl_var[6] },
-  { "acl_c7",              vtype_stringptr,   &acl_var[7] },
-  { "acl_c8",              vtype_stringptr,   &acl_var[8] },
-  { "acl_c9",              vtype_stringptr,   &acl_var[9] },
-  { "acl_m0",              vtype_stringptr,   &acl_var[10] },
-  { "acl_m1",              vtype_stringptr,   &acl_var[11] },
-  { "acl_m2",              vtype_stringptr,   &acl_var[12] },
-  { "acl_m3",              vtype_stringptr,   &acl_var[13] },
-  { "acl_m4",              vtype_stringptr,   &acl_var[14] },
-  { "acl_m5",              vtype_stringptr,   &acl_var[15] },
-  { "acl_m6",              vtype_stringptr,   &acl_var[16] },
-  { "acl_m7",              vtype_stringptr,   &acl_var[17] },
-  { "acl_m8",              vtype_stringptr,   &acl_var[18] },
-  { "acl_m9",              vtype_stringptr,   &acl_var[19] },
   { "acl_verify_message",  vtype_stringptr,   &acl_verify_message },
   { "address_data",        vtype_stringptr,   &deliver_address_data },
   { "address_file",        vtype_stringptr,   &address_file },
   { "acl_verify_message",  vtype_stringptr,   &acl_verify_message },
   { "address_data",        vtype_stringptr,   &deliver_address_data },
   { "address_file",        vtype_stringptr,   &address_file },
@@ -399,6 +379,7 @@ static var_entry var_table[] = {
   { "message_body",        vtype_msgbody,     &message_body },
   { "message_body_end",    vtype_msgbody_end, &message_body_end },
   { "message_body_size",   vtype_int,         &message_body_size },
   { "message_body",        vtype_msgbody,     &message_body },
   { "message_body_end",    vtype_msgbody_end, &message_body_end },
   { "message_body_size",   vtype_int,         &message_body_size },
+  { "message_exim_id",     vtype_stringptr,   &message_id },
   { "message_headers",     vtype_msgheaders,  NULL },
   { "message_id",          vtype_stringptr,   &message_id },
   { "message_linecount",   vtype_int,         &message_linecount },
   { "message_headers",     vtype_msgheaders,  NULL },
   { "message_id",          vtype_stringptr,   &message_id },
   { "message_linecount",   vtype_int,         &message_linecount },
@@ -481,7 +462,8 @@ static var_entry var_table[] = {
   { "sender_rcvhost",      vtype_stringptr,   &sender_rcvhost },
   { "sender_verify_failure",vtype_stringptr,  &sender_verify_failure },
   { "smtp_active_hostname", vtype_stringptr,  &smtp_active_hostname },
   { "sender_rcvhost",      vtype_stringptr,   &sender_rcvhost },
   { "sender_verify_failure",vtype_stringptr,  &sender_verify_failure },
   { "smtp_active_hostname", vtype_stringptr,  &smtp_active_hostname },
-  { "smtp_command_argument", vtype_stringptr, &smtp_command_argument },
+  { "smtp_command",        vtype_stringptr,   &smtp_cmd_buffer },
+  { "smtp_command_argument", vtype_stringptr, &smtp_cmd_argument },
   { "sn0",                 vtype_filter_int,  &filter_sn[0] },
   { "sn1",                 vtype_filter_int,  &filter_sn[1] },
   { "sn2",                 vtype_filter_int,  &filter_sn[2] },
   { "sn0",                 vtype_filter_int,  &filter_sn[0] },
   { "sn1",                 vtype_filter_int,  &filter_sn[1] },
   { "sn2",                 vtype_filter_int,  &filter_sn[2] },
@@ -1203,7 +1185,8 @@ else
   uschar *decoded, *error;
   while (ptr > yield && isspace(ptr[-1])) ptr--;
   *ptr = 0;
   uschar *decoded, *error;
   while (ptr > yield && isspace(ptr[-1])) ptr--;
   *ptr = 0;
-  decoded = rfc2047_decode2(yield, TRUE, charset, '?', NULL, newsize, &error);
+  decoded = rfc2047_decode2(yield, check_rfc2047_length, charset, '?', NULL,
+    newsize, &error);
   if (error != NULL)
     {
     DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
   if (error != NULL)
     {
     DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
@@ -1246,6 +1229,33 @@ find_variable(uschar *name, BOOL exists_only, BOOL skipping, int *newsize)
 int first = 0;
 int last = var_table_size;
 
 int first = 0;
 int last = var_table_size;
 
+/* Handle ACL variables, which are not in the table because their number may
+vary depending on a build-time setting. */
+
+if (Ustrncmp(name, "acl_", 4) == 0)
+  {
+  int offset, max, n;
+  uschar *endptr;
+
+  if (name[4] == 'm')
+    {
+    offset = ACL_CVARS;
+    max = ACL_MVARS;
+    }
+  else if (name[4] == 'c')
+    {
+    offset = 0;
+    max = ACL_CVARS;
+    }
+  else return NULL;
+
+  n = Ustrtoul(name + 5, &endptr, 10);
+  if (*endptr != 0 || n >= max) return NULL;
+  return (acl_var[offset + n] == NULL)? US"" : acl_var[offset + n];
+  }
+
+/* For all other variables, search the table */
+
 while (last > first)
   {
   uschar *s, *domain;
 while (last > first)
   {
   uschar *s, *domain;
@@ -1257,7 +1267,7 @@ while (last > first)
   if (c < 0) { last = middle; continue; }
 
   /* Found an existing variable. If in skipping state, the value isn't needed,
   if (c < 0) { last = middle; continue; }
 
   /* Found an existing variable. If in skipping state, the value isn't needed,
-  and we want to avoid processing (such as looking up up the host name). */
+  and we want to avoid processing (such as looking up the host name). */
 
   if (skipping) return US"";
 
 
   if (skipping) return US"";
 
@@ -1423,10 +1433,22 @@ while (last > first)
     return tod_stamp(tod_log_datestamp);
 
     case vtype_reply:                          /* Get reply address */
     return tod_stamp(tod_log_datestamp);
 
     case vtype_reply:                          /* Get reply address */
-    s = find_header(US"reply-to:", exists_only, newsize, FALSE,
+    s = find_header(US"reply-to:", exists_only, newsize, TRUE,
       headers_charset);
       headers_charset);
+    if (s != NULL) while (isspace(*s)) s++;
     if (s == NULL || *s == 0)
     if (s == NULL || *s == 0)
-      s = find_header(US"from:", exists_only, newsize, FALSE, headers_charset);
+      {
+      *newsize = 0;                            /* For the *s==0 case */
+      s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset);
+      }
+    if (s != NULL)
+      {
+      uschar *t;
+      while (isspace(*s)) s++;
+      for (t = s; *t != 0; t++) if (*t == '\n') *t = ' ';
+      while (t > s && isspace(t[-1])) t--;
+      *t = 0;
+      }
     return (s == NULL)? US"" : s;
 
     /* A recipients list is available only during system message filtering,
     return (s == NULL)? US"" : s;
 
     /* A recipients list is available only during system message filtering,
@@ -1707,7 +1729,7 @@ switch(cond_type)
     case ECOND_ISIP4:
     case ECOND_ISIP6:
     rc = string_is_ip_address(sub[0], NULL);
     case ECOND_ISIP4:
     case ECOND_ISIP6:
     rc = string_is_ip_address(sub[0], NULL);
-    *yield = ((cond_type == ECOND_ISIP)? (rc > 0) :
+    *yield = ((cond_type == ECOND_ISIP)? (rc != 0) :
              (cond_type == ECOND_ISIP4)? (rc == 4) : (rc == 6)) == testfor;
     break;
 
              (cond_type == ECOND_ISIP4)? (rc == 4) : (rc == 6)) == testfor;
     break;
 
@@ -1967,7 +1989,7 @@ switch(cond_type)
     goto MATCHED_SOMETHING;
 
     case ECOND_MATCH_IP:       /* Match IP address in a host list */
     goto MATCHED_SOMETHING;
 
     case ECOND_MATCH_IP:       /* Match IP address in a host list */
-    if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) <= 0)
+    if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0)
       {
       expand_string_message = string_sprintf("\"%s\" is not an IP address",
         sub[0]);
       {
       expand_string_message = string_sprintf("\"%s\" is not an IP address",
         sub[0]);
@@ -2745,12 +2767,14 @@ uschar *s = *sptr;
 int x = eval_term(&s, decimal, error);
 if (*error == NULL)
   {
 int x = eval_term(&s, decimal, error);
 if (*error == NULL)
   {
-  while (*s == '*' || *s == '/')
+  while (*s == '*' || *s == '/' || *s == '%')
     {
     int op = *s++;
     int y = eval_term(&s, decimal, error);
     if (*error != NULL) break;
     {
     int op = *s++;
     int y = eval_term(&s, decimal, error);
     if (*error != NULL) break;
-    if (op == '*') x *= y; else x /= y;
+    if (op == '*') x *= y;
+      else if (op == '/') x /= y;
+      else x %= y;
     }
   }
 *sptr = s;
     }
   }
 *sptr = s;
@@ -3125,7 +3149,7 @@ while (*s != 0)
       /* Check that a key was provided for those lookup types that need it,
       and was not supplied for those that use the query style. */
 
       /* Check that a key was provided for those lookup types that need it,
       and was not supplied for those that use the query style. */
 
-      if (!mac_islookup(stype, lookup_querystyle))
+      if (!mac_islookup(stype, lookup_querystyle|lookup_absfilequery))
         {
         if (key == NULL)
           {
         {
         if (key == NULL)
           {
@@ -3145,7 +3169,9 @@ while (*s != 0)
         }
 
       /* Get the next string in brackets and expand it. It is the file name for
         }
 
       /* Get the next string in brackets and expand it. It is the file name for
-      single-key+file lookups, and the whole query otherwise. */
+      single-key+file lookups, and the whole query otherwise. In the case of
+      queries that also require a file name (e.g. sqlite), the file name comes
+      first. */
 
       if (*s != '{') goto EXPAND_FAILED_CURLY;
       filename = expand_string_internal(s+1, TRUE, &s, skipping);
 
       if (*s != '{') goto EXPAND_FAILED_CURLY;
       filename = expand_string_internal(s+1, TRUE, &s, skipping);
@@ -3154,12 +3180,30 @@ while (*s != 0)
       while (isspace(*s)) s++;
 
       /* If this isn't a single-key+file lookup, re-arrange the variables
       while (isspace(*s)) s++;
 
       /* If this isn't a single-key+file lookup, re-arrange the variables
-      to be appropriate for the search_ functions. */
+      to be appropriate for the search_ functions. For query-style lookups,
+      there is just a "key", and no file name. For the special query-style +
+      file types, the query (i.e. "key") starts with a file name. */
 
       if (key == NULL)
         {
 
       if (key == NULL)
         {
+        while (isspace(*filename)) filename++;
         key = filename;
         key = filename;
-        filename = NULL;
+
+        if (mac_islookup(stype, lookup_querystyle))
+          {
+          filename = NULL;
+          }
+        else
+          {
+          if (*filename != '/')
+            {
+            expand_string_message = string_sprintf(
+              "absolute file name expected for \"%s\" lookup", name);
+            goto EXPAND_FAILED;
+            }
+          while (*key != 0 && !isspace(*key)) key++;
+          if (*key != 0) *key++ = 0;
+          }
         }
 
       /* If skipping, don't do the next bit - just lookup_value == NULL, as if
         }
 
       /* If skipping, don't do the next bit - just lookup_value == NULL, as if
@@ -3333,15 +3377,24 @@ while (*s != 0)
       domain = Ustrrchr(sub_arg[0],'@');
       if ( (domain == NULL) || (domain == sub_arg[0]) || (Ustrlen(domain) == 1) )
         {
       domain = Ustrrchr(sub_arg[0],'@');
       if ( (domain == NULL) || (domain == sub_arg[0]) || (Ustrlen(domain) == 1) )
         {
-        expand_string_message = US"first parameter must be a qualified email address";
+        expand_string_message = US"prvs first argument must be a qualified email address";
+        goto EXPAND_FAILED;
+        }
+
+      /* Calculate the hash. The second argument must be a single-digit
+      key number, or unset. */
+
+      if (sub_arg[2] != NULL &&
+          (!isdigit(sub_arg[2][0]) || sub_arg[2][1] != 0))
+        {
+        expand_string_message = US"prvs second argument must be a single digit";
         goto EXPAND_FAILED;
         }
 
         goto EXPAND_FAILED;
         }
 
-      /* Calculate the hash */
       p = prvs_hmac_sha1(sub_arg[0],sub_arg[1],sub_arg[2],prvs_daystamp(7));
       if (p == NULL)
         {
       p = prvs_hmac_sha1(sub_arg[0],sub_arg[1],sub_arg[2],prvs_daystamp(7));
       if (p == NULL)
         {
-        expand_string_message = US"hmac-sha1 conversion failed";
+        expand_string_message = US"prvs hmac-sha1 conversion failed";
         goto EXPAND_FAILED;
         }
 
         goto EXPAND_FAILED;
         }
 
@@ -3368,18 +3421,23 @@ while (*s != 0)
       int mysize = 0, myptr = 0;
       const pcre *re;
       uschar *p;
       int mysize = 0, myptr = 0;
       const pcre *re;
       uschar *p;
-      /* Ugliness: We want to expand parameter 1 first, then set
+
+      /* TF: Ugliness: We want to expand parameter 1 first, then set
          up expansion variables that are used in the expansion of
          parameter 2. So we clone the string for the first
          up expansion variables that are used in the expansion of
          parameter 2. So we clone the string for the first
-         expansion, where we only expand paramter 1. */
-      uschar *s_backup = string_copy(s);
+         expansion, where we only expand parameter 1.
+
+         PH: Actually, that isn't necessary. The read_subs() function is
+         designed to work this way for the ${if and ${lookup expansions. I've
+         tidied the code.
+      */
 
       /* Reset expansion variables */
       prvscheck_result = NULL;
       prvscheck_address = NULL;
       prvscheck_keynum = NULL;
 
 
       /* Reset expansion variables */
       prvscheck_result = NULL;
       prvscheck_address = NULL;
       prvscheck_keynum = NULL;
 
-      switch(read_subs(sub_arg, 1, 1, &s_backup, skipping, FALSE, US"prvs"))
+      switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs"))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -3389,7 +3447,8 @@ while (*s != 0)
       re = regex_must_compile(US"^prvs\\=(.+)\\/([0-9])([0-9]{3})([A-F0-9]{6})\\@(.+)$",
                               TRUE,FALSE);
 
       re = regex_must_compile(US"^prvs\\=(.+)\\/([0-9])([0-9]{3})([A-F0-9]{6})\\@(.+)$",
                               TRUE,FALSE);
 
-      if (regex_match_and_setup(re,sub_arg[0],0,-1)) {
+      if (regex_match_and_setup(re,sub_arg[0],0,-1))
+        {
         uschar *local_part = string_copyn(expand_nstring[1],expand_nlength[1]);
         uschar *key_num = string_copyn(expand_nstring[2],expand_nlength[2]);
         uschar *daystamp = string_copyn(expand_nstring[3],expand_nlength[3]);
         uschar *local_part = string_copyn(expand_nstring[1],expand_nlength[1]);
         uschar *key_num = string_copyn(expand_nstring[2],expand_nlength[2]);
         uschar *daystamp = string_copyn(expand_nstring[3],expand_nlength[3]);
@@ -3409,21 +3468,19 @@ while (*s != 0)
         prvscheck_address[myptr] = '\0';
         prvscheck_keynum = string_copy(key_num);
 
         prvscheck_address[myptr] = '\0';
         prvscheck_keynum = string_copy(key_num);
 
-        /* Now re-expand all arguments in the usual manner */
-        switch(read_subs(sub_arg, 3, 3, &s, skipping, TRUE, US"prvs"))
+        /* Now expand the second argument */
+        switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs"))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
           case 3: goto EXPAND_FAILED;
           }
 
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
           case 3: goto EXPAND_FAILED;
           }
 
-        if (*sub_arg[2] == '\0')
-          yield = string_cat(yield,&size,&ptr,prvscheck_address,Ustrlen(prvscheck_address));
-        else
-          yield = string_cat(yield,&size,&ptr,sub_arg[2],Ustrlen(sub_arg[2]));
-
         /* Now we have the key and can check the address. */
         /* Now we have the key and can check the address. */
-        p = prvs_hmac_sha1(prvscheck_address, sub_arg[1], prvscheck_keynum, daystamp);
+
+        p = prvs_hmac_sha1(prvscheck_address, sub_arg[0], prvscheck_keynum,
+          daystamp);
+
         if (p == NULL)
           {
           expand_string_message = US"hmac-sha1 conversion failed";
         if (p == NULL)
           {
           expand_string_message = US"hmac-sha1 conversion failed";
@@ -3432,14 +3489,15 @@ while (*s != 0)
 
         DEBUG(D_expand) debug_printf("prvscheck: received hash is %s\n", hash);
         DEBUG(D_expand) debug_printf("prvscheck:      own hash is %s\n", p);
 
         DEBUG(D_expand) debug_printf("prvscheck: received hash is %s\n", hash);
         DEBUG(D_expand) debug_printf("prvscheck:      own hash is %s\n", p);
+
         if (Ustrcmp(p,hash) == 0)
           {
           /* Success, valid BATV address. Now check the expiry date. */
           uschar *now = prvs_daystamp(0);
           unsigned int inow = 0,iexpire = 1;
 
         if (Ustrcmp(p,hash) == 0)
           {
           /* Success, valid BATV address. Now check the expiry date. */
           uschar *now = prvs_daystamp(0);
           unsigned int inow = 0,iexpire = 1;
 
-          sscanf(CS now,"%u",&inow);
-          sscanf(CS daystamp,"%u",&iexpire);
+          (void)sscanf(CS now,"%u",&inow);
+          (void)sscanf(CS daystamp,"%u",&iexpire);
 
           /* When "iexpire" is < 7, a "flip" has occured.
              Adjust "inow" accordingly. */
 
           /* When "iexpire" is < 7, a "flip" has occured.
              Adjust "inow" accordingly. */
@@ -3461,12 +3519,35 @@ while (*s != 0)
           prvscheck_result = NULL;
           DEBUG(D_expand) debug_printf("prvscheck: hash failure, $pvrs_result unset\n");
           }
           prvscheck_result = NULL;
           DEBUG(D_expand) debug_printf("prvscheck: hash failure, $pvrs_result unset\n");
           }
-      }
+
+        /* Now expand the final argument. We leave this till now so that
+        it can include $prvscheck_result. */
+
+        switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, US"prvs"))
+          {
+          case 1: goto EXPAND_FAILED_CURLY;
+          case 2:
+          case 3: goto EXPAND_FAILED;
+          }
+
+        if (sub_arg[0] == NULL || *sub_arg[0] == '\0')
+          yield = string_cat(yield,&size,&ptr,prvscheck_address,Ustrlen(prvscheck_address));
+        else
+          yield = string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0]));
+
+        /* Reset the "internal" variables afterwards, because they are in
+        dynamic store that will be reclaimed if the expansion succeeded. */
+
+        prvscheck_address = NULL;
+        prvscheck_keynum = NULL;
+        }
       else
         {
         /* Does not look like a prvs encoded address, return the empty string.
       else
         {
         /* Does not look like a prvs encoded address, return the empty string.
-           We need to make sure all subs are expanded first. */
-        switch(read_subs(sub_arg, 3, 3, &s, skipping, TRUE, US"prvs"))
+           We need to make sure all subs are expanded first, so as to skip over
+           the entire item. */
+
+        switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"prvs"))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -3511,7 +3592,7 @@ while (*s != 0)
         }
 
       yield = cat_file(f, yield, &size, &ptr, sub_arg[1]);
         }
 
       yield = cat_file(f, yield, &size, &ptr, sub_arg[1]);
-      fclose(f);
+      (void)fclose(f);
       continue;
       }
 
       continue;
       }
 
@@ -3604,7 +3685,7 @@ while (*s != 0)
         alarm(timeout);
         yield = cat_file(f, yield, &size, &ptr, sub_arg[3]);
         alarm(0);
         alarm(timeout);
         yield = cat_file(f, yield, &size, &ptr, sub_arg[3]);
         alarm(0);
-        fclose(f);
+        (void)fclose(f);
 
         /* After a timeout, we restore the pointer in the result, that is,
         make sure we add nothing from the socket. */
 
         /* After a timeout, we restore the pointer in the result, that is,
         make sure we add nothing from the socket. */
@@ -3701,7 +3782,7 @@ while (*s != 0)
 
         /* Nothing is written to the standard input. */
 
 
         /* Nothing is written to the standard input. */
 
-        close(fd_in);
+        (void)close(fd_in);
 
         /* Wait for the process to finish, applying the timeout, and inspect its
         return code for serious disasters. Simple non-zero returns are passed on.
 
         /* Wait for the process to finish, applying the timeout, and inspect its
         return code for serious disasters. Simple non-zero returns are passed on.
@@ -3732,7 +3813,7 @@ while (*s != 0)
         f = fdopen(fd_out, "rb");
         lookup_value = NULL;
         lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
         f = fdopen(fd_out, "rb");
         lookup_value = NULL;
         lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
-        fclose(f);
+        (void)fclose(f);
         }
 
       /* Process the yes/no strings; $value may be useful in both cases */
         }
 
       /* Process the yes/no strings; $value may be useful in both cases */
@@ -4331,6 +4412,8 @@ while (*s != 0)
         continue;
         }
 
         continue;
         }
 
+      /* Note that for Darwin and Cygwin, BASE_62 actually has the value 36 */
+
       case EOP_BASE62D:
         {
         uschar buf[16];
       case EOP_BASE62D:
         {
         uschar buf[16];
@@ -4342,10 +4425,11 @@ while (*s != 0)
           if (t == NULL)
             {
             expand_string_message = string_sprintf("argument for base62d "
           if (t == NULL)
             {
             expand_string_message = string_sprintf("argument for base62d "
-              "operator is \"%s\", which is not a base 62 number", sub);
+              "operator is \"%s\", which is not a base %d number", sub,
+              BASE_62);
             goto EXPAND_FAILED;
             }
             goto EXPAND_FAILED;
             }
-          n = n * 62 + (t - base62_chars);
+          n = n * BASE_62 + (t - base62_chars);
           }
         (void)sprintf(CS buf, "%ld", n);
         yield = string_cat(yield, &size, &ptr, buf, Ustrlen(buf));
           }
         (void)sprintf(CS buf, "%ld", n);
         yield = string_cat(yield, &size, &ptr, buf, Ustrlen(buf));
@@ -4825,6 +4909,12 @@ while (*s != 0)
         mode_t mode;
         struct stat st;
 
         mode_t mode;
         struct stat st;
 
+        if ((expand_forbid & RDO_EXISTS) != 0)
+          {
+          expand_string_message = US"Use of the stat() expansion is not permitted";
+          goto EXPAND_FAILED;
+          }
+
         if (stat(CS sub, &st) < 0)
           {
           expand_string_message = string_sprintf("stat(%s) failed: %s",
         if (stat(CS sub, &st) < 0)
           {
           expand_string_message = string_sprintf("stat(%s) failed: %s",