[exim-cvs] SOCKS: as a client, talk SMTP via a socks5 proxy.…

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] SOCKS: as a client, talk SMTP via a socks5 proxy. Bug 1590
Gitweb: http://git.exim.org/exim.git/commitdiff/7eb6c37c5084760c1d1469bd4be652b479a8df55
Commit:     7eb6c37c5084760c1d1469bd4be652b479a8df55
Parent:     7c9f0469cb39055fff4096d1cb17ed72429c6b6c
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Sun Mar 15 12:32:11 2015 +0000
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Sun Mar 15 12:34:33 2015 +0000


    SOCKS: as a client, talk SMTP via a socks5 proxy.  Bug 1590
---
 doc/doc-txt/NewStuff                     |    4 +
 doc/doc-txt/experimental-spec.txt        |   33 ++++
 src/scripts/MakeLinks                    |    1 +
 src/src/EDITME                           |    3 +
 src/src/config.h.defaults                |    3 +-
 src/src/exim.c                           |    3 +
 src/src/functions.h                      |   10 +-
 src/src/ip.c                             |   64 ++++---
 src/src/smtp_out.c                       |  142 ++++++++------
 src/src/transports/Makefile              |    3 +-
 src/src/transports/smtp.c                |   14 +-
 src/src/transports/smtp.h                |    8 +
 src/src/transports/smtp_socks.c          |  310 ++++++++++++++++++++++++++++++
 src/src/verify.c                         |    9 +-
 test/README                              |   10 +-
 test/confs/4020                          |   44 +++++
 test/confs/4028                          |   63 ++++++
 test/confs/4029                          |   64 ++++++
 test/log/4020                            |    6 +
 test/log/4028                            |    9 +
 test/log/4029                            |   11 +
 test/scripts/2000-GnuTLS/2000            |    2 +-
 test/scripts/2000-GnuTLS/2018            |    2 +-
 test/scripts/4000-scanning/4006          |    2 +-
 test/scripts/4020-socks/4020             |   85 ++++++++
 test/scripts/4020-socks/REQUIRES         |    1 +
 test/scripts/4028-GnuTLS-socks/4028      |   30 +++
 test/scripts/4028-GnuTLS-socks/REQUIRES  |    4 +
 test/scripts/4029-OpenSSL-socks/4029     |   30 +++
 test/scripts/4029-OpenSSL-socks/REQUIRES |    4 +
 test/src/server.c                        |  162 ++++++++++++----
 test/stdout/0259                         |    2 +-
 test/stdout/4003                         |   10 +-
 test/stdout/4006                         |    6 +-
 test/stdout/4020                         |   68 +++++++
 test/stdout/4028                         |   12 ++
 test/stdout/4029                         |   12 ++
 37 files changed, 1081 insertions(+), 165 deletions(-)


diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 33d23f7..e4bc586 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -22,6 +22,10 @@ Version 4.86

6. A commandline option to write a comment into the logfile.

+ 7. If built with EXPERIMENTAL_SOCKS feature enabled, the smtp transport can
+    be configured to make connections via socks5 proxies
+
+
 Version 4.85
 ------------


diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index 4f76365..e6e066c 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -1086,6 +1086,39 @@ QUIT



+SOCKS
+------------------------------------------------------------
+Support for proxying outbound SMTP via a Socks 5 proxy
+(RFC 1928) is included if Exim is compiled with
+EXPERIMENTAL_SOCKS defined.
+
+If an smtp transport has a nonempty socks_proxy option
+defined, this is active.  The option is expanded and
+should be a list (colon-separated by default) of
+proxy specifiers.  Each proxy specifier is a list
+(space-separated by default) where the initial element
+is an IP address and any subsequent elements are options.
+
+Options are a string <name>=<value>.
+These options are currently defined:
+- "auth", with possible values "none" and "name".
+  Using "name" selects username/password authentication
+  per RFC 1929. Default is "none".
+- "name" sets the authentication username. Default is empty.
+- "pass" sets the authentication password. Default is empty.
+- "port" sets the tcp port number for the proxy. Default is 1080.
+- "tmo" sets a connection timeout in seconds for this proxy. Default is 5.
+
+Proxies from the list are tried in order until
+one responds.  The timeout for the overall connection
+applies to the set of proxied attempts.
+
+If events are used, the remote IP/port during a
+tcp:connect event will be that of the proxy.
+
+
+
+
 DANE
 ------------------------------------------------------------
 DNS-based Authentication of Named Entities, as applied
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index a50f75b..f68fd6f 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -105,6 +105,7 @@ ln -s ../../src/transports/pipe.h           pipe.h
 ln -s ../../src/transports/pipe.c           pipe.c
 ln -s ../../src/transports/smtp.h           smtp.h
 ln -s ../../src/transports/smtp.c           smtp.c
+ln -s ../../src/transports/smtp_socks.c     smtp_socks.c


 ln -s ../../src/transports/tf_maildir.c     tf_maildir.c
 ln -s ../../src/transports/tf_maildir.h     tf_maildir.h
diff --git a/src/src/EDITME b/src/src/EDITME
index 353dde3..d48f268 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -494,6 +494,9 @@ EXIM_MONITOR=eximon.bin
 # Uncomment the following line to add DANE support
 # EXPERIMENTAL_DANE=yes


+# Uncomment the following line to add SOCKS support
+# EXPERIMENTAL_SOCKS=yes
+
 ###############################################################################
 #                 THESE ARE THINGS YOU MIGHT WANT TO SPECIFY                  #
 ###############################################################################
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index e339bdf..ac4994a 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -170,11 +170,12 @@ it's a default value. */
 #define EXPERIMENTAL_DANE
 #define EXPERIMENTAL_DCC
 #define EXPERIMENTAL_DMARC
+#define EXPERIMENTAL_EVENT
 #define EXPERIMENTAL_PROXY
 #define EXPERIMENTAL_REDIS
+#define EXPERIMENTAL_SOCKS
 #define EXPERIMENTAL_SPF
 #define EXPERIMENTAL_SRS
-#define EXPERIMENTAL_EVENT


/* For developers */
#define WANT_DEEPER_PRINTF_CHECKS
diff --git a/src/src/exim.c b/src/src/exim.c
index c94bdc1..54725ef 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -853,6 +853,9 @@ fprintf(f, "Support for:");
#ifdef EXPERIMENTAL_REDIS
fprintf(f, " Experimental_Redis");
#endif
+#ifdef EXPERIMENTAL_SOCKS
+ fprintf(f, " Experimental_SOCKS");
+#endif
fprintf(f, "\n");

 fprintf(f, "Lookups (built-in):");
diff --git a/src/src/functions.h b/src/src/functions.h
index 49bb5a9..df40d64 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -208,6 +208,7 @@ extern uschar *host_ntoa(int, const void *, uschar *, int *);
 extern int     host_scan_for_local_hosts(host_item *, host_item **, BOOL *);


 extern void    invert_address(uschar *, uschar *);
