Memory management: when running under the testsuite, check every string variable...
[exim.git] / src / src / expand.c
index a0b36f7e2268fd0d5741fa9e2dc87dc355227f67..d35bf99018dee942bcefc1df263d72d100b750bf 100644 (file)
@@ -46,7 +46,7 @@ the first 8 characters of the password using a 20-round version of crypt
 (standard crypt does 25 rounds).  It then crypts the next 8 characters,
 or an empty block if the password is less than 9 characters, using a
 20-round version of crypt and the same salt as was used for the first
-block.  Charaters after the first 16 are ignored.  It always generates
+block.  Characters after the first 16 are ignored.  It always generates
 a 16-byte hash, which is expressed together with the salt as a string
 of 24 base 64 digits.  Here are some links to peruse:
 
@@ -199,12 +199,15 @@ enum {
 static uschar *op_table_main[] = {
   US"address",
   US"addresses",
+  US"base32",
+  US"base32d",
   US"base62",
   US"base62d",
   US"base64",
   US"base64d",
   US"domain",
   US"escape",
+  US"escape8bit",
   US"eval",
   US"eval10",
   US"expand",
@@ -242,12 +245,15 @@ static uschar *op_table_main[] = {
 enum {
   EOP_ADDRESS =  nelem(op_table_underscore),
   EOP_ADDRESSES,
+  EOP_BASE32,
+  EOP_BASE32D,
   EOP_BASE62,
   EOP_BASE62D,
   EOP_BASE64,
   EOP_BASE64D,
   EOP_DOMAIN,
   EOP_ESCAPE,
+  EOP_ESCAPE8BIT,
   EOP_EVAL,
   EOP_EVAL10,
   EOP_EXPAND,
@@ -838,6 +844,9 @@ static int utf8_table2[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01};
     }
 
 
+
+static uschar * base32_chars = US"abcdefghijklmnopqrstuvwxyz234567";
+
 /*************************************************
 *           Binary chop search on a table        *
 *************************************************/
@@ -1580,7 +1589,7 @@ for (i = 0; i < 2; i++)
 
         size += ilen + comma + 1;  /* +1 for the newline */
 
-        /* Second pass - concatentate the data, up to a maximum. Note that
+        /* Second pass - concatenate the data, up to a maximum. Note that
         the loop stops when size hits the limit. */
 
         if (i != 0)
@@ -1957,6 +1966,7 @@ return;          /* Unknown variable name, fail silently */
 
 
 
+
 /*************************************************
 *           Read and expand substrings           *
 *************************************************/
@@ -3041,6 +3051,8 @@ switch(cond_type)
        "value \"%s\"", t);
       return NULL;
       }
+    DEBUG(D_expand) debug_printf("%s: condition evaluated to %s\n", ourname,
+        boolvalue? "true":"false");
     if (yield != NULL) *yield = (boolvalue == testfor);
     return s;
     }
@@ -3182,16 +3194,17 @@ items. */
 while (isspace(*s)) s++;
 if (*s == '}')
   {
-  if (type[0] == 'i')
-    {
-    if (yes) *yieldptr = string_catn(*yieldptr, sizeptr, ptrptr, US"true", 4);
-    }
-  else
-    {
-    if (yes && lookup_value)
-      *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value);
-    lookup_value = save_lookup;
-    }
+  if (!skipping)
+    if (type[0] == 'i')
+      {
+      if (yes) *yieldptr = string_catn(*yieldptr, sizeptr, ptrptr, US"true", 4);
+      }
+    else
+      {
+      if (yes && lookup_value)
+       *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value);
+      lookup_value = save_lookup;
+      }
   s++;
   goto RETURN;
   }
@@ -3488,7 +3501,6 @@ Returns:       new value of string pointer
 static uschar *
 cat_file(FILE *f, uschar *yield, int *sizep, int *ptrp, uschar *eol)
 {
-int eollen = eol ? Ustrlen(eol) : 0;
 uschar buffer[1024];
 
 while (Ufgets(buffer, sizeof(buffer), f))
@@ -3496,8 +3508,8 @@ while (Ufgets(buffer, sizeof(buffer), f))
   int len = Ustrlen(buffer);
   if (eol && buffer[len-1] == '\n') len--;
   yield = string_catn(yield, sizep, ptrp, buffer, len);
-  if (buffer[len] != 0)
-    yield = string_catn(yield, sizep, ptrp, eol, eollen);
+  if (eol && buffer[len])
+    yield = string_cat(yield, sizep, ptrp, eol);
   }
 
 if (yield) yield[*ptrp] = 0;
