String expansions: fix ${extract }, for the numeric/3-string case. Bug 1807
[exim.git] / src / src / expand.c
index 55b3d8c293c9c1ec5008890c3a9640855cf81767..47f1453bebc2cf89cf906e8d1d75c698f1cfeffd 100644 (file)
@@ -201,6 +201,8 @@ static uschar *op_table_main[] = {
   US"addresses",
   US"base62",
   US"base62d",
+  US"base64",
+  US"base64d",
   US"domain",
   US"escape",
   US"eval",
@@ -241,6 +243,8 @@ enum {
   EOP_ADDRESSES,
   EOP_BASE62,
   EOP_BASE62D,
+  EOP_BASE64,
+  EOP_BASE64D,
   EOP_DOMAIN,
   EOP_ESCAPE,
   EOP_EVAL,
@@ -516,7 +520,7 @@ static var_entry var_table[] = {
   { "dnslist_value",       vtype_stringptr,   &dnslist_value },
   { "domain",              vtype_stringptr,   &deliver_domain },
   { "domain_data",         vtype_stringptr,   &deliver_domain_data },
-#ifdef EXPERIMENTAL_EVENT
+#ifndef DISABLE_EVENT
   { "event_data",          vtype_stringptr,   &event_data },
 
   /*XXX want to use generic vars for as many of these as possible*/
@@ -539,6 +543,7 @@ static var_entry var_table[] = {
   { "host_lookup_deferred",vtype_int,         &host_lookup_deferred },
   { "host_lookup_failed",  vtype_int,         &host_lookup_failed },
   { "host_port",           vtype_int,         &deliver_host_port },
+  { "initial_cwd",         vtype_stringptr,   &initial_cwd },
   { "inode",               vtype_ino,         &deliver_inode },
   { "interface_address",   vtype_stringptr,   &interface_address },
   { "interface_port",      vtype_int,         &interface_port },
@@ -1069,6 +1074,8 @@ return s;
 
 Returns:  a pointer to the character after the last digit
 */
+/*XXX consider expanding to int_eximarith_t.  But the test for
+"overbig numbers" in 0002 still needs to overflow it. */
 
 static uschar *
 read_number(int *n, uschar *s)
@@ -1725,7 +1732,7 @@ if ((Ustrncmp(name, "acl_c", 5) == 0 || Ustrncmp(name, "acl_m", 5) == 0) &&
   {
   tree_node *node =
     tree_search((name[4] == 'c')? acl_var_c : acl_var_m, name + 4);
-  return (node == NULL)? (strict_acl_vars? NULL : US"") : node->data.ptr;
+  return node ? node->data.ptr : strict_acl_vars ? NULL : US"";
   }
 
 /* Handle $auth<n> variables. */
@@ -1760,153 +1767,150 @@ val = vp->value;
 switch (vp->type)
   {
   case vtype_filter_int:
-  if (!filter_running) return NULL;
-  /* Fall through */
-  /* VVVVVVVVVVVV */
+    if (!filter_running) return NULL;
+    /* Fall through */
+    /* VVVVVVVVVVVV */
   case vtype_int:
-  sprintf(CS var_buffer, "%d", *(int *)(val)); /* Integer */
-  return var_buffer;
+    sprintf(CS var_buffer, "%d", *(int *)(val)); /* Integer */
+    return var_buffer;
 
   case vtype_ino:
-  sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(val))); /* Inode */
-  return var_buffer;
+    sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(val))); /* Inode */
+    return var_buffer;
 
   case vtype_gid:
-  sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(val))); /* gid */
-  return var_buffer;
+    sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(val))); /* gid */
+    return var_buffer;
 
   case vtype_uid:
-  sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(val))); /* uid */
-  return var_buffer;
+    sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(val))); /* uid */
+    return var_buffer;
 
   case vtype_bool:
-  sprintf(CS var_buffer, "%s", *(BOOL *)(val) ? "yes" : "no"); /* bool */
-  return var_buffer;
+    sprintf(CS var_buffer, "%s", *(BOOL *)(val) ? "yes" : "no"); /* bool */
+    return var_buffer;
 
   case vtype_stringptr:                      /* Pointer to string */
