[Git][debian-proftpd-team/proftpd-mod-proxy][upstream] New upstream version 0.9.5

Hilmar Preuße (@hilmar) gitlab at salsa.debian.org
Wed Feb 26 13:42:15 GMT 2025



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


Commits:
1cdea7e4 by Hilmar Preuße at 2025-02-26T12:14:24+01:00
New upstream version 0.9.5
- - - - -


19 changed files:

- .github/workflows/ci.yml
- .github/workflows/codeql.yml
- .github/workflows/regressions.yml
- include/proxy/tls.h
- lib/proxy/conn.c
- lib/proxy/ftp/conn.c
- lib/proxy/ftp/dirlist.c
- lib/proxy/ftp/xfer.c
- lib/proxy/reverse/db.c
- lib/proxy/session.c
- lib/proxy/ssh.c
- lib/proxy/ssh/crypto.c
- lib/proxy/ssh/msg.c
- lib/proxy/tls.c
- mod_proxy.c
- mod_proxy.h.in
- mod_proxy.html
- t/lib/ProFTPD/Tests/Modules/mod_proxy.pm
- t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh.pm


Changes:

=====================================
.github/workflows/ci.yml
=====================================
@@ -7,6 +7,9 @@ on:
     paths-ignore:
       - '*.html'
       - '*.md'
+      - 't/etc/**'
+      - 't/lib/**'
+      - 't/modules/**'
   pull_request:
     branches:
       - master
@@ -18,6 +21,11 @@ jobs:
     runs-on: ubuntu-latest
 
     env:
+      # We need to avoid using NodeJS v20, because it doesn't work with
+      # older glibc versions.  See:
+      #  https://github.com/actions/checkout/issues/1809.
+      ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
+
       CI: true
 
     strategy:
@@ -27,8 +35,8 @@ jobs:
           - gcc
         container:
           - almalinux:8
-          - alpine:3.15
-          - ubuntu:18.04
+          - alpine:3.18
+          - ubuntu:22.04
 
     container: ${{ matrix.container }}
 
@@ -45,7 +53,7 @@ jobs:
           path: proftpd/contrib/mod_proxy
 
       - name: Whitespace check
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           apt-get update -qq
           apt-get install -y git
@@ -58,11 +66,11 @@ jobs:
           fi
 
       - name: Install Alpine packages
-        if: ${{ matrix.container == 'alpine:3.15' }}
+        if: ${{ matrix.container == 'alpine:3.18' }}
         run: |
           apk update
           # for builds
-          apk add bash build-base clang compiler-rt-static gcc make zlib-dev
+          apk add bash build-base clang compiler-rt gcc make zlib-dev
           # for unit tests
           apk add check check-dev subunit subunit-dev
           # for Redis support
@@ -95,7 +103,7 @@ jobs:
           openssl version -a
 
       - name: Install Ubuntu packages
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           apt-get update -qq
           # for builds
@@ -121,7 +129,7 @@ jobs:
           openssl version -a
 
       - name: Prepare code coverage
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           lcov --directory proftpd --zerocounters
 
@@ -164,7 +172,7 @@ jobs:
         env:
           CC: ${{ matrix.compiler }}
         # Note: Skip the unit tests on Alpine
-        if: ${{ matrix.container != 'alpine:3.15' }}
+        if: ${{ matrix.container != 'alpine:3.18' }}
         run: |
           cd proftpd/contrib/mod_proxy
           make TEST_VERBOSE=1 check
@@ -201,7 +209,7 @@ jobs:
           CC: ${{ matrix.compiler }}
           CFLAGS: -fsanitize=address,undefined
           LDFLAGS: -fsanitize=address,undefined
-        if: ${{ matrix.compiler == 'clang' && matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.compiler == 'clang' && matrix.container == 'ubuntu:22.04' }}
         run: |
           cd proftpd
           make clean
@@ -212,7 +220,7 @@ jobs:
           make TEST_VERBOSE=1 check
 
       - name: Check HTML docs
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           cd proftpd/contrib/mod_proxy
           for f in $(/bin/ls *.html); do echo "Processing $f"; tidy -errors -omit -q $f; done || exit 0


=====================================
.github/workflows/codeql.yml
=====================================
@@ -8,6 +8,9 @@ on:
       - '*.html'
       - '**/*.md'
       - '**/doc/*'
+      - 't/etc/**'
+      - 't/lib/**'
+      - 't/modules/**'
   pull_request:
     branches:
       - master
@@ -34,12 +37,12 @@ jobs:
 
     steps:
       - name: Checkout ProFTPD
-        uses: actions/checkout at v3
+        uses: actions/checkout at v4
         with:
           repository: proftpd/proftpd
 
       - name: Checkout mod_proxy
-        uses: actions/checkout at v3
+        uses: actions/checkout at v4
         with:
           path: contrib/mod_proxy
 


=====================================
.github/workflows/regressions.yml
=====================================
@@ -16,6 +16,11 @@ jobs:
     runs-on: ubuntu-latest
 
     env:
+      # We need to avoid using NodeJS v20, because it doesn't work with
+      # older glibc versions.  See:
+      #  https://github.com/actions/checkout/issues/1809.
+      ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
+
       DEBIAN_FRONTEND: noninteractive
       REDIS_HOST: redis
       TZ: America/Los_Angeles
@@ -36,7 +41,7 @@ jobs:
         compiler:
           - gcc
         container:
-          - ubuntu:18.04
+          - ubuntu:22.04
 
     container: ${{ matrix.container }}
 


=====================================
include/proxy/tls.h
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy TLS API
- * Copyright (c) 2015-2021 TJ Saunders
+ * Copyright (c) 2015-2024 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
@@ -39,7 +39,9 @@
 # include <openssl/x509v3.h>
 # include <openssl/rand.h>
 # if OPENSSL_VERSION_NUMBER > 0x000907000L
-#  include <openssl/engine.h>
+#  if defined(PR_USE_OPENSSL_ENGINE)
+#   include <openssl/engine.h>
+#  endif /* PR_USE_OPENSSL_ENGINE */
 #  include <openssl/ocsp.h>
 # endif
 # ifdef PR_USE_OPENSSL_ECC


=====================================
lib/proxy/conn.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy conn implementation
- * Copyright (c) 2012-2023 TJ Saunders
+ * Copyright (c) 2012-2025 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
@@ -677,7 +677,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
   const char *remote_ipstr = NULL;
   unsigned int remote_port;
   conn_t *server_conn, *ctrl_conn;
