readsocket expansion: response caching
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 18 Apr 2020 14:36:54 +0000 (15:36 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Wed, 22 Apr 2020 18:27:27 +0000 (19:27 +0100)
19 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/scripts/MakeLinks
src/scripts/lookups-Makefile
src/src/drtables.c
src/src/expand.c
src/src/functions.h
src/src/lookups/Makefile
src/src/lookups/readsock.c [new file with mode: 0644]
src/src/search.c
test/confs/0373
test/log/0373
test/rejectlog/0373
test/scripts/0000-Basic/0373
test/stderr/2200
test/stderr/2201
test/stderr/2610
test/stderr/2620
test/stdout/0373

index c54437181b2bc1bfd83c2109fd4bf07f54b88550..8702485703c279d78f21cf6e86aa02bd77dcd4d5 100644 (file)
@@ -10285,21 +10285,37 @@ ${readsocket{/socket/name}{request string}{3s}}
 .endd
 
 The third argument is a list of options, of which the first element is the timeout
 .endd
 
 The third argument is a list of options, of which the first element is the timeout
-and must be present if the argument is given.
+and must be present if any options are given.
 Further elements are options of form &'name=value'&.
 Further elements are options of form &'name=value'&.
-Two option types is currently recognised: shutdown and tls.
-The first defines whether (the default)
-or not a shutdown is done on the connection after sending the request.
-Example, to not do so (preferred, eg. by some webservers):
+Example:
 .code
 ${readsocket{/socket/name}{request string}{3s:shutdown=no}}
 .endd
 .code
 ${readsocket{/socket/name}{request string}{3s:shutdown=no}}
 .endd
-The second, tls, controls the use of TLS on the connection.  Example:
-.code
-${readsocket{/socket/name}{request string}{3s:tls=yes}}
-.endd
-The default is to not use TLS.
+
+.new
+The following option names are recognised:
+.ilist
+&*cache*&
+Defines if the result data can be cached for use by a later identical
+request in the same process.
+Values are &"yes"& or &"no"& (the default).
+If not, all cached results for this connection specification
+will be invalidated.
+
+.next
+&*shutdown*&
+Defines whether or not a write-shutdown is done on the connection after
+sending the request. Values are &"yes"& (the default) or &"no"&
+(preferred, eg. by some webservers).
+
+.next
+&*tls*&
+Controls the use of TLS on the connection.
+Values are &"yes"& or &"no"& (the default).
 If it is enabled, a shutdown as descripbed above is never done.
 If it is enabled, a shutdown as descripbed above is never done.
+.endlist
+.wen
+
 
 A fourth argument allows you to change any newlines that are in the data
 that is read, in the same way as for &%readfile%& (see above). This example
 
 A fourth argument allows you to change any newlines that are in the data
 that is read, in the same way as for &%readfile%& (see above). This example
index f922f2cf4c226e27362f77e907eed96c5db8c0fd..4ae49c2faa72122f2dc3177b4b32c2d7ff059ede 100644 (file)
@@ -54,7 +54,10 @@ Version 4.94
 14. Options on pgsql and mysql lookups, to specify server separate from the
     lookup string.
 
 14. Options on pgsql and mysql lookups, to specify server separate from the
     lookup string.
 
-15. Expansion item ${listquote {<char} {<item>}}
+15. Expansion item ${listquote {<char} {<item>}}.
+
+16. An option for the ${readsocket {}{}{}} expansion to make the result data
+    cacheable.
 
 
 
 
 
 
index 14fdb00005ed2330a0b1d67076fb8fab8ec409ae..277a1c0268b48e70238c44c52573caa8f3835e18 100755 (executable)
@@ -31,8 +31,8 @@ mkdir lookups
 cd lookups
 # Makefile is generated
 for f in README cdb.c dbmdb.c dnsdb.c dsearch.c ibase.c json.c ldap.h ldap.c \
 cd lookups
 # Makefile is generated
 for f in README cdb.c dbmdb.c dnsdb.c dsearch.c ibase.c json.c ldap.h ldap.c \
-  lmdb.c lsearch.c mysql.c redis.c nis.c nisplus.c oracle.c passwd.c \
-  pgsql.c spf.c sqlite.c testdb.c whoson.c \
+  lmdb.c lsearch.c mysql.c nis.c nisplus.c oracle.c passwd.c \
+  pgsql.c readsock.c redis.c spf.c sqlite.c testdb.c whoson.c \
   lf_functions.h lf_check_file.c lf_quote.c lf_sqlperform.c
 do
   ln -s ../../src/lookups/$f $f
   lf_functions.h lf_check_file.c lf_quote.c lf_sqlperform.c
 do
   ln -s ../../src/lookups/$f $f
index 498184ff6eea9f73d03c5f45586d120a22428559..63f3eb86e6d71899badb99577ce6c6ca7afb7681 100755 (executable)
@@ -182,6 +182,9 @@ then
   OBJ="${OBJ} lmdb.o"
 fi
 
   OBJ="${OBJ} lmdb.o"
 fi
 
+# readsock is always wanted as it implements the ${readsock } expansion
+OBJ="${OBJ} readsock.o"
+
 echo "MODS = $MODS"
 echo "OBJ = $OBJ"
 
 echo "MODS = $MODS"
 echo "OBJ = $OBJ"
 
index 363c07bf49c7c6a17a0706a5b16a49e2c35f1394..7ecca505629bfba639dd18cd5eb191260e52f00e 100644 (file)
@@ -617,6 +617,8 @@ extern lookup_module_info testdb_lookup_module_info;
 extern lookup_module_info whoson_lookup_module_info;
 #endif
 
 extern lookup_module_info whoson_lookup_module_info;
 #endif
 
+extern lookup_module_info readsock_lookup_module_info;
+
 
 void
 init_lookup_list(void)
 
 void
 init_lookup_list(void)
@@ -715,6 +717,8 @@ addlookupmodule(NULL, &testdb_lookup_module_info);
 addlookupmodule(NULL, &whoson_lookup_module_info);
 #endif
 
 addlookupmodule(NULL, &whoson_lookup_module_info);
 #endif
 
+addlookupmodule(NULL, &readsock_lookup_module_info);
+
 #ifdef LOOKUP_MODULE_DIR
 if (!(dd = exim_opendir(LOOKUP_MODULE_DIR)))
   {
 #ifdef LOOKUP_MODULE_DIR
 if (!(dd = exim_opendir(LOOKUP_MODULE_DIR)))
   {
index cdc914f5e13c31a6789b5d4f0327d427410b1235..5ae74ef52c3555a92f437b260aa041d70eaf3d75 100644 (file)
@@ -3927,7 +3927,7 @@ Arguments:
 Returns:       new pointer for expandable string, terminated if non-null
 */
 
 Returns:       new pointer for expandable string, terminated if non-null
 */
 
-static gstring *
+gstring *
 cat_file(FILE *f, gstring *yield, uschar *eol)
 {
 uschar buffer[1024];
 cat_file(FILE *f, gstring *yield, uschar *eol)
 {
 uschar buffer[1024];
@@ -3947,7 +3947,7 @@ return yield;
 
 
 #ifndef DISABLE_TLS
 
 
 #ifndef DISABLE_TLS
-static gstring *
+gstring *
 cat_file_tls(void * tls_ctx, gstring * yield, uschar * eol)
 {
 int rc;
 cat_file_tls(void * tls_ctx, gstring * yield, uschar * eol)
 {
 int rc;
@@ -5286,7 +5286,6 @@ while (*s != 0)
       host_item host;
       BOOL do_shutdown = TRUE;
       BOOL do_tls = FALSE;     /* Only set under ! DISABLE_TLS */
       host_item host;
       BOOL do_shutdown = TRUE;
       BOOL do_tls = FALSE;     /* Only set under ! DISABLE_TLS */
-      blob reqstr;
 
       if (expand_forbid & RDO_READSOCK)
         {
 
       if (expand_forbid & RDO_READSOCK)
         {
@@ -5304,218 +5303,77 @@ while (*s != 0)
         case 3: goto EXPAND_FAILED;
         }
 
         case 3: goto EXPAND_FAILED;
         }
 
-      /* Grab the request string, if any */
-
-      reqstr.data = sub_arg[1];
-      reqstr.len = Ustrlen(sub_arg[1]);
-
-      /* Sort out timeout, if given.  The second arg is a list with the first element
-      being a time value.  Any more are options of form "name=value".  Currently the
-      only options recognised are "shutdown" and "tls". */
-
-      if (sub_arg[2])
-        {
-       const uschar * list = sub_arg[2];
-       uschar * item;
-       int sep = 0;
-
-       if (  !(item = string_nextinlist(&list, &sep, NULL, 0))
-          || !*item
-          || (timeout = readconf_readtime(item, 0, FALSE)) < 0)
-          {
-          expand_string_message = string_sprintf("bad time value %s", item);
-          goto EXPAND_FAILED;
-          }
-
-       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; }
-#ifndef DISABLE_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 */
-
       /* If skipping, we don't actually do anything. Otherwise, arrange to
       connect to either an IP or a Unix socket. */
 
       if (!skipping)
         {
       /* If skipping, we don't actually do anything. Otherwise, arrange to
       connect to either an IP or a Unix socket. */
 
       if (!skipping)
         {
-        /* Handle an IP (internet) domain */
-
-        if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
-          {
-          int port;
-          uschar * port_name;
+       int stype = search_findtype(US"readsock", 8);
+       gstring * g = NULL;
+       void * handle;
+       int expand_setup = -1;
+       uschar * s;
 
 
-          server_name = sub_arg[0] + 5;
-          port_name = Ustrrchr(server_name, ':');
+       /* If the reqstr is empty, flag that and set a dummy */
 
 
-          /* Sort out the port */
+       if (!sub_arg[1][0])
+         {
+         g = string_append_listele(g, ',', US"send=no");
+         sub_arg[1] = US"DUMMY";
+         }
 
 
-          if (!port_name)
-            {
-            expand_string_message =
-              string_sprintf("missing port for readsocket %s", sub_arg[0]);
-            goto EXPAND_FAILED;
-            }
-          *port_name++ = 0;           /* Terminate server name */
+       /* Re-marshall the options */
 
 
-          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)
-              {
-              expand_string_message = string_sprintf("unknown port \"%s\"",
-                port_name);
-              goto EXPAND_FAILED;
-              }
-            port = ntohs(service_info->s_port);
-            }
+       if (sub_arg[2])
+         {
+         const uschar * list = sub_arg[2];
+         uschar * item;
+         int sep = 0;
 
 
-         /*XXX we trust that the request is idempotent for TFO.  Hmm. */
-         cctx.sock = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
-                 timeout, &host, &expand_string_message,
-                 do_tls ? NULL : &reqstr);
-         callout_address = NULL;
-         if (cctx.sock < 0)
-           goto SOCK_FAIL;
-         if (!do_tls)
-           reqstr.len = 0;
-          }
+         /* First option has no tag and is timeout */
+         if ((item = string_nextinlist(&list, &sep, NULL, 0)))
+           g = string_append_listele(g, ',',
+                 string_sprintf("timeout=%s", item));
 
 
-        /* Handle a Unix domain socket */
+         /* The rest of the options from the expansion */
+         while ((item = string_nextinlist(&list, &sep, NULL, 0)))
+           g = string_append_listele(g, ',', item);
 
 
-        else
-          {
-         struct sockaddr_un sockun;         /* don't call this "sun" ! */
-          int rc;
+         /* possibly plus an EOL string */
+         if (sub_arg[3] && *sub_arg[3])
+           g = string_append_listele(g, ',',
+                 string_sprintf("eol=%s", sub_arg[3]));
 
 
-          if ((cctx.sock = 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]);
-         server_name = US sockun.sun_path;
-
-          sigalrm_seen = FALSE;
-          ALARM(timeout);
-          rc = connect(cctx.sock, (struct sockaddr *)(&sockun), sizeof(sockun));
-          ALARM_CLR(0);
-          if (sigalrm_seen)
-            {
-            expand_string_message = US "socket connect timed out";
-            goto SOCK_FAIL;
-            }
-          if (rc < 0)
-            {
-            expand_string_message = string_sprintf("failed to connect to socket "
-              "%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]);
+       /* Gat a (possibly cached) handle for the connection */
 
 
-#ifndef DISABLE_TLS
-       if (do_tls)
+       if (!(handle = search_open(sub_arg[0], stype, 0, NULL, NULL)))
          {
          {
-         smtp_connect_args conn_args = {.host = &host };
-         tls_support tls_dummy = {.sni=NULL};
-         uschar * 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;
-           }
+         if (*expand_string_message) goto EXPAND_FAILED;
+         expand_string_message = search_error_message;
+         search_error_message = NULL;
+         goto SOCK_FAIL;
          }
          }
-#endif
-
-       /* Allow sequencing of test actions */
-       testharness_pause_ms(100);
-
-        /* Write the request string, if not empty or already done */
-
-        if (reqstr.len)
-          {
-          DEBUG(D_expand) debug_printf_indent("writing \"%s\" to socket\n",
-            reqstr.data);
-          if ( (
-#ifndef DISABLE_TLS
-             do_tls ? tls_write(cctx.tls_ctx, reqstr.data, reqstr.len, FALSE) :
-#endif
-                       write(cctx.sock, reqstr.data, reqstr.len)) != reqstr.len)
-            {
-            expand_string_message = string_sprintf("request write to socket "
-              "failed: %s", strerror(errno));
-            goto SOCK_FAIL;
-            }
-          }
-
-        /* Shut down the sending side of the socket. This helps some servers to
-        recognise that it is their turn to do some work. Just in case some
-        system doesn't have this function, make it conditional. */
 
 
-#ifdef SHUT_WR
-       if (!do_tls && do_shutdown) shutdown(cctx.sock, SHUT_WR);
-#endif
-
-       testharness_pause_ms(100);
-
-        /* Now we need to read from the socket, under a timeout. The function
-        that reads a file can be used. */
-
-       if (!do_tls)
-         fp = fdopen(cctx.sock, "rb");
-        sigalrm_seen = FALSE;
-        ALARM(timeout);
-        yield =
-#ifndef DISABLE_TLS
-         do_tls ? cat_file_tls(cctx.tls_ctx, yield, sub_arg[3]) :
-#endif
-                   cat_file(fp, yield, sub_arg[3]);
-        ALARM_CLR(0);
+       /* Get (possibly cached) results for the lookup */
+       /* sspec: sub_arg[0]  req: sub_arg[1]  opts: g */
 
 
-#ifndef DISABLE_TLS
-       if (do_tls)
+       if ((s = search_find(handle, sub_arg[0], sub_arg[1], -1, NULL, 0, 0,
+                                   &expand_setup, string_from_gstring(g))))
+         yield = string_cat(yield, s);
+       else if (f.search_find_defer)
          {
          {
-         tls_close(cctx.tls_ctx, TRUE);
-         close(cctx.sock);
+         expand_string_message = search_error_message;
+         search_error_message = NULL;
+         goto SOCK_FAIL;
          }
        else
          }
        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. */
-
-        if (sigalrm_seen)
-          {
-          if (yield) yield->ptr = save_ptr;
-          expand_string_message = US "socket read timed out";
-          goto SOCK_FAIL;
-          }
+         {     /* should not happen, at present */
+         expand_string_message = search_error_message;
+         search_error_message = NULL;
+         goto SOCK_FAIL;
+         }
         }
 
       /* The whole thing has worked (or we were skipping). If there is a
         }
 
       /* The whole thing has worked (or we were skipping). If there is a
index 8206c480e397950ac448682058bb2abcbaad1596..8ea5e4ef629e041f5e914804d77b7bbd77744428 100644 (file)
@@ -149,6 +149,8 @@ extern void    bits_clear(unsigned int *, size_t, int *);
 extern void    bits_set(unsigned int *, size_t, int *);
 
 extern void    cancel_cutthrough_connection(BOOL, const uschar *);
 extern void    bits_set(unsigned int *, size_t, int *);
 
 extern void    cancel_cutthrough_connection(BOOL, const uschar *);
+extern gstring *cat_file(FILE *, gstring *, uschar *);
+extern gstring *cat_file_tls(void *, gstring *, uschar *);
 extern int     check_host(void *, const uschar *, const uschar **, uschar **);
 extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
 extern pid_t   child_open_exim_function(int *, const uschar *);
 extern int     check_host(void *, const uschar *, const uschar **, uschar **);
 extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
 extern pid_t   child_open_exim_function(int *, const uschar *);
index 01910d542ed5ac2f0d999fea283d995a6edc675c..9231d666cbb832dcc1ac24217d857e968d8233f3 100644 (file)
@@ -43,6 +43,7 @@ nisplus.o:       $(PHDRS) nisplus.c
 oracle.o:        $(PHDRS) oracle.c
 passwd.o:        $(PHDRS) passwd.c
 pgsql.o:         $(PHDRS) pgsql.c
 oracle.o:        $(PHDRS) oracle.c
 passwd.o:        $(PHDRS) passwd.c
 pgsql.o:         $(PHDRS) pgsql.c
+readsock.o:      $(PHDRS) readsock.c
 redis.o:         $(PHDRS) redis.c
 spf.o:           $(PHDRS) spf.c
 sqlite.o:        $(PHDRS) sqlite.c
 redis.o:         $(PHDRS) redis.c
 spf.o:           $(PHDRS) spf.c
 sqlite.o:        $(PHDRS) sqlite.c
diff --git a/src/src/lookups/readsock.c b/src/src/lookups/readsock.c
new file mode 100644 (file)
index 0000000..c2088b7
--- /dev/null
@@ -0,0 +1,319 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "lf_functions.h"
+
+
+static int
+internal_readsock_open(client_conn_ctx * cctx, const uschar * sspec,
+  int timeout, BOOL do_tls, uschar ** errmsg)
+{
+int sep = ',';
+uschar * ele;
+const uschar * server_name;
+host_item host;
+
+if (Ustrncmp(sspec, "inet:", 5) == 0)
+  {
+  int port;
+  uschar * port_name;
+
+  DEBUG(D_lookup)
+    debug_printf_indent("  new inet socket needed for readsocket\n");
+
+  server_name = sspec + 5;
+  port_name = Ustrrchr(server_name, ':');
+
+  /* Sort out the port */
+
+  if (!port_name)
+    {
+    /* expand_string_message results in an EXPAND_FAIL, from our
+    only caller.  Lack of it gets a SOCK_FAIL; we feed back via errmsg
+    for that, which gets copied to search_error_message. */
+
+    expand_string_message =
+      string_sprintf("missing port for readsocket %s", sspec);
+    return FAIL;
+    }
+  *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);
+      return FAIL;
+      }
+    }
+  else
+    {
+    struct servent *service_info = getservbyname(CS port_name, "tcp");
+    if (!service_info)
+      {
+      expand_string_message = string_sprintf("unknown port \"%s\"",
+       port_name);
+      return FAIL;
+      }
+    port = ntohs(service_info->s_port);
+    }
+
+  /* Not having the request-string here in the open routine means
+  that we cannot do TFO; a pity */
+
+  cctx->sock = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+         timeout, &host, errmsg, NULL);
+  callout_address = NULL;
+  if (cctx->sock < 0)
+    return FAIL;
+  }
+
+else
+  {
+  struct sockaddr_un sockun;         /* don't call this "sun" ! */
+  int rc;
+
+  DEBUG(D_lookup)
+    debug_printf_indent("  new unix socket needed for readsocket\n");
+
+  if ((cctx->sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+    {
+    *errmsg = string_sprintf("failed to create socket: %s", strerror(errno));
+    return FAIL;
+    }
+
+  sockun.sun_family = AF_UNIX;
+  sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
+    sspec);
+  server_name = US sockun.sun_path;
+
+  sigalrm_seen = FALSE;
+  ALARM(timeout);
+  rc = connect(cctx->sock, (struct sockaddr *)(&sockun), sizeof(sockun));
+  ALARM_CLR(0);
+  if (sigalrm_seen)
+    {
+    *errmsg = US "socket connect timed out";
+    goto bad;
+    }
+  if (rc < 0)
+    {
+    *errmsg = string_sprintf("failed to connect to socket "
+      "%s: %s", sspec, strerror(errno));
+    goto bad;
+    }
+  host.name = server_name;
+  host.address = US"";
+  }
+
+#ifndef DISABLE_TLS
+if (do_tls)
+  {
+  smtp_connect_args conn_args = {.host = &host };
+  tls_support tls_dummy = {.sni=NULL};
+  uschar * errstr;
+
+  if (!tls_client_start(cctx, &conn_args, NULL, &tls_dummy, &errstr))
+    {
+    *errmsg = string_sprintf("TLS connect failed: %s", errstr);
+    goto bad;
+    }
+  }
+#endif
+
+DEBUG(D_expand|D_lookup) debug_printf_indent("  connected to socket %s\n", sspec);
+return OK;
+
+bad:
+  close(cctx->sock);
+  return FAIL;
+}
+
+/* All use of allocations will be done against the POOL_SEARCH memory,
+which is freed once by search_tidyup(). */
+
+/*************************************************
+*              Open entry point                  *
+*************************************************/
+
+/* See local README for interface description */
+/* We just create a placeholder record with a closed socket, so
+that connection cacheing at the framework layer works. */
+
+static void *
+readsock_open(const uschar * filename, uschar ** errmsg)
+{
+client_conn_ctx * cctx = store_get(sizeof(*cctx), FALSE);
+cctx->sock = -1;
+cctx->tls_ctx = NULL;
+DEBUG(D_lookup) debug_printf_indent("readsock: allocated context\n");
+return cctx;
+}
+
+
+
+
+
+/*************************************************
+*         Find entry point for lsearch           *
+*************************************************/
+
+/* See local README for interface description */
+
+static int
+readsock_find(void * handle, const uschar * filename, const uschar * keystring,
+  int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+  const uschar * opts)
+{
+client_conn_ctx * cctx = handle;
+int sep = ',';
+struct {
+       BOOL do_shutdown:1;
+       BOOL do_tls:1;
+       BOOL cache:1;
+} lf = {.do_shutdown = TRUE};
+uschar * eol = NULL;
+int timeout = 5;
+FILE * fp;
+gstring * yield;
+int ret = DEFER;
+
+DEBUG(D_lookup) debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n", filename, keystring, length, opts);
+
+/* Parse options */
+
+if (opts) for (uschar * s; s = string_nextinlist(&opts, &sep, NULL, 0); )
+  if (Ustrncmp(s, "timeout=", 8) == 0)
+    timeout = readconf_readtime(s + 8, 0, FALSE);
+  else if (Ustrncmp(s, "shutdown=", 9) == 0)
+    lf.do_shutdown = Ustrcmp(s + 9, "no") != 0;
+#ifndef DISABLE_TLS
+  else if (Ustrncmp(s, "tls=", 4) == 0 && Ustrcmp(s + 4, US"no") != 0)
+    lf.do_tls = TRUE;
+#endif
+  else if (Ustrncmp(s, "eol=", 4) == 0)
+    eol = s + 4;
+  else if (Ustrcmp(s, "cache=yes") == 0)
+    lf.cache = TRUE;
+  else if (Ustrcmp(s, "send=no") == 0)
+    length = 0;
+
+if (!filename) return FAIL;    /* Server spec is required */
+
+/* Open the socket, if not cached */
+
+if (cctx->sock == -1)
+  if (internal_readsock_open(cctx, filename, timeout, lf.do_tls, errmsg) != OK)
+    return ret;
+
+testharness_pause_ms(100);     /* Allow sequencing of test actions */
+
+/* Write the request string, if not empty or already done */
+
+if (length)
+  {
+  if ((
+#ifndef DISABLE_TLS
+      cctx->tls_ctx ? tls_write(cctx->tls_ctx, keystring, length, FALSE) :
+#endif
+                     write(cctx->sock, keystring, length)) != length)
+    {
+    *errmsg = string_sprintf("request write to socket "
+      "failed: %s", strerror(errno));
+    goto out;
+    }
+  }
+
+/* Shut down the sending side of the socket. This helps some servers to
+recognise that it is their turn to do some work. Just in case some
+system doesn't have this function, make it conditional. */
+
+#ifdef SHUT_WR
+if (!cctx->tls_ctx && lf.do_shutdown)
+  shutdown(cctx->sock, SHUT_WR);
+#endif
+
+testharness_pause_ms(100);
+
+/* Now we need to read from the socket, under a timeout. The function
+that reads a file can be used.  If we're using a stdio buffered read,
+and might need later write ops on the socket, the stdio must be in
+writable mode or the underlying socket goes non-writable. */
+
+if (!cctx->tls_ctx)
+  fp = fdopen(cctx->sock, lf.do_shutdown ? "rb" : "wb");
+
+sigalrm_seen = FALSE;
+ALARM(timeout);
+yield =
+#ifndef DISABLE_TLS
+  cctx->tls_ctx ? cat_file_tls(cctx->tls_ctx, NULL, eol) :
+#endif
+                 cat_file(fp, NULL, eol);
+ALARM_CLR(0);
+
+if (sigalrm_seen)
+  { *errmsg = US "socket read timed out"; goto out; }
+
+*result = yield ? string_from_gstring(yield) : US"";
+ret = OK;
+if (!lf.cache) *do_cache = 0;
+
+out:
+
+(void) close(cctx->sock);
+cctx->sock = -1;
+return ret;
+}
+
+
+
+/*************************************************
+*              Close entry point                 *
+*************************************************/
+
+/* See local README for interface description */
+
+static void
+readsock_close(void * handle)
+{
+client_conn_ctx * cctx = handle;
+if (cctx->sock < 0) return;
+#ifndef DISABLE_TLS
+if (cctx->tls_ctx) tls_close(cctx->tls_ctx, TRUE);
+#endif
+close(cctx->sock);
+cctx->sock = -1;
+}
+
+
+
+static lookup_info readsock_lookup_info = {
+  .name = US"readsock",                        /* lookup name */
+  .type = lookup_querystyle,
+  .open = readsock_open,               /* open function */
+  .check = NULL,
+  .find = readsock_find,               /* find function */
+  .close = readsock_close,
+  .tidy = NULL,
+  .quote = NULL,                       /* no quoting function */
+  .version_report = NULL
+};
+
+
+#ifdef DYNLOOKUP
+#define readsock_lookup_module_info _lookup_module_info
+#endif
+
+static lookup_info *_lookup_list[] = { &readsock_lookup_info };
+lookup_module_info readsock_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
+
+/* End of lookups/readsock.c */
index d929322ba0f3808ee2852bdb062fe55093151a53..d3511df31b58999dd2fc84ddfe9aab3ad1373251 100644 (file)
@@ -239,12 +239,10 @@ Returns:     nothing
 static void
 tidyup_subtree(tree_node *t)
 {
 static void
 tidyup_subtree(tree_node *t)
 {
-search_cache *c = (search_cache *)(t->data.ptr);
-if (t->left != NULL) tidyup_subtree(t->left);
-if (t->right != NULL) tidyup_subtree(t->right);
-if (c != NULL &&
-    c->handle != NULL &&
-    lookup_list[c->search_type]->close != NULL)
+search_cache * c = (search_cache *)(t->data.ptr);
+if (t->left)  tidyup_subtree(t->left);
+if (t->right) tidyup_subtree(t->right);
+if (c && c->handle && lookup_list[c->search_type]->close)
   lookup_list[c->search_type]->close(c->handle);
 }
 
   lookup_list[c->search_type]->close(c->handle);
 }
 
@@ -522,7 +520,8 @@ else
   DEBUG(D_lookup)
     {
     if (t)
   DEBUG(D_lookup)
     {
     if (t)
-      debug_printf_indent("cached data found but either wrong opts or dated; ");
+      debug_printf_indent("cached data found but %s; ",
+       e->expiry && e->expiry <= time(NULL) ? "out-of-date" : "wrong opts");
     debug_printf_indent("%s lookup required for %s%s%s\n",
       filename ? US"file" : US"database",
       keystring,
     debug_printf_indent("%s lookup required for %s%s%s\n",
       filename ? US"file" : US"database",
       keystring,
@@ -545,13 +544,10 @@ else
 
   else if (do_cache)
     {
 
   else if (do_cache)
     {
-    if (!t) /* No existing entry.  Create new one. */
+    if (!t)    /* No existing entry.  Create new one. */
       {
       int len = keylength + 1;
       e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len, is_tainted(keystring));
       {
       int len = keylength + 1;
       e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len, is_tainted(keystring));
-      e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
-      e->opts = opts;
-      e->data.ptr = data;
       t = (tree_node *)(e+1);
       memcpy(t->name, keystring, len);
       t->data.ptr = e;
       t = (tree_node *)(e+1);
       memcpy(t->name, keystring, len);
       t->data.ptr = e;
@@ -560,7 +556,7 @@ else
       /* Else previous, out-of-date cache entry.  Update with the */
       /* new result and forget the old one */
     e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
       /* Else previous, out-of-date cache entry.  Update with the */
       /* new result and forget the old one */
     e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
-    e->opts = opts;
+    e->opts = opts ? string_copy(opts) : NULL;
     e->data.ptr = data;
     }
 
     e->data.ptr = data;
     }
 
@@ -570,7 +566,7 @@ else
   else
     {
     DEBUG(D_lookup) debug_printf_indent("lookup forced cache cleanup\n");
   else
     {
     DEBUG(D_lookup) debug_printf_indent("lookup forced cache cleanup\n");
-    c->item_cache = NULL;
+    c->item_cache = NULL;      /* forget all lookups on this connection */
     }
   }
 
     }
   }
 
index 10d64b782511fb491cbe7ce4b91e05992b40cf81..2909d7de09a8d5e2d6b8c1af034b67fcf2af3bc5 100644 (file)
@@ -10,6 +10,8 @@ domainlist local_domains = test.ex : *.test.ex
 acl_smtp_connect = connect
 trusted_users = CALLER
 
 acl_smtp_connect = connect
 trusted_users = CALLER
 
+log_selector = +millisec
+
 
 # ----- ACL -----
 
 
 # ----- ACL -----
 
index 7082f4d53bb5f7f12655142eb850fb2eaab48b30..33bdb386f3e6afb03ff9de67882d3e9b5776efdd 100644 (file)
@@ -1 +1 @@
-1999-03-02 09:44:33 H=[V4NET.0.0.0] U=CALLER temporarily rejected connection in "connect" ACL: failed to expand ACL string "${readsocket{TESTSUITE/test-socket}{QUERY-ACL\n}{2s}{*EOL*}}": socket read timed out
+2017-07-30 18:51:05.712 H=[V4NET.0.0.0] U=CALLER temporarily rejected connection in "connect" ACL: failed to expand ACL string "${readsocket{TESTSUITE/test-socket}{QUERY-ACL\n}{2s}{*EOL*}}": socket read timed out
index 7082f4d53bb5f7f12655142eb850fb2eaab48b30..33bdb386f3e6afb03ff9de67882d3e9b5776efdd 100644 (file)
@@ -1 +1 @@
-1999-03-02 09:44:33 H=[V4NET.0.0.0] U=CALLER temporarily rejected connection in "connect" ACL: failed to expand ACL string "${readsocket{TESTSUITE/test-socket}{QUERY-ACL\n}{2s}{*EOL*}}": socket read timed out
+2017-07-30 18:51:05.712 H=[V4NET.0.0.0] U=CALLER temporarily rejected connection in "connect" ACL: failed to expand ACL string "${readsocket{TESTSUITE/test-socket}{QUERY-ACL\n}{2s}{*EOL*}}": socket read timed out
index 0f63cee9bf0492514a182392ade13bc8ffc3c63f..5d8bbee880eaf3e361246374221cb755e8b68011 100644 (file)
@@ -2,6 +2,7 @@
 need_ipv4
 #
 exim -be
 need_ipv4
 #
 exim -be
+connfail cases (no server)
 1 >>${readsocket{DIR/test-socket}{QUERY-1\n}}<<
 2 ${if exists{DIR/test-socket}\
   {>>${readsocket{DIR/test-socket}{QUERY-1\n}}<<}\
 1 >>${readsocket{DIR/test-socket}{QUERY-1\n}}<<
 2 ${if exists{DIR/test-socket}\
   {>>${readsocket{DIR/test-socket}{QUERY-1\n}}<<}\
@@ -38,6 +39,7 @@ QUERY-9
 ****
 millisleep 500
 exim -be
 ****
 millisleep 500
 exim -be
+unix-socket cases
 1 >>${readsocket{DIR/test-socket}{QUERY-1\n}}<<
 2 >>${readsocket{DIR/test-socket}{QUERY-2\n}}<<
 3 >>${readsocket{DIR/test-socket}{QUERY-3\n}{2s}{*EOL*}}<<
 1 >>${readsocket{DIR/test-socket}{QUERY-1\n}}<<
 2 >>${readsocket{DIR/test-socket}{QUERY-2\n}}<<
 3 >>${readsocket{DIR/test-socket}{QUERY-3\n}{2s}{*EOL*}}<<
@@ -90,19 +92,44 @@ QUERY-10
 ****
 millisleep 500
 exim -be
 ****
 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}}<<
-11 >>${readsocket{inet:thisloop:PORT_S}{QUERY-11\n}{2s:shutdown=no}}<<
+ipv4 cases
+1  ANSWER-1      >>${readsocket{inet:thisloop:PORT_S}{QUERY-1\n}}<<
+2  ANSWER-2      >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-2\n}}<<
+3  ANSWER-3*EOL* >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-3\n}{2s}{*EOL*}}<<
+4  ANSWER-4*EOL* >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-4\n}{2s}{*EOL*}{sock error}}<<
+5  ANSWER-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 read timed out >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-8\n}{1s}}<<
+9  sock error    >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-9\n}{1s}{}{sock error}}<<
+10 ANSWER-10\\n     >>${readsocket{inet:badloop:PORT_S}{QUERY-10\n}}<<
+11 ANSWER-11     >>${readsocket{inet:thisloop:PORT_S}{QUERY-11\n}{2s:shutdown=no}}<<
 ****
 #
 exim -be
 crash-regression-check >>${readsocket{inet:127.0.0.1:PORT_N}{}{}}<<
 ****
 ****
 #
 exim -be
 crash-regression-check >>${readsocket{inet:127.0.0.1:PORT_N}{}{}}<<
 ****
