[packages/proftpd] Up to 1.3.9d

arekm arekm at pld-linux.org
Sun Oct 18 23:37:34 CEST 2026


commit 3a64ce33644a3d0a1ef10c5aa8278fd8b3307446
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date:   Sun Oct 18 22:57:05 2026 +0200

    Up to 1.3.9d

 proftpd-issue-2319.patch        |  60 ++++
 proftpd-issues-2256-2286.patch  | 613 ++++++++++++++++++++++++++++++++++++++++
 proftpd-rewrite-utf8trans.patch |  47 +++
 proftpd.spec                    |  56 ++--
 4 files changed, 759 insertions(+), 17 deletions(-)
---
diff --git a/proftpd.spec b/proftpd.spec
index 6575eaf..2ba980a 100644
--- a/proftpd.spec
+++ b/proftpd.spec
@@ -26,13 +26,13 @@ Summary(pl.UTF-8):	PROfesionalny serwer FTP
 Summary(pt_BR.UTF-8):	Servidor FTP profissional, com sintaxe de configuração semelhante à do apache
 Summary(zh_CN.UTF-8):	易于管理的,安全的 FTP 服务器
 Name:		proftpd
-Version:	1.3.9c
+Version:	1.3.9d
 Release:	1
 Epoch:		2
 License:	GPL v2+
 Group:		Networking/Daemons
 Source0:	ftp://ftp.proftpd.org/distrib/source/%{name}-%{version}.tar.gz
-# Source0-md5:	96c30417d992b396e6a76f4edb0a2bf8
+# Source0-md5:	d7773e706687ffdaec3d56f285bd9aab
 # https://github.com/jbenden/mod_clamav/releases
 Source1:	https://github.com/jbenden/mod_clamav/archive/v%{mod_clamav_version}/mod_clamav-%{mod_clamav_version}.tar.gz
 # Source1-md5:	7d1e7e33added5a6ce1c4d96ca572167
@@ -53,6 +53,9 @@ Patch1:		%{name}-noautopriv.patch
 Patch2:		%{name}-wtmp.patch
 Patch3:		%{name}-pool.patch
 Patch5:		mod_clamav-compat.patch
+Patch6:		proftpd-issues-2256-2286.patch
+Patch7:		proftpd-issue-2319.patch
+Patch8:		proftpd-rewrite-utf8trans.patch
 URL:		http://www.proftpd.org/
 BuildRequires:	GeoIP-devel
 BuildRequires:	acl-devel
@@ -60,6 +63,7 @@ BuildRequires:	autoconf
 BuildRequires:	automake
 BuildRequires:	hiredis-devel
 BuildRequires:	libcap-devel
+BuildRequires:	libsodium-devel
 BuildRequires:	libmemcached-devel
 BuildRequires:	libnsl-devel
 BuildRequires:	libstdc++-devel
@@ -71,7 +75,7 @@ BuildRequires:	ncurses-devel
 %if %{with ldap} || %{with quotaldap}
 BuildRequires:	openldap-devel
 %endif
-%{?with_ssl:BuildRequires:	openssl-devel >= 0.9.7d}
+BuildRequires:	openssl-devel >= 0.9.7d
 %{?with_pam:BuildRequires:		pam-devel}
 %if %{with pgsql} || %{with quotapgsql}
 BuildRequires:	postgresql-devel
@@ -327,6 +331,12 @@ Group:		Networking/Daemons
 Requires:	%{name}-common = %{epoch}:%{version}-%{release}
 Requires:	%{name}-mod_quotatab = %{epoch}:%{version}-%{release}
 Requires:	%{name}-mod_sql = %{epoch}:%{version}-%{release}
+%if %{with quotamysql}
+Requires:	%{name}-mod_sql_mysql = %{epoch}:%{version}-%{release}
+%endif
+%if %{with quotapgsql}
+Requires:	%{name}-mod_sql_postgres = %{epoch}:%{version}-%{release}
+%endif
 
 %description mod_quotatab_sql
 A mod_quotatab sub-module for managing quota data via SQL-based