-  s = *((uschar **)(val));
-  return (s == NULL)? US"" : s;
+    return (s = *((uschar **)(val))) ? s : US"";
 
   case vtype_pid:
-  sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */
-  return var_buffer;
+    sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */
+    return var_buffer;
 
   case vtype_load_avg:
-  sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */
-  return var_buffer;
+    sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */
+    return var_buffer;
 
   case vtype_host_lookup:                    /* Lookup if not done so */
-  if (sender_host_name == NULL && sender_host_address != NULL &&
-      !host_lookup_failed && host_name_lookup() == OK)
-    host_build_sender_fullhost();
-  return (sender_host_name == NULL)? US"" : sender_host_name;
+    if (sender_host_name == NULL && sender_host_address != NULL &&
+       !host_lookup_failed && host_name_lookup() == OK)
+      host_build_sender_fullhost();
+    return (sender_host_name == NULL)? US"" : sender_host_name;
 
   case vtype_localpart:                      /* Get local part from address */
-  s = *((uschar **)(val));
-  if (s == NULL) return US"";
-  domain = Ustrrchr(s, '@');
-  if (domain == NULL) return s;
-  if (domain - s > sizeof(var_buffer) - 1)
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT
-       " in string expansion", sizeof(var_buffer));
-  Ustrncpy(var_buffer, s, domain - s);
-  var_buffer[domain - s] = 0;
-  return var_buffer;
+    s = *((uschar **)(val));
+    if (s == NULL) return US"";
+    domain = Ustrrchr(s, '@');
+    if (domain == NULL) return s;
+    if (domain - s > sizeof(var_buffer) - 1)
+      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT
+         " in string expansion", sizeof(var_buffer));
+    Ustrncpy(var_buffer, s, domain - s);
+    var_buffer[domain - s] = 0;
+    return var_buffer;
 
   case vtype_domain:                         /* Get domain from address */
-  s = *((uschar **)(val));
-  if (s == NULL) return US"";
-  domain = Ustrrchr(s, '@');
-  return (domain == NULL)? US"" : domain + 1;
+    s = *((uschar **)(val));
+    if (s == NULL) return US"";
+    domain = Ustrrchr(s, '@');
+    return (domain == NULL)? US"" : domain + 1;
 
   case vtype_msgheaders:
-  return find_header(NULL, exists_only, newsize, FALSE, NULL);
+    return find_header(NULL, exists_only, newsize, FALSE, NULL);
 
   case vtype_msgheaders_raw:
-  return find_header(NULL, exists_only, newsize, TRUE, NULL);
+    return find_header(NULL, exists_only, newsize, TRUE, NULL);
 
   case vtype_msgbody:                        /* Pointer to msgbody string */
   case vtype_msgbody_end:                    /* Ditto, the end of the msg */
