[Git][debian-proftpd-team/proftpd-mod-proxy][master] 3 commits: New upstream version 0.7

Hilmar Preuße gitlab at salsa.debian.org
Sat Oct 31 23:09:48 GMT 2020



Hilmar Preuße pushed to branch master at Debian ProFTPD Team / proftpd-mod-proxy


Commits:
21d27214 by Hilmar Preusse at 2020-10-31T23:49:45+01:00
New upstream version 0.7
- - - - -
eacbf524 by Hilmar Preusse at 2020-10-31T23:49:45+01:00
Update upstream source from tag 'upstream/0.7'

Update to upstream version '0.7'
with Debian dir 96d0f124a33625ba145ddc9b5d1209f55f0263d9
- - - - -
6d2dc178 by Hilmar Preusse at 2020-11-01T00:08:14+01:00
New upstream release 0.7.

- - - - -


20 changed files:

- .gitignore
- debian/changelog
- include/proxy/ftp/ctrl.h
- include/proxy/tls.h
- lib/proxy/conn.c
- lib/proxy/forward.c
- lib/proxy/ftp/ctrl.c
- lib/proxy/ftp/sess.c
- lib/proxy/inet.c
- lib/proxy/reverse.c
- lib/proxy/tls.c
- lib/proxy/uri.c
- mod_proxy.c
- mod_proxy.h.in
- mod_proxy.html
- t/api/conn.c
- t/api/tls.c
- t/lib/ProFTPD/Tests/Modules/mod_proxy.pm
- t/lib/ProFTPD/Tests/Modules/mod_proxy/sql.pm
- t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm


Changes:

=====================================
.gitignore
=====================================
@@ -5,6 +5,8 @@ config.status
 autom4te.cache
 mod_proxy.h
 tests.log
+t/api-tests
+t/api-tests.log
 .libs
 *.a
 *.sw?


