[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