-  ss = (uschar **)(val);
-  if (*ss == NULL && deliver_datafile >= 0)  /* Read body when needed */
-    {
-    uschar *body;
-    off_t start_offset = SPOOL_DATA_START_OFFSET;
-    int len = message_body_visible;
-    if (len > message_size) len = message_size;
-    *ss = body = store_malloc(len+1);
-    body[0] = 0;
-    if (vp->type == vtype_msgbody_end)
+    ss = (uschar **)(val);
+    if (*ss == NULL && deliver_datafile >= 0)  /* Read body when needed */
       {
-      struct stat statbuf;
-      if (fstat(deliver_datafile, &statbuf) == 0)
+      uschar *body;
+      off_t start_offset = SPOOL_DATA_START_OFFSET;
+      int len = message_body_visible;
+      if (len > message_size) len = message_size;
+      *ss = body = store_malloc(len+1);
+      body[0] = 0;
+      if (vp->type == vtype_msgbody_end)
        {
-       start_offset = statbuf.st_size - len;
-       if (start_offset < SPOOL_DATA_START_OFFSET)
-         start_offset = SPOOL_DATA_START_OFFSET;
-       }
-      }
-    lseek(deliver_datafile, start_offset, SEEK_SET);
-    len = read(deliver_datafile, body, len);
-    if (len > 0)
-      {
-      body[len] = 0;
-      if (message_body_newlines)   /* Separate loops for efficiency */
-       {
-       while (len > 0)
-         { if (body[--len] == 0) body[len] = ' '; }
+       struct stat statbuf;
+       if (fstat(deliver_datafile, &statbuf) == 0)
+         {
+         start_offset = statbuf.st_size - len;
+         if (start_offset < SPOOL_DATA_START_OFFSET)
+           start_offset = SPOOL_DATA_START_OFFSET;
+         }
        }
-      else
+      if (lseek(deliver_datafile, start_offset, SEEK_SET) < 0)
+       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "deliver_datafile lseek: %s",
+         strerror(errno));
+      len = read(deliver_datafile, body, len);
+      if (len > 0)
        {
-       while (len > 0)
-         { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
+       body[len] = 0;
+       if (message_body_newlines)   /* Separate loops for efficiency */
+         while (len > 0)
+           { if (body[--len] == 0) body[len] = ' '; }
+       else
+         while (len > 0)
+           { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
        }
       }
-    }
-  return (*ss == NULL)? US"" : *ss;
+    return (*ss == NULL)? US"" : *ss;
 
   case vtype_todbsdin:                       /* BSD inbox time of day */
-  return tod_stamp(tod_bsdin);
+    return tod_stamp(tod_bsdin);
 
   case vtype_tode:                           /* Unix epoch time of day */
-  return tod_stamp(tod_epoch);
+    return tod_stamp(tod_epoch);
 
   case vtype_todel:                          /* Unix epoch/usec time of day */
-  return tod_stamp(tod_epoch_l);
+    return tod_stamp(tod_epoch_l);
 
   case vtype_todf:                           /* Full time of day */
-  return tod_stamp(tod_full);
+    return tod_stamp(tod_full);
 
   case vtype_todl:                           /* Log format time of day */
-  return tod_stamp(tod_log_bare);            /* (without timezone) */
+    return tod_stamp(tod_log_bare);            /* (without timezone) */
 
   case vtype_todzone:                        /* Time zone offset only */
-  return tod_stamp(tod_zone);
+    return tod_stamp(tod_zone);
 
   case vtype_todzulu:                        /* Zulu time */
-  return tod_stamp(tod_zulu);
+    return tod_stamp(tod_zulu);
 
   case vtype_todlf:                          /* Log file datestamp tod */
-  return tod_stamp(tod_log_datestamp_daily);
+    return tod_stamp(tod_log_datestamp_daily);
 
   case vtype_reply:                          /* Get reply address */
-  s = find_header(US"reply-to:", exists_only, newsize, TRUE,
-    headers_charset);
-  if (s != NULL) while (isspace(*s)) s++;
-  if (s == NULL || *s == 0)
-    {
-    *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;
+    s = find_header(US"reply-to:", exists_only, newsize, TRUE,
+      headers_charset);
+    if (s != NULL) while (isspace(*s)) s++;
+    if (s == NULL || *s == 0)
+      {
+      *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;
 
   case vtype_string_func:
     {
@@ -1931,12 +1935,12 @@ switch (vp->type)
   return var_buffer;
 
   case vtype_cert:
-  return *(void **)val ? US"<cert>" : US"";
+    return *(void **)val ? US"<cert>" : US"";
 
-  #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
   case vtype_dkim:
-  return dkim_exim_expand_query((int)(long)val);
-  #endif
+    return dkim_exim_expand_query((int)(long)val);
+#endif
 
   }
 
@@ -2692,7 +2696,7 @@ switch(cond_type)
 
       if (sublen == 24)
         {
-        uschar *coded = auth_b64encode((uschar *)digest, 16);
+        uschar *coded = 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);
         tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
@@ -2730,7 +2734,7 @@ switch(cond_type)
 
       if (sublen == 28)
         {
-        uschar *coded = auth_b64encode((uschar *)digest, 20);
+        uschar *coded = 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);
         tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
@@ -2787,7 +2791,7 @@ switch(cond_type)
       #define XSTR(s) STR(s)
       DEBUG(D_auth) debug_printf("crypteq: using %s()\n"
         "  subject=%s\n  crypted=%s\n",
-        (which == 0)? XSTR(DEFAULT_CRYPT) : (which == 1)? "crypt" : "crypt16",
+        which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16",
         coded, sub[1]);
       #undef STR
       #undef XSTR
@@ -2796,8 +2800,16 @@ switch(cond_type)
       salt), force failure. Otherwise we get false positives: with an empty
       string the yield of crypt() is an empty string! */
 
-      tempcond = (Ustrlen(sub[1]) < 2)? FALSE :
-        (Ustrcmp(coded, sub[1]) == 0);
+      if (coded)
+       tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0;
+      else if (errno == EINVAL)
+       tempcond = FALSE;
+      else
+       {
+       expand_string_message = string_sprintf("crypt error: %s\n",
+         US strerror(errno));
+       return NULL;
+       }
       }
     break;
     #endif  /* SUPPORT_CRYPTEQ */
@@ -3143,7 +3155,8 @@ Arguments:
   yieldptr       points to the output string pointer
   sizeptr        points to the output string size
   ptrptr         points to the output string pointer
-  type           "lookup" or "if" or "extract" or "run", for error message
+  type           "lookup", "if", "extract", "run", "env", "listextract" or
+                 "certextract" for error message
   resetok       if not NULL, pointer to flag - write FALSE if unsafe to reset
                the store.
 
@@ -3174,7 +3187,7 @@ if (*s == '}')
     }
   else
     {
-    if (yes && lookup_value != NULL)
+    if (yes && lookup_value)
       *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value,
         Ustrlen(lookup_value));
     lookup_value = save_lookup;
