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

Hilmar Preuße (@hilmar) gitlab at salsa.debian.org
Sun Jun 7 17:37:45 BST 2026



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


Commits:
d26450d2 by Hilmar Preuße at 2026-06-07T18:33:41+02:00
New upstream version 0.9.7
- - - - -
0127276e by Hilmar Preuße at 2026-06-07T18:33:43+02:00
Update upstream source from tag 'upstream/0.9.7'

Update to upstream version '0.9.7'
with Debian dir 7c379cae4ca53c4ee82af913331e28044c2d4ed7
- - - - -
b4fa6c19 by Hilmar Preuße at 2026-06-07T18:35:58+02:00
Finalize d/changelog for upload.

- - - - -


16 changed files:

- .github/workflows/regressions.yml
- Makefile.in
- debian/changelog
- include/proxy/ssh/crypto.h
- include/proxy/ssh/keys.h
- + include/proxy/ssh/provider.h
- lib/proxy/ssh.c
- lib/proxy/ssh/crypto.c
- lib/proxy/ssh/kex.c
- lib/proxy/ssh/keys.c
- lib/proxy/ssh/mac.c
- lib/proxy/ssh/packet.c
- + lib/proxy/ssh/provider.c
- lib/proxy/tls.c
- mod_proxy.c
- mod_proxy.h.in


Changes:

=====================================
.github/workflows/regressions.yml
=====================================
@@ -10,6 +10,7 @@ on:
   pull_request:
     branches:
       - master
+  workflow_dispatch:
 
 jobs:
   build:


=====================================
Makefile.in
=====================================
@@ -47,6 +47,7 @@ MODULE_OBJS=mod_proxy.o \
   lib/proxy/ssh/cipher.o \
   lib/proxy/ssh/compress.o \
   lib/proxy/ssh/crypto.o \
+  lib/proxy/ssh/provider.o \
   lib/proxy/ssh/disconnect.o \
   lib/proxy/ssh/interop.o \
   lib/proxy/ssh/kex.o \
@@ -98,6 +99,7 @@ SHARED_MODULE_OBJS=mod_proxy.lo \
   lib/proxy/ssh/cipher.lo \
   lib/proxy/ssh/compress.lo \
   lib/proxy/ssh/crypto.lo \
+  lib/proxy/ssh/provider.lo \
   lib/proxy/ssh/disconnect.lo \
   lib/proxy/ssh/interop.lo \
   lib/proxy/ssh/kex.lo \


=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+proftpd-mod-proxy (0.9.7-1) unstable; urgency=medium
+
+  * New upstream release.
+    - Can be built using OpenSSL 4.0.0.
+
+ -- Hilmar Preuße <hille42 at debian.org>  Sun, 07 Jun 2026 18:34:53 +0200
+
 proftpd-mod-proxy (0.9.6-1) unstable; urgency=medium
 
   * New upstream version, bump standards version.


=====================================
include/proxy/ssh/crypto.h
=====================================
@@ -35,7 +35,9 @@
 void proxy_ssh_crypto_free(int flags);
 const EVP_CIPHER *proxy_ssh_crypto_get_cipher(const char *algo, size_t *key_len,
   size_t *auth_len, size_t *discard_len);
-const EVP_MD *proxy_ssh_crypto_get_digest(const char *algo, uint32_t *mac_len);
+const EVP_MD *proxy_ssh_crypto_get_digest(const char *algo, uint32_t *mac_len,
+  int *free_digest);
+void proxy_ssh_crypto_free_digest(const EVP_MD *md);
 const char *proxy_ssh_crypto_get_kexinit_cipher_list(pool *p);
 const char *proxy_ssh_crypto_get_kexinit_digest_list(pool *p);
 


=====================================
include/proxy/ssh/keys.h
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy SSH keys API
- * Copyright (c) 2021-2025 TJ Saunders
+ * Copyright (c) 2021-2026 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
@@ -13,8 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  *
  * As a special exemption, TJ Saunders and other respective copyright holders
  * give permission to link this program with OpenSSL, and distribute the
@@ -59,6 +58,9 @@ enum proxy_ssh_key_type_e proxy_ssh_keys_get_key_type(const char *algo);
 const char *proxy_ssh_keys_get_key_type_desc(enum proxy_ssh_key_type_e);
 
 void proxy_ssh_keys_free(void);
