ACL: remove obsolete demime condition
[exim.git] / src / src / expand.c
index 7e10ee5533407debae9c015d2708de03cf2106ae..1c9a3be45a23c2577ef358eddee4c00dbc126005 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -94,10 +94,6 @@ bcrypt ({CRYPT}$2a$).
 
 
 
-#ifndef nelements
-# define nelements(arr) (sizeof(arr) / sizeof(*arr))
-#endif
-
 /*************************************************
 *            Local statics and tables            *
 *************************************************/
@@ -109,12 +105,13 @@ static uschar *item_table[] = {
   US"acl",
   US"certextract",
   US"dlfunc",
+  US"env",
   US"extract",
   US"filter",
   US"hash",
   US"hmac",
   US"if",
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   US"imapfolder",
 #endif
   US"length",
@@ -138,12 +135,13 @@ enum {
   EITEM_ACL,
   EITEM_CERTEXTRACT,
   EITEM_DLFUNC,
+  EITEM_ENV,
   EITEM_EXTRACT,
   EITEM_FILTER,
   EITEM_HASH,
   EITEM_HMAC,
   EITEM_IF,
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   EITEM_IMAPFOLDER,
 #endif
   EITEM_LENGTH,
@@ -175,7 +173,7 @@ static uschar *op_table_underscore[] = {
   US"reverse_ip",
   US"time_eval",
   US"time_interval"
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
  ,US"utf8_domain_from_alabel",
   US"utf8_domain_to_alabel",
   US"utf8_localpart_from_alabel",
@@ -190,7 +188,7 @@ enum {
   EOP_REVERSE_IP,
   EOP_TIME_EVAL,
   EOP_TIME_INTERVAL
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
  ,EOP_UTF8_DOMAIN_FROM_ALABEL,
   EOP_UTF8_DOMAIN_TO_ALABEL,
   EOP_UTF8_LOCALPART_FROM_ALABEL,
@@ -203,6 +201,8 @@ static uschar *op_table_main[] = {
   US"addresses",
   US"base62",
   US"base62d",
+  US"base64",
+  US"base64d",
   US"domain",
   US"escape",
   US"eval",
@@ -212,6 +212,8 @@ static uschar *op_table_main[] = {
   US"hash",
   US"hex2b64",
   US"hexquote",
+  US"ipv6denorm",
+  US"ipv6norm",
   US"l",
   US"lc",
   US"length",
@@ -237,10 +239,12 @@ static uschar *op_table_main[] = {
   US"utf8clean" };
 
 enum {
-  EOP_ADDRESS =  sizeof(op_table_underscore)/sizeof(uschar *),
+  EOP_ADDRESS =  nelem(op_table_underscore),
   EOP_ADDRESSES,
   EOP_BASE62,
   EOP_BASE62D,
+  EOP_BASE64,
+  EOP_BASE64D,
   EOP_DOMAIN,
   EOP_ESCAPE,
   EOP_EVAL,
@@ -250,6 +254,8 @@ enum {
   EOP_HASH,
   EOP_HEX2B64,
   EOP_HEXQUOTE,
+  EOP_IPV6DENORM,
+  EOP_IPV6NORM,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
@@ -464,6 +470,7 @@ static var_entry var_table[] = {
   { "bounce_return_size_limit", vtype_int,    &bounce_return_size_limit },
   { "caller_gid",          vtype_gid,         &real_gid },
   { "caller_uid",          vtype_uid,         &real_uid },
+  { "callout_address",     vtype_stringptr,   &callout_address },
   { "compile_date",        vtype_stringptr,   &version_date },
   { "compile_number",      vtype_stringptr,   &version_cnumber },
   { "config_dir",          vtype_stringptr,   &config_main_directory },
@@ -473,10 +480,6 @@ static var_entry var_table[] = {
   { "dcc_header",          vtype_stringptr,   &dcc_header },
   { "dcc_result",          vtype_stringptr,   &dcc_result },
 #endif
-#ifdef WITH_OLD_DEMIME
-  { "demime_errorlevel",   vtype_int,         &demime_errorlevel },
-  { "demime_reason",       vtype_stringptr,   &demime_reason },
-#endif
 #ifndef DISABLE_DKIM
   { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
   { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
@@ -490,6 +493,7 @@ static var_entry var_table[] = {
   { "dkim_headernames",    vtype_dkim,        (void *)DKIM_HEADERNAMES },
   { "dkim_identity",       vtype_dkim,        (void *)DKIM_IDENTITY },
   { "dkim_key_granularity",vtype_dkim,        (void *)DKIM_KEY_GRANULARITY },
+  { "dkim_key_length",     vtype_int,         &dkim_key_length },
   { "dkim_key_nosubdomains",vtype_dkim,       (void *)DKIM_NOSUBDOMAINS },
   { "dkim_key_notes",      vtype_dkim,        (void *)DKIM_KEY_NOTES },
   { "dkim_key_srvtype",    vtype_dkim,        (void *)DKIM_KEY_SRVTYPE },
@@ -512,7 +516,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*/
@@ -524,9 +528,6 @@ static var_entry var_table[] = {
   { "exim_path",           vtype_stringptr,   &exim_path },
   { "exim_uid",            vtype_uid,         &exim_uid },
   { "exim_version",        vtype_stringptr,   &version_string },
-#ifdef WITH_OLD_DEMIME
-  { "found_extension",     vtype_stringptr,   &found_extension },
-#endif
   { "headers_added",       vtype_string_func, &fn_hdrs_added },
   { "home",                vtype_stringptr,   &deliver_home },
   { "host",                vtype_stringptr,   &deliver_host },
@@ -535,6 +536,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 },
@@ -569,7 +571,7 @@ static var_entry var_table[] = {
   { "message_id",          vtype_stringptr,   &message_id },
   { "message_linecount",   vtype_int,         &message_linecount },
   { "message_size",        vtype_int,         &message_size },
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   { "message_smtputf8",    vtype_bool,        &message_smtputf8 },
 #endif
 #ifdef WITH_CONTENT_SCAN
@@ -607,13 +609,16 @@ static var_entry var_table[] = {
   { "parent_domain",       vtype_stringptr,   &deliver_domain_parent },
   { "parent_local_part",   vtype_stringptr,   &deliver_localpart_parent },
   { "pid",                 vtype_pid,         NULL },
+#ifndef DISABLE_PRDR
+  { "prdr_requested",      vtype_bool,        &prdr_requested },
+#endif
   { "primary_hostname",    vtype_stringptr,   &primary_hostname },
-#ifdef EXPERIMENTAL_PROXY
-  { "proxy_host_address",  vtype_stringptr,   &proxy_host_address },
-  { "proxy_host_port",     vtype_int,         &proxy_host_port },
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+  { "proxy_external_address",vtype_stringptr, &proxy_external_address },
+  { "proxy_external_port", vtype_int,         &proxy_external_port },
+  { "proxy_local_address", vtype_stringptr,   &proxy_local_address },
+  { "proxy_local_port",    vtype_int,         &proxy_local_port },
   { "proxy_session",       vtype_bool,        &proxy_session },
-  { "proxy_target_address",vtype_stringptr,   &proxy_target_address },
-  { "proxy_target_port",   vtype_int,         &proxy_target_port },
 #endif
   { "prvscheck_address",   vtype_stringptr,   &prvscheck_address },
   { "prvscheck_keynum",    vtype_stringptr,   &prvscheck_keynum },
@@ -762,7 +767,7 @@ static var_entry var_table[] = {
   { "warnmsg_recipients",  vtype_stringptr,   &warnmsg_recipients }
 };
 
-static int var_table_size = sizeof(var_table)/sizeof(var_entry);
+static int var_table_size = nelem(var_table);
 static uschar var_buffer[256];
 static BOOL malformed_header;
 
@@ -1062,6 +1067,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)
@@ -1268,7 +1275,7 @@ certfield * cp;
 
 if (!(vp = find_var_ent(certvar)))
   {
-  expand_string_message = 
+  expand_string_message =
     string_sprintf("no variable named \"%s\"", certvar);
   return NULL;          /* Unknown variable name */
   }
@@ -1276,7 +1283,7 @@ if (!(vp = find_var_ent(certvar)))
 want to do that in future */
 if (vp->type != vtype_cert)
   {
-  expand_string_message = 
+  expand_string_message =
     string_sprintf("\"%s\" is not a certificate", certvar);
   return NULL;          /* Unknown variable name */
   }
@@ -1287,7 +1294,7 @@ if (*field >= '0' && *field <= '9')
   return tls_cert_ext_by_oid(*(void **)vp->value, field, 0);
 
 for(cp = certfields;
-    cp < certfields + nelements(certfields);
+    cp < certfields + nelem(certfields);
     cp++)
   if (Ustrncmp(cp->name, field, cp->namelen) == 0)
     {
@@ -1296,7 +1303,7 @@ for(cp = certfields;
     return (*cp->getfn)( *(void **)vp->value, modifier );
     }
 
-expand_string_message = 
+expand_string_message =
   string_sprintf("bad field selector \"%s\" for certextract", field);
 return NULL;
 }
@@ -1457,7 +1464,7 @@ unsigned long int total = 0; /* no overflow */
 
 while (*s != 0)
   {
-  if (i == 0) i = sizeof(prime)/sizeof(int) - 1;
+  if (i == 0) i = nelem(prime) - 1;
   total += prime[i--] * (unsigned int)(*s++);
   }
 
@@ -1718,7 +1725,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. */
@@ -1728,7 +1735,14 @@ if (Ustrncmp(name, "auth", 4) == 0)
   uschar *endptr;
   int n = Ustrtoul(name + 4, &endptr, 10);
   if (*endptr == 0 && n != 0 && n <= AUTH_VARS)
-    return (auth_vars[n-1] == NULL)? US"" : auth_vars[n-1];
+    return !auth_vars[n-1] ? US"" : auth_vars[n-1];
+  }
+else if (Ustrncmp(name, "regex", 5) == 0)
+  {
+  uschar *endptr;
+  int n = Ustrtoul(name + 5, &endptr, 10);
+  if (*endptr == 0 && n != 0 && n <= REGEX_VARS)
+    return !regex_vars[n-1] ? US"" : regex_vars[n-1];
   }
 
 /* For all other variables, search the table */
@@ -1746,153 +1760,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:
     {
@@ -1917,12 +1928,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
 
   }
 
@@ -2040,7 +2051,7 @@ Load args from sub array to globals, and call acl_check().
 Sub array will be corrupted on return.
 
 Returns:       OK         access is granted by an ACCEPT verb
-               DISCARD    access is granted by a DISCARD verb
+               DISCARD    access is (apparently) granted by a DISCARD verb
               FAIL       access is denied
               FAIL_DROP  access is denied; drop the connection
               DEFER      can't tell at the moment
@@ -2055,7 +2066,7 @@ int ret;
 uschar * dummy_logmsg;
 extern int acl_where;
 
-if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
+if(--nsub > nelem(acl_arg)) nsub = nelem(acl_arg);
 for (i = 0; i < nsub && sub[i+1]; i++)
   {
   uschar * tmp = acl_arg[i];
@@ -2157,7 +2168,7 @@ if (name[0] == 0)
 
 /* Find which condition we are dealing with, and switch on it */
 
-cond_type = chop_match(name, cond_table, sizeof(cond_table)/sizeof(uschar *));
+cond_type = chop_match(name, cond_table, nelem(cond_table));
 switch(cond_type)
   {
   /* def: tests for a non-empty variable, or for the existence of a header. If
@@ -2346,7 +2357,7 @@ switch(cond_type)
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
-    switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
+    switch(read_subs(sub, nelem(sub), 1,
       &s, yield == NULL, TRUE, US"acl", resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
@@ -2355,8 +2366,8 @@ switch(cond_type)
       case 3: return NULL;
       }
 
-    *resetok = FALSE;
-    if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+    *resetok = FALSE;  /* eval_acl() might allocate; do not reclaim */
+    if (yield != NULL) switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
          cond = TRUE;
@@ -2372,6 +2383,7 @@ switch(cond_type)
 
        case DEFER:
           expand_string_forcedfail = TRUE;
+         /*FALLTHROUGH*/
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          return NULL;
@@ -2388,29 +2400,32 @@ switch(cond_type)
   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;       /* }-for-text-editors */
-  switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd", resetok))
-    {
-    case 1: expand_string_message = US"too few arguments or bracketing "
-      "error for saslauthd";
-    case 2:
-    case 3: return NULL;
-    }
-  if (sub[2] == NULL) sub[3] = NULL;  /* realm if no service */
-  if (yield != NULL)
+#ifndef CYRUS_SASLAUTHD_SOCKET
+    goto COND_FAILED_NOT_COMPILED;
+#else
     {
-    int rc;
-    rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3],
-      &expand_string_message);
-    if (rc == ERROR || rc == DEFER) return NULL;
-    *yield = (rc == OK) == testfor;
+    uschar *sub[4];
+    while (isspace(*s)) s++;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
+    switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, US"saslauthd",
+                   resetok))
+      {
+      case 1: expand_string_message = US"too few arguments or bracketing "
+       "error for saslauthd";
+      case 2:
+      case 3: return NULL;
+      }
+    if (sub[2] == NULL) sub[3] = NULL;  /* realm if no service */
+    if (yield != NULL)
+      {
+      int rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3],
+       &expand_string_message);
+      if (rc == ERROR || rc == DEFER) return NULL;
+      *yield = (rc == OK) == testfor;
+      }
+    return s;
     }
-  return s;
-  #endif /* CYRUS_SASLAUTHD_SOCKET */
+#endif /* CYRUS_SASLAUTHD_SOCKET */
 
 
   /* symbolic operators for numeric and string comparison, and a number of
@@ -2675,7 +2690,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);
@@ -2713,7 +2728,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);
@@ -2770,7 +2785,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
@@ -2779,8 +2794,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 */
@@ -3126,7 +3149,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.
 
@@ -3157,7 +3181,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;
@@ -3184,10 +3208,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;
 
@@ -3377,8 +3402,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);
@@ -3418,9 +3443,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
@@ -3435,21 +3460,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;
 }
@@ -3623,13 +3646,20 @@ eval_op_sum(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
 int_eximarith_t x = eval_op_mult(&s, decimal, error);
-if (*error == NULL)
+if (!*error)
   {
   while (*s == '+' || *s == '-')
     {
     int op = *s++;
     int_eximarith_t y = eval_op_mult(&s, decimal, error);
-    if (*error != NULL) break;
+    if (*error) break;
+    if (  (x >=   EXIM_ARITH_MAX/2  && x >=   EXIM_ARITH_MAX/2)
+       || (x <= -(EXIM_ARITH_MAX/2) && y <= -(EXIM_ARITH_MAX/2)))
+      {                        /* over-conservative check */
+      *error = op == '+'
+       ? US"overflow in sum" : US"overflow in difference";
+      break;
+      }
     if (op == '+') x += y; else x -= y;
     }
   }
@@ -3906,16 +3936,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
@@ -3983,7 +4009,7 @@ while (*s != 0)
   OK. */
 
   s = read_name(name, sizeof(name), s, US"_-");
-  item_type = chop_match(name, item_table, sizeof(item_table)/sizeof(uschar *));
+  item_type = chop_match(name, item_table, nelem(item_table));
 
   switch(item_type)
     {
@@ -4002,7 +4028,8 @@ while (*s != 0)
       uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
       uschar *user_msg;
 
-      switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl", &resetok))
+      switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, US"acl",
+                     &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4011,7 +4038,7 @@ while (*s != 0)
       if (skipping) continue;
 
       resetok = FALSE;
-      switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+      switch(eval_acl(sub, nelem(sub), &user_msg))
        {
        case OK:
        case FAIL:
@@ -4023,6 +4050,7 @@ while (*s != 0)
 
        case DEFER:
           expand_string_forcedfail = TRUE;
+         /*FALLTHROUGH*/
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
          goto EXPAND_FAILED;
@@ -4077,13 +4105,14 @@ while (*s != 0)
       continue;
       }
 
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
     case EITEM_IMAPFOLDER:
       {                                /* ${imapfolder {name}{sep]{specials}} */
       uschar *sub_arg[3];
       uschar *encoded;
 
-      switch(read_subs(sub_arg, 3, 1, &s, skipping, TRUE, name, &resetok))
+      switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name,
+                     &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -4092,17 +4121,14 @@ while (*s != 0)
 
       if (sub_arg[1] == NULL)          /* One argument */
        {
-       sub_arg[1] = "/";               /* default separator */
+       sub_arg[1] = US"/";             /* default separator */
        sub_arg[2] = NULL;
        }
-      else if (sub_arg[2] == NULL)     /* Two arguments */
-       sub_arg[2] = NULL;
-
-      if (Ustrlen(sub_arg[1]) != 1)
+      else if (Ustrlen(sub_arg[1]) != 1)
        {
-       expand_string_message = 
+       expand_string_message =
          string_sprintf(
-               "IMAP folder separator must be one character, found \"%s\"", 
+               "IMAP folder separator must be one character, found \"%s\"",
                sub_arg[1]);
        goto EXPAND_FAILED;
        }
@@ -4850,8 +4876,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)
         {
@@ -4879,15 +4904,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));
@@ -4900,12 +4921,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);
 
@@ -5205,7 +5228,7 @@ while (*s != 0)
         {
         int ovector[3*(EXPAND_MAXN+1)];
         int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra,
-          PCRE_EOPT | emptyopt, ovector, sizeof(ovector)/sizeof(int));
+          PCRE_EOPT | emptyopt, ovector, nelem(ovector));
         int nn;
         uschar *insert;
 
@@ -5286,9 +5309,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 == '{')                                                 /*}*/
@@ -5315,27 +5354,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;
              }
             }
           }
@@ -5453,7 +5489,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 */
@@ -5525,7 +5561,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 */
@@ -5862,12 +5898,12 @@ while (*s != 0)
     #define EXPAND_DLFUNC_MAX_ARGS 8
 
     case EITEM_DLFUNC:
-    #ifndef EXPAND_DLFUNC
-    expand_string_message = US"\"${dlfunc\" encountered, but this facility "   /*}*/
-      "is not included in this binary";
-    goto EXPAND_FAILED;
+#ifndef EXPAND_DLFUNC
+      expand_string_message = US"\"${dlfunc\" encountered, but this facility " /*}*/
+       "is not included in this binary";
+      goto EXPAND_FAILED;
 
-    #else   /* EXPAND_DLFUNC */
+#else   /* EXPAND_DLFUNC */
       {
       tree_node *t;
       exim_dlfunc_t *func;
@@ -5953,7 +5989,39 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
       }
-    #endif /* EXPAND_DLFUNC */
+#endif /* EXPAND_DLFUNC */
+
+    case EITEM_ENV:    /* ${env {name} {val_if_found} {val_if_unfound}} */
+      {
+      uschar * key;
+      uschar *save_lookup_value = lookup_value;
+
+      while (isspace(*s)) s++;
+      if (*s != '{')                                   /*}*/
+       goto EXPAND_FAILED;
+
+      key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+      if (!key) goto EXPAND_FAILED;                    /*{*/
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      lookup_value = US getenv(CS key);
+
+      switch(process_yesno(
+               skipping,                     /* were previously skipping */
+               lookup_value != NULL,         /* success/failure indicator */
+               save_lookup_value,            /* value to reset for string2 */
+               &s,                           /* input pointer */
+               &yield,                       /* output pointer */
+               &size,                        /* output size */
+               &ptr,                         /* output current point */
+               US"env",                      /* condition type */
+              &resetok))
+        {
+        case 1: goto EXPAND_FAILED;          /* when all is well, the */
+        case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
+        }
+      continue;
+      }
     }  /* EITEM_* switch */
 
   /* Control reaches here if the name is not recognized as one of the more
@@ -5974,13 +6042,12 @@ while (*s != 0)
     the arguments and then scan the main table. */
 
     if ((c = chop_match(name, op_table_underscore,
-       sizeof(op_table_underscore)/sizeof(uschar *))) < 0)
+                       nelem(op_table_underscore))) < 0)
       {
       arg = Ustrchr(name, '_');
       if (arg != NULL) *arg = 0;
-      c = chop_match(name, op_table_main,
-        sizeof(op_table_main)/sizeof(uschar *));
-      if (c >= 0) c += sizeof(op_table_underscore)/sizeof(uschar *);
+      c = chop_match(name, op_table_main, nelem(op_table_main));
+      if (c >= 0) c += nelem(op_table_underscore);
       if (arg != NULL) *arg++ = '_';   /* Put back for error messages */
       }
 