-  int res;
+  int res, default_inet_family = 0, xerrno;
 
   if (proxy_sess->connect_timeout > 0) {
     const char *notes_key = "mod_proxy.proxy-connect-address";
@@ -750,8 +750,21 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
   }
 
   bind_addr = proxy_sess->src_addr;
+
+  /* We need to set the default inet family to use for the local address of
+   * our socket.  We do NOT want to just use the family of the local address of
+   * our control connection, since we could be listening on an IPv6 address
+   * and want to connect to a backend IPv4 address, or vice versa; see
+   * Issue #272.
+   */
   if (bind_addr == NULL) {
-    bind_addr = local_addr;
+    int remote_family;
+
+    remote_family = pr_netaddr_get_family(remote_addr);
+
+    pr_trace_msg(trace_channel, 9, "using %s family for socket local address",
+      remote_family == AF_INET ? "IPv4" : "IPv6");
+    default_inet_family = pr_inet_set_default_family(p, remote_family);
   }
 
   /* Note: IF mod_proxy is running on localhost, and the connection to be
@@ -760,7 +773,8 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
    * and of course not reachable from a public IP.  Thus we check for this
    * edge case (which happens often for development).
    */
-  if (pr_netaddr_is_loopback(bind_addr) == TRUE &&
+  if (bind_addr != NULL &&
+      pr_netaddr_is_loopback(bind_addr) == TRUE &&
       pr_netaddr_is_loopback(remote_addr) != TRUE) {
     const char *local_name;
     const pr_netaddr_t *new_local_addr;
@@ -802,9 +816,14 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
   }
 
   server_conn = pr_inet_create_conn(p, -1, bind_addr, INPORT_ANY, FALSE);
-  if (server_conn == NULL) {
-    int xerrno = errno;
+  xerrno = errno;
 
+  /* Restore the previous default inet family if necessary. */
+  if (bind_addr == NULL) {
+    (void) pr_inet_set_default_family(p, default_inet_family);
+  }
+
+  if (server_conn == NULL) {
     (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
       "error creating connection to %s: %s", pr_netaddr_get_ipstr(bind_addr),
       strerror(xerrno));
@@ -821,7 +840,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
   res = pr_inet_connect_nowait(p, server_conn, remote_addr,
     ntohs(pr_netaddr_get_port(remote_addr)));
   if (res < 0) {
-    int xerrno = errno;
+    xerrno = errno;
 
     (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
       "error starting connect to %s#%u: %s", remote_ipstr, remote_port,
@@ -884,7 +903,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
     nstrm = proxy_netio_open(p, PR_NETIO_STRM_OTHR, server_conn->listen_fd,
       nstrm_mode);
     if (nstrm == NULL) {
-      int xerrno = errno;
+      xerrno = errno;
 
       (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
         "error opening stream to %s#%u: %s", remote_ipstr, remote_port,
@@ -908,7 +927,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
       switch (polled) {
         case 1: {
           /* Aborted, timed out.  Note that we shouldn't reach here. */
-          int xerrno = ETIMEDOUT;
+          xerrno = ETIMEDOUT;
 
           (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
             "error connecting to %s#%u: %s", remote_ipstr, remote_port,
@@ -923,7 +942,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
 
         case -1: {
           /* Error */
-          int xerrno = nstrm->strm_errno;
+          xerrno = nstrm->strm_errno;
 
           if (xerrno == 0) {
             xerrno = errno;
@@ -957,7 +976,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
 
           res = pr_inet_get_conn_info(server_conn, server_conn->listen_fd);
           if (res < 0) {
-            int xerrno = errno;
+            xerrno = errno;
 
             (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
               "error obtaining local socket info on fd %d: %s",
@@ -986,7 +1005,7 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
   ctrl_conn = proxy_inet_openrw(p, server_conn, NULL, PR_NETIO_STRM_CTRL, -1,
     -1, -1, FALSE);
   if (ctrl_conn == NULL) {
-    int xerrno = errno;
+    xerrno = errno;
 
     (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
       "unable to open control connection to %s#%u: %s", remote_ipstr,


=====================================
lib/proxy/ftp/conn.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy FTP connection routines
- * Copyright (c) 2013-2022 TJ Saunders
+ * Copyright (c) 2013-2025 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
@@ -134,7 +134,7 @@ conn_t *proxy_ftp_conn_accept(pool *p, conn_t *data_conn, conn_t *ctrl_conn,
 conn_t *proxy_ftp_conn_connect(pool *p, const pr_netaddr_t *bind_addr,
     const pr_netaddr_t *remote_addr, int frontend_data) {
   conn_t *conn, *opened = NULL;
-  int res, reverse_dns;
+  int default_inet_family = 0, remote_family, res, reverse_dns, xerrno;
 
   if (p == NULL ||
       remote_addr == NULL) {
@@ -142,8 +142,19 @@ conn_t *proxy_ftp_conn_connect(pool *p, const pr_netaddr_t *bind_addr,
     return NULL;
   }
 
+  remote_family = pr_netaddr_get_family(remote_addr);
+  pr_trace_msg(trace_channel, 9,
+    "using %s family for backend socket address %s",
+    remote_family == AF_INET ? "IPv4" : "IPv6",
+    pr_netaddr_get_ipstr(remote_addr));
+  default_inet_family = pr_inet_set_default_family(p, remote_family);
+
   conn = pr_inet_create_conn(session.pool, -1, bind_addr, INPORT_ANY, TRUE);
+  xerrno = errno;
+
   if (conn == NULL) {
+    pr_inet_set_default_family(p, default_inet_family);
+    errno = xerrno;
     return NULL;
   }
 
@@ -179,7 +190,7 @@ conn_t *proxy_ftp_conn_connect(pool *p, const pr_netaddr_t *bind_addr,
   }
 
   if (res < 0) {
-    int xerrno = errno;
+    xerrno = errno;
 
     (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
       "unable to connect to %s#%u: %s\n", pr_netaddr_get_ipstr(remote_addr),
@@ -208,7 +219,7 @@ conn_t *proxy_ftp_conn_connect(pool *p, const pr_netaddr_t *bind_addr,
   pr_netaddr_set_reverse_dns(reverse_dns);
 
   if (opened == NULL) {
-    int xerrno = errno;
+    xerrno = errno;
 
     if (frontend_data == FALSE) {
       proxy_inet_close(session.pool, conn);


=====================================
lib/proxy/ftp/dirlist.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy FTP dirlist routines
- * Copyright (c) 2020-2021 TJ Saunders
+ * Copyright (c) 2020-2025 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
@@ -126,7 +126,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
     return NULL;
   }
 
-  pr_trace_msg(trace_channel, 19, "parsing Windows text: '%*s'",
+  pr_trace_msg(trace_channel, 19, "parsing Windows text: '%.*s'",
     (int) textlen, text);
 
   /* 24 is the minimum length of a well-formatted Windows directory listing
@@ -134,7 +134,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
    */
   if (textlen < 24) {
     pr_trace_msg(trace_channel, 3,
-      "error parsing Windows text (too short, need at least 24 bytes): '%*s'",
+      "error parsing Windows text (too short, need at least 24 bytes): '%.*s'",
         (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -148,7 +148,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
 
   if (strpbrk(buf, "0123456789-") == NULL) {
     pr_trace_msg(trace_channel, 3,
-      "unexpected Windows date format: '%*s'", (int) buflen, buf);
+      "unexpected Windows date format: '%.*s'", (int) buflen, buf);
     errno = EINVAL;
     return NULL;
   }
@@ -156,7 +156,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
   ptr += buflen;
   if (strncmp(ptr, "  ", 2) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Windows text (expected 2 spaces after date): '%*s'",
+      "malformed Windows text (expected 2 spaces after date): '%.*s'",
       (int) textlen, text); errno = EINVAL;
     return NULL;
   }
@@ -167,7 +167,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
   buf = pstrndup(p, ptr, buflen);
 
   if (strpbrk(buf, "AMP0123456789:") == NULL) {
-    pr_trace_msg(trace_channel, 3, "unexpected Windows time format: '%*s'",
+    pr_trace_msg(trace_channel, 3, "unexpected Windows time format: '%.*s'",
       (int) buflen, buf);
     errno = EINVAL;
     return NULL;
@@ -189,10 +189,10 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
   buf = pstrndup(p, text, buflen);
 
   pr_trace_msg(trace_channel, 19,
-    "parsing Windows-style timestamp: '%*s'", (int) buflen, buf);
+    "parsing Windows-style timestamp: '%.*s'", (int) buflen, buf);
   if (strptime(buf, windows_ts_fmt, pdf->tm) == NULL) {
     pr_trace_msg(trace_channel, 3,
-      "unexpected Windows timestamp format: '%*s'", (int) buflen, buf);
+      "unexpected Windows timestamp format: '%.*s'", (int) buflen, buf);
     errno = EINVAL;
     return NULL;
   }
@@ -202,7 +202,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
   /* We now expect at least 7 spaces. */
   if (strncmp(ptr, "       ", 7) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Windows text (expected 7 spaces after timestamp): '%*s'",
+      "malformed Windows text (expected 7 spaces after timestamp): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -220,7 +220,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
 
     if (strncmp(ptr, "          ", 10) != 0) {
       pr_trace_msg(trace_channel, 3,
-        "malformed Windows text (expected 10 spaces after dir): '%*s'",
+        "malformed Windows text (expected 10 spaces after dir): '%.*s'",
         (int) textlen, text);
       errno = EINVAL;
       return NULL;
@@ -245,7 +245,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
 
     if (strpbrk(buf, "0123456789 ") == NULL) {
       pr_trace_msg(trace_channel, 3,
-        "malformed Windows text (expected filesize with '%*s'): '%*s'",
+        "malformed Windows text (expected filesize with '%.*s'): '%.*s'",
         (int) buflen, buf, (int) textlen, text);
       errno = EINVAL;
       return NULL;
@@ -254,7 +254,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
     size_ptr = strpbrk(buf, "0123456789");
     if (size_ptr == NULL) {
       pr_trace_msg(trace_channel, 3,
-        "malformed Windows text (expected filesize not found): '%*s'",
+        "malformed Windows text (expected filesize not found): '%.*s'",
         (int) textlen, text);
       errno = EINVAL;
       return NULL;
@@ -265,7 +265,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
 
     if (pr_str_get_nbytes(size_ptr, NULL, &filesz) < 0) {
       pr_trace_msg(trace_channel, 3,
-        "malformed Windows text (unable to parse filesize: %s): '%*s'",
+        "malformed Windows text (unable to parse filesize: %s): '%.*s'",
         strerror(errno), (int) textlen, text);
       errno = EINVAL;
       return NULL;
@@ -276,7 +276,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
 
     if (strncmp(ptr, " ", 1) != 0) {
       pr_trace_msg(trace_channel, 3,
-        "malformed Windows text (missing space after filesize): '%*s'",
+        "malformed Windows text (missing space after filesize): '%.*s'",
         (int) textlen, text);
       errno = EINVAL;
       return NULL;
@@ -286,7 +286,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_dos(pool *p,
 
   } else {
     pr_trace_msg(trace_channel, 3,
-      "malformed Windows text (unexpected spaces after timestamp): '%*s'",
+      "malformed Windows text (unexpected spaces after timestamp): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -519,7 +519,7 @@ static int get_unix_user(pool *p, char *buf, size_t buflen,
   res = sscanf(buf, "%s", user);
   if (res != 1) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (unable to parse user): '%*s'", (int) buflen, buf);
+      "malformed Unix text (unable to parse user): '%.*s'", (int) buflen, buf);
     errno = EINVAL;
     return -1;
   }
@@ -552,7 +552,7 @@ static int get_unix_group(pool *p, char *buf, size_t buflen,
   res = sscanf(buf, "%s", group);
   if (res != 1) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (unable to parse group): '%*s'", (int) buflen, buf);
+      "malformed Unix text (unable to parse group): '%.*s'", (int) buflen, buf);
     errno = EINVAL;
     return -1;
   }
@@ -574,7 +574,7 @@ static int get_unix_filesize(pool *p, char *buf, size_t buflen,
 
   if (pr_str_get_nbytes(buf, NULL, &filesz) < 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (unable to parse filesize: %s): '%*s'",
+      "malformed Unix text (unable to parse filesize: %s): '%.*s'",
       strerror(errno), (int) buflen, buf);
     errno = EINVAL;
     return -1;
@@ -610,7 +610,7 @@ static int get_unix_timestamp(pool *p, char *buf, size_t buflen,
 
   if (found_month == FALSE) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (unable to month in '%*s')", (int) buflen, buf);
+      "malformed Unix text (unable to month in '%.*s')", (int) buflen, buf);
     errno = EINVAL;
     return -1;
   }
@@ -620,7 +620,7 @@ static int get_unix_timestamp(pool *p, char *buf, size_t buflen,
 
   if (strncmp(buf, " ", 1) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected space after month): '%*s'",
+      "malformed Unix text (expected space after month): '%.*s'",
       (int) buflen, buf);
     errno = EINVAL;
     return -1;
@@ -632,7 +632,7 @@ static int get_unix_timestamp(pool *p, char *buf, size_t buflen,
   res = sscanf(buf, "%2d", &mday);
   if (res != 1) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected mday after month): '%*s'",
+      "malformed Unix text (expected mday after month): '%.*s'",
       (int) buflen, buf);
     errno = EINVAL;
     return -1;
@@ -664,7 +664,7 @@ static int get_unix_timestamp(pool *p, char *buf, size_t buflen,
 
     } else {
       pr_trace_msg(trace_channel, 3,
-        "malformed Unix text (expected year/hour/min after mday): '%*s'",
+        "malformed Unix text (expected year/hour/min after mday): '%.*s'",
         (int) buflen, buf);
       errno = EINVAL;
       return -1;
@@ -689,7 +689,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     return NULL;
   }
 
-  pr_trace_msg(trace_channel, 19, "parsing Unix text: '%*s'",
+  pr_trace_msg(trace_channel, 19, "parsing Unix text: '%.*s'",
     (int) textlen, text);
 
   /* 43 is the minimum length of a well-formatted Unix directory listing
@@ -697,7 +697,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
    */
   if (textlen < 43) {
     pr_trace_msg(trace_channel, 3,
-      "error parsing Unix text (too short, need at least 43 bytes): '%*s'",
+      "error parsing Unix text (too short, need at least 43 bytes): '%.*s'",
         (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -775,7 +775,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
       break;
 
     default:
-      pr_trace_msg(trace_channel, 3, "unknown Unix file type: '%*s'", 1, text);
+      pr_trace_msg(trace_channel, 3, "unknown Unix file type: '%.*s'", 1, text);
       errno = EINVAL;
       return NULL;
   }
@@ -786,7 +786,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
 
   if (strpbrk(buf, "rwx-tTsS") == NULL) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected permissions): '%*s'", (int) buflen, buf);
+      "malformed Unix text (expected permissions): '%.*s'", (int) buflen, buf);
     errno = EINVAL;
     return NULL;
   }
@@ -800,7 +800,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
   ptr += buflen;
   if (strncmp(ptr, " ", 1) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected space after permissions): '%*s'",
+      "malformed Unix text (expected space after permissions): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -815,7 +815,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     ptr2 = strchr(ptr, ' ');
     if (ptr2 == NULL) {
       pr_trace_msg(trace_channel, 3,
-        "malformed Unix text (expected space after nlink): '%*s'",
+        "malformed Unix text (expected space after nlink): '%.*s'",
         (int) textlen, text);
       errno = EINVAL;
       return NULL;
@@ -829,7 +829,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     int xerrno = errno;
 
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected nlink with '%*s'): '%*s'", (int) buflen,
+      "malformed Unix text (expected nlink with '%.*s'): '%.*s'", (int) buflen,
       buf, (int) textlen, text);
 
     errno = xerrno;
@@ -839,7 +839,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
   ptr += buflen;
   if (strncmp(ptr, " ", 1) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected space after nlink): '%*s'",
+      "malformed Unix text (expected space after nlink): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -853,7 +853,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     int xerrno = errno;
 
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected user with '%*s'): '%*s'", (int) buflen,
+      "malformed Unix text (expected user with '%.*s'): '%.*s'", (int) buflen,
       buf, (int) textlen, text);
 
     errno = xerrno;
@@ -863,7 +863,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
   ptr += buflen;
   if (strncmp(ptr, " ", 1) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected space after user): '%*s'",
+      "malformed Unix text (expected space after user): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -877,7 +877,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     int xerrno = errno;
 
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected group with '%*s'): '%*s'", (int) buflen,
+      "malformed Unix text (expected group with '%.*s'): '%.*s'", (int) buflen,
       buf, (int) textlen, text);
 
     errno = xerrno;
@@ -887,7 +887,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
   ptr += buflen;
   if (strncmp(ptr, " ", 1) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected space after group): '%*s'",
+      "malformed Unix text (expected space after group): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -904,7 +904,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
   ptr2 = strchr(ptr, ' ');
   if (ptr2 == NULL) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected space after filesize): '%*s'",
+      "malformed Unix text (expected space after filesize): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -916,8 +916,8 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     int xerrno = errno;
 
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected filesize with '%*s'): '%*s'", (int) buflen,
-      buf, (int) textlen, text);
+      "malformed Unix text (expected filesize with '%.*s'): '%.*s'",
+      (int) buflen, buf, (int) textlen, text);
 
     errno = xerrno;
     return NULL;
@@ -926,7 +926,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
   ptr += buflen;
   if (strncmp(ptr, " ", 1) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected space after filesize): '%*s'",
+      "malformed Unix text (expected space after filesize): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -941,7 +941,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     int xerrno = errno;
 
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected timestamp with '%*s'): '%*s'",
+      "malformed Unix text (expected timestamp with '%.*s'): '%.*s'",
       (int) buflen, buf, (int) textlen, text);
 
     errno = xerrno;
@@ -951,7 +951,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
   ptr += buflen;
   if (strncmp(ptr, " ", 1) != 0) {
     pr_trace_msg(trace_channel, 3,
-      "malformed Unix text (expected space after timestamp): '%*s'",
+      "malformed Unix text (expected space after timestamp): '%.*s'",
       (int) textlen, text);
     errno = EINVAL;
     return NULL;
@@ -963,7 +963,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     ptr2 = strchr(ptr, ' ');
     if (ptr2 == NULL) {
       pr_trace_msg(trace_channel, 3,
-        "malformed Unix text (expected space after symlink source): '%*s'",
+        "malformed Unix text (expected space after symlink source): '%.*s'",
         (int) textlen, text);
       errno = EINVAL;
       return NULL;
@@ -975,7 +975,7 @@ struct proxy_dirlist_fileinfo *proxy_ftp_dirlist_fileinfo_from_unix(pool *p,
     ptr = ptr2 + 1;
     if (strncmp(ptr, "-> ", 3) != 0) {
       pr_trace_msg(trace_channel, 3,
-        "malformed Unix text (expected arrow after symlink source): '%*s'",
+        "malformed Unix text (expected arrow after symlink source): '%.*s'",
         (int) textlen, text);
       errno = EINVAL;
       return NULL;
@@ -1372,7 +1372,7 @@ int proxy_ftp_dirlist_to_text(pool *p, char *buf, size_t buflen,
     pdf = proxy_ftp_dirlist_fileinfo_from_text(tmp_pool, input_line,
       input_linelen, tm, user_data, proxy_sess->dirlist_opts);
     if (pdf == NULL) {
-      pr_trace_msg(trace_channel, 3, "error parsing text '%*s': %s",
+      pr_trace_msg(trace_channel, 3, "error parsing text '%.*s': %s",
         (int) input_linelen, input_line, strerror(errno));
       continue;
     }
@@ -1401,7 +1401,7 @@ int proxy_ftp_dirlist_to_text(pool *p, char *buf, size_t buflen,
     output_line = proxy_ftp_dirlist_fileinfo_to_facts(tmp_pool, pdf,
       &output_linelen);
 
-    pr_trace_msg(trace_channel, 19, "emitting line: '%*s'",
+    pr_trace_msg(trace_channel, 19, "emitting line: '%.*s'",
       (int) output_linelen, output_line);
 
     /* XXX What to do if this will exceed capacity of output buffer? */


=====================================
lib/proxy/ftp/xfer.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy FTP data transfer routines
- * Copyright (c) 2013-2022 TJ Saunders
+ * Copyright (c) 2013-2025 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
@@ -550,24 +550,44 @@ const pr_netaddr_t *proxy_ftp_xfer_prepare_passive(int policy_id, cmd_rec *cmd,
 
   remote_port = ntohs(pr_netaddr_get_port(remote_addr));
 
-  /* Make sure that the given address matches the address to which we
-   * originally connected.
+  /* See if the given address matches the address to which we originally
+   * connected.
    */
-
   if (pr_netaddr_cmp(remote_addr,
       proxy_sess->backend_ctrl_conn->remote_addr) != 0) {
-    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
-      "Refused %s address %s (address mismatch with %s)",
-      (char *) pasv_cmd->argv[0], pr_netaddr_get_ipstr(remote_addr),
-      pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr));
-    xerrno = EPERM;
 
-    pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
-      strerror(xerrno));
-    pr_response_flush(&resp_err_list);
+    pr_trace_msg(trace_channel, 2,
+      "backend passive transfer address %s does not match backend control "
+      "connection address %s", pr_netaddr_get_ipstr(remote_addr),
+      pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr));
 
-    errno = xerrno;
-    return NULL;
+    if (proxy_opts & PROXY_OPT_IGNORE_FOREIGN_ADDRESS) {
+      (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+        "Ignoring %s address %s per IgnoreForeignAddress ProxyOption, using %s "
+        "instead", (char *) pasv_cmd->argv[0],
+        pr_netaddr_get_ipstr(remote_addr),
+        pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr));
+
+      remote_addr = pr_netaddr_dup(proxy_sess->dataxfer_pool,
+        proxy_sess->backend_ctrl_conn->remote_addr);
+      pr_netaddr_set_port2((pr_netaddr_t *) remote_addr, remote_port);
+
+    } else {
+      if (!(proxy_opts & PROXY_OPT_ALLOW_FOREIGN_ADDRESS)) {
+        (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+          "Refused %s address %s (address mismatch with %s)",
+          (char *) pasv_cmd->argv[0], pr_netaddr_get_ipstr(remote_addr),
+          pr_netaddr_get_ipstr(proxy_sess->backend_ctrl_conn->remote_addr));
+        xerrno = EPERM;
+
+        pr_response_add_err(error_code, "%s: %s", (char *) cmd->argv[0],
+          strerror(xerrno));
+        pr_response_flush(&resp_err_list);
+
+        errno = xerrno;
+        return NULL;
+      }
+    }
   }
 
   if (remote_port < 1024) {


=====================================
lib/proxy/reverse/db.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy reverse datastore implementation
- * Copyright (c) 2012-2021 TJ Saunders
+ * Copyright (c) 2012-2025 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
@@ -1507,10 +1507,21 @@ static const struct proxy_conn *reverse_db_policy_next_backend(pool *p,
 
   if (proxy_reverse_policy_is_sticky(policy_id) != TRUE) {
     if (conns == NULL &&
-        default_backends != NULL &&
         db_backends == NULL) {
-      conns = default_backends->elts;
-      nelts = default_backends->nelts;
+
+      if (default_backends != NULL) {
+        conns = default_backends->elts;
+        nelts = default_backends->nelts;
+
+      } else {
+        /* Prevent possible null pointer dereferences later due to missing
+         * default URIs for non-sticky ConnectPolicy configurations.
+         */
+        pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+          "missing required default/global ProxyReverseServers");
+        errno = EPERM;
+        return NULL;
+      }
     }
   }
 


=====================================
lib/proxy/session.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy session routines
- * Copyright (c) 2012-2022 TJ Saunders
+ * Copyright (c) 2012-2024 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
@@ -299,8 +299,6 @@ int proxy_session_setup_env(pool *p, const char *user, int flags) {
       strerror(xerrno));
   }
 
-  session.disable_id_switching = TRUE;
-
   session.proc_prefix = pstrdup(session.pool, session.c->remote_name);
   session.sf_flags = 0;
 


=====================================
lib/proxy/ssh.c
=====================================
@@ -44,6 +44,7 @@
 #include "proxy/ssh/utf8.h"
 
 #if defined(PR_USE_OPENSSL)
+#include <openssl/conf.h>
 #include <openssl/err.h>
 #include <openssl/ssl.h>
 


=====================================
lib/proxy/ssh/crypto.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy SSH crypto
- * Copyright (c) 2021-2022 TJ Saunders
+ * Copyright (c) 2021-2024 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
@@ -36,6 +36,13 @@
 #endif /* !OPENSSL_NO_DES */
 #include <openssl/err.h>
 
+#if OPENSSL_VERSION_NUMBER > 0x000907000L && \
+    OPENSSL_VERSION_NUMBER < 0x10100000L && \
+    defined(PR_USE_OPENSSL_ENGINE)
+# include <openssl/engine.h>
+static const char *crypto_engine = NULL;
+#endif
+
 struct proxy_ssh_cipher {
   const char *name;
   const char *openssl_name;
@@ -1401,8 +1408,9 @@ void proxy_ssh_crypto_free(int flags) {
       pr_module_get("mod_tls.c") == NULL) {
 
 #if OPENSSL_VERSION_NUMBER > 0x000907000L && \
-    OPENSSL_VERSION_NUMBER < 0x10100000L
-    if (crypto_engine) {
+    OPENSSL_VERSION_NUMBER < 0x10100000L && \
+    defined(PR_USE_OPENSSL_ENGINE)
+    if (crypto_engine != NULL) {
       ENGINE_cleanup();
       crypto_engine = NULL;
     }


=====================================
lib/proxy/ssh/msg.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy SSH message format
- * Copyright (c) 2021-2022 TJ Saunders
+ * Copyright (c) 2021-2024 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
@@ -70,7 +70,7 @@ uint32_t proxy_ssh_msg_read_byte(pool *p, unsigned char **buf, uint32_t *buflen,
 }
 
 uint32_t proxy_ssh_msg_read_bool(pool *p, unsigned char **buf, uint32_t *buflen,
-    int *bool) {
+    int *b) {
   unsigned char byte = 0;
   uint32_t len;
 
@@ -81,7 +81,7 @@ uint32_t proxy_ssh_msg_read_bool(pool *p, unsigned char **buf, uint32_t *buflen,
     return 0;
   }
 
-  *bool = byte;
+  *b = byte;
   return len;
 }
 
@@ -355,8 +355,8 @@ uint32_t proxy_ssh_msg_write_byte(unsigned char **buf, uint32_t *buflen,
 }
 
 uint32_t proxy_ssh_msg_write_bool(unsigned char **buf, uint32_t *buflen,
-    unsigned char bool) {
-  return proxy_ssh_msg_write_byte(buf, buflen, bool == 0 ? 0 : 1);
+    unsigned char b) {
+  return proxy_ssh_msg_write_byte(buf, buflen, b == 0 ? 0 : 1);
 }
 
 uint32_t proxy_ssh_msg_write_data(unsigned char **buf, uint32_t *buflen,


=====================================
lib/proxy/tls.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy TLS implementation
- * Copyright (c) 2015-2022 TJ Saunders
+ * Copyright (c) 2015-2024 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
@@ -3641,7 +3641,8 @@ static void tls_tlsext_cb(SSL *ssl, int server, int type,
       (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
         "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s",
         server ? "server" : "client", extension_name, type, tlsext_datalen,
-        tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen, ext_info);
+        tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen,
+        ext_info != NULL ? ext_info : "");
 
       if (bio != NULL) {
         BIO_free(bio);
@@ -3766,7 +3767,8 @@ static void tls_tlsext_cb(SSL *ssl, int server, int type,
       (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
         "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s",
         server ? "server" : "client", extension_name, type, tlsext_datalen,
-        tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen, ext_info);
+        tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen,
+        ext_info != NULL ? ext_info : "");
 
       if (bio != NULL) {
         BIO_free(bio);
@@ -3878,7 +3880,8 @@ static void tls_tlsext_cb(SSL *ssl, int server, int type,
       (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
         "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s",
         server ? "server" : "client", extension_name, type, tlsext_datalen,
-        tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen, ext_info);
+        tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen,
+        ext_info != NULL ? ext_info : "");
 
       if (bio != NULL) {
         BIO_free(bio);
@@ -3932,7 +3935,8 @@ static void tls_tlsext_cb(SSL *ssl, int server, int type,
       (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
         "[tls.tlsext] TLS %s extension \"%s\" (ID %d, %d %s)%.*s",
         server ? "server" : "client", extension_name, type, tlsext_datalen,
-        tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen, ext_info);
+        tlsext_datalen != 1 ? "bytes" : "byte", (int) ext_infolen,
+        ext_info != NULL ? ext_info : "");
 
       if (bio != NULL) {
         BIO_free(bio);


=====================================
mod_proxy.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy
- * Copyright (c) 2012-2023 TJ Saunders
+ * Copyright (c) 2012-2025 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
@@ -485,6 +485,8 @@ static void proxy_restrict_session(void) {
    */
   PRIVS_REVOKE
 
+  session.disable_id_switching = TRUE;
+
   if (proxy_chroot != NULL) {
     (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
       "proxy session running as UID %lu, GID %lu, restricted to '%s'",
@@ -811,6 +813,12 @@ MODRET set_proxyoptions(cmd_rec *cmd) {
     } else if (strcmp(cmd->argv[i], "IgnoreConfigPerms") == 0) {
       opts |= PROXY_OPT_IGNORE_CONFIG_PERMS;
 
+    } else if (strcmp(cmd->argv[i], "AllowForeignAddress") == 0) {
+      opts |= PROXY_OPT_ALLOW_FOREIGN_ADDRESS;
+
+    } else if (strcmp(cmd->argv[i], "IgnoreForeignAddress") == 0) {
+      opts |= PROXY_OPT_IGNORE_FOREIGN_ADDRESS;
+
     } else {
       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown ProxyOption '",
         (char *) cmd->argv[i], "'", NULL));
@@ -820,6 +828,13 @@ MODRET set_proxyoptions(cmd_rec *cmd) {
   c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
   *((unsigned long *) c->argv[0]) = opts;
 
+  if (pr_module_exists("mod_ifsession.c")) {
+    /* These are needed in case this directive is used with mod_ifsession
+     * configuration.
+     */
+    c->flags |= CF_MULTI;
+  }
+
   return PR_HANDLED(cmd);
 }
 
@@ -978,6 +993,13 @@ MODRET set_proxyreverseservers(cmd_rec *cmd) {
     c->argv[1] = pstrdup(c->pool, uri);
   }
 
+  if (pr_module_exists("mod_ifsession.c")) {
+    /* These are needed in case this directive is used with mod_ifsession
+     * configuration.
+     */
+    c->flags |= CF_MULTI;
+  }
+
   return PR_HANDLED(cmd);
 }
 
@@ -1254,6 +1276,13 @@ MODRET set_proxysftpoptions(cmd_rec *cmd) {
   c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
   *((unsigned long *) c->argv[0]) = opts;
 
+  if (pr_module_exists("mod_ifsession.c")) {
+    /* These are needed in case this directive is used with mod_ifsession
+     * configuration.
+     */
+    c->flags |= CF_MULTI;
+  }
+
   return PR_HANDLED(cmd);
 }
 
@@ -1784,7 +1813,7 @@ MODRET set_proxytlscertkeyfile(cmd_rec *cmd) {
 
 /* usage: ProxyTLSCipherSuite [protocol] ciphers */
 MODRET set_proxytlsciphersuite(cmd_rec *cmd) {
-#ifdef PR_USE_OPENSSL
+#if defined(PR_USE_OPENSSL)
   config_rec *c = NULL;
   char *ciphersuite = NULL;
   int protocol = 0;
@@ -1880,6 +1909,13 @@ MODRET set_proxytlsciphersuite(cmd_rec *cmd) {
   c->argv[1] = palloc(c->pool, sizeof(int));
   *((int *) c->argv[1]) = protocol;
 
+  if (pr_module_exists("mod_ifsession.c")) {
+    /* These are needed in case this directive is used with mod_ifsession
+     * configuration.
+     */
+    c->flags |= CF_MULTI;
+  }
+
   return PR_HANDLED(cmd);
 #else
   CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
@@ -1929,7 +1965,7 @@ MODRET set_proxytlsengine(cmd_rec *cmd) {
 
 /* usage: ProxyTLSOptions ... */
 MODRET set_proxytlsoptions(cmd_rec *cmd) {
-#ifdef PR_USE_OPENSSL
+#if defined(PR_USE_OPENSSL)
   config_rec *c = NULL;
   register unsigned int i = 0;
   unsigned long opts = 0UL;
@@ -1970,6 +2006,13 @@ MODRET set_proxytlsoptions(cmd_rec *cmd) {
   c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
   *((unsigned long *) c->argv[0]) = opts;
 
+  if (pr_module_exists("mod_ifsession.c")) {
+    /* These are needed in case this directive is used with mod_ifsession
+     * configuration.
+     */
+    c->flags |= CF_MULTI;
+  }
+
   return PR_HANDLED(cmd);
 #else
   CONF_ERROR(cmd, "Missing required OpenSSL support (see --enable-openssl configure option)");
@@ -2409,10 +2452,16 @@ static int proxy_data_prepare_backend_conn(struct proxy_session *proxy_sess,
      * response until we connect to the backend data address/port.
      */
 
+    /* Specify the specific address/interface to use as the source address for
+     * connections to the destination server.
+     */
+    bind_addr = proxy_sess->src_addr;
+
     /* Check the family of the remote address vs what we'll be using to connect.
      * If there's a mismatch, we need to get an addr with the matching family.
      */
-    if (pr_netaddr_get_family(bind_addr) != pr_netaddr_get_family(proxy_sess->backend_data_addr)) {
+    if (bind_addr != NULL &&
+        pr_netaddr_get_family(bind_addr) != pr_netaddr_get_family(proxy_sess->backend_data_addr)) {
       /* In this scenario, the proxy has an IPv6 socket, but the remote/backend
        * server has an IPv4 (or IPv4-mapped IPv6) address.  OR it's the proxy
        * which has an IPv4 socket, and the remote/backend server has an IPv6
@@ -2456,10 +2505,6 @@ static int proxy_data_prepare_backend_conn(struct proxy_session *proxy_sess,
       }
     }
 
-    /* Specify the specific address/interface to use as the source address for
-     * connections to the destination server.
-     */
-    bind_addr = proxy_sess->src_addr;
     if (bind_addr == NULL) {
       bind_addr = local_addr;
     }
@@ -2512,14 +2557,9 @@ static int proxy_data_prepare_backend_conn(struct proxy_session *proxy_sess,
     backend_conn = proxy_ftp_conn_connect(cmd->pool, bind_addr,
       proxy_sess->backend_data_addr, FALSE);
     if (backend_conn == NULL) {
-      xerrno = errno;
-
-      pr_response_add_err(R_425, _("%s: %s"), (char *) cmd->argv[0],
-        strerror(xerrno));
-      pr_response_flush(&resp_err_list);
-
-      errno = xerrno;
-      return -1;
+      pr_trace_msg(trace_channel, 9,
+        "error connecting to backend server for passive data transfer: %s",
+        strerror(errno));
     }
 
     proxy_sess->backend_data_conn = backend_conn;
@@ -5566,8 +5606,38 @@ static int proxy_sess_init(void) {
    * needed.
    */
   proxy_sess = (struct proxy_session *) proxy_session_alloc(proxy_pool);
-  if (pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess,
-      sizeof(struct proxy_session)) < 0) {
+  res = pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess,
+    sizeof(struct proxy_session));
+
+  if (res < 0 &&
+      errno == ENOSPC) {
+    int nents, nmaxents;
+
+    nents = pr_table_count(session.notes);
+    nmaxents = nents * 2;
+
+    /* Attempt to handle the unusual case where the table is full, since
+     * we really need this note.
+     */
+    pr_trace_msg(trace_channel, 1,
+      "session notes table is full (%u), increasing entry limit to %u",
+      nents, nmaxents);
+
+    if (pr_table_ctl(session.notes, PR_TABLE_CTL_SET_MAX_ENTS,
+        &nmaxents) == 0) {
+      res = pr_table_add(session.notes, "mod_proxy.proxy-session", proxy_sess,
+        sizeof(struct proxy_session));
+
+    } else {
+      (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+        "error increasing session notes max entries: %s", strerror(errno));
+
+      res = -1;
+      errno = ENOSPC;
+    }
+  }
+
+  if (res < 0) {
     (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
       "error stashing proxy session note: %s", strerror(errno));
 


=====================================
mod_proxy.h.in
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy
- * Copyright (c) 2012-2024 TJ Saunders
+ * Copyright (c) 2012-2025 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
@@ -88,7 +88,7 @@
 /* Define if you have the strnstr(3) function.  */
 #undef HAVE_STRNSTR
 
-#define MOD_PROXY_VERSION	"mod_proxy/0.9.4"
+#define MOD_PROXY_VERSION	"mod_proxy/0.9.5"
 
 /* Make sure the version of proftpd is as necessary. */
 #if PROFTPD_VERSION_NUMBER < 0x0001030706
@@ -103,6 +103,8 @@
 #define PROXY_OPT_IGNORE_CONFIG_PERMS		0x0010
 #define PROXY_OPT_USE_PROXY_PROTOCOL_V2		0x0020
 #define PROXY_OPT_USE_PROXY_PROTOCOL_V2_TLVS	0x0040
+#define PROXY_OPT_ALLOW_FOREIGN_ADDRESS		0x0080
+#define PROXY_OPT_IGNORE_FOREIGN_ADDRESS	0x0100
 
 /* mod_proxy datastores */
 #define PROXY_DATASTORE_SQLITE			1


=====================================
mod_proxy.html
=====================================
@@ -492,6 +492,52 @@ behavior of <code>mod_proxy</code>.  For example:
 <p>
 The currently implemented options are:
 <ul>
+  <li><code>AllowForeignAddress</code><br>
+    <p>
+    The <a href="http://www.proftpd.org/docs/modules/mod_core.html#AllowForeignAddress"><code>AllowForeignAddress</code></a> directive controls the policy for
+    <i>frontend</i> data transfer requests from clients connecting to the proxy
+    server; it does <b>not</b> apply to <i>backend</i> data transfer requests.
+    For those, you will want to use this <code>AllowForeignAddress</code>
+    option:
+    <pre>
+  # Allow for cases where the backend server tells us to use a different IP
+  # address for data transfers than the IP address to which mod_proxy connected.
+  ProxyOptions AllowForeignAddress
+    </pre>
+
+    <p>
+    Note that the <code>IgnoreForeignAddress</code> option takes precedence
+    over this option.
+  </li>
+
+  <p>
+  <li><code>IgnoreForeignAddress</code><br>
+    <p>
+    Use this option to tell the <code>mod_proxy</code> module to <i>always</i>
+    use the same IP address for passive data transfers to the backend server as
+    used for the control connection, ignoring any different IP address that
+    the backend server may provide in its <code>PASV</code> response.
+
+    <p>
+    This option may be needed in cases where you see backend data transfers fail with errors logged such as:
+    <pre>
+      unable to connect to 172.16.1.2#8200: Network unreachable
+      unable to connect to 172.16.2.2#8200: Connection refused
+    </pre>
+    Example:
+    <pre>
+  # When the backend server tells us to use a different IP address for data
+  # transfers than the IP address to which mod_proxy connected, ignore that
+  # different IP address and use the original initial IP address.
+  ProxyOptions IgnoreForeignAddress
+    </pre>
+
+    <p>
+    Note that this option takes precedence over the
+    <code>AllowForeignAddress</code> option.
+  </li>
+
+  <p>
   <li><code>ShowFeatures</code><br>
     <p>
     When reverse proxying, <code>mod_proxy</code> defaults to not responding to
@@ -2546,7 +2592,7 @@ development library/header files <b>must</b> be installed for building
 <hr>
 
 <font size=2><b><i>
-© Copyright 2015-2024 TJ Saunders<br>
+© Copyright 2015-2025 TJ Saunders<br>
  All Rights Reserved<br>
 </i></b></font>
 


=====================================
t/lib/ProFTPD/Tests/Modules/mod_proxy.pm
=====================================
@@ -4963,11 +4963,12 @@ sub proxy_reverse_eprt_ipv6 {
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $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',
+    Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.conn: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,
     AuthOrder => 'mod_auth_file.c',
+    UseIPv6 => 'on',
 
     SocketBindTight => 'on',
     TimeoutIdle => $timeout_idle,
@@ -5036,19 +5037,16 @@ EOC
 
       my ($resp_code, $resp_msg) = $client->eprt('|2|::ffff:127.0.0.1|4856|');
 
-      my $expected;
-
-      $expected = 200;
+      my $expected = 200;
       $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
+        test_msg("Expected response code $expected, got $resp_code"));
 
       $expected = "EPRT command successful";
       $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
 
       $client->quit();
     };
-
     if ($@) {
       $ex = $@;
     }
@@ -23599,6 +23597,7 @@ sub proxy_forward_noproxyauth_login_ipv6_dst_addr {
 
     ServerIdent => 'on "Forward Proxy Server"',
     SocketBindTight => 'on',
+    UseIPv6 => 'on',
 
     IfModules => {
       'mod_proxy.c' => $proxy_config,
@@ -26121,6 +26120,7 @@ sub proxy_forward_eprt_ipv6 {
 
     ServerIdent => 'on "Forward Proxy Server"',
     SocketBindTight => 'on',
+    UseIPv6 => 'on',
 
     IfModules => {
       'mod_proxy.c' => $proxy_config,


=====================================
t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh.pm
=====================================
@@ -612,7 +612,28 @@ sub list_tests {
     }
   }
 
-  return testsuite_get_runnable_tests($TESTS);
+  my @tests = testsuite_get_runnable_tests($TESTS);
+
+  # These tests need to be skipped due to lack of support when using newer
+  # OpenSSL versions, i.e. 3.x or later.
+  my $skipped_tests = {
+    proxy_reverse_backend_ssh_cipher_3des_ctr => 1,
+    proxy_reverse_backend_ssh_cipher_arcfour128 => 1,
+    proxy_reverse_backend_ssh_cipher_arcfour256 => 1,
+    proxy_reverse_backend_ssh_cipher_blowfish_ctr => 1,
+  };
+
+  foreach my $key (keys(%$skipped_tests)) {
+    my $ntests = scalar(@tests);
+    for (my $i = 0; $i < $ntests; $i++) {
+      if ($tests[$i] eq $key) {
+        splice(@tests, $i, 1);
+        last;
+      }
+    }
+  }
+
+  return @tests;
 }
 
 sub set_up {



View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-proxy/-/commit/1cdea7e4fcccb42c1fb3df022a8b2a6fe1232ae5

-- 
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-proxy/-/commit/1cdea7e4fcccb42c1fb3df022a8b2a6fe1232ae5
You're receiving this email because of your account on salsa.debian.org.




More information about the Pkg-proftpd-maintainers mailing list