@@ -3201,10 +3214,11 @@ if (*s++ != '}') goto FAILED_CURLY;
 if (yes)
   *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub1, Ustrlen(sub1));
 
-/* If this is called from a lookup or an extract, we want to restore $value to
-what it was at the start of the item, so that it has this value during the
-second string expansion. For the call from "if" or "run" to this function,
-save_lookup is set to lookup_value, so that this statement does nothing. */
+/* If this is called from a lookup/env or a (cert)extract, we want to restore
+$value to what it was at the start of the item, so that it has this value
+during the second string expansion. For the call from "if" or "run" to this
+function, save_lookup is set to lookup_value, so that this statement does
+nothing. */
 
 lookup_value = save_lookup;
 
@@ -3394,8 +3408,8 @@ if (Ustrlen(key) > 64)
   return NULL;
 
 hash_source = string_cat(NULL,&size,&offset,key_num,1);
-string_cat(hash_source,&size,&offset,daystamp,3);
-string_cat(hash_source,&size,&offset,address,Ustrlen(address));
+hash_source = string_cat(hash_source,&size,&offset,daystamp,3);
+hash_source = string_cat(hash_source,&size,&offset,address,Ustrlen(address));
 hash_source[offset] = '\0';
 
 DEBUG(D_expand) debug_printf("prvs: hash source is '%s'\n", hash_source);
@@ -3435,9 +3449,9 @@ return finalhash_hex;
 *        Join a file onto the output string      *
 *************************************************/
 