@@ -3624,7 +3636,7 @@ if (*error == NULL)
     /* SIGFPE both on div/mod by zero and on INT_MIN / -1, which would give
      * a value of INT_MAX+1. Note that INT_MIN * -1 gives INT_MIN for me, which
      * is a bug somewhere in [gcc 4.2.1, FreeBSD, amd64].  In fact, -N*-M where
-     * -N*M is INT_MIN will yielf INT_MIN.
+     * -N*M is INT_MIN will yield INT_MIN.
      * Since we don't support floating point, this is somewhat simpler.
      * Ideally, we'd return an error, but since we overflow for all other
      * arithmetic, consistency suggests otherwise, but what's the correct value
@@ -3802,7 +3814,7 @@ them here in detail any more.
 We use an internal routine recursively to handle embedded substrings. The
 external function follows. The yield is NULL if the expansion failed, and there
 are two cases: if something collapsed syntactically, or if "fail" was given
-as the action on a lookup failure. These can be distinguised by looking at the
+as the action on a lookup failure. These can be distinguished by looking at the
 variable expand_string_forcedfail, which is TRUE in the latter case.
 
 The skipping flag is set true when expanding a substring that isn't actually
@@ -4101,12 +4113,13 @@ while (*s != 0)
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       while (isspace(*s)) s++;
-      next_s = eval_condition(s, &resetok, skipping? NULL : &cond);
+      next_s = eval_condition(s, &resetok, skipping ? NULL : &cond);
       if (next_s == NULL) goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
-        debug_printf("condition: %.*s\n   result: %s\n", (int)(next_s - s), s,
-          cond? "true" : "false");
+        debug_printf("  condition: %.*s\n     result: %s\n",
+         (int)(next_s - s), s,
+          cond ? "true" : "false");
 
       s = next_s;
 
@@ -4164,11 +4177,13 @@ while (*s != 0)
        goto EXPAND_FAILED;
        }
 
-      if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
-                         sub_arg[1][0], sub_arg[2], &expand_string_message)))
-       goto EXPAND_FAILED;
       if (!skipping)
+       {
+       if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
+                           sub_arg[1][0], sub_arg[2], &expand_string_message)))
+         goto EXPAND_FAILED;
        yield = string_cat(yield, &size, &ptr, encoded);
+       }
       continue;
       }
 #endif
@@ -4646,7 +4661,6 @@ while (*s != 0)
         prvscheck_keynum = NULL;
         }
       else
-        {
         /* Does not look like a prvs encoded address, return the empty string.
            We need to make sure all subs are expanded first, so as to skip over
            the entire item. */
@@ -4657,7 +4671,6 @@ while (*s != 0)
           case 2:
           case 3: goto EXPAND_FAILED;
           }
-        }
 
       continue;
       }
@@ -4952,7 +4965,10 @@ while (*s != 0)
        }
 
       if (skipping)   /* Just pretend it worked when we're skipping */
+       {
         runrc = 0;
+       lookup_value = NULL;
+       }
       else
         {
         if (!transport_set_up_command(&argv,    /* anchor for arg list */
@@ -4994,9 +5010,9 @@ while (*s != 0)
         return code for serious disasters. Simple non-zero returns are passed on.
         */
 
-        if (sigalrm_seen == TRUE || (runrc = child_close(pid, 30)) < 0)
+        if (sigalrm_seen || (runrc = child_close(pid, 30)) < 0)
           {
-          if (sigalrm_seen == TRUE || runrc == -256)
+          if (sigalrm_seen || runrc == -256)
             {
             expand_string_message = string_sprintf("command timed out");
             killpg(pid, SIGKILL);       /* Kill the whole process group */
@@ -5166,79 +5182,81 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
-      if (Ustrcmp(sub[0], "md5") == 0)
-        {
-        type = HMAC_MD5;
-        use_base = &md5_base;
-        hashlen = 16;
-        hashblocklen = 64;
-        }
-      else if (Ustrcmp(sub[0], "sha1") == 0)
-        {
-        type = HMAC_SHA1;
-        use_base = &sha1_ctx;
-        hashlen = 20;
-        hashblocklen = 64;
-        }
-      else
-        {
-        expand_string_message =
-          string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]);
-        goto EXPAND_FAILED;
-        }
+      if (!skipping)
+       {
+       if (Ustrcmp(sub[0], "md5") == 0)
+         {
+         type = HMAC_MD5;
+         use_base = &md5_base;
+         hashlen = 16;
+         hashblocklen = 64;
+         }
+       else if (Ustrcmp(sub[0], "sha1") == 0)
+         {
+         type = HMAC_SHA1;
+         use_base = &sha1_ctx;
+         hashlen = 20;
+         hashblocklen = 64;
+         }
+       else
+         {
+         expand_string_message =
+           string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]);
+         goto EXPAND_FAILED;
+         }
 
-      keyptr = sub[1];
-      keylen = Ustrlen(keyptr);
+       keyptr = sub[1];
+       keylen = Ustrlen(keyptr);
 
-      /* If the key is longer than the hash block length, then hash the key
-      first */
+       /* If the key is longer than the hash block length, then hash the key
+       first */
 
-      if (keylen > hashblocklen)
-        {
-        chash_start(type, use_base);
-        chash_end(type, use_base, keyptr, keylen, keyhash);
-        keyptr = keyhash;
-        keylen = hashlen;
-        }
+       if (keylen > hashblocklen)
+         {
+         chash_start(type, use_base);
+         chash_end(type, use_base, keyptr, keylen, keyhash);
+         keyptr = keyhash;
+         keylen = hashlen;
+         }
 
-      /* Now make the inner and outer key values */
+       /* Now make the inner and outer key values */
 
-      memset(innerkey, 0x36, hashblocklen);
-      memset(outerkey, 0x5c, hashblocklen);
+       memset(innerkey, 0x36, hashblocklen);
+       memset(outerkey, 0x5c, hashblocklen);
 
-      for (i = 0; i < keylen; i++)
-        {
-        innerkey[i] ^= keyptr[i];
-        outerkey[i] ^= keyptr[i];
-        }
+       for (i = 0; i < keylen; i++)
+         {
+         innerkey[i] ^= keyptr[i];
+         outerkey[i] ^= keyptr[i];
+         }
 
-      /* Now do the hashes */
+       /* Now do the hashes */
 
-      chash_start(type, use_base);
-      chash_mid(type, use_base, innerkey);
-      chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash);
+       chash_start(type, use_base);
+       chash_mid(type, use_base, innerkey);
+       chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash);
 
