Extend ${readsocket to TCP sockets (modified John Jetmore's patch).
authorPhilip Hazel <ph10@hermes.cam.ac.uk>
Tue, 18 Apr 2006 11:13:19 +0000 (11:13 +0000)
committerPhilip Hazel <ph10@hermes.cam.ac.uk>
Tue, 18 Apr 2006 11:13:19 +0000 (11:13 +0000)
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/src/expand.c
test/confs/1010 [new file with mode: 0644]
test/dnszones-src/db.test.ex
test/scripts/0000-Basic/0373
test/scripts/1000-Basic-ipv6/1010 [new file with mode: 0644]
test/stdout/0373
test/stdout/1010 [new file with mode: 0644]

index ca221b4..d69e9ea 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.339 2006/04/04 17:10:07 fanf2 Exp $
+$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.340 2006/04/18 11:13:19 ph10 Exp $
 
 Change log file for Exim from version 4.21
 -------------------------------------------
@@ -9,6 +9,10 @@ Exim version 4.62
 TF/01 Fix the add_header change below (4.61 PH/55) which had a bug that (amongst
       other effects) broke the use of negated acl sub-conditions.
 
+PH/01 ${readsocket now supports Internet domain sockets (modified John Jetmore
+      patch).
+
+
 Exim version 4.61
 -----------------
 
index c476371..38c2099 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/NewStuff,v 1.99 2006/04/10 08:14:58 ph10 Exp $
+$Cambridge: exim/doc/doc-txt/NewStuff,v 1.100 2006/04/18 11:13:19 ph10 Exp $
 
 New Features in Exim
 --------------------
@@ -8,6 +8,24 @@ but have not yet made it into the main manual (which is most conveniently
 updated when there is a relatively large batch of changes). The doc/ChangeLog
 file contains a listing of all changes, including bug fixes.
 
+Version 4.62
+------------
+
+1. The ${readsocket expansion item now supports Internet domain sockets as well
+   as Unix domain sockets. If the first argument begins "inet:", it must be of
+   the form "inet:host:port". The port is mandatory; it may be a number or the
+   name of a TCP port in /etc/services. The host may be a name, or it may be an
+   IP address. An ip address may optionally be enclosed in square brackets.
+   This is best for IPv6 addresses. For example:
+
+     ${readsocket{inet:[::1]:1234}{<request data>}...
+
+   Only a single host name may be given, but if looking it up yield more than
+   one IP address, they are each tried in turn until a connection is made. Once
+   a connection has been made, the behaviour is as for ${readsocket with a Unix
+   domain socket.
+
+
 Version 4.61
 ------------
 
index 3b50363..4cd98f7 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/expand.c,v 1.57 2006/03/08 11:13:07 ph10 Exp $ */
+/* $Cambridge: exim/src/src/expand.c,v 1.58 2006/04/18 11:13:19 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -3655,28 +3655,148 @@ while (*s != 0)
         }
       else sub_arg[3] = NULL;                     /* No eol if no timeout */
 
-      /* If skipping, we don't actually do anything */
+      /* If skipping, we don't actually do anything. Otherwise, arrange to
+      connect to either an IP or a Unix socket. */
 
       if (!skipping)
         {
-        /* Make a connection to the socket */
+        /* Handle an IP (internet) domain */
 
-        if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+        if (strncmp(sub_arg[0], "inet:", 5) == 0)
           {
-          expand_string_message = string_sprintf("failed to create socket: %s",
-            strerror(errno));
-          goto SOCK_FAIL;
+          BOOL connected = FALSE;
+          int namelen, port;
+          host_item shost;
+          host_item *h;
+          uschar *server_name = sub_arg[0] + 5;
+          uschar *port_name = Ustrrchr(server_name, ':');
+
+          /* Sort out the port */
+
+          if (port_name == NULL)
+            {
+            expand_string_message =
+              string_sprintf("missing port for readsocket %s", sub_arg[0]);
+            goto EXPAND_FAILED;
+            }
+          *port_name++ = 0;           /* Terminate server name */
+
+          if (isdigit(*port_name))
+            {
+            uschar *end;
+            port = Ustrtol(port_name, &end, 0);
+            if (end != port_name + Ustrlen(port_name))
+              {
+              expand_string_message =
+                string_sprintf("invalid port number %s", port_name);
+              goto EXPAND_FAILED;
+              }
+            }
+          else
+            {
+            struct servent *service_info = getservbyname(CS port_name, "tcp");
+            if (service_info == NULL)
+              {
+              expand_string_message = string_sprintf("unknown port \"%s\"",
+                port_name);
+              goto EXPAND_FAILED;
+              }
+            port = ntohs(service_info->s_port);
+            }
+
+          /* Sort out the server. */
+
+          shost.next = NULL;
+          shost.address = NULL;
+          shost.port = port;
+          shost.mx = -1;
+
+          namelen = Ustrlen(server_name);
+
+          /* Anything enclosed in [] must be an IP address. */
+
+          if (server_name[0] == '[' &&
+              server_name[namelen - 1] == ']')
+            {
+            server_name[namelen - 1] = 0;
+            server_name++;
+            if (string_is_ip_address(server_name, NULL) == 0)
+              {
+              expand_string_message =
+                string_sprintf("malformed IP address \"%s\"", server_name);
+              goto EXPAND_FAILED;
+              }
+            shost.name = shost.address = server_name;
+            }
+
+          /* Otherwise check for an unadorned IP address */
+
+          else if (string_is_ip_address(server_name, NULL) != 0)
+            shost.name = shost.address = server_name;
+
+          /* Otherwise lookup IP address(es) from the name */
+
+          else
+            {
+            shost.name = server_name;
+            if (host_find_byname(&shost, NULL, NULL, FALSE) != HOST_FOUND)
+              {
+              expand_string_message =
+                string_sprintf("no IP address found for host %s", shost.name);
+              goto EXPAND_FAILED;
+              }
+            }
+
+          /* Try to connect to the server - test each IP till one works */
+
+          for (h = &shost; h != NULL; h = h->next)
+            {
+            int af = (Ustrchr(h->address, ':') != 0)? AF_INET6 : AF_INET;
+            if ((fd = ip_socket(SOCK_STREAM, af)) == -1)
+              {
+              expand_string_message = string_sprintf("failed to create socket: "
+                "%s", strerror(errno));
+              goto SOCK_FAIL;
+              }
+
+            if (ip_connect(fd, af, h->address, port, timeout) == 0)
+              {
+              connected = TRUE;
+              break;
+              }
+            }
+
+          if (!connected)
+            {
+            expand_string_message = string_sprintf("failed to connect to "
+              "socket %s: couldn't connect to any host", sub_arg[0],
+              strerror(errno));
+            goto SOCK_FAIL;
+            }
           }
 
-        sockun.sun_family = AF_UNIX;
-        sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
-          sub_arg[0]);
-        if(connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun)) == -1)
+        /* Handle a Unix domain socket */
+
+        else
           {
-          expand_string_message = string_sprintf("failed to connect to socket "
-            "%s: %s", sub_arg[0], strerror(errno));
-          goto SOCK_FAIL;
+          if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+            {
+            expand_string_message = string_sprintf("failed to create socket: %s",
+              strerror(errno));
+            goto SOCK_FAIL;
+            }
+
+          sockun.sun_family = AF_UNIX;
+          sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
+            sub_arg[0]);
+          if(connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun)) == -1)
+            {
+            expand_string_message = string_sprintf("failed to connect to socket "
+              "%s: %s", sub_arg[0], strerror(errno));
+            goto SOCK_FAIL;
+            }
           }
+
         DEBUG(D_expand) debug_printf("connected to socket %s\n", sub_arg[0]);
 
         /* Write the request string, if not empty */
@@ -3710,7 +3830,7 @@ while (*s != 0)
         if (sigalrm_seen)
           {
           ptr = save_ptr;
-          expand_string_message = US"socket read timed out";
+          expand_string_message = US "socket read timed out";
           goto SOCK_FAIL;
           }
         }