-/* This is used for readfile and after a run expansion. It joins the contents
-of a file onto the output string, globally replacing newlines with a given
-string (optionally). The file is closed at the end.
+/* This is used for readfile/readsock and after a run expansion.
+It joins the contents of a file onto the output string, globally replacing
+newlines with a given string (optionally).
 
 Arguments:
   f            the FILE
@@ -3452,21 +3466,19 @@ Returns:       new value of string pointer
 static uschar *
 cat_file(FILE *f, uschar *yield, int *sizep, int *ptrp, uschar *eol)
 {
-int eollen;
+int eollen = eol ? Ustrlen(eol) : 0;
 uschar buffer[1024];
 
-eollen = (eol == NULL)? 0 : Ustrlen(eol);
-
-while (Ufgets(buffer, sizeof(buffer), f) != NULL)
+while (Ufgets(buffer, sizeof(buffer), f))
   {
   int len = Ustrlen(buffer);
-  if (eol != NULL && buffer[len-1] == '\n') len--;
+  if (eol && buffer[len-1] == '\n') len--;
   yield = string_cat(yield, sizep, ptrp, buffer, len);
   if (buffer[len] != 0)
     yield = string_cat(yield, sizep, ptrp, eol, eollen);
   }
 
-if (yield != NULL) yield[*ptrp] = 0;
+if (yield) yield[*ptrp] = 0;
 
 return yield;
 }
@@ -3923,16 +3935,12 @@ while (*s != 0)
 
     /* Variable */
 
-    else
+    else if (!(value = find_variable(name, FALSE, skipping, &newsize)))
       {
-      value = find_variable(name, FALSE, skipping, &newsize);
-      if (value == NULL)
-        {
-        expand_string_message =
-          string_sprintf("unknown variable name \"%s\"", name);
-          check_variable_error_message(name);
-        goto EXPAND_FAILED;
-        }
+      expand_string_message =
+       string_sprintf("unknown variable name \"%s\"", name);
+       check_variable_error_message(name);
+      goto EXPAND_FAILED;
       }
 
     /* If the data is known to be in a new buffer, newsize will be set to the
@@ -4866,8 +4874,7 @@ while (*s != 0)
       const uschar **argv;
       pid_t pid;
       int fd_in, fd_out;
-      int lsize = 0;
-      int lptr = 0;
+      int lsize = 0, lptr = 0;
 
       if ((expand_forbid & RDO_RUN) != 0)
         {
@@ -4895,15 +4902,11 @@ while (*s != 0)
             NULL,                               /* no transporting address */
             US"${run} expansion",               /* for error messages */
             &expand_string_message))            /* where to put error message */
-          {
           goto EXPAND_FAILED;
-          }
 
         /* Create the child process, making it a group leader. */
 
-        pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE);
-
-        if (pid < 0)
+        if ((pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE)) < 0)
           {
           expand_string_message =
             string_sprintf("couldn't create child process: %s", strerror(errno));
@@ -4916,12 +4919,14 @@ while (*s != 0)
 
         /* Read the pipe to get the command's output into $value (which is kept
         in lookup_value). Read during execution, so that if the output exceeds
-        the OS pipe buffer limit, we don't block forever. */
+        the OS pipe buffer limit, we don't block forever. Remember to not release
+       memory just allocated for $value. */
 
+       resetok = FALSE;
         f = fdopen(fd_out, "rb");
         sigalrm_seen = FALSE;
         alarm(60);
-        lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
+        lookup_value = cat_file(f, NULL, &lsize, &lptr, NULL);
         alarm(0);
         (void)fclose(f);
 
@@ -5302,9 +5307,25 @@ while (*s != 0)
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
-      /* Read the arguments */
+      /* While skipping we cannot rely on the data for expansions being
+      available (eg. $item) hence cannot decide on numeric vs. keyed.
+      Just read as many arguments as there are. */
 
-      for (i = 0; i < j; i++)
+      if (skipping)
+       {
+        while (isspace(*s)) s++;
+        while (*s == '{')
+         {
+          if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))
+           goto EXPAND_FAILED;                                 /*{*/
+          if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+         while (isspace(*s)) s++;
+         }
+       if (*s != '}')
+         goto EXPAND_FAILED_CURLY;
+       }
+
+      else for (i = 0; i < j; i++) /* Read the proper number of arguments */
         {
         while (isspace(*s)) s++;
         if (*s == '{')                                                 /*}*/
@@ -5331,27 +5352,24 @@ while (*s != 0)
             while (len > 0 && isspace(p[len-1])) len--;
             p[len] = 0;
 
-            if (!skipping)
+           if (*p == 0)
              {
-             if (*p == 0)
-               {
-               expand_string_message = US"first argument of \"extract\" must "
-                 "not be empty";
-               goto EXPAND_FAILED;
-               }
+             expand_string_message = US"first argument of \"extract\" must "
+               "not be empty";
+             goto EXPAND_FAILED;
+             }
 
-             if (*p == '-')
-               {
-               field_number = -1;
-               p++;
-               }
-             while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
-             if (*p == 0)
-               {
-               field_number *= x;
-               j = 3;               /* Need 3 args */
-               field_number_set = TRUE;
-               }
+           if (*p == '-')
+             {
+             field_number = -1;
+             p++;
+             }
+           while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
+           if (*p == 0)
+             {
+             field_number *= x;
+             j = 3;               /* Need 3 args */
+             field_number_set = TRUE;
              }
             }
           }