+extern int     ip_addr(void *, int, const uschar *, int);
 extern int     ip_bind(int, int, uschar *, int);
 extern int     ip_connect(int, int, const uschar *, int, int);
 extern int     ip_connectedsocket(int, const uschar *, int, int,
@@ -358,11 +359,10 @@ extern int     sieve_interpret(uschar *, int, uschar *, uschar *, uschar *,
 extern void    sigalrm_handler(int);
 extern BOOL    smtp_buffered(void);
 extern void    smtp_closedown(uschar *);
-extern int     smtp_connect(host_item *, int, int, uschar *, int, BOOL, const uschar *
-#ifdef EXPERIMENTAL_EVENT
-               , uschar *
-#endif
-               );
+extern int     smtp_connect(host_item *, int, int, uschar *, int,
+                transport_instance *);
+extern int     smtp_sock_connect(host_item *, int, int, uschar *,
+         transport_instance * tb, int);
 extern int     smtp_feof(void);
 extern int     smtp_ferror(void);
 extern uschar *smtp_get_connection_info(void);
diff --git a/src/src/ip.c b/src/src/ip.c
index f6072c2..83c8d16 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -97,24 +97,11 @@ ip_addrinfo(const uschar *address, struct sockaddr_in6 *saddr)
 *         Bind socket to interface and port      *
 *************************************************/


-/* This function binds a socket to a local interface address and port. For a
-wildcard IPv6 bind, the address is ":".
-
-Arguments:
-  sock           the socket
-  af             AF_INET or AF_INET6 - the socket type
-  address        the IP address, in text form
-  port           the IP port (host order)
-
-Returns:         the result of bind()
-*/
-
 int
-ip_bind(int sock, int af, uschar *address, int port)
+ip_addr(void * sin_, int af, const uschar * address, int port)
 {
-int s_len;
-union sockaddr_46 sin;
-memset(&sin, 0, sizeof(sin));
+union sockaddr_46 * sin = sin_;
+memset(sin, 0, sizeof(sin));


 /* Setup code when using an IPv6 socket. The wildcard address is ":", to
 ensure an IPv6 socket is used. */
@@ -124,15 +111,13 @@ if (af == AF_INET6)
   {
   if (address[0] == ':' && address[1] == 0)
     {
-    sin.v6.sin6_family = AF_INET6;
-    sin.v6.sin6_addr = in6addr_any;
+    sin->v6.sin6_family = AF_INET6;
+    sin->v6.sin6_addr = in6addr_any;
     }
   else
-    {
-    ip_addrinfo(address, &sin.v6);  /* Panic-dies on error */
-    }
-  sin.v6.sin6_port = htons(port);
-  s_len = sizeof(sin.v6);
+    ip_addrinfo(address, &sin->v6);  /* Panic-dies on error */
+  sin->v6.sin6_port = htons(port);
+  return sizeof(sin->v6);
   }
 else
 #else     /* HAVE_IPv6 */
@@ -142,17 +127,34 @@ af = af;  /* Avoid compiler warning */
 /* Setup code when using IPv4 socket. The wildcard address is "". */


   {
-  sin.v4.sin_family = AF_INET;
-  sin.v4.sin_port = htons(port);
-  s_len = sizeof(sin.v4);
-  if (address[0] == 0)
-    sin.v4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
-  else
-    sin.v4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(CS address);
+  sin->v4.sin_family = AF_INET;
+  sin->v4.sin_port = htons(port);
+  sin->v4.sin_addr.s_addr = address[0] == 0
+    ? (S_ADDR_TYPE)INADDR_ANY
+    : (S_ADDR_TYPE)inet_addr(CS address);
+  return sizeof(sin->v4);
   }
+}


-/* Now we can call the bind() function */

+
+/* This function binds a socket to a local interface address and port. For a
+wildcard IPv6 bind, the address is ":".
+
+Arguments:
+  sock           the socket
+  af             AF_INET or AF_INET6 - the socket type
+  address        the IP address, in text form
+  port           the IP port (host order)
+
+Returns:         the result of bind()
+*/
+
+int
+ip_bind(int sock, int af, uschar *address, int port)
+{
+union sockaddr_46 sin;
+int s_len = ip_addr(&sin, af, address, port);
 return bind(sock, (struct sockaddr *)&sin, s_len);
 }


diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 8147b61..7243395 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -2,13 +2,14 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/


-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
/* See the file NOTICE for conditions of use and distribution. */

/* A number of functions for driving outgoing SMTP calls. */


#include "exim.h"
+#include "transports/smtp.h"



@@ -143,75 +144,26 @@ return TRUE;



-/*************************************************
-*           Connect to remote host               *
-*************************************************/
-
-/* Create a socket, and connect it to a remote host. IPv6 addresses are
-detected by checking for a colon in the address. AF_INET6 is defined even on
-non-IPv6 systems, to enable the code to be less messy. However, on such systems
-host->address will always be an IPv4 address.
-
-The port field in the host item is used if it is set (usually router from SRV
-records or elsewhere). In other cases, the default passed as an argument is
-used, and the host item is updated with its value.
-
-Arguments:
-  host        host item containing name and address (and sometimes port)
-  host_af     AF_INET or AF_INET6
-  port        default remote port to connect to, in host byte order, for those
-                hosts whose port setting is PORT_NONE
-  interface   outgoing interface address or NULL
-  timeout     timeout value or 0
-  keepalive   TRUE to use keepalive
-  dscp        DSCP value to assign to socket
-  event       event expansion
-
-Returns:      connected socket number, or -1 with errno set
-*/
-
 int
-smtp_connect(host_item *host, int host_af, int port, uschar *interface,
-  int timeout, BOOL keepalive, const uschar *dscp
-#ifdef EXPERIMENTAL_EVENT
-  , uschar * event
-#endif
-  )
+smtp_sock_connect(host_item * host, int host_af, int port, uschar * interface,
+  transport_instance * tb, int timeout)
 {
-int on = 1;
-int save_errno = 0;
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
+const uschar * dscp = ob->dscp;
 int dscp_value;
 int dscp_level;
 int dscp_option;
 int sock;
-
-if (host->port != PORT_NONE)
-  {
-  HDEBUG(D_transport|D_acl|D_v)
-    debug_printf("Transport port=%d replaced by host-specific port=%d\n", port,
-      host->port);
-  port = host->port;
-  }
-else host->port = port;    /* Set the port actually used */
-
-HDEBUG(D_transport|D_acl|D_v)
-  {
-  if (interface == NULL)
-    debug_printf("Connecting to %s [%s]:%d ... ",host->name,host->address,port);
-  else
-    debug_printf("Connecting to %s [%s]:%d from %s ... ", host->name,
-      host->address, port, interface);
-  }
+int on = 1;
+int save_errno = 0;


#ifdef EXPERIMENTAL_EVENT
- deliver_host_address = host->address;
- deliver_host_port = port;
- if (event_raise(event, US"tcp:connect", NULL)) return -1;
- /* Logging? Debug? */
+deliver_host_address = host->address;
+deliver_host_port = port;
+if (event_raise(tb->event_action, US"tcp:connect", NULL)) return -1;
#endif

-/* Create the socket */
-
if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return -1;

 /* Set TCP_NODELAY; Exim does its own buffering. */
@@ -232,15 +184,13 @@ if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value))
   option for both; ignore failures here */
   if (host_af == AF_INET6 &&
       dscp_lookup(dscp, AF_INET, &dscp_level, &dscp_option, &dscp_value))
-    {
     (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
-    }
   }


/* Bind to a specific interface if requested. Caller must ensure the interface
is the same type (IPv4 or IPv6) as the outgoing address. */

-if (interface != NULL && ip_bind(sock, host_af, interface, 0) < 0)
+if (interface && ip_bind(sock, host_af, interface, 0) < 0)
   {
   save_errno = errno;
   HDEBUG(D_transport|D_acl|D_v)
@@ -286,11 +236,73 @@ else
     close(sock);
     return -1;
     }
-  if (keepalive) ip_keepalive(sock, host->address, TRUE);
+  if (ob->keepalive) ip_keepalive(sock, host->address, TRUE);
   return sock;
   }
 }