-      chash_start(type, use_base);
-      chash_mid(type, use_base, outerkey);
-      chash_end(type, use_base, innerhash, hashlen, finalhash);
+       chash_start(type, use_base);
+       chash_mid(type, use_base, outerkey);
+       chash_end(type, use_base, innerhash, hashlen, finalhash);
 
-      /* Encode the final hash as a hex string */
+       /* Encode the final hash as a hex string */
 
-      p = finalhash_hex;
-      for (i = 0; i < hashlen; i++)
-        {
-        *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
-        *p++ = hex_digits[finalhash[i] & 0x0f];
-        }
+       p = finalhash_hex;
+       for (i = 0; i < hashlen; i++)
+         {
+         *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
+         *p++ = hex_digits[finalhash[i] & 0x0f];
+         }
 
-      DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%.*s)=%.*s\n", sub[0],
-        (int)keylen, keyptr, Ustrlen(sub[2]), sub[2], hashlen*2, finalhash_hex);
+       DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
+         sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex);
 
-      yield = string_catn(yield, &size, &ptr, finalhash_hex, hashlen*2);
+       yield = string_catn(yield, &size, &ptr, finalhash_hex, hashlen*2);
+       }
+      continue;
       }
 
-    continue;
-
     /* Handle global substitution for "sg" - like Perl's s/xxx/yyy/g operator.
     We have to save the numerical variables and restore them afterwards. */
 