+#
+# Caching of response value
+server DIR/test-socket 3
+QUERY-1
+>LF>ANSWER-1
+>*eof
+QUERY-2
+>LF>ANSWER-2
+>*eof
+QUERY-1
+>LF>ANSWER-1
+>*eof
+****
+millisleep 500
+exim -be
+caching of response value
+1  >>${readsocket{DIR/test-socket}{QUERY-1\n}{5s:cache=yes}}<<
+1+ >>${readsocket{DIR/test-socket}{QUERY-1\n}{5s:cache=yes}}<<
+2  >>${readsocket{DIR/test-socket}{QUERY-2\n}{5s:cache=yes}}<<
+2- >>${readsocket{DIR/test-socket2}{QUERY-2\n}{5s:cache=yes}{}{expected failure}}<<
+1- >>${readsocket{DIR/test-socket2}{QUERY-1\n}{5s:cache=yes}{}{expected failure}}<<
+1+ >>${readsocket{DIR/test-socket}{QUERY-1\n}{5s:cache=yes}}<<
+1- >>${readsocket{DIR/test-socket}{QUERY-1\n}{5s}}<<
+****
index c8320321a27f90554abc41839cbe6bc1f1bf3ebe..ba50dffa7900af1293815530d2475bdab5adb62b 100644 (file)
@@ -43,7 +43,7 @@ search_tidyup called
   LRU list:
   internal_search_find: file="NULL"
     type=dnsdb key="a=shorthost.test.ex" opts=NULL
   LRU list:
   internal_search_find: file="NULL"
     type=dnsdb key="a=shorthost.test.ex" opts=NULL
