[Git][debian-proftpd-team/proftpd][upstream] New upstream version 1.3.9b~dfsg
Hilmar Preuße (@hilmar)
gitlab at salsa.debian.org
Sun Jun 7 22:21:40 BST 2026
Hilmar Preuße pushed to branch upstream at Debian ProFTPD Team / proftpd
Commits:
6d6fe6ac by Hilmar Preuße at 2026-06-07T23:07:19+02:00
New upstream version 1.3.9b~dfsg
- - - - -
25 changed files:
- .github/workflows/ci.yml
- .github/workflows/legacy-platforms-ci.yml
- NEWS
- README.md
- RELEASE_NOTES
- contrib/dist/rpm/proftpd.spec
- contrib/mod_digest.c
- contrib/mod_quotatab.c
- contrib/mod_sftp/Makefile.in
- contrib/mod_sftp/crypto.c
- contrib/mod_sftp/crypto.h
- contrib/mod_sftp/fxp.c
- contrib/mod_sftp/kex.c
- contrib/mod_sftp/keys.c
- contrib/mod_sftp/mac.c
- contrib/mod_sftp/mod_sftp.c
- contrib/mod_sftp/packet.c
- + contrib/mod_sftp/provider.c
- + contrib/mod_sftp/provider.h
- contrib/mod_sql.c
- contrib/mod_tls.c
- contrib/mod_wrap2_sql.c
- include/version.h
- + tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/quotatab.pm
- + tests/t/modules/mod_sftp/quotatab.t
Changes:
=====================================
.github/workflows/ci.yml
=====================================
@@ -44,7 +44,7 @@ jobs:
# https://github.com/actions/checkout/issues/1809.
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
- PACKAGE_VERSION: 1.3.9a
+ PACKAGE_VERSION: 1.3.9b
REDIS_HOST: redis
strategy:
=====================================
.github/workflows/legacy-platforms-ci.yml
=====================================
@@ -44,7 +44,7 @@ jobs:
# https://github.com/actions/checkout/issues/1809.
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
- PACKAGE_VERSION: 1.3.9a
+ PACKAGE_VERSION: 1.3.9b
REDIS_HOST: redis
strategy:
=====================================
NEWS
=====================================
@@ -15,6 +15,26 @@
where `N' is the issue number.
-----------------------------------------------------------------------------
+1.3.9b - Released 02-Jun-2026
+--------------------------------
+- Issue 2057 - SQL Injection in mod_wrap2_sql via reverse DNS
+ hostname (CVE-2026-44331).
+- Issue 2056 - Incomplete fix for session management with OpenSSL 3.2.x or
+ later, when using TLSv1.2 or earlier. This complements the fix for
+ Issue #1963.
+- Issue 2098 - Hard quota limits on uploads do not cause SFTP WRITE requests
+ to fail as expected.
+- Issue 2102 - SSH payload length underflow calculation for ETM/ChaChaPoly
+ algorithms in mod_sftp.
+- Issue 2104 - SSH packet with empty payload triggers null pointer dereference
+ in mod_sftp.
+- Issue 2106 - Bad DSA signatures can lead to out-of-bounds read of heap memory
+ in mod_sftp.
+- Issue 2108 - Mismatched RSA/DSA algorithm signatures can lead to null
+ dereference in mod_sftp.
+- Issue 2115 - SFTP request payload length underflow calculation in mod_sftp.
+- Issue 2120 - Several modules fail to build using OpenSSL 4.0.
+
1.3.9a - Released 27-Apr-2026
--------------------------------
- Issue 1886 - SCP transfers fail for files with spaces in their names.
=====================================
README.md
=====================================
@@ -7,7 +7,7 @@
[](https://coveralls.io/github/proftpd/proftpd?branch=master)
[](https://github.com/proftpd/proftpd/actions/workflows/codeql.yml)
[](https://scan.coverity.com/projects/198)
-[](https://github.com/proftpd/proftpd/releases/latest)
+[](https://github.com/proftpd/proftpd/releases/latest)
[](https://img.shields.io/badge/license-GPL-brightgreen.svg)
## Introduction
=====================================
RELEASE_NOTES
=====================================
@@ -6,6 +6,15 @@ This file contains a description of the major changes to ProFTPD for the
releases. More information on these changes can be found in the NEWS and
ChangeLog files.
+1.3.9b
+---------
+
+ + Additional fixes for SQL injection (CVE-2026-42167), notably for handling
+ `%{env:...}` and `%{note:...}` variables.
+
+ + Updated modules to build against OpenSSL 4.x.
+
+
1.3.9a
---------
=====================================
contrib/dist/rpm/proftpd.spec
=====================================
@@ -53,7 +53,7 @@
# RHEL5 and clones don't have suitably recent versions of pcre/libmemcached
# so use --with rhel5 to inhibit those features when using --with everything
-%global proftpd_version 1.3.9a
+%global proftpd_version 1.3.9b
# rc_version should be incremented for each RC release, and reset back to 1
# AFTER each stable release.
@@ -61,7 +61,7 @@
# release_version should be incremented for each maint release, and reset back
# to 1 BEFORE starting new release cycle.
-%global release_version 2
+%global release_version 3
%if %(echo %{proftpd_version} | grep rc >/dev/null 2>&1 && echo 1 || echo 0)
%global rpm_version %(echo %{proftpd_version} | sed -e 's/rc.*//')
=====================================
contrib/mod_digest.c
=====================================
@@ -82,6 +82,14 @@
# define HAVE_LIBRESSL 1
#endif
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+# include <openssl/core.h>
+# include <openssl/core_dispatch.h>
+# include <openssl/core_names.h>
+# include <openssl/params.h>
+# include <openssl/provider.h>
+#endif /* OpenSSL 4.x and later */
+
module digest_module;
static int digest_caching = TRUE;
@@ -169,6 +177,7 @@ static unsigned long digest_algos = DIGEST_DEFAULT_ALGOS;
static const EVP_MD *digest_hash_md = NULL;
static unsigned long digest_hash_algo = DIGEST_ALGO_SHA1;
+static int digest_hash_free_md = FALSE;
/* Flags for determining the style of hash function names. */
#define DIGEST_ALGO_FL_IANA_STYLE 0x0001
@@ -180,6 +189,10 @@ static unsigned long digest_hash_algo = DIGEST_ALGO_SHA1;
# define DIGEST_PROGRESS_NTH_ITER 40000
#endif
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+static OSSL_PROVIDER *crc32_provider = NULL;
+#endif /* OpenSSL 4.x and later */
+
static const char *trace_channel = "digest";
/* Necessary prototypes. */
@@ -229,7 +242,7 @@ static char *digest_bin2hex(pool *p, const unsigned char *buf, size_len,
return hex;
}
-#endif
+#endif /* ProFTPD 1.3.6 and earlier */
/* CRC32 implementation, as OpenSSL EVP_MD. The following OpenSSL files
* used as templates:
@@ -318,6 +331,131 @@ static int CRC32_Free(CRC32_CTX *ctx) {
return 1;
}
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+
+/* Our custom CRC32 digest provider implementation. */
+
+static void *crc32_ctx_new(void *vctx) {
+ CRC32_CTX *ctx;
+
+ ctx = OPENSSL_zalloc(sizeof(CRC32_CTX));
+ return ctx;
+}
+
+static void crc32_ctx_free(void *vctx) {
+ CRC32_CTX *ctx;
+
+ ctx = vctx;
+ CRC32_Free(ctx);
+ OPENSSL_free(ctx);
+}
+
+static int crc32_md_init(void *vctx) {
+ CRC32_CTX *ctx;
+
+ ctx = vctx;
+ return CRC32_Init(ctx);
+}
+
+static int crc32_md_update(void *vctx, const unsigned char *data, size_t len) {
+ CRC32_CTX *ctx;
+
+ ctx = vctx;
+ return CRC32_Update(ctx, data, len);
+}
+
+static int crc32_md_final(void *vctx, unsigned char *out, size_t *out_len,
+ size_t outsz) {
+ CRC32_CTX *ctx;
+
+ ctx = vctx;
+
+ *out_len = outsz;
+
+ if (outsz != 0) {
+ return CRC32_Final(out, ctx);
+ }
+
+ return 1;
+}
+
+static const OSSL_PARAM crc32_params[] = {
+ OSSL_PARAM_size_t(OSSL_DIGEST_PARAM_BLOCK_SIZE, NULL),
+ OSSL_PARAM_size_t(OSSL_DIGEST_PARAM_SIZE, NULL),
+ OSSL_PARAM_END
+};
+
+static int crc32_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, CRC32_BLOCK) != 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, CRC32_DIGEST_LENGTH) != 1) {
+ ok = 0;
+ }
+ }
+ }
+
+ return ok;
+}
+
+static const OSSL_PARAM *crc32_gettable_params(void) {
+ return crc32_params;
+}
+
+static const OSSL_DISPATCH crc32_md_functions[] = {
+ { OSSL_FUNC_DIGEST_NEWCTX, (void (*)(void)) crc32_ctx_new },
+ { OSSL_FUNC_DIGEST_FREECTX, (void (*)(void)) crc32_ctx_free },
+ { OSSL_FUNC_DIGEST_INIT, (void (*)(void)) crc32_md_init },
+ { OSSL_FUNC_DIGEST_UPDATE, (void (*)(void)) crc32_md_update },
+ { OSSL_FUNC_DIGEST_FINAL, (void (*)(void)) crc32_md_final },
+ { OSSL_FUNC_DIGEST_GET_PARAMS, (void (*)(void)) crc32_get_params },
+ { OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (void (*)(void)) crc32_gettable_params },
+
+ { 0, NULL }
+};
+
+static const OSSL_ALGORITHM crc32_digests[] = {
+ { "crc32", NULL, crc32_md_functions },
+
+ { NULL, NULL, NULL }
+};
+
+static const OSSL_ALGORITHM *crc32_provider_operation(void *provctx,
+ int operation_id, int *no_cache) {
+ *no_cache = 0;
+
+ if (operation_id == OSSL_OP_DIGEST) {
+ return crc32_digests;
+ }
+
+ return NULL;
+}
+
+static const OSSL_DISPATCH crc32_provider_functions[] = {
+ { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void)) crc32_provider_operation },
+
+ { 0, NULL }
+};
+
+static int crc32_provider_init(const OSSL_CORE_HANDLE *core,
+ const OSSL_DISPATCH *in, const OSSL_DISPATCH **out, void **provctx) {
+ *out = crc32_provider_functions;
+ *provctx = (void *) core;
+
+ return 1;
+}
+
+#else
static int crc32_init(EVP_MD_CTX *ctx) {
void *md_data;
@@ -411,6 +549,7 @@ static const EVP_MD *EVP_crc32(void) {
return md;
}
+#endif /* OpenSSL before 4.x */
static const char *get_errors(void) {
unsigned int count = 0;
@@ -611,28 +750,28 @@ MODRET set_digestalgorithms(cmd_rec *cmd) {
algos |= DIGEST_ALGO_CRC32;
} else if (strcasecmp(cmd->argv[i], "md5") == 0) {
-#ifndef OPENSSL_NO_MD5
+#if !defined(OPENSSL_NO_MD5)
algos |= DIGEST_ALGO_MD5;
#else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "installed OpenSSL does not support the '", cmd->argv[i], "' DigestAlgorithm", NULL));
#endif /* OPENSSL_NO_MD5 */
} else if (strcasecmp(cmd->argv[i], "sha1") == 0) {
-#ifndef OPENSSL_NO_SHA
+#if !defined(OPENSSL_NO_SHA)
algos |= DIGEST_ALGO_SHA1;
#else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "installed OpenSSL does not support the '", cmd->argv[i], "' DigestAlgorithm", NULL));
#endif /* OPENSSL_NO_SHA */
} else if (strcasecmp(cmd->argv[i], "sha256") == 0) {
-#ifndef OPENSSL_NO_SHA256
+#if !defined(OPENSSL_NO_SHA256)
algos |= DIGEST_ALGO_SHA256;
#else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "installed OpenSSL does not support the '", cmd->argv[i], "' DigestAlgorithm", NULL));
#endif /* OPENSSL_NO_SHA256 */
} else if (strcasecmp(cmd->argv[i], "sha512") == 0) {
-#ifndef OPENSSL_NO_SHA512
+#if !defined(OPENSSL_NO_SHA512)
algos |= DIGEST_ALGO_SHA512;
#else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "installed OpenSSL does not support the '", cmd->argv[i], "' DigestAlgorithm", NULL));
@@ -1026,11 +1165,11 @@ static int can_digest_file(pool *p, const char *path, off_t start, size_t len,
static int blacklisted_file(const char *path) {
int res = FALSE;
- if (strncasecmp("/dev/full", path, 10) == 0 ||
- strncasecmp("/dev/null", path, 10) == 0 ||
- strncasecmp("/dev/random", path, 12) == 0 ||
- strncasecmp("/dev/urandom", path, 13) == 0 ||
- strncasecmp("/dev/zero", path, 10) == 0) {
+ if (strcasecmp("/dev/full", path) == 0 ||
+ strcasecmp("/dev/null", path) == 0 ||
+ strcasecmp("/dev/random", path) == 0 ||
+ strcasecmp("/dev/urandom", path) == 0 ||
+ strcasecmp("/dev/zero", path) == 0) {
res = TRUE;
}
@@ -1044,7 +1183,7 @@ static int compute_digest(pool *p, const char *path, off_t start, off_t len,
pr_fh_t *fh;
struct stat st;
unsigned char *buf;
- size_t bufsz, readsz, iter_count;
+ size_t bufsz, readsz;
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
(defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER < 0x3050000L)
EVP_MD_CTX ctx;
@@ -1130,13 +1269,10 @@ static int compute_digest(pool *p, const char *path, off_t start, off_t len,
readsz = len;
}
- iter_count = 0;
res = pr_fsio_read(fh, (char *) buf, readsz);
xerrno = errno;
while (len > 0) {
- iter_count++;
-
if (res < 0 &&
errno == EAGAIN) {
/* Add a small delay by treating this as EINTR. */
@@ -1201,12 +1337,35 @@ static int compute_digest(pool *p, const char *path, off_t start, off_t len,
return 0;
}
-static const EVP_MD *get_algo_md(unsigned long algo) {
+static void free_algo_md(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 */
+}
+
+static const EVP_MD *get_algo_md(unsigned long algo, int *free_md) {
const EVP_MD *md = NULL;
+ *free_md = FALSE;
+
switch (algo) {
case DIGEST_ALGO_CRC32:
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+ md = EVP_MD_fetch(NULL, "crc32", NULL);
+ if (md == NULL) {
+ pr_trace_msg(trace_channel, 3, "error obtaining CRC32 EVP_MD: %s",
+ get_errors());
+
+ } else {
+ *free_md = TRUE;
+ }
+#else
md = EVP_crc32();
+#endif /* OpenSSL before 4.x */
break;
#if !defined(OPENSSL_NO_MD5)
@@ -1537,14 +1696,18 @@ static int add_cached_digest(pool *p, cmd_rec *cmd, unsigned long algo,
algo_name = get_algo_name(algo, 0);
if (pr_table_add(cmd->notes, "mod_digest.algo",
pstrdup(cmd->pool, algo_name), 0) < 0) {
- pr_trace_msg(trace_channel, 3,
+ if (errno != EEXIST) {
+ pr_trace_msg(trace_channel, 3,
"error adding 'mod_digest.algo' note: %s", strerror(errno));
+ }
}
if (pr_table_add(cmd->notes, "mod_digest.digest",
pstrdup(cmd->pool, hex_digest), 0) < 0) {
- pr_trace_msg(trace_channel, 3,
- "error adding 'mod_digest.digest' note: %s", strerror(errno));
+ if (errno != EEXIST) {
+ pr_trace_msg(trace_channel, 3,
+ "error adding 'mod_digest.digest' note: %s", strerror(errno));
+ }
}
res = pr_table_add(cache, cache_key->key, (void *) cache_key->hex_digest, 0);
@@ -1682,7 +1845,7 @@ static int check_cache_size(cmd_rec *cmd) {
if (cache_size >= digest_cache_max_size) {
int xerrno = EAGAIN;
-#ifdef EBUSY
+#if defined(EBUSY)
/* This errno value may not be available on all platforms, but it is
* the most appropriate.
*/
@@ -1708,7 +1871,7 @@ static int check_cache_size(cmd_rec *cmd) {
static char *get_digest(cmd_rec *cmd, unsigned long algo, const char *path,
time_t mtime, off_t start, size_t len, int flags) {
- int res;
+ int res, free_md = FALSE;
const EVP_MD *md;
unsigned char *digest = NULL;
unsigned int digest_len;
@@ -1751,13 +1914,17 @@ static char *get_digest(cmd_rec *cmd, unsigned long algo, const char *path,
return hex_digest;
}
- md = get_algo_md(algo);
+ md = get_algo_md(algo, &free_md);
digest_len = EVP_MD_size(md);
digest = palloc(cmd->tmp_pool, digest_len);
res = compute_digest(cmd->tmp_pool, path, start, len, md, digest,
&digest_len, &mtime);
if (res < 0) {
+ if (free_md == TRUE) {
+ free_algo_md(md);
+ }
+
return NULL;
}
@@ -1793,6 +1960,10 @@ static char *get_digest(cmd_rec *cmd, unsigned long algo, const char *path,
}
}
+ if (free_md == TRUE) {
+ free_algo_md(md);
+ }
+
return hex_digest;
}
@@ -1883,11 +2054,13 @@ static modret_t *digest_xcmd(cmd_rec *cmd, unsigned long algo) {
} else {
off_t len, start_pos, end_pos;
+ const EVP_MD *md = NULL;
+ int free_md = FALSE;
if (cmd->argc > 3) {
char *ptr = NULL;
-#ifdef HAVE_STRTOULL
+#if defined(HAVE_STRTOULL)
start_pos = strtoull(cmd->argv[2], &ptr, 10);
#else
start_pos = strtoul(cmd->argv[2], &ptr, 10);
@@ -1901,7 +2074,7 @@ static modret_t *digest_xcmd(cmd_rec *cmd, unsigned long algo) {
}
ptr = NULL;
-#ifdef HAVE_STRTOULL
+#if defined(HAVE_STRTOULL)
end_pos = strtoull(cmd->argv[3], &ptr, 10);
#else
end_pos = strtoul(cmd->argv[3], &ptr, 10);
@@ -1940,9 +2113,14 @@ static modret_t *digest_xcmd(cmd_rec *cmd, unsigned long algo) {
return PR_ERROR(cmd);
}
- if (get_algo_md(algo) != NULL) {
+ md = get_algo_md(algo, &free_md);
+ if (md != NULL) {
char *hex_digest;
+ if (free_md == TRUE) {
+ free_algo_md(md);
+ }
+
hex_digest = get_digest(cmd, algo, path, st.st_mtime, start_pos, len,
PR_STR_FL_HEX_USE_UC);
if (hex_digest != NULL) {
@@ -2073,9 +2251,9 @@ MODRET digest_hash(cmd_rec *cmd) {
}
switch (xerrno) {
-#ifdef EBUSY
+#if defined(EBUSY)
case EBUSY:
-#endif
+#endif /* EBUSY */
case EAGAIN:
/* The HASH draft recommends using 450 for these cases. */
error_code = R_450;
@@ -2130,19 +2308,27 @@ MODRET digest_opts_hash(cmd_rec *cmd) {
if (strcasecmp(algo_name, "CRC32") == 0) {
if (digest_algos & DIGEST_ALGO_CRC32) {
+ if (digest_hash_free_md == TRUE) {
+ free_algo_md(digest_hash_md);
+ }
+
digest_hash_algo = DIGEST_ALGO_CRC32;
- digest_hash_md = get_algo_md(digest_hash_algo);
+ digest_hash_md = get_algo_md(digest_hash_algo, &digest_hash_free_md);
} else {
pr_response_add_err(R_501, _("%s: Unsupported algorithm"), algo_name);
return PR_ERROR(cmd);
}
-#ifndef OPENSSL_NO_MD5
+#if !defined(OPENSSL_NO_MD5)
} else if (strcasecmp(algo_name, "MD5") == 0) {
if (digest_algos & DIGEST_ALGO_MD5) {
+ if (digest_hash_free_md == TRUE) {
+ free_algo_md(digest_hash_md);
+ }
+
digest_hash_algo = DIGEST_ALGO_MD5;
- digest_hash_md = get_algo_md(digest_hash_algo);
+ digest_hash_md = get_algo_md(digest_hash_algo, &digest_hash_free_md);
} else {
pr_response_add_err(R_501, _("%s: Unsupported algorithm"), algo_name);
@@ -2150,11 +2336,15 @@ MODRET digest_opts_hash(cmd_rec *cmd) {
}
#endif /* OPENSSL_NO_MD5 */
-#ifndef OPENSSL_NO_SHA1
+#if !defined(OPENSSL_NO_SHA1)
} else if (strcasecmp(algo_name, "SHA-1") == 0) {
if (digest_algos & DIGEST_ALGO_SHA1) {
+ if (digest_hash_free_md == TRUE) {
+ free_algo_md(digest_hash_md);
+ }
+
digest_hash_algo = DIGEST_ALGO_SHA1;
- digest_hash_md = get_algo_md(digest_hash_algo);
+ digest_hash_md = get_algo_md(digest_hash_algo, &digest_hash_free_md);
} else {
pr_response_add_err(R_501, _("%s: Unsupported algorithm"), algo_name);
@@ -2162,11 +2352,15 @@ MODRET digest_opts_hash(cmd_rec *cmd) {
}
#endif /* OPENSSL_NO_SHA1 */
-#ifndef OPENSSL_NO_SHA256
+#if !defined(OPENSSL_NO_SHA256)
} else if (strcasecmp(algo_name, "SHA-256") == 0) {
if (digest_algos & DIGEST_ALGO_SHA256) {
+ if (digest_hash_free_md == TRUE) {
+ free_algo_md(digest_hash_md);
+ }
+
digest_hash_algo = DIGEST_ALGO_SHA256;
- digest_hash_md = get_algo_md(digest_hash_algo);
+ digest_hash_md = get_algo_md(digest_hash_algo, &digest_hash_free_md);
} else {
pr_response_add_err(R_501, _("%s: Unsupported algorithm"), algo_name);
@@ -2174,11 +2368,15 @@ MODRET digest_opts_hash(cmd_rec *cmd) {
}
#endif /* OPENSSL_NO_SHA256 */
-#ifndef OPENSSL_NO_SHA512
+#if !defined(OPENSSL_NO_SHA512)
} else if (strcasecmp(algo_name, "SHA-512") == 0) {
if (digest_algos & DIGEST_ALGO_SHA512) {
+ if (digest_hash_free_md == TRUE) {
+ free_algo_md(digest_hash_md);
+ }
+
digest_hash_algo = DIGEST_ALGO_SHA512;
- digest_hash_md = get_algo_md(digest_hash_algo);
+ digest_hash_md = get_algo_md(digest_hash_algo, &digest_hash_free_md);
} else {
pr_response_add_err(R_501, _("%s: Unsupported algorithm"), algo_name);
@@ -2781,9 +2979,18 @@ static void digest_data_xfer_ev(const void *event_data, void *user_data) {
#if defined(PR_SHARED_MODULE)
static void digest_mod_unload_ev(const void *event_data, void *user_data) {
- if (strcmp((char *) event_data, "mod_digest.c") == 0) {
- pr_event_unregister(&digest_module, NULL, NULL);
+ if (strcmp((char *) event_data, "mod_digest.c") != 0) {
+ return;
}
+
+ pr_event_unregister(&digest_module, NULL, NULL);
+
+# if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+ if (crc32_provider != NULL) {
+ OSSL_PROVIDER_unload(crc32_provider);
+ crc32_provider = NULL;
+ }
+# endif /* OpenSSL 4.x and later */
}
#endif /* PR_SHARED_MODULE */
@@ -2811,6 +3018,15 @@ static void digest_sess_reinit_ev(const void *event_data, void *user_data) {
}
}
+static void digest_shutdown_ev(const void *event_data, void *user_data) {
+#if defined(HAVE_OSSL_PROVIDER_LOAD_OPENSSL)
+ if (crc32_provider != NULL) {
+ OSSL_PROVIDER_unload(crc32_provider);
+ crc32_provider = NULL;
+ }
+#endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */
+}
+
/* Initialization routines
*/
@@ -2822,6 +3038,27 @@ static int digest_init(void) {
pr_event_register(&digest_module, "core.module-unload", digest_mod_unload_ev,
NULL);
#endif /* PR_SHARED_MODULE */
+ pr_event_register(&digest_module, "core.shutdown", digest_shutdown_ev, NULL);
+
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+ if (OSSL_PROVIDER_add_builtin(NULL, "crc32", crc32_provider_init) != 1) {
+ pr_log_debug(DEBUG1, MOD_DIGEST_VERSION
+ ": error registering 'crc32' OpenSSL provider: %s", get_errors());
+
+ } else {
+ pr_trace_msg(trace_channel, 9, "%s", "registered 'crc32' OpenSSL provider");
+ }
+
+ /* Load our custom "crc32" OpenSSL algorithm provider. */
+ crc32_provider = OSSL_PROVIDER_load(NULL, "crc32");
+ if (crc32_provider == NULL) {
+ pr_log_pri(PR_LOG_NOTICE, MOD_DIGEST_VERSION
+ ": error loading 'crc32' OpenSSL provider: %s", get_errors());
+
+ } else {
+ pr_trace_msg(trace_channel, 9, "%s", "loaded 'crc32' OpenSSL provider");
+ }
+#endif /* OpenSSL 4.x and later */
return 0;
}
@@ -2893,7 +3130,7 @@ static int digest_sess_init(void) {
}
}
- digest_hash_md = get_algo_md(digest_hash_algo);
+ digest_hash_md = get_algo_md(digest_hash_algo, &digest_hash_free_md);
c = find_config(main_server->conf, CONF_PARAM, "DigestCache", FALSE);
if (c != NULL) {
=====================================
contrib/mod_quotatab.c
=====================================
@@ -1432,6 +1432,73 @@ int quotatab_write(quota_tally_t *tally,
static off_t copied_bytes = 0;
+static ssize_t quotatab_fsio_pwrite(pr_fh_t *fh, int fd, const void *buf,
+ size_t bufsz, off_t offset) {
+ ssize_t res;
+ off_t total_bytes;
+
+ res = pwrite(fd, buf, bufsz, offset);
+ if (res < 0) {
+ return res;
+ }
+
+ if (have_quota_update == 0) {
+ return res;
+ }
+
+ /* Check to see if we've exceeded our upload limit. mod_xfer will
+ * have called pr_data_xfer(), which will have updated
+ * session.xfer.total_bytes, before calling pr_fsio_write(), so
+ * we do not have to worry about updated/changing session.xfer.total_bytes
+ * ourselves.
+ *
+ * Note that there is a race condition here: it is possible for the same
+ * user to be writing to the same file in chunks from multiple
+ * simultaneous connections.
+ */
+
+ /* If the client is copying a file (versus uploading a file), then we need
+ * to track the "total bytes" differently.
+ */
+ if (session.curr_cmd_id == PR_CMD_SITE_ID &&
+ (session.curr_cmd_rec->argc >= 2 &&
+ (strcasecmp(session.curr_cmd_rec->argv[1], "CPTO") == 0 ||
+ strcasecmp(session.curr_cmd_rec->argv[1], "COPY") == 0))) {
+ copied_bytes += res;
+ total_bytes = copied_bytes;
+
+ } else {
+ total_bytes = session.xfer.total_bytes;
+ }
+
+ if (sess_limit.bytes_in_avail > 0.0 &&
+ sess_tally.bytes_in_used + total_bytes > sess_limit.bytes_in_avail) {
+ int xerrno;
+ char *errstr = NULL;
+
+ xerrno = get_quota_exceeded_errno(EIO, &errstr);
+ quotatab_log("quotatab write(): limit exceeded, returning %s", errstr);
+
+ errno = xerrno;
+ return -1;
+ }
+
+ if (sess_limit.bytes_xfer_avail > 0.0 &&
+ sess_tally.bytes_xfer_used + total_bytes > sess_limit.bytes_xfer_avail) {
+ int xerrno;
+ char *errstr = NULL;
+
+ xerrno = get_quota_exceeded_errno(EIO, &errstr);
+ quotatab_log("quotatab write(): transfer limit exceeded, returning %s",
+ errstr);
+
+ errno = xerrno;
+ return -1;
+ }
+
+ return res;
+}
+
static int quotatab_fsio_write(pr_fh_t *fh, int fd, const char *buf,
size_t bufsz) {
int res;
@@ -3214,6 +3281,7 @@ MODRET quotatab_post_pass(cmd_rec *cmd) {
* For Issue #1764, this is not a problem due to the fact that
* mod_vroot does not override the system FS write callbacks.
*/
+ fs->pwrite = quotatab_fsio_pwrite;
fs->write = quotatab_fsio_write;
} else {
=====================================
contrib/mod_sftp/Makefile.in
=====================================
@@ -19,13 +19,14 @@ MODULE_OBJS=mod_sftp.o msg.o packet.o cipher.o mac.o umac.o umac128.o \
compress.o kex.o keys.o crypto.o utf8.o session.o service.o kbdint.o \
auth-hostbased.o auth-kbdint.o auth-password.o auth-publickey.o auth.o \
disconnect.o rfc4716.o keystore.o channel.o blacklist.o agent.o \
- interop.o tap.o fxp.o scp.o display.o misc.o date.o bcrypt.o poly1305.o
+ interop.o tap.o fxp.o scp.o display.o misc.o date.o bcrypt.o poly1305.o \
+ provider.o
SHARED_MODULE_OBJS=mod_sftp.lo msg.lo packet.lo cipher.lo mac.lo umac.lo \
umac128.lo compress.lo kex.lo keys.lo crypto.lo utf8.lo session.lo \
service.lo kbdint.lo auth-hostbased.lo auth-kbdint.lo auth-password.lo \
auth-publickey.lo auth.lo disconnect.lo rfc4716.lo keystore.lo channel.lo \
blacklist.lo agent.lo interop.lo tap.lo fxp.lo scp.lo display.lo misc.lo \
- date.lo bcrypt.lo poly1305.lo
+ date.lo bcrypt.lo poly1305.lo provider.lo
# Necessary redefinitions
INCLUDES=-I. -I../.. -I../../include -I$(top_srcdir)/../../include @INCLUDES@
=====================================
contrib/mod_sftp/crypto.c
=====================================
@@ -24,6 +24,7 @@
#include "mod_sftp.h"
#include "crypto.h"
+#include "provider.h"
#include "umac.h"
/* In OpenSSL 0.9.7, all des_ functions were renamed to DES_ to avoid
@@ -882,6 +883,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;
@@ -1010,12 +1014,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)) || \
- (defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3050000L)
+ *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",
+ sftp_crypto_get_errors());
+
+ } else {
+ *free_md = TRUE;
+ }
+
+#else
+# if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(HAVE_LIBRESSL)) || \
+ (defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3050000L)
/* XXX TODO: At some point, we also need to call EVP_MD_meth_free() on
* this, to avoid a resource leak.
*/
@@ -1026,7 +1044,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));
@@ -1041,16 +1059,30 @@ static const EVP_MD *get_umac64_digest(void) {
umac64_digest.block_size = 32;
md = &umac64_digest;
-#endif /* prior to OpenSSL-1.1.0/LibreSSL-3.5.0 */
+# endif /* prior to OpenSSL-1.1.0/LibreSSL-3.5.0 */
+#endif /* OpenSSL before 4.x */
return md;
}
-static const EVP_MD *get_umac128_digest(void) {
- EVP_MD *md;
+static const EVP_MD *get_umac128_digest(int *free_md) {
+ EVP_MD *md = NULL;
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(HAVE_LIBRESSL)) || \
- (defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3050000L)
+ *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 'umac128' EVP_MD: %s",
+ sftp_crypto_get_errors());
+
+ } else {
+ *free_md = TRUE;
+ }
+
+#else
+# if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(HAVE_LIBRESSL)) || \
+ (defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3050000L)
/* XXX TODO: At some point, we also need to call EVP_MD_meth_free() on
* this, to avoid a resource leak.
*/
@@ -1061,7 +1093,7 @@ static const EVP_MD *get_umac128_digest(void) {
EVP_MD_meth_set_update(md, update_umac128);
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));
@@ -1076,7 +1108,8 @@ static const EVP_MD *get_umac128_digest(void) {
umac128_digest.block_size = 64;
md = &umac128_digest;
-#endif /* prior to OpenSSL-1.1.0/LibreSSL-3.5.0 */
+# endif /* prior to OpenSSL-1.1.0/LibreSSL-3.5.0 */
+#endif /* OpenSSL before 4.x */
return md;
}
@@ -1183,7 +1216,18 @@ const EVP_CIPHER *sftp_crypto_get_cipher(const char *name, size_t *key_len,
return NULL;
}
-const EVP_MD *sftp_crypto_get_digest(const char *name, uint32_t *mac_len) {
+void sftp_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 *sftp_crypto_get_digest(const char *name, uint32_t *mac_len,
+ int *free_md) {
register unsigned int i;
if (name == NULL) {
@@ -1191,6 +1235,8 @@ const EVP_MD *sftp_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;
@@ -1198,11 +1244,11 @@ const EVP_MD *sftp_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 */
@@ -1572,7 +1618,16 @@ size_t sftp_crypto_get_size(size_t first, size_t second) {
#endif /* !roundup */
}
+int sftp_crypto_init(void) {
+ if (sftp_provider_init() < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
void sftp_crypto_free(int flags) {
+ sftp_provider_free();
/* Only call EVP_cleanup() et al if other OpenSSL-using modules are not
* present. If we called EVP_cleanup() here during a restart,
=====================================
contrib/mod_sftp/crypto.h
=====================================
@@ -1,6 +1,6 @@
/*
* ProFTPD - mod_sftp misc crypto routines
- * Copyright (c) 2008-2022 TJ Saunders
+ * Copyright (c) 2008-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
@@ -27,10 +27,12 @@
#include "mod_sftp.h"
+int sftp_crypto_init(void);
void sftp_crypto_free(int);
const EVP_CIPHER *sftp_crypto_get_cipher(const char *name, size_t *key_len,
size_t *auth_len, size_t *discard_len);
-const EVP_MD *sftp_crypto_get_digest(const char *, uint32_t *);
+const EVP_MD *sftp_crypto_get_digest(const char *, uint32_t *, int *);
+void sftp_crypto_free_digest(const EVP_MD *md);
int sftp_crypto_is_hostkey(const char *name);
int sftp_crypto_is_key_exchange(const char *name);
int sftp_crypto_set_driver(const char *);
=====================================
contrib/mod_sftp/fxp.c
=====================================
@@ -281,12 +281,14 @@ static size_t fxp_packet_data_allocsz = 0;
#define FXP_PACKET_DATA_DEFAULT_SZ (1024 * 16)
#define FXP_RESPONSE_DATA_DEFAULT_SZ 512
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
+/* Impose limits on the xattr value length we are willing to process. */
+# define FXP_XATTR_VALUE_MAX_LEN (1024 * 64)
/* Allocate larger buffers for extended attributes */
# define FXP_RESPONSE_NAME_DEFAULT_SZ (1024 * 4)
#endif /* PR_USE_XATTR */
-#ifndef FXP_RESPONSE_NAME_DEFAULT_SZ
+#if !defined(FXP_RESPONSE_NAME_DEFAULT_SZ)
# define FXP_RESPONSE_NAME_DEFAULT_SZ FXP_RESPONSE_DATA_DEFAULT_SZ
#endif
@@ -1893,7 +1895,7 @@ static char *fxp_strattrs(pool *p, struct stat *st, uint32_t *attr_flags) {
if (fxp_session->client_version >= 6) {
flags |= SSH2_FX_ATTR_LINK_COUNT;
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
flags |= SSH2_FX_ATTR_EXTENDED;
#endif /* PR_USE_XATTR */
}
@@ -2572,7 +2574,7 @@ static uint32_t fxp_xattrs_write(pool *p, struct fxp_buffer *fxb,
const char *path) {
uint32_t len = 0;
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
int res;
array_header *names = NULL;
@@ -3410,6 +3412,23 @@ static struct fxp_packet *fxp_packet_read(uint32_t channel_id,
"(%lu bytes remaining in buffer)", (unsigned long) fxp->packet_len,
(unsigned long) buflen);
+ /* We require 5 bytes of SFTP request data at a minimum: 1 byte for the
+ * request type, and 4 bytes for the payload length (Issue #2115).
+ */
+ if (fxp->packet_len < 5) {
+ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+ "illegal SFTP request length (%lu bytes, require at least 5 bytes), "
+ "rejecting", (unsigned long) fxp->packet_len);
+ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+ }
+
+ if (fxp->packet_len > FXP_MAX_PACKET_LEN) {
+ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+ "received excessive SFTP packet (len %lu > max %lu bytes), rejecting",
+ (unsigned long) fxp->packet_len, (unsigned long) FXP_MAX_PACKET_LEN);
+ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+ }
+
if (buflen == 0) {
fxp_packet_set_packet(fxp);
fxp_packet_clear_cache_data();
@@ -5954,7 +5973,7 @@ static int fxp_handle_ext_statvfs(struct fxp_packet *fxp, const char *path) {
}
#endif /* !HAVE_SYS_STATVFS_H */
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
static int fxp_handle_ext_getxattr(struct fxp_packet *fxp, const char *path,
const char *name, uint32_t valsz) {
ssize_t res;
@@ -5964,6 +5983,36 @@ static int fxp_handle_ext_getxattr(struct fxp_packet *fxp, const char *path,
const char *reason;
struct fxp_packet *resp;
+ buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ;
+ buf = ptr = palloc(fxp->pool, bufsz);
+
+ if (valsz > FXP_XATTR_VALUE_MAX_LEN) {
+ int xerrno = EINVAL;
+
+ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+ "getxattr(2) on '%s' for attribute '%s' (%lu bytes) exceeds maximum "
+ "value size (%lu bytes), denying", path, name, (unsigned long) valsz,
+ (unsigned long) FXP_XATTR_VALUE_MAX_LEN);
+
+ status_code = fxp_errno2status(xerrno, &reason);
+
+ pr_trace_msg(trace_channel, 8, "sending response: STATUS %lu '%s' "
+ "('%s' [%d])", (unsigned long) status_code, reason, strerror(xerrno),
+ xerrno);
+
+ fxp_status_write(fxp->pool, &buf, &buflen, fxp->request_id, status_code,
+ reason, NULL);
+
+ resp = fxp_packet_create(fxp->pool, fxp->channel_id);
+ resp->payload = ptr;
+ resp->payload_sz = (bufsz - buflen);
+
+ return fxp_packet_write(resp);
+ }
+
+ /* Now that the value size has been checked, we allocate a new, larger
+ * buffer for that size.
+ */
val = pcalloc(fxp->pool, (size_t) valsz+1);
buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ + valsz;
@@ -6017,7 +6066,7 @@ static int fxp_handle_ext_fgetxattr(struct fxp_packet *fxp, const char *handle,
struct fxp_handle *fxh;
struct fxp_packet *resp;
- buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ + valsz;
+ buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ;
buf = ptr = palloc(fxp->pool, bufsz);
fxh = fxp_handle_get(handle);
@@ -6066,6 +6115,37 @@ static int fxp_handle_ext_fgetxattr(struct fxp_packet *fxp, const char *handle,
}
path = fxh->fh->fh_path;
+
+ if (valsz > FXP_XATTR_VALUE_MAX_LEN) {
+ int xerrno = EINVAL;
+
+ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+ "fgetxattr(2) on '%s' for attribute '%s' (%lu bytes) exceeds maximum "
+ "value size (%lu bytes), denying", path, name, (unsigned long) valsz,
+ (unsigned long) FXP_XATTR_VALUE_MAX_LEN);
+
+ status_code = fxp_errno2status(xerrno, &reason);
+
+ pr_trace_msg(trace_channel, 8, "sending response: STATUS %lu '%s' "
+ "('%s' [%d])", (unsigned long) status_code, reason, strerror(xerrno),
+ xerrno);
+
+ fxp_status_write(fxp->pool, &buf, &buflen, fxp->request_id, status_code,
+ reason, NULL);
+
+ resp = fxp_packet_create(fxp->pool, fxp->channel_id);
+ resp->payload = ptr;
+ resp->payload_sz = (bufsz - buflen);
+
+ return fxp_packet_write(resp);
+ }
+
+ /* Now that the value size has been checked, we allocate a new, larger
+ * buffer for that size.
+ */
+ buflen = bufsz = FXP_RESPONSE_DATA_DEFAULT_SZ + valsz;
+ buf = ptr = palloc(fxp->pool, bufsz);
+
val = pcalloc(fxp->pool, (size_t) valsz+1);
res = pr_fsio_fgetxattr(fxp->pool, fxh->fh, name, val, (size_t) valsz);
@@ -8054,7 +8134,7 @@ static int fxp_handle_fstat(struct fxp_packet *fxp) {
pr_trace_msg(trace_channel, 7, "received request: FSTAT %s", name);
attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS|
SSH2_FX_ATTR_ACMODTIME;
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) {
attr_flags |= SSH2_FX_ATTR_EXTENDED;
}
@@ -8787,7 +8867,7 @@ static int fxp_handle_lstat(struct fxp_packet *fxp) {
pr_trace_msg(trace_channel, 7, "received request: LSTAT %s", path);
attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS|
SSH2_FX_ATTR_ACMODTIME;
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) {
attr_flags |= SSH2_FX_ATTR_EXTENDED;
}
@@ -10349,7 +10429,7 @@ static int fxp_handle_read(struct fxp_packet *fxp) {
unsigned char *buf, *data = NULL, *ptr;
char *file, *name, *ptr2;
ssize_t res;
- uint32_t buflen, bufsz, datalen;
+ uint32_t buflen, bufsz, datalen, max_readsz;
uint64_t offset;
struct fxp_handle *fxh;
struct fxp_packet *resp;
@@ -10360,17 +10440,17 @@ static int fxp_handle_read(struct fxp_packet *fxp) {
offset = sftp_msg_read_long(fxp->pool, &fxp->payload, &fxp->payload_sz);
datalen = sftp_msg_read_int(fxp->pool, &fxp->payload, &fxp->payload_sz);
-#if 0
- /* XXX This doesn't appear to be needed now. But I'll keep it around,
- * just in case some buggy client needs this treatment.
+ /* We tell clients that request the "limits at openssh.com" extension what
+ * the maximum allowed READ length is; we should enforce that here.
*/
+ max_readsz = FXP_MAX_PACKET_LEN - 1024;
+
if (datalen > max_readsz) {
pr_trace_msg(trace_channel, 8,
"READ requested len %lu exceeds max (%lu), truncating",
(unsigned long) datalen, (unsigned long) max_readsz);
datalen = max_readsz;
}
-#endif
cmd = fxp_cmd_alloc(fxp->pool, "READ", name);
cmd->cmd_class = CL_READ|CL_SFTP;
@@ -10844,7 +10924,7 @@ static int fxp_handle_readdir(struct fxp_packet *fxp) {
pr_signals_handle();
/* How much non-path data do we expect to be associated with this entry? */
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
/* Note that the "extra space" to allocate for extended attributes is
* currently a bit of a guess. Initially, this was 4K; that was causing
* slower directory listings due to the need for more READDIR requests,
@@ -10996,7 +11076,7 @@ static int fxp_handle_readdir(struct fxp_packet *fxp) {
* Thus we CHOOSE to only provide these extended attributes, if supported,
* to protocol version 6 clients.
*/
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) {
attr_flags |= SSH2_FX_ATTR_EXTENDED;
}
@@ -12873,7 +12953,7 @@ static int fxp_handle_stat(struct fxp_packet *fxp) {
pr_trace_msg(trace_channel, 7, "received request: STAT %s", path);
attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS|
SSH2_FX_ATTR_ACMODTIME;
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) {
attr_flags |= SSH2_FX_ATTR_EXTENDED;
}
@@ -14052,15 +14132,6 @@ int sftp_fxp_handle_packet(pool *p, void *ssh2, uint32_t channel_id,
(unsigned long) channel_id);
}
- if (fxp->packet_len > FXP_MAX_PACKET_LEN) {
- (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
- "received excessive SFTP packet (len %lu > max %lu bytes), rejecting",
- (unsigned long) fxp->packet_len, (unsigned long) FXP_MAX_PACKET_LEN);
- destroy_pool(fxp->pool);
- errno = EPERM;
- return -1;
- }
-
fxp_session = fxp_get_session(channel_id);
if (fxp_session == NULL) {
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
=====================================
contrib/mod_sftp/kex.c
=====================================
@@ -892,7 +892,7 @@ static int have_good_dh(DH *dh, const BIGNUM *pub_key) {
}
static int get_dh_nbits(struct sftp_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;
@@ -952,7 +952,7 @@ static int get_dh_nbits(struct sftp_kex *kex) {
}
algo = kex->session_names->c2s_mac_algo;
- digest = sftp_crypto_get_digest(algo, NULL);
+ digest = sftp_crypto_get_digest(algo, NULL, &free_digest);
if (digest != NULL) {
int mac_len;
@@ -963,10 +963,14 @@ static int get_dh_nbits(struct sftp_kex *kex) {
"set DH size to %d bytes, matching client-to-server '%s' digest size",
dh_size, algo);
}
+
+ if (free_digest == TRUE) {
+ sftp_crypto_free_digest(digest);
+ }
}
algo = kex->session_names->s2c_mac_algo;
- digest = sftp_crypto_get_digest(algo, NULL);
+ digest = sftp_crypto_get_digest(algo, NULL, &free_digest);
if (digest != NULL) {
int mac_len;
@@ -977,6 +981,10 @@ static int get_dh_nbits(struct sftp_kex *kex) {
"set DH size to %d bytes, matching server-to-client '%s' digest size",
dh_size, algo);
}
+
+ if (free_digest == TRUE) {
+ sftp_crypto_free_digest(digest);
+ }
}
/* We want to return bits, not bytes. */
=====================================
contrib/mod_sftp/keys.c
=====================================
@@ -5337,6 +5337,12 @@ static int verify_rsa_signed_data(pool *p, EVP_PKEY *pkey,
}
rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+ "error obtaining RSA key: %s", sftp_crypto_get_errors());
+ errno = EINVAL;
+ return -1;
+ }
if (keys_rsa_min_nbits > 0) {
int rsa_nbits;
@@ -5471,6 +5477,8 @@ static int dsa_verify_signed_data(pool *p, EVP_PKEY *pkey,
if (sig_len != 40) {
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"bad DSA signature len (%lu)", (unsigned long) sig_len);
+ errno = EINVAL;
+ return -1;
}
len = sftp_msg_read_data2(p, &signature, &signature_len, sig_len, &sig);
@@ -5487,6 +5495,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(sftp_logfd, MOD_SFTP_VERSION,
+ "error obtaining DSA key: %s", sftp_crypto_get_errors());
+ errno = EINVAL;
+ return -1;
+ }
if (keys_dsa_min_nbits > 0) {
int dsa_nbits;
@@ -6175,6 +6189,14 @@ int sftp_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(sftp_logfd, MOD_SFTP_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);
@@ -6210,6 +6232,14 @@ int sftp_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(sftp_logfd, MOD_SFTP_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 */
=====================================
contrib/mod_sftp/mac.c
=====================================
@@ -39,6 +39,7 @@ struct sftp_mac {
const char *algo;
unsigned int algo_type;
int is_etm;
+ int free_digest;
const EVP_MD *digest;
@@ -68,15 +69,15 @@ struct sftp_mac {
*/
static struct sftp_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 sftp_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];
@@ -754,16 +755,25 @@ int sftp_mac_set_read_algo(const char *algo) {
case SFTP_MAC_ALGO_TYPE_UMAC64:
umac_delete(umac_read_ctxs[idx]);
umac_read_ctxs[idx] = NULL;
+ if (read_macs[idx].free_digest == TRUE) {
+ sftp_crypto_free_digest(read_macs[idx].digest);
+ read_macs[idx].digest = NULL;
+ }
break;
case SFTP_MAC_ALGO_TYPE_UMAC128:
umac128_delete(umac_read_ctxs[idx]);
umac_read_ctxs[idx] = NULL;
+ if (read_macs[idx].free_digest == TRUE) {
+ sftp_crypto_free_digest(read_macs[idx].digest);
+ read_macs[idx].digest = NULL;
+ }
break;
}
}
- read_macs[idx].digest = sftp_crypto_get_digest(algo, &mac_len);
+ read_macs[idx].digest = sftp_crypto_get_digest(algo, &mac_len,
+ &(read_macs[idx].free_digest));
if (read_macs[idx].digest == NULL) {
return -1;
}
@@ -928,16 +938,25 @@ int sftp_mac_set_write_algo(const char *algo) {
case SFTP_MAC_ALGO_TYPE_UMAC64:
umac_delete(umac_write_ctxs[idx]);
umac_write_ctxs[idx] = NULL;
+ if (write_macs[idx].free_digest == TRUE) {
+ sftp_crypto_free_digest(write_macs[idx].digest);
+ write_macs[idx].digest = NULL;
+ }
break;
case SFTP_MAC_ALGO_TYPE_UMAC128:
umac128_delete(umac_write_ctxs[idx]);
umac_write_ctxs[idx] = NULL;
+ if (write_macs[idx].free_digest == TRUE) {
+ sftp_crypto_free_digest(write_macs[idx].digest);
+ write_macs[idx].digest = NULL;
+ }
break;
}
}
- write_macs[idx].digest = sftp_crypto_get_digest(algo, &mac_len);
+ write_macs[idx].digest = sftp_crypto_get_digest(algo, &mac_len,
+ &(write_macs[idx].free_digest));
if (write_macs[idx].digest == NULL) {
return -1;
}
=====================================
contrib/mod_sftp/mod_sftp.c
=====================================
@@ -895,14 +895,21 @@ MODRET set_sftpclientmatch(cmd_rec *cmd) {
digests = create_config(c->pool, "SFTPDigests", algos->nelts);
for (j = 0; j < algos->nelts; j++) {
+ const EVP_MD *md;
const char *algo;
+ int free_md = FALSE;
algo = ((char **) algos->elts)[j];
- if (sftp_crypto_get_digest(algo, NULL) == NULL) {
+ md = sftp_crypto_get_digest(algo, NULL, &free_md);
+ if (md == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unsupported digest algorithm: ", algo, NULL));
}
+ if (free_md == TRUE) {
+ sftp_crypto_free_digest(md);
+ }
+
digests->argv[j] = pstrdup(digests->pool, algo);
}
@@ -1285,10 +1292,18 @@ MODRET set_sftpdigests(cmd_rec *cmd) {
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
for (i = 1; i < cmd->argc; i++) {
- if (sftp_crypto_get_digest(cmd->argv[i], NULL) == NULL) {
+ const EVP_MD *md;
+ int free_md = FALSE;
+
+ md = sftp_crypto_get_digest(cmd->argv[i], NULL, &free_md);
+ if (md == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unsupported digest algorithm: ", cmd->argv[i], NULL));
}
+
+ if (free_md == TRUE) {
+ sftp_crypto_free_digest(md);
+ }
}
c = add_config_param(cmd->argv[0], cmd->argc-1, NULL);
@@ -2580,6 +2595,7 @@ static int sftp_init(void) {
#endif /* HAVE_OSSL_PROVIDER_LOAD_OPENSSL */
sftp_keystore_init();
+ sftp_crypto_init();
sftp_cipher_init();
sftp_mac_init();
=====================================
contrib/mod_sftp/packet.c
=====================================
@@ -89,6 +89,21 @@ static unsigned int client_alive_interval = 0;
static const char *trace_channel = "ssh2";
static const char *timing_channel = "timing";
+/* This is admittedly an arbitrary upper limit on the number of EXT_INFO
+ * extensions we will handle.
+ *
+ * draft-ssh-ext-info-05 currently defines five:
+ *
+ * server-sig-algs
+ * no-flow-control
+ * accept-channels
+ * elevation
+ * delay-compression
+ *
+ * And some implementations, like OpenSSH, will have their own namespaced
+ * extensions.
+ */
+#define MAX_EXT_INFO_COUNT 32
#define MAX_POLL_TIMEOUTS 3
static int packet_poll(int sockfd, int io) {
@@ -1136,6 +1151,15 @@ int sftp_ssh2_packet_read(int sockfd, struct ssh2_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(sftp_logfd, MOD_SFTP_VERSION,
+ "illegal padding length (%u bytes) exceeds packet length "
+ "(%lu bytes)", (unsigned int) pkt->padding_len,
+ (unsigned long) pkt->packet_len);
+ read_packet_discard(sockfd);
+ 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);
@@ -1794,6 +1818,14 @@ void sftp_ssh2_packet_handle_ext_info(struct ssh2_packet *pkt) {
pr_trace_msg(trace_channel, 9, "client sent EXT_INFO with %lu %s",
(unsigned long) ext_count, ext_count != 1 ? "extensions" : "extension");
+ if (ext_count > MAX_EXT_INFO_COUNT) {
+ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+ "client sent too many EXT_INFO extensions (%lu, max %lu), ignoring",
+ (unsigned long) ext_count, (unsigned long) MAX_EXT_INFO_COUNT);
+ destroy_pool(pkt->pool);
+ return;
+ }
+
for (i = 0; i < ext_count; i++) {
char *ext_name = NULL;
uint32_t ext_datalen = 0;
@@ -1947,13 +1979,13 @@ static int handle_ssh2_packet(void *data) {
!(sftp_sess_state & SFTP_SESS_STATE_HAVE_EXT_INFO)) {
sftp_ssh2_packet_handle_ext_info(pkt);
sftp_sess_state |= SFTP_SESS_STATE_HAVE_EXT_INFO;
- break;
} else {
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"unable to handle %s (%d) message: wrong message order",
sftp_ssh2_packet_get_msg_type_desc(msg_type), msg_type);
}
+ break;
case SFTP_SSH2_MSG_SERVICE_REQUEST:
if (sftp_sess_state & SFTP_SESS_STATE_HAVE_KEX) {
@@ -1962,13 +1994,13 @@ static int handle_ssh2_packet(void *data) {
}
sftp_sess_state |= SFTP_SESS_STATE_HAVE_SERVICE;
- break;
} else {
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"unable to handle %s (%d) message: Key exchange required",
sftp_ssh2_packet_get_msg_type_desc(msg_type), msg_type);
}
+ break;
case SFTP_SSH2_MSG_USER_AUTH_REQUEST:
if (sftp_sess_state & SFTP_SESS_STATE_HAVE_SERVICE) {
@@ -1994,13 +2026,12 @@ static int handle_ssh2_packet(void *data) {
}
}
- break;
-
} else {
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"unable to handle %s (%d) message: Service request required",
sftp_ssh2_packet_get_msg_type_desc(msg_type), msg_type);
}
+ break;
case SFTP_SSH2_MSG_CHANNEL_OPEN:
case SFTP_SSH2_MSG_CHANNEL_REQUEST:
@@ -2013,13 +2044,12 @@ static int handle_ssh2_packet(void *data) {
SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
}
- break;
-
} else {
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"unable to handle %s (%d) message: User authentication required",
sftp_ssh2_packet_get_msg_type_desc(msg_type), msg_type);
}
+ break;
default:
handle_unknown_msg(pkt, msg_type);
@@ -2042,6 +2072,13 @@ int sftp_ssh2_packet_process(pool *p) {
pr_response_clear(&resp_err_list);
pr_response_set_pool(pkt->pool);
+ if (pkt->payload_len == 0 ||
+ pkt->payload == NULL) {
+ (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+ "received illegal packet with no payload, disconnecting");
+ SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+ }
+
/* If a custom handler rejects this packet with ENOSYS, it means we need
* to fall back to handling it ourselves. Our own handler never returns
* ENOSYS.
=====================================
contrib/mod_sftp/provider.c
=====================================
@@ -0,0 +1,313 @@
+/*
+ * ProFTPD - mod_sftp 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_sftp.h"
+#include "crypto.h"
+#include "provider.h"
+#include "umac.h"
+
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+# 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 = "ssh2";
+
+/* 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) {
+ 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 = umac_new((unsigned char *) data);
+ if (umac == NULL) {
+ return 0;
+ }
+
+ ctx->umac = umac;
+ return 1;
+ }
+
+ return 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 = 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 = umac128_new((unsigned char *) data);
+ if (umac == NULL) {
+ return 0;
+ }
+
+ ctx->umac = umac;
+ return 1;
+ }
+
+ return 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 = 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 */
+
+int sftp_provider_init(void) {
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+ if (OSSL_PROVIDER_add_builtin(NULL, "umac", umac_provider_init) != 1) {
+ pr_log_debug(DEBUG1, MOD_SFTP_VERSION
+ ": error registering 'umac' OpenSSL provider: %s",
+ sftp_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_SFTP_VERSION
+ ": error loading 'umac' OpenSSL provider: %s", sftp_crypto_get_errors());
+
+ } else {
+ pr_trace_msg(trace_channel, 9, "%s", "loaded 'umac' OpenSSL provider");
+ }
+#endif /* OpenSSL 4.x and later */
+
+ return 0;
+}
+
+void sftp_provider_free(void) {
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L && !defined(HAVE_LIBRESSL)
+ if (umac_provider != NULL) {
+ OSSL_PROVIDER_unload(umac_provider);
+ umac_provider = NULL;
+ }
+#endif /* OpenSSL 4.x and later */
+}
=====================================
contrib/mod_sftp/provider.h
=====================================
@@ -0,0 +1,32 @@
+/*
+ * ProFTPD - mod_sftp 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_SFTP_PROVIDER_H
+#define MOD_SFTP_PROVIDER_H
+
+#include "mod_sftp.h"
+
+int sftp_provider_init(void);
+void sftp_provider_free(void);
+
+#endif /* MOD_SFTP_PROVIDER_H */
=====================================
contrib/mod_sql.c
=====================================
@@ -984,9 +984,11 @@ static int sql_resolve_on_meta(pool *p, pr_jot_ctx_t *jot_ctx,
case LOGFMT_META_COMMAND:
case LOGFMT_META_DIR_NAME:
case LOGFMT_META_DIR_PATH:
+ case LOGFMT_META_ENV_VAR:
case LOGFMT_META_FILENAME:
case LOGFMT_META_IDENT_USER:
case LOGFMT_META_METHOD:
+ case LOGFMT_META_NOTE_VAR:
case LOGFMT_META_ORIGINAL_USER:
case LOGFMT_META_RESPONSE_STR:
case LOGFMT_META_REMOTE_HOST:
@@ -1009,14 +1011,12 @@ static int sql_resolve_on_meta(pool *p, pr_jot_ctx_t *jot_ctx,
}
case LOGFMT_META_CLASS:
- case LOGFMT_META_ENV_VAR:
case LOGFMT_META_EOS_REASON:
case LOGFMT_META_GROUP:
case LOGFMT_META_ISO8601:
case LOGFMT_META_LOCAL_FQDN:
case LOGFMT_META_LOCAL_IP:
case LOGFMT_META_LOCAL_NAME:
- case LOGFMT_META_NOTE_VAR:
case LOGFMT_META_PROTOCOL:
case LOGFMT_META_REMOTE_IP:
case LOGFMT_META_VERSION:
@@ -1999,8 +1999,11 @@ static struct passwd *sql_getpasswd(cmd_rec *cmd, struct passwd *p) {
}
} else {
+ /* The username has been escaped according to the backend database' rules
+ * at this point.
+ */
mr = sql_lookup(sql_make_cmd(cmd->tmp_pool, 3, MOD_SQL_DEF_CONN_NAME,
- cmap.usercustom, realname ? realname : "NULL"));
+ cmap.usercustom, username ? username : "NULL"));
if (check_response(mr, 0) < 0) {
return NULL;
=====================================
contrib/mod_tls.c
=====================================
@@ -794,12 +794,12 @@ static int tls_ssl_set_all(server_rec *, SSL *);
static int tls_openlog(void);
static int tls_seed_prng(void);
static int tls_sess_init(void);
-static void tls_setup_environ(pool *, SSL *);
-static void tls_setup_notes(pool *, SSL *);
-static int tls_verify_cb(int, X509_STORE_CTX *);
-static int tls_verify_crl(int, X509_STORE_CTX *);
-static int tls_verify_ocsp(int, X509_STORE_CTX *);
-static char *tls_x509_name_oneline(X509_NAME *);
+static void tls_setup_environ(pool *p, SSL *ssl);
+static void tls_setup_notes(pool *p, SSL *ssl);
+static int tls_verify_cb(int ok, X509_STORE_CTX *ctx);
+static int tls_verify_crl(int ok, X509_STORE_CTX *ctx);
+static int tls_verify_ocsp(int ok, X509_STORE_CTX *ctx);
+static char *tls_x509_name_oneline(const X509_NAME *x509_name);
static int tls_readmore(int);
static int tls_writemore(int);
@@ -2754,9 +2754,9 @@ static int tls_cert_match_ip_san(pool *p, X509 *cert, const char *ipstr) {
static char *tls_get_cert_cn(pool *p, X509 *cert) {
int 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;
@@ -6180,7 +6180,7 @@ static int ocsp_add_cached_response(pool *p, const char *fingerprint,
return res;
}
-static int tls_feature_cmp(ASN1_STRING *str, void *feat_data,
+static int tls_feature_cmp(const ASN1_STRING *str, void *feat_data,
size_t feat_datasz) {
int is_feat = FALSE, res;
ASN1_STRING *feat;
@@ -6224,8 +6224,8 @@ static int tls_cert_must_staple(X509 *cert, int *v2) {
for (i = 0; i < ext_count; i++) {
char buf[1024];
- X509_EXTENSION *ext;
- ASN1_OBJECT *obj;
+ const X509_EXTENSION *ext;
+ const ASN1_OBJECT *obj;
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(HAVE_LIBRESSL)) || \
(defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3050000L)
@@ -6241,7 +6241,7 @@ static int tls_cert_must_staple(X509 *cert, int *v2) {
/* Double-check that the OID is that of the "TLS Feature" extension. */
if (strcmp(buf, TLS_X509V3_TLS_FEAT_OID_TEXT) == 0) {
char status_request[] = TLS_X509V3_TLS_FEAT_STATUS_REQUEST;
- ASN1_OCTET_STRING *value;
+ const ASN1_OCTET_STRING *value;
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(HAVE_LIBRESSL)) || \
(defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3050000L)
@@ -9073,6 +9073,7 @@ static void tls_end_sess(SSL *ssl, conn_t *conn, int flags) {
}
if (ssl != ctrl_ssl &&
+ ctrl_ssl != NULL &&
SSL_get_session(ssl) == SSL_get_session(ctrl_ssl)) {
/* Uh-oh; our two SSL objects are pointing at the same SSL_SESSION object.
* This can happen when the SSL_SESSION is resumed, enabled by session
@@ -9095,9 +9096,23 @@ static void tls_end_sess(SSL *ssl, conn_t *conn, int flags) {
*/
pr_trace_msg(trace_channel, 29,
"data SSL %p being ended has same SSL_SESSION %p as control SSL, "
- "clearing the data SSL pointer manually (Issue #1963)", ssl,
+ "clearing the data SSL pointer manually (see Issue #1963)", ssl,
SSL_get_session(ssl));
- SSL_set_session(ssl, NULL);
+ if (SSL_set_session(ssl, NULL) != 1) {
+ pr_trace_msg(trace_channel, 29,
+ "error setting NULL session on SSL %p: %s", ssl, tls_get_errors());
+ }
+
+ /* Note that, per findings in Issue #2056, we also need to manually
+ * increment the refcount of the ctrl_ssl session, lest we still
+ * inadvertently corrupt the OpenSSL internal session cache state.
+ * Sigh.
+ */
+ if (SSL_SESSION_up_ref(SSL_get_session(ctrl_ssl)) != 1) {
+ pr_trace_msg(trace_channel, 29,
+ "error incrementing session refcount on SSL %p: %s", ctrl_ssl,
+ tls_get_errors());
+ }
}
SSL_free(ssl);
@@ -9434,14 +9449,14 @@ static int tls_cert_to_user(const char *user_name, const char *field_name) {
}
if (strcmp(field_name, "CommonName") == 0) {
- X509_NAME *name;
+ const X509_NAME *name;
int pos = -1;
name = X509_get_subject_name(client_cert);
while (TRUE) {
- X509_NAME_ENTRY *entry;
- ASN1_STRING *data;
+ const X509_NAME_ENTRY *entry;
+ const ASN1_STRING *data;
int data_len;
const unsigned char *data_str = NULL;
@@ -9552,8 +9567,8 @@ static int tls_cert_to_user(const char *user_name, const char *field_name) {
register int i;
for (i = 0; i < nexts; i++) {
- X509_EXTENSION *ext = NULL;
- ASN1_OBJECT *asn_object = NULL;
+ const X509_EXTENSION *ext = NULL;
+ const ASN1_OBJECT *asn_object = NULL;
char oid[PR_TUNABLE_PATH_MAX];
pr_signals_handle();
@@ -9565,7 +9580,7 @@ static int tls_cert_to_user(const char *user_name, const char *field_name) {
memset(oid, '\0', sizeof(oid));
if (OBJ_obj2txt(oid, sizeof(oid)-1, asn_object, 1) > 0) {
if (strcmp(oid, field_name) == 0) {
- ASN1_OCTET_STRING *asn_data = NULL;
+ const ASN1_OCTET_STRING *asn_data = NULL;
const unsigned char *asn_datastr = NULL;
int asn_datalen;
@@ -9862,7 +9877,8 @@ static void tls_setup_cert_ext_environ(const char *env_prefix, X509 *cert) {
* email Email NID_pkcs9_emailAddress
*/
-static void tls_setup_cert_dn_environ(const char *env_prefix, X509_NAME *name) {
+static void tls_setup_cert_dn_environ(const char *env_prefix,
+ const X509_NAME *name) {
register int i;
int nentries;
char *k, *v;
@@ -9874,7 +9890,7 @@ static void tls_setup_cert_dn_environ(const char *env_prefix, X509_NAME *name) {
#endif /* OpenSSL-1.1.x and later */
for (i = 0; i < nentries; i++) {
- X509_NAME_ENTRY *entry;
+ const X509_NAME_ENTRY *entry;
const unsigned char *entry_data;
int nid, entry_len;
@@ -9991,7 +10007,7 @@ static void tls_setup_cert_environ(pool *p, const char *env_prefix,
char buf[80] = {'\0'};
ASN1_INTEGER *serial = X509_get_serialNumber(cert);
const X509_ALGOR *algo = NULL;
- X509_PUBKEY *pubkey = NULL;
+ const X509_PUBKEY *pubkey = NULL;
memset(buf, '\0', sizeof(buf));
pr_snprintf(buf, sizeof(buf) - 1, "%lu", X509_get_version(cert) + 1);
@@ -10001,7 +10017,7 @@ static void tls_setup_cert_environ(pool *p, const char *env_prefix,
v = pstrdup(p, buf);
pr_env_set(p, k, v);
- if (serial->length < 4) {
+ if (ASN1_STRING_length(serial) < 4) {
memset(buf, '\0', sizeof(buf));
pr_snprintf(buf, sizeof(buf) - 1, "%lu", ASN1_INTEGER_get(serial));
buf[sizeof(buf)-1] = '\0';
@@ -10281,7 +10297,7 @@ static void tls_setup_notes(pool *p, SSL *ssl) {
client_cert = SSL_get_peer_certificate(ssl);
if (client_cert != NULL) {
const X509_ALGOR *algo = NULL;
- X509_PUBKEY *pubkey = NULL;
+ const X509_PUBKEY *pubkey = NULL;
BIO *bio = NULL;
char *data = NULL;
long datalen = 0;
@@ -10436,8 +10452,8 @@ static int tls_verify_cb(int ok, X509_STORE_CTX *ctx) {
}
static int tls_verify_crl(int ok, X509_STORE_CTX *ctx) {
- register int i = 0;
- X509_NAME *subject = NULL, *issuer = NULL;
+ register unsigned int i = 0;
+ const X509_NAME *subject = NULL, *issuer = NULL;
X509 *xs = NULL;
STACK_OF(X509_CRL) *crls = NULL;
int res, verify_error;
@@ -10615,7 +10631,7 @@ static int tls_verify_ocsp_url(X509_STORE_CTX *ctx, X509 *cert,
const char *url) {
BIO *conn;
X509 *issuing_cert = NULL;
- X509_NAME *subj = NULL;
+ const X509_NAME *subj = NULL;
X509_STORE *store = NULL;
const char *subj_name;
char *host = NULL, *port = NULL, *uri = NULL;
@@ -11033,8 +11049,14 @@ static int tls_verify_ocsp(int ok, X509_STORE_CTX *ctx) {
ocsp_urls = make_array(tmp_pool, 1, sizeof(char *));
}
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(HAVE_LIBRESSL)
*((char **) push_array(ocsp_urls)) = pstrdup(tmp_pool,
- (char *) desc->location->d.uniformResourceIdentifier->data);
+ (char *) ASN1_STRING_get0_data(desc->location->d.uniformResourceIdentifier));
+#else
+ *((char **) push_array(ocsp_urls)) = pstrdup(tmp_pool,
+ (char *) ASN1_STRING_data(desc->location->d.uniformResourceIdentifier));
+#endif /* OpenSSL 1.1.x and later */
}
}
@@ -11102,7 +11124,7 @@ static ssize_t tls_write(SSL *ssl, const void *buf, size_t len) {
return count;
}
-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
=====================================
contrib/mod_wrap2_sql.c
=====================================
@@ -1,7 +1,7 @@
/*
* ProFTPD: mod_wrap2_sql -- a mod_wrap2 sub-module for supplying IP-based
* access control data via SQL tables
- * Copyright (c) 2002-2016 TJ Saunders
+ * Copyright (c) 2002-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
@@ -25,7 +25,7 @@
#include "mod_wrap2.h"
#include "mod_sql.h"
-#define MOD_WRAP2_SQL_VERSION "mod_wrap2_sql/1.0"
+#define MOD_WRAP2_SQL_VERSION "mod_wrap2_sql/1.1"
#define WRAP2_SQL_NSLOTS 2
#define WRAP2_SQL_CLIENT_QUERY_IDX 0
@@ -62,6 +62,41 @@ static int sqltab_close_cb(wrap2_table_t *sqltab) {
return 0;
}
+static char *sqltab_get_escaped_text(pool *p, wrap2_table_t *sqltab,
+ const char *text) {
+ pool *tmp_pool = NULL;
+ cmdtable *sql_cmdtab = NULL;
+ cmd_rec *sql_cmd = NULL;
+ modret_t *sql_res = NULL;
+
+ /* Find the cmdtable for the sql_escapestr command, as the provided
+ * name needs to be properly escaped for SQL syntax; see Issue #2057.
+ */
+ sql_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_escapestr", NULL, NULL,
+ NULL);
+ if (sql_cmdtab == NULL) {
+ wrap2_log("error: unable to find SQL hook symbol 'sql_escapestr': "
+ "perhaps your proftpd.conf needs 'LoadModule mod_sql.c'?");
+ return NULL;
+ }
+
+ sql_cmd = sql_cmd_create(tmp_pool, 1, text);
+ sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
+ if (sql_res == NULL) {
+ wrap2_log("sql_escapestr '%s' returned no data; "
+ "see the mod_sql.c SQLLogFile for more details", text);
+ return NULL;
+ }
+
+ if (MODRET_ISERROR(sql_res)) {
+ wrap2_log("error processing sql_escapestr '%s': "
+ "check the mod_sql.c SQLLogFile for more details", text);
+ return NULL;
+ }
+
+ return sql_res->data;
+}
+
static array_header *sqltab_fetch_clients_cb(wrap2_table_t *sqltab,
const char *name) {
register unsigned int i;
@@ -70,12 +105,18 @@ static array_header *sqltab_fetch_clients_cb(wrap2_table_t *sqltab,
cmd_rec *sql_cmd = NULL;
modret_t *sql_res = NULL;
array_header *sql_data = NULL;
- char *query = NULL, **vals = NULL;
+ char *escaped_name = NULL, *query = NULL, **vals = NULL;
array_header *clients_list = NULL;
/* Allocate a temporary pool for the duration of this read. */
tmp_pool = make_sub_pool(sqltab->tab_pool);
+ escaped_name = sqltab_get_escaped_text(tmp_pool, sqltab, name);
+ if (escaped_name == NULL) {
+ destroy_pool(tmp_pool);
+ return NULL;
+ }
+
query = ((char **) sqltab->tab_data)[WRAP2_SQL_CLIENT_QUERY_IDX];
/* Find the cmdtable for the sql_lookup command. */
@@ -89,7 +130,7 @@ static array_header *sqltab_fetch_clients_cb(wrap2_table_t *sqltab,
}
/* Prepare the SELECT query. */
- sql_cmd = sql_cmd_create(tmp_pool, 3, "sql_lookup", query, name);
+ sql_cmd = sql_cmd_create(tmp_pool, 3, "sql_lookup", query, escaped_name);
/* Call the handler. */
sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
@@ -192,16 +233,22 @@ static array_header *sqltab_fetch_options_cb(wrap2_table_t *sqltab,
cmd_rec *sql_cmd = NULL;
modret_t *sql_res = NULL;
array_header *sql_data = NULL;
- char *query = NULL, **vals = NULL;
+ char *escaped_name = NULL, *query = NULL, **vals = NULL;
array_header *options_list = NULL;
/* Allocate a temporary pool for the duration of this read. */
tmp_pool = make_sub_pool(sqltab->tab_pool);
+ escaped_name = sqltab_get_escaped_text(tmp_pool, sqltab, name);
+ if (escaped_name == NULL) {
+ destroy_pool(tmp_pool);
+ return NULL;
+ }
+
query = ((char **) sqltab->tab_data)[WRAP2_SQL_OPTION_QUERY_IDX];
/* The options-query is not necessary. Skip if not present. */
- if (!query) {
+ if (query == NULL) {
destroy_pool(tmp_pool);
return NULL;
}
@@ -217,7 +264,7 @@ static array_header *sqltab_fetch_options_cb(wrap2_table_t *sqltab,
}
/* Prepare the SELECT query. */
- sql_cmd = sql_cmd_create(tmp_pool, 3, "sql_lookup", query, name);
+ sql_cmd = sql_cmd_create(tmp_pool, 3, "sql_lookup", query, escaped_name);
/* Call the handler. */
sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
=====================================
include/version.h
=====================================
@@ -28,8 +28,8 @@
#include "buildstamp.h"
/* Application version (in various forms) */
-#define PROFTPD_VERSION_NUMBER 0x0001030905
-#define PROFTPD_VERSION_TEXT "1.3.9a"
+#define PROFTPD_VERSION_NUMBER 0x0001030906
+#define PROFTPD_VERSION_TEXT "1.3.9b"
/* Module API version */
#define PR_MODULE_API_VERSION 0x20
=====================================
tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/quotatab.pm
=====================================
@@ -0,0 +1,271 @@
+package ProFTPD::Tests::Modules::mod_sftp::quotatab;
+
+use lib qw(t/lib);
+use base qw(ProFTPD::TestSuite::Child);
+use strict;
+
+use File::Path qw(mkpath);
+use File::Spec;
+use IO::Handle;
+use POSIX qw(:fcntl_h);
+
+use ProFTPD::TestSuite::FTP;
+use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
+
+$| = 1;
+
+my $order = 0;
+
+my $TESTS = {
+ sftp_quotatab_upload_hard_limit_issue2098 => {
+ order => ++$order,
+ test_class => [qw(bug forking mod_quotatab_sql mod_sql_sqlite)],
+ },
+
+};
+
+sub new {
+ return shift()->SUPER::new(@_);
+}
+
+sub list_tests {
+ return testsuite_get_runnable_tests($TESTS);
+}
+
+sub set_up {
+ my $self = shift;
+ $self->SUPER::set_up(@_);
+
+ # Make sure that mod_sftp does not complain about permissions on the hostkey
+ # files.
+
+ my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+ my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+ unless (chmod(0400, $rsa_host_key, $dsa_host_key)) {
+ die("Can't set perms on $rsa_host_key, $dsa_host_key: $!");
+ }
+}
+
+sub sftp_quotatab_upload_hard_limit_issue2098 {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'quotatab');
+
+ my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+ my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+ my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
+
+ my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
+
+ if (open(my $fh, "> $db_script")) {
+ print $fh <<EOS;
+CREATE TABLE quotalimits (
+ name TEXT NOT NULL PRIMARY KEY,
+ quota_type TEXT NOT NULL,
+ per_session TEXT NOT NULL,
+ limit_type TEXT NOT NULL,
+ bytes_in_avail REAL NOT NULL,
+ bytes_out_avail REAL NOT NULL,
+ bytes_xfer_avail REAL NOT NULL,
+ files_in_avail INTEGER NOT NULL,
+ files_out_avail INTEGER NOT NULL,
+ files_xfer_avail INTEGER NOT NULL
+);
+INSERT INTO quotalimits (name, quota_type, per_session, limit_type, bytes_in_avail, bytes_out_avail, bytes_xfer_avail, files_in_avail, files_out_avail, files_xfer_avail) VALUES ('$setup->{user}', 'user', 'false', 'hard', 32, 0, 0, 2, 0, 0);
+
+CREATE TABLE quotatallies (
+ name TEXT NOT NULL PRIMARY KEY,
+ quota_type TEXT NOT NULL,
+ bytes_in_used REAL NOT NULL,
+ bytes_out_used REAL NOT NULL,
+ bytes_xfer_used REAL NOT NULL,
+ files_in_used INTEGER NOT NULL,
+ files_out_used INTEGER NOT NULL,
+ files_xfer_used INTEGER NOT NULL
+);
+EOS
+
+ unless (close($fh)) {
+ die("Can't write $db_script: $!");
+ }
+
+ } else {
+ die("Can't open $db_script: $!");
+ }
+
+ my $cmd = "sqlite3 $db_file < $db_script";
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "Executing sqlite3: $cmd\n";
+ }
+
+ my @output = `$cmd`;
+ if (scalar(@output) &&
+ $ENV{TEST_VERBOSE}) {
+ print STDERR "Output: ", join('', @output), "\n";
+ }
+
+ # Make sure that, if we're running as root, the database file has
+ # the permissions/privs set for use by proftpd
+ if ($< == 0) {
+ unless (chmod(0666, $db_file)) {
+ die("Can't set perms on $db_file to 0666: $!");
+ }
+ }
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'fsio:20 quotatab:20 sql:20 ssh2:20 sftp:30',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_quotatab_sql.c' => [
+ 'SQLNamedQuery get-quota-limit SELECT "name, quota_type, per_session, limit_type, bytes_in_avail, bytes_out_avail, bytes_xfer_avail, files_in_avail, files_out_avail, files_xfer_avail FROM quotalimits WHERE name = \'%{0}\' AND quota_type = \'%{1}\'"',
+ 'SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used, files_in_used, files_out_used, files_xfer_used FROM quotatallies WHERE name = \'%{0}\' AND quota_type = \'%{1}\'"',
+ 'SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used = files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name = \'%{6}\' AND quota_type = \'%{7}\'" quotatallies',
+ 'SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4}, %{5}, %{6}, %{7}" quotatallies',
+
+ 'QuotaEngine on',
+ "QuotaLog $setup->{log_file}",
+ 'QuotaLimitTable sql:/get-quota-limit',
+ 'QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally',
+ ],
+
+ 'mod_sftp.c' => [
+ "SFTPEngine on",
+ "SFTPLog $setup->{log_file}",
+ "SFTPHostKey $rsa_host_key",
+ "SFTPHostKey $dsa_host_key",
+ ],
+
+ 'mod_sql.c' => {
+ SQLEngine => 'log',
+ SQLBackend => 'sqlite3',
+ SQLConnectInfo => $db_file,
+ SQLLogFile => $setup->{log_file},
+ },
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # Open pipes, for use between the parent and child processes. Specifically,
+ # the child will indicate when it's done with its test by writing a message
+ # to the parent.
+ my ($rfh, $wfh);
+ unless (pipe($rfh, $wfh)) {
+ die("Can't open pipe: $!");
+ }
+
+ require Net::SSH2;
+
+ my $ex;
+
+ # Fork child
+ $self->handle_sigchld();
+ defined(my $pid = fork()) or die("Can't fork: $!");
+ if ($pid) {
+ eval {
+ my $ssh2 = Net::SSH2->new();
+
+ sleep(1);
+
+ unless ($ssh2->connect('127.0.0.1', $port)) {
+ my ($err_code, $err_name, $err_str) = $ssh2->error();
+ die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+ }
+
+ unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) {
+ my ($err_code, $err_name, $err_str) = $ssh2->error();
+ die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+ }
+
+ my $sftp = $ssh2->sftp();
+ unless ($sftp) {
+ my ($err_code, $err_name, $err_str) = $ssh2->error();
+ die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+ }
+
+ my $fh = $sftp->open('test.dat', O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ unless ($fh) {
+ my ($err_code, $err_name) = $sftp->error();
+ die("Can't open test.txt: [$err_name] ($err_code)");
+ }
+
+ my $count = 20;
+ for (my $i = 0; $i < $count; $i++) {
+ print $fh "ABCD" x 8192;
+ }
+
+ # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle
+ $fh = undef;
+
+ $sftp = undef;
+ $ssh2->disconnect();
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ $wfh->print("done\n");
+ $wfh->flush();
+
+ } else {
+ eval { server_wait($setup->{config_file}, $rfh) };
+ if ($@) {
+ warn($@);
+ exit 1;
+ }
+
+ exit 0;
+ }
+
+ # Stop server
+ server_stop($setup->{pid_file});
+ $self->assert_child_ok($pid);
+
+ eval {
+ if (open(my $fh, "< $setup->{log_file}")) {
+ my $ok = 0;
+
+ while (my $line = <$fh>) {
+ chomp($line);
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /limit exceeded/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected QuotaLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
+1;
=====================================
tests/t/modules/mod_sftp/quotatab.t
=====================================
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+
+use lib qw(t/lib);
+use strict;
+
+use Test::Unit::HarnessUnit;
+
+$| = 1;
+
+my $r = Test::Unit::HarnessUnit->new();
+$r->start("ProFTPD::Tests::Modules::mod_sftp::quotatab");
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/6d6fe6ac47162620d9d09a3d12012a81b080bab5
--
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/6d6fe6ac47162620d9d09a3d12012a81b080bab5
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