@@ -5992,6 +6059,7 @@ while (*s != 0)
       case EOP_MD5:
       case EOP_SHA1:
       case EOP_SHA256:
+      case EOP_BASE64:
        if (s[1] == '$')
          {
          const uschar * s1 = s;
@@ -6195,7 +6263,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;
         }
@@ -6364,6 +6432,39 @@ while (*s != 0)
         continue;
         }
 
+      case EOP_IPV6NORM:
+      case EOP_IPV6DENORM:
+       {
+        int type = string_is_ip_address(sub, NULL);
+       int binary[4];
+       uschar buffer[44];
+
+       switch (type)
+         {
+         case 6:
+           (void) host_aton(sub, binary);
+           break;
+
+         case 4:       /* convert to IPv4-mapped IPv6 */
+           binary[0] = binary[1] = 0;
+           binary[2] = 0x0000ffff;
+           (void) host_aton(sub, binary+3);
+           break;
+
+         case 0:
+           expand_string_message =
+             string_sprintf("\"%s\" is not an IP address", sub);
+           goto EXPAND_FAILED;
+         }
+
+       yield = string_cat(yield, &size, &ptr, buffer,
+                 c == EOP_IPV6NORM
+                   ? ipv6_nmtoa(binary, buffer)
+                   : host_nmtoa(4, binary, -1, buffer, ':')
+                 );
+       continue;
+       }
+
       case EOP_ADDRESS:
       case EOP_LOCAL_PART:
       case EOP_DOMAIN:
@@ -6597,7 +6698,7 @@ while (*s != 0)
         }
 
          /* replace illegal UTF-8 sequences by replacement character  */
-         
+
       #define UTF8_REPLACEMENT_CHAR US"?"
 
       case EOP_UTF8CLEAN:
@@ -6606,7 +6707,7 @@ while (*s != 0)
         int bytes_left = 0;
        long codepoint = -1;
         uschar seq_buff[4];                    /* accumulate utf-8 here */
-        
+
         while (*sub != 0)
          {
          int complete = 0;
@@ -6677,7 +6778,7 @@ while (*s != 0)
         continue;
         }
 
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
       case EOP_UTF8_DOMAIN_TO_ALABEL:
        {
         uschar * error = NULL;
@@ -6803,9 +6904,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;
         }
 
@@ -7361,7 +7483,7 @@ regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup)
 {
 int ovector[3*(EXPAND_MAXN+1)];
 int n = pcre_exec(re, NULL, subject, Ustrlen(subject), 0, PCRE_EOPT|options,
-  ovector, sizeof(ovector)/sizeof(int));
+  ovector, nelem(ovector));
 BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
@@ -7402,22 +7524,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);