@@ -5469,7 +5487,7 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"extract",                  /* condition type */
+               US"listextract",              /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -5541,7 +5559,7 @@ while (*s != 0)
                &yield,                       /* output pointer */
                &size,                        /* output size */
                &ptr,                         /* output current point */
-               US"extract",                  /* condition type */
+               US"certextract",              /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -6039,6 +6057,7 @@ while (*s != 0)
       case EOP_MD5:
       case EOP_SHA1:
       case EOP_SHA256:
+      case EOP_BASE64:
        if (s[1] == '$')
          {
          const uschar * s1 = s;
@@ -6242,7 +6261,7 @@ while (*s != 0)
             }
           }
 
-        enc = auth_b64encode(sub, out - sub);
+        enc = b64encode(sub, out - sub);
         yield = string_cat(yield, &size, &ptr, enc, Ustrlen(enc));
         continue;
         }
@@ -6883,9 +6902,30 @@ while (*s != 0)
       /* Convert string to base64 encoding */
 
       case EOP_STR2B64:
+      case EOP_BASE64:
+       {
+#ifdef SUPPORT_TLS
+       uschar * s = vp && *(void **)vp->value
+         ? tls_cert_der_b64(*(void **)vp->value)
+         : b64encode(sub, Ustrlen(sub));
+#else
+       uschar * s = b64encode(sub, Ustrlen(sub));
+#endif
+       yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+       continue;
+       }
+
+      case EOP_BASE64D:
         {
-        uschar *encstr = auth_b64encode(sub, Ustrlen(sub));
-        yield = string_cat(yield, &size, &ptr, encstr, Ustrlen(encstr));
+        uschar * s;
+        int len = b64decode(sub, &s);
+       if (len < 0)
+          {
+          expand_string_message = string_sprintf("string \"%s\" is not "
+            "well-formed for \"%s\" operator", sub, name);
+          goto EXPAND_FAILED;
+          }
+        yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
         continue;
         }
 
@@ -7482,22 +7522,22 @@ for (i = 1; i < argc; i++)
     if (Ustrspn(argv[i], "abcdefghijklmnopqrtsuvwxyz0123456789-.:/") ==
         Ustrlen(argv[i]))
       {
-      #ifdef LOOKUP_LDAP
+#ifdef LOOKUP_LDAP
       eldap_default_servers = argv[i];
-      #endif
-      #ifdef LOOKUP_MYSQL
+#endif
+#ifdef LOOKUP_MYSQL
       mysql_servers = argv[i];
-      #endif
-      #ifdef LOOKUP_PGSQL
+#endif
+#ifdef LOOKUP_PGSQL
       pgsql_servers = argv[i];
-      #endif
-      #ifdef EXPERIMENTAL_REDIS
+#endif
+#ifdef LOOKUP_REDIS
       redis_servers = argv[i];
-      #endif
+#endif
       }
-  #ifdef EXIM_PERL
+#ifdef EXIM_PERL
   else opt_perl_startup = argv[i];
-  #endif
+#endif
   }
 
 printf("Testing string expansion: debug_level = %d\n\n", debug_level);