TLS: refactor client-start interface
[exim.git] / src / src / expand.c
index 4dc13ff4847b731411eef6cf4502082543e2baf2..14d7ce451471a8d734311c565360c16ac437e3bb 100644 (file)
@@ -312,7 +312,11 @@ static uschar *cond_table[] = {
   US"exists",
   US"first_delivery",
   US"forall",
+  US"forall_json",
+  US"forall_jsons",
   US"forany",
+  US"forany_json",
+  US"forany_jsons",
   US"ge",
   US"gei",
   US"gt",
@@ -358,7 +362,11 @@ enum {
   ECOND_EXISTS,
   ECOND_FIRST_DELIVERY,
   ECOND_FORALL,
+  ECOND_FORALL_JSON,
+  ECOND_FORALL_JSONS,
   ECOND_FORANY,
+  ECOND_FORANY_JSON,
+  ECOND_FORANY_JSONS,
   ECOND_STR_GE,
   ECOND_STR_GEI,
   ECOND_STR_GT,
@@ -741,6 +749,7 @@ static var_entry var_table[] = {
   { "tls_in_bits",         vtype_int,         &tls_in.bits },
   { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified },
   { "tls_in_cipher",       vtype_stringptr,   &tls_in.cipher },
+  { "tls_in_cipher_std",   vtype_stringptr,   &tls_in.cipher_stdname },
   { "tls_in_ocsp",         vtype_int,         &tls_in.ocsp },
   { "tls_in_ourcert",      vtype_cert,        &tls_in.ourcert },
   { "tls_in_peercert",     vtype_cert,        &tls_in.peercert },
@@ -751,6 +760,7 @@ static var_entry var_table[] = {
   { "tls_out_bits",        vtype_int,         &tls_out.bits },
   { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
   { "tls_out_cipher",      vtype_stringptr,   &tls_out.cipher },
+  { "tls_out_cipher_std",  vtype_stringptr,   &tls_out.cipher_stdname },
 #ifdef SUPPORT_DANE
   { "tls_out_dane",        vtype_bool,        &tls_out.dane_verified },
 #endif
@@ -2144,6 +2154,89 @@ return ret;
 
 
 
+/* Return pointer to dewrapped string, with enclosing specified chars removed.
+The given string is modified on return.  Leading whitespace is skipped while
+looking for the opening wrap character, then the rest is scanned for the trailing
+(non-escaped) wrap character.  A backslash in the string will act as an escape.
+
+A nul is written over the trailing wrap, and a pointer to the char after the
+leading wrap is returned.
+
+Arguments:
+  s    String for de-wrapping
+  wrap  Two-char string, the first being the opener, second the closer wrapping
+        character
+Return:
+  Pointer to de-wrapped string, or NULL on error (with expand_string_message set).
+*/
+
+static uschar *
+dewrap(uschar * s, const uschar * wrap)
+{
+uschar * p = s;
+unsigned depth = 0;
+BOOL quotesmode = wrap[0] == wrap[1];
+
+while (isspace(*p)) p++;
+
+if (*p == *wrap)
+  {
+  s = ++p;
+  wrap++;
+  while (*p)
+    {
+    if (*p == '\\') p++;
+    else if (!quotesmode && *p == wrap[-1]) depth++;
+    else if (*p == *wrap)
+      if (depth == 0)
+       {
+       *p = '\0';
+       return s;
+       }
+      else
+       depth--;
+    p++;
+    }
+  }
+expand_string_message = string_sprintf("missing '%c'", *wrap);
+return NULL;
+}
+
+
+/* Pull off the leading array or object element, returning
+a copy in an allocated string.  Update the list pointer.
+
+The element may itself be an abject or array.
+Return NULL when the list is empty.
+*/
+
+static uschar *
+json_nextinlist(const uschar ** list)
+{
+unsigned array_depth = 0, object_depth = 0;
+const uschar * s = *list, * item;
+
+while (isspace(*s)) s++;
+
+for (item = s;
+     *s && (*s != ',' || array_depth != 0 || object_depth != 0);
+     s++)
+  switch (*s)
+    {
+    case '[': array_depth++; break;
+    case ']': array_depth--; break;
+    case '{': object_depth++; break;
+    case '}': object_depth--; break;
+    }
+*list = *s ? s+1 : s;
+if (item == s) return NULL;
+item = string_copyn(item, s - item);
+DEBUG(D_expand) debug_printf_indent("  json ele: '%s'\n", item);
+return US item;
+}
+
+
+
 /*************************************************
 *        Read and evaluate a condition           *
 *************************************************/
@@ -2170,6 +2263,7 @@ BOOL testfor = TRUE;
 BOOL tempcond, combined_cond;
 BOOL *subcondptr;
 BOOL sub2_honour_dollar = TRUE;
+BOOL is_forany, is_json, is_jsons;
 int rc, cond_type, roffset;
 int_eximarith_t num[2];
 struct stat statbuf;
@@ -2945,8 +3039,14 @@ switch(cond_type)
 
   /* forall/forany: iterates a condition with different values */
 
-  case ECOND_FORALL:
-  case ECOND_FORANY:
+  case ECOND_FORALL:     is_forany = FALSE;  is_json = FALSE; is_jsons = FALSE; goto FORMANY;
+  case ECOND_FORANY:     is_forany = TRUE;   is_json = FALSE; is_jsons = FALSE; goto FORMANY;
+  case ECOND_FORALL_JSON: is_forany = FALSE;  is_json = TRUE;  is_jsons = FALSE; goto FORMANY;
+  case ECOND_FORANY_JSON: is_forany = TRUE;   is_json = TRUE;  is_jsons = FALSE; goto FORMANY;
+  case ECOND_FORALL_JSONS: is_forany = FALSE; is_json = TRUE;  is_jsons = TRUE;  goto FORMANY;
+  case ECOND_FORANY_JSONS: is_forany = TRUE;  is_json = TRUE;  is_jsons = TRUE;  goto FORMANY;
+
+  FORMANY:
     {
     const uschar * list;
     int sep = 0;
@@ -2987,10 +3087,22 @@ switch(cond_type)
       return NULL;
       }
 
-    if (yield != NULL) *yield = !testfor;
+    if (yield) *yield = !testfor;
     list = sub[0];
-    while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
+    if (is_json) list = dewrap(string_copy(list), US"[]");
+    while ((iterate_item = is_json
+      ? json_nextinlist(&list) : string_nextinlist(&list, &sep, NULL, 0)))
       {
+      if (is_jsons)
+       if (!(iterate_item = dewrap(iterate_item, US"\"\"")))
+         {
+         expand_string_message =
+           string_sprintf("%s wrapping string result for extract jsons",
+             expand_string_message);
+         iterate_item = save_iterate_item;
+         return NULL;
+         }
+
       DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, iterate_item);
       if (!eval_condition(sub[1], resetok, &tempcond))
         {
@@ -3002,8 +3114,8 @@ switch(cond_type)
       DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", name,
         tempcond? "true":"false");
 
-      if (yield != NULL) *yield = (tempcond == testfor);
-      if (tempcond == (cond_type == ECOND_FORANY)) break;
+      if (yield) *yield = (tempcond == testfor);
+      if (tempcond == is_forany) break;
       }
 
     iterate_item = save_iterate_item;
@@ -3841,87 +3953,6 @@ return x;
 
 
 
-/* Return pointer to dewrapped string, with enclosing specified chars removed.
-The given string is modified on return.  Leading whitespace is skipped while
-looking for the opening wrap character, then the rest is scanned for the trailing
-(non-escaped) wrap character.  A backslash in the string will act as an escape.
-
-A nul is written over the trailing wrap, and a pointer to the char after the
-leading wrap is returned.
-
-Arguments:
-  s    String for de-wrapping
-  wrap  Two-char string, the first being the opener, second the closer wrapping
-        character
-Return:
-  Pointer to de-wrapped string, or NULL on error (with expand_string_message set).
-*/
-
-static uschar *
-dewrap(uschar * s, const uschar * wrap)
-{
-uschar * p = s;
-unsigned depth = 0;
-BOOL quotesmode = wrap[0] == wrap[1];
-
-while (isspace(*p)) p++;
-
-if (*p == *wrap)
-  {
-  s = ++p;
-  wrap++;
-  while (*p)
-    {
-    if (*p == '\\') p++;
-    else if (!quotesmode && *p == wrap[-1]) depth++;
-    else if (*p == *wrap)
-      if (depth == 0)
-       {
-       *p = '\0';
-       return s;
-       }
-      else
-       depth--;
-    p++;
-    }
-  }
-expand_string_message = string_sprintf("missing '%c'", *wrap);
-return NULL;
-}
-
-
-/* Pull off the leading array or object element, returning
-a copy in an allocated string.  Update the list pointer.
-
-The element may itself be an abject or array.
-*/
-
-uschar *
-json_nextinlist(const uschar ** list)
-{
-unsigned array_depth = 0, object_depth = 0;
-const uschar * s = *list, * item;
-
-while (isspace(*s)) s++;
-
-for (item = s;
-     *s && (*s != ',' || array_depth != 0 || object_depth != 0);
-     s++)
-  switch (*s)
-    {
-    case '[': array_depth++; break;
-    case ']': array_depth--; break;
-    case '{': object_depth++; break;
-    case '}': object_depth--; break;
-    }
-*list = *s ? s+1 : s;
-item = string_copyn(item, s - item);
-DEBUG(D_expand) debug_printf_indent("  json ele: '%s'\n", item);
-return US item;
-}
-
-
-
 /*************************************************
 *                 Expand string                  *
 *************************************************/
@@ -4903,7 +4934,7 @@ while (*s != 0)
 
     case EITEM_READSOCK:
       {
-      int fd;
+      client_conn_ctx cctx;
       int timeout = 5;
       int save_ptr = yield->ptr;
       FILE * fp;
@@ -4913,7 +4944,6 @@ while (*s != 0)
       host_item host;
       BOOL do_shutdown = TRUE;
       BOOL do_tls = FALSE;     /* Only set under SUPPORT_TLS */
-      void * tls_ctx = NULL;   /* ditto                      */
       blob reqstr;
 
       if (expand_forbid & RDO_READSOCK)
@@ -5014,11 +5044,11 @@ while (*s != 0)
             }
 
          /*XXX we trust that the request is idempotent.  Hmm. */
-         fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+         cctx.sock = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
                  timeout, &host, &expand_string_message,
                  do_tls ? NULL : &reqstr);
          callout_address = NULL;
-         if (fd < 0)
+         if (cctx.sock < 0)
            goto SOCK_FAIL;
          if (!do_tls)
            reqstr.len = 0;
@@ -5031,7 +5061,7 @@ while (*s != 0)
          struct sockaddr_un sockun;         /* don't call this "sun" ! */
           int rc;
 
-          if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+          if ((cctx.sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
             {
             expand_string_message = string_sprintf("failed to create socket: %s",
               strerror(errno));
@@ -5045,7 +5075,7 @@ while (*s != 0)
 
           sigalrm_seen = FALSE;
           ALARM(timeout);
-          rc = connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun));
+          rc = connect(cctx.sock, (struct sockaddr *)(&sockun), sizeof(sockun));
           ALARM_CLR(0);
           if (sigalrm_seen)
             {
@@ -5067,14 +5097,11 @@ while (*s != 0)
 #ifdef SUPPORT_TLS
        if (do_tls)
          {
+         smtp_connect_args conn_args = {.host = &host };
          tls_support tls_dummy = {.sni=NULL};
          uschar * errstr;
 
-         if (!(tls_ctx = tls_client_start(fd, &host, NULL, NULL,
-# ifdef SUPPORT_DANE
-                               NULL,
-# endif
-                               &tls_dummy, &errstr)))
+         if (!tls_client_start(&cctx, &conn_args, NULL, &tls_dummy, &errstr))
            {
            expand_string_message = string_sprintf("TLS connect failed: %s", errstr);
            goto SOCK_FAIL;
@@ -5093,9 +5120,9 @@ while (*s != 0)
             reqstr.data);
           if ( (
 #ifdef SUPPORT_TLS
-             tls_ctx ? tls_write(tls_ctx, reqstr.data, reqstr.len, FALSE) :
+             cctx.tls_ctx ? tls_write(cctx.tls_ctx, reqstr.data, reqstr.len, FALSE) :
 #endif
-                       write(fd, reqstr.data, reqstr.len)) != reqstr.len)
+                       write(cctx.sock, reqstr.data, reqstr.len)) != reqstr.len)
             {
             expand_string_message = string_sprintf("request write to socket "
               "failed: %s", strerror(errno));
@@ -5108,7 +5135,7 @@ while (*s != 0)
         system doesn't have this function, make it conditional. */
 
 #ifdef SHUT_WR
-       if (!tls_ctx && do_shutdown) shutdown(fd, SHUT_WR);
+       if (!cctx.tls_ctx && do_shutdown) shutdown(cctx.sock, SHUT_WR);
 #endif
 
        if (f.running_in_test_harness) millisleep(100);
@@ -5116,22 +5143,22 @@ while (*s != 0)
         /* Now we need to read from the socket, under a timeout. The function
         that reads a file can be used. */
 
-       if (!tls_ctx)
-         fp = fdopen(fd, "rb");
+       if (!cctx.tls_ctx)
+         fp = fdopen(cctx.sock, "rb");
         sigalrm_seen = FALSE;
         ALARM(timeout);
         yield =
 #ifdef SUPPORT_TLS
-         tls_ctx ? cat_file_tls(tls_ctx, yield, sub_arg[3]) :
+         cctx.tls_ctx ? cat_file_tls(cctx.tls_ctx, yield, sub_arg[3]) :
 #endif
                    cat_file(fp, yield, sub_arg[3]);
         ALARM_CLR(0);
 
 #ifdef SUPPORT_TLS
-       if (tls_ctx)
+       if (cctx.tls_ctx)
          {
-         tls_close(tls_ctx, TRUE);
-         close(fd);
+         tls_close(cctx.tls_ctx, TRUE);
+         close(cctx.sock);
          }
        else
 #endif
@@ -5633,7 +5660,13 @@ while (*s != 0)
       uschar *sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
-      enum {extract_basic, extract_json} fmt = extract_basic;
+
+      /* On reflection the original behaviour of extract-json for a string
+      result, leaving it quoted, was a mistake.  But it was already published,
+      hence the addition of jsons.  In a future major version, make json
+      work like josons, and withdraw jsons. */
+
+      enum {extract_basic, extract_json, extract_jsons} fmt = extract_basic;
 
       while (isspace(*s)) s++;
 
@@ -5641,7 +5674,10 @@ while (*s != 0)
 
       if (*s != '{')                                   /*}*/
        if (Ustrncmp(s, "json", 4) == 0)
-         {fmt = extract_json; s += 4;}
+         if (*(s += 4) == 's')
+           {fmt = extract_jsons; s++;}
+         else
+           fmt = extract_json;
 
       /* While skipping we cannot rely on the data for expansions being
       available (eg. $item) hence cannot decide on numeric vs. keyed.
@@ -5722,7 +5758,7 @@ while (*s != 0)
            if (*p == 0)
              {
              field_number *= x;
-             if (fmt != extract_json) j = 3;               /* Need 3 args */
+             if (fmt == extract_basic) j = 3;               /* Need 3 args */
              field_number_set = TRUE;
              }
             }
@@ -5749,6 +5785,7 @@ while (*s != 0)
          break;
 
        case extract_json:
+       case extract_jsons:
          {
          uschar * s, * item;
          const uschar * list;
@@ -5776,10 +5813,11 @@ while (*s != 0)
              }
            while (field_number > 0 && (item = json_nextinlist(&list)))
              field_number--;
-           s = item;
-           lookup_value = s;
-           while (*s) s++;
-           while (--s >= lookup_value && isspace(*s)) *s = '\0';
+           if ((lookup_value = s = item))
+             {
+             while (*s) s++;
+             while (--s >= lookup_value && isspace(*s)) *s = '\0';
+             }
            }
          else
            {
@@ -5814,6 +5852,17 @@ while (*s != 0)
              }
            }
          }
