From 1cce3af8c29cfa691a3e28c79227f358e5a7b3b9 Mon Sep 17 00:00:00 2001 From: Philip Hazel Date: Tue, 18 Apr 2006 11:13:19 +0000 Subject: [PATCH] Extend ${readsocket to TCP sockets (modified John Jetmore's patch). --- doc/doc-txt/ChangeLog | 6 +- doc/doc-txt/NewStuff | 20 +++- src/src/expand.c | 150 +++++++++++++++++++++++++++--- test/confs/1010 | 27 ++++++ test/dnszones-src/db.test.ex | 12 ++- test/scripts/0000-Basic/0373 | 46 ++++++++- test/scripts/1000-Basic-ipv6/1010 | 43 +++++++++ test/stdout/0373 | 60 ++++++++++++ test/stdout/1010 | 55 +++++++++++ 9 files changed, 400 insertions(+), 19 deletions(-) create mode 100644 test/confs/1010 create mode 100644 test/scripts/1000-Basic-ipv6/1010 create mode 100644 test/stdout/1010 diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index ca221b4e8..d69e9eafb 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -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 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index c4763713e..38c20991b 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -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}{}... + + 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 ------------ diff --git a/src/src/expand.c b/src/src/expand.c index 3b5036345..4cd98f7d4 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -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 index 000000000..4a4929bcf --- /dev/null +++ b/test/confs/1010 @@ -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 diff --git a/test/dnszones-src/db.test.ex b/test/dnszones-src/db.test.ex index 534f3a71f..8ac35367e 100644 --- a/test/dnszones-src/db.test.ex +++ b/test/dnszones-src/db.test.ex @@ -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 diff --git a/test/scripts/0000-Basic/0373 b/test/scripts/0000-Basic/0373 index a20e79ecd..d5e2cb182 100644 --- a/test/scripts/0000-Basic/0373 +++ b/test/scripts/0000-Basic/0373 @@ -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 index 000000000..720c0cffb --- /dev/null +++ b/test/scripts/1000-Basic-ipv6/1010 @@ -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}}<< +**** diff --git a/test/stdout/0373 b/test/stdout/0373 index 6cb4425eb..dabc1f5a7 100644 --- a/test/stdout/0373 +++ b/test/stdout/0373 @@ -13,6 +13,19 @@ > 9 >>sock error<< > 451 Temporary local problem - please try later +> 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 index 000000000..cacc1541b --- /dev/null +++ b/test/stdout/1010 @@ -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 -- 2.25.1