=====================================
debian/changelog
=====================================
@@ -1,3 +1,12 @@
+proftpd-mod-proxy (0.7-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Support for implicit FTPS
+    - Support proxying of FTP data transfer aborts
+    - Fixed TLS session truncation for FTPS uploads
+
+ -- Hilmar Preusse <hille42 at web.de>  Sun, 01 Nov 2020 00:05:19 +0100
+
 proftpd-mod-proxy (0.6-1) unstable; urgency=medium
 
   * New upstream release.


=====================================
include/proxy/ftp/ctrl.h
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy FTP control conn API
- * Copyright (c) 2012-2016 TJ Saunders
+ * Copyright (c) 2012-2020 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@ int proxy_ftp_ctrl_handle_async(pool *p, conn_t *backend_conn,
 
 pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn,
   unsigned int *resp_nlines, int flags);
+int proxy_ftp_ctrl_send_abort(pool *p, conn_t *ctrl_conn, cmd_rec *cmd);
 int proxy_ftp_ctrl_send_cmd(pool *p, conn_t *ctrl_conn, cmd_rec *cmd);
 int proxy_ftp_ctrl_send_resp(pool *p, conn_t *ctrl_conn, pr_response_t *resp,
   unsigned int resp_nlines);


=====================================
include/proxy/tls.h
=====================================
@@ -51,6 +51,9 @@
 #define PROXY_TLS_ENGINE_ON		1
 #define PROXY_TLS_ENGINE_OFF		2
 #define PROXY_TLS_ENGINE_AUTO		3
+#define PROXY_TLS_ENGINE_IMPLICIT	4
+
+#define PROXY_TLS_IMPLICIT_FTPS_PORT	990
 
 /* ProxyTLSOptions values */
 #define PROXY_TLS_OPT_ENABLE_DIAGS		0x0001


=====================================
lib/proxy/conn.c
=====================================
@@ -143,13 +143,18 @@ const struct proxy_conn *proxy_conn_create(pool *p, const char *uri) {
     return NULL;
   }
 
-  if (strncmp(proto, "ftps", 5) == 0) {
+  if (strcmp(proto, "ftps") == 0) {
     /* If the 'ftps' scheme is used, then FTPS is REQUIRED for connections
      * to this server.
      */
     use_tls = PROXY_TLS_ENGINE_ON;
 
-  } else if (strncmp(proto, "sftp", 5) == 0) {
+    /* We automatically (and only) use implicit FTPS for port 990. */
+    if (remote_port == PROXY_TLS_IMPLICIT_FTPS_PORT) {
+      use_tls = PROXY_TLS_ENGINE_IMPLICIT;
+    }
+
+  } else if (strcmp(proto, "sftp") == 0) {
     /* As might be obvious, do not try to use TLS against an SSH2/SFTP
      * server.
      */
@@ -469,7 +474,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
 
   if (res == 0) {
     pr_netio_stream_t *nstrm;
-    int connected = FALSE, nstrm_mode = PR_NETIO_IO_RD;
+    int connected = FALSE, nstrm_mode = PR_NETIO_IO_RD, use_tls;
 
     if ((proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V1) ||
         (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2)) {
@@ -480,6 +485,14 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
       nstrm_mode = PR_NETIO_IO_WR;
     }
 
+    use_tls = proxy_tls_using_tls();
+    if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) {
+      /* For implicit FTPS connections, we will be initiating the TLS
+       * handshake, and thus we need to wait for the stream to be writable.
+       */
+      nstrm_mode = PR_NETIO_IO_WR;
+    }
+
     /* Not yet connected. */
     nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, server_conn->listen_fd,
       nstrm_mode);
@@ -499,7 +512,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
 
     proxy_netio_set_poll_interval(nstrm, 1);
 
-    while (!connected) {
+    while (connected == FALSE) {
       int polled;
 
       pr_signals_handle();


=====================================
lib/proxy/forward.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy forward proxy implementation
- * Copyright (c) 2012-2016 TJ Saunders
+ * Copyright (c) 2012-2020 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -152,16 +152,60 @@ int proxy_forward_have_authenticated(cmd_rec *cmd) {
   return authd;
 }
 
+static int forward_tls_postopen(pool *p, struct proxy_session *proxy_sess,
+    conn_t *server_conn, pr_response_t **resp) {
+  int xerrno;
+
+  if (proxy_netio_postopen(server_conn->instrm) < 0) {
+    xerrno = errno;
+
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "postopen error for backend control connection input stream: %s",
+      strerror(xerrno));
+    proxy_inet_close(session.pool, server_conn);
+    proxy_sess->backend_ctrl_conn = NULL;
+
+    *resp = NULL;
+    errno = xerrno;
+    return -1;
+  }
+
+  if (proxy_netio_postopen(server_conn->outstrm) < 0) {
+    xerrno = errno;
+
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "postopen error for backend control connection output stream: %s",
+      strerror(xerrno));
+    proxy_inet_close(session.pool, server_conn);
+    proxy_sess->backend_ctrl_conn = NULL;
+
+    *resp = NULL;
+    errno = xerrno;
+    return -1;
+  }
+
+  return 0;
+}
+
 static int forward_connect(pool *p, struct proxy_session *proxy_sess,
     pr_response_t **resp, unsigned int *resp_nlines) {
   conn_t *server_conn = NULL;
   int banner_ok = TRUE, use_tls, xerrno = 0;
   const pr_netaddr_t *dst_addr;
   array_header *other_addrs = NULL;
+  char port_text[32];
 
   dst_addr = proxy_sess->dst_addr;
   other_addrs = proxy_sess->other_addrs;
 
+  /* If the destination port is 990, assume implicit FTPS. */
+  if (ntohs(pr_netaddr_get_port(dst_addr)) == PROXY_TLS_IMPLICIT_FTPS_PORT) {
+    pr_trace_msg(trace_channel, 9, "%s#%u requesting, using implicit FTPS",
+      pr_netaddr_get_ipstr(dst_addr),
+      (unsigned int) ntohs(pr_netaddr_get_port(dst_addr)));
+    proxy_tls_set_tls(PROXY_TLS_ENGINE_IMPLICIT);
+  }
+
   server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr);
   if (server_conn == NULL) {
     xerrno = errno;
@@ -200,13 +244,22 @@ static int forward_connect(pool *p, struct proxy_session *proxy_sess,
     return -1;
   }
 
+  proxy_sess->frontend_ctrl_conn = session.c;
+  proxy_sess->backend_ctrl_conn = server_conn;
+
+  use_tls = proxy_tls_using_tls();
+
+  /* Handle implicit FTPS connects. */
+  if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) {
+    if (forward_tls_postopen(p, proxy_sess, server_conn, resp) < 0) {
+      return -1;
+    }
+  }
+
   /* XXX Support/send a CLNT command of our own?  Configurable via e.g.
    * "UserAgent" string?
    */
 
-  proxy_sess->frontend_ctrl_conn = session.c;
-  proxy_sess->backend_ctrl_conn = server_conn;
-
   /* Read the response from the backend server. */
   *resp = proxy_ftp_ctrl_recv_resp(p, proxy_sess->backend_ctrl_conn,
     resp_nlines, 0);
@@ -250,7 +303,8 @@ static int forward_connect(pool *p, struct proxy_session *proxy_sess,
   }
 
   use_tls = proxy_tls_using_tls();
-  if (use_tls != PROXY_TLS_ENGINE_OFF) {
+  if (use_tls != PROXY_TLS_ENGINE_OFF &&
+      use_tls != PROXY_TLS_ENGINE_IMPLICIT) {
     if (proxy_ftp_sess_send_auth_tls(p, proxy_sess) < 0 &&
         errno != ENOSYS) {
       xerrno = errno;
@@ -269,32 +323,11 @@ static int forward_connect(pool *p, struct proxy_session *proxy_sess,
     use_tls = proxy_tls_using_tls();
   }
 
-  if (proxy_netio_postopen(server_conn->instrm) < 0) {
-    xerrno = errno;
-
-    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
-      "postopen error for backend control connection input stream: %s",
-      strerror(xerrno));
-    proxy_inet_close(session.pool, server_conn);
-    proxy_sess->backend_ctrl_conn = NULL;
-
-    *resp = NULL;
-    errno = xerrno;
-    return -1;
-  }
-
-  if (proxy_netio_postopen(server_conn->outstrm) < 0) {
-    xerrno = errno;
-
-    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
-      "postopen error for backend control connection output stream: %s",
-      strerror(xerrno));
-    proxy_inet_close(session.pool, server_conn);
-    proxy_sess->backend_ctrl_conn = NULL;
-
-    *resp = NULL;
-    errno = xerrno;
-    return -1;
+  if (use_tls != PROXY_TLS_ENGINE_OFF &&
+      use_tls != PROXY_TLS_ENGINE_IMPLICIT) {
+    if (forward_tls_postopen(p, proxy_sess, server_conn, resp) < 0) {
+      return -1;
+    }
   }
 
   if (use_tls != PROXY_TLS_ENGINE_OFF) {
@@ -306,6 +339,18 @@ static int forward_connect(pool *p, struct proxy_session *proxy_sess,
 
   (void) proxy_ftp_sess_send_host(p, proxy_sess);
 
+  /* Populate the session notes about this connection. */
+  memset(port_text, '\0', sizeof(port_text));
+  pr_snprintf(port_text, sizeof(port_text)-1, "%d",
+    proxy_conn_get_port(proxy_sess->dst_pconn));
+  (void) pr_table_add_dup(session.notes, "mod_proxy.backend-ip",
+    pr_netaddr_get_ipstr(dst_addr), 0);
+  (void) pr_table_remove(session.notes, "mod_proxy.backend-port", NULL);
+  (void) pr_table_add_dup(session.notes, "mod_proxy.backend-port",
+    port_text, 0);
+  (void) pr_table_add_dup(session.notes, "mod_proxy.backend-url",
+    proxy_conn_get_uri(proxy_sess->dst_pconn), 0);
+
   proxy_sess_state |= PROXY_SESS_STATE_CONNECTED;
   return 0;
 }


=====================================
lib/proxy/ftp/ctrl.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy FTP control conn routines
- * Copyright (c) 2012-2016 TJ Saunders
+ * Copyright (c) 2012-2020 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
 
 #include "proxy/netio.h"
 #include "proxy/ftp/ctrl.h"
+#include "proxy/tls.h"
 
 static const char *trace_channel = "proxy.ftp.ctrl";
 
@@ -366,6 +367,84 @@ pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn,
   return resp;
 }
 
+#ifndef TELNET_DM
+# define TELNET_DM	242
+#endif /* TELNET_DM */
+
+#ifndef TELNET_IAC
+# define TELNET_IAC	255
+#endif /* TELNET_IAC */
+
+#ifndef TELNET_IP
+# define TELNET_IP	244
+#endif /* TELNET_IP */
+
+int proxy_ftp_ctrl_send_abort(pool *p, conn_t *ctrl_conn, cmd_rec *cmd) {
+  int fd, res, use_tls, xerrno;
+  unsigned char buf[7];
+
+  if (p == NULL ||
+      ctrl_conn == NULL ||
+      cmd == NULL) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  /* If we are proxying the ABOR command, preface it with the Telnet "Sync"
+   * mechanism, using OOB data.  If the receiving server supports this, it can
+   * generate a signal to interrupt any IO occurring on the backend server
+   * (such as when sendfile(2) is used).
+   *
+   * Note that such Telnet codes can only be used if we are NOT using TLS
+   * on the backend control connection.
+   */
+  use_tls = proxy_tls_using_tls();
+  if (use_tls != PROXY_TLS_ENGINE_OFF) {
+    return proxy_ftp_ctrl_send_cmd(p, ctrl_conn, cmd);
+  }
+
+  fd = PR_NETIO_FD(ctrl_conn->outstrm);
+
+  buf[0] = TELNET_IAC;
+  buf[1] = TELNET_IP;
+  buf[2] = TELNET_IAC;
+
+  pr_trace_msg(trace_channel, 9,
+    "sending Telnet abort code out-of-band to backend");
+  res = send(fd, &buf, 3, MSG_OOB);
+  xerrno = errno;
+
+  if (res < 0) {
+    pr_trace_msg(trace_channel, 1,
+      "error sending Telnet abort code out-of-band to backend: %s",
+      strerror(xerrno));
+    errno = xerrno;
+    return -1;
+  }
+
+  buf[0] = TELNET_DM;
+  buf[1] = 'A';
+  buf[2] = 'B';
+  buf[3] = 'O';
+  buf[4] = 'R';
+  buf[5] = '\r';
+  buf[6] = '\n';
+
+  pr_trace_msg(trace_channel, 9,
+    "proxied %s command from frontend to backend", (char *) cmd->argv[0]);
+  res = send(fd, &buf, 7, 0);
+  xerrno = errno;
+
+  if (res < 0) {
+    pr_trace_msg(trace_channel, 1,
+      "error sending Telnet DM code to backend: %s", strerror(xerrno));
+    errno = xerrno;
+    return -1;
+  }
+
+  return 0;
+}
+
 int proxy_ftp_ctrl_send_cmd(pool *p, conn_t *ctrl_conn, cmd_rec *cmd) {
   int res;
 


=====================================
lib/proxy/ftp/sess.c
=====================================
@@ -307,7 +307,13 @@ int proxy_ftp_sess_send_auth_tls(pool *p,
   use_tls = proxy_tls_using_tls();
   if (use_tls == PROXY_TLS_ENGINE_OFF) {
     pr_trace_msg(trace_channel, 19,
-      "TLS support not enabled/desired, skipping");
+      "TLS support not enabled/desired, skipping 'AUTH TLS' command");
+    return 0;
+  }
+
+  if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) {
+    pr_trace_msg(trace_channel, 19,
+      "implicit FTPS support requested, skipping 'AUTH TLS' command");
     return 0;
   }
 


=====================================
lib/proxy/inet.c
=====================================
@@ -72,14 +72,21 @@ void proxy_inet_close(pool *p, conn_t *conn) {
      * functions for closing, too.
      */
 
+    /* Shutdowns first, then closes. */
     if (conn->instrm != NULL) {
       proxy_netio_shutdown(conn->instrm, 0);
+    }
+
+    if (conn->outstrm != NULL) {
+      proxy_netio_shutdown(conn->outstrm, 1);
+    }
+
+    if (conn->instrm != NULL) {
       proxy_netio_close(conn->instrm);
       conn->instrm = NULL;
     }
 
     if (conn->outstrm != NULL) {
-      proxy_netio_shutdown(conn->outstrm, 1);
       proxy_netio_close(conn->outstrm);
       conn->outstrm = NULL;
     }


=====================================
lib/proxy/reverse.c
=====================================
@@ -698,9 +698,52 @@ static const struct proxy_conn *get_reverse_server_conn(pool *p,
   return pconn;
 }
 
+static int reverse_tls_postopen(pool *p, struct proxy_session *proxy_sess,
+    conn_t *server_conn) {
+  int xerrno;
+
+  if (proxy_netio_postopen(server_conn->instrm) < 0) {
+    xerrno = errno;
+
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "postopen error for backend control connection input stream: %s",
+      strerror(xerrno));
+    proxy_inet_close(session.pool, server_conn);
+    proxy_sess->backend_ctrl_conn = NULL;
+
+    pr_response_block(FALSE);
+
+    /* Note that we explicitly return EINVAL here, to indicate to the calling
+     * code in mod_proxy that it should return e.g. "Login incorrect."
+     */
+    errno = EINVAL;
+    return -1;
+  }
+
+  if (proxy_netio_postopen(server_conn->outstrm) < 0) {
+    xerrno = errno;
+
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "postopen error for backend control connection output stream: %s",
+      strerror(xerrno));
+    proxy_inet_close(session.pool, server_conn);
+    proxy_sess->backend_ctrl_conn = NULL;
+
+    pr_response_block(FALSE);
+
+    /* Note that we explicitly return EINVAL here, to indicate to the calling
+     * code in mod_proxy that it should return e.g. "Login incorrect."
+     */
+    errno = EINVAL;
+    return -1;
+  }
+
+  return 0;
+}
+
 static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
     const void *connect_data) {
-  int backend_id = -1, use_tls, xerrno = 0;
+  int backend_id = -1, uri_tls, use_tls, xerrno = 0;
   conn_t *server_conn = NULL;
   pr_response_t *resp = NULL;
   unsigned int resp_nlines = 0;
@@ -708,6 +751,7 @@ static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
   const pr_netaddr_t *dst_addr;
   array_header *other_addrs = NULL;
   uint64_t connecting_ms, connected_ms;
+  char port_text[32];
 
   pconn = get_reverse_server_conn(p, proxy_sess, &backend_id, connect_data);
   if (pconn == NULL) {
@@ -719,6 +763,14 @@ static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
   proxy_sess->dst_pconn = pconn;
   proxy_sess->other_addrs = other_addrs;
 
+  uri_tls = proxy_conn_get_tls(pconn);
+  if (uri_tls == PROXY_TLS_ENGINE_IMPLICIT) {
+    pr_trace_msg(trace_channel, 9, "%s#%u requesting, using implicit FTPS",
+      pr_netaddr_get_ipstr(dst_addr),
+      (unsigned int) ntohs(pr_netaddr_get_port(dst_addr)));
+    proxy_tls_set_tls(uri_tls);
+  }
+
   pr_gettimeofday_millis(&connecting_ms);
   server_conn = proxy_conn_get_server_conn(p, proxy_sess, dst_addr);
   if (server_conn == NULL) {
@@ -794,15 +846,22 @@ static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
     }
   }
 
-  /* XXX Support/send a CLNT command of our own?  Configurable via e.g.
-   * "UserAgent" string?
-   */
-
   proxy_sess->frontend_ctrl_conn = session.c;
   proxy_sess->backend_ctrl_conn = server_conn;
 
   use_tls = proxy_tls_using_tls();
 
+  /* Handle implicit FTPS connects. */
+  if (use_tls == PROXY_TLS_ENGINE_IMPLICIT) {
+    if (reverse_tls_postopen(p, proxy_sess, server_conn) < 0) {
+      return -1;
+    }
+  }
+
+  /* XXX Support/send a CLNT command of our own?  Configurable via e.g.
+   * "UserAgent" string?
+   */
+
   resp = proxy_ftp_ctrl_recv_resp(p, server_conn, &resp_nlines, 0);
   if (resp == NULL) {
     xerrno = errno;
@@ -860,7 +919,8 @@ static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
 
   pr_response_block(TRUE);
 
-  if (use_tls != PROXY_TLS_ENGINE_OFF) {
+  if (use_tls != PROXY_TLS_ENGINE_OFF &&
+      use_tls != PROXY_TLS_ENGINE_IMPLICIT) {
     if (proxy_ftp_sess_send_auth_tls(p, proxy_sess) < 0 &&
         errno != ENOSYS) {
       xerrno = errno;
@@ -879,40 +939,11 @@ static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
     use_tls = proxy_tls_using_tls();
   }
 
-  if (proxy_netio_postopen(server_conn->instrm) < 0) {
-    xerrno = errno;
-
-    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
-      "postopen error for backend control connection input stream: %s",
-      strerror(xerrno));
-    proxy_inet_close(session.pool, server_conn);
-    proxy_sess->backend_ctrl_conn = NULL;
-
-    pr_response_block(FALSE);
-
-    /* Note that we explicitly return EINVAL here, to indicate to the calling
-     * code in mod_proxy that it should return e.g. "Login incorrect."
-     */
-    errno = EINVAL;
-    return -1;
-  }
-
-  if (proxy_netio_postopen(server_conn->outstrm) < 0) {
-    xerrno = errno;
-
-    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
-      "postopen error for backend control connection output stream: %s",
-      strerror(xerrno));
-    proxy_inet_close(session.pool, server_conn);
-    proxy_sess->backend_ctrl_conn = NULL;
-
-    pr_response_block(FALSE);
-
-    /* Note that we explicitly return EINVAL here, to indicate to the calling
-     * code in mod_proxy that it should return e.g. "Login incorrect."
-     */
-    errno = EINVAL;
-    return -1;
+  if (use_tls != PROXY_TLS_ENGINE_OFF &&
+      use_tls != PROXY_TLS_ENGINE_IMPLICIT) {
+    if (reverse_tls_postopen(p, proxy_sess, server_conn) < 0) {
+      return -1;
+    }
   }
 
   if (use_tls != PROXY_TLS_ENGINE_OFF) {
@@ -937,6 +968,18 @@ static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
 
   (void) proxy_ftp_sess_send_host(p, proxy_sess);
 
+  /* Populate the session notes about this connection. */
+  memset(port_text, '\0', sizeof(port_text));
+  pr_snprintf(port_text, sizeof(port_text)-1, "%d",
+    proxy_conn_get_port(proxy_sess->dst_pconn));
+  (void) pr_table_add_dup(session.notes, "mod_proxy.backend-ip",
+    pr_netaddr_get_ipstr(dst_addr), 0);
+  (void) pr_table_remove(session.notes, "mod_proxy.backend-port", NULL);
+  (void) pr_table_add_dup(session.notes, "mod_proxy.backend-port",
+    port_text, 0);
+  (void) pr_table_add_dup(session.notes, "mod_proxy.backend-url",
+    proxy_conn_get_uri(proxy_sess->dst_pconn), 0);
+
   proxy_sess_state |= PROXY_SESS_STATE_CONNECTED;
   return 0;
 }


=====================================
lib/proxy/tls.c
=====================================
@@ -2387,7 +2387,8 @@ int proxy_tls_set_tls(int engine) {
 #ifdef PR_USE_OPENSSL
   if (engine != PROXY_TLS_ENGINE_ON &&
       engine != PROXY_TLS_ENGINE_OFF &&
-      engine != PROXY_TLS_ENGINE_AUTO) {
+      engine != PROXY_TLS_ENGINE_AUTO &&
+      engine != PROXY_TLS_ENGINE_IMPLICIT) {
     errno = EINVAL;
     return -1;
   }
@@ -2504,6 +2505,9 @@ static int get_disabled_protocols(unsigned int supported_protocols) {
 # ifdef SSL_OP_NO_TLSv1_2
   disabled_protocols |= SSL_OP_NO_TLSv1_2;
 # endif
+# ifdef SSL_OP_NO_TLSv1_3
+  disabled_protocols |= SSL_OP_NO_TLSv1_3;
+# endif
 
   /* Now, based on the given bitset of supported protocols, clear the
    * necessary bits.
@@ -3749,7 +3753,7 @@ static void tls_tlsext_cb(SSL *ssl, int server, int type,
     case TLSEXT_TYPE_supported_versions: {
       BIO *bio = NULL;
       char *ext_info = NULL;
-      long ext_infolen;
+      long ext_infolen = 0;
 
       /* If we are the server responding, we only indicate the selected
        * protocol version.  Otherwise, we are a client indicating the range
@@ -3819,7 +3823,7 @@ static void tls_tlsext_cb(SSL *ssl, int server, int type,
     case TLSEXT_TYPE_psk_kex_modes: {
       BIO *bio = NULL;
       char *ext_info = NULL;
-      long ext_infolen;
+      long ext_infolen = 0;
 
       extension_name = "PSK KEX modes";
 


=====================================
lib/proxy/uri.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy URI implementation
- * Copyright (c) 2012-2016 TJ Saunders
+ * Copyright (c) 2012-2020 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -286,11 +286,11 @@ int proxy_uri_parse(pool *p, const char *uri, char **scheme, char **host,
   if (ptr2 == NULL) {
     *host = uri_parse_host(p, uri, ptr, NULL);
 
-    if (strncmp(*scheme, "ftp", 4) == 0 ||
-        strncmp(*scheme, "ftps", 5) == 0) {
+    if (strcmp(*scheme, "ftp") == 0 ||
+        strcmp(*scheme, "ftps") == 0) {
       *port = 21;
 
-    } else if (strncmp(*scheme, "sftp", 5) == 0) {
+    } else if (strcmp(*scheme, "sftp") == 0) {
       *port = 22;
 
     } else {
@@ -312,11 +312,11 @@ int proxy_uri_parse(pool *p, const char *uri, char **scheme, char **host,
   if (ptr2 == NULL) {
     /* XXX How to configure "implicit" FTPS, if at all? */
 
-    if (strncmp(*scheme, "ftp", 4) == 0 ||
-        strncmp(*scheme, "ftps", 5) == 0) {
+    if (strcmp(*scheme, "ftp") == 0 ||
+        strcmp(*scheme, "ftps") == 0) {
       *port = 21;
 
-    } else if (strncmp(*scheme, "sftp", 5) == 0) {
+    } else if (strcmp(*scheme, "sftp") == 0) {
       *port = 22;
 
     } else {


=====================================
mod_proxy.c
=====================================
@@ -84,28 +84,12 @@ static void proxy_timeoutidle_ev(const void *, void *);
 static void proxy_timeoutnoxfer_ev(const void *, void *);
 static void proxy_timeoutstalled_ev(const void *, void *);
 
-MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess,
+static int recv_resp(cmd_rec *cmd, struct proxy_session *proxy_sess,
     pr_response_t **rp) {
   int res, xerrno = 0;
   pr_response_t *resp;
   unsigned int resp_nlines = 0;
 
-  res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
-    cmd);
-  if (res < 0) {
-    xerrno = errno;
-    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
-      "error sending %s to backend: %s", (char *) cmd->argv[0],
-      strerror(xerrno));
-
-    pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
-      strerror(xerrno));
-    pr_response_flush(&resp_err_list);
-
-    errno = xerrno;
-    return PR_ERROR(cmd);
-  }
-
   resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
     &resp_nlines, 0);
   if (resp == NULL) {
@@ -133,7 +117,7 @@ MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess,
     pr_response_flush(&resp_err_list);
 
     errno = xerrno;
-    return PR_ERROR(cmd);
+    return -1;
   }
 
   res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn,
@@ -142,16 +126,91 @@ MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess,
     xerrno = errno;
 
     pr_response_block(TRUE);
+    errno = xerrno;
+    return -1;
+  }
+
+  if (rp != NULL) {
+    *rp = resp;
+  }
+
+  return 0;
+}
+
+MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess,
+    pr_response_t **rp) {
+  int res, xerrno = 0;
+
+  res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn,
+    cmd);
+  xerrno = errno;
+
+  if (res < 0) {
+    xerrno = errno;
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "error sending %s to backend: %s", (char *) cmd->argv[0],
+      strerror(xerrno));
+
+    pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
+      strerror(xerrno));
+    pr_response_flush(&resp_err_list);
+
     errno = xerrno;
     return PR_ERROR(cmd);
   }
 
+  if (recv_resp(cmd, proxy_sess, rp) < 0) {
+    return PR_ERROR(cmd);
+  }
+
   pr_response_block(TRUE);
+  return PR_HANDLED(cmd);
+}
 