+int proxy_ssh_keys_compare_keys(pool *p, unsigned char *remote_key_data,
+  uint32_t remote_key_datalen, unsigned char *local_key_data,
+  uint32_t local_key_datalen);
 int proxy_ssh_keys_have_hostkey(enum proxy_ssh_key_type_e);
 int proxy_ssh_keys_get_hostkey(pool *p, const char *);
 const unsigned char *proxy_ssh_keys_get_hostkey_data(pool *,


=====================================
include/proxy/ssh/provider.h
=====================================
@@ -0,0 +1,32 @@
+/*
+ * ProFTPD - mod_proxy OpenSSL provider
+ * Copyright (c) 2026 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ *
+ * As a special exemption, TJ Saunders and other respective copyright holders
+ * give permission to link this program with OpenSSL, and distribute the
+ * resulting executable, without including the source code for OpenSSL in the
+ * source distribution.
+ */
+
+#ifndef MOD_PROXY_SSH_PROVIDER_H
+#define MOD_PROXY_SSH_PROVIDER_H
+
+#include "mod_proxy.h"
+
+int proxy_ssh_provider_init(void);
+void proxy_ssh_provider_free(void);
+
+#endif /* MOD_PROXY_SSH_PROVIDER_H */


=====================================
lib/proxy/ssh.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy SSH implementation
- * Copyright (c) 2021-2025 TJ Saunders
+ * Copyright (c) 2021-2026 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
@@ -13,8 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  *
  * As a special exemption, TJ Saunders and other respective copyright holders
  * give permission to link this program with OpenSSL, and distribute the
@@ -35,6 +34,7 @@
 #include "proxy/ssh/db.h"
 #include "proxy/ssh/redis.h"
 #include "proxy/ssh/crypto.h"
+#include "proxy/ssh/provider.h"
 #include "proxy/ssh/packet.h"
 #include "proxy/ssh/interop.h"
 #include "proxy/ssh/kex.h"
@@ -758,6 +758,7 @@ int proxy_ssh_free(pool *p) {
   proxy_ssh_cipher_free();
   proxy_ssh_mac_free();
   proxy_ssh_utf8_free();
+  proxy_ssh_provider_free();
   proxy_ssh_crypto_free(0);
 
   return 0;


=====================================
lib/proxy/ssh/crypto.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy SSH crypto
- * Copyright (c) 2021-2025 TJ Saunders
+ * Copyright (c) 2021-2026 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
@@ -13,8 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  *
  * As a special exemption, TJ Saunders and other respective copyright holders
  * give permission to link this program with OpenSSL, and distribute the
@@ -759,6 +758,9 @@ static const EVP_CIPHER *get_aes_ctr_cipher(int key_len) {
 }
 #endif /* OpenSSL implements AES CTR modes */
 
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+/* We'll use the Provider interface for UMAC digests in this case. */
+#else
 static int update_umac64(EVP_MD_CTX *ctx, const void *data, size_t len) {
   int res;
   void *md_data;
@@ -887,12 +889,26 @@ static int delete_umac128(EVP_MD_CTX *ctx) {
 
   return 1;
 }
+#endif /* OpenSSL before 4.x */
 
-static const EVP_MD *get_umac64_digest(void) {
-  EVP_MD *md;
+static const EVP_MD *get_umac64_digest(int *free_md) {
+  EVP_MD *md = NULL;
 
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
-    !defined(HAVE_LIBRESSL)
+  *free_md = FALSE;
+
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+  md = EVP_MD_fetch(NULL, "umac64", NULL);
+  if (md == NULL) {
+    pr_trace_msg(trace_channel, 4, "error fetching 'umac64' EVP_MD: %s",
+      proxy_ssh_crypto_get_errors());
+
+  } else {
+    *free_md = TRUE;
+  }
+
+#else
+# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+     !defined(HAVE_LIBRESSL)
   /* XXX TODO: At some point, we also need to call EVP_MD_meth_free() on
    * this, to avoid a resource leak.
    */
@@ -903,7 +919,7 @@ static const EVP_MD *get_umac64_digest(void) {
   EVP_MD_meth_set_update(md, update_umac64);
   EVP_MD_meth_set_final(md, final_umac64);
   EVP_MD_meth_set_cleanup(md, delete_umac64);
-#else
+# else
   static EVP_MD umac64_digest;
 
   memset(&umac64_digest, 0, sizeof(EVP_MD));
@@ -917,16 +933,30 @@ static const EVP_MD *get_umac64_digest(void) {
   umac64_digest.block_size = 32;
 
   md = &umac64_digest;
-#endif /* prior to OpenSSL-1.1.0 */
+# endif /* prior to OpenSSL-1.1.0 */
+#endif /* OpenSSL before 4.x */
 
   return md;
 }
 
-static const EVP_MD *get_umac128_digest(void) {
+static const EVP_MD *get_umac128_digest(int *free_md) {
   EVP_MD *md;
 
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
-    !defined(HAVE_LIBRESSL)
+  *free_md = FALSE;
+
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+  md = EVP_MD_fetch(NULL, "umac128", NULL);
+  if (md == NULL) {
+    pr_trace_msg(trace_channel, 4, "error fetching 'umac64' EVP_MD: %s",
+      proxy_ssh_crypto_get_errors());
+
+  } else {
+    *free_md = TRUE;
+  }
+
+#else
+# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+     !defined(HAVE_LIBRESSL)
   /* XXX TODO: At some point, we also need to call EVP_MD_meth_free() on
    * this, to avoid a resource leak.
    */
@@ -938,7 +968,7 @@ static const EVP_MD *get_umac128_digest(void) {
   EVP_MD_meth_set_final(md, final_umac128);
   EVP_MD_meth_set_cleanup(md, delete_umac128);
 
-#else
+# else
   static EVP_MD umac128_digest;
 
   memset(&umac128_digest, 0, sizeof(EVP_MD));
@@ -952,7 +982,8 @@ static const EVP_MD *get_umac128_digest(void) {
   umac128_digest.block_size = 64;
 
   md = &umac128_digest;
-#endif /* prior to OpenSSL-1.1.0 */
+# endif /* prior to OpenSSL-1.1.0 */
+#endif /* OpenSSL before 4.x */
 
   return md;
 }
@@ -1048,7 +1079,18 @@ const EVP_CIPHER *proxy_ssh_crypto_get_cipher(const char *name, size_t *key_len,
   return NULL;
 }
 
-const EVP_MD *proxy_ssh_crypto_get_digest(const char *name, uint32_t *mac_len) {
+void proxy_ssh_crypto_free_digest(const EVP_MD *md) {
+#if (OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(HAVE_LIBRESSL)) || \
+     (defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3080000L)
+  EVP_MD_free((EVP_MD *) md);
+#else
+  /* Avoid compiler warnings. */
+  (void) md;
+#endif /* OpenSSL-3.x/LibreSSL-3.8.x and later */
+}
+
+const EVP_MD *proxy_ssh_crypto_get_digest(const char *name, uint32_t *mac_len,
+    int *free_md) {
   register unsigned int i;
 
   if (name == NULL) {
@@ -1056,6 +1098,8 @@ const EVP_MD *proxy_ssh_crypto_get_digest(const char *name, uint32_t *mac_len) {
     return NULL;
   }
 
+  *free_md = FALSE;
+
   for (i = 0; digests[i].name; i++) {
     if (strcmp(digests[i].name, name) == 0) {
       const EVP_MD *digest = NULL;
@@ -1063,11 +1107,11 @@ const EVP_MD *proxy_ssh_crypto_get_digest(const char *name, uint32_t *mac_len) {
 #if OPENSSL_VERSION_NUMBER > 0x000907000L
       if (strcmp(name, "umac-64 at openssh.com") == 0 ||
           strcmp(name, "umac-64-etm at openssh.com") == 0) {
-        digest = get_umac64_digest();
+        digest = get_umac64_digest(free_md);
 
       } else if (strcmp(name, "umac-128 at openssh.com") == 0 ||
                  strcmp(name, "umac-128-etm at openssh.com") == 0) {
-        digest = get_umac128_digest();
+        digest = get_umac128_digest(free_md);
 #else
       if (FALSE) {
 #endif /* OpenSSL older than 0.9.7 */


=====================================
lib/proxy/ssh/kex.c
=====================================
@@ -824,7 +824,7 @@ static int have_good_dh(DH *dh, const BIGNUM *pub_key) {
 }
 
 static int get_dh_nbits(struct proxy_ssh_kex *kex) {
-  int dh_nbits = 0, dh_size = 0;
+  int dh_nbits = 0, dh_size = 0, free_digest = FALSE;
   const char *algo;
   const EVP_CIPHER *cipher;
   const EVP_MD *digest;
@@ -884,7 +884,7 @@ static int get_dh_nbits(struct proxy_ssh_kex *kex) {
   }
 
   algo = kex->session_names->c2s_mac_algo;
-  digest = proxy_ssh_crypto_get_digest(algo, NULL);
+  digest = proxy_ssh_crypto_get_digest(algo, NULL, &free_digest);
   if (digest != NULL) {
     int mac_len;
 
@@ -895,10 +895,14 @@ static int get_dh_nbits(struct proxy_ssh_kex *kex) {
         "set DH size to %d bytes, matching client-to-server '%s' digest size",
         dh_size, algo);
     }
+
+    if (free_digest == TRUE) {
+      proxy_ssh_crypto_free_digest(digest);
+    }
   }
 
   algo = kex->session_names->s2c_mac_algo;
-  digest = proxy_ssh_crypto_get_digest(algo, NULL);
+  digest = proxy_ssh_crypto_get_digest(algo, NULL, &free_digest);
   if (digest != NULL) {
     int mac_len;
 
@@ -909,6 +913,10 @@ static int get_dh_nbits(struct proxy_ssh_kex *kex) {
         "set DH size to %d bytes, matching server-to-client '%s' digest size",
         dh_size, algo);
     }
+
+    if (free_digest == TRUE) {
+      proxy_ssh_crypto_free_digest(digest);
+    }
   }
 
   /* We want to return bits, not bytes. */


=====================================
lib/proxy/ssh/keys.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy SSH key mgmt (keys)
- * Copyright (c) 2021-2025 TJ Saunders
+ * Copyright (c) 2021-2026 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
@@ -13,8 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
+ * along with this program; see <https://www.gnu.org/licenses/>.
  *
  * As a special exemption, TJ Saunders and other respective copyright holders
  * give permission to link this program with OpenSSL, and distribute the
@@ -1902,6 +1901,7 @@ const char *proxy_ssh_keys_get_key_type_desc(enum proxy_ssh_key_type_e key_type)
   return key_desc;
 }
 
+#if defined(PR_USE_OPENSSL_ECC)
 /* This is used to validate the ECDSA parameters we might receive e.g. from
  * a server.  These checks come from Section 3.2.2.1 of 'Standards for
  * Efficient Cryptography Group, "Elliptic Curve Cryptography", SEC 1,
@@ -2102,6 +2102,7 @@ int proxy_ssh_keys_validate_ecdsa_params(const EC_GROUP *group,
   BN_CTX_free(bn_ctx);
   return 0;
 }
+#endif /* PR_USE_OPENSSL_ECC */
 
 #ifdef SFTP_DEBUG_KEYS
 static void debug_rsa_key(pool *p, const char *label, RSA *rsa) {
@@ -4965,6 +4966,13 @@ static int verify_rsa_signed_data(pool *p, EVP_PKEY *pkey,
   }
 
   rsa = EVP_PKEY_get1_RSA(pkey);
+  if (rsa == NULL) {
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "error obtaining RSA key: %s",  proxy_ssh_crypto_get_errors());
+    errno = EINVAL;
+    return -1;
+  }
+
   modulus_len = RSA_size(rsa);
 
   /* If the signature provided by the server is more than the expected
@@ -5084,6 +5092,8 @@ static int dsa_verify_signed_data(pool *p, EVP_PKEY *pkey,
   if (sig_len != 40) {
     (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
       "bad DSA signature len (%lu)", (unsigned long) sig_len);
+    errno = EINVAL;
+    return -1;
   }
 
   len = proxy_ssh_msg_read_data(p, &signature, &signature_len, sig_len, &sig);
@@ -5100,6 +5110,12 @@ static int dsa_verify_signed_data(pool *p, EVP_PKEY *pkey,
   }
 
   dsa = EVP_PKEY_get1_DSA(pkey);
+  if (dsa == NULL) {
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "error obtaining DSA key: %s",  proxy_ssh_crypto_get_errors());
+    errno = EINVAL;
+    return -1;
+  }
 
   dsa_sig = DSA_SIG_new();
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
@@ -5488,6 +5504,14 @@ int proxy_ssh_keys_verify_signed_data(pool *p, const char *pubkey_algo,
   }
 
   if (strcmp(sig_type, "ssh-rsa") == 0) {
+    if (strcmp(pubkey_algo, sig_type) != 0) {
+      (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+        "unable to verify signed data: signature type '%s' does not match "
+        "publickey algorithm '%s'", sig_type, pubkey_algo);
+      errno = EINVAL;
+      return -1;
+    }
+
     res = rsa_verify_signed_data(p, pkey, signature, signature_len, sig_data,
       sig_datalen);
 
@@ -5505,6 +5529,14 @@ int proxy_ssh_keys_verify_signed_data(pool *p, const char *pubkey_algo,
 
 #if !defined(OPENSSL_NO_DSA)
   } else if (strcmp(sig_type, "ssh-dss") == 0) {
+    if (strcmp(pubkey_algo, sig_type) != 0) {
+      (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+        "unable to verify signed data: signature type '%s' does not match "
+        "publickey algorithm '%s'", sig_type, pubkey_algo);
+      errno = EINVAL;
+      return -1;
+    }
+
     res = dsa_verify_signed_data(p, pkey, signature, signature_len, sig_data,
       sig_datalen);
 #endif /* !OPENSSL_NO_DSA */
@@ -5513,7 +5545,6 @@ int proxy_ssh_keys_verify_signed_data(pool *p, const char *pubkey_algo,
   } else if (strcmp(sig_type, "ecdsa-sha2-nistp256") == 0 ||
              strcmp(sig_type, "ecdsa-sha2-nistp384") == 0 ||
              strcmp(sig_type, "ecdsa-sha2-nistp521") == 0) {
-
     if (strcmp(pubkey_algo, sig_type) != 0) {
       (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
         "unable to verify signed data: public key algorithm '%s' does not "


=====================================
lib/proxy/ssh/mac.c
=====================================
@@ -41,6 +41,7 @@ struct proxy_ssh_mac {
   const char *algo;
   unsigned int algo_type;
   int is_etm;
+  int free_digest;
 
   const EVP_MD *digest;
   unsigned char *key;
@@ -69,15 +70,15 @@ struct proxy_ssh_mac {
  */
 
 static struct proxy_ssh_mac read_macs[] = {
-  { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 },
-  { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 }
+  { NULL, NULL, 0, FALSE, FALSE, NULL, NULL, 0, 0, 0 },
+  { NULL, NULL, 0, FALSE, FALSE, NULL, NULL, 0, 0, 0 }
 };
 static HMAC_CTX *hmac_read_ctxs[2];
 static struct umac_ctx *umac_read_ctxs[2];
 
 static struct proxy_ssh_mac write_macs[] = {
-  { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 },
-  { NULL, NULL, 0, FALSE, NULL, NULL, 0, 0, 0 }
+  { NULL, NULL, 0, FALSE, FALSE, NULL, NULL, 0, 0, 0 },
+  { NULL, NULL, 0, FALSE, FALSE, NULL, NULL, 0, 0, 0 }
 };
 static HMAC_CTX *hmac_write_ctxs[2];
 static struct umac_ctx *umac_write_ctxs[2];
@@ -781,16 +782,25 @@ int proxy_ssh_mac_set_read_algo(pool *p, const char *algo) {
       case PROXY_SSH_MAC_ALGO_TYPE_UMAC64:
         proxy_ssh_umac_delete(umac_read_ctxs[idx]);
         umac_read_ctxs[idx] = NULL;
+        if (read_macs[idx].free_digest == TRUE) {
+          proxy_ssh_crypto_free_digest(read_macs[idx].digest);
+          read_macs[idx].digest = NULL;
+        }
         break;
 
       case PROXY_SSH_MAC_ALGO_TYPE_UMAC128:
         proxy_ssh_umac128_delete(umac_read_ctxs[idx]);
         umac_read_ctxs[idx] = NULL;
+        if (read_macs[idx].free_digest == TRUE) {
+          proxy_ssh_crypto_free_digest(read_macs[idx].digest);
+          read_macs[idx].digest = NULL;
+        }
         break;
     }
   }
 
-  read_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len);
+  read_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len,
+    &(read_macs[idx].free_digest));
   if (read_macs[idx].digest == NULL) {
     return -1;
   }
@@ -955,16 +965,25 @@ int proxy_ssh_mac_set_write_algo(pool *p, const char *algo) {
       case PROXY_SSH_MAC_ALGO_TYPE_UMAC64:
         proxy_ssh_umac_delete(umac_write_ctxs[idx]);
         umac_write_ctxs[idx] = NULL;
+        if (write_macs[idx].free_digest == TRUE) {
+          proxy_ssh_crypto_free_digest(write_macs[idx].digest);
+          write_macs[idx].digest = NULL;
+        }
         break;
 
       case PROXY_SSH_MAC_ALGO_TYPE_UMAC128:
         proxy_ssh_umac128_delete(umac_write_ctxs[idx]);
         umac_write_ctxs[idx] = NULL;
+        if (write_macs[idx].free_digest == TRUE) {
+          proxy_ssh_crypto_free_digest(write_macs[idx].digest);
+          write_macs[idx].digest = NULL;
+        }
         break;
     }
   }
 
-  write_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len);
+  write_macs[idx].digest = proxy_ssh_crypto_get_digest(algo, &mac_len,
+    &(write_macs[idx].free_digest));
   if (write_macs[idx].digest == NULL) {
     return -1;
   }


=====================================
lib/proxy/ssh/packet.c
=====================================
@@ -1264,6 +1264,15 @@ int proxy_ssh_packet_read(conn_t *conn, struct proxy_ssh_packet *pkt) {
       pr_trace_msg(trace_channel, 20, "SSH2 packet padding len = %u bytes",
         (unsigned int) pkt->padding_len);
 
+      if (pkt->packet_len < (pkt->padding_len + 1)) {
+        (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+          "illegal padding length (%u bytes) exceeds packet length "
+          "(%lu bytes)", (unsigned int) pkt->padding_len,
+          (unsigned long) pkt->packet_len);
+        read_packet_discard(conn);
+        return -1;
+      }
+
       pkt->payload_len = (pkt->packet_len - pkt->padding_len - 1);
       pr_trace_msg(trace_channel, 20, "SSH2 packet payload len = %lu bytes",
         (unsigned long) pkt->payload_len);


=====================================
lib/proxy/ssh/provider.c
=====================================
@@ -0,0 +1,324 @@
+/*
+ * ProFTPD - mod_proxy OpenSSL provider
+ * Copyright (c) 2026 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ *
+ * As a special exemption, TJ Saunders and other respective copyright holders
+ * give permission to link this program with OpenSSL, and distribute the
+ * resulting executable, without including the source code for OpenSSL in the
+ * source distribution.
+ */
+
+#include "mod_proxy.h"
+#include "proxy/ssh/crypto.h"
+#include "proxy/ssh/umac.h"
+#include "proxy/ssh/provider.h"
+
+/* The mod_sftp module in ProFTPD 1.3.10rc2 also registers a 'umac' provider
+ * with OpenSSL; see Issue #2120.
+ *
+ * So we only need our copy of that provider if using an older version of
+ * ProFTPD.  Otherwise, we can reuse the algorithms provided by mod_sftp's
+ * provider.
+ */
+
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL) && \
+    PROFTPD_VERSION_NUMBER < 0x0001030A02
+# include <openssl/core.h>
+# include <openssl/core_dispatch.h>
+# include <openssl/core_names.h>
+# include <openssl/params.h>
+# include <openssl/provider.h>
+
+static OSSL_PROVIDER *umac_provider = NULL;
+
+static const char *trace_channel = "proxy.ssh.provider";
+
+/* Our custom algorithm provider implementation. */
+
+/* UMAC */
+
+typedef struct umac_ctx_st {
+  struct umac_ctx *umac;
+} UMAC_CTX;
+
+static void *umac_ctx_new(void *vctx) {
+  UMAC_CTX *ctx;
+
+  ctx = OPENSSL_zalloc(sizeof(UMAC_CTX));
+  return ctx;
+}
+
+static void umac_ctx_free(void *vctx) {
+  UMAC_CTX *ctx;
+
+  ctx = vctx;
+  if (ctx->umac != NULL) {
+    proxy_ssh_umac_delete(ctx->umac);
+    ctx->umac = NULL;
+  }
+
+  OPENSSL_free(ctx);
+}
+
+/* The Provider interface for digests expects an "init" callback, even though
+ * it is not functionally needed for our situation.
+ */
+static int umac_md_init(void *vctx) {
+  (void) vctx;
+
+  return 1;
+}
+
+static const OSSL_PARAM umac_params[] = {
+  OSSL_PARAM_size_t(OSSL_DIGEST_PARAM_BLOCK_SIZE, NULL),
+  OSSL_PARAM_size_t(OSSL_DIGEST_PARAM_SIZE, NULL),
+  OSSL_PARAM_END
+};
+
+/* UMAC64 */
+
+static int umac64_md_update(void *vctx, const unsigned char *data, size_t len) {
+  UMAC_CTX *ctx;
+  struct umac_ctx *umac;
+
+  ctx = vctx;
+  umac = ctx->umac;
+
+  /* The allocation of the umac_ctx is deliberately delayed until the first
+   * update, since the computation of keys depends on the initial bytes
+   * provided.
+   */
+  if (umac == NULL) {
+    umac = proxy_ssh_umac_new((unsigned char *) data);
+    if (umac == NULL) {
+      return 0;
+    }
+
+    ctx->umac = umac;
+    return 1;
+  }
+
+  return proxy_ssh_umac_update(umac, (unsigned char *) data, (long) len);
+}
+
+static int umac64_md_final(void *vctx, unsigned char *out, size_t *out_len,
+    size_t outsz) {
+  int res = 1;
+  struct umac_ctx *ctx;
+  unsigned char nonce[8];
+
+  ctx = vctx;
+
+  *out_len = outsz;
+
+  if (outsz != 0) {
+    res = proxy_ssh_umac_final(ctx, out, nonce);
+  }
+
+  return res;
+}
+
+static int umac64_get_params(void *provctx, OSSL_PARAM params[]) {
+  OSSL_PARAM *p;
+  int ok = 1;
+
+  p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_BLOCK_SIZE);
+  if (p != NULL) {
+    if (OSSL_PARAM_set_size_t(p, 32) != 1) {
+      ok = 0;
+    }
+  }
+
+  if (ok == 1) {
+    p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_SIZE);
+    if (p != NULL) {
+      if (OSSL_PARAM_set_size_t(p, 8) != 1) {
+        ok = 0;
+      }
+    }
+  }
+
+  return ok;
+}
+
+static const OSSL_PARAM *umac64_gettable_params(void) {
+  return umac_params;
+}
+
+static const OSSL_DISPATCH umac64_functions[] = {
+  { OSSL_FUNC_DIGEST_NEWCTX, (void (*)(void)) umac_ctx_new },
+  { OSSL_FUNC_DIGEST_FREECTX, (void (*)(void)) umac_ctx_free },
+  { OSSL_FUNC_DIGEST_INIT, (void (*)(void)) umac_md_init },
+  { OSSL_FUNC_DIGEST_UPDATE, (void (*)(void)) umac64_md_update },
+  { OSSL_FUNC_DIGEST_FINAL, (void (*)(void)) umac64_md_final },
+  { OSSL_FUNC_DIGEST_GET_PARAMS, (void (*)(void)) umac64_get_params },
+  { OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (void (*)(void)) umac64_gettable_params },
+
+  { 0, NULL }
+};
+
+/* UMAC128 */
+
+static int umac128_md_update(void *vctx, const unsigned char *data,
+    size_t len) {
+  UMAC_CTX *ctx;
+  struct umac_ctx *umac;
+
+  ctx = vctx;
+  umac = ctx->umac;
+
+  /* The allocation of the umac_ctx is deliberately delayed until the first
+   * update, since the computation of keys depends on the initial bytes
+   * provided.
+   */
+  if (umac == NULL) {
+    umac = proxy_ssh_umac128_new((unsigned char *) data);
+    if (umac == NULL) {
+      return 0;
+    }
+
+    ctx->umac = umac;
+    return 1;
+  }
+
+  return proxy_ssh_umac128_update(umac, (unsigned char *) data, (long) len);
+}
+
+static int umac128_md_final(void *vctx, unsigned char *out, size_t *out_len,
+    size_t outsz) {
+  int res = 1;
+  struct umac_ctx *ctx;
+  unsigned char nonce[8];
+
+  ctx = vctx;
+
+  *out_len = outsz;
+
+  if (outsz != 0) {
+    res = proxy_ssh_umac128_final(ctx, out, nonce);
+  }
+
+  return res;
+}
+
+static int umac128_get_params(void *provctx, OSSL_PARAM params[]) {
+  OSSL_PARAM *p;
+  int ok = 1;
+
+  p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_BLOCK_SIZE);
+  if (p != NULL) {
+    if (OSSL_PARAM_set_size_t(p, 64) != 1) {
+      ok = 0;
+    }
+  }
+
+  if (ok == 1) {
+    p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_SIZE);
+    if (p != NULL) {
+      if (OSSL_PARAM_set_size_t(p, 16) != 1) {
+        ok = 0;
+      }
+    }
+  }
+
+  return ok;
+}
+
+static const OSSL_PARAM *umac128_gettable_params(void) {
+  return umac_params;
+}
+
+static const OSSL_DISPATCH umac128_functions[] = {
+  { OSSL_FUNC_DIGEST_NEWCTX, (void (*)(void)) umac_ctx_new },
+  { OSSL_FUNC_DIGEST_FREECTX, (void (*)(void)) umac_ctx_free },
+  { OSSL_FUNC_DIGEST_INIT, (void (*)(void)) umac_md_init },
+  { OSSL_FUNC_DIGEST_UPDATE, (void (*)(void)) umac128_md_update },
+  { OSSL_FUNC_DIGEST_FINAL, (void (*)(void)) umac128_md_final },
+  { OSSL_FUNC_DIGEST_GET_PARAMS, (void (*)(void)) umac128_get_params },
+  { OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (void (*)(void)) umac128_gettable_params },
+
+  { 0, NULL }
+};
+
+static const OSSL_ALGORITHM umac_digests[] = {
+  { "umac64", NULL, umac64_functions },
+  { "umac128", NULL, umac128_functions },
+
+  { NULL, NULL, NULL }
+};
+
+static const OSSL_ALGORITHM *umac_provider_operations(void *provctx,
+    int operation_id, int *no_cache) {
+  *no_cache = 0;
+
+  if (operation_id == OSSL_OP_DIGEST) {
+    return umac_digests;
+  }
+
+  return NULL;
+}
+
+static const OSSL_DISPATCH umac_provider_functions[] = {
+  { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void)) umac_provider_operations },
+
+  { 0, NULL }
+};
+
+static int umac_provider_init(const OSSL_CORE_HANDLE *core,
+    const OSSL_DISPATCH *in, const OSSL_DISPATCH **out, void **provctx) {
+  *out = umac_provider_functions;
+  *provctx = (void *) core;
+
+  return 1;
+}
+#endif /* OpenSSL 4.x and later, ProFTPD before 1.3.10rc2 */
+
+int proxy_ssh_provider_init(void) {
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL) && \
+    PROFTPD_VERSION_NUMBER < 0x0001030A02
+  if (OSSL_PROVIDER_add_builtin(NULL, "umac", umac_provider_init) != 1) {
+    pr_log_debug(DEBUG1, MOD_PROXY_VERSION
+      ": error registering 'umac' OpenSSL provider: %s",
+      proxy_ssh_crypto_get_errors());
+
+  } else {
+    pr_trace_msg(trace_channel, 9, "%s", "registered 'umac' OpenSSL provider");
+  }
+
+  /* Load our custom OpenSSL algorithm provider. */
+  umac_provider = OSSL_PROVIDER_load(NULL, "umac");
+  if (umac_provider == NULL) {
+    pr_log_pri(PR_LOG_NOTICE, MOD_PROXY_VERSION
+      ": error loading 'umac' OpenSSL provider: %s",
+      proxy_ssh_crypto_get_errors());
+
+  } else {
+    pr_trace_msg(trace_channel, 9, "%s", "loaded 'umac' OpenSSL provider");
+  }
+#endif /* OpenSSL 4.x and later, ProFTPD before 1.3.10rc2 */
+
+  return 0;
+}
+
+void proxy_ssh_provider_free(void) {
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL) && \
+    PROFTPD_VERSION_NUMBER < 0x0001030A02
+  if (umac_provider != NULL) {
+    OSSL_PROVIDER_unload(umac_provider);
+    umac_provider = NULL;
+  }
+#endif /* OpenSSL 4.x and later, ProFTPD before 1.3.10rc2 */
+}


=====================================
lib/proxy/tls.c
=====================================
@@ -180,7 +180,7 @@ const char *proxy_tls_get_errors(void) {
   return str;
 }
 
-static char *tls_x509_name_oneline(X509_NAME *x509_name) {
+static char *tls_x509_name_oneline(const X509_NAME *x509_name) {
   static char buf[1024] = {'\0'};
 
   /* If we are using OpenSSL 0.9.6 or newer, we want to use
@@ -814,7 +814,12 @@ static int cert_match_dns_san(pool *p, X509 *cert, const char *dns_name,
         char *dns_san;
         size_t dns_sanlen;
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+    !defined(HAVE_LIBRESSL)
+        dns_san = (char *) ASN1_STRING_get0_data(alt_name->d.ia5);
+#else
         dns_san = (char *) ASN1_STRING_data(alt_name->d.ia5);
+#endif /* OpenSSL 1.1.x and later */
         dns_sanlen = strlen(dns_san);
 
         /* Check for subjectAltName values which contain embedded NULs.
@@ -881,7 +886,7 @@ static int cert_match_ip_san(pool *p, X509 *cert, const char *ipstr) {
       GENERAL_NAME *alt_name = sk_GENERAL_NAME_value(sans, i);
 
       if (alt_name->type == GEN_IPADD) {
-        unsigned char *san_data = NULL;
+        const unsigned char *san_data = NULL;
         int have_ipstr = FALSE, san_datalen;
 #if defined(PR_USE_IPV6)
         char san_ipstr[INET6_ADDRSTRLEN + 1] = {'\0'};
@@ -889,7 +894,13 @@ static int cert_match_ip_san(pool *p, X509 *cert, const char *ipstr) {
         char san_ipstr[INET_ADDRSTRLEN + 1] = {'\0'};
 #endif /* PR_USE_IPV6 */
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+    !defined(HAVE_LIBRESSL)
+        san_data = ASN1_STRING_get0_data(alt_name->d.ip);
+#else
         san_data = ASN1_STRING_data(alt_name->d.ip);
+#endif /* OpenSSL 1.1.x and later */
+
         memset(san_ipstr, '\0', sizeof(san_ipstr));
 
         san_datalen = ASN1_STRING_length(alt_name->d.ip);
@@ -971,9 +982,9 @@ static int cert_match_ip_san(pool *p, X509 *cert, const char *ipstr) {
 static int cert_match_cn(pool *p, X509 *cert, const char *name,
     int allow_wildcards) {
   int matched = FALSE, idx = -1;
-  X509_NAME *subj_name = NULL;
-  X509_NAME_ENTRY *cn_entry = NULL;
-  ASN1_STRING *cn_asn1 = NULL;
+  const X509_NAME *subj_name = NULL;
+  const X509_NAME_ENTRY *cn_entry = NULL;
+  const ASN1_STRING *cn_asn1 = NULL;
   char *cn_str = NULL;
   size_t cn_len = 0;
 
@@ -1015,7 +1026,12 @@ static int cert_match_cn(pool *p, X509 *cert, const char *name,
     return 0;
   }
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+    !defined(HAVE_LIBRESSL)
+  cn_str = (char *) ASN1_STRING_get0_data(cn_asn1);
+#else
   cn_str = (char *) ASN1_STRING_data(cn_asn1);
+#endif /* OpenSSL 1.1.x and later */
 
   /* Check for CommonName values which contain embedded NULs.  This can cause
    * verification problems (spoofing), e.g. if the string is


=====================================
mod_proxy.c
=====================================
@@ -48,6 +48,7 @@
 #include "proxy/ssh/auth.h"
 #include "proxy/ssh/crypto.h"
 #include "proxy/tls/pkcs11.h"
+#include "proxy/ssh/provider.h"
 
 #if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL)
 # include <openssl/provider.h>
@@ -1101,10 +1102,18 @@ MODRET set_proxysftpdigests(cmd_rec *cmd) {
   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
 
   for (i = 1; i < cmd->argc; i++) {
-    if (proxy_ssh_crypto_get_digest(cmd->argv[i], NULL) == NULL) {
+    const EVP_MD *digest;
+    int free_digest = FALSE;
+
+    digest = proxy_ssh_crypto_get_digest(cmd->argv[i], NULL, &free_digest);
+    if (digest == NULL) {
       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
         "unsupported digest algorithm: ", cmd->argv[i], NULL));
     }
+
+    if (free_digest == TRUE) {
+      proxy_ssh_crypto_free_digest(digest);
+    }
   }
 
   c = add_config_param(cmd->argv[0], cmd->argc-1, NULL);
@@ -5195,13 +5204,32 @@ static void proxy_mod_unload_ev(const void *event_data, void *user_data) {
   /* Unregister ourselves from all events. */
   pr_event_unregister(&proxy_module, NULL, NULL);
 
+  (void) proxy_forward_free(proxy_pool);
+  (void) proxy_reverse_free(proxy_pool);
+  (void) proxy_ssh_free(proxy_pool);
+  (void) proxy_tls_free(proxy_pool);
+
+  if (proxy_db_close(proxy_pool, NULL) < 0) {
+    (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
+      "error closing database: %s", strerror(errno));
+  }
+
+  (void) proxy_db_free();
+
+#if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL)
+  if (legacy_provider != NULL) {
+    OSSL_PROVIDER_unload(legacy_provider);
+    legacy_provider = NULL;
+  }
+#endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */
+
   destroy_pool(proxy_pool);
   proxy_pool = NULL;
 
   (void) close(proxy_logfd);
   proxy_logfd = -1;
 }
-#endif
+#endif /* PR_SHARED_MODULE */
 
 static void proxy_postparse_ev(const void *event_data, void *user_data) {
   int engine = FALSE;
@@ -5422,7 +5450,7 @@ static int proxy_init(void) {
 #if defined(PR_SHARED_MODULE)
   pr_event_register(&proxy_module, "core.module-unload", proxy_mod_unload_ev,
     NULL);
-#endif
+#endif /* PR_SHARED_MODULE */
   pr_event_register(&proxy_module, "core.postparse", proxy_postparse_ev, NULL);
   pr_event_register(&proxy_module, "core.restart", proxy_restart_ev, NULL);
   pr_event_register(&proxy_module, "core.shutdown", proxy_shutdown_ev, NULL);
@@ -5438,6 +5466,11 @@ static int proxy_init(void) {
     return -1;
   }
 
+  /* We need to do this now, before parsing, in case the configuration uses
+   * algorithms provided by our custom provider.
+   */
+  proxy_ssh_provider_init();
+
   return 0;
 }
 


=====================================
mod_proxy.h.in
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_proxy
- * Copyright (c) 2012-2025 TJ Saunders
+ * Copyright (c) 2012-2026 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
@@ -13,8 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  *
  * As a special exemption, TJ Saunders and other respective copyright holders
  * give permission to link this program with OpenSSL, and distribute the
@@ -116,7 +115,7 @@
 /* Define if you have the strnstr(3) function.  */
 #undef HAVE_STRNSTR
 
-#define MOD_PROXY_VERSION	"mod_proxy/0.9.6"
+#define MOD_PROXY_VERSION	"mod_proxy/0.9.7"
 
 /* Make sure the version of proftpd is as necessary. */
 #if PROFTPD_VERSION_NUMBER < 0x0001030706



View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-proxy/-/compare/ef47239f4eeea78daee0ee4d052fbc2589d53486...b4fa6c19336ca7c75df0cf1e66053a41cbcb5574

-- 
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-proxy/-/compare/ef47239f4eeea78daee0ee4d052fbc2589d53486...b4fa6c19336ca7c75df0cf1e66053a41cbcb5574
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help




More information about the Pkg-proftpd-maintainers mailing list