+/*************************************************
+*           Connect to remote host               *
+*************************************************/
+
+/* Create a socket, and connect it to a remote host. IPv6 addresses are
+detected by checking for a colon in the address. AF_INET6 is defined even on
+non-IPv6 systems, to enable the code to be less messy. However, on such systems
+host->address will always be an IPv4 address.
+
+The port field in the host item is used if it is set (usually router from SRV
+records or elsewhere). In other cases, the default passed as an argument is
+used, and the host item is updated with its value.
+
+Arguments:
+  host        host item containing name and address (and sometimes port)
+  host_af     AF_INET or AF_INET6
+  port        default remote port to connect to, in host byte order, for those
+                hosts whose port setting is PORT_NONE
+  interface   outgoing interface address or NULL
+  timeout     timeout value or 0
+  tb          transport
+
+Returns:      connected socket number, or -1 with errno set
+*/
+
+int
+smtp_connect(host_item *host, int host_af, int port, uschar *interface,
+  int timeout, transport_instance * tb)
+{
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
+
+if (host->port != PORT_NONE)
+  {
+  HDEBUG(D_transport|D_acl|D_v)
+    debug_printf("Transport port=%d replaced by host-specific port=%d\n", port,
+      host->port);
+  port = host->port;
+  }
+else host->port = port;    /* Set the port actually used */
+
+HDEBUG(D_transport|D_acl|D_v)
+  {
+  uschar * s = US" ";
+  if (interface) s = string_sprintf(" from %s ", interface);
+#ifdef EXPERIMENTAL_SOCKS
+  if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s);
+#endif
+  debug_printf("Connecting to %s [%s]:%d%s... ",
+    host->name, host->address, port, s);
+  }
+
+/* Create and connect the socket */
+
+#ifdef EXPERIMENTAL_SOCKS
+if (ob->socks_proxy)
+  return socks_sock_connect(host, host_af, port, interface, tb, timeout);
+#endif
+
+return smtp_sock_connect(host, host_af, port, interface, tb, timeout);
+}
+


 /*************************************************
 *        Flush outgoing command buffer           *
@@ -581,3 +593,5 @@ return buffer[0] == okdigit;
 }


/* End of smtp_out.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/transports/Makefile b/src/src/transports/Makefile
index 02cf0c8..25973d5 100644
--- a/src/src/transports/Makefile
+++ b/src/src/transports/Makefile
@@ -2,7 +2,7 @@
# calling it transports.a. This is called from the main make file, after cd'ing
# to the transports subdirectory.

-OBJ = appendfile.o autoreply.o lmtp.o pipe.o smtp.o tf_maildir.o
+OBJ = appendfile.o autoreply.o lmtp.o pipe.o smtp.o smtp_socks.o tf_maildir.o

 transports.a:    $(OBJ)
          @$(RM_COMMAND) -f transports.a
@@ -19,6 +19,7 @@ autoreply.o:     $(HDRS) autoreply.c autoreply.h
 lmtp.o:          $(HDRS) lmtp.c lmtp.h
 pipe.o:          $(HDRS) pipe.c pipe.h
 smtp.o:          $(HDRS) smtp.c smtp.h
+smtp_socks.o:    $(HDRS) smtp_socks.c smtp.h


 tf_maildir.o:    $(HDRS) tf_maildir.c tf_maildir.h appendfile.h


diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 5662ffb..11d7fdd 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -159,6 +159,10 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, serialize_hosts) },
   { "size_addition",        opt_int,
       (void *)offsetof(smtp_transport_options_block, size_addition) }
+#ifdef EXPERIMENTAL_SOCKS
+ ,{ "socks_proxy",          opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, socks_proxy) }
+#endif
 #ifdef SUPPORT_TLS
  ,{ "tls_certificate",      opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_certificate) },
@@ -246,6 +250,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   FALSE,               /* lmtp_ignore_quota */
   NULL,               /* expand_retry_include_ip_address */
   TRUE                 /* retry_include_ip_address */
+#ifdef EXPERIMENTAL_SOCKS
+#endif
+ ,NULL                 /* socks_proxy */
 #ifdef SUPPORT_TLS
  ,NULL,                /* tls_certificate */
   NULL,                /* tls_crl */