+
+         if (  fmt == extract_jsons
+            && lookup_value
+            && !(lookup_value = dewrap(lookup_value, US"\"\"")))
+           {
+           expand_string_message =
+             string_sprintf("%s wrapping string result for extract jsons",
+               expand_string_message);
+           goto EXPAND_FAILED_CURLY;
+           }
+         break;        /* json/s */
        }
 
       /* If no string follows, $value gets substituted; otherwise there can
@@ -7050,16 +7099,11 @@ while (*s != 0)
         uschar * t = parse_extract_address(sub, &error, &start, &end, &domain,
           FALSE);
         if (t)
-          if (c != EOP_DOMAIN)
-            {
-            if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
-            yield = string_catn(yield, sub+start, end-start);
-            }
-          else if (domain != 0)
-            {
-            domain += start;
-            yield = string_catn(yield, sub+domain, end-domain);
-            }
+         yield = c == EOP_DOMAIN
+           ? string_cat(yield, t + domain)
+           : c == EOP_LOCAL_PART && domain > 0
+           ? string_catn(yield, t, domain - 1 )
+           : string_cat(yield, t);
         continue;
         }
 
@@ -7083,7 +7127,7 @@ while (*s != 0)
 
         for (;;)
           {
-          uschar *p = parse_find_address_end(sub, FALSE);
+          uschar * p = parse_find_address_end(sub, FALSE);
           uschar saveend = *p;
           *p = '\0';
           address = parse_extract_address(sub, &error, &start, &end, &domain,
@@ -7096,7 +7140,7 @@ while (*s != 0)
           list, add in a space if the new address begins with the separator
           character, or is an empty string. */
 
-          if (address != NULL)
+          if (address)
             {
             if (yield->ptr != save_ptr && address[0] == *outsep)
               yield = string_catn(yield, US" ", 1);