-  cached data found but either wrong opts or dated;   database lookup required for a=shorthost.test.ex
+  cached data found but out-of-date;   database lookup required for a=shorthost.test.ex
   dnsdb key: shorthost.test.ex
   lookup yielded: 127.0.0.1
 LOG: MAIN
   dnsdb key: shorthost.test.ex
   lookup yielded: 127.0.0.1
 LOG: MAIN
index fb618fc64866f741607ea8bbd408db02f8382dc5..8f9d0b585e1677b6e8097181a02185d95329eca3 100644 (file)
@@ -176,7 +176,7 @@ search_find: file="NULL"
 LRU list:
 internal_search_find: file="NULL"
   type=dnsdb key="a=shorthost.test.ex" opts=NULL
 LRU list:
 internal_search_find: file="NULL"
   type=dnsdb key="a=shorthost.test.ex" opts=NULL
-cached data found but either wrong opts or dated; database lookup required for a=shorthost.test.ex
+cached data found but out-of-date; database lookup required for a=shorthost.test.ex
 dnsdb key: shorthost.test.ex
 lookup yielded: 127.0.0.1
 LOG: MAIN
 dnsdb key: shorthost.test.ex
 lookup yielded: 127.0.0.1
 LOG: MAIN
index 951ddecff75f04970d0157da1d8f49d72d9e6859..b8eda85424aaf86af8d3b44c77e3b54e3e5e9121 100644 (file)
@@ -264,7 +264,7 @@ check set acl_m0 = ok:   ${lookup mysql                    {select name from the
  LRU list:
  internal_search_find: file="NULL"
    type=mysql key="select name from them where id = 'c'" opts="servers=127.0.0.1::1223/test/root/"
  LRU list:
  internal_search_find: file="NULL"
    type=mysql key="select name from them where id = 'c'" opts="servers=127.0.0.1::1223/test/root/"
- cached data found but either wrong opts or dated;  database lookup required for select name from them where id = 'c'
+ cached data found but wrong opts;  database lookup required for select name from them where id = 'c'
  MySQL query: "select name from them where id = 'c'" opts 'servers=127.0.0.1::1223/test/root/'
  MYSQL using cached connection for 127.0.0.1:1223/test/root
  MYSQL: no data found
  MySQL query: "select name from them where id = 'c'" opts 'servers=127.0.0.1::1223/test/root/'
  MYSQL using cached connection for 127.0.0.1:1223/test/root
  MYSQL: no data found
@@ -278,7 +278,7 @@ check set acl_m0 = ok:   ${lookup mysql,servers=127.0.0.1::1223/test/root/
  LRU list:
  internal_search_find: file="NULL"
    type=mysql key="select name from them where id = 'c'" opts="servers=127.0.0.1::1223"
  LRU list:
  internal_search_find: file="NULL"
    type=mysql key="select name from them where id = 'c'" opts="servers=127.0.0.1::1223"
- cached data found but either wrong opts or dated;  database lookup required for select name from them where id = 'c'
+ cached data found but wrong opts;  database lookup required for select name from them where id = 'c'
  MySQL query: "select name from them where id = 'c'" opts 'servers=127.0.0.1::1223'
  MYSQL using cached connection for 127.0.0.1:1223/test/root
  MYSQL: no data found
  MySQL query: "select name from them where id = 'c'" opts 'servers=127.0.0.1::1223'
  MYSQL using cached connection for 127.0.0.1:1223/test/root
  MYSQL: no data found
index 337d518507b47fb9ef16b3ee91e81d8c68cc1f1c..2c378284bf9ad7217c68de7b7731ed20899bca4c 100644 (file)
@@ -254,7 +254,7 @@ check set acl_m0 = ok:   ${lookup pgsql                    {select name from the
  LRU list:
  internal_search_find: file="NULL"
    type=pgsql key="select name from them where id = 'c'" opts="servers=SSPEC"
  LRU list:
  internal_search_find: file="NULL"
    type=pgsql key="select name from them where id = 'c'" opts="servers=SSPEC"
- cached data found but either wrong opts or dated;  database lookup required for select name from them where id = 'c'
+ cached data found but wrong opts;  database lookup required for select name from them where id = 'c'
  PostgreSQL query: "select name from them where id = 'c'" opts 'servers=SSPEC'
  lookup deferred: PostgreSQL server "SSPEC" not found in pgsql_servers
 warn: condition test deferred in ACL "check_recipient"
  PostgreSQL query: "select name from them where id = 'c'" opts 'servers=SSPEC'
  lookup deferred: PostgreSQL server "SSPEC" not found in pgsql_servers
 warn: condition test deferred in ACL "check_recipient"
@@ -361,7 +361,7 @@ check set acl_m0 = ok:   ${lookup pgsql                    {select name from the
  LRU list:
  internal_search_find: file="NULL"
    type=pgsql key="select name from them where id = 'c'" opts="servers=SSPEC"
  LRU list:
  internal_search_find: file="NULL"
    type=pgsql key="select name from them where id = 'c'" opts="servers=SSPEC"
- cached data found but either wrong opts or dated;  database lookup required for select name from them where id = 'c'
+ cached data found but wrong opts;  database lookup required for select name from them where id = 'c'
  PostgreSQL query: "select name from them where id = 'c'" opts 'servers=SSPEC'
  lookup deferred: PostgreSQL server "SSPEC" not found in pgsql_servers
 warn: condition test deferred in ACL "check_recipient"
  PostgreSQL query: "select name from them where id = 'c'" opts 'servers=SSPEC'
  lookup deferred: PostgreSQL server "SSPEC" not found in pgsql_servers
 warn: condition test deferred in ACL "check_recipient"
index a4acc6591ba3153b4bebc44287dc7386f94e5027..513f36468c29ef0517c8574b02d66d30767049cd 100644 (file)
@@ -1,6 +1,8 @@
+> connfail cases (no server)
 > Failed: failed to connect to socket TESTSUITE/test-socket: No such file or directory
 > 2 ++ no socket ++
 > 
 > Failed: failed to connect to socket TESTSUITE/test-socket: No such file or directory
 > 2 ++ no socket ++
 > 
+> unix-socket cases
 > 1 >>ANSWER-1
 <<
 > 2 >>ANSWER-2<<
 > 1 >>ANSWER-1
 <<
 > 2 >>ANSWER-2<<
 > 9 >>sock error<<
 > 
 451 Temporary local problem - please try later\r
 > 9 >>sock error<<
 > 
 451 Temporary local problem - please try later\r
-> 1 >>ANSWER-1
+> ipv4 cases
+> 1  ANSWER-1      >>ANSWER-1
 <<
 <<
-> 2 >>ANSWER-2<<
-> 3 >>ANSWER-3*EOL*<<
-> 4 >>ANSWER-4*EOL*<<
-> 5 >>ANSWER-5<<
-> 6 >><<
-> 7 >><<
+> 2  ANSWER-2      >>ANSWER-2<<
+> 3  ANSWER-3*EOL* >>ANSWER-3*EOL*<<
+> 4  ANSWER-4*EOL* >>ANSWER-4*EOL*<<
+> 5  ANSWER-5      >>ANSWER-5<<
+> 6                >><<
+> 7                >><<
 > Failed: socket read timed out
 > Failed: socket read timed out
-> 9 >>sock error<<
-> 10 >>ANSWER-10
-<<
-> 11 >>ANSWER-11
+> 9  sock error    >>sock error<<
+> 10 ANSWER-10\n     >>ANSWER-10
 <<
 <<
+> 11 ANSWER-11     >><<
 > 
 > 
-> Failed: bad time value NULL
+> Failed: failed to connect to any address for 127.0.0.1: Connection refused
+> 
+> caching of response value
+> 1  >>ANSWER-1
+<<
+> 1+ >>ANSWER-1
+<<
+> 2  >>ANSWER-2
+<<
+> 2- >>expected failure<<
+> 1- >>expected failure<<
+> 1+ >>ANSWER-1
+<<
+> 1- >>ANSWER-1
+<<
 > 
 
 ******** SERVER ********
 > 
 
 ******** SERVER ********
@@ -129,3 +145,19 @@ Connection request from [ip4.ip4.ip4.ip4]
 >LF>ANSWER-11
 >*eof
 End of script
 >LF>ANSWER-11
 >*eof
 End of script
+Listening on TESTSUITE/test-socket ... 
+Connection request
+QUERY-1
+>LF>ANSWER-1
+>*eof
+Listening on TESTSUITE/test-socket ... 
+Connection request
+QUERY-2
+>LF>ANSWER-2
+>*eof
+Listening on TESTSUITE/test-socket ... 
+Connection request
+QUERY-1
+>LF>ANSWER-1
+>*eof
+End of script