diff --git a/test/confs/1010 b/test/confs/1010
new file mode 100644 (file)
index 0000000..4a4929b
--- /dev/null
@@ -0,0 +1,27 @@
+# Exim test configuration 1010
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+rfc1413_query_timeout = 0s
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+domainlist local_domains = test.ex : *.test.ex
+acl_smtp_connect = connect
+trusted_users = CALLER
+
+
+# ----- ACL -----
+
+begin acl
+
+connect:
+  deny condition = ${readsocket{DIR/test-socket}{QUERY-ACL\n}{2s}{*EOL*}}
+  accept
+
+# End
index 534f3a7..8ac3536 100644 (file)
@@ -1,4 +1,4 @@
-; $Cambridge: exim/test/dnszones-src/db.test.ex,v 1.3 2006/02/20 16:25:00 ph10 Exp $
+; $Cambridge: exim/test/dnszones-src/db.test.ex,v 1.4 2006/04/18 11:13:19 ph10 Exp $
 
 ; This is a testing zone file for use when testing DNS handling in Exim. This
 ; is a fake zone of no real use - hence no SOA record. The zone name is
@@ -48,6 +48,16 @@ mx.π        A       V4NET.255.255.255
 
 thishost     A       127.0.0.1
 
+; Something that gives both the IP and the loopback
+
+thisloop     A       HOSTIPV4
+             A       127.0.0.1
+
+; Something that gives an unreachable IP and the loopback
+
+badloop      A       V4NET.0.0.1
+             A       127.0.0.1
+
 ; Another host with both A and AAAA records
 
 46           A       V4NET.0.0.4