@@ -1350,12 +1357,7 @@ if (continue_hostname == NULL)
   {
   /* This puts port into host->port */
   inblock.sock = outblock.sock =
-    smtp_connect(host, host_af, port, interface, ob->connect_timeout,
-          ob->keepalive, ob->dscp
-#ifdef EXPERIMENTAL_EVENT
-          , tblock->event_action
-#endif
-        );
+    smtp_connect(host, host_af, port, interface, ob->connect_timeout, tblock);


   if (inblock.sock < 0)
     {
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 1b51c13..84fb9f5 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -60,6 +60,9 @@ typedef struct {
   BOOL    lmtp_ignore_quota;
   uschar *expand_retry_include_ip_address;
   BOOL    retry_include_ip_address;
+#ifdef EXPERIMENTAL_SOCKS
+  uschar *socks_proxy;
+#endif
 #ifdef SUPPORT_TLS
   uschar *tls_certificate;
   uschar *tls_crl;
@@ -109,4 +112,9 @@ extern int     smtp_auth(uschar *, unsigned, address_item *, host_item *,
 extern BOOL    smtp_mail_auth_str(uschar *, unsigned,
          address_item *, smtp_transport_options_block *);


+#ifdef EXPERMENTAL_SOCKS
+extern int     socks_sock_connect(host_item, int, int, uschar *,
+             transport_instance *, int);
+#endif
+
 /* End of transports/smtp.h */
diff --git a/src/src/transports/smtp_socks.c b/src/src/transports/smtp_socks.c
new file mode 100644
index 0000000..cf9f73b
--- /dev/null
+++ b/src/src/transports/smtp_socks.c
@@ -0,0 +1,310 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2015 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* SOCKS version 5 proxy, client-mode */
+
+#include "../exim.h"
+#include "smtp.h"
+
+#ifdef EXPERIMENTAL_SOCKS    /* entire file */
+
+#ifndef nelem
+# define nelem(arr) (sizeof(arr)/sizeof(*arr))
+#endif
+
+
+/* Defaults */
+#define SOCKS_PORT    1080
+#define SOCKS_TIMEOUT    5
+
+#define AUTH_NONE    0
+#define AUTH_NAME    2        /* user/password per RFC 1929 */
+#define AUTH_NAME_VER    1
+
+struct socks_err
+  {
+  uschar *    reason;
+  int        errcode;
+  } socks_errs[] =
+  {
+    {NULL, 0},
+    {US"general SOCKS server failure",        EIO},
+    {US"connection not allowed by ruleset",    EACCES},
+    {US"Network unreachable",            ENETUNREACH},
+    {US"Host unreachable",            EHOSTUNREACH},
+    {US"Connection refused",            ECONNREFUSED},
+    {US"TTL expired",                ECANCELED},
+    {US"Command not supported",            EOPNOTSUPP},
+    {US"Address type not supported",        EAFNOSUPPORT}
+  };
+
+typedef struct
+  {
+  uschar        auth_type;    /* RFC 1928 encoding */
+  const uschar *    auth_name;
+  const uschar *    auth_pwd;
+  short            port;
+  unsigned        timeout;
+  } socks_opts;
+
+static void
+socks_option_defaults(socks_opts * sob)
+{
+sob->auth_type = AUTH_NONE;
+sob->auth_name = US"";
+sob->auth_pwd = US"";
+sob->port = SOCKS_PORT;
+sob->timeout = SOCKS_TIMEOUT;
+}
+
+static void
+socks_option(socks_opts * sob, const uschar * opt)
+{
+const uschar * s;
+
+if (Ustrncmp(opt, "auth=", 5) == 0)
+  {
+  opt += 5;
+  if (Ustrcmp(opt, "none") == 0)     sob->auth_type = AUTH_NONE;
+  else if (Ustrcmp(opt, "name") == 0)    sob->auth_type = AUTH_NAME;
+  }
+else if (Ustrncmp(opt, "name=", 5) == 0)
+  sob->auth_name = opt + 5;
+else if (Ustrncmp(opt, "pass=", 5) == 0)
+  sob->auth_pwd = opt + 5;
+else if (Ustrncmp(opt, "port=", 5) == 0)
+  sob->port = atoi(opt + 5);
+else if (Ustrncmp(opt, "tmo=", 4) == 0)
+  sob->timeout = atoi(opt + 4);
+return;
+}
+
+static int
+socks_auth(int fd, int method, socks_opts * sob, time_t tmo)
+{
+uschar * s;
+int len, i, j;
+
+switch(method)
+  {
+  default:
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "Unrecognised socks auth method %d", method);
+    return FAIL;
+  case AUTH_NONE:
+    return OK;
+  case AUTH_NAME:
+    HDEBUG(D_transport|D_acl|D_v) debug_printf("  socks auth NAME '%s' '%s'\n",
+      sob->auth_name, sob->auth_pwd);
+    i = Ustrlen(sob->auth_name);
+    j = Ustrlen(sob->auth_pwd);
+    s = string_sprintf("%c%c%.255s%c%.255s", AUTH_NAME_VER,
+      i, sob->auth_name, j, sob->auth_pwd);
+    len = i + j + 3;
+    HDEBUG(D_transport|D_acl|D_v)
+      {
+      int i;
+      debug_printf("  SOCKS>>");
+      for (i = 0; i<len; i++) debug_printf(" %02x", s[i]);
+      debug_printf("\n");
+      }
+    if (  send(fd, s, len, 0) < 0
+       || !fd_ready(fd, tmo-time(NULL))
+       || read(fd, s, 2) != 2
+       )
+      return FAIL;
+    HDEBUG(D_transport|D_acl|D_v)
+      debug_printf("  SOCKS<< %02x %02x\n", s[0], s[1]);
+    if (s[0] == AUTH_NAME_VER && s[1] == 0)
+      {
+      HDEBUG(D_transport|D_acl|D_v) debug_printf("  socks auth OK\n");
+      return OK;
+      }
+
+    log_write(0, LOG_MAIN|LOG_PANIC, "socks auth failed");
+    errno = EPROTO;
+    return FAIL;
+  }
+}
+
+
+
+/* Make a connection via a socks proxy
+
+Arguments:
+ host        smtp target host
+ host_af    address family
+ port        remote tcp port number
+ interface    local interface
+ tb        transport
+ timeout    connection timeout (zero for indefinite)
+
+Return value:
+ 0 on success; -1 on failure, with errno set
+*/
+
+int
+socks_sock_connect(host_item * host, int host_af, int port, uschar * interface,
+  transport_instance * tb, int timeout)
+
+{
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
+const uschar * proxy_list;
+const uschar * proxy_spec;
+int sep = 0;
+int fd;
+time_t tmo;
+const uschar * state;
+uschar buf[24];
+
+if (!timeout) timeout = 24*60*60;    /* use 1 day for "indefinite" */
+tmo = time(NULL) + timeout;
+
+if (!(proxy_list = expand_string(ob->socks_proxy)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
+    tb->name);
+  return -1;
+  }
+
+/* Loop over proxy list, trying in order until one works */
+while ((proxy_spec = string_nextinlist(&proxy_list, &sep, NULL, 0)))
+  {
+  const uschar * proxy_host;
+  int subsep = -' ';
+  host_item proxy;
+  int proxy_af;
+  union sockaddr_46 sin;
+  unsigned size;
+  socks_opts sob;
+  const uschar * option;
+
+  if (!(proxy_host = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
+    {
+    /* paniclog config error */
+    return -1;
+    }
+
+  /*XXX consider global options eg. "hide socks_password = wibble" on the tpt */
+  socks_option_defaults(&sob);
+
+  /* extract any further per-proxy options */
+  while ((option = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
+    socks_option(&sob, option);
+
+  /* bodge up a host struct for the proxy */
+  proxy.address = proxy_host;
+  proxy_af = Ustrchr(proxy_host, ':') ? AF_INET6 : AF_INET;
+
+  if ((fd = smtp_sock_connect(&proxy, proxy_af, sob.port,
+          interface, tb, sob.timeout)) < 0)
+    continue;
+
+  /* Do the socks protocol stuff */
+  /* Send method-selection */
+  state = US"method select";
+  HDEBUG(D_transport|D_acl|D_v) debug_printf("  SOCKS>> 05 01 %02x\n", sob.auth_type);
+  buf[0] = 5; buf[1] = 1; buf[2] = sob.auth_type;
+  if (send(fd, buf, 3, 0) < 0)
+    goto snd_err;
+
+  /* expect method response */
+  if (  !fd_ready(fd, tmo-time(NULL))
+     || read(fd, buf, 2) != 2
+     )
+    goto rcv_err;
+  HDEBUG(D_transport|D_acl|D_v)
+    debug_printf("  SOCKS<< %02x %02x\n", buf[0], buf[1]);
+  if (  buf[0] != 5
+     || socks_auth(fd, buf[1], &sob, tmo) != OK
+     )
+    goto proxy_err;
+
+  (void) ip_addr(&sin, host_af, host->address, port);
+
+  /* send connect (ipver, ipaddr, port) */
+  buf[0] = 5; buf[1] = 1; buf[2] = 0; buf[3] = host_af == AF_INET6 ? 4 : 1;
+  if (host_af == AF_INET6)
+    {
+    memcpy(buf+4, &sin.v6.sin6_addr,       sizeof(sin.v6.sin6_addr));
+    memcpy(buf+4+sizeof(sin.v6.sin6_addr),
+      &sin.v6.sin6_port, sizeof(sin.v6.sin6_port));
+    size = 4+sizeof(sin.v6.sin6_addr)+sizeof(sin.v6.sin6_port);
+    }
+  else
+    {
+    memcpy(buf+4, &sin.v4.sin_addr.s_addr, sizeof(sin.v4.sin_addr.s_addr));
+    memcpy(buf+4+sizeof(sin.v4.sin_addr.s_addr),
+      &sin.v4.sin_port, sizeof(sin.v4.sin_port));
+    size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port);
+    }
+
+  state = US"connect";
+  HDEBUG(D_transport|D_acl|D_v)
+    {
+    int i;
+    debug_printf("  SOCKS>>");
+    for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
+    debug_printf("\n");
+    }
+  if (send(fd, buf, size, 0) < 0)
+    goto snd_err;
+
+  /* expect conn-reply (success, local(ipver, addr, port))
+  of same length as conn-request, or non-success fail code */
+  if (  !fd_ready(fd, tmo-time(NULL))
+     || (size = read(fd, buf, size)) < 2
+     )
+    goto rcv_err;
+  HDEBUG(D_transport|D_acl|D_v)
+    {
+    int i;
+    debug_printf("  SOCKS>>");
+    for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
+    debug_printf("\n");
+    }
+  if (  buf[0] != 5
+     || buf[1] != 0
+     )
+    goto proxy_err;
+
+  /*XXX log proxy outbound addr/port? */
+  HDEBUG(D_transport|D_acl|D_v)
+    debug_printf("  proxy farside local: [%s]:%d\n",
+      host_ntoa(buf[3] == 4 ? AF_INET6 : AF_INET, buf+4, NULL, NULL),
+      ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8)))));
+
+  return fd;
+  }
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf("  no proxies left\n");
+return -1;
+
+snd_err:
+  HDEBUG(D_transport|D_acl|D_v) debug_printf("  proxy snd_err %s: %s\n", state, strerror(errno));
+  return -1;
+
+proxy_err:
+  {
+  struct socks_err * se =
+    buf[1] > nelem(socks_errs) ? NULL : socks_errs + buf[1];
+  HDEBUG(D_transport|D_acl|D_v)
+    debug_printf("  proxy %s: %s\n", state, se ? se->reason : US"unknown error code received");
+  errno = se ? se->errcode : EPROTO;
+  }
+
+rcv_err:
+  HDEBUG(D_transport|D_acl|D_v) debug_printf("  proxy rcv_err %s: %s\n", state, strerror(errno));
+  if (!errno) errno = EPROTO;
+  else if (errno == ENOENT) errno = ECONNABORTED;
+  return -1;
+}
+
+#endif    /* entire file */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/verify.c b/src/src/verify.c
index c1fb177..d85ef3b 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -641,13 +641,8 @@ can do it there for the non-rcpt-verify case.  For this we keep an addresscount.
     tls_out.cipher = tls_out.peerdn = tls_out.peercert = NULL;


     inblock.sock = outblock.sock =
-      smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL
-#ifdef EXPERIMENTAL_EVENT
-    /*XXX event action? NULL for now. */
-          , NULL
-#endif
-          );
-    /* reconsider DSCP here */
+      smtp_connect(host, host_af, port, interface, callout_connect,
+          addr->transport);
     if (inblock.sock < 0)
       {
       addr->message = string_sprintf("could not connect to %s [%s]: %s",
diff --git a/test/README b/test/README
index e544857..653cf95 100644
--- a/test/README
+++ b/test/README
@@ -1021,7 +1021,10 @@ are of the following kinds:
     (d) If the line starts with ">*eof", nothing is sent and the connection
           is closed.


-    The data that is sent starts after the initial '>' sequence.
+    The data that is sent starts after the initial '>' sequence.  Within
+    each line the sequence '\x' followed by two hex digits can be used
+    to specify an arbitrary byte value.  The sequence '\\' specifies a
+    single backslash.


 (2) A line that starts with "*sleep" specifies a number of seconds to wait
     before proceeding.
@@ -1035,7 +1038,10 @@ are of the following kinds:
 (5) Otherwise, the line defines the start of an input line that the client
     is expected to send. To allow for lines that start with digits, the line
     may start with '<', which is not taken as part of the input data. If the
-    input does not match, the server bombs out with an error message.
+    lines starts with '<<' then only the characters are expected; no return-
+    linefeed terminator. If the input does not match, the server bombs out
+    with an error message.  Backslash-escape sequences may be used in the
+    line content as for output lines.


Here is a simple example of server use in a test script:

diff --git a/test/confs/4020 b/test/confs/4020
new file mode 100644
index 0000000..8a3a91f
--- /dev/null
+++ b/test/confs/4020
@@ -0,0 +1,44 @@
+# Exim test configuration 4020
+
+OPT =
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+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_rcpt = accept
+
+
+# ----- Routers -----
+
+begin routers
+
+my_main_router:
+ driver = manualroute
+ route_list = * 127.0.0.1
+ self = send
+ transport = my_smtp
+ debug_print = router_name <$router_name>
+ no_more
+
+
+# ----- Transports -----
+
+begin transports
+
+my_smtp:
+ driver = smtp
+ interface = HOSTIPV4
+ port = PORT_S
+ socks_proxy = 127.0.0.1 port=PORT_S OPT
+ debug_print = transport_name <$transport_name>
+
+
+# End
diff --git a/test/confs/4028 b/test/confs/4028
new file mode 100644
index 0000000..3174e75
--- /dev/null
+++ b/test/confs/4028
@@ -0,0 +1,63 @@
+# Exim test configuration 4028
+# starttls over socks
+
+OPT =
+SERVER=
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/SERVER%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+log_selector = +tls_peerdn
+domainlist local_domains = test.ex : *.test.ex
+acl_smtp_rcpt = accept
+
+tls_advertise_hosts = *
+
+# Set certificate only if server
+
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+tls_verify_hosts = *
+tls_verify_certificates = ${if eq {SERVER}{server}{DIR/aux-fixed/cert2}fail}
+
+# ----- Routers -----
+
+begin routers
+
+client:
+ driver = manualroute
+ condition = ${if eq {SERVER}{server}{no}{yes}}
+ route_list = * 127.0.0.1
+ self = send
+ transport = my_smtp
+ no_more
+
+server:
+ driver = redirect
+ data = :blackhole:
+
+
+# ----- Transports -----
+
+begin transports
+
+my_smtp:
+ driver = smtp
+ port = PORT_D
+ socks_proxy = 127.0.0.1 port=1080 OPT
+ tls_certificate = DIR/aux-fixed/cert2
+ tls_privatekey = DIR/aux-fixed/cert2
+ tls_verify_certificates = DIR/aux-fixed/cert2
+ tls_try_verify_hosts = *
+
+
+
+# End
diff --git a/test/confs/4029 b/test/confs/4029
new file mode 100644
index 0000000..ae4e718
--- /dev/null
+++ b/test/confs/4029
@@ -0,0 +1,64 @@
+# Exim test configuration 4029
+# starttls over socks
+
+OPT =
+SERVER=
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/SERVER%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+log_selector = +tls_peerdn
+domainlist local_domains = test.ex : *.test.ex
+acl_smtp_rcpt = accept
+
+tls_advertise_hosts = *
+
+# Set certificate only if server
+
+tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
+
+tls_verify_hosts = *
+tls_verify_certificates = ${if eq {SERVER}{server}{DIR/aux-fixed/cert2}fail}
+
+# ----- Routers -----
+
+begin routers
+
+client:
+ driver = manualroute
+ condition = ${if eq {SERVER}{server}{no}{yes}}
+ route_list = * 127.0.0.1
+ self = send
+ transport = my_smtp
+ no_more
+
+server:
+ driver = redirect
+ data = :blackhole:
+
+
+# ----- Transports -----
+
+begin transports
+
+my_smtp:
+ driver = smtp
+ port = PORT_D
+ socks_proxy = 127.0.0.1 port=1080 OPT
+ tls_certificate = DIR/aux-fixed/cert2
+ tls_privatekey = DIR/aux-fixed/cert2
+ tls_verify_certificates = DIR/aux-fixed/cert2
+ tls_try_verify_hosts = *
+
+
+
+# End
+
diff --git a/test/log/4020 b/test/log/4020
new file mode 100644
index 0000000..f289bef
--- /dev/null
+++ b/test/log/4020
@@ -0,0 +1,6 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => userx@??? R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1] C="250 accepted OK"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 => userx@??? R=my_main_router T=my_smtp H=127.0.0.1 [127.0.0.1] C="250 accepted OK"
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
diff --git a/test/log/4028 b/test/log/4028
new file mode 100644
index 0000000..373bdf0
--- /dev/null
+++ b/test/log/4028
@@ -0,0 +1,9 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => userx@??? R=client T=my_smtp H=127.0.0.1 [127.0.0.1] X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 CV=no DN="C=UK,O=The Exim Maintainers,OU=Test Suite,CN=Phil Pennock" C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 CV=yes DN="C=UK,O=The Exim Maintainers,OU=Test Suite,CN=Phil Pennock" S=sss id=E10HmaX-0005vi-00@???
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <userx@???> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
diff --git a/test/log/4029 b/test/log/4029
new file mode 100644
index 0000000..a2ef850
--- /dev/null
+++ b/test/log/4029
@@ -0,0 +1,11 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local-esmtp S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 [127.0.0.1] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+1999-03-02 09:44:33 10HmaX-0005vi-00 [127.0.0.1] SSL verify error: certificate name mismatch: "/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock"
+1999-03-02 09:44:33 10HmaX-0005vi-00 => userx@??? R=client T=my_smtp H=127.0.0.1 [127.0.0.1] X=TLSv1:AES256-SHA:256 CV=no DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 CV=yes DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" S=sss id=E10HmaX-0005vi-00@???
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <userx@???> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
diff --git a/test/scripts/2000-GnuTLS/2000 b/test/scripts/2000-GnuTLS/2000
index c8dcb6a..a1299e5 100644
--- a/test/scripts/2000-GnuTLS/2000
+++ b/test/scripts/2000-GnuTLS/2000
@@ -1,7 +1,7 @@
# TLS client: TLS setup fails - retry in clear
#
# For this first GnuTLS test, we do not obey "gnutls", so that Exim has to
-# create the GnuTLS paramter data for itself.
+# create the GnuTLS parameter data for itself.
#
echo ==> Creating GnuTLS parameter data ...
exim -DSERVER=server -bd -oX PORT_D
diff --git a/test/scripts/2000-GnuTLS/2018 b/test/scripts/2000-GnuTLS/2018
index ac6049f..3f06e59 100644
--- a/test/scripts/2000-GnuTLS/2018
+++ b/test/scripts/2000-GnuTLS/2018
@@ -1,4 +1,4 @@
-# TLS: ACL encryption test
+# TLS ACL encryption test
gnutls
exim -DSERVER=server -bd -oX PORT_D
****
diff --git a/test/scripts/4000-scanning/4006 b/test/scripts/4000-scanning/4006
index 1adf8b1..a58188c 100644
--- a/test/scripts/4000-scanning/4006
+++ b/test/scripts/4000-scanning/4006
@@ -59,7 +59,7 @@ server DIR/eximdir/avast_sock
>LF>220 ready

<SCAN
>LF>210 SCAN DATA

->LF>b\ l\ a\ h    [L]9.9    9 VNAME
+>LF>b\\ l\\ a\\ h    [L]9.9    9 VNAME

>LF>200 SCAN OK

<QUIT
<*eof
diff --git a/test/scripts/4020-socks/4020 b/test/scripts/4020-socks/4020
new file mode 100644
index 0000000..49d97c0
--- /dev/null
+++ b/test/scripts/4020-socks/4020
@@ -0,0 +1,85 @@
+# socks5 proxy on smtp transport
+#
+munge loopback
+#
+# auth: null
+server PORT_S
+<<\x05\x01\x00
+>>\x05\x00
+<<\x05\x01\x00\x01\x7f\x00\x00\x01\x04\xc8
+>>\x05\x00\x00\x01\x7f\x00\x00\x01\xbe\xef
+220 Connected OK
+EHLO
+250-server id
+250
+MAIL FROM
+250
+RCPT TO
+250
+DATA
+354 hit me
+.
+250 accepted OK
+QUIT
+250 bye
+****
+#
+#
+#
+exim -odi -bs -DOPT=
+ehlo test.ex
+mail from:<>
+rcpt to:<userx@???>
+data
+Date: Fri, 17 Dec 2004 14:35:01 +0100
+Subject: message should be sent
+
+via null-auth proxy
+.
+quit
+****
+#
+#
+#
+# auth: username/password
+server PORT_S
+<<\x05\x01\x02
+>>\x05\x02
+<<\x01\x04fred\x05fubar
+>>\x01\x00
+<<\x05\x01\x00\x01\x7f\x00\x00\x01\x04\xc8
+>>\x05\x00\x00\x01\x7f\x00\x00\x01\xbe\xef
+220 Connected OK
+EHLO
+250-server id
+250
+MAIL FROM
+250
+RCPT TO
+250
+DATA
+354 hit me
+.
+250 accepted OK
+QUIT
+250 bye
+****
+#
+#
+#
+exim -odi -bs -DOPT="auth=name name=fred pass=fubar"
+ehlo test.ex
+mail from:<>
+rcpt to:<userx@???>
+data
+Date: Fri, 17 Dec 2004 14:35:01 +0100
+Subject: message should be sent
+
+via name/pwd-auth proxy
+.
+quit
+****
+#
+#
+#
+
diff --git a/test/scripts/4020-socks/REQUIRES b/test/scripts/4020-socks/REQUIRES
new file mode 100644
index 0000000..135603c
--- /dev/null
+++ b/test/scripts/4020-socks/REQUIRES
@@ -0,0 +1 @@
+support Experimental_SOCKS
diff --git a/test/scripts/4028-GnuTLS-socks/4028 b/test/scripts/4028-GnuTLS-socks/4028
new file mode 100644
index 0000000..1692bea
--- /dev/null
+++ b/test/scripts/4028-GnuTLS-socks/4028
@@ -0,0 +1,30 @@
+# socks5 proxy on smtp/starttls transport
+#
+munge loopback
+gnutls
+#
+# a TLS-capable server to receive the mail
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+#
+# THIS TEST ASSUMES we have a socks proxy
+# running and listening on 1080
+#
+# a mail sender
+exim -odi -bs -DOPT=
+ehlo test.ex
+mail from:<>
+rcpt to:<userx@???>
+data
+Date: Fri, 17 Dec 2004 14:35:01 +0100
+Subject: message should be sent
+
+via null-auth proxy
+.
+quit
+****
+#
+#
+killdaemon
+no_msglog_check
diff --git a/test/scripts/4028-GnuTLS-socks/REQUIRES b/test/scripts/4028-GnuTLS-socks/REQUIRES
new file mode 100644
index 0000000..0b41941
--- /dev/null
+++ b/test/scripts/4028-GnuTLS-socks/REQUIRES
@@ -0,0 +1,4 @@
+support Experimental_SOCKS
+support GnuTLS
+running IPv4
+running socks
diff --git a/test/scripts/4029-OpenSSL-socks/4029 b/test/scripts/4029-OpenSSL-socks/4029
new file mode 100644
index 0000000..ac87b37
--- /dev/null
+++ b/test/scripts/4029-OpenSSL-socks/4029
@@ -0,0 +1,30 @@
+# socks5 proxy on smtp/starttls transport
+#
+munge loopback
+#
+#
+# a TLS-capable server to receive the mail
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+#
+# THIS TEST ASSUMES we have a socks proxy
+# running and listening on 1080
+#
+# a mail sender
+exim -odi -bs -DOPT=
+ehlo test.ex
+mail from:<>
+rcpt to:<userx@???>
+data
+Date: Fri, 17 Dec 2004 14:35:01 +0100
+Subject: message should be sent
+
+via null-auth proxy
+.
+quit
+****
+#
+#
+killdaemon
+no_msglog_check
diff --git a/test/scripts/4029-OpenSSL-socks/REQUIRES b/test/scripts/4029-OpenSSL-socks/REQUIRES
new file mode 100644
index 0000000..b24bbd9
--- /dev/null
+++ b/test/scripts/4029-OpenSSL-socks/REQUIRES
@@ -0,0 +1,4 @@
+support Experimental_SOCKS
+support OpenSSL
+running IPv4
+running socks
diff --git a/test/src/server.c b/test/src/server.c
index f4173ec..e425880 100644
--- a/test/src/server.c
+++ b/test/src/server.c
@@ -57,6 +57,7 @@ on all interfaces, unless the option -noipv6 is given. */

typedef struct line {
struct line *next;
+ unsigned len;
char line[1];
} line;

@@ -123,6 +124,25 @@ return buffer;
}


+
+static void
+printit(char * s, int n)
+{
+while(n--)
+  {
+  unsigned char c = *s++;
+  if (c == '\\')
+    printf("\\\\");
+  else if (c >= ' ' && c <= '~')    /* assumes ascii */
+    putchar(c);
+  else
+    printf("\\x%02x", c);
+  }
+putchar('\n');
+}
+
+
+
 /*************************************************
 *                 Main Program                   *
 *************************************************/
@@ -152,6 +172,7 @@ line *script = NULL;
 line *last = NULL;
 line *s;
 FILE *in, *out;
+int linebuf = 1;


char *sockname = NULL;
unsigned char buffer[10240];
@@ -394,18 +415,39 @@ script of things to do. A line containing "++++" is treated as end of file.
This is so that the Perl driving script doesn't have to close the pipe -
because that would cause it to wait for this process, which it doesn't yet want
to do. The driving script adds the "++++" automatically - it doesn't actually
-appear in the test script. */
+appear in the test script. Within lines we interpret \xNN and \\ groups */

 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
   {
   line *next;
+  char * d;
   int n = (int)strlen(CS buffer);
+
+  if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
+    linebuf = 0;
   while (n > 0 && isspace(buffer[n-1])) n--;
   buffer[n] = 0;
   if (strcmp(CS buffer, "++++") == 0) break;
   next = malloc(sizeof(line) + n);
   next->next = NULL;
-  strcpy(next->line, CS buffer);
+  d = next->line;
+    {
+    char * s = CS buffer;
+    do
+      {
+      char ch;
+      char cl = *s;
+      if (cl == '\\' && (cl = *++s) == 'x')
+    {
+    if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
+    if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
+    cl |= ch << 4;
+    }
+      *d++ = cl;
+      }
+    while (*s++);
+    }
+  next->len = d - next->line - 1;
   if (last == NULL) script = last = next;
     else last->next = next;
   last = next;
@@ -529,7 +571,8 @@ for (count = 0; count < connection_count; count++)
     if (ss[0] == '>')
       {
       char *end = "\r\n";
-      printf("%s\n", ss++);
+      unsigned len = s->len;
+      printit(ss++, len--);


       if (strncmp(ss, "*eof", 4) == 0)
         {
@@ -538,13 +581,14 @@ for (count = 0; count < connection_count; count++)
         }


       if (*ss == '>')
-        { end = ""; ss++; }
+        { end = ""; ss++; len--; }
       else if (strncmp(ss, "CR>", 3) == 0)
-        { end = "\r"; ss += 3; }
+        { end = "\r"; ss += 3; len -= 3; }
       else if (strncmp(ss, "LF>", 3) == 0)
-        { end = "\n"; ss += 3; }
+        { end = "\n"; ss += 3; len -= 3; }


-      fprintf(out, "%s%s", ss, end);
+      fwrite(ss, 1, len, out);
+      if (*end) fprintf(out, end);
       }


     else if (isdigit((unsigned char)ss[0]))
@@ -569,47 +613,93 @@ for (count = 0; count < connection_count; count++)
     connection. Read command line or data lines; the latter are indicated
     by the expected line being just ".". If the line starts with '<', that
     doesn't form part of the expected input. (This allows for incoming data
-    starting with a digit.) */
+    starting with a digit.) If the line starts with '<<' we operate in
+    unbuffered rather than line mode and assume that a single read gets the
+    entire message. */


     else
       {
       int offset;
       int data = strcmp(ss, ".") == 0;


-      if (ss[0] == '<')
+      if (ss[0] != '<')
+    offset = 0;
+      else
         {
         buffer[0] = '<';
-        offset = 1;
+    if (ss[1] != '<')
+      offset = 1;
+    else
+      {
+      buffer[1] = '<';
+      offset = 2;
+      }
         }
-      else offset = 0;


       fflush(out);


-      for (;;)
-        {
-        int n;
-        alarm(timeout);
-        if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
-          {
-          printf("%sxpected EOF read from client\n",
-            (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
-          s = s->next;
-          goto END_OFF;
-          }
-        alarm(0);
-        n = (int)strlen(CS buffer);
-        while (n > 0 && isspace(buffer[n-1])) n--;
-        buffer[n] = 0;
-        printf("%s\n", buffer);
-        if (!data || strcmp(CS buffer, ".") == 0) break;
-        }
-
-      if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
-        {
-        printf("Comparison failed - bailing out\n");
-        printf("Expected: %s\n", ss);
-        break;
-        }
+      if (!linebuf)
+    {
+    int n;
+    char c;
+
+    alarm(timeout);
+    n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
+    if (n == 0)
+      {
+      printf("%sxpected EOF read from client\n",
+        (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
+      s = s->next;
+      goto END_OFF;
+      }
+    if (offset != 2)
+      while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
+    alarm(0);
+    n += offset;
+
+    printit(buffer, n);
+
+    if (data) do
+      {
+      n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
+      while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
+        ;
+      } while (!n);
+    else if (memcmp(ss, buffer, n) != 0)
+      {
+      printf("Comparison failed - bailing out\nExpected: ");
+      printit(ss, n);
+      break;
+      }
+    }
+      else
+    {
+    for (;;)
+      {
+      int n;
+      alarm(timeout);
+      if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
+        {
+        printf("%sxpected EOF read from client\n",
+          (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
+        s = s->next;
+        goto END_OFF;
+        }
+      alarm(0);
+      n = (int)strlen(CS buffer);
+      while (n > 0 && isspace(buffer[n-1])) n--;
+      buffer[n] = 0;
+      printf("%s\n", buffer);
+      if (!data || strcmp(CS buffer, ".") == 0) break;
+      }
+
+    if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
+      {
+      printf("Comparison failed - bailing out\n");
+      printf("Expected: %s\n", ss);
+      break;
+      }
+    }
       }
     }


diff --git a/test/stdout/0259 b/test/stdout/0259
index c4f4ea0..b65d884 100644
--- a/test/stdout/0259
+++ b/test/stdout/0259
@@ -69,7 +69,7 @@ End of script
 Listening on port 1413 ... 
 Connection request from [127.0.0.1]
 <999 , 25
->999 , 25 : USERID : UNIX :ab
cd
+>999 , 25 : USERID : UNIX :ab\x0dcd
 End of script
 Listening on port 1413 ... 
 Connection request from [127.0.0.1]
diff --git a/test/stdout/4003 b/test/stdout/4003
index f27d192..e705096 100644
--- a/test/stdout/4003
+++ b/test/stdout/4003
@@ -68,7 +68,7 @@ Connection request
 <SCAN    TESTSUITE/spool/scan/10HmbB-0005vi-00/10HmbB-0005vi-00.eml

>LF>random ignored line
>LF>random ignored line 2

->LF>OK    Scan ok.
+>LF>OK\x09Scan ok.
 Expected EOF read from client
 End of script
 Listening on TESTSUITE/eximdir/fsec_sock ... 
@@ -82,8 +82,8 @@ Connection request
 <CONFIGURE    MIME    1

>ignored_response

 <SCAN    TESTSUITE/spool/scan/10HmaZ-0005vi-00/10HmaZ-0005vi-00.eml
->LF>xxxINFECTED    blah    VNAME    blah
->LF>OK    Scan ok.
+>LF>xxxINFECTED\x09blah\x09VNAME\x09blah
+>LF>OK\x09Scan ok.
 Expected EOF read from client
 End of script
 Listening on TESTSUITE/eximdir/fsec_sock ... 
@@ -105,7 +105,7 @@ Connection request
 <CONFIGURE    MIME    1

>ignored_response

 <SCAN    TESTSUITE/spool/scan/10HmbA-0005vi-00/10HmbA-0005vi-00.eml
->LF>xxxINFECTED    blah    VNAME    blah
->LF>OK    Scan ok.
+>LF>xxxINFECTED\x09blah\x09VNAME\x09blah
+>LF>OK\x09Scan ok.
 Expected EOF read from client
 End of script
diff --git a/test/stdout/4006 b/test/stdout/4006
index bd18c95..19e0f30 100644
--- a/test/stdout/4006
+++ b/test/stdout/4006
@@ -63,7 +63,7 @@ Connection request

>LF>200 FLAGS OK

<SCAN TESTSUITE/spool/scan/10HmbB-0005vi-00
>LF>210 SCAN DATA

->LF>blah    [+]
+>LF>blah\x09[+]

>LF>200 SCAN OK

<QUIT
Unexpected EOF read from client
@@ -73,7 +73,7 @@ Connection request
>LF>220 ready

<SCAN TESTSUITE/spool/scan/10HmaX-0005vi-00
>LF>210 SCAN DATA

->LF>blah    [E]
+>LF>blah\x09[E]

>LF>200 SCAN OK

Unexpected EOF read from client
Listening on TESTSUITE/eximdir/avast_sock ...
@@ -81,7 +81,7 @@ Connection request
>LF>220 ready

<SCAN TESTSUITE/spool/scan/10HmbA-0005vi-00
>LF>210 SCAN DATA

->LF>b\ l\ a\ h    [L]9.9    9 VNAME
+>LF>b\\ l\\ a\\ h\x09[L]9.9\x099 VNAME

>LF>200 SCAN OK

Unexpected EOF read from client
Listening on TESTSUITE/eximdir/avast_sock ...
diff --git a/test/stdout/4020 b/test/stdout/4020
new file mode 100644
index 0000000..720c954
--- /dev/null
+++ b/test/stdout/4020
@@ -0,0 +1,68 @@
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaX-0005vi-00
+221 myhost.test.ex closing connection
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaY-0005vi-00
+221 myhost.test.ex closing connection
+
+******** SERVER ********
+Listening on port 1224 ...
+Connection request from [ip4.ip4.ip4.ip4]
+<<\x05\x01\x00
+>>\x05\x00
+<<\x05\x01\x00\x01\x7f\x00\x00\x01\x04\xc8
+>>\x05\x00\x00\x01\x7f\x00\x00\x01\xbe\xef
+220 Connected OK
+EHLO
+250-server id
+250
+MAIL FROM
+250
+RCPT TO
+250
+DATA
+354 hit me
+R
+250 accepted OK
+QUIT
+250 bye
+End of script
+Listening on port 1224 ...
+Connection request from [ip4.ip4.ip4.ip4]
+<<\x05\x01\x02
+>>\x05\x02
+<<\x01\x04fred\x05fubar
+>>\x01\x00
+<<\x05\x01\x00\x01\x7f\x00\x00\x01\x04\xc8
+>>\x05\x00\x00\x01\x7f\x00\x00\x01\xbe\xef
+220 Connected OK
+EHLO
+250-server id
+250
+MAIL FROM
+250
+RCPT TO
+250
+DATA
+354 hit me
+R
+250 accepted OK
+QUIT
+250 bye
+End of script
diff --git a/test/stdout/4028 b/test/stdout/4028
new file mode 100644
index 0000000..9c94d76
--- /dev/null
+++ b/test/stdout/4028
@@ -0,0 +1,12 @@
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250-STARTTLS
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaX-0005vi-00
+221 myhost.test.ex closing connection
diff --git a/test/stdout/4029 b/test/stdout/4029
new file mode 100644
index 0000000..9c94d76
--- /dev/null
+++ b/test/stdout/4029
@@ -0,0 +1,12 @@
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+250-myhost.test.ex Hello CALLER at test.ex
+250-SIZE 52428800
+250-8BITMIME
+250-PIPELINING
+250-STARTTLS
+250 HELP
+250 OK
+250 Accepted
+354 Enter message, ending with "." on a line by itself
+250 OK id=10HmaX-0005vi-00
+221 myhost.test.ex closing connection