-  if (rp != NULL) {
-    *rp = resp;
+MODRET proxy_abort(cmd_rec *cmd, struct proxy_session *proxy_sess,
+    pr_response_t **rp) {
+  int res, xerrno = 0;
+
+  res = proxy_ftp_ctrl_send_abort(cmd->tmp_pool,
+    proxy_sess->backend_ctrl_conn, cmd);
+  xerrno = errno;
+
+  if (res < 0) {
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "error sending %s to backend: %s", (char *) cmd->argv[0],
+      strerror(xerrno));
+
+    pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0],
+      strerror(xerrno));
+    pr_response_flush(&resp_err_list);
+
+    errno = xerrno;
+    return PR_ERROR(cmd);
+  }
+
+  if (proxy_sess->backend_data_conn != NULL) {
+    pr_trace_msg(trace_channel, 19, "received ABOR on frontend connection, "
+      "closing backend data connection");
+    proxy_inet_close(session.pool, proxy_sess->backend_data_conn);
+    proxy_sess->backend_data_conn = NULL;
+  }
+
+  if (recv_resp(cmd, proxy_sess, rp) < 0) {
+    return PR_ERROR(cmd);
+  }
+
+  /* The ABOR command might have two responses, as when there is a data
+   * transfer in progress: one response for the data transfer, and one for
+   * handling the ABOR command itself.
+   */
+  if ((proxy_sess->frontend_sess_flags & SF_XFER) ||
+      (proxy_sess->backend_sess_flags & SF_XFER)) {
+    if (recv_resp(cmd, proxy_sess, rp) < 0) {
+      return PR_ERROR(cmd);
+    }
   }
 
+  pr_response_block(TRUE);
   return PR_HANDLED(cmd);
 }
 