index a20e79e..d5e2cb1 100644 (file)
@@ -1,4 +1,4 @@
-# ${readsocket
+# ${readsocket (Unix domain and IPv4)
 need_ipv4
 #
 exim -be
@@ -55,3 +55,47 @@ QUERY-ACL
 exim -odq -bs -oMa V4NET.0.0.0
 quit
 ****
+#
+# Tests of IPv4 sockets
+#
+server PORT_S 10
+QUERY-1
+>LF>ANSWER-1
+>*eof
+QUERY-2
+>>ANSWER-2
+>*eof
+QUERY-3
+>LF>ANSWER-3
+>*eof
+QUERY-4
+>LF>ANSWER-4
+>*eof
+>>ANSWER-5
+>*eof
+*sleep 1
+>*eof
+>*eof
+QUERY-8
+*sleep 2
+*eof
+QUERY-9
+*sleep 2
+*eof
+QUERY-10
+>LF>ANSWER-10
+>*eof
+****
+millisleep 500
+exim -be
+1 >>${readsocket{inet:thisloop:PORT_S}{QUERY-1\n}}<<
+2 >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-2\n}}<<
+3 >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-3\n}{2s}{*EOL*}}<<
+4 >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-4\n}{2s}{*EOL*}{sock error}}<<
+5 >>${readsocket{inet:127.0.0.1:PORT_S}{}}<<
+6 >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-6\n}}<<
+7 >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-7\n}{1s}{}{sock error}}<<
+8 >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-8\n}{1s}}<<
+9 >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-9\n}{1s}{}{sock error}}<<
+10 >>${readsocket{inet:badloop:PORT_S}{QUERY-10\n}}<<
+****
diff --git a/test/scripts/1000-Basic-ipv6/1010 b/test/scripts/1000-Basic-ipv6/1010
new file mode 100644 (file)
index 0000000..720c0cf
--- /dev/null
@@ -0,0 +1,43 @@
+# ${readsocket (IPv6)
+#
+# Note the difference between:
+#   >*eof      => close the connection
+#   *eof       => expect to read EOF from client
+#
+server PORT_S 9
+QUERY-1
+>LF>ANSWER-1
+>*eof
+QUERY-2
+>>ANSWER-2
+>*eof
+QUERY-3
+>LF>ANSWER-3
+>*eof
+QUERY-4
+>LF>ANSWER-4
+>*eof
+>>ANSWER-5
+>*eof
+*sleep 1
+>*eof
+>*eof
+QUERY-8
+*sleep 2
+*eof
+QUERY-9
+*sleep 2
+*eof
+****
+millisleep 500
+exim -be
+1 >>${readsocket{inet:[::1]:PORT_S}{QUERY-1\n}}<<
+2 >>${readsocket{inet:[::1]:PORT_S}{QUERY-2\n}}<<
+3 >>${readsocket{inet:[::1]:PORT_S}{QUERY-3\n}{2s}{*EOL*}}<<
+4 >>${readsocket{inet:[::1]:PORT_S}{QUERY-4\n}{2s}{*EOL*}{sock error}}<<
+5 >>${readsocket{inet:[::1]:PORT_S}{}}<<
+6 >>${readsocket{inet:[::1]:PORT_S}{QUERY-6\n}}<<
+7 >>${readsocket{inet:[::1]:PORT_S}{QUERY-7\n}{1s}{}{sock error}}<<
+8 >>${readsocket{inet:[::1]:PORT_S}{QUERY-8\n}{1s}}<<
+9 >>${readsocket{inet:[::1]:PORT_S}{QUERY-9\n}{1s}{}{sock error}}<<
+****
index 6cb4425..dabc1f5 100644 (file)
 > 9 >>sock error<<
 > 
 451 Temporary local problem - please try later\r