@@ -566,6 +576,9 @@ http://www.proftpd.org/docs/contrib/mod_sftp.html
 cp -a mod_clamav-%{mod_clamav_version}/*.{c,h} contrib/
 
 %patch -P5 -p1
+%patch -P6 -p1
+%patch -P7 -p1
+%patch -P8 -p1
 
 cp -f /usr/share/automake/config.sub .
 
@@ -599,11 +612,13 @@ mod_wrap2
 mod_ifsession
 %{?with_ssl:mod_tls}
 %{?with_pam:mod_auth_pam}
-%{?with_ldap:mod_ldap}
+%if %{with ldap} || %{with quotaldap}
+mod_ldap
+%endif
 %{?with_quotafile:mod_quotatab mod_quotatab_file}
 %{?with_quotaldap:mod_quotatab mod_quotatab_ldap}
-%{?with_quotamysql:mod_quotatab mod_quotatab_sql}
-%{?with_quotapgsql:mod_quotatab mod_quotatab_sql}
+%{?with_quotamysql:mod_quotatab mod_quotatab_sql mod_sql mod_sql_mysql}
+%{?with_quotapgsql:mod_quotatab mod_quotatab_sql mod_sql mod_sql_postgres}
 %{?with_mysql:mod_sql mod_sql_mysql}
 %{?with_pgsql:mod_sql mod_sql_postgres}
 %{?with_wrap2file:mod_wrap2 mod_wrap2_file}
@@ -646,7 +661,7 @@ install -d $RPM_BUILD_ROOT/etc/{pam.d,security,sysconfig/rc-inetd,rc.d/init.d} \
 %{__rm} $RPM_BUILD_ROOT%{_sbindir}/in.proftpd
 
 cp -p %{SOURCE2} $RPM_BUILD_ROOT%{_sysconfdir}
-cp -p %{SOURCE9} $RPM_BUILD_ROOT%{_sysconfdir}/conf.d/mod_auth_pam.conf
+%{?with_pam:cp -p %{SOURCE9} $RPM_BUILD_ROOT%{_sysconfdir}/conf.d/mod_auth_pam.conf}
 MODULES="
 mod_auth_file
 mod_ident
@@ -660,11 +675,13 @@ mod_geoip
 mod_memcache
 mod_redis
 mod_sftp
-%{?with_ldap:mod_ldap}
+%if %{with ldap} || %{with quotaldap}
+mod_ldap
+%endif
 %{?with_quotafile:mod_quotatab mod_quotatab_file}
 %{?with_quotaldap:mod_quotatab mod_quotatab_ldap}
-%{?with_quotamysql:mod_quotatab mod_quotatab_sql}
-%{?with_quotapgsql:mod_quotatab mod_quotatab_sql}
+%{?with_quotamysql:mod_quotatab mod_quotatab_sql mod_sql mod_sql_mysql}
+%{?with_quotapgsql:mod_quotatab mod_quotatab_sql mod_sql mod_sql_postgres}
 %{?with_mysql:mod_sql mod_sql_mysql}
 %{?with_pgsql:mod_sql mod_sql_postgres}
 %{?with_wrap2file:mod_wrap2 mod_wrap2_file}
@@ -672,7 +689,7 @@ mod_sftp
 for module in $MODULES; do
 	echo "LoadModule	$module.c" > $RPM_BUILD_ROOT%{_sysconfdir}/conf.d/$module.conf
 done
-cp -p %{SOURCE10} $RPM_BUILD_ROOT%{_sysconfdir}/conf.d/mod_tls.conf
+%{?with_ssl:cp -p %{SOURCE10} $RPM_BUILD_ROOT%{_sysconfdir}/conf.d/mod_tls.conf}
 cp -p %{SOURCE11} $RPM_BUILD_ROOT%{_sysconfdir}/conf.d/anonftp.conf
 cp -p %{SOURCE12} $RPM_BUILD_ROOT%{_sysconfdir}/conf.d/mod_clamav.conf
 
@@ -692,7 +709,7 @@ cp -p %{SOURCE13} $RPM_BUILD_ROOT%{systemdtmpfilesdir}/%{name}.conf
 # only for -inetd package?
 ln -sf proftpd $RPM_BUILD_ROOT%{_sbindir}/ftpd
 
-:> $RPM_BUILD_ROOT/etc/security/blacklist.ftp
+%{?with_pam::> $RPM_BUILD_ROOT/etc/security/blacklist.ftp}
 
 # cannot just --disable-static because build process depend on static objects
 %{__rm} $RPM_BUILD_ROOT%{_libexecdir}/*.a
@@ -833,7 +850,8 @@ fi
 %attr(755,root,root) %{_sbindir}/ftpscrub
 %attr(755,root,root) %{_sbindir}/ftpshut
 %attr(755,root,root) %{_sbindir}/proftpd
-%attr(755,root,root) %{_sbindir}/ftpd
+%{_sbindir}/ftpd
+%{_localedir}/en_US/LC_MESSAGES/proftpd.mo
 %dir %{_libexecdir}
 %attr(755,root,root) %{_libexecdir}/mod_auth_file.so
 %attr(755,root,root) %{_libexecdir}/mod_facl.so
@@ -889,17 +907,19 @@ fi
 %attr(755,root,root) %{_libexecdir}/mod_auth_pam.so
 %endif
 
-%if %{with ldap}
+%if %{with ldap} || %{with quotaldap}
 %files mod_ldap
 %defattr(644,root,root,755)
 %attr(640,root,root) %config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/conf.d/mod_ldap.conf
 %attr(755,root,root) %{_libexecdir}/mod_ldap.so
 %endif
 
+%if %{with quotafile} || %{with quotaldap} || %{with quotamysql} || %{with quotapgsql}
 %files mod_quotatab
 %defattr(644,root,root,755)
 %attr(640,root,root) %config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/conf.d/mod_quotatab.conf
 %attr(755,root,root) %{_libexecdir}/mod_quotatab.so
+%endif
 
 %if %{with quotafile}
 %files mod_quotatab_file
@@ -938,31 +958,33 @@ fi
 %attr(640,root,root) %config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/conf.d/mod_rewrite.conf
 %attr(755,root,root) %{_libexecdir}/mod_rewrite.so
 
-%if %{with mysql} || %{with pgsql}
+%if %{with mysql} || %{with pgsql} || %{with quotamysql} || %{with quotapgsql}
 %files mod_sql
 %defattr(644,root,root,755)
 %attr(640,root,root) %config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/conf.d/mod_sql.conf
 %attr(755,root,root) %{_libexecdir}/mod_sql.so
 %endif
 
-%if %{with mysql}
+%if %{with mysql} || %{with quotamysql}
 %files mod_sql_mysql
 %defattr(644,root,root,755)
 %attr(640,root,root) %config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/conf.d/mod_sql_mysql.conf
 %attr(755,root,root) %{_libexecdir}/mod_sql_mysql.so
 %endif
 
-%if %{with pgsql}
+%if %{with pgsql} || %{with quotapgsql}
 %files mod_sql_postgres
 %defattr(644,root,root,755)
 %attr(640,root,root) %config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/conf.d/mod_sql_postgres.conf
 %attr(755,root,root) %{_libexecdir}/mod_sql_postgres.so
 %endif
 
+%if %{with ssl}
 %files mod_tls
 %defattr(644,root,root,755)
 %attr(640,root,root) %config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/conf.d/mod_tls.conf
 %attr(755,root,root) %{_libexecdir}/mod_tls.so
+%endif
 
 %files mod_wrap
 %defattr(644,root,root,755)
diff --git a/proftpd-issue-2319.patch b/proftpd-issue-2319.patch
new file mode 100644
index 0000000..5de3eba
--- /dev/null
+++ b/proftpd-issue-2319.patch
@@ -0,0 +1,60 @@
+# Reset the mod_auth passwordless-auth state between USER attempts and on
+# session reinit, so a stale flag cannot let a wrong password authenticate
+# Upstream Issue #2319: https://github.com/proftpd/proftpd/issues/2319
+--- a/modules/mod_auth.c
++++ b/modules/mod_auth.c
+@@ -70,4 +70,4 @@
+- * Note: Once this function returns true (i.e. client has authenticated),
+- * it will ALWAYS return true.  At least until REIN is implemented.  Thus
+- * we have a flag for such a situation, to save on redundant lookups for
+- * the "authenticated" record.
++ * Note: Once this function returns true (i.e. client has successfully
++ * authenticated and logged in), it will ALWAYS return true.  At least until
++ * REIN is implemented.  Thus we have a flag for such a situation, to save on
++ * redundant lookups for the "authenticated" record.
+@@ -143,0 +144,17 @@
++static void reset_authenticated_without_pass(cmd_rec *cmd) {
++  unsigned char *authenticated = NULL;
++
++  if (cmd != NULL) {
++    authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE);
++  }
++  if (authenticated != NULL) {
++    remove_config(main_server->conf, "authenticated", FALSE);
++  }
++
++  if (cmd != NULL) {
++    (void) pr_table_remove(cmd->notes, "mod_auth.checked-auth", NULL);
++  }
++
++  authenticated_without_pass = FALSE;
++}
++
+@@ -164,3 +180,0 @@
+-  /* Reset the CreateHome setting. */
+-  mkhome = FALSE;
+-
+@@ -172,0 +187,2 @@
++  reset_authenticated_without_pass(NULL);
++  auth_have_authenticated = FALSE;
+@@ -1280,3 +1296,2 @@
+-    if (!authenticated_without_pass) {
+-      auth_code = do_auth(p, c ? c->subset : main_server->conf, user_name,
+-        pass);
++    if (authenticated_without_pass == TRUE) {
++      auth_code = PR_AUTH_OK_NO_PASS;
+@@ -1285 +1300,2 @@
+-      auth_code = PR_AUTH_OK_NO_PASS;
++      auth_code = do_auth(p, c ? c->subset : main_server->conf, user_name,
++        pass);
+@@ -2019,0 +2036 @@
++  reset_authenticated_without_pass(cmd);
+@@ -2022,0 +2040 @@
++
+@@ -2518,0 +2537 @@
++  reset_authenticated_without_pass(cmd);
+@@ -2525 +2544 @@
+-  if (nopass) {
++  if (nopass == TRUE) {
+@@ -2745,0 +2765 @@
++  reset_authenticated_without_pass(cmd);
diff --git a/proftpd-issues-2256-2286.patch b/proftpd-issues-2256-2286.patch
new file mode 100644
index 0000000..c974d98
--- /dev/null
+++ b/proftpd-issues-2256-2286.patch
@@ -0,0 +1,613 @@
+# Reject invalid transfer offsets and preserve symlink semantics on EXDEV renames.
+# Upstream Issue #2286: https://github.com/proftpd/proftpd/issues/2286
+# Commits: 16e5d14, c8bf3c6, 7e810ca, 9ad4b03
+# Upstream Issue #2256: https://github.com/proftpd/proftpd/issues/2256
+# Commits: bdb4901, 8255a02, 7610f88, 0fa0f97
+diff --git a/contrib/mod_copy.c b/contrib/mod_copy.c
+index a87a4363acd45715743f706019135bfce86c27b9..a807c2fca3b46e370feb6b80ba79161c3acf4efe 100644
+--- a/contrib/mod_copy.c
++++ b/contrib/mod_copy.c
+@@ -160,31 +159,0 @@ static int create_path(pool *p, const char *path) {
+-static int copy_symlink(pool *p, const char *src_path, const char *dst_path,
+-    int flags) {
+-  char *link_path = pcalloc(p, PR_TUNABLE_BUFFER_SIZE);
+-  int len;
+-
+-  len = pr_fsio_readlink(src_path, link_path, PR_TUNABLE_BUFFER_SIZE-1);
+-  if (len < 0) {
+-    int xerrno = errno;
+-
+-    pr_log_pri(PR_LOG_WARNING, MOD_COPY_VERSION ": error reading link '%s': %s",
+-      src_path, strerror(xerrno));
+-
+-    errno = xerrno;
+-    return -1;
+-  }
+-  link_path[len] = '\0';
+-
+-  if (pr_fsio_symlink(link_path, dst_path) < 0) {
+-    int xerrno = errno;
+-
+-    pr_log_pri(PR_LOG_WARNING, MOD_COPY_VERSION
+-      ": error symlinking '%s' to '%s': %s", link_path, dst_path,
+-      strerror(xerrno));
+-
+-    errno = xerrno;
+-    return -1;
+-  }
+-
+-  return 0;
+-}
+-
+@@ -320 +289 @@ static int copy_dir(pool *p, const char *src_dir, const char *dst_dir,
+-      if (copy_symlink(iter_pool, src_path, dst_path, flags) < 0) {
++      if (pr_fs_copy_symlink(src_path, dst_path, flags) < 0) {
+@@ -471,0 +441,4 @@ static int copy_paths(pool *p, const char *from, const char *to) {
++
++    } else {
++      pr_trace_msg(trace_channel, 19,
++        "lstat(2) error for destination path '%s': %s", to, strerror(errno));
+@@ -474 +447 @@ static int copy_paths(pool *p, const char *from, const char *to) {
+-    res = copy_symlink(p, from, to, flags);
++    res = pr_fs_copy_file2(from, to, flags, NULL);
+diff --git a/contrib/mod_sftp/fxp.c b/contrib/mod_sftp/fxp.c
+index 6bcbf29955698c02ab4a878d6b87500b905e2e3b..ba60840a3244b0a41f254dc9b5a45da8e085b2d1 100644
+--- a/contrib/mod_sftp/fxp.c
++++ b/contrib/mod_sftp/fxp.c
+@@ -4328 +4328,2 @@ static int fxp_handle_ext_check_file(struct fxp_packet *fxp, char *digest_list,
+-  if (offset >= st.st_size) {
++  if (offset < 0 ||
++      offset >= st.st_size) {
+@@ -4333 +4334 @@ static int fxp_handle_ext_check_file(struct fxp_packet *fxp, char *digest_list,
+-      " >= %" PR_LU " file size)", (pr_off_t) offset, (pr_off_t) st.st_size);
++      " vs %" PR_LU " file size)", (pr_off_t) offset, (pr_off_t) st.st_size);
+@@ -5337,0 +5339,116 @@ static int fxp_handle_ext_limits(struct fxp_packet *fxp) {
++static int rename_across_mounts(pool *p, const char *src_path,
++    const char *dst_path) {
++  int res, xerrno;
++  struct stat src_st, dst_st;
++
++  pr_fs_clear_cache2(src_path);
++  res = pr_fsio_lstat(src_path, &src_st);
++  if (res < 0) {
++    xerrno = errno;
++
++    pr_trace_msg(trace_channel, 3, "rename: lstat(2) error on '%s': %s",
++      src_path, strerror(xerrno));
++
++    errno = xerrno;
++    return -1;
++  }
++
++  if (S_ISDIR(src_st.st_mode)) {
++    xerrno = EISDIR;
++
++    pr_trace_msg(trace_channel, 3,
++      "rename: cannot rename '%s' across mounts: %s", src_path,
++      strerror(xerrno));
++
++    errno = xerrno;
++    return -1;
++  }
++
++  if (!S_ISREG(src_st.st_mode) &&
++      !S_ISLNK(src_st.st_mode)) {
++    xerrno = EPERM;
++
++    pr_trace_msg(trace_channel, 3,
++      "rename: cannot rename unsupported file type '%s' across mounts: %s",
++      src_path, strerror(xerrno));
++
++    errno = xerrno;
++    return -1;
++  }
++
++  pr_fs_clear_cache2(dst_path);
++  res = pr_fsio_lstat(dst_path, &dst_st);
++  if (res < 0 &&
++      errno != ENOENT) {
++    xerrno = errno;
++
++    pr_trace_msg(trace_channel, 3, "rename: lstat(2) error on '%s': %s",
++      dst_path, strerror(xerrno));
++
++    errno = xerrno;
++    return -1;
++  }
++
++  if (res == 0) {
++    if (S_ISDIR(dst_st.st_mode)) {
++      xerrno = EPERM;
++
++      pr_trace_msg(trace_channel, 3,
++        "rename: cannot rename '%s' across mounts into directory '%s': %s",
++        src_path, dst_path, strerror(xerrno));
++
++      errno = xerrno;
++      return -1;
++    }
++
++    res = pr_fsio_unlink(dst_path);
++    if (res < 0) {
++      xerrno = errno;
++
++      pr_trace_msg(trace_channel, 3, "rename: unlink(2) error on '%s': %s",
++        dst_path, strerror(xerrno));
++
++      errno = xerrno;
++      return -1;
++    }
++  }
++
++  /* At this point, we know that the source path exists, and is a regular file
++   * or a symlink.  And we know that the destination path does not exist.
++   */
++
++  if (S_ISREG(src_st.st_mode)) {
++    res = pr_fs_copy_file(src_path, dst_path);
++    xerrno = errno;
++
++  } else if (S_ISLNK(src_st.st_mode)) {
++    res = pr_fs_copy_symlink(src_path, dst_path, 0);
++    xerrno = errno;
++
++  } else {
++    res = -1;
++    xerrno = EPERM;
++  }
++
++  if (res < 0) {
++    (void) pr_trace_msg("fileperms", 1, "RENAME, user '%s' (UID %s, "
++      "GID %s): error copying '%s' to '%s': %s", session.user,
++      pr_uid2str(p, session.uid), pr_gid2str(p, session.gid), src_path,
++      dst_path, strerror(xerrno));
++
++    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
++      "error copying '%s' to '%s': %s", src_path, dst_path, strerror(xerrno));
++
++    errno = xerrno;
++    return -1;
++  }
++
++  /* Once copied, remove the original path. */
++  if (pr_fsio_unlink(src_path) < 0) {
++    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
++      "error deleting '%s': %s", src_path, strerror(errno));
++  }
++
++  return 0;
++}
++
+@@ -5599 +5716 @@ static int fxp_handle_ext_posix_rename(struct fxp_packet *fxp, char *src,
+-      res = pr_fs_copy_file2(src, dst, 0, NULL);
++      res = rename_across_mounts(fxp->pool, src, dst);
+@@ -5613,9 +5729,0 @@ static int fxp_handle_ext_posix_rename(struct fxp_packet *fxp, char *src,
+-
+-      } else {
+-        /* Once copied, remove the original path. */
+-        if (pr_fsio_unlink(src) < 0) {
+-          (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+-            "error deleting '%s': %s", src, strerror(errno));
+-        }
+-
+-        xerrno = errno = 0;
+@@ -5637 +5745 @@ static int fxp_handle_ext_posix_rename(struct fxp_packet *fxp, char *src,
+-  if (session.xfer.p) {
++  if (session.xfer.p != NULL) {
+@@ -10527,0 +10636,25 @@ static int fxp_handle_read(struct fxp_packet *fxp) {
++  if (((off_t) offset) < 0) {
++    int xerrno = EINVAL;
++    uint32_t status_code;
++    const char *reason;
++
++    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
++      "requested invalid read offset (%" PR_LU " bytes)", (pr_off_t) offset);
++
++    status_code = fxp_errno2status(xerrno, &reason);
++
++    pr_trace_msg(trace_channel, 8, "sending response: STATUS %lu '%s'",
++      (unsigned long) status_code, fxp_strerror(status_code));
++
++    fxp_status_write(fxp->pool, &buf, &buflen, fxp->request_id, status_code,
++      reason, NULL);
++
++    fxp_cmd_dispatch_err(cmd);
++
++    resp = fxp_packet_create(fxp->pool, fxp->channel_id);
++    resp->payload = ptr;
++    resp->payload_sz = (bufsz - buflen);
++
++    return fxp_packet_write(resp);
++  }
++
+@@ -12330,0 +12464,2 @@ static int fxp_handle_rename(struct fxp_packet *fxp) {
++      errno = 0;
++
+@@ -12334,2 +12469 @@ static int fxp_handle_rename(struct fxp_packet *fxp) {
+-      errno = 0;
+-      if (pr_fs_copy_file2(old_path, new_path, 0, NULL) < 0) {
++      if (rename_across_mounts(fxp->pool, old_path, new_path) < 0) {
+@@ -12339 +12473 @@ static int fxp_handle_rename(struct fxp_packet *fxp) {
+-          "GID %s): error copying '%s' to '%s': %s", session.user,
++          "GID %s): error renaming '%s' to '%s': %s", session.user,
+@@ -12345,2 +12479,2 @@ static int fxp_handle_rename(struct fxp_packet *fxp) {
+-          "error copying '%s' to '%s': %s", old_path, new_path,
+-          strerror(xerrno));
++          "RENAME of '%s' to '%s' across mounts failed: %s", old_path,
++          new_path, strerror(xerrno));
+@@ -12349,9 +12482,0 @@ static int fxp_handle_rename(struct fxp_packet *fxp) {
+-
+-      } else {
+-        /* Once copied, remove the original path. */
+-        if (pr_fsio_unlink(old_path) < 0) {
+-          (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+-            "error deleting '%s': %s", old_path, strerror(errno));
+-        }
+-
+-        xerrno = errno = 0;
+@@ -12373 +12498 @@ static int fxp_handle_rename(struct fxp_packet *fxp) {
+-  if (session.xfer.p) {
++  if (session.xfer.p != NULL) {
+@@ -13584,0 +13710,25 @@ static int fxp_handle_write(struct fxp_packet *fxp) {
++  if (((off_t) offset) < 0) {
++    int xerrno = EINVAL;
++    uint32_t status_code;
++    const char *reason;
++
++    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
++      "requested invalid write offset (%" PR_LU " bytes)", (pr_off_t) offset);
++
++    status_code = fxp_errno2status(xerrno, &reason);
++
++    pr_trace_msg(trace_channel, 8, "sending response: STATUS %lu '%s'",
++      (unsigned long) status_code, fxp_strerror(status_code));
++
++    fxp_status_write(fxp->pool, &buf, &buflen, fxp->request_id, status_code,
++      reason, NULL);
++
++    fxp_cmd_dispatch_err(cmd);
++
++    resp = fxp_packet_create(fxp->pool, fxp->channel_id);
++    resp->payload = ptr;
++    resp->payload_sz = (bufsz - buflen);
++
++    return fxp_packet_write(resp);
++  }
++
+diff --git a/contrib/mod_sftp/scp.c b/contrib/mod_sftp/scp.c
+index 4fb6cb232bd4a5006333da8ece4908058b122808..02c7ea02c6f38a6b5e146ced9631228491c6c257 100644
+--- a/contrib/mod_sftp/scp.c
++++ b/contrib/mod_sftp/scp.c
+@@ -607,0 +608 @@ static int recv_filesz(pool *p, uint32_t channel_id, char *size_str,
++  int res = 0;
+@@ -609,0 +611 @@ static int recv_filesz(pool *p, uint32_t channel_id, char *size_str,
++
+@@ -611,0 +614 @@ static int recv_filesz(pool *p, uint32_t channel_id, char *size_str,
++  errno = 0;
+@@ -613,0 +617,4 @@ static int recv_filesz(pool *p, uint32_t channel_id, char *size_str,
++  if (sz == ULLONG_MAX &&
++      errno == ERANGE) {
++    res = -1;
++  }
+@@ -615,0 +623,4 @@ static int recv_filesz(pool *p, uint32_t channel_id, char *size_str,
++  if (sz == ULONG_MAX &&
++      errno == ERANGE) {
++    res = -1;
++  }
+@@ -617,0 +629,7 @@ static int recv_filesz(pool *p, uint32_t channel_id, char *size_str,
++  /* Watch for cases where the sent file size might overflow our size type. */
++  if (res < 0) {
++    pr_trace_msg(trace_channel, 2, "file size '%s' out of range", size_str);
++    write_confirm(p, channel_id, 1, "file size out of range");
++    return -1;
++  }
++
+@@ -620,3 +638,2 @@ static int recv_filesz(pool *p, uint32_t channel_id, char *size_str,
+-  /* Watch for cases where the sent file size might overflow our size type. */
+-  if (*filesz < 0) {
+-    pr_trace_msg(trace_channel, 2, "file size out of range");
++  if (*filesz < 0 || (unsigned long long) *filesz != sz) {
++    pr_trace_msg(trace_channel, 2, "file size '%s' out of range", size_str);
+diff --git a/include/fsio.h b/include/fsio.h
+index 5ac00183a40f3bc70e665ff5179a9530d8807abb..b9b184aa77dca40adf53b508d2ca2467009e11bb 100644
+--- a/include/fsio.h
++++ b/include/fsio.h
+@@ -416,0 +417,5 @@ int pr_fs_copy_file2(const char *src, const char *dst, int flags,
++/* Copy a symlink from the given source path to the destination path, creating
++ * a symlink pointing to the same file at the destination.
++ */
++int pr_fs_copy_symlink(const char *src, const char *dst, int flags);
++
+diff --git a/modules/mod_core.c b/modules/mod_core.c
+index e2b38ae87c1a4a13299d2d1b5a76c8c8013559a3..18766f323ec14e5158373912d03eea2ed05fb620 100644
+--- a/modules/mod_core.c
++++ b/modules/mod_core.c
+@@ -6136,0 +6137,119 @@ MODRET core_dele(cmd_rec *cmd) {
++static int handle_cross_mount_rename(cmd_rec *cmd, const char *src_path,
++    const char *dst_path, struct stat *dst_st) {
++  int res, xerrno;
++  struct stat src_st;
++
++  pr_fs_clear_cache2(src_path);
++  res = pr_fsio_lstat(src_path, &src_st);
++  if (res < 0) {
++    xerrno = errno;
++
++    pr_log_debug(DEBUG4, "Unable to stat '%s' for cross filesystem mount "
++      "point rename: %s", src_path, strerror(xerrno));
++
++    pr_response_add_err(R_550, _("%s: %s"), cmd->arg, strerror(xerrno));
++    pr_cmd_set_errno(cmd, xerrno);
++    errno = xerrno;
++
++    return -1;
++  }
++
++  if (S_ISDIR(src_st.st_mode)) {
++    xerrno = EPERM;
++
++    pr_log_debug(DEBUG4, "Cannot rename directory '%s' across a filesystem "
++      "mount", src_path);
++
++    pr_response_add_err(R_550, _("%s: %s"), cmd->arg, strerror(xerrno));
++    pr_cmd_set_errno(cmd, xerrno);
++    errno = xerrno;
++
++    return -1;
++  }
++
++  if (!S_ISREG(src_st.st_mode) &&
++      !S_ISLNK(src_st.st_mode)) {
++    xerrno = EPERM;
++
++    pr_log_debug(DEBUG4, "Cannot rename unsupported file type '%s' across "
++      "a filesystem mount", src_path);
++
++    pr_response_add_err(R_550, _("%s: %s"), cmd->arg, strerror(xerrno));
++    pr_cmd_set_errno(cmd, xerrno);
++    errno = xerrno;
++
++    return -1;
++  }
++
++  /* If the destination exists, unlink it first.  Unless it is a directory,
++   * in which case we error out now; we do not copy into directories across
++   * mounts at the moment.
++   */
++  if (dst_st != NULL) {
++    if (S_ISDIR(dst_st->st_mode)) {
++      xerrno = EPERM;
++
++      pr_log_debug(DEBUG4, "Cannot rename path '%s' across a filesystem mount "
++        "point into directory '%s'", src_path, dst_path);
++
++      pr_response_add_err(R_550, _("%s: %s"), cmd->arg, strerror(xerrno));
++      pr_cmd_set_errno(cmd, xerrno);
++      errno = xerrno;
++
++      return -1;
++    }
++
++    res = pr_fsio_unlink(dst_path);
++    if (res < 0) {
++      xerrno = errno;
++
++      pr_log_debug(DEBUG4, "Unable to delete '%s' for cross filesystem mount "
++        "point rename: %s", dst_path, strerror(xerrno));
++
++      pr_response_add_err(R_550, _("%s: %s"), cmd->arg, strerror(xerrno));
++      pr_cmd_set_errno(cmd, xerrno);
++      errno = xerrno;
++
++      return -1;
++    }
++
++    dst_st = NULL;
++  }
++
++  /* At this point, we know that the source path exists, and is a regular file
++   * or symlink.  And we know that the destination path does not exist.
++   */
++
++  if (S_ISREG(src_st.st_mode)) {
++    res = pr_fs_copy_file(src_path, dst_path);
++    xerrno = errno;
++
++  } else if (S_ISLNK(src_st.st_mode)) {
++    res = pr_fs_copy_symlink(src_path, dst_path, 0);
++    xerrno = errno;
++  }
++
++  if (res < 0) {
++    (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
++      "error copying '%s' to '%s': %s", (char *) cmd->argv[0], session.user,
++      pr_uid2str(cmd->tmp_pool, session.uid),
++      pr_gid2str(cmd->tmp_pool, session.gid), src_path, dst_path,
++      strerror(xerrno));
++
++    pr_response_add_err(R_550, _("Rename %s: %s"), cmd->arg,
++      strerror(xerrno));
++    pr_cmd_set_errno(cmd, xerrno);
++    errno = xerrno;
++
++    return -1;
++  }
++
++  /* Once copied, unlink the original file. */
++  res = pr_fsio_unlink(src_path);
++  if (res < 0) {
++    pr_log_debug(DEBUG0, "error deleting '%s': %s", src_path, strerror(errno));
++  }
++
++  return 0;
++}
++
+@@ -6138 +6257 @@ MODRET core_rnto(cmd_rec *cmd) {
+-  int res;
++  int res, xerrno, dst_exists = FALSE;
+@@ -6162 +6281 @@ MODRET core_rnto(cmd_rec *cmd) {
+-    int xerrno = errno;
++    xerrno = errno;
+@@ -6202,2 +6320,0 @@ MODRET core_rnto(cmd_rec *cmd) {
+-  allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE);
+-
+@@ -6206,0 +6324,2 @@ MODRET core_rnto(cmd_rec *cmd) {
++  allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE);
++
+@@ -6208,4 +6327,3 @@ MODRET core_rnto(cmd_rec *cmd) {
+-  if ((!allow_overwrite || *allow_overwrite == FALSE) &&
+-      pr_fsio_stat(path, &st) == 0) {
+-    pr_log_debug(DEBUG6, "AllowOverwrite denied permission for %s", path);
+-    pr_response_add_err(R_550, _("%s: Rename permission denied"), cmd->arg);
++  res = pr_fsio_lstat(path, &st);
++  if (res == 0) {
++    dst_exists = TRUE;
+@@ -6213,3 +6331,9 @@ MODRET core_rnto(cmd_rec *cmd) {
+-    pr_cmd_set_errno(cmd, EACCES);
+-    errno = EACCES;
+-    return PR_ERROR(cmd);
++    if (allow_overwrite == NULL ||
++        *allow_overwrite == FALSE) {
++      pr_log_debug(DEBUG6, "AllowOverwrite denied permission for %s", path);
++      pr_response_add_err(R_550, _("%s: Rename permission denied"), cmd->arg);
++
++      pr_cmd_set_errno(cmd, EACCES);
++      errno = EACCES;
++      return PR_ERROR(cmd);
++    }
+@@ -6218 +6342 @@ MODRET core_rnto(cmd_rec *cmd) {
+-  if (!path ||
++  if (path == NULL ||
+@@ -6231 +6355 @@ MODRET core_rnto(cmd_rec *cmd) {
+-    int xerrno = errno;
++    xerrno = errno;
+@@ -6298,14 +6422,2 @@ MODRET core_rnto(cmd_rec *cmd) {
+-    if (pr_fs_copy_file(session.xfer.path, path) < 0) {
+-      xerrno = errno;
+-
+-      (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
+-        "error copying '%s' to '%s': %s", (char *) cmd->argv[0], session.user,
+-        pr_uid2str(cmd->tmp_pool, session.uid),
+-        pr_gid2str(cmd->tmp_pool, session.gid), session.xfer.path, path,
+-        strerror(xerrno));
+-
+-      pr_response_add_err(R_550, _("Rename %s: %s"), cmd->arg,
+-        strerror(xerrno));
+-
+-      pr_cmd_set_errno(cmd, xerrno);
+-      errno = xerrno;
++    if (handle_cross_mount_rename(cmd, session.xfer.path, path,
++        dst_exists == TRUE ? &st : NULL) < 0) {
+@@ -6314,20 +6425,0 @@ MODRET core_rnto(cmd_rec *cmd) {
+-
+-    /* Once copied, unlink the original file. */
+-    res = pr_fsio_unlink_with_error(cmd->pool, session.xfer.path, &err);
+-    if (res < 0) {
+-      xerrno = errno;
+-
+-      pr_error_set_where(err, &core_module, __FILE__, __LINE__ - 4);
+-      pr_error_set_why(err, pstrcat(cmd->pool, "delete file '",
+-        session.xfer.path, "'", NULL));
+-
+-      if (err != NULL) {
+-        pr_log_debug(DEBUG0, "%s", pr_error_strerror(err, 0));
+-        pr_error_destroy(err);
+-        err = NULL;
+-
+-      } else {
+-        pr_log_debug(DEBUG0, "error deleting '%s': %s", session.xfer.path,
+-          strerror(xerrno));
+-      }
+-    }
+diff --git a/modules/mod_xfer.c b/modules/mod_xfer.c
+index f93922c2b594a795075ee476191c027a17219c5e..68938514965245d7ed234bb8720152edbe358494 100644
+--- a/modules/mod_xfer.c
++++ b/modules/mod_xfer.c
+@@ -2449,0 +2450 @@ static int parse_offset(char *str, off_t *num) {
++  int res = 0;
+@@ -2450,0 +2452,2 @@ static int parse_offset(char *str, off_t *num) {
++  unsigned long long n;
++  off_t sz;
+@@ -2461,2 +2464,8 @@ static int parse_offset(char *str, off_t *num) {
+-#ifdef HAVE_STRTOULL
+-  *num = strtoull(ptr, &tmp, 10);
++  errno = 0;
++
++#if defined(HAVE_STRTOULL)
++  n = strtoull(ptr, &tmp, 10);
++  if (n == ULLONG_MAX &&
++      errno == ERANGE) {
++    res = -1;
++  }
+@@ -2464 +2473,5 @@ static int parse_offset(char *str, off_t *num) {
+-  *num = strtoul(ptr, &tmp, 10);
++  n = strtoul(ptr, &tmp, 10);
++  if (n == ULONG_MAX &&
++      errno == ERANGE) {
++    res = -1;
++  }
+@@ -2466,0 +2480,7 @@ static int parse_offset(char *str, off_t *num) {
++  if (res < 0) {
++    pr_trace_msg(trace_channel, 3,
++      "client-sent offset '%s' conversion error: %s", ptr, strerror(errno));
++    errno = EINVAL;
++    return -1;
++  }
++
+@@ -2471,0 +2492,12 @@ static int parse_offset(char *str, off_t *num) {
++  /* Watch for cases where the sent file offset might overflow our size
++   * type (Issue #2286).
++   */
++  sz = n;
++  if (sz < 0 || (unsigned long long) sz != n) {
++    pr_trace_msg(trace_channel, 3,
++      "client-sent offset '%s' out of range, rejecting", ptr);
++    errno = EINVAL;
++    return -1;
++  }
++
++  *num = sz;
+diff --git a/src/fsio.c b/src/fsio.c
+index 999aebd0a8bb182ac2cd4159b3d6e575e8a4b2b2..90c4906b486ff669975bfce585571200491fa7de 100644
+--- a/src/fsio.c
++++ b/src/fsio.c
+@@ -2237,0 +2238,39 @@ int pr_fs_copy_file(const char *src, const char *dst) {
++int pr_fs_copy_symlink(const char *src, const char *dst, int flags) {
++  char link_path[PR_TUNABLE_BUFFER_SIZE];
++  int len;
++
++  /* Currently unused. */
++  (void) flags;
++
++  if (src == NULL ||
++      dst == NULL) {
++    errno = EINVAL;
++    return -1;
++  }
++
++  memset(link_path, '\0', sizeof(link_path));
++  len = pr_fsio_readlink(src, link_path, sizeof(link_path)-1);
++  if (len < 0) {
++    int xerrno = errno;
++
++    pr_log_pri(PR_LOG_WARNING, "error reading symlink '%s': %s", src,
++      strerror(xerrno));
++
++    errno = xerrno;
++    return -1;
++  }
++  link_path[len] = '\0';
++
++  if (pr_fsio_symlink(link_path, dst) < 0) {
++    int xerrno = errno;
++
++    pr_log_pri(PR_LOG_WARNING, "error symlinking '%s' to '%s': %s", link_path,
++      dst, strerror(xerrno));
++
++    errno = xerrno;
++    return -1;
++  }
++
++  return 0;
++}
++
diff --git a/proftpd-rewrite-utf8trans.patch b/proftpd-rewrite-utf8trans.patch
new file mode 100644
index 0000000..1f0f0f3
--- /dev/null
+++ b/proftpd-rewrite-utf8trans.patch
@@ -0,0 +1,47 @@
+# Reject oversized client keys before the utf8trans RewriteMap overflows its
+# static buffers. Upstream: https://github.com/proftpd/proftpd/commit/4bafd1e9861506e77b9ea3ed277b58e60eacfe4d
+# Disclosure: https://github.com/abraxas/proftpd-rewrite-utf8trans
+--- a/contrib/mod_rewrite.c
++++ b/contrib/mod_rewrite.c
+@@ -1659,9 +1659,10 @@ static char rewrite_hex_to_char(const char *what) {
+  */
+ static int rewrite_utf8_to_ucs4(unsigned long *ucs4_buf,
+     size_t utf8_len, unsigned char *utf8_buf) {
+-  const unsigned char *utf8_endbuf = utf8_buf + utf8_len;
++  const unsigned char *utf8_endbuf;
+   int ucs_len = 0;
+ 
++  utf8_endbuf = utf8_buf + utf8_len;
+   while (utf8_buf != utf8_endbuf) {
+     pr_signals_handle();
+ 
+@@ -2035,13 +2036,27 @@ static int rewrite_write_fifo(int fd, char *buf, size_t buflen) {
+ }
+ 
+ static char *rewrite_map_int_utf8trans(pool *map_pool, char *key) {
+-  int ucs4strlen = 0;
+   static unsigned char utf8_val[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
+   static unsigned long ucs4_longs[PR_TUNABLE_BUFFER_SIZE] = {0L};
++  int ucs4strlen = 0;
++  size_t key_len = 0;
+ 
+   /* If the key is NULL or empty, do nothing. */
++  if (key != NULL) {
++    key_len = strlen(key);
++  }
++
+   if (key == NULL ||
+-      strlen(key) == 0) {
++      key_len == 0) {
++    return NULL;
++  }
++
++  /* If the key length exceeds our buffers, reject it. */
++  if (key_len >= sizeof(utf8_val)) {
++    rewrite_log("utf8trans: key '%.100s...' length (%lu bytes) exceeds "
++      "maximum allowed length (%lu bytes), rejecting", key,
++      (unsigned long) key_len, (unsigned long) sizeof(utf8_val));
++    errno = EPERM;
+     return NULL;
+   }
+ 
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/proftpd.git/commitdiff/3a64ce33644a3d0a1ef10c5aa8278fd8b3307446



More information about the pld-cvs-commit mailing list