@@ -2311,12 +2370,22 @@ MODRET proxy_data(struct proxy_session *proxy_sess, cmd_rec *cmd) {
     xfer_direction = PR_NETIO_IO_WR;
 
     session.xfer.path = pr_table_get(cmd->notes, "mod_xfer.store-path", NULL);
+    if (session.xfer.path == NULL) {
+      /* Add this note for the Jot API, and resolving %F/%f variables. */
+      (void) pr_table_add_dup(cmd->notes, "mod_xfer.store-path", cmd->arg, 0);
+      session.xfer.path = cmd->arg;
+    }
 
   } else {
     /* Downloading, i.e. reading from backend data conn.*/
     xfer_direction = PR_NETIO_IO_RD;
 
     session.xfer.path = pr_table_get(cmd->notes, "mod_xfer.retr-path", NULL);
+    if (session.xfer.path == NULL) {
+      /* Add this note for the Jot API, and resolving %F/%f variables. */
+      (void) pr_table_add_dup(cmd->notes, "mod_xfer.retr-path", cmd->arg, 0);
+      session.xfer.path = cmd->arg;
+    }
   }
 
   res = proxy_data_prepare_conns(proxy_sess, cmd, &frontend_conn,
@@ -2515,19 +2584,28 @@ MODRET proxy_data(struct proxy_session *proxy_sess, cmd_rec *cmd) {
       continue;
     }
 
-#if 0
     /* Any commands from the frontend client take priority */
-
-    /* NOTE: This is temporarily disabled, until I can better handle an
-     * ABOR command on the frontend control connection whilst in the middle
-     * of a data transfer.
-     */
     if (frontend_ctrlfd >= 0 &&
         FD_ISSET(frontend_ctrlfd, &rfds)) {
       proxy_process_cmd();
       pr_response_block(FALSE);
+
+      /* Check for closed frontend/backend data connections, as from an ABOR
+       * command.
+       */
+      if (session.d == NULL ||
+          proxy_sess->backend_data_conn == NULL) {
+        if (pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED) > 0) {
+          pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE);
+        }
+
+        pr_throttle_pause(bytes_transferred, TRUE);
+        pr_response_clear(&resp_list);
+        pr_response_clear(&resp_err_list);
+
+        return PR_HANDLED(cmd);
+      }
     }
-#endif
 
     if (src_data_conn != NULL &&
         datafd >= 0 &&
@@ -4347,6 +4425,23 @@ MODRET proxy_any(cmd_rec *cmd) {
         return PR_DECLINED(cmd);
       }
       break;
+
+    case PR_CMD_ABOR_ID:
+      mr = proxy_abort(cmd, proxy_sess, NULL);
+      if ((proxy_sess->frontend_sess_flags & SF_XFER) ||
+          (proxy_sess->backend_sess_flags & SF_XFER)) {
+        pr_trace_msg(trace_channel, 19, "received ABOR on frontend connection, "
+          "closing frontend data connection");
+
+        if (session.d != NULL) {
+          pr_inet_close(session.pool, proxy_sess->frontend_data_conn);
+          proxy_sess->frontend_data_conn = session.d = NULL;
+        }
+
+        proxy_sess->frontend_sess_flags &= ~SF_XFER;
+        proxy_sess->backend_sess_flags &= ~SF_XFER;
+      }
+      return mr;
   }
 
   /* If we are not connected to a backend server, then don't try to proxy
@@ -4422,8 +4517,11 @@ static void proxy_exit_ev(const void *event_data, void *user_data) {
   proxy_sess = (struct proxy_session *) pr_table_get(session.notes,
     "mod_proxy.proxy-session", NULL);
   if (proxy_sess != NULL) {
+    /* proxy_sess->frontend_ctrl_conn is session.c; let the core engine
+     * close that connection.  If we try to close it here via pr_inet_close(),
+     * we risk segfaults due to double-free of the memory, stale pointers, etc.
+     */
     if (proxy_sess->frontend_ctrl_conn != NULL) {
-      pr_inet_close(proxy_sess->pool, proxy_sess->frontend_ctrl_conn);
       proxy_sess->frontend_ctrl_conn = NULL;
     }
 
@@ -4821,6 +4919,9 @@ static int proxy_sess_init(void) {
     return -1;
   }
 