+> 1 >>ANSWER-1
+<<
+> 2 >>ANSWER-2<<
+> 3 >>ANSWER-3*EOL*<<
+> 4 >>ANSWER-4*EOL*<<
+> 5 >>ANSWER-5<<
+> 6 >><<
+> 7 >><<
+> Failed: socket read timed out
+> 9 >>sock error<<
+> 10 >>ANSWER-10
+<<
+> 
 
 ******** SERVER ********
 Listening on TESTSUITE/test-socket ... 
@@ -61,3 +74,50 @@ Connection request
 QUERY-ACL
 *sleep 3
 End of script
+Listening on port 1224 ... 
+Connection request from [ip4.ip4.ip4.ip4]
+QUERY-1
+>LF>ANSWER-1
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-2
+>>ANSWER-2
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-3
+>LF>ANSWER-3
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-4
+>LF>ANSWER-4
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+>>ANSWER-5
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+*sleep 1
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-8
+*sleep 2
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-9
+*sleep 2
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-10
+>LF>ANSWER-10
+>*eof
+End of script
diff --git a/test/stdout/1010 b/test/stdout/1010
new file mode 100644 (file)
index 0000000..cacc154
--- /dev/null
@@ -0,0 +1,55 @@
+> 1 >>ANSWER-1
+<<
+> 2 >>ANSWER-2<<
+> 3 >>ANSWER-3*EOL*<<
+> 4 >>ANSWER-4*EOL*<<
+> 5 >>ANSWER-5<<
+> 6 >><<
+> 7 >><<
+> Failed: socket read timed out
+> 9 >>sock error<<
+> 
+
+******** SERVER ********
+Listening on port 1224 ... 
+Connection request from [::1]
+QUERY-1
+>LF>ANSWER-1
+>*eof
+Listening on port 1224 ... 
+Connection request from [::1]
+QUERY-2
+>>ANSWER-2
+>*eof
+Listening on port 1224 ... 
+Connection request from [::1]
+QUERY-3
+>LF>ANSWER-3
+>*eof
+Listening on port 1224 ... 
+Connection request from [::1]
+QUERY-4
+>LF>ANSWER-4
+>*eof
+Listening on port 1224 ... 
+Connection request from [::1]
+>>ANSWER-5
+>*eof
+Listening on port 1224 ... 
+Connection request from [::1]
+*sleep 1
+>*eof
+Listening on port 1224 ... 
+Connection request from [::1]
+>*eof
+Listening on port 1224 ... 
+Connection request from [::1]
+QUERY-8
+*sleep 2
+Expected EOF read from client
+Listening on port 1224 ... 
+Connection request from [::1]
+QUERY-9
+*sleep 2
+Expected EOF read from client
+End of script