Expansions: A tls option on ${readsocket }. Bug 2282
[exim.git] / src / src / expand.c
index e386bbd97510128b411231c88e39f5128a7f7428..596fb240494f46184121d75240d442b472609b38 100644 (file)
@@ -1525,10 +1525,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 +1537,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
@@ -1678,13 +1675,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;
 }
 
@@ -3550,6 +3550,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
 
 
 /*************************************************
@@ -4801,9 +4821,15 @@ while (*s != 0)
       int timeout = 5;
       int save_ptr = yield->ptr;
       FILE *f;
-      uschar *arg;
-      uschar *sub_arg[4];
+      uschar * arg;
+      uschar * sub_arg[4];
+      uschar * server_name = NULL;
+      host_item host;
       BOOL do_shutdown = TRUE;
+#ifdef SUPPORT_TLS
+      BOOL do_tls = FALSE;
+      void * tls_ctx = NULL;
+#endif
       blob reqstr;
 
       if (expand_forbid & RDO_READSOCK)
@@ -4846,10 +4872,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 +4891,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 +4930,12 @@ 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;
+         if (!do_tls) reqstr.len = 0;
           }
 
         /* Handle a Unix domain socket */
@@ -4922,6 +4955,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,10 +4972,27 @@ 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 = {0};
+         uschar * errstr;
+
+         if (!(tls_ctx = tls_client_start(fd, &host, NULL, NULL, NULL,
+                               &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);
 
@@ -4951,7 +5002,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,7 +5019,7 @@ 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);
@@ -4972,12 +5027,26 @@ while (*s != 0)
         /* 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)
+         f = 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(f, 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(f);
 
         /* After a timeout, we restore the pointer in the result, that is,
         make sure we add nothing from the socket. */