+  /* Provide default note values. */
+  (void) pr_table_add_dup(session.notes, "mod_proxy.backend-port", "0", 0);
+
   c = find_config(main_server->conf, CONF_PARAM, "ProxySourceAddress", FALSE);
   if (c != NULL) {
     proxy_sess->src_addr = c->argv[0];


=====================================
mod_proxy.h.in
=====================================
@@ -58,11 +58,11 @@
 /* Define if you have the strnstr(3) function.  */
 #undef HAVE_STRNSTR
 
-#define MOD_PROXY_VERSION	"mod_proxy/0.6"
+#define MOD_PROXY_VERSION	"mod_proxy/0.7"
 
 /* Make sure the version of proftpd is as necessary. */
-#if PROFTPD_VERSION_NUMBER < 0x0001030605
-# error "ProFTPD 1.3.6rc5 or later required"
+#if PROFTPD_VERSION_NUMBER < 0x0001030706
+# error "ProFTPD 1.3.7a or later required"
 #endif
 
 /* mod_proxy option flags */


=====================================
mod_proxy.html
=====================================
@@ -648,18 +648,20 @@ The <code>ProxyReverseServers</code> directive configures the list of servers
 to be used as the backend servers for reverse proxying.
 
 <p>
-Each server <b>must</b> be configured as a <i>URL</i>.  Only the "ftp" scheme
-is currently supported.  If not specified, the port will be 21.  IPv6 addresses
-<b>must</b> be enclosed within square brackets.  Thus, for example, the
-following are all valid URLs:
+Each server <b>must</b> be configured as a <i>URL</i>.  Only the "ftp" and
+"ftps" schemes are currently supported.  If not specified, the port will be
+21.  IPv6 addresses <b>must</b> be enclosed within square brackets.  Thus, for
+example, the following are all valid URLs:
 <pre>
   ftp://<em>ftp1.example.com:2121</em>
   ftp://<em>1.2.3.4</em>
   ftp://<em>[::ffff:6.7.8.9]:2121</em>
+  ftps://<em>ftp2.example.com</em>
+  ftps://<em>ftp3.example.com:990</em>
 </pre>
 And using them all in the configuration would look like:
 <pre>
-  ProxyReverseServers ftp://ftp1.example.com:2121 ftp://1.2.3.4 ftp://[::ffff:6.7.8.9]:2121
+  ProxyReverseServers ftp://ftp1.example.com:2121 ftps://1.2.3.4 ftp://[::ffff:6.7.8.9]:2121
 </pre>
 
 <p>
@@ -1391,9 +1393,14 @@ for supporting forward proxying:
 
     ProxyRole forward
     ProxyForwardMethod user at host
-    ProxyForwardTo ^ftp\.example\.com [NC]
+    ProxyForwardTo ^ftp\.example\.com\:21$ [NC]
   </IfModule>
 </pre>
+If the configured <code>ProxyForwardTo</code> pattern is <em>not</em> met,
+the following will be logged in the <code>ProxyLog</code>:
+<pre>
+mod_proxy/0.7[16151]: host/port 'server.example.org:2121' did not match ProxyForwardTo ^ftp\.example\.com\:21$, rejecting
+</pre>
 
 <p>
 <b>Reverse Proxy Configuration</b><br>
@@ -1549,6 +1556,27 @@ The <em>sticky</em> policies are:
   <li><code>PerUser</code>
 </ul>
 
+<p>
+<b>Implicit FTPS Support</b><br>
+The <code>mod_proxy</code> module includes support for using <a href="https://en.wikipedia.org/wiki/FTPS#Implicit">implicit FTPS</a> with backend servers,
+both when forward and reverse proxying.
+
+<p>
+In order to use implicit FTPS for a reverse proxy server, the URI syntax
+must be used in a <code>ProxyReverseServers</code> directive; the scheme
+<b>must</b> be "ftps" <i>and</b> the port must be explicitly specified as 990,
+thus:
+<code>
+  ProxyReverseServers ftps://ftp.example.com:990
+</code>
+
+<p>
+When forward proxying, the client must request the destination server <i>and</i>
+specify a port of 990, <i>e.g.</i>:
+<code>
+  USER user at ftp.example.com:990
+</code>
+
 <p>
 <b>SFTP/SCP Support</b><br>
 The <code>mod_proxy</code> module only works for FTP/FTPS sessions; it does
@@ -1601,7 +1629,6 @@ The following lists the features I hope to add to <code>mod_proxy</code>,
 according to need, demand, inclination, and time:
 <ul>
   <li><code>MODE Z</code> support
-  <li>Directory format translation (<i>e.g.</i> <code>LIST</code> to <code>MLSD</code>)
   <li>SFTP/SCP support
 </ul>
 


=====================================
t/api/conn.c
=====================================
@@ -311,6 +311,7 @@ START_TEST (conn_get_tls_test) {
   const char *url;
   const struct proxy_conn *pconn;
 
+  mark_point();
   tls = proxy_conn_get_tls(NULL);
   fail_unless(tls < 0, "Got TLS from null pconn unexpectedly");
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
@@ -321,10 +322,12 @@ START_TEST (conn_get_tls_test) {
   fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected",
     url);
 
+  mark_point();
   tls = proxy_conn_get_tls(pconn);
   fail_unless(tls == PROXY_TLS_ENGINE_AUTO, "Expected TLS auto, got %d", tls);
   proxy_conn_free(pconn);
 
+  mark_point();
   url = "ftps://127.0.0.1:21";
   pconn = proxy_conn_create(p, url);
   fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected",
@@ -332,7 +335,17 @@ START_TEST (conn_get_tls_test) {
 
   tls = proxy_conn_get_tls(pconn);
   fail_unless(tls == PROXY_TLS_ENGINE_ON, "Expected TLS on, got %d", tls);
+  proxy_conn_free(pconn);
+
+  mark_point();
+  url = "ftps://127.0.0.1:990";
+  pconn = proxy_conn_create(p, url);
+  fail_if(pconn == NULL, "Failed to create pconn for URL '%s' as expected",
+    url);
 
+  tls = proxy_conn_get_tls(pconn);
+  fail_unless(tls == PROXY_TLS_ENGINE_IMPLICIT,
+    "Expected TLS implicit, got %d", tls);
   proxy_conn_free(pconn);
 }
 END_TEST


=====================================
t/api/tls.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy testsuite
- * Copyright (c) 2015-2017 TJ Saunders <tj at castaglia.org>
+ * Copyright (c) 2015-2020 TJ Saunders <tj at castaglia.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -249,6 +249,10 @@ START_TEST (tls_using_tls_test) {
   res = proxy_tls_set_tls(PROXY_TLS_ENGINE_AUTO);
   tls = proxy_tls_using_tls();
   fail_unless(tls == PROXY_TLS_ENGINE_AUTO, "Expected TLS auto, got %d", tls);
+
+  res = proxy_tls_set_tls(PROXY_TLS_ENGINE_IMPLICIT);
+  tls = proxy_tls_using_tls();
+  fail_unless(tls == PROXY_TLS_ENGINE_IMPLICIT, "Expected TLS implicit, got %d", tls);
 #endif /* PR_USE_OPENSSL */
 }
 END_TEST


=====================================
t/lib/ProFTPD/Tests/Modules/mod_proxy.pm
=====================================
@@ -91,6 +91,11 @@ my $TESTS = {
     test_class => [qw(forking reverse)],
   },
 
+  proxy_reverse_abort => {
+    order => ++$order,
+    test_class => [qw(forking reverse)],
+  },
+
   proxy_reverse_list_pasv => {
     order => ++$order,
     test_class => [qw(forking reverse)],
@@ -3011,6 +3016,121 @@ EOC
   unlink($log_file);
 }
 
+sub proxy_reverse_abort {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'proxy');
+
+  my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
+  $vhost_port += 12;
+
+  my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file},
+    $vhost_port);
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    SocketBindTight => 'on',
+
+    IfModules => {
+      'mod_proxy.c' => $proxy_config,
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  if (open(my $fh, ">> $setup->{config_file}")) {
+    print $fh <<EOC;
+<VirtualHost 127.0.0.1>
+  Port $vhost_port
+  ServerName "Real Server"
+
+  AuthUserFile $setup->{auth_user_file}
+  AuthGroupFile $setup->{auth_group_file}
+  AuthOrder mod_auth_file.c
+
+  AllowOverride off
+  WtmpLog off
+  TransferLog none
+</VirtualHost>
+EOC
+    unless (close($fh)) {
+      die("Can't write $setup->{config_file}: $!");
+    }
+
+  } else {
+    die("Can't open $setup->{config_file}: $!");
+  }
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      sleep(1);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
+      $client->login($setup->{user}, $setup->{passwd});
+
+      $client->quote('ABOR');
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+
+      my $expected = 226;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'Abort successful';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      $client->quit();
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 sub proxy_reverse_list_pasv {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
@@ -4966,38 +5086,7 @@ EOC
 sub proxy_reverse_retr_abort {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
-
-  my $config_file = "$tmpdir/proxy.conf";
-  my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid");
-  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard");
-
-  my $log_file = test_get_logfile();
-
-  my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd");
-  my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group");
-
-  my $user = 'proftpd';
-  my $passwd = 'test';
-  my $group = 'ftpd';
-  my $home_dir = File::Spec->rel2abs($tmpdir);
-  my $uid = 500;
-  my $gid = 500;
-
-  # Make sure that, if we're running as root, that the home directory has
-  # permissions/privs set for the account we create
-  if ($< == 0) {
-    unless (chmod(0755, $home_dir)) {
-      die("Can't set perms on $home_dir to 0755: $!");
-    }
-
-    unless (chown($uid, $gid, $home_dir)) {
-      die("Can't set owner of $home_dir to $uid/$gid: $!");
-    }
-  }
-
-  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
-    '/bin/bash');
-  auth_group_write($auth_group_file, $group, $gid, $user);
+  my $setup = test_setup($tmpdir, 'proxy');
 
   my $test_datalen = (4 * 1024 * 1024);
   my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
@@ -5015,22 +5104,23 @@ sub proxy_reverse_retr_abort {
   my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
   $vhost_port += 12;
 
-  my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port);
-
+  my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file},
+    $vhost_port);
   my $timeout_idle = 10;
 
   my $config = {
-    PidFile => $pid_file,
-    ScoreboardFile => $scoreboard_file,
-    SystemLog => $log_file,
-    TraceLog => $log_file,
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
     Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20',
 
-    AuthUserFile => $auth_user_file,
-    AuthGroupFile => $auth_group_file,
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
     SocketBindTight => 'on',
     TimeoutIdle => $timeout_idle,
-    UseIPv6 => 'on',
+    TimeoutLinger => 1,
+    UseIPv6 => 'off',
 
     IfModules => {
       'mod_proxy.c' => $proxy_config,
@@ -5042,37 +5132,38 @@ sub proxy_reverse_retr_abort {
 
     Limit => {
       LOGIN => {
-        DenyUser => $user,
+        DenyUser => $setup->{user},
       },
     },
-
   };
 
-  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
 
-  if (open(my $fh, ">> $config_file")) {
+  if (open(my $fh, ">> $setup->{config_file}")) {
     print $fh <<EOC;
 <VirtualHost 127.0.0.1>
   Port $vhost_port
   ServerName "Real Server"
 
-  AuthUserFile $auth_user_file
-  AuthGroupFile $auth_group_file
+  AuthUserFile $setup->{auth_user_file}
+  AuthGroupFile $setup->{auth_group_file}
   AuthOrder mod_auth_file.c
 
   AllowOverride off
   TimeoutIdle $timeout_idle
+  TimeoutLinger 1
 
   TransferLog none
   WtmpLog off
 </VirtualHost>
 EOC
     unless (close($fh)) {
-      die("Can't write $config_file: $!");
+      die("Can't write $setup->{config_file}: $!");
     }
 
   } else {
-    die("Can't open $config_file: $!");
+    die("Can't open $setup->{config_file}: $!");
   }
 
   # Open pipes, for use between the parent and child processes.  Specifically,
@@ -5091,8 +5182,9 @@ EOC
   if ($pid) {
     eval {
       sleep(1);
+
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
-      $client->login($user, $passwd);
+      $client->login($setup->{user}, $setup->{passwd});
       $client->type('binary');
 
       my $conn = $client->retr_raw($test_file);
@@ -5103,7 +5195,7 @@ EOC
 
       my $buf;
       $conn->read($buf, 8192, 30);
-      eval { $conn->close() };
+      eval { $client->quote('ABOR') };
 
       my $resp_code = $client->response_code();
       my $resp_msg = $client->response_msg();
@@ -5111,7 +5203,6 @@ EOC
 
       $client->quit();
     };
-
     if ($@) {
       $ex = $@;
     }
@@ -5120,7 +5211,7 @@ EOC
     $wfh->flush();
 
   } else {
-    eval { server_wait($config_file, $rfh, $timeout_idle + 2) };
+    eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) };
     if ($@) {
       warn($@);
       exit 1;
@@ -5130,18 +5221,10 @@ EOC
   }
 
   # Stop server
-  server_stop($pid_file);
-
+  server_stop($setup->{pid_file});
   $self->assert_child_ok($pid);
 
-  if ($ex) {
-    test_append_logfile($log_file, $ex);
-    unlink($log_file);
-
-    die($ex);
-  }
-
-  unlink($log_file);
+  test_cleanup($setup->{log_file}, $ex);
 }
 
 sub proxy_reverse_stor_pasv {
@@ -6230,38 +6313,7 @@ EOC
 sub proxy_reverse_stor_abort {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
-
-  my $config_file = "$tmpdir/proxy.conf";
-  my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid");
-  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard");
-
-  my $log_file = test_get_logfile();
-
-  my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd");
-  my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group");
-
-  my $user = 'proftpd';
-  my $passwd = 'test';
-  my $group = 'ftpd';
-  my $home_dir = File::Spec->rel2abs($tmpdir);
-  my $uid = 500;
-  my $gid = 500;
-
-  # Make sure that, if we're running as root, that the home directory has
-  # permissions/privs set for the account we create
-  if ($< == 0) {
-    unless (chmod(0755, $home_dir)) {
-      die("Can't set perms on $home_dir to 0755: $!");
-    }
-
-    unless (chown($uid, $gid, $home_dir)) {
-      die("Can't set owner of $home_dir to $uid/$gid: $!");
-    }
-  }
-
-  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
-    '/bin/bash');
-  auth_group_write($auth_group_file, $group, $gid, $user);
+  my $setup = test_setup($tmpdir, 'proxy');
 
   my $test_datalen = (4 * 1024 * 1024);
   my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
@@ -6269,22 +6321,24 @@ sub proxy_reverse_stor_abort {
   my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
   $vhost_port += 12;
 
-  my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port);
+  my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file},
+    $vhost_port);
 
   my $timeout_idle = 10;
 
   my $config = {
-    PidFile => $pid_file,
-    ScoreboardFile => $scoreboard_file,
-    SystemLog => $log_file,
-    TraceLog => $log_file,
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
     Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20',
 
-    AuthUserFile => $auth_user_file,
-    AuthGroupFile => $auth_group_file,
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
     SocketBindTight => 'on',
     TimeoutIdle => $timeout_idle,
-    UseIPv6 => 'on',
+    TimeoutLinger => 1,
+    UseIPv6 => 'off',
 
     IfModules => {
       'mod_proxy.c' => $proxy_config,
@@ -6296,37 +6350,37 @@ sub proxy_reverse_stor_abort {
 
     Limit => {
       LOGIN => {
-        DenyUser => $user,
+        DenyUser => $setup->{user},
       },
     },
-
   };
 
-  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
 
-  if (open(my $fh, ">> $config_file")) {
+  if (open(my $fh, ">> $setup->{config_file}")) {
     print $fh <<EOC;
 <VirtualHost 127.0.0.1>
   Port $vhost_port
   ServerName "Real Server"
 
-  AuthUserFile $auth_user_file
-  AuthGroupFile $auth_group_file
+  AuthUserFile $setup->{auth_user_file}
+  AuthGroupFile $setup->{auth_group_file}
   AuthOrder mod_auth_file.c
 
   AllowOverride off
   TimeoutIdle $timeout_idle
-
+  TimeoutLinger 1
   TransferLog none
   WtmpLog off
 </VirtualHost>
 EOC
     unless (close($fh)) {
-      die("Can't write $config_file: $!");
+      die("Can't write $setup->{config_file}: $!");
     }
 
   } else {
-    die("Can't open $config_file: $!");
+    die("Can't open $setup->{config_file}: $!");
   }
 
   # Open pipes, for use between the parent and child processes.  Specifically,
@@ -6345,8 +6399,9 @@ EOC
   if ($pid) {
     eval {
       sleep(1);
+
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
-      $client->login($user, $passwd);
+      $client->login($setup->{user}, $setup->{passwd});
       $client->type('binary');
 
       my $conn = $client->stor_raw($test_file);
@@ -6365,7 +6420,6 @@ EOC
 
       $client->quit();
     };
-
     if ($@) {
       $ex = $@;
     }
@@ -6374,7 +6428,7 @@ EOC
     $wfh->flush();
 
   } else {
-    eval { server_wait($config_file, $rfh, $timeout_idle + 2) };
+    eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) };
     if ($@) {
       warn($@);
       exit 1;
@@ -6384,18 +6438,10 @@ EOC
   }
 
   # Stop server
-  server_stop($pid_file);
-
+  server_stop($setup->{pid_file});
   $self->assert_child_ok($pid);
 
-  if ($ex) {
-    test_append_logfile($log_file, $ex);
-    unlink($log_file);
-
-    die($ex);
-  }
-
-  unlink($log_file);
+  test_cleanup($setup->{log_file}, $ex);
 }
 
 sub proxy_reverse_rest_retr {


=====================================
t/lib/ProFTPD/Tests/Modules/mod_proxy/sql.pm
=====================================
@@ -46,6 +46,26 @@ my $TESTS = {
     test_class => [qw(forking mod_sql_sqlite reverse)],
   },
 
+  proxy_sql_sqllog_forward_proxied_address_note_issue175 => {
+    order => ++$order,
+    test_class => [qw(forking forward mod_sql_sqlite)],
+  },
+
+  proxy_sql_sqllog_forward_proxied_file_xfer_issue175 => {
+    order => ++$order,
+    test_class => [qw(forking forward mod_sql_sqlite)],
+  },
+
+  proxy_sql_sqllog_reverse_proxied_address_note_issue175 => {
+    order => ++$order,
+    test_class => [qw(forking mod_sql_sqlite reverse)],
+  },
+
+  proxy_sql_sqllog_reverse_proxied_file_xfer_issue175 => {
+    order => ++$order,
+    test_class => [qw(forking mod_sql_sqlite reverse)],
+  },
+
 };
 
 sub new {
@@ -107,6 +127,31 @@ sub get_redis_config {
   return $config;
 }
 
+sub get_forward_proxy_config {
+  my $tmpdir = shift;
+  my $log_file = shift;
+  my $vhost_port = shift;
+
+  my $table_dir = File::Spec->rel2abs("$tmpdir/var/proxy");
+
+  my $config = {
+    ProxyEngine => 'on',
+    ProxyLog => $log_file,
+    ProxyRole => 'forward',
+    ProxyTables => $table_dir,
+    ProxyTimeoutConnect => '1sec',
+
+    Class => {
+      'forward-proxy' => {
+        From => '127.0.0.1',
+        ProxyForwardEnabled => 'on',
+      },
+    },
+  };
+
+  return $config;
+}
+
 sub get_reverse_proxy_config {
   my $tmpdir = shift;
   my $log_file = shift;
@@ -1257,4 +1302,843 @@ EOC
   test_cleanup($setup->{log_file}, $ex);
 }
 
+sub get_sessions {
+  my $db_file = shift;
+  my $where = shift;
+
+  my $sql = "SELECT user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, timestamp FROM proxy_sessions";
+  if ($where) {
+    $sql .= " WHERE $where";
+  }
+
+  my $cmd = "sqlite3 $db_file \"$sql\"";
+
+  if ($ENV{TEST_VERBOSE}) {
+    print STDERR "Executing sqlite3: $cmd\n";
+  }
+
+  my $res = join('', `$cmd`);
+  chomp($res);
+
+  # The default sqlite3 delimiter is '|'
+  return split(/\|/, $res);
+}
+
+sub get_transfers {
+  my $db_file = shift;
+  my $where = shift;
+
+  my $sql = "SELECT user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, file, timestamp FROM proxy_transfers";
+  if ($where) {
+    $sql .= " WHERE $where";
+  }
+
+  my $cmd = "sqlite3 $db_file \"$sql\"";
+
+  if ($ENV{TEST_VERBOSE}) {
+    print STDERR "Executing sqlite3: $cmd\n";
+  }
+
+  my $res = join('', `$cmd`);
+  chomp($res);
+
+  # The default sqlite3 delimiter is '|'
+  return split(/\|/, $res);
+}
+
+sub proxy_sql_sqllog_forward_proxied_address_note_issue175 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'proxy');
+
+  my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
+  $vhost_port += 12;
+  my $vhost_port2 = $vhost_port - 7;
+
+  my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file},
+    $vhost_port);
+  $proxy_config->{ProxyForwardMethod} = 'proxyuser at host,user';
+
+  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
+
+  # Build up the sqlite3 command to create tables and populate them
+  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
+  if (open(my $fh, "> $db_script")) {
+    print $fh <<EOS;
+CREATE TABLE proxy_sessions (
+  user TEXT,
+  protocol TEXT,
+  frontend_ipaddr TEXT,
+  local_ipaddr TEXT,
+  backend_ipaddr TEXT,
+  backend_port INTEGER,
+  timestamp TEXT
+);
+EOS
+    unless (close($fh)) {
+      die("Can't write $db_script: $!");
+    }
+
+  } else {
+    die("Can't open $db_script: $!");
+  }
+
+  my $cmd = "sqlite3 $db_file < $db_script";
+  build_db($cmd, $db_script);
+
+  # Make sure that, if we're running as root, the database file has
+  # the permissions/privs set for use by proftpd
+  if ($< == 0) {
+    unless (chmod(0666, $db_file)) {
+      die("Can't set perms on $db_file to 0666: $!");
+    }
+  }
+
+  my $timeout_idle = 10;
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 auth:0 event:0 jot:20 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 sql:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    SocketBindTight => 'on',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_proxy.c' => $proxy_config,
+
+      'mod_sql.c' => {
+        SQLEngine => 'log',
+        SQLBackend => 'sqlite3',
+        SQLConnectInfo => $db_file,
+        SQLLogFile => $setup->{log_file},
+        SQLNamedQuery => 'session_start FREEFORM "INSERT INTO proxy_sessions (user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, timestamp) VALUES (\'%u\', \'%{protocol}\', \'%a\', \'%L\', \'%{note:mod_proxy.backend-ip}\', %{note:mod_proxy.backend-port}, \'%{iso8601}\')"',
+        SQLLog => 'PASS session_start',
+      }
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  if (open(my $fh, ">> $setup->{config_file}")) {
+    print $fh <<EOC;
+<VirtualHost 127.0.0.1>
+  Port $vhost_port
+  ServerName "Real Server"
+
+  AuthUserFile $setup->{auth_user_file}
+  AuthGroupFile $setup->{auth_group_file}
+  AuthOrder mod_auth_file.c
+
+  AllowOverride off
+  TimeoutIdle $timeout_idle
+
+  TransferLog none
+  WtmpLog off
+</VirtualHost>
+EOC
+    unless (close($fh)) {
+      die("Can't write $setup->{config_file}: $!");
+    }
+
+  } else {
+    die("Can't open $setup->{config_file}: $!");
+  }
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      sleep(1);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0);
+      $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd});
+      $client->login($setup->{user}, $setup->{passwd});
+      $client->quit();
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex) if $ex;
+
+  eval {
+    my ($login, $protocol, $frontend_ip, $local_ip, $backend_ip, $backend_port, $timestamp) = get_sessions($db_file, "user = \'$setup->{user}\'");
+
+    my $expected = $setup->{user};
+    $self->assert($expected eq $login, "Expected '$expected', got '$login'");
+
+    my $expected = 'ftp';
+    $self->assert($expected eq $protocol, "Expected '$expected', got '$protocol'");
+
+    $expected = '127.0.0.1';
+    $self->assert($expected eq $frontend_ip,
+      "Expected frontend IP '$expected', got '$frontend_ip'");
+
+    $self->assert($expected eq $local_ip,
+      "Expected local IP '$expected', got '$local_ip'");
+
+    $self->assert($expected eq $backend_ip,
+      "Expected backend IP '$expected', got '$backend_ip'");
+
+    $expected = $vhost_port;
+    $self->assert($expected == $backend_port,
+      "Expected backend port $expected, got $backend_port");
+  };
+  if ($@) {
+    $ex = $@;
+  }
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
+sub proxy_sql_sqllog_forward_proxied_file_xfer_issue175 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'proxy');
+
+  my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
+  $vhost_port += 12;
+  my $vhost_port2 = $vhost_port - 7;
+
+  my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file},
+    $vhost_port);
+  $proxy_config->{ProxyForwardMethod} = 'proxyuser at host,user';
+
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.dat");
+  if (open(my $fh, "> $test_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $test_file: $!");
+    }
+
+  } else {
+    die("Can't open $test_file: $!");
+  }
+
+  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
+
+  # Build up the sqlite3 command to create tables and populate them
+  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
+  if (open(my $fh, "> $db_script")) {
+    print $fh <<EOS;
+CREATE TABLE proxy_transfers (
+  user TEXT,
+  protocol TEXT,
+  frontend_ipaddr TEXT,
+  local_ipaddr TEXT,
+  backend_ipaddr TEXT,
+  backend_port INTEGER,
+  timestamp TEXT,
+  file TEXT
+);
+EOS
+    unless (close($fh)) {
+      die("Can't write $db_script: $!");
+    }
+
+  } else {
+    die("Can't open $db_script: $!");
+  }
+
+  my $cmd = "sqlite3 $db_file < $db_script";
+  build_db($cmd, $db_script);
+
+  # Make sure that, if we're running as root, the database file has
+  # the permissions/privs set for use by proftpd
+  if ($< == 0) {
+    unless (chmod(0666, $db_file)) {
+      die("Can't set perms on $db_file to 0666: $!");
+    }
+  }
+
+  my $timeout_idle = 10;
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 auth:0 event:0 jot:20 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.forward:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 sql:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    SocketBindTight => 'on',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_proxy.c' => $proxy_config,
+
+      'mod_sql.c' => {
+        SQLEngine => 'log',
+        SQLBackend => 'sqlite3',
+        SQLConnectInfo => $db_file,
+        SQLLogFile => $setup->{log_file},
+        SQLNamedQuery => 'file_xfer FREEFORM "INSERT INTO proxy_transfers (user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, file, timestamp) VALUES (\'%u\', \'%{protocol}\', \'%a\', \'%L\', \'%{note:mod_proxy.backend-ip}\', %{note:mod_proxy.backend-port}, \'%F\', \'%{iso8601}\')"',
+        SQLLog => 'RETR,STOR file_xfer',
+      }
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  if (open(my $fh, ">> $setup->{config_file}")) {
+    print $fh <<EOC;
+<VirtualHost 127.0.0.1>
+  Port $vhost_port
+  ServerName "Real Server"
+
+  AuthUserFile $setup->{auth_user_file}
+  AuthGroupFile $setup->{auth_group_file}
+  AuthOrder mod_auth_file.c
+
+  AllowOverride off
+  TimeoutIdle $timeout_idle
+
+  TransferLog none
+  WtmpLog off
+</VirtualHost>
+EOC
+    unless (close($fh)) {
+      die("Can't write $setup->{config_file}: $!");
+    }
+
+  } else {
+    die("Can't open $setup->{config_file}: $!");
+  }
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      sleep(1);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0);
+      $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd});
+      $client->login($setup->{user}, $setup->{passwd});
+
+      my $conn = $client->retr_raw('test.dat');
+      unless ($conn) {
+        die("RETR failed: " . $client->response_code() . ' ' .
+          $client->response_msg());
+      }
+
+      my $buf = '';
+      $conn->read($buf, 1024, 10);
+      eval { $conn->close() };
+
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+
+      $client->quit();
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex) if $ex;
+
+  eval {
+    my ($login, $protocol, $frontend_ip, $local_ip, $backend_ip, $backend_port, $path, $timestamp) = get_transfers($db_file, "user = \'$setup->{user}\'");
+
+    my $expected = $setup->{user};
+    $self->assert($expected eq $login, "Expected '$expected', got '$login'");
+
+    my $expected = 'ftp';
+    $self->assert($expected eq $protocol, "Expected '$expected', got '$protocol'");
+
+    $expected = '127.0.0.1';
+    $self->assert($expected eq $frontend_ip,
+      "Expected frontend IP '$expected', got '$frontend_ip'");
+
+    $self->assert($expected eq $local_ip,
+      "Expected local IP '$expected', got '$local_ip'");
+
+    $self->assert($expected eq $backend_ip,
+      "Expected backend IP '$expected', got '$backend_ip'");
+
+    $expected = $vhost_port;
+    $self->assert($expected == $backend_port,
+      "Expected backend port $expected, got $backend_port");
+
+    $expected = 'test.dat';
+    $self->assert($expected eq $path,
+      "Expected transfer path '$expected', got '$path'");
+  };
+  if ($@) {
+    $ex = $@;
+  }
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
+sub proxy_sql_sqllog_reverse_proxied_address_note_issue175 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'proxy');
+
+  my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
+  $vhost_port += 12;
+  my $vhost_port2 = $vhost_port - 7;
+
+  my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file},
+    $vhost_port);
+  $proxy_config->{ProxyTimeoutConnect} = '1sec';
+  $proxy_config->{ProxyReverseConnectPolicy} = 'RoundRobin';
+
+  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
+
+  # Build up the sqlite3 command to create tables and populate them
+  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
+  if (open(my $fh, "> $db_script")) {
+    print $fh <<EOS;
+CREATE TABLE proxy_sessions (
+  user TEXT,
+  protocol TEXT,
+  frontend_ipaddr TEXT,
+  local_ipaddr TEXT,
+  backend_ipaddr TEXT,
+  backend_port INTEGER,
+  timestamp TEXT
+);
+EOS
+    unless (close($fh)) {
+      die("Can't write $db_script: $!");
+    }
+
+  } else {
+    die("Can't open $db_script: $!");
+  }
+
+  my $cmd = "sqlite3 $db_file < $db_script";
+  build_db($cmd, $db_script);
+
+  # Make sure that, if we're running as root, the database file has
+  # the permissions/privs set for use by proftpd
+  if ($< == 0) {
+    unless (chmod(0666, $db_file)) {
+      die("Can't set perms on $db_file to 0666: $!");
+    }
+  }
+
+  my $timeout_idle = 10;
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 auth:0 event:0 jot:20 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 sql:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    SocketBindTight => 'on',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_proxy.c' => $proxy_config,
+
+      'mod_sql.c' => {
+        SQLEngine => 'log',
+        SQLBackend => 'sqlite3',
+        SQLConnectInfo => $db_file,
+        SQLLogFile => $setup->{log_file},
+        SQLNamedQuery => 'session_start FREEFORM "INSERT INTO proxy_sessions (user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, timestamp) VALUES (\'%u\', \'%{protocol}\', \'%a\', \'%L\', \'%{note:mod_proxy.backend-ip}\', %{note:mod_proxy.backend-port}, \'%{iso8601}\')"',
+        SQLLog => 'PASS session_start',
+      }
+    },
+
+    Limit => {
+      LOGIN => {
+        DenyUser => $setup->{user},
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  if (open(my $fh, ">> $setup->{config_file}")) {
+    print $fh <<EOC;
+<VirtualHost 127.0.0.1>
+  Port $vhost_port
+  ServerName "Real Server"
+
+  AuthUserFile $setup->{auth_user_file}
+  AuthGroupFile $setup->{auth_group_file}
+  AuthOrder mod_auth_file.c
+
+  AllowOverride off
+  TimeoutIdle $timeout_idle
+
+  TransferLog none
+  WtmpLog off
+</VirtualHost>
+EOC
+    unless (close($fh)) {
+      die("Can't write $setup->{config_file}: $!");
+    }
+
+  } else {
+    die("Can't open $setup->{config_file}: $!");
+  }
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      sleep(1);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0);
+      $client->login($setup->{user}, $setup->{passwd});
+      $client->quit();
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex) if $ex;
+
+  eval {
+    my ($login, $protocol, $frontend_ip, $local_ip, $backend_ip, $backend_port, $timestamp) = get_sessions($db_file, "user = \'$setup->{user}\'");
+
+    my $expected = $setup->{user};
+    $self->assert($expected eq $login, "Expected '$expected', got '$login'");
+
+    my $expected = 'ftp';
+    $self->assert($expected eq $protocol, "Expected '$expected', got '$protocol'");
+
+    $expected = '127.0.0.1';
+    $self->assert($expected eq $frontend_ip,
+      "Expected frontend IP '$expected', got '$frontend_ip'");
+
+    $self->assert($expected eq $local_ip,
+      "Expected local IP '$expected', got '$local_ip'");
+
+    $self->assert($expected eq $backend_ip,
+      "Expected backend IP '$expected', got '$backend_ip'");
+
+    $expected = $vhost_port;
+    $self->assert($expected == $backend_port,
+      "Expected backend port $expected, got $backend_port");
+  };
+  if ($@) {
+    $ex = $@;
+  }
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
+sub proxy_sql_sqllog_reverse_proxied_file_xfer_issue175 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'proxy');
+
+  my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
+  $vhost_port += 12;
+  my $vhost_port2 = $vhost_port - 7;
+
+  my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file},
+    $vhost_port);
+  $proxy_config->{ProxyReverseConnectPolicy} = 'Shuffle';
+
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.dat");
+  if (open(my $fh, "> $test_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $test_file: $!");
+    }
+
+  } else {
+    die("Can't open $test_file: $!");
+  }
+
+  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
+
+  # Build up the sqlite3 command to create tables and populate them
+  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
+  if (open(my $fh, "> $db_script")) {
+    print $fh <<EOS;
+CREATE TABLE proxy_transfers (
+  user TEXT,
+  protocol TEXT,
+  frontend_ipaddr TEXT,
+  local_ipaddr TEXT,
+  backend_ipaddr TEXT,
+  backend_port INTEGER,
+  timestamp TEXT,
+  file TEXT
+);
+EOS
+    unless (close($fh)) {
+      die("Can't write $db_script: $!");
+    }
+
+  } else {
+    die("Can't open $db_script: $!");
+  }
+
+  my $cmd = "sqlite3 $db_file < $db_script";
+  build_db($cmd, $db_script);
+
+  # Make sure that, if we're running as root, the database file has
+  # the permissions/privs set for use by proftpd
+  if ($< == 0) {
+    unless (chmod(0666, $db_file)) {
+      die("Can't set perms on $db_file to 0666: $!");
+    }
+  }
+
+  my $timeout_idle = 10;
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 auth:0 event:0 jot:20 lock:0 scoreboard:0 signal:0 proxy:20 proxy.db:20 proxy.reverse:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 sql:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    SocketBindTight => 'on',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_proxy.c' => $proxy_config,
+
+      'mod_sql.c' => {
+        SQLEngine => 'log',
+        SQLBackend => 'sqlite3',
+        SQLConnectInfo => $db_file,
+        SQLLogFile => $setup->{log_file},
+        SQLNamedQuery => 'file_xfer FREEFORM "INSERT INTO proxy_transfers (user, protocol, frontend_ipaddr, local_ipaddr, backend_ipaddr, backend_port, file, timestamp) VALUES (\'%u\', \'%{protocol}\', \'%a\', \'%L\', \'%{note:mod_proxy.backend-ip}\', %{note:mod_proxy.backend-port}, \'%F\', \'%{iso8601}\')"',
+        SQLLog => 'RETR,STOR file_xfer',
+      }
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  if (open(my $fh, ">> $setup->{config_file}")) {
+    print $fh <<EOC;
+<VirtualHost 127.0.0.1>
+  Port $vhost_port
+  ServerName "Real Server"
+
+  AuthUserFile $setup->{auth_user_file}
+  AuthGroupFile $setup->{auth_group_file}
+  AuthOrder mod_auth_file.c
+
+  AllowOverride off
+  TimeoutIdle $timeout_idle
+
+  TransferLog none
+  WtmpLog off
+</VirtualHost>
+EOC
+    unless (close($fh)) {
+      die("Can't write $setup->{config_file}: $!");
+    }
+
+  } else {
+    die("Can't open $setup->{config_file}: $!");
+  }
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      sleep(1);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 0);
+      $client->login($setup->{user}, $setup->{passwd});
+
+      my $conn = $client->retr_raw('test.dat');
+      unless ($conn) {
+        die("RETR failed: " . $client->response_code() . ' ' .
+          $client->response_msg());
+      }
+
+      my $buf = '';
+      $conn->read($buf, 1024, 10);
+      eval { $conn->close() };
+
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+
+      $client->quit();
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex) if $ex;
+
+  eval {
+    my ($login, $protocol, $frontend_ip, $local_ip, $backend_ip, $backend_port, $path, $timestamp) = get_transfers($db_file, "user = \'$setup->{user}\'");
+
+    my $expected = $setup->{user};
+    $self->assert($expected eq $login, "Expected '$expected', got '$login'");
+
+    my $expected = 'ftp';
+    $self->assert($expected eq $protocol, "Expected '$expected', got '$protocol'");
+
+    $expected = '127.0.0.1';
+    $self->assert($expected eq $frontend_ip,
+      "Expected frontend IP '$expected', got '$frontend_ip'");
+
+    $self->assert($expected eq $local_ip,
+      "Expected local IP '$expected', got '$local_ip'");
+
+    $self->assert($expected eq $backend_ip,
+      "Expected backend IP '$expected', got '$backend_ip'");
+
+    $expected = $vhost_port;
+    $self->assert($expected == $backend_port,
+      "Expected backend port $expected, got $backend_port");
+
+    $expected = 'test.dat';
+    $self->assert($expected eq $path,
+      "Expected transfer path '$expected', got '$path'");
+  };
+  if ($@) {
+    $ex = $@;
+  }
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 1;