@@ -5369,7 +5387,7 @@ while (*s != 0)
 
       /* While skipping we cannot rely on the data for expansions being
       available (eg. $item) hence cannot decide on numeric vs. keyed.
-      Read a maximum of 5 arguments (inclding the yes/no) */
+      Read a maximum of 5 arguments (including the yes/no) */
 
       if (skipping)
        {
@@ -6256,6 +6274,47 @@ while (*s != 0)
 
     switch(c)
       {
+      case EOP_BASE32:
+       {
+        uschar *t;
+        unsigned long int n = Ustrtoul(sub, &t, 10);
+       uschar * s = NULL;
+       int sz = 0, i = 0;
+
+        if (*t != 0)
+          {
+          expand_string_message = string_sprintf("argument for base32 "
+            "operator is \"%s\", which is not a decimal number", sub);
+          goto EXPAND_FAILED;
+          }
+       for ( ; n; n >>= 5)
+         s = string_catn(s, &sz, &i, &base32_chars[n & 0x1f], 1);
+
+       while (i > 0) yield = string_catn(yield, &size, &ptr, &s[--i], 1);
+       continue;
+       }
+
+      case EOP_BASE32D:
+        {
+        uschar *tt = sub;
+        unsigned long int n = 0;
+       uschar * s;
+        while (*tt)
+          {
+          uschar * t = Ustrchr(base32_chars, *tt++);
+          if (t == NULL)
+            {
+            expand_string_message = string_sprintf("argument for base32d "
+              "operator is \"%s\", which is not a base 32 number", sub);
+            goto EXPAND_FAILED;
+            }
+          n = n * 32 + (t - base32_chars);
+          }
+        s = string_sprintf("%ld", n);
+        yield = string_cat(yield, &size, &ptr, s);
+        continue;
+        }
+
       case EOP_BASE62:
         {
         uschar *t;
@@ -6382,7 +6441,11 @@ while (*s != 0)
          blob b;
          char st[3];
 
-         exim_sha_init(&h, HASH_SHA256);
+         if (!exim_sha_init(&h, HASH_SHA256))
+           {
+           expand_string_message = US"unrecognised sha256 variant";
+           goto EXPAND_FAILED;
+           }
          exim_sha_update(&h, sub, Ustrlen(sub));
          exim_sha_finish(&h, &b);
          while (b.len-- > 0)
@@ -6409,13 +6472,12 @@ while (*s != 0)
          : Ustrcmp(arg, "512") == 0 ? HASH_SHA3_512
          : HASH_BADTYPE;
 
-       if (m == HASH_BADTYPE)
+       if (m == HASH_BADTYPE || !exim_sha_init(&h, m))
          {
          expand_string_message = US"unrecognised sha3 variant";
          goto EXPAND_FAILED;
          }
 
-       exim_sha_init(&h, m);
        exim_sha_update(&h, sub, Ustrlen(sub));
        exim_sha_finish(&h, &b);
        while (b.len-- > 0)
@@ -7055,11 +7117,23 @@ while (*s != 0)
 
       case EOP_ESCAPE:
         {
-        const uschar *t = string_printing(sub);
+        const uschar * t = string_printing(sub);
         yield = string_cat(yield, &size, &ptr, t);
         continue;
         }
 
+      case EOP_ESCAPE8BIT:
+       {
+       const uschar * s = sub;
+       uschar c;
+
+       for (s = sub; (c = *s); s++)
+         yield = c < 127 && c != '\\'
+           ? string_catn(yield, &size, &ptr, s, 1)
+           : string_catn(yield, &size, &ptr, string_sprintf("\\%03o", c), 4);
+       continue;
+       }
+
       /* Handle numeric expression evaluation */
 
       case EOP_EVAL:
@@ -7357,8 +7431,7 @@ while (*s != 0)
       yield = NULL;
       size = 0;
       }
-    value = find_variable(name, FALSE, skipping, &newsize);
-    if (value == NULL)
+    if (!(value = find_variable(name, FALSE, skipping, &newsize)))
       {
       expand_string_message =
         string_sprintf("unknown variable in \"${%s}\"", name);
@@ -7366,13 +7439,14 @@ while (*s != 0)
       goto EXPAND_FAILED;
       }
     len = Ustrlen(value);
-    if (yield == NULL && newsize != 0)
+    if (!yield && newsize)
       {
       yield = value;
       size = newsize;
       ptr = len;
       }
-    else yield = string_catn(yield, &size, &ptr, value, len);
+    else
+      yield = string_catn(yield, &size, &ptr, value, len);
     continue;
     }
 
@@ -7703,6 +7777,55 @@ return (  (  Ustrstr(s, "failed to expand") != NULL
 
 
 
+/*************************************************
+* Error-checking for testsuite                   *
+*************************************************/
+typedef struct {
+  const char * filename;
+  int          linenumber;
+  uschar *     region_start;
+  uschar *     region_end;
+  const uschar *var_name;
+} err_ctx;
+
+static void
+assert_variable_notin(uschar * var_name, uschar * var_data, void * ctx)
+{
+err_ctx * e = ctx;
+if (var_data >= e->region_start  &&  var_data < e->region_end)
+  e->var_name = CUS var_name;
+}
+
+void
+assert_no_variables(void * ptr, int len, const char * filename, int linenumber)
+{
+err_ctx e = {filename, linenumber, ptr, US ptr + len, NULL };
+int i;
+var_entry * v;
+
+/* check acl_ variables */
+tree_walk(acl_var_c, assert_variable_notin, &e);
+tree_walk(acl_var_m, assert_variable_notin, &e);
+
+/* check auth<n> variables */
+for (i = 0; i < AUTH_VARS; i++) if (auth_vars[i])
+  assert_variable_notin(US"auth<n>", auth_vars[i], &e);
+
+/* check regex<n> variables */
+for (i = 0; i < REGEX_VARS; i++) if (regex_vars[i])
+  assert_variable_notin(US"regex<n>", regex_vars[i], &e);
+
+/* check known-name variables */
+for (v = var_table; v < var_table + var_table_size; v++)
+  if (v->type == vtype_stringptr)
+    assert_variable_notin(US v->name, *(USS v->value), &e);
+
+if (e.var_name)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "live variable '%s' destroyed by reset_store"
+    " at %s:%d\n", e.var_name, e.filename, e.linenumber);
+}
+
+
 
 /*************************************************
 **************************************************