Use single-bit fields for global flags
[exim.git] / src / src / expand.c
index e386bbd97510128b411231c88e39f5128a7f7428..b3e918ea8f9f00570641b55e834c0251354282c6 100644 (file)
@@ -660,6 +660,9 @@ static var_entry var_table[] = {
   { "regex_match_string",  vtype_stringptr,   &regex_match_string },
 #endif
   { "reply_address",       vtype_reply,       NULL },
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+  { "requiretls",          vtype_bool,        &tls_requiretls },
+#endif
   { "return_path",         vtype_stringptr,   &return_path },
   { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
   { "router_name",         vtype_stringptr,   &router_name },
@@ -924,7 +927,7 @@ int rc;
 uschar *ss = expand_string(condition);
 if (ss == NULL)
   {
-  if (!expand_string_forcedfail && !search_find_defer)
+  if (!f.expand_string_forcedfail && !f.search_find_defer)
     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand condition \"%s\" "
       "for %s %s: %s", condition, m1, m2, expand_string_message);
   return FALSE;
@@ -1525,10 +1528,7 @@ can also return a concatenation of all the header lines. When concatenating
 specific headers that contain lists of addresses, a comma is inserted between
 them. Otherwise we use a straight concatenation. Because some messages can have
 pathologically large number of lines, there is a limit on the length that is
-returned. Also, to avoid massive store use which would result from using
-string_cat() as it copies and extends strings, we do a preliminary pass to find
-out exactly how much store will be needed. On "normal" messages this will be
-pretty trivial.
+returned.
 
 Arguments:
   name          the name of the header, without the leading $header_ or $h_,
@@ -1540,7 +1540,7 @@ Arguments:
                  just return a string that is not "" and not "0" if the header
                  exists
                FH_WANT_RAW
-                 set if called for $rh_ or $rheader_ variables; no processing,
+                 set if called for $rh_ or $rheader_ items; no processing,
                  other than concatenating, will be done on the header. Also used
                  for $message_headers_raw.
                FH_WANT_LIST
@@ -1662,7 +1662,7 @@ if this was a non-smtp message.
 static gstring *
 authres_local(gstring * g, const uschar * sysname)
 {
-if (!authentication_local)
+if (!f.authentication_local)
   return g;
 g = string_append(g, 3, US";\n\tlocal=pass (non-smtp, ", sysname, US")");
 if (authenticated_id) g = string_append(g, 2, " u=", authenticated_id);
@@ -1678,13 +1678,16 @@ static gstring *
 authres_iprev(gstring * g)
 {
 if (sender_host_name)
-  return string_append(g, sender_host_address ? 5 : 3,
-    US";\n\tiprev=pass (", sender_host_name, US")",
-    US" smtp.client-ip=", sender_host_address);
-if (host_lookup_deferred)
-  return string_catn(g, US";\n\tiprev=temperror", 19);
-if (host_lookup_failed)
-  return string_catn(g, US";\n\tiprev=fail", 13);
+  g = string_append(g, 3, US";\n\tiprev=pass (", sender_host_name, US")");
+else if (host_lookup_deferred)
+  g = string_catn(g, US";\n\tiprev=temperror", 19);
+else if (host_lookup_failed)
+  g = string_catn(g, US";\n\tiprev=fail", 13);
+else
+  return g;
+
+if (sender_host_address)
+  g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
 return g;
 }
 
@@ -1704,7 +1707,7 @@ uschar * s;
 gstring * g = NULL;
 int i;
 
-if (!enable_dollar_recipients) return NULL;
+if (!f.enable_dollar_recipients) return NULL;
 
 for (i = 0; i < recipients_count; i++)
   {
@@ -1796,7 +1799,7 @@ val = vp->value;
 switch (vp->type)
   {
   case vtype_filter_int:
-    if (!filter_running) return NULL;
+    if (!f.filter_running) return NULL;
     /* Fall through */
     /* VVVVVVVVVVVV */
   case vtype_int:
@@ -2273,7 +2276,7 @@ switch(cond_type)
   /* first_delivery tests for first delivery attempt */
 
   case ECOND_FIRST_DELIVERY:
-  if (yield != NULL) *yield = deliver_firsttime == testfor;
+  if (yield != NULL) *yield = f.deliver_firsttime == testfor;
   return s;
 
 
@@ -2428,7 +2431,7 @@ switch(cond_type)
          break;
 
        case DEFER:
-          expand_string_forcedfail = TRUE;
+          f.expand_string_forcedfail = TRUE;
          /*FALLTHROUGH*/
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
@@ -3255,8 +3258,8 @@ want this string. Set skipping in the call in the fail case (this will always
 be the case if we were already skipping). */
 
 sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok);
-if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED;
-expand_string_forcedfail = FALSE;
+if (sub1 == NULL && (yes || !f.expand_string_forcedfail)) goto FAILED;
+f.expand_string_forcedfail = FALSE;
 if (*s++ != '}')
   {
   errwhere = US"'yes' part did not end with '}'";
@@ -3285,8 +3288,8 @@ while (isspace(*s)) s++;
 if (*s == '{')
   {
   sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok);
-  if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED;
-  expand_string_forcedfail = FALSE;
+  if (sub2 == NULL && (!yes || !f.expand_string_forcedfail)) goto FAILED;
+  f.expand_string_forcedfail = FALSE;
   if (*s++ != '}')
     {
     errwhere = US"'no' part did not start with '{'";
@@ -3321,7 +3324,7 @@ else if (*s != '}')
        }
       expand_string_message =
         string_sprintf("\"%s\" failed and \"fail\" requested", type);
-      expand_string_forcedfail = TRUE;
+      f.expand_string_forcedfail = TRUE;
       goto FAILED;
       }
     }
@@ -3550,6 +3553,26 @@ return yield;
 }
 
 
+#ifdef SUPPORT_TLS
+static gstring *
+cat_file_tls(void * tls_ctx, gstring * yield, uschar * eol)
+{
+int rc;
+uschar * s;
+uschar buffer[1024];
+
+while ((rc = tls_read(tls_ctx, buffer, sizeof(buffer))) > 0)
+  for (s = buffer; rc--; s++)
+    yield = eol && *s == '\n'
+      ? string_cat(yield, eol) : string_catn(yield, s, 1);
+
+/* We assume that all errors, and any returns of zero bytes,
+are actually EOF. */
+
+(void) string_from_gstring(yield);
+return yield;
+}
+#endif
 
 
 /*************************************************
@@ -3908,7 +3931,7 @@ DEBUG(D_expand)
     : "considering",
     string);
 
-expand_string_forcedfail = FALSE;
+f.expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
 while (*s != 0)
@@ -4135,7 +4158,7 @@ while (*s != 0)
          continue;
 
        case DEFER:
-          expand_string_forcedfail = TRUE;
+          f.expand_string_forcedfail = TRUE;
          /*FALLTHROUGH*/
        default:
           expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
@@ -4433,7 +4456,7 @@ while (*s != 0)
           }
         lookup_value = search_find(handle, filename, key, partial, affix,
           affixlen, starflags, &expand_setup);
-        if (search_find_defer)
+        if (f.search_find_defer)
           {
           expand_string_message =
             string_sprintf("lookup of \"%s\" gave DEFER: %s",
@@ -4542,7 +4565,7 @@ while (*s != 0)
           expand_string_message =
             string_sprintf("Perl subroutine \"%s\" returned undef to force "
               "failure", sub_arg[0]);
-          expand_string_forcedfail = TRUE;
+          f.expand_string_forcedfail = TRUE;
           }
         goto EXPAND_FAILED;
         }
@@ -4550,7 +4573,7 @@ while (*s != 0)
       /* Yield succeeded. Ensure forcedfail is unset, just in case it got
       set during a callback from Perl. */
 
-      expand_string_forcedfail = FALSE;
+      f.expand_string_forcedfail = FALSE;
       yield = new_yield;
       continue;
       }
@@ -4800,10 +4823,14 @@ while (*s != 0)
       int fd;
       int timeout = 5;
       int save_ptr = yield->ptr;
-      FILE *f;
-      uschar *arg;
-      uschar *sub_arg[4];
+      FILE * fp;
+      uschar * arg;
+      uschar * sub_arg[4];
+      uschar * server_name = NULL;
+      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)
@@ -4846,10 +4873,14 @@ while (*s != 0)
 
        while ((item = string_nextinlist(&list, &sep, NULL, 0)))
          if (Ustrncmp(item, US"shutdown=", 9) == 0)
-           if (Ustrcmp(item + 9, US"no") == 0)
-             do_shutdown = FALSE;
+           { if (Ustrcmp(item + 9, US"no") == 0) do_shutdown = FALSE; }
+#ifdef SUPPORT_TLS
+         else if (Ustrncmp(item, US"tls=", 4) == 0)
+           { if (Ustrcmp(item + 9, US"no") != 0) do_tls = TRUE; }
+#endif
         }
-      else sub_arg[3] = NULL;                     /* No eol if no timeout */
+      else
+       sub_arg[3] = NULL;                     /* No eol if no timeout */
 
       /* If skipping, we don't actually do anything. Otherwise, arrange to
       connect to either an IP or a Unix socket. */
@@ -4861,8 +4892,10 @@ while (*s != 0)
         if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
           {
           int port;
-          uschar * server_name = sub_arg[0] + 5;
-          uschar * port_name = Ustrrchr(server_name, ':');
+          uschar * port_name;
+
+          server_name = sub_arg[0] + 5;
+          port_name = Ustrrchr(server_name, ':');
 
           /* Sort out the port */
 
@@ -4898,11 +4931,13 @@ while (*s != 0)
             }
 
          fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
-                 timeout, NULL, &expand_string_message, &reqstr);
+                 timeout, &host, &expand_string_message,
+                 do_tls ? NULL : &reqstr);
          callout_address = NULL;
          if (fd < 0)
-              goto SOCK_FAIL;
-         reqstr.len = 0;
+           goto SOCK_FAIL;
+         if (!do_tls)
+           reqstr.len = 0;
           }
 
         /* Handle a Unix domain socket */
@@ -4922,6 +4957,7 @@ while (*s != 0)
           sockun.sun_family = AF_UNIX;
           sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
             sub_arg[0]);
+         server_name = sockun.sun_path;
 
           sigalrm_seen = FALSE;
           alarm(timeout);
@@ -4938,12 +4974,32 @@ while (*s != 0)
               "%s: %s", sub_arg[0], strerror(errno));
             goto SOCK_FAIL;
             }
+         host.name = server_name;
+         host.address = US"";
           }
 
         DEBUG(D_expand) debug_printf_indent("connected to socket %s\n", sub_arg[0]);
 
+#ifdef SUPPORT_TLS
+       if (do_tls)
+         {
+         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)))
+           {
+           expand_string_message = string_sprintf("TLS connect failed: %s", errstr);
+           goto SOCK_FAIL;
+           }
+         }
+#endif
+
        /* Allow sequencing of test actions */
-       if (running_in_test_harness) millisleep(100);
+       if (f.running_in_test_harness) millisleep(100);
 
         /* Write the request string, if not empty or already done */
 
@@ -4951,7 +5007,11 @@ while (*s != 0)
           {
           DEBUG(D_expand) debug_printf_indent("writing \"%s\" to socket\n",
             reqstr.data);
-          if (write(fd, reqstr.data, reqstr.len) != reqstr.len)
+          if ( (
+#ifdef SUPPORT_TLS
+             tls_ctx ? tls_write(tls_ctx, reqstr.data, reqstr.len, FALSE) :
+#endif
+                       write(fd, reqstr.data, reqstr.len)) != reqstr.len)
             {
             expand_string_message = string_sprintf("request write to socket "
               "failed: %s", strerror(errno));
@@ -4964,20 +5024,34 @@ while (*s != 0)
         system doesn't have this function, make it conditional. */
 
 #ifdef SHUT_WR
-       if (do_shutdown) shutdown(fd, SHUT_WR);
+       if (!tls_ctx && do_shutdown) shutdown(fd, SHUT_WR);
 #endif
 
-       if (running_in_test_harness) millisleep(100);
+       if (f.running_in_test_harness) millisleep(100);
 
         /* Now we need to read from the socket, under a timeout. The function
         that reads a file can be used. */
 
-        f = fdopen(fd, "rb");
+       if (!tls_ctx)
+         fp = fdopen(fd, "rb");
         sigalrm_seen = FALSE;
         alarm(timeout);
-        yield = cat_file(f, yield, sub_arg[3]);
+        yield =
+#ifdef SUPPORT_TLS
+         tls_ctx ? cat_file_tls(tls_ctx, yield, sub_arg[3]) :
+#endif
+                   cat_file(fp, yield, sub_arg[3]);
         alarm(0);
-        (void)fclose(f);
+
+#ifdef SUPPORT_TLS
+       if (tls_ctx)
+         {
+         tls_close(tls_ctx, TRUE);
+         close(fd);
+         }
+       else
+#endif
+         (void)fclose(fp);
 
         /* After a timeout, we restore the pointer in the result, that is,
         make sure we add nothing from the socket. */
@@ -6244,7 +6318,7 @@ while (*s != 0)
       else
         {
         expand_string_message = result == NULL ? US"(no message)" : result;
-        if(status == FAIL_FORCED) expand_string_forcedfail = TRUE;
+        if(status == FAIL_FORCED) f.expand_string_forcedfail = TRUE;
           else if(status != FAIL)
             log_write(0, LOG_MAIN|LOG_PANIC, "dlfunc{%s}{%s} failed (%d): %s",
               argv[0], argv[1], status, expand_string_message);
@@ -6862,7 +6936,7 @@ while (*s != 0)
               "missing in expanding ${addresses:%s}", --sub);
             goto EXPAND_FAILED;
             }
-        parse_allow_group = TRUE;
+        f.parse_allow_group = TRUE;
 
         for (;;)
           {
@@ -6911,7 +6985,7 @@ while (*s != 0)
         separator. */
 
         if (yield->ptr != save_ptr) yield->ptr--;
-        parse_allow_group = FALSE;
+        f.parse_allow_group = FALSE;
         continue;
         }
 
@@ -7069,12 +7143,13 @@ while (*s != 0)
         {
         int seq_len = 0, index = 0;
         int bytes_left = 0;
-       long codepoint = -1;
+        long codepoint = -1;
+        int complete;
         uschar seq_buff[4];                    /* accumulate utf-8 here */
 
         while (*sub != 0)
          {
-         int complete = 0;
+         complete = 0;
          uschar c = *sub++;
 
          if (bytes_left)
@@ -7139,6 +7214,13 @@ while (*s != 0)
                        /* ASCII character follows incomplete sequence */
              yield = string_catn(yield, &c, 1);
          }
+        /* If given a sequence truncated mid-character, we also want to report ?
+        * Eg, ${length_1:フィル} is one byte, not one character, so we expect
+        * ${utf8clean:${length_1:フィル}} to yield '?' */
+        if (bytes_left != 0)
+          {
+          yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
+          }
         continue;
         }
 
@@ -7622,9 +7704,9 @@ DEBUG(D_expand)
     string);
   debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
     "error message: %s\n",
-    expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+    f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
     expand_string_message);
-  if (expand_string_forcedfail)
+  if (f.expand_string_forcedfail)
     debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
   }
 if (resetok_p && !resetok) *resetok_p = FALSE;
@@ -7649,7 +7731,7 @@ if (Ustrpbrk(string, "$\\") != NULL)
   int old_pool = store_pool;
   uschar * s;
 
-  search_find_defer = FALSE;
+  f.search_find_defer = FALSE;
   malformed_header = FALSE;
   store_pool = POOL_MAIN;
     s = expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
@@ -7842,7 +7924,7 @@ if (svalue == NULL) { *rvalue = bvalue; return OK; }
 expanded = expand_string(svalue);
 if (expanded == NULL)
   {
-  if (expand_string_forcedfail)
+  if (f.expand_string_forcedfail)
     {
     DEBUG(dbg_opt) debug_printf("expansion of \"%s\" forced failure\n", oname);
     *rvalue = bvalue;
@@ -7880,7 +7962,7 @@ expand_hide_passwords(uschar * s)
 {
 return (  (  Ustrstr(s, "failed to expand") != NULL
          || Ustrstr(s, "expansion of ")    != NULL
-         ) 
+         )
        && (  Ustrstr(s, "mysql")   != NULL
          || Ustrstr(s, "pgsql")   != NULL
          || Ustrstr(s, "redis")   != NULL
@@ -7890,7 +7972,7 @@ return (  (  Ustrstr(s, "failed to expand") != NULL
          || Ustrstr(s, "ldapi:")  != NULL
          || Ustrstr(s, "ldapdn:") != NULL
          || Ustrstr(s, "ldapm:")  != NULL
-       )  ) 
+       )  )
   ? US"Temporary internal error" : s;
 }
 
@@ -8099,9 +8181,9 @@ while (fgets(buffer, sizeof(buffer), stdin) != NULL)
     }
   else
     {
-    if (search_find_defer) printf("search_find deferred\n");
+    if (f.search_find_defer) printf("search_find deferred\n");
     printf("Failed: %s\n", expand_string_message);
-    if (expand_string_forcedfail) printf("Forced failure\n");
+    if (f.expand_string_forcedfail) printf("Forced failure\n");
     printf("\n");
     }
   }