=====================================
t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm
=====================================
@@ -45,6 +45,11 @@ my $TESTS = {
     test_class => [qw(forking mod_tls reverse)],
   },
 
+  proxy_reverse_backend_tls_implicit_login => {
+    order => ++$order,
+    test_class => [qw(forking mod_tls reverse)],
+  },
+
   proxy_reverse_backend_tls_login_cached_session => {
     order => ++$order,
     test_class => [qw(forking mod_tls mod_tls_shmcache reverse)],
@@ -95,6 +100,11 @@ my $TESTS = {
     test_class => [qw(forking mod_tls reverse)],
   },
 
+  proxy_reverse_frontend_backend_tls_abort => {
+    order => ++$order,
+    test_class => [qw(forking mod_tls reverse)],
+  },
+
   proxy_reverse_frontend_tls_json_peruser => {
     order => ++$order,
     test_class => [qw(forking mod_tls reverse)],
@@ -155,6 +165,11 @@ my $TESTS = {
     test_class => [qw(forking mod_tls forward)],
   },
 
+  proxy_forward_backend_tls_implicit_login => {
+    order => ++$order,
+    test_class => [qw(forking mod_tls forward)],
+  },
+
   proxy_forward_backend_tls_login_failed_unknown_ca => {
     order => ++$order,
     test_class => [qw(forking mod_tls forward)],
@@ -1197,6 +1212,100 @@ EOC
   unlink($log_file);
 }
 
+sub proxy_reverse_backend_tls_implicit_login {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'proxy');
+
+  my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem');
+  my $ca_file = File::Spec->rel2abs('t/etc/modules/mod_tls/ca-cert.pem');
+
+  my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, 990);
+  $proxy_config->{ProxyReverseServers} = 'ftps://demo:password@test.rebex.net:990';
+  $proxy_config->{ProxyTimeoutConnect} = '10s';
+  $proxy_config->{ProxyTLSEngine} = 'auto';
+  $proxy_config->{ProxyTLSCACertificateFile} = $ca_file;
+  $proxy_config->{ProxyTLSVerifyServer} = 'off';
+
+  if ($ENV{TEST_VERBOSE}) {
+    $proxy_config->{ProxyTLSOptions} = 'EnableDiags';
+  }
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.db:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.netio:20 proxy.tls:20 netio:20 tls:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    SocketBindTight => 'on',
+
+    IfModules => {
+      'mod_proxy.c' => $proxy_config,
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+
+    Limit => {
+      LOGIN => {
+        DenyUser => $setup->{user},
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      # Give the server a chance to start up
+      sleep(2);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($setup->{user}, $setup->{passwd});
+      $client->quit();
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 # TODO: Note that this test is used for manually reviewing the generated logs;
 # it does NOT currently fail if session caching fails (although it should).
 sub proxy_reverse_backend_tls_login_cached_session {
@@ -3219,6 +3328,186 @@ EOC
   unlink($log_file);
 }
 
+sub proxy_reverse_frontend_backend_tls_abort {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'proxy');
+
+  my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem');
+  my $ca_file = File::Spec->rel2abs('t/etc/modules/mod_tls/ca-cert.pem');
+
+  my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
+  $vhost_port += 12;
+
+  my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file},
+    $vhost_port);
+  $proxy_config->{ProxyTLSEngine} = 'auto';
+  $proxy_config->{ProxyTLSCACertificateFile} = $ca_file;
+  $proxy_config->{ProxyTLSVerifyServer} = 'off';
+
+  if ($ENV{TEST_VERBOSE}) {
+    $proxy_config->{ProxyTLSOptions} = 'EnableDiags';
+  }
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    SocketBindTight => 'on',
+
+    IfModules => {
+      'mod_proxy.c' => $proxy_config,
+
+      'mod_tls.c' => {
+        TLSEngine => 'on',
+        TLSLog => $setup->{log_file},
+        TLSProtocol => 'SSLv3 TLSv1',
+        TLSRequired => 'on',
+        TLSRSACertificateFile => $cert_file,
+        TLSCACertificateFile => $ca_file,
+        TLSOptions => 'NoSessionReuseRequired EnableDiags',
+        TLSTimeoutHandshake => 5,
+        TLSVerifyClient => 'off',
+        TLSVerifyServer => 'off',
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+
+    Limit => {
+      LOGIN => {
+        DenyUser => $setup->{user},
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  if (open(my $fh, ">> $setup->{config_file}")) {
+    print $fh <<EOC;
+<VirtualHost 127.0.0.1>
+  Port $vhost_port
+  ServerName "Real Server"
+
+  AuthUserFile $setup->{auth_user_file}
+  AuthGroupFile $setup->{auth_group_file}
+  AuthOrder mod_auth_file.c
+
+  AllowOverride off
+  WtmpLog off
+  TransferLog none
+
+  <IfModule mod_tls.c>
+    TLSEngine on
+    TLSLog $setup->{log_file}
+    TLSProtocol SSLv3 TLSv1
+    TLSRequired on
+    TLSRSACertificateFile $cert_file
+    TLSCACertificateFile $ca_file
+
+    TLSVerifyClient off
+    TLSVerifyServer off
+    TLSOptions EnableDiags NoSessionReuseRequired
+  </IfModule>
+</VirtualHost>
+EOC
+    unless (close($fh)) {
+      die("Can't write $setup->{config_file}: $!");
+    }
+
+  } else {
+    die("Can't open $setup->{config_file}: $!");
+  }
+
+  require Net::FTPSSL;
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      # Give the server a chance to start up
+      sleep(2);
+
+      my $ssl_opts = {
+        SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE,
+      };
+
+      my $client_opts = {
+        Encryption => 'E',
+        Port => $port,
+        SSL_Client_Certificate => $ssl_opts,
+      };
+
+      if ($ENV{TEST_VERBOSE}) {
+        $client_opts->{Debug} = 1;
+      }
+
+      my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts);
+
+      unless ($client) {
+        die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr());
+      }
+
+      unless ($client->login($setup->{user}, $setup->{passwd})) {
+        die("Can't login: " . $client->last_message());
+      }
+
+      my $res = $client->_abort();
+      unless ($res) {
+        die("ABOR failed unexpectedly: " . $client->last_message() .
+          "(" . IO::Socket::SSL::errstr() . ")");
+      }
+
+      my $resp_msg = $client->last_message();
+      my $expected = '226 Abort successful';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response '$expected', got '$resp_msg'"));
+
+      $client->quit();
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh, 20) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 sub proxy_reverse_frontend_tls_json_peruser {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
@@ -5402,6 +5691,96 @@ EOC
   unlink($log_file);
 }
 
+sub proxy_forward_backend_tls_implicit_login {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'proxy');
+
+  my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem');
+  my $ca_file = File::Spec->rel2abs('t/etc/modules/mod_tls/ca-cert.pem');
+
+  my $proxy_config = get_forward_proxy_config($tmpdir, $setup->{log_file}, 990);
+  $proxy_config->{ProxyForwardMethod} = 'user at host';
+  $proxy_config->{ProxyTLSEngine} = 'on';
+  $proxy_config->{ProxyTLSCACertificateFile} = $ca_file;
+  $proxy_config->{ProxyTLSVerifyServer} = 'off';
+  $proxy_config->{ProxyRetryCount} = 1;
+
+  if ($ENV{TEST_VERBOSE}) {
+    $proxy_config->{ProxyTLSOptions} = 'EnableDiags';
+  }
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn:20 proxy.forward:20 proxy.netio:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 netio:20 tls:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    SocketBindTight => 'on',
+
+    IfModules => {
+      'mod_proxy.c' => $proxy_config,
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  require Net::FTPSSL;
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      # Give the server a chance to start up
+      sleep(2);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login('demo at test.rebex.net:990', 'password');
+      $client->quit();
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 sub proxy_forward_backend_tls_login_failed_unknown_ca {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};



View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-proxy/-/compare/3c9144bb515e43046bd5322d4443395068e757ad...6d2dc178503ab3b75863577956c3a0aec4a4727b

-- 
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-proxy/-/compare/3c9144bb515e43046bd5322d4443395068e757ad...6d2dc178503ab3b75863577956c3a0aec4a4727b
You're receiving this email because of your account on salsa.debian.org.




More information about the Pkg-proftpd-maintainers mailing list