From 26861c7519bb141345c1e942e0d6be8534227b11 Mon Sep 17 00:00:00 2001 From: mgb01105731 Date: Mon, 12 Jun 2023 13:35:54 +0800 Subject: [PATCH] init upstream from version 3.0.5 --- ...-provided-script-to-locate-libraries.patch | 27 + 0001-Fix-timestamp-handling-in-MDTM.patch | 151 ++++++ ...ve-closing-standard-FDs-after-listen.patch | 46 ++ ...out-the-ftp_home_dir-SELinux-boolean.patch | 25 + ...e-until-it-succeeds-if-it-fails-with.patch | 108 ++++ ...rted-only-after-record-insertion-rem.patch | 53 ++ 0002-Drop-an-unused-global-variable.patch | 56 ++ 0002-Enable-build-with-SSL.patch | 25 + 0002-Prevent-recursion-in-bug.patch | 107 ++++ ...at-pututxline-if-it-fails-with-EINTR.patch | 105 ++++ 0003-Enable-build-with-TCP-Wrapper.patch | 25 + ...-dir-for-config-files-instead-of-etc.patch | 483 ++++++++++++++++++ ...en-calling-PAM-authentication-module.patch | 75 +++ ...err-before-listening-for-incoming-co.patch | 35 ++ 0007-Make-filename-filters-smarter.patch | 102 ++++ 0008-Write-denied-logins-into-the-log.patch | 147 ++++++ ...itespaces-when-reading-configuration.patch | 99 ++++ 0010-Improve-daemonizing.patch | 209 ++++++++ ...-Fix-listing-with-more-than-one-star.patch | 38 ++ ...lace-syscall-__NR_clone-.-with-clone.patch | 35 ++ 0013-Extend-man-pages-with-systemd-info.patch | 86 ++++ ...dd-support-for-square-brackets-in-ls.patch | 277 ++++++++++ 0015-Listen-on-IPv6-by-default.patch | 55 ++ ...e-VSFTP_AS_LIMIT-from-200UL-to-400UL.patch | 27 + ...-an-issue-with-timestamps-during-DST.patch | 161 ++++++ ...he-default-log-file-in-configuration.patch | 43 ++ ...troduce-reverse_lookup_enable-option.patch | 109 ++++ ...d-int-for-uid-and-gid-representation.patch | 250 +++++++++ ...-support-for-DHE-based-cipher-suites.patch | 164 ++++++ ...upport-for-EDDHE-based-cipher-suites.patch | 128 +++++ ...n-for-isolate_-options.-Correct-defa.patch | 63 +++ 0024-Introduce-new-return-value-450.patch | 77 +++ 0025-Improve-local_max_rate-option.patch | 90 ++++ 0026-Prevent-hanging-in-SIGCHLD-handler.patch | 81 +++ 0027-Delete-files-when-upload-fails.patch | 138 +++++ 0028-Fix-man-page-rendering.patch | 26 + 0029-Fix-segfault-in-config-file-parser.patch | 25 + ...g-into-syslog-when-enabled-in-config.patch | 25 + ...on-mark-wildcard-withing-a-file-name.patch | 28 + ...errors-from-nfs-with-quota-to-client.patch | 147 ++++++ ...omp-sandbox-because-it-is-too-strict.patch | 25 + 0036-Redefine-VSFTP_COMMAND_FD-to-1.patch | 29 ++ ...ationship-of-text_userdb_names-and-c.patch | 29 ++ ...low_writeable_chroot-in-the-man-page.patch | 32 ++ ...tation-of-ASCII-mode-in-the-man-page.patch | 34 ++ 0040-Use-system-wide-crypto-policy.patch | 27 + ...-default-for-ssl_ciphers-in-the-man-.patch | 31 ++ ...nymous_enable-in-default-config-file.patch | 26 + ...on-of-ascii_-options-behaviour-in-ma.patch | 52 ++ ...r-to-the-man-page-regarding-the-asci.patch | 27 + 0047-Disable-tcp_wrappers-support.patch | 49 ++ ...e-of-strict_ssl_read_eof-in-man-page.patch | 29 ++ ...-generation-algorithm-for-STOU-comma.patch | 322 ++++++++++++ 0050-Don-t-link-with-libnsl.patch | 27 + ...ation-of-better_stou-in-the-man-page.patch | 30 ++ 0052-Fix-rDNS-with-IPv6.patch | 195 +++++++ 0053-Always-do-chdir-after-chroot.patch | 32 ++ ...imeo-Check-return-value-of-setsockop.patch | 33 ++ ...tz-Check-the-return-value-of-syscall.patch | 108 ++++ 0056-Log-die-calls-to-syslog.patch | 206 ++++++++ ...ssage-when-max-number-of-bind-attemp.patch | 27 + ...e-max-number-of-bind-retries-tunable.patch | 103 ++++ ...when-running-in-a-container-as-PID-1.patch | 58 +++ fix-str_open.patch | 27 + ...-add-option-for-tlsv1.3-ciphersuites.patch | 79 +++ ...wc_logs-replace_unprintable_with_hex.patch | 215 ++++++++ ...replace-deprecated-openssl-functions.patch | 70 +++ ...5-replace-old-network-addr-functions.patch | 139 +++++ vsftpd-3.0.5-use-old-tlsv-options.patch | 15 + vsftpd-3.0.5.tar.gz | Bin 0 -> 197778 bytes vsftpd-generator | 15 + vsftpd.ftpusers | 15 + vsftpd.pam | 8 + vsftpd.service | 10 + vsftpd.spec | 183 +++++++ vsftpd.target | 6 + vsftpd.user_list | 20 + vsftpd.xinetd | 14 + vsftpd@.service | 11 + vsftpd_conf_migrate.sh | 13 + 80 files changed, 6322 insertions(+) create mode 100644 0001-Don-t-use-the-provided-script-to-locate-libraries.patch create mode 100644 0001-Fix-timestamp-handling-in-MDTM.patch create mode 100644 0001-Move-closing-standard-FDs-after-listen.patch create mode 100644 0001-Remove-a-hint-about-the-ftp_home_dir-SELinux-boolean.patch create mode 100644 0001-Repeat-pututxline-until-it-succeeds-if-it-fails-with.patch create mode 100644 0001-Set-s_uwtmp_inserted-only-after-record-insertion-rem.patch create mode 100644 0002-Drop-an-unused-global-variable.patch create mode 100644 0002-Enable-build-with-SSL.patch create mode 100644 0002-Prevent-recursion-in-bug.patch create mode 100644 0002-Repeat-pututxline-if-it-fails-with-EINTR.patch create mode 100644 0003-Enable-build-with-TCP-Wrapper.patch create mode 100644 0004-Use-etc-vsftpd-dir-for-config-files-instead-of-etc.patch create mode 100644 0005-Use-hostname-when-calling-PAM-authentication-module.patch create mode 100644 0006-Close-stdin-out-err-before-listening-for-incoming-co.patch create mode 100644 0007-Make-filename-filters-smarter.patch create mode 100644 0008-Write-denied-logins-into-the-log.patch create mode 100644 0009-Trim-whitespaces-when-reading-configuration.patch create mode 100644 0010-Improve-daemonizing.patch create mode 100644 0011-Fix-listing-with-more-than-one-star.patch create mode 100644 0012-Replace-syscall-__NR_clone-.-with-clone.patch create mode 100644 0013-Extend-man-pages-with-systemd-info.patch create mode 100644 0014-Add-support-for-square-brackets-in-ls.patch create mode 100644 0015-Listen-on-IPv6-by-default.patch create mode 100644 0016-Increase-VSFTP_AS_LIMIT-from-200UL-to-400UL.patch create mode 100644 0017-Fix-an-issue-with-timestamps-during-DST.patch create mode 100644 0018-Change-the-default-log-file-in-configuration.patch create mode 100644 0019-Introduce-reverse_lookup_enable-option.patch create mode 100644 0020-Use-unsigned-int-for-uid-and-gid-representation.patch create mode 100644 0021-Introduce-support-for-DHE-based-cipher-suites.patch create mode 100644 0022-Introduce-support-for-EDDHE-based-cipher-suites.patch create mode 100644 0023-Add-documentation-for-isolate_-options.-Correct-defa.patch create mode 100644 0024-Introduce-new-return-value-450.patch create mode 100644 0025-Improve-local_max_rate-option.patch create mode 100644 0026-Prevent-hanging-in-SIGCHLD-handler.patch create mode 100644 0027-Delete-files-when-upload-fails.patch create mode 100644 0028-Fix-man-page-rendering.patch create mode 100644 0029-Fix-segfault-in-config-file-parser.patch create mode 100644 0030-Fix-logging-into-syslog-when-enabled-in-config.patch create mode 100644 0031-Fix-question-mark-wildcard-withing-a-file-name.patch create mode 100644 0032-Propagate-errors-from-nfs-with-quota-to-client.patch create mode 100644 0034-Turn-off-seccomp-sandbox-because-it-is-too-strict.patch create mode 100644 0036-Redefine-VSFTP_COMMAND_FD-to-1.patch create mode 100644 0037-Document-the-relationship-of-text_userdb_names-and-c.patch create mode 100644 0038-Document-allow_writeable_chroot-in-the-man-page.patch create mode 100644 0039-Improve-documentation-of-ASCII-mode-in-the-man-page.patch create mode 100644 0040-Use-system-wide-crypto-policy.patch create mode 100644 0041-Document-the-new-default-for-ssl_ciphers-in-the-man-.patch create mode 100644 0044-Disable-anonymous_enable-in-default-config-file.patch create mode 100644 0045-Expand-explanation-of-ascii_-options-behaviour-in-ma.patch create mode 100644 0046-vsftpd.conf-Refer-to-the-man-page-regarding-the-asci.patch create mode 100644 0047-Disable-tcp_wrappers-support.patch create mode 100644 0048-Fix-default-value-of-strict_ssl_read_eof-in-man-page.patch create mode 100644 0049-Add-new-filename-generation-algorithm-for-STOU-comma.patch create mode 100644 0050-Don-t-link-with-libnsl.patch create mode 100644 0051-Improve-documentation-of-better_stou-in-the-man-page.patch create mode 100644 0052-Fix-rDNS-with-IPv6.patch create mode 100644 0053-Always-do-chdir-after-chroot.patch create mode 100644 0054-vsf_sysutil_rcvtimeo-Check-return-value-of-setsockop.patch create mode 100644 0055-vsf_sysutil_get_tz-Check-the-return-value-of-syscall.patch create mode 100644 0056-Log-die-calls-to-syslog.patch create mode 100644 0057-Improve-error-message-when-max-number-of-bind-attemp.patch create mode 100644 0058-Make-the-max-number-of-bind-retries-tunable.patch create mode 100644 0059-Fix-SEGFAULT-when-running-in-a-container-as-PID-1.patch create mode 100644 fix-str_open.patch create mode 100644 vsftpd-3.0.5-add-option-for-tlsv1.3-ciphersuites.patch create mode 100644 vsftpd-3.0.5-enable_wc_logs-replace_unprintable_with_hex.patch create mode 100644 vsftpd-3.0.5-replace-deprecated-openssl-functions.patch create mode 100644 vsftpd-3.0.5-replace-old-network-addr-functions.patch create mode 100644 vsftpd-3.0.5-use-old-tlsv-options.patch create mode 100644 vsftpd-3.0.5.tar.gz create mode 100755 vsftpd-generator create mode 100644 vsftpd.ftpusers create mode 100644 vsftpd.pam create mode 100644 vsftpd.service create mode 100644 vsftpd.spec create mode 100644 vsftpd.target create mode 100644 vsftpd.user_list create mode 100644 vsftpd.xinetd create mode 100644 vsftpd@.service create mode 100755 vsftpd_conf_migrate.sh diff --git a/0001-Don-t-use-the-provided-script-to-locate-libraries.patch b/0001-Don-t-use-the-provided-script-to-locate-libraries.patch new file mode 100644 index 0000000..f4a67e3 --- /dev/null +++ b/0001-Don-t-use-the-provided-script-to-locate-libraries.patch @@ -0,0 +1,27 @@ +From 7bd573d76e9c1996ad5a96f0289731a253a24301 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Tue, 6 Sep 2016 13:35:51 +0200 +Subject: [PATCH 01/59] Don't use the provided script to locate libraries. + +This branch is Fedora (RHEL) specific, so we know what +libraries we have and want to use. +--- + Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/Makefile b/Makefile +index c63ed1b..98118dc 100644 +--- a/Makefile ++++ b/Makefile +@@ -8,7 +8,7 @@ CFLAGS = -O2 -fPIE -fstack-protector --param=ssp-buffer-size=4 \ + -D_FORTIFY_SOURCE=2 \ + #-pedantic -Wconversion + +-LIBS = `./vsf_findlibs.sh` ++LIBS = -lwrap -lnsl -lpam -lcap -ldl -lcrypto + LINK = -Wl,-s + LDFLAGS = -fPIE -pie -Wl,-z,relro -Wl,-z,now + +-- +2.14.4 + diff --git a/0001-Fix-timestamp-handling-in-MDTM.patch b/0001-Fix-timestamp-handling-in-MDTM.patch new file mode 100644 index 0000000..3975bf3 --- /dev/null +++ b/0001-Fix-timestamp-handling-in-MDTM.patch @@ -0,0 +1,151 @@ +From 6a4dc470e569df38b8a7ea09ee6aace3c73b7353 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Wed, 28 Mar 2018 09:06:34 +0200 +Subject: [PATCH 1/2] Fix timestamp handling in MDTM + +There were two problems with the timestamp handling with MDTM: + +1. In vsf_sysutil_parse_time(), the `the_time.tm_isdst` attribute was + always set to 0, regardless of whether DST (daylight saving time) + is active on the given date or not. + + This made glibc shift the timestamp when DST was in fact active on + the given date, in an attempt to correct the discrepancy between + the given timestamp and the `tm_isdst` attribute. The shifting + produced incorrect results however. + + We fix this by setting `tm_isdst` to -1 to let glibc decide if DST + is active or not at the time of the timestamp. glibc won't touch + the timestamp then. + +2. vsftpd used to record the offset from UTC of the current timezone + in the global variable `s_timezone`. This variable was then + subtracted from the variable `the_time` in vsf_sysutil_setmodtime() + when the config option use_localtime=NO was set. This was done to + compensate for the fact that mktime(), used in + vsf_sysutil_parse_time(), expects a timestamp expressed as local + time, whereas vsftpd is dealing with universal time. + + However, this did not work in the case when the offset stored in + `s_timezone` did not match the timezone of the timestamp given to + mktime() - this happens when DST is active at the current time, but + DST is not active at the time of the timestamp, or vice versa. + + We fix this by subtracting the real timezone offset directly in + vsf_sysutil_parse_time(). + + Note that the `tm_gmtoff` attribute, used in this fix, is a + BSD/glic extension. However, using `tm_gmtoff` seems like the + simplest solution and we need to make this work only with glibc + anyway. + +The fix was tested in the following way. We checked that the timestamp +given to the MDTM command when setting modification time exactly +matches the timestamp received as response from MDTM when reading back +the modification time. Additionally, we checked that the modification +time was set correctly on the given file on disk. + +These two checks were performed under various conditions - all the +combinations of DST/non-DST system time, DST/non-DST modification +time, use_localtime=YES/NO. + +Note that (I think) this will still not work if the rules for when DST +is active change. For example, if DST is ever completely cancelled in +the Europe/Prague timezone, and vsftpd is dealing with a timestamp +from a time when DST was active, it will produce incorrect results. I +think we would need the full zone file to fix this, but the zone file +is hard to provide when we're chroot-ed. + +Resolves: rhbz#1567855 +--- + postlogin.c | 5 +++-- + sysutil.c | 17 ++++++++++------- + sysutil.h | 4 ++-- + 3 files changed, 15 insertions(+), 11 deletions(-) + +diff --git a/postlogin.c b/postlogin.c +index 7c749ef..8a3d9d2 100644 +--- a/postlogin.c ++++ b/postlogin.c +@@ -1788,7 +1788,8 @@ handle_mdtm(struct vsf_session* p_sess) + if (do_write != 0) + { + str_split_char(&p_sess->ftp_arg_str, &s_filename_str, ' '); +- modtime = vsf_sysutil_parse_time(str_getbuf(&p_sess->ftp_arg_str)); ++ modtime = vsf_sysutil_parse_time( ++ str_getbuf(&p_sess->ftp_arg_str), tunable_use_localtime); + str_copy(&p_sess->ftp_arg_str, &s_filename_str); + } + resolve_tilde(&p_sess->ftp_arg_str, p_sess); +@@ -1809,7 +1810,7 @@ handle_mdtm(struct vsf_session* p_sess) + else + { + retval = vsf_sysutil_setmodtime( +- str_getbuf(&p_sess->ftp_arg_str), modtime, tunable_use_localtime); ++ str_getbuf(&p_sess->ftp_arg_str), modtime); + if (retval != 0) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, +diff --git a/sysutil.c b/sysutil.c +index e847650..66d4c5e 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -2819,11 +2819,13 @@ vsf_sysutil_syslog(const char* p_text, int severe) + } + + long +-vsf_sysutil_parse_time(const char* p_text) ++vsf_sysutil_parse_time(const char* p_text, int is_localtime) + { ++ long res; + struct tm the_time; + unsigned int len = vsf_sysutil_strlen(p_text); + vsf_sysutil_memclr(&the_time, sizeof(the_time)); ++ the_time.tm_isdst = -1; + if (len >= 8) + { + char yr[5]; +@@ -2848,17 +2850,18 @@ vsf_sysutil_parse_time(const char* p_text) + the_time.tm_min = vsf_sysutil_atoi(mins); + the_time.tm_sec = vsf_sysutil_atoi(sec); + } +- return mktime(&the_time); ++ res = mktime(&the_time); ++ if (!is_localtime) ++ { ++ res += the_time.tm_gmtoff; ++ } ++ return res; + } + + int +-vsf_sysutil_setmodtime(const char* p_file, long the_time, int is_localtime) ++vsf_sysutil_setmodtime(const char* p_file, long the_time) + { + struct utimbuf new_times; +- if (!is_localtime) +- { +- the_time -= s_timezone; +- } + vsf_sysutil_memclr(&new_times, sizeof(new_times)); + new_times.actime = the_time; + new_times.modtime = the_time; +diff --git a/sysutil.h b/sysutil.h +index 7a59f13..b90f6ca 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -349,9 +349,9 @@ void vsf_sysutil_chroot(const char* p_root_path); + */ + long vsf_sysutil_get_time_sec(void); + long vsf_sysutil_get_time_usec(void); +-long vsf_sysutil_parse_time(const char* p_text); ++long vsf_sysutil_parse_time(const char* p_text, int is_localtime); + void vsf_sysutil_sleep(double seconds); +-int vsf_sysutil_setmodtime(const char* p_file, long the_time, int is_localtime); ++int vsf_sysutil_setmodtime(const char* p_file, long the_time); + + /* Limits */ + void vsf_sysutil_set_address_space_limit(unsigned long bytes); +-- +2.24.1 + diff --git a/0001-Move-closing-standard-FDs-after-listen.patch b/0001-Move-closing-standard-FDs-after-listen.patch new file mode 100644 index 0000000..12511e7 --- /dev/null +++ b/0001-Move-closing-standard-FDs-after-listen.patch @@ -0,0 +1,46 @@ +From 40fea4552377504ce69935149e64e39a595f4600 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Sat, 3 Aug 2019 17:50:14 +0200 +Subject: [PATCH 1/2] Move closing standard FDs after listen() + +The vsf_sysutil_close() calls need to be moved a bit further so that +die() works properly in case listen() fails. + +I see no reason the calls should be placed before listen() +specifically, as they are now. My guess is that the author who added +the calls thought that listen() is a blocking call, which is not the +case. The only thing we need to satisfy is that close() is called +before accept, because that is a blocking call. That's all that is +needed to fix the bug that was fixed by adding the close() calls. + +Resolves: rhbz#1666380 +--- + standalone.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/standalone.c b/standalone.c +index 3f35e9e..b358ca1 100644 +--- a/standalone.c ++++ b/standalone.c +@@ -152,15 +152,15 @@ vsf_standalone_main(void) + vsf_sysutil_kill(vsf_sysutil_getppid(), kVSFSysUtilSigUSR1); + } + } +- vsf_sysutil_close(0); +- vsf_sysutil_close(1); +- vsf_sysutil_close(2); + retval = vsf_sysutil_listen(listen_sock, VSFTP_LISTEN_BACKLOG); + if (vsf_sysutil_retval_is_error(retval)) + { + die("could not listen"); + } + vsf_sysutil_sockaddr_alloc(&p_accept_addr); ++ vsf_sysutil_close(0); ++ vsf_sysutil_close(1); ++ vsf_sysutil_close(2); + while (1) + { + struct vsf_client_launch child_info; +-- +2.20.1 + diff --git a/0001-Remove-a-hint-about-the-ftp_home_dir-SELinux-boolean.patch b/0001-Remove-a-hint-about-the-ftp_home_dir-SELinux-boolean.patch new file mode 100644 index 0000000..88640ac --- /dev/null +++ b/0001-Remove-a-hint-about-the-ftp_home_dir-SELinux-boolean.patch @@ -0,0 +1,25 @@ +From ab797dcffc855b05c9e7c8db4e5be2fc7510831b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 17 Mar 2020 12:57:36 +0100 +Subject: [PATCH] Remove a hint about the ftp_home_dir SELinux boolean + +The boolean has been removed from SELinux. +--- + vsftpd.conf | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/vsftpd.conf b/vsftpd.conf +index 6b8eebb..ea20a72 100644 +--- a/vsftpd.conf ++++ b/vsftpd.conf +@@ -12,7 +12,6 @@ + anonymous_enable=NO + # + # Uncomment this to allow local users to log in. +-# When SELinux is enforcing check for SE bool ftp_home_dir + local_enable=YES + # + # Uncomment this to enable any form of FTP write command. +-- +2.25.1 + diff --git a/0001-Repeat-pututxline-until-it-succeeds-if-it-fails-with.patch b/0001-Repeat-pututxline-until-it-succeeds-if-it-fails-with.patch new file mode 100644 index 0000000..63d555e --- /dev/null +++ b/0001-Repeat-pututxline-until-it-succeeds-if-it-fails-with.patch @@ -0,0 +1,108 @@ +From 7957425ef5ab365fc96ea0615f99705581c6dbd8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Mon, 12 Aug 2019 18:15:36 +0200 +Subject: [PATCH] Repeat pututxline() until it succeeds if it fails with EINTR + +Since the pututxline() bug rhbz#1749439 is now fixed in glibc in +Fedora and RHEL-8, we can implement a complete solution for the stale +utmp entries issue originally reported as rhbz#1688848. + +This patch is a followup to commit 896b3694ca062d7. + +Resolves: rhbz#1688852 +Resolves: rhbz#1737433 +--- + sysdeputil.c | 53 +++++++++++++--------------------------------------- + 1 file changed, 13 insertions(+), 40 deletions(-) + +diff --git a/sysdeputil.c b/sysdeputil.c +index 4fbcca7..75be680 100644 +--- a/sysdeputil.c ++++ b/sysdeputil.c +@@ -1203,7 +1203,7 @@ void + vsf_insert_uwtmp(const struct mystr* p_user_str, + const struct mystr* p_host_str) + { +- int attempts; ++ struct utmpx* p_res; + + if (sizeof(s_utent.ut_line) < 16) + { +@@ -1233,34 +1233,21 @@ vsf_insert_uwtmp(const struct mystr* p_user_str, + vsf_sysutil_strcpy(s_utent.ut_host, str_getbuf(p_host_str), + sizeof(s_utent.ut_host)); + s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec(); +- for (attempts = 2; attempts > 0; --attempts) ++ setutxent(); ++ do + { +- struct utmpx* p_res; +- setutxent(); + p_res = pututxline(&s_utent); + /* For now we'll ignore errors other than EINTR and EAGAIN */ +- if (p_res != NULL || (errno != EINTR && errno != EAGAIN)) +- { +- break; +- } +- } +- if (attempts == 0) +- { +- /* This makes us skip pututxline() in vsf_remove_uwtmp() */ +- s_uwtmp_inserted = -1; +- } +- else +- { +- s_uwtmp_inserted = 1; +- endutxent(); +- } ++ } while (p_res == NULL && (errno == EINTR || errno == EAGAIN)); ++ s_uwtmp_inserted = 1; ++ endutxent(); + updwtmpx(WTMPX_FILE, &s_utent); + } + + void + vsf_remove_uwtmp(void) + { +- int attempts; ++ struct utmpx* p_res; + + if (!s_uwtmp_inserted) + { +@@ -1270,27 +1257,13 @@ vsf_remove_uwtmp(void) + vsf_sysutil_memclr(s_utent.ut_user, sizeof(s_utent.ut_user)); + vsf_sysutil_memclr(s_utent.ut_host, sizeof(s_utent.ut_host)); + s_utent.ut_tv.tv_sec = 0; +- if (s_uwtmp_inserted == 1) ++ setutxent(); ++ do + { +- for (attempts = 2; attempts > 0; --attempts) +- { +- struct utmpx* p_res; +- setutxent(); +- p_res = pututxline(&s_utent); +- /* For now we'll ignore errors other than EINTR and EAGAIN */ +- if (p_res != NULL || (errno != EINTR && errno != EAGAIN)) +- { +- break; +- } +- } +- if (attempts != 0) +- { +- endutxent(); +- } +- } +- /* Set s_uwtmp_inserted to 0 regardless of the result of +- * pututxline() to make sure we won't run this function twice. +- */ ++ p_res = pututxline(&s_utent); ++ /* For now we'll ignore errors other than EINTR and EAGAIN */ ++ } while (p_res == NULL && (errno == EINTR || errno == EAGAIN)); ++ endutxent(); + s_uwtmp_inserted = 0; + s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec(); + updwtmpx(WTMPX_FILE, &s_utent); +-- +2.20.1 + diff --git a/0001-Set-s_uwtmp_inserted-only-after-record-insertion-rem.patch b/0001-Set-s_uwtmp_inserted-only-after-record-insertion-rem.patch new file mode 100644 index 0000000..00bf82c --- /dev/null +++ b/0001-Set-s_uwtmp_inserted-only-after-record-insertion-rem.patch @@ -0,0 +1,53 @@ +From 96698a525784ad91cb27b572dd5f871c183fdfa5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Sun, 28 Jul 2019 12:25:35 +0200 +Subject: [PATCH 1/2] Set s_uwtmp_inserted only after record insertion/removal + +pututxline() is the function that actually inserts the new record, so +setting 's_uwtmp_inserted' before calling pututxline() doesn't make +sense. + +We'll need this change for other fixes. +--- + sysdeputil.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/sysdeputil.c b/sysdeputil.c +index 4fe56c2..bd1e8c9 100644 +--- a/sysdeputil.c ++++ b/sysdeputil.c +@@ -1224,7 +1224,6 @@ vsf_insert_uwtmp(const struct mystr* p_user_str, + sizeof(s_utent.ut_line)); + str_free(&line_str); + } +- s_uwtmp_inserted = 1; + s_utent.ut_type = USER_PROCESS; + s_utent.ut_pid = vsf_sysutil_getpid(); + vsf_sysutil_strcpy(s_utent.ut_user, str_getbuf(p_user_str), +@@ -1235,6 +1234,7 @@ vsf_insert_uwtmp(const struct mystr* p_user_str, + setutxent(); + (void) pututxline(&s_utent); + endutxent(); ++ s_uwtmp_inserted = 1; + updwtmpx(WTMPX_FILE, &s_utent); + } + +@@ -1245,7 +1245,6 @@ vsf_remove_uwtmp(void) + { + return; + } +- s_uwtmp_inserted = 0; + s_utent.ut_type = DEAD_PROCESS; + vsf_sysutil_memclr(s_utent.ut_user, sizeof(s_utent.ut_user)); + vsf_sysutil_memclr(s_utent.ut_host, sizeof(s_utent.ut_host)); +@@ -1253,6 +1252,7 @@ vsf_remove_uwtmp(void) + setutxent(); + (void) pututxline(&s_utent); + endutxent(); ++ s_uwtmp_inserted = 0; + s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec(); + updwtmpx(WTMPX_FILE, &s_utent); + } +-- +2.20.1 + diff --git a/0002-Drop-an-unused-global-variable.patch b/0002-Drop-an-unused-global-variable.patch new file mode 100644 index 0000000..53af589 --- /dev/null +++ b/0002-Drop-an-unused-global-variable.patch @@ -0,0 +1,56 @@ +From d0045e35674d64d166d17c3c079ae03e8c2e6361 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Thu, 13 Feb 2020 17:29:06 +0100 +Subject: [PATCH 2/2] Drop an unused global variable + +The global variable `s_timezone` is not used anymore, so we can drop +it. +--- + sysutil.c | 17 +++-------------- + 1 file changed, 3 insertions(+), 14 deletions(-) + +diff --git a/sysutil.c b/sysutil.c +index 66d4c5e..0ccf551 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -72,8 +72,6 @@ static struct timeval s_current_time; + static int s_current_pid = -1; + /* Exit function */ + static exitfunc_t s_exit_func; +-/* Difference in timezone from GMT in seconds */ +-static long s_timezone; + + /* Our internal signal handling implementation details */ + static struct vsf_sysutil_sig_details +@@ -2661,7 +2659,6 @@ char* vsf_sysutil_get_tz() + void + vsf_sysutil_tzset(void) + { +- int retval; + char *tz=NULL, tzbuf[sizeof("+HHMM!")]; + time_t the_time = time(NULL); + struct tm* p_tm; +@@ -2681,17 +2678,9 @@ vsf_sysutil_tzset(void) + { + die("localtime"); + } +- retval = strftime(tzbuf, sizeof(tzbuf), "%z", p_tm); +- tzbuf[sizeof(tzbuf) - 1] = '\0'; +- if (retval == 5) +- { +- s_timezone = ((tzbuf[1] - '0') * 10 + (tzbuf[2] - '0')) * 60 * 60; +- s_timezone += ((tzbuf[3] - '0') * 10 + (tzbuf[4] - '0')) * 60; +- if (tzbuf[0] == '+') +- { +- s_timezone *= -1; +- } +- } ++ /* Not sure if the following call to strftime() has any desired side ++ effects, so I'm keeping it to be safe. */ ++ (void) strftime(tzbuf, sizeof(tzbuf), "%z", p_tm); + /* Call in to the time subsystem again now that TZ is set, trying to force + * caching of the actual zoneinfo for the timezone. + */ +-- +2.24.1 + diff --git a/0002-Enable-build-with-SSL.patch b/0002-Enable-build-with-SSL.patch new file mode 100644 index 0000000..e772099 --- /dev/null +++ b/0002-Enable-build-with-SSL.patch @@ -0,0 +1,25 @@ +From 6fe24bc56694808ac7f8038855883a971967f0fb Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Tue, 6 Sep 2016 13:40:53 +0200 +Subject: [PATCH 02/59] Enable build with SSL. + +--- + builddefs.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/builddefs.h b/builddefs.h +index e908352..63cc62b 100644 +--- a/builddefs.h ++++ b/builddefs.h +@@ -3,7 +3,7 @@ + + #undef VSF_BUILD_TCPWRAPPERS + #define VSF_BUILD_PAM +-#undef VSF_BUILD_SSL ++#define VSF_BUILD_SSL + + #endif /* VSF_BUILDDEFS_H */ + +-- +2.14.4 + diff --git a/0002-Prevent-recursion-in-bug.patch b/0002-Prevent-recursion-in-bug.patch new file mode 100644 index 0000000..061fd1e --- /dev/null +++ b/0002-Prevent-recursion-in-bug.patch @@ -0,0 +1,107 @@ +From e679a3ce0f2cf1558da31e0bccd9e2398b89c7e9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 30 Jul 2019 16:07:01 +0200 +Subject: [PATCH 2/2] Prevent recursion in bug() + +Resolves: rhbz#1666380 +--- + sysutil.c | 35 +++++++++++++++++++++++++++++++---- + sysutil.h | 1 + + utility.c | 12 +++++++----- + 3 files changed, 39 insertions(+), 9 deletions(-) + +diff --git a/sysutil.c b/sysutil.c +index fd07d99..e2df671 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -774,21 +774,48 @@ vsf_sysutil_deactivate_linger_failok(int fd) + (void) setsockopt(fd, SOL_SOCKET, SO_LINGER, &the_linger, sizeof(the_linger)); + } + +-void +-vsf_sysutil_activate_noblock(int fd) ++static int ++vsf_sysutil_activate_noblock_internal(int fd, int return_err) + { + int retval; + int curr_flags = fcntl(fd, F_GETFL); + if (vsf_sysutil_retval_is_error(curr_flags)) + { +- die("fcntl"); ++ if (return_err) ++ { ++ return -1; ++ } ++ else ++ { ++ die("fcntl"); ++ } + } + curr_flags |= O_NONBLOCK; + retval = fcntl(fd, F_SETFL, curr_flags); + if (retval != 0) + { +- die("fcntl"); ++ if (return_err) ++ { ++ return -1; ++ } ++ else ++ { ++ die("fcntl"); ++ } + } ++ return 0; ++} ++ ++void ++vsf_sysutil_activate_noblock(int fd) ++{ ++ (void) vsf_sysutil_activate_noblock_internal(fd, 0); ++} ++ ++int ++vsf_sysutil_activate_noblock_no_die(int fd) ++{ ++ return vsf_sysutil_activate_noblock_internal(fd, 1); + } + + void +diff --git a/sysutil.h b/sysutil.h +index 2df14ed..0772423 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -281,6 +281,7 @@ void vsf_sysutil_activate_oobinline(int fd); + void vsf_sysutil_activate_linger(int fd); + void vsf_sysutil_deactivate_linger_failok(int fd); + void vsf_sysutil_activate_noblock(int fd); ++int vsf_sysutil_activate_noblock_no_die(int fd); + void vsf_sysutil_deactivate_noblock(int fd); + /* This does SHUT_RDWR */ + void vsf_sysutil_shutdown_failok(int fd); +diff --git a/utility.c b/utility.c +index 75e5bdd..5619a04 100644 +--- a/utility.c ++++ b/utility.c +@@ -47,11 +47,13 @@ bug(const char* p_text) + { + vsf_log_die(p_text); + } +- vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); +- (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "500 OOPS: ", 10); +- (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text, +- vsf_sysutil_strlen(p_text)); +- (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "\r\n", 2); ++ if (vsf_sysutil_activate_noblock_no_die(VSFTP_COMMAND_FD) == 0) ++ { ++ (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "500 OOPS: ", 10); ++ (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text, ++ vsf_sysutil_strlen(p_text)); ++ (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "\r\n", 2); ++ } + if (tunable_log_die) + { + /* Workaround for https://github.com/systemd/systemd/issues/2913 */ +-- +2.20.1 + diff --git a/0002-Repeat-pututxline-if-it-fails-with-EINTR.patch b/0002-Repeat-pututxline-if-it-fails-with-EINTR.patch new file mode 100644 index 0000000..e89efcf --- /dev/null +++ b/0002-Repeat-pututxline-if-it-fails-with-EINTR.patch @@ -0,0 +1,105 @@ +From 896b3694ca062d747cd67e9e9ba246adb3fc706b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Mon, 5 Aug 2019 13:55:37 +0200 +Subject: [PATCH 2/2] Repeat pututxline() if it fails with EINTR + +This is a partial fix for rhbz#1688848. We cannot resolve it +completely until glibc bug rhbz#1734791 is fixed. See +https://bugzilla.redhat.com/show_bug.cgi?id=1688848#c13. + +The maximum number of attempts is currently 2, which might seem +low. However setting it to 2 was a decision based on data - see +https://bugzilla.redhat.com/show_bug.cgi?id=1688848#c16. + +Resolves: rhbz#1688848 +--- + sysdeputil.c | 53 +++++++++++++++++++++++++++++++++++++++++++++------- + 1 file changed, 46 insertions(+), 7 deletions(-) + +diff --git a/sysdeputil.c b/sysdeputil.c +index bd1e8c9..4fbcca7 100644 +--- a/sysdeputil.c ++++ b/sysdeputil.c +@@ -1203,6 +1203,8 @@ void + vsf_insert_uwtmp(const struct mystr* p_user_str, + const struct mystr* p_host_str) + { ++ int attempts; ++ + if (sizeof(s_utent.ut_line) < 16) + { + return; +@@ -1231,16 +1233,35 @@ vsf_insert_uwtmp(const struct mystr* p_user_str, + vsf_sysutil_strcpy(s_utent.ut_host, str_getbuf(p_host_str), + sizeof(s_utent.ut_host)); + s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec(); +- setutxent(); +- (void) pututxline(&s_utent); +- endutxent(); +- s_uwtmp_inserted = 1; ++ for (attempts = 2; attempts > 0; --attempts) ++ { ++ struct utmpx* p_res; ++ setutxent(); ++ p_res = pututxline(&s_utent); ++ /* For now we'll ignore errors other than EINTR and EAGAIN */ ++ if (p_res != NULL || (errno != EINTR && errno != EAGAIN)) ++ { ++ break; ++ } ++ } ++ if (attempts == 0) ++ { ++ /* This makes us skip pututxline() in vsf_remove_uwtmp() */ ++ s_uwtmp_inserted = -1; ++ } ++ else ++ { ++ s_uwtmp_inserted = 1; ++ endutxent(); ++ } + updwtmpx(WTMPX_FILE, &s_utent); + } + + void + vsf_remove_uwtmp(void) + { ++ int attempts; ++ + if (!s_uwtmp_inserted) + { + return; +@@ -1249,9 +1270,27 @@ vsf_remove_uwtmp(void) + vsf_sysutil_memclr(s_utent.ut_user, sizeof(s_utent.ut_user)); + vsf_sysutil_memclr(s_utent.ut_host, sizeof(s_utent.ut_host)); + s_utent.ut_tv.tv_sec = 0; +- setutxent(); +- (void) pututxline(&s_utent); +- endutxent(); ++ if (s_uwtmp_inserted == 1) ++ { ++ for (attempts = 2; attempts > 0; --attempts) ++ { ++ struct utmpx* p_res; ++ setutxent(); ++ p_res = pututxline(&s_utent); ++ /* For now we'll ignore errors other than EINTR and EAGAIN */ ++ if (p_res != NULL || (errno != EINTR && errno != EAGAIN)) ++ { ++ break; ++ } ++ } ++ if (attempts != 0) ++ { ++ endutxent(); ++ } ++ } ++ /* Set s_uwtmp_inserted to 0 regardless of the result of ++ * pututxline() to make sure we won't run this function twice. ++ */ + s_uwtmp_inserted = 0; + s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec(); + updwtmpx(WTMPX_FILE, &s_utent); +-- +2.20.1 + diff --git a/0003-Enable-build-with-TCP-Wrapper.patch b/0003-Enable-build-with-TCP-Wrapper.patch new file mode 100644 index 0000000..e656776 --- /dev/null +++ b/0003-Enable-build-with-TCP-Wrapper.patch @@ -0,0 +1,25 @@ +From 1e0e2b13836d40f5a3f4cb20f2b3ea8204115b51 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Tue, 6 Sep 2016 13:42:09 +0200 +Subject: [PATCH 03/59] Enable build with TCP Wrapper + +--- + builddefs.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/builddefs.h b/builddefs.h +index 63cc62b..83de674 100644 +--- a/builddefs.h ++++ b/builddefs.h +@@ -1,7 +1,7 @@ + #ifndef VSF_BUILDDEFS_H + #define VSF_BUILDDEFS_H + +-#undef VSF_BUILD_TCPWRAPPERS ++#define VSF_BUILD_TCPWRAPPERS + #define VSF_BUILD_PAM + #define VSF_BUILD_SSL + +-- +2.14.4 + diff --git a/0004-Use-etc-vsftpd-dir-for-config-files-instead-of-etc.patch b/0004-Use-etc-vsftpd-dir-for-config-files-instead-of-etc.patch new file mode 100644 index 0000000..e82cd84 --- /dev/null +++ b/0004-Use-etc-vsftpd-dir-for-config-files-instead-of-etc.patch @@ -0,0 +1,483 @@ +From fff93602a4b252be8d674e27083dde68a7acf038 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Tue, 6 Sep 2016 13:46:03 +0200 +Subject: [PATCH 04/59] Use /etc/vsftpd/ dir for config files instead of /etc. + +--- + EXAMPLE/INTERNET_SITE/README | 6 +++--- + EXAMPLE/INTERNET_SITE_NOINETD/README | 4 ++-- + EXAMPLE/PER_IP_CONFIG/README | 4 ++-- + EXAMPLE/VIRTUAL_USERS/README | 14 +++++++------- + FAQ | 8 ++++---- + INSTALL | 10 +++++----- + README | 5 +++++ + defs.h | 2 +- + tunables.c | 10 +++++----- + vsftpd.8 | 10 +++++----- + vsftpd.conf | 20 +++++++++++++------- + vsftpd.conf.5 | 22 +++++++++++----------- + 12 files changed, 63 insertions(+), 52 deletions(-) + +diff --git a/EXAMPLE/INTERNET_SITE/README b/EXAMPLE/INTERNET_SITE/README +index 12b10a5..fe3d7ca 100644 +--- a/EXAMPLE/INTERNET_SITE/README ++++ b/EXAMPLE/INTERNET_SITE/README +@@ -41,13 +41,13 @@ no_access = 192.168.1.3 + As an example of how to ban certain sites from connecting, 192.168.1.3 will + be denied access. + +-banner_fail = /etc/vsftpd.busy_banner ++banner_fail = /etc/vsftpd/busy_banner + + This is the file to display to users if the connection is refused for whatever + reason (too many users, IP banned). + + Example of how to populate it: +-echo "421 Server busy, please try later." > /etc/vsftpd.busy_banner ++echo "421 Server busy, please try later." > /etc/vsftpd/busy_banner + + log_on_success += PID HOST DURATION + log_on_failure += HOST +@@ -62,7 +62,7 @@ Step 2) Set up your vsftpd configuration file. + + An example file is supplied. Install it like this: + +-cp vsftpd.conf /etc ++cp vsftpd.conf /etc/vsftpd + + Let's example the contents of the file: + +diff --git a/EXAMPLE/INTERNET_SITE_NOINETD/README b/EXAMPLE/INTERNET_SITE_NOINETD/README +index ce17af2..9198c5f 100644 +--- a/EXAMPLE/INTERNET_SITE_NOINETD/README ++++ b/EXAMPLE/INTERNET_SITE_NOINETD/README +@@ -17,7 +17,7 @@ even per-connect-IP configurability. + + To use this example config: + +-1) Copy the vsftpd.conf file in this directory to /etc/vsftpd.conf. ++1) Copy the vsftpd.conf file in this directory to /etc/vsftpd/vsftpd.conf. + + 2) Start up vsftpd, e.g. + vsftpd & +@@ -51,5 +51,5 @@ in the vsftpd.conf: + listen_address=192.168.1.2 + + And launch vsftpd with a specific config file like this: +-vsftpd /etc/vsftpd.conf.site1 & ++vsftpd /etc/vsftpd/vsftpd.conf.site1 & + +diff --git a/EXAMPLE/PER_IP_CONFIG/README b/EXAMPLE/PER_IP_CONFIG/README +index a9ef352..34924d5 100644 +--- a/EXAMPLE/PER_IP_CONFIG/README ++++ b/EXAMPLE/PER_IP_CONFIG/README +@@ -20,12 +20,12 @@ directory: hosts.allow. It lives at /etc/hosts.allow. + + Let's have a look at the example: + +-vsftpd: 192.168.1.3: setenv VSFTPD_LOAD_CONF /etc/vsftpd_tcp_wrap.conf ++vsftpd: 192.168.1.3: setenv VSFTPD_LOAD_CONF /etc/vsftpd/tcp_wrap.conf + vsftpd: 192.168.1.4: DENY + + The first line: + If a client connects from 192.168.1.3, then vsftpd will apply the vsftpd +-config file /etc/vsftpd_tcp_wrap.conf to the session! These settings are ++config file /etc/vsftpd/tcp_wrap.conf to the session! These settings are + applied ON TOP of the default vsftpd.conf. + This is obviously very powerful. You might use this to apply different + access restrictions for some IPs (e.g. the ability to upload). +diff --git a/EXAMPLE/VIRTUAL_USERS/README b/EXAMPLE/VIRTUAL_USERS/README +index b48995d..72972fa 100644 +--- a/EXAMPLE/VIRTUAL_USERS/README ++++ b/EXAMPLE/VIRTUAL_USERS/README +@@ -15,7 +15,7 @@ See example file "logins.txt" - this specifies "tom" with password "foo" and + "fred" with password "bar". + Whilst logged in as root, create the actual database file like this: + +-db_load -T -t hash -f logins.txt /etc/vsftpd_login.db ++db_load -T -t hash -f logins.txt /etc/vsftpd/login.db + (Requires the Berkeley db program installed). + NOTE: Many systems have multiple versions of "db" installed, so you may + need to use e.g. db3_load for correct operation. This is known to affect +@@ -23,10 +23,10 @@ some Debian systems. The core issue is that pam_userdb expects its login + database to be a specific db version (often db3, whereas db4 may be installed + on your system). + +-This will create /etc/vsftpd_login.db. Obviously, you may want to make sure ++This will create /etc/vsftpd/login.db. Obviously, you may want to make sure + the permissions are restricted: + +-chmod 600 /etc/vsftpd_login.db ++chmod 600 /etc/vsftpd/login.db + + For more information on maintaing your login database, look around for + documentation on "Berkeley DB", e.g. +@@ -37,8 +37,8 @@ Step 2) Create a PAM file which uses your new database. + + See the example file vsftpd.pam. It contains two lines: + +-auth required /lib/security/pam_userdb.so db=/etc/vsftpd_login +-account required /lib/security/pam_userdb.so db=/etc/vsftpd_login ++auth required /lib/security/pam_userdb.so db=/etc/vsftpd/login ++account required /lib/security/pam_userdb.so db=/etc/vsftpd/login + + This tells PAM to authenticate users using our new database. Copy this PAM + file to the PAM directory - typically /etc/pam.d/ +@@ -108,9 +108,9 @@ pasv_max_port=30999 + These put a port range on passive FTP incoming requests - very useful if + you are configuring a firewall. + +-Copy the example vsftpd.conf file to /etc: ++Copy the example vsftpd.conf file to /etc/vsftpd: + +-cp vsftpd.conf /etc/ ++cp vsftpd.conf /etc/vsftpd/ + + + Step 5) Start up vsftpd. +diff --git a/FAQ b/FAQ +index 59fe56b..0142a0d 100644 +--- a/FAQ ++++ b/FAQ +@@ -35,7 +35,7 @@ needs this user to run bits of itself with no privilege. + Q) Help! Local users cannot log in. + A) There are various possible problems. + A1) By default, vsftpd disables any logins other than anonymous logins. Put +-local_enable=YES in your /etc/vsftpd.conf to allow local users to log in. ++local_enable=YES in your /etc/vsftpd/vsftpd.conf to allow local users to log in. + A2) vsftpd tries to link with PAM. (Run "ldd vsftpd" and look for libpam to + find out whether this has happened or not). If vsftpd links with PAM, then + you will need to have a PAM file installed for the vsftpd service. There is +@@ -47,12 +47,12 @@ system have a "shadow.h" file in the include path? + A4) If you are not using PAM, then vsftpd will do its own check for a valid + user shell in /etc/shells. You may need to disable this if you use an invalid + shell to disable logins other than FTP logins. Put check_shell=NO in your +-/etc/vsftpd.conf. ++/etc/vsftpd/vsftpd.conf. + + Q) Help! Uploads or other write commands give me "500 Unknown command.". + A) By default, write commands, including uploads and new directories, are + disabled. This is a security measure. To enable writes, put write_enable=YES +-in your /etc/vsftpd.conf. ++in your /etc/vsftpd/vsftpd.conf. + + Q) Help! What are the security implications referred to in the + "chroot_local_user" option? +@@ -88,7 +88,7 @@ A2) Alternatively, run as many copies as vsftpd as necessary, in standalone + mode. Use "listen_address=x.x.x.x" to set the virtual IP. + + Q) Help! Does vsftpd support virtual users? +-A) Yes, via PAM integration. Set "guest_enable=YES" in /etc/vsftpd.conf. This ++A) Yes, via PAM integration. Set "guest_enable=YES" in /etc/vsftpd/vsftpd.conf. This + has the effect of mapping every non-anonymous successful login to the local + username specified in "guest_username". Then, use PAM and (e.g.) its pam_userdb + module to provide authentication against an external (i.e. non-/etc/passwd) +diff --git a/INSTALL b/INSTALL +index 4f811aa..93a8a81 100644 +--- a/INSTALL ++++ b/INSTALL +@@ -56,14 +56,14 @@ cp vsftpd.8 /usr/local/man/man8 + + "make install" doesn't copy the sample config file. It is recommended you + do this: +-cp vsftpd.conf /etc ++cp vsftpd.conf /etc/vsftpd + + Step 4) Smoke test (without an inetd). + + vsftpd can run standalone or via an inetd (such as inetd or xinetd). You will + typically get more control running vsftpd from an inetd. But first we will run + it without, so we can check things are going well so far. +-Edit /etc/vsftpd.conf, and add this line at the bottom: ++Edit /etc/vsftpd/vsftpd.conf, and add this line at the bottom: + + listen=YES + +@@ -135,11 +135,11 @@ cp RedHat/vsftpd.pam /etc/pam.d/ftp + Step 7) Customize your configuration + + As well as the above three pre-requisites, you are recommended to install a +-config file. The default location for the config file is /etc/vsftpd.conf. ++config file. The default location for the config file is /etc/vsftpd/vsftpd.conf. + There is a sample vsftpd.conf in the distribution tarball. You probably want +-to copy that to /etc/vsftpd.conf as a basis for modification, i.e.: ++to copy that to /etc/vsftpd/vsftpd.conf as a basis for modification, i.e.: + +-cp vsftpd.conf /etc ++cp vsftpd.conf /etc/vsftpd + + The default configuration allows neither local user logins nor anonymous + uploads. You may wish to change these defaults. +diff --git a/README b/README +index 86643c1..adc7f42 100644 +--- a/README ++++ b/README +@@ -37,3 +37,8 @@ All configuration options are documented in the manual page vsftpd.conf.5. + Various example configurations are discussed in the EXAMPLE directory. + Frequently asked questions are tackled in the FAQ file. + ++Important Note ++============== ++The location of configuration files was changed to /etc/vsftpd/. If you want ++to migrate your old conf files from /etc (files vsftpd.xxxx.rpmsave) use ++/etc/vsfptd/vsftpd_conf_migrate.sh +diff --git a/defs.h b/defs.h +index 0ff5864..ca11eac 100644 +--- a/defs.h ++++ b/defs.h +@@ -1,7 +1,7 @@ + #ifndef VSF_DEFS_H + #define VSF_DEFS_H + +-#define VSFTP_DEFAULT_CONFIG "/etc/vsftpd.conf" ++#define VSFTP_DEFAULT_CONFIG "/etc/vsftpd/vsftpd.conf" + + #define VSFTP_COMMAND_FD 0 + +diff --git a/tunables.c b/tunables.c +index 284a10d..0ac4c34 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -190,7 +190,7 @@ tunables_load_defaults() + tunable_listen_ipv6 = 0; + tunable_dual_log_enable = 0; + tunable_syslog_enable = 0; +- tunable_background = 0; ++ tunable_background = 1; + tunable_virtual_use_local_privs = 0; + tunable_session_support = 0; + tunable_download_enable = 1; +@@ -262,11 +262,11 @@ tunables_load_defaults() + install_str_setting(".message", &tunable_message_file); + install_str_setting("nobody", &tunable_nopriv_user); + install_str_setting(0, &tunable_ftpd_banner); +- install_str_setting("/etc/vsftpd.banned_emails", &tunable_banned_email_file); +- install_str_setting("/etc/vsftpd.chroot_list", &tunable_chroot_list_file); ++ install_str_setting("/etc/vsftpd/banned_emails", &tunable_banned_email_file); ++ install_str_setting("/etc/vsftpd/chroot_list", &tunable_chroot_list_file); + install_str_setting("ftp", &tunable_pam_service_name); + install_str_setting("ftp", &tunable_guest_username); +- install_str_setting("/etc/vsftpd.user_list", &tunable_userlist_file); ++ install_str_setting("/etc/vsftpd/user_list", &tunable_userlist_file); + install_str_setting(0, &tunable_anon_root); + install_str_setting(0, &tunable_local_root); + install_str_setting(0, &tunable_banner_file); +@@ -279,7 +279,7 @@ tunables_load_defaults() + install_str_setting(0, &tunable_hide_file); + install_str_setting(0, &tunable_deny_file); + install_str_setting(0, &tunable_user_sub_token); +- install_str_setting("/etc/vsftpd.email_passwords", ++ install_str_setting("/etc/vsftpd/email_passwords", + &tunable_email_password_file); + install_str_setting("/usr/share/ssl/certs/vsftpd.pem", + &tunable_rsa_cert_file); +diff --git a/vsftpd.8 b/vsftpd.8 +index 6640b57..c920e7d 100644 +--- a/vsftpd.8 ++++ b/vsftpd.8 +@@ -21,7 +21,7 @@ itself will listen on the network. This latter mode is easier to use, and + recommended. It is activated by setting + .Pa listen=YES + in +-.Pa /etc/vsftpd.conf . ++.Pa /etc/vsftpd/vsftpd.conf . + Direct execution of the + .Nm vsftpd + binary will then launch the FTP service ready for immediate client connections. +@@ -33,7 +33,7 @@ as root. Any command line option not starting with a "-" character is treated + as a config file that will be loaded. Note that config files are loaded in the + strict order that they are encountered on the command line. + If no config files are specified, the default configuration file of +-.Pa /etc/vsftpd.conf ++.Pa /etc/vsftpd/vsftpd.conf + will be loaded, after all other command line options are processed. + .Pp + Supported options are: +@@ -47,14 +47,14 @@ their appearance on the command line, including intermingling with loading of + config files. + .El + .Sh EXAMPLES +-vsftpd -olisten=NO /etc/vsftpd.conf -oftpd_banner=blah ++vsftpd -olisten=NO /etc/vsftpd/vsftpd.conf -oftpd_banner=blah + .Pp + That example overrides vsftpd's built-in default for the "listen" option to be +-NO, but then loads /etc/vsftpd.conf which may override that setting. Finally, ++NO, but then loads /etc/vsftpd/vsftpd.conf which may override that setting. Finally, + the "ftpd_banner" setting is set to "blah", which overrides any default vsftpd + setting and any identical setting that was in the config file. + .Sh FILES +-.Pa /etc/vsftpd.conf ++.Pa /etc/vsftpd/vsftpd.conf + .Sh SEE ALSO + .Xr vsftpd.conf 5 + .end +diff --git a/vsftpd.conf b/vsftpd.conf +index cc1c607..db44170 100644 +--- a/vsftpd.conf ++++ b/vsftpd.conf +@@ -1,4 +1,4 @@ +-# Example config file /etc/vsftpd.conf ++# Example config file /etc/vsftpd/vsftpd.conf + # + # The default compiled in settings are fairly paranoid. This sample file + # loosens things up a bit, to make the ftp daemon more usable. +@@ -12,18 +12,20 @@ + anonymous_enable=YES + # + # Uncomment this to allow local users to log in. +-#local_enable=YES ++# When SELinux is enforcing check for SE bool ftp_home_dir ++local_enable=YES + # + # Uncomment this to enable any form of FTP write command. +-#write_enable=YES ++write_enable=YES + # + # Default umask for local users is 077. You may wish to change this to 022, + # if your users expect that (022 is used by most other ftpd's) +-#local_umask=022 ++local_umask=022 + # + # Uncomment this to allow the anonymous FTP user to upload files. This only + # has an effect if the above global write enable is activated. Also, you will + # obviously need to create a directory writable by the FTP user. ++# When SELinux is enforcing check for SE bool allow_ftpd_anon_write, allow_ftpd_full_access + #anon_upload_enable=YES + # + # Uncomment this if you want the anonymous FTP user to be able to create +@@ -52,7 +54,7 @@ connect_from_port_20=YES + # + # If you want, you can have your log file in standard ftpd xferlog format. + # Note that the default log file location is /var/log/xferlog in this case. +-#xferlog_std_format=YES ++xferlog_std_format=YES + # + # You may change the default value for timing out an idle session. + #idle_session_timeout=600 +@@ -87,7 +89,7 @@ connect_from_port_20=YES + # useful for combatting certain DoS attacks. + #deny_email_enable=YES + # (default follows) +-#banned_email_file=/etc/vsftpd.banned_emails ++#banned_email_file=/etc/vsftpd/banned_emails + # + # You may specify an explicit list of local users to chroot() to their home + # directory. If chroot_local_user is YES, then this list becomes a list of +@@ -98,7 +100,7 @@ connect_from_port_20=YES + #chroot_local_user=YES + #chroot_list_enable=YES + # (default follows) +-#chroot_list_file=/etc/vsftpd.chroot_list ++#chroot_list_file=/etc/vsftpd/chroot_list + # + # You may activate the "-R" option to the builtin ls. This is disabled by + # default to avoid remote users being able to cause excessive I/O on large +@@ -115,3 +117,7 @@ listen=YES + # sockets, you must run two copies of vsftpd with two configuration files. + # Make sure, that one of the listen options is commented !! + #listen_ipv6=YES ++ ++pam_service_name=vsftpd ++userlist_enable=YES ++tcp_wrappers=YES +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index fcc6022..5e46a2f 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -4,7 +4,7 @@ vsftpd.conf \- config file for vsftpd + .SH DESCRIPTION + vsftpd.conf may be used to control various aspects of vsftpd's behaviour. By + default, vsftpd looks for this file at the location +-.BR /etc/vsftpd.conf . ++.BR /etc/vsftpd/vsftpd.conf . + However, you may override this by specifying a command line argument to + vsftpd. The command line argument is the pathname of the configuration file + for vsftpd. This behaviour is useful because you may wish to use an advanced +@@ -110,7 +110,7 @@ When enabled, and vsftpd is started in "listen" mode, vsftpd will background + the listener process. i.e. control will immediately be returned to the shell + which launched vsftpd. + +-Default: NO ++Default: YES + .TP + .B check_shell + Note! This option only has an effect for non-PAM builds of vsftpd. If disabled, +@@ -138,7 +138,7 @@ chroot() jail in their home directory upon login. The meaning is slightly + different if chroot_local_user is set to YES. In this case, the list becomes + a list of users which are NOT to be placed in a chroot() jail. + By default, the file containing this list is +-/etc/vsftpd.chroot_list, but you may override this with the ++/etc/vsftpd/chroot_list, but you may override this with the + .BR chroot_list_file + setting. + +@@ -177,7 +177,7 @@ Default: NO + .B deny_email_enable + If activated, you may provide a list of anonymous password e-mail responses + which cause login to be denied. By default, the file containing this list is +-/etc/vsftpd.banned_emails, but you may override this with the ++/etc/vsftpd/banned_emails, but you may override this with the + .BR banned_email_file + setting. + +@@ -433,7 +433,7 @@ anonymous logins are prevented unless the password provided is listed in the + file specified by the + .BR email_password_file + setting. The file format is one password per line, no extra whitespace. The +-default filename is /etc/vsftpd.email_passwords. ++default filename is /etc/vsftpd/email_passwords. + + Default: NO + .TP +@@ -764,7 +764,7 @@ passwords which are not permitted. This file is consulted if the option + .BR deny_email_enable + is enabled. + +-Default: /etc/vsftpd.banned_emails ++Default: /etc/vsftpd/banned_emails + .TP + .B banner_file + This option is the name of a file containing text to display when someone +@@ -803,7 +803,7 @@ is enabled. If the option + is enabled, then the list file becomes a list of users to NOT place in a + chroot() jail. + +-Default: /etc/vsftpd.chroot_list ++Default: /etvsftpd.confc/vsftpd.chroot_list + .TP + .B cmds_allowed + This options specifies a comma separated list of allowed FTP commands (post +@@ -864,7 +864,7 @@ This option can be used to provide an alternate file for usage by the + .BR secure_email_list_enable + setting. + +-Default: /etc/vsftpd.email_passwords ++Default: /etc/vsftpd/email_passwords + .TP + .B ftp_username + This is the name of the user we use for handling anonymous FTP. The home +@@ -987,10 +987,10 @@ the manual page, on a per-user basis. Usage is simple, and is best illustrated + with an example. If you set + .BR user_config_dir + to be +-.BR /etc/vsftpd_user_conf ++.BR /etc/vsftpd/user_conf + and then log on as the user "chris", then vsftpd will apply the settings in + the file +-.BR /etc/vsftpd_user_conf/chris ++.BR /etc/vsftpd/user_conf/chris + for the duration of the session. The format of this file is as detailed in + this manual page! PLEASE NOTE that not all settings are effective on a + per-user basis. For example, many settings only prior to the user's session +@@ -1026,7 +1026,7 @@ This option is the name of the file loaded when the + .BR userlist_enable + option is active. + +-Default: /etc/vsftpd.user_list ++Default: /etc/vsftpd/user_list + .TP + .B vsftpd_log_file + This option is the name of the file to which we write the vsftpd style +-- +2.14.4 + diff --git a/0005-Use-hostname-when-calling-PAM-authentication-module.patch b/0005-Use-hostname-when-calling-PAM-authentication-module.patch new file mode 100644 index 0000000..af842f5 --- /dev/null +++ b/0005-Use-hostname-when-calling-PAM-authentication-module.patch @@ -0,0 +1,75 @@ +From 08c49b78942d40c99fae8c40e7668aa73e1bd695 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Tue, 6 Sep 2016 15:01:23 +0200 +Subject: [PATCH 05/59] Use hostname when calling PAM authentication module. + +Currently the vsftpd passes all logins as IP addresses +into PAM. This prevents administrators from setting up +ACLs based on domain (e.g. .example.com). This patch +enables reverse host lookup and use hostname instead +of address if there is one. +--- + sysdeputil.c | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +diff --git a/sysdeputil.c b/sysdeputil.c +index 06f01f4..b2782da 100644 +--- a/sysdeputil.c ++++ b/sysdeputil.c +@@ -16,6 +16,10 @@ + #include "tunables.h" + #include "builddefs.h" + ++/* For gethostbyaddr, inet_addr */ ++#include ++#include ++ + /* For Linux, this adds nothing :-) */ + #include "port/porting_junk.h" + +@@ -323,6 +327,10 @@ vsf_sysdep_check_auth(struct mystr* p_user_str, + const struct mystr* p_remote_host) + { + int retval = -1; ++#ifdef PAM_RHOST ++ struct sockaddr_in sin; ++ struct hostent *host; ++#endif + pam_item_t item; + const char* pam_user_name = 0; + struct pam_conv the_conv = +@@ -346,7 +354,12 @@ vsf_sysdep_check_auth(struct mystr* p_user_str, + return 0; + } + #ifdef PAM_RHOST +- retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); ++ sin.sin_addr.s_addr = inet_addr(str_getbuf(p_remote_host)); ++ host = gethostbyaddr((char*)&sin.sin_addr.s_addr,sizeof(struct in_addr),AF_INET); ++ if (host != (struct hostent*)0) ++ retval = pam_set_item(s_pamh, PAM_RHOST, host->h_name); ++ else ++ retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); + if (retval != PAM_SUCCESS) + { + (void) pam_end(s_pamh, retval); +@@ -559,7 +572,7 @@ vsf_sysdep_has_capabilities(void) + } + return s_runtime_has_caps; + } +- ++ + #ifndef VSF_SYSDEP_HAVE_LIBCAP + static int + do_checkcap(void) +@@ -1081,7 +1094,7 @@ vsf_sysutil_recv_fd(const int sock_fd) + msg.msg_flags = 0; + /* In case something goes wrong, set the fd to -1 before the syscall */ + p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg)); +- *p_fd = -1; ++ *p_fd = -1; + retval = recvmsg(sock_fd, &msg, 0); + if (retval != 1) + { +-- +2.14.4 + diff --git a/0006-Close-stdin-out-err-before-listening-for-incoming-co.patch b/0006-Close-stdin-out-err-before-listening-for-incoming-co.patch new file mode 100644 index 0000000..f030f35 --- /dev/null +++ b/0006-Close-stdin-out-err-before-listening-for-incoming-co.patch @@ -0,0 +1,35 @@ +From 423cbf4ddca6578b87e0f8a3fc425688cd1ca89c Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Tue, 6 Sep 2016 16:18:39 +0200 +Subject: [PATCH 06/59] Close stdin/out/err before listening for incoming + connections. + +When running vsftpd as a stand-alone FTP daemon, vsftpd +did not close stdin/out/err. This caused the start script +to hang waiting for stdin to close. Before this patch was +applied, one had to hit ctrl+c in order to get shell prompt +back. Correct behavior: +$ /etc/init.d/vsftpd start | tee +Starting vsftpd for vsftpd: [ OK ] +$ +--- + standalone.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/standalone.c b/standalone.c +index ba01ab1..e0f2d5b 100644 +--- a/standalone.c ++++ b/standalone.c +@@ -130,6 +130,9 @@ vsf_standalone_main(void) + die("could not bind listening IPv6 socket"); + } + } ++ vsf_sysutil_close(0); ++ vsf_sysutil_close(1); ++ vsf_sysutil_close(2); + retval = vsf_sysutil_listen(listen_sock, VSFTP_LISTEN_BACKLOG); + if (vsf_sysutil_retval_is_error(retval)) + { +-- +2.14.4 + diff --git a/0007-Make-filename-filters-smarter.patch b/0007-Make-filename-filters-smarter.patch new file mode 100644 index 0000000..6db2d1a --- /dev/null +++ b/0007-Make-filename-filters-smarter.patch @@ -0,0 +1,102 @@ +From 548375b2122f83771dc0b8571f16e5b5adabba98 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 10:04:31 +0200 +Subject: [PATCH 07/59] Make filename filters smarter. + +In the original version vsftpd was not able to prevent +users from downloading for instance /etc/passwd by +defining filters such as deny_file=/etc/passwd or /etc* +or passwd. Example of erroneous behavior: +230 Login successful. +Remote system type is UNIX. +Using binary mode to transfer files. +ftp> cd / +250 Directory successfully changed. +ftp> cd /etc +550 Permission denied. +ftp> cd etc +250 Directory successfully changed. +ftp> get passwd +local: passwd remote: passwd +227 Entering Passive Mode (127,0,0,1,99,251) +150 Opening BINARY mode data connection for passwd (2813 bytes). +226 File send OK. +2813 bytes received in 0.00016 seconds (1.7e+04 Kbytes/s) +ftp> quit +221 Goodbye. +--- + ls.c | 24 +++++++++++++++++++++++- + str.c | 11 +++++++++++ + str.h | 1 + + 3 files changed, 35 insertions(+), 1 deletion(-) + +diff --git a/ls.c b/ls.c +index 7e1376d..f489478 100644 +--- a/ls.c ++++ b/ls.c +@@ -246,8 +246,30 @@ vsf_filename_passes_filter(const struct mystr* p_filename_str, + int ret = 0; + char last_token = 0; + int must_match_at_current_pos = 1; ++ ++ + str_copy(&filter_remain_str, p_filter_str); +- str_copy(&name_remain_str, p_filename_str); ++ ++ if (!str_isempty (&filter_remain_str) && !str_isempty(p_filename_str)) { ++ if (str_get_char_at(p_filter_str, 0) == '/') { ++ if (str_get_char_at(p_filename_str, 0) != '/') { ++ str_getcwd (&name_remain_str); ++ ++ if (str_getlen(&name_remain_str) > 1) /* cwd != root dir */ ++ str_append_char (&name_remain_str, '/'); ++ ++ str_append_str (&name_remain_str, p_filename_str); ++ } ++ else ++ str_copy (&name_remain_str, p_filename_str); ++ } else { ++ if (str_get_char_at(p_filter_str, 0) != '{') ++ str_basename (&name_remain_str, p_filename_str); ++ else ++ str_copy (&name_remain_str, p_filename_str); ++ } ++ } else ++ str_copy(&name_remain_str, p_filename_str); + + while (!str_isempty(&filter_remain_str) && *iters < VSFTP_MATCHITERS_MAX) + { +diff --git a/str.c b/str.c +index 6596204..ba4b92a 100644 +--- a/str.c ++++ b/str.c +@@ -711,3 +711,14 @@ str_replace_unprintable(struct mystr* p_str, char new_char) + } + } + ++void ++str_basename (struct mystr* d_str, const struct mystr* path) ++{ ++ static struct mystr tmp; ++ ++ str_copy (&tmp, path); ++ str_split_char_reverse(&tmp, d_str, '/'); ++ ++ if (str_isempty(d_str)) ++ str_copy (d_str, path); ++} +diff --git a/str.h b/str.h +index ab0a9a4..3a21b50 100644 +--- a/str.h ++++ b/str.h +@@ -100,6 +100,7 @@ void str_replace_unprintable(struct mystr* p_str, char new_char); + int str_atoi(const struct mystr* p_str); + filesize_t str_a_to_filesize_t(const struct mystr* p_str); + unsigned int str_octal_to_uint(const struct mystr* p_str); ++void str_basename (struct mystr* d_str, const struct mystr* path); + + /* PURPOSE: Extract a line of text (delimited by \n or EOF) from a string + * buffer, starting at character position 'p_pos'. The extracted line will +-- +2.14.4 + diff --git a/0008-Write-denied-logins-into-the-log.patch b/0008-Write-denied-logins-into-the-log.patch new file mode 100644 index 0000000..5e16953 --- /dev/null +++ b/0008-Write-denied-logins-into-the-log.patch @@ -0,0 +1,147 @@ +From 75c172596aa9e7a9f32062579f7f98783341c924 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 10:17:17 +0200 +Subject: [PATCH 08/59] Write denied logins into the log. + +This patch adds a new option 'userlist_log'. If enabled, +every login denial based on the user list will be logged. +--- + logging.c | 7 +++++++ + logging.h | 11 +++++++++++ + parseconf.c | 1 + + prelogin.c | 14 ++++++++++++++ + tunables.c | 2 ++ + tunables.h | 1 + + vsftpd.conf.5 | 8 ++++++++ + 7 files changed, 44 insertions(+) + +diff --git a/logging.c b/logging.c +index ad531d6..99671b4 100644 +--- a/logging.c ++++ b/logging.c +@@ -103,6 +103,13 @@ vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, + vsf_log_common(p_sess, 1, what, p_str); + } + ++void ++vsf_log_failed_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, ++ struct mystr* p_str) ++{ ++ vsf_log_common(p_sess, 0, what, p_str); ++} ++ + int + vsf_log_entry_pending(struct vsf_session* p_sess) + { +diff --git a/logging.h b/logging.h +index 48f88ec..1ff57d1 100644 +--- a/logging.h ++++ b/logging.h +@@ -80,5 +80,16 @@ void vsf_log_do_log(struct vsf_session* p_sess, int succeeded); + void vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, + struct mystr* p_str); + ++/* vsf_log_failed_line() ++ * PURPOSE ++ * Same as vsf_log_line(), except that it logs the line as failed operation. ++ * PARAMETERS ++ * p_sess - the current session object ++ * what - the type of operation to log ++ * p_str - the string to log ++ */ ++void vsf_log_failed_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, ++ struct mystr* p_str); ++ + #endif /* VSF_LOGGING_H */ + +diff --git a/parseconf.c b/parseconf.c +index ea2242b..385afd2 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -91,6 +91,7 @@ parseconf_bool_array[] = + { "mdtm_write", &tunable_mdtm_write }, + { "lock_upload_files", &tunable_lock_upload_files }, + { "pasv_addr_resolve", &tunable_pasv_addr_resolve }, ++ { "userlist_log", &tunable_userlist_log }, + { "debug_ssl", &tunable_debug_ssl }, + { "require_cert", &tunable_require_cert }, + { "validate_cert", &tunable_validate_cert }, +diff --git a/prelogin.c b/prelogin.c +index df4aade..1588bc1 100644 +--- a/prelogin.c ++++ b/prelogin.c +@@ -246,6 +246,20 @@ handle_user_command(struct vsf_session* p_sess) + check_login_delay(); + vsf_cmdio_write(p_sess, FTP_LOGINERR, "Permission denied."); + check_login_fails(p_sess); ++ if (tunable_userlist_log) ++ { ++ struct mystr str_log_line = INIT_MYSTR; ++ if (tunable_userlist_deny) ++ { ++ str_alloc_text(&str_log_line, "User is in the deny user list."); ++ } ++ else ++ { ++ str_alloc_text(&str_log_line, "User is not in the allow user list."); ++ } ++ vsf_log_failed_line(p_sess, kVSFLogEntryLogin, &str_log_line); ++ str_free(&str_log_line); ++ } + str_empty(&p_sess->user_str); + return; + } +diff --git a/tunables.c b/tunables.c +index 0ac4c34..b30fca1 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -72,6 +72,7 @@ int tunable_force_anon_data_ssl; + int tunable_mdtm_write; + int tunable_lock_upload_files; + int tunable_pasv_addr_resolve; ++int tunable_userlist_log; + int tunable_debug_ssl; + int tunable_require_cert; + int tunable_validate_cert; +@@ -212,6 +213,7 @@ tunables_load_defaults() + tunable_mdtm_write = 1; + tunable_lock_upload_files = 1; + tunable_pasv_addr_resolve = 0; ++ tunable_userlist_log = 0; + tunable_debug_ssl = 0; + tunable_require_cert = 0; + tunable_validate_cert = 0; +diff --git a/tunables.h b/tunables.h +index 05d2456..e44d64c 100644 +--- a/tunables.h ++++ b/tunables.h +@@ -73,6 +73,7 @@ extern int tunable_force_anon_data_ssl; /* Require anon data uses SSL */ + extern int tunable_mdtm_write; /* Allow MDTM to set timestamps */ + extern int tunable_lock_upload_files; /* Lock uploading files */ + extern int tunable_pasv_addr_resolve; /* DNS resolve pasv_addr */ ++extern int tunable_userlist_log; /* Log every failed login attempt */ + extern int tunable_debug_ssl; /* Verbose SSL logging */ + extern int tunable_require_cert; /* SSL client cert required */ + extern int tunable_validate_cert; /* SSL certs must be valid */ +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 5e46a2f..9d767b1 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -586,6 +586,14 @@ Default: NO + If set to yes, all SSL client certificates received must validate OK. + Self-signed certs do not constitute OK validation. (New in v2.0.6). + ++Default: NO ++.TP ++.B userlist_log ++This option is examined if ++.BR userlist_enable ++is activated. If enabled, every login denial based on the user list will be ++logged. ++ + Default: NO + .TP + .B virtual_use_local_privs +-- +2.14.4 + diff --git a/0009-Trim-whitespaces-when-reading-configuration.patch b/0009-Trim-whitespaces-when-reading-configuration.patch new file mode 100644 index 0000000..97f3e4f --- /dev/null +++ b/0009-Trim-whitespaces-when-reading-configuration.patch @@ -0,0 +1,99 @@ +From d024bc27cee40f21e6a3841266062408c44e56fb Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 10:35:54 +0200 +Subject: [PATCH 09/59] Trim whitespaces when reading configuration. + +--- + parseconf.c | 2 +- + str.c | 12 ++++++++++++ + str.h | 1 + + sysutil.c | 12 ++++++++++++ + sysutil.h | 1 + + 5 files changed, 27 insertions(+), 1 deletion(-) + +diff --git a/parseconf.c b/parseconf.c +index 385afd2..30df598 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -280,7 +280,7 @@ vsf_parseconf_load_setting(const char* p_setting, int errs_fatal) + } + else + { +- *p_curr_setting = str_strdup(&s_value_str); ++ *p_curr_setting = str_strdup_trimmed(&s_value_str); + } + return; + } +diff --git a/str.c b/str.c +index ba4b92a..41b27db 100644 +--- a/str.c ++++ b/str.c +@@ -104,6 +104,18 @@ str_strdup(const struct mystr* p_str) + return vsf_sysutil_strdup(str_getbuf(p_str)); + } + ++const char* ++str_strdup_trimmed(const struct mystr* p_str) ++{ ++ const char* p_trimmed = str_getbuf(p_str); ++ int h, t, newlen; ++ ++ for (h = 0; h < (int)str_getlen(p_str) && vsf_sysutil_isspace(p_trimmed[h]); h++) ; ++ for (t = str_getlen(p_str) - 1; t >= 0 && vsf_sysutil_isspace(p_trimmed[t]); t--) ; ++ newlen = t - h + 1; ++ return newlen ? vsf_sysutil_strndup(p_trimmed+h, (unsigned int)newlen) : 0L; ++} ++ + void + str_alloc_alt_term(struct mystr* p_str, const char* p_src, char term) + { +diff --git a/str.h b/str.h +index 3a21b50..44270da 100644 +--- a/str.h ++++ b/str.h +@@ -31,6 +31,7 @@ void str_alloc_ulong(struct mystr* p_str, unsigned long the_ulong); + void str_alloc_filesize_t(struct mystr* p_str, filesize_t the_filesize); + void str_copy(struct mystr* p_dest, const struct mystr* p_src); + const char* str_strdup(const struct mystr* p_str); ++const char* str_strdup_trimmed(const struct mystr* p_str); + void str_empty(struct mystr* p_str); + void str_free(struct mystr* p_str); + void str_trunc(struct mystr* p_str, unsigned int trunc_len); +diff --git a/sysutil.c b/sysutil.c +index 5cdb6ef..428a34a 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -1035,6 +1035,18 @@ vsf_sysutil_strdup(const char* p_str) + return strdup(p_str); + } + ++char* ++vsf_sysutil_strndup(const char* p_str, unsigned int p_len) ++{ ++ char *new = (char *)malloc(p_len+1); ++ ++ if (new == NULL) ++ return NULL; ++ ++ new[p_len]='\0'; ++ return (char *)memcpy(new, p_str, p_len); ++} ++ + void + vsf_sysutil_memclr(void* p_dest, unsigned int size) + { +diff --git a/sysutil.h b/sysutil.h +index c34778c..c2ddd15 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -186,6 +186,7 @@ int vsf_sysutil_wait_get_exitcode( + /* Various string functions */ + unsigned int vsf_sysutil_strlen(const char* p_text); + char* vsf_sysutil_strdup(const char* p_str); ++char* vsf_sysutil_strndup(const char* p_str, unsigned int p_len); + void vsf_sysutil_memclr(void* p_dest, unsigned int size); + void vsf_sysutil_memcpy(void* p_dest, const void* p_src, + const unsigned int size); +-- +2.14.4 + diff --git a/0010-Improve-daemonizing.patch b/0010-Improve-daemonizing.patch new file mode 100644 index 0000000..d2de767 --- /dev/null +++ b/0010-Improve-daemonizing.patch @@ -0,0 +1,209 @@ +From 569e7078244470ac0fcc2af3947c2735338555ec Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 11:29:29 +0200 +Subject: [PATCH 10/59] Improve daemonizing + +Init script gets correct return code if binding fails. +--- + standalone.c | 38 +++++++++++++++++++++++++++++++++++++- + sysutil.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + sysutil.h | 7 ++++++- + 3 files changed, 102 insertions(+), 2 deletions(-) + +diff --git a/standalone.c b/standalone.c +index e0f2d5b..3b65ea2 100644 +--- a/standalone.c ++++ b/standalone.c +@@ -26,6 +26,8 @@ static unsigned int s_ipaddr_size; + + static void handle_sigchld(void* duff); + static void handle_sighup(void* duff); ++static void handle_sigusr1(int sig); ++static void handle_sigalrm(int sig); + static void prepare_child(int sockfd); + static unsigned int handle_ip_count(void* p_raw_addr); + static void drop_ip_count(void* p_raw_addr); +@@ -46,11 +48,23 @@ vsf_standalone_main(void) + } + if (tunable_background) + { ++ vsf_sysutil_sigaction(kVSFSysUtilSigALRM, handle_sigalrm); ++ vsf_sysutil_sigaction(kVSFSysUtilSigUSR1, handle_sigusr1); ++ + int forkret = vsf_sysutil_fork(); + if (forkret > 0) + { + /* Parent, just exit */ +- vsf_sysutil_exit(0); ++ vsf_sysutil_set_alarm(3); ++ vsf_sysutil_pause(); ++ ++ vsf_sysutil_exit(1); ++ } ++ else if (forkret == 0) ++ { ++ // Son, restore original signal handler ++ vsf_sysutil_sigaction(kVSFSysUtilSigALRM, 0L); ++ vsf_sysutil_sigaction(kVSFSysUtilSigUSR1, 0L); + } + /* Son, close standard FDs to avoid SSH hang-on-exit */ + vsf_sysutil_reopen_standard_fds(); +@@ -99,6 +113,10 @@ vsf_standalone_main(void) + { + die("could not bind listening IPv4 socket"); + } ++ if (tunable_background) ++ { ++ vsf_sysutil_kill(vsf_sysutil_getppid(), kVSFSysUtilSigUSR1); ++ } + } + else + { +@@ -129,6 +147,10 @@ vsf_standalone_main(void) + { + die("could not bind listening IPv6 socket"); + } ++ if (tunable_background) ++ { ++ vsf_sysutil_kill(vsf_sysutil_getppid(), kVSFSysUtilSigUSR1); ++ } + } + vsf_sysutil_close(0); + vsf_sysutil_close(1); +@@ -268,6 +290,20 @@ handle_sighup(void* duff) + vsf_parseconf_load_file(0, 0); + } + ++static void ++handle_sigalrm(int sig) ++{ ++ (void)sig; // avoid unused parameter error ++ vsf_sysutil_exit(1); ++} ++ ++static void ++handle_sigusr1(int sig) ++{ ++ (void)sig; // avoid unused parameter error ++ vsf_sysutil_exit(0); ++} ++ + static unsigned int + hash_ip(unsigned int buckets, void* p_key) + { +diff --git a/sysutil.c b/sysutil.c +index 428a34a..c848356 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -201,6 +201,9 @@ vsf_sysutil_translate_sig(const enum EVSFSysUtilSignal sig) + case kVSFSysUtilSigHUP: + realsig = SIGHUP; + break; ++ case kVSFSysUtilSigUSR1: ++ realsig = SIGUSR1; ++ break; + default: + bug("unknown signal in vsf_sysutil_translate_sig"); + break; +@@ -549,6 +552,12 @@ vsf_sysutil_getpid(void) + return (unsigned int) s_current_pid; + } + ++unsigned int ++vsf_sysutil_getppid(void) ++{ ++ return (unsigned int)getppid(); ++} ++ + int + vsf_sysutil_fork(void) + { +@@ -2871,3 +2880,53 @@ vsf_sysutil_post_fork() + s_sig_details[i].pending = 0; + } + } ++ ++static struct sigaction sigalr, sigusr1; ++ ++void ++vsf_sysutil_sigaction(const enum EVSFSysUtilSignal sig, void (*p_handlefunc)(int)) ++{ ++ int realsig = vsf_sysutil_translate_sig(sig); ++ int retval; ++ struct sigaction sigact, *origsigact=NULL; ++ if (realsig==SIGALRM) ++ { ++ origsigact = &sigalr; ++ } ++ else if (realsig==SIGUSR1) ++ { ++ origsigact = &sigusr1; ++ } ++ vsf_sysutil_memclr(&sigact, sizeof(sigact)); ++ if (p_handlefunc != NULL) ++ { ++ sigact.sa_handler = p_handlefunc; ++ retval = sigfillset(&sigact.sa_mask); ++ if (retval != 0) ++ { ++ die("sigfillset"); ++ } ++ retval = sigaction(realsig, &sigact, origsigact); ++ } ++ else ++ { ++ retval = sigaction(realsig, origsigact, NULL); ++ } ++ if (retval != 0) ++ { ++ die("sigaction"); ++ } ++} ++ ++int ++vsf_sysutil_kill(int pid, int sig) ++{ ++ int realsig = vsf_sysutil_translate_sig(sig); ++ return kill(pid, realsig); ++} ++ ++int ++vsf_sysutil_pause() ++{ ++ return pause(); ++} +diff --git a/sysutil.h b/sysutil.h +index c2ddd15..bfc92cb 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -30,7 +30,8 @@ enum EVSFSysUtilSignal + kVSFSysUtilSigCHLD, + kVSFSysUtilSigPIPE, + kVSFSysUtilSigURG, +- kVSFSysUtilSigHUP ++ kVSFSysUtilSigHUP, ++ kVSFSysUtilSigUSR1 + }; + enum EVSFSysUtilInterruptContext + { +@@ -165,6 +166,7 @@ void vsf_sysutil_free(void* p_ptr); + + /* Process creation/exit/process handling */ + unsigned int vsf_sysutil_getpid(void); ++unsigned int vsf_sysutil_getppid(void); + void vsf_sysutil_post_fork(void); + int vsf_sysutil_fork(void); + int vsf_sysutil_fork_failok(void); +@@ -182,6 +184,9 @@ int vsf_sysutil_wait_exited_normally( + const struct vsf_sysutil_wait_retval* p_waitret); + int vsf_sysutil_wait_get_exitcode( + const struct vsf_sysutil_wait_retval* p_waitret); ++void vsf_sysutil_sigaction(const enum EVSFSysUtilSignal sig, void (*p_handlefunc)(int)); ++int vsf_sysutil_kill(int pid, int sig); ++int vsf_sysutil_pause(); + + /* Various string functions */ + unsigned int vsf_sysutil_strlen(const char* p_text); +-- +2.14.4 + diff --git a/0011-Fix-listing-with-more-than-one-star.patch b/0011-Fix-listing-with-more-than-one-star.patch new file mode 100644 index 0000000..a675978 --- /dev/null +++ b/0011-Fix-listing-with-more-than-one-star.patch @@ -0,0 +1,38 @@ +From 32e6642640635d7305969f808b5badb706a11bff Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 11:36:17 +0200 +Subject: [PATCH 11/59] Fix listing with more than one star '*'. + +This is a regression introduced by some previous patch. +--- + ls.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/ls.c b/ls.c +index f489478..616b2d9 100644 +--- a/ls.c ++++ b/ls.c +@@ -311,6 +311,20 @@ vsf_filename_passes_filter(const struct mystr* p_filename_str, + { + goto out; + } ++ if (!must_match_at_current_pos && last_token == 0) ++ { ++ struct mystr last_str = INIT_MYSTR; ++ str_mid_to_end(&name_remain_str, &last_str, ++ str_getlen(&name_remain_str) - str_getlen(&s_match_needed_str)); ++ locate_result = str_locate_str(&last_str, &s_match_needed_str); ++ str_free(&last_str); ++ ++ if (locate_result.found) ++ { ++ ret = 1; ++ } ++ goto out; ++ } + /* Chop matched string out of remainder */ + str_mid_to_end(&name_remain_str, &temp_str, + indexx + str_getlen(&s_match_needed_str)); +-- +2.14.4 + diff --git a/0012-Replace-syscall-__NR_clone-.-with-clone.patch b/0012-Replace-syscall-__NR_clone-.-with-clone.patch new file mode 100644 index 0000000..84d01e6 --- /dev/null +++ b/0012-Replace-syscall-__NR_clone-.-with-clone.patch @@ -0,0 +1,35 @@ +From 0c3a1123c391995ab46cfde603fa025ff180a819 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 11:43:54 +0200 +Subject: [PATCH 12/59] Replace syscall(__NR_clone ..) with clone () + +in order to fix incorrect order of params on s390 arch +--- + sysdeputil.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/sysdeputil.c b/sysdeputil.c +index b2782da..3bbabaa 100644 +--- a/sysdeputil.c ++++ b/sysdeputil.c +@@ -1306,7 +1306,7 @@ vsf_sysutil_fork_isolate_failok() + static int cloneflags_work = 1; + if (cloneflags_work) + { +- int ret = syscall(__NR_clone, CLONE_NEWPID | CLONE_NEWIPC | SIGCHLD, NULL); ++ int ret = clone(NULL, NULL, CLONE_NEWPID | CLONE_NEWIPC | SIGCHLD, NULL); + if (ret != -1 || (errno != EINVAL && errno != EPERM)) + { + if (ret == 0) +@@ -1328,7 +1328,7 @@ vsf_sysutil_fork_newnet() + static int cloneflags_work = 1; + if (cloneflags_work) + { +- int ret = syscall(__NR_clone, CLONE_NEWNET | SIGCHLD, NULL); ++ int ret = clone(NULL, NULL, CLONE_NEWNET | SIGCHLD, NULL); + if (ret != -1 || (errno != EINVAL && errno != EPERM)) + { + if (ret == 0) +-- +2.14.4 + diff --git a/0013-Extend-man-pages-with-systemd-info.patch b/0013-Extend-man-pages-with-systemd-info.patch new file mode 100644 index 0000000..5dcd965 --- /dev/null +++ b/0013-Extend-man-pages-with-systemd-info.patch @@ -0,0 +1,86 @@ +From 813a4bc45d45f4af94c699893cb2d2ba998d5d31 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 11:53:07 +0200 +Subject: [PATCH 13/59] Extend man pages with systemd info. + +Man pages now reflect how is vsftpd used as +systemd service. +--- + vsftpd.8 | 24 ++++++++++++++++++++++++ + vsftpd.conf.5 | 18 +++++++++++++++++- + 2 files changed, 41 insertions(+), 1 deletion(-) + +diff --git a/vsftpd.8 b/vsftpd.8 +index c920e7d..fbeb1a2 100644 +--- a/vsftpd.8 ++++ b/vsftpd.8 +@@ -25,6 +25,23 @@ in + Direct execution of the + .Nm vsftpd + binary will then launch the FTP service ready for immediate client connections. ++.Pp ++Systemd changes the vsftpd daemon start-up. The vsftpd package contains vsftpd-generator script generating symbolic links to /var/run/systemd/generator/vsftpd.target.wants directory. The generator is called during e.g. 'systemctl --system daemon-reload'. All these symbolic links link /usr/lib/systemd/system/vsftpd@.service file. ++The vsftpd daemon(s) is/are controlled by one of following ways: ++.Pp ++1. Single daemon using default /etc/vsftpd/vsftpd.conf configuration file ++.br ++# systemctl {start,stop,...} vsftpd[.service] ++.Pp ++2. Single daemon using /etc/vsftpd/.conf ++.br ++# systemctl {start,stop,...} vsftpd@[.service] ++.Pp ++3. All instances together ++.br ++# systemctl {restart,stop} vsftpd.target ++.Pp ++See systemd.unit(5), systemd.target(5) for further details. + .Sh OPTIONS + An optional + configuration file or files +@@ -55,6 +72,13 @@ the "ftpd_banner" setting is set to "blah", which overrides any default vsftpd + setting and any identical setting that was in the config file. + .Sh FILES + .Pa /etc/vsftpd/vsftpd.conf ++.Pp ++.Pa /usr/lib/systemd/system/vsftpd.service ++.Pp ++.Pa /usr/lib/systemd/system/vsftpd@.service ++.Pp ++.Pa /usr/lib/systemd/system/vsftpd.target + .Sh SEE ALSO + .Xr vsftpd.conf 5 ++.Xr systemd.unit 5 + .end +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 9d767b1..0744f85 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -12,7 +12,23 @@ inetd such as + .BR xinetd + to launch vsftpd with different configuration files on a per virtual host + basis. +- ++.P ++Systemd changes the vsftpd daemon start-up. The vsftpd package contains vsftpd-generator script generating symbolic links to /var/run/systemd/generator/vsftpd.target.wants directory. The generator is called during e. g. 'systemctl --system daemon-reload'. All these symbolic links link /usr/lib/systemd/system/vsftpd@.service file. ++The vsftpd daemon(s) is/are controlled by one of following ways: ++.P ++1. Single daemon using default /etc/vsftpd/vsftpd.conf configuration file ++.br ++# systemctl {start,stop,...} vsftpd[.service] ++.P ++2. Single daemon using /etc/vsftpd/.conf ++.br ++# systemctl {start,stop,...} vsftpd@[.service] ++.P ++3. All instances together ++.br ++# systemctl {restart,stop} vsftpd.target ++.P ++See systemd.unit(5), systemd.target(5) for further details. + .SH FORMAT + The format of vsftpd.conf is very simple. Each line is either a comment or + a directive. Comment lines start with a # and are ignored. A directive line +-- +2.14.4 + diff --git a/0014-Add-support-for-square-brackets-in-ls.patch b/0014-Add-support-for-square-brackets-in-ls.patch new file mode 100644 index 0000000..27f5374 --- /dev/null +++ b/0014-Add-support-for-square-brackets-in-ls.patch @@ -0,0 +1,277 @@ +From ba0520650ae7f9f63e48ba9fb3a94297aebe2d0c Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 14:22:21 +0200 +Subject: [PATCH 14/59] Add support for square brackets in ls. + +--- + ls.c | 222 +++++++++++++++++++++++++++++++++++++++++++++---------------------- + 1 file changed, 150 insertions(+), 72 deletions(-) + +diff --git a/ls.c b/ls.c +index 616b2d9..b840136 100644 +--- a/ls.c ++++ b/ls.c +@@ -246,7 +246,7 @@ vsf_filename_passes_filter(const struct mystr* p_filename_str, + int ret = 0; + char last_token = 0; + int must_match_at_current_pos = 1; +- ++ int matched = 0; + + str_copy(&filter_remain_str, p_filter_str); + +@@ -276,7 +276,7 @@ vsf_filename_passes_filter(const struct mystr* p_filename_str, + static struct mystr s_match_needed_str; + /* Locate next special token */ + struct str_locate_result locate_result = +- str_locate_chars(&filter_remain_str, "*?{"); ++ str_locate_chars(&filter_remain_str, "*?{["); + (*iters)++; + /* Isolate text leading up to token (if any) - needs to be matched */ + if (locate_result.found) +@@ -294,94 +294,172 @@ vsf_filename_passes_filter(const struct mystr* p_filename_str, + str_empty(&filter_remain_str); + last_token = 0; + } +- if (!str_isempty(&s_match_needed_str)) +- { +- /* Need to match something.. could be a match which has to start at +- * current position, or we could allow it to start anywhere +- */ +- unsigned int indexx; +- locate_result = str_locate_str(&name_remain_str, &s_match_needed_str); +- if (!locate_result.found) ++ ++ matched = 0; ++ do { ++ if (!str_isempty(&s_match_needed_str)) + { +- /* Fail */ +- goto out; ++ if (!matched) ++ { ++ matched = 1; ++ } ++ /* Need to match something.. could be a match which has to start at ++ * current position, or we could allow it to start anywhere ++ */ ++ unsigned int indexx; ++ locate_result = str_locate_str(&name_remain_str, &s_match_needed_str); ++ if (!locate_result.found) ++ { ++ /* Fail */ ++ goto out; ++ } ++ indexx = locate_result.index; ++ if (must_match_at_current_pos && indexx > 0) ++ { ++ goto out; ++ } ++ if (!must_match_at_current_pos && last_token == 0) ++ { ++ struct mystr last_str = INIT_MYSTR; ++ str_mid_to_end(&name_remain_str, &last_str, ++ str_getlen(&name_remain_str) - str_getlen(&s_match_needed_str)); ++ locate_result = str_locate_str(&last_str, &s_match_needed_str); ++ str_free(&last_str); ++ ++ if (locate_result.found) ++ { ++ ret = 1; ++ } ++ goto out; ++ } ++ /* Chop matched string out of remainder */ ++ str_mid_to_end(&name_remain_str, &temp_str, ++ indexx + str_getlen(&s_match_needed_str)); ++ str_copy(&name_remain_str, &temp_str); + } +- indexx = locate_result.index; +- if (must_match_at_current_pos && indexx > 0) ++ if (last_token == '?') + { +- goto out; ++ if (str_isempty(&name_remain_str)) ++ { ++ goto out; ++ } ++ str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1); ++ str_copy(&name_remain_str, &temp_str); ++ must_match_at_current_pos = 1; + } +- if (!must_match_at_current_pos && last_token == 0) ++ else if (last_token == '{') + { +- struct mystr last_str = INIT_MYSTR; +- str_mid_to_end(&name_remain_str, &last_str, +- str_getlen(&name_remain_str) - str_getlen(&s_match_needed_str)); +- locate_result = str_locate_str(&last_str, &s_match_needed_str); +- str_free(&last_str); ++ struct str_locate_result end_brace = ++ str_locate_char(&filter_remain_str, '}'); ++ must_match_at_current_pos = 1; ++ if (end_brace.found) ++ { ++ int entire = (*iters == 1 && last_token == '{'); + +- if (locate_result.found) ++ str_split_char(&filter_remain_str, &temp_str, '}'); ++ str_copy(&brace_list_str, &filter_remain_str); ++ str_copy(&filter_remain_str, &temp_str); ++ str_split_char(&brace_list_str, &temp_str, ','); ++ while (!str_isempty(&brace_list_str)) ++ { ++ str_empty(&new_filter_str); ++ if (!matched && !entire) ++ { ++ str_append_char(&new_filter_str, '*'); ++ } ++ str_append_str(&new_filter_str, &brace_list_str); ++ str_append_str(&new_filter_str, &filter_remain_str); ++ if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str, ++ iters)) ++ { ++ ret = 1; ++ goto out; ++ } ++ str_copy(&brace_list_str, &temp_str); ++ str_split_char(&brace_list_str, &temp_str, ','); ++ } ++ goto out; ++ } ++ else if (str_isempty(&name_remain_str) || ++ str_get_char_at(&name_remain_str, 0) != '{') + { +- ret = 1; ++ goto out; ++ } ++ else ++ { ++ str_right(&name_remain_str, &temp_str, ++ str_getlen(&name_remain_str) - 1); ++ str_copy(&name_remain_str, &temp_str); + } +- goto out; +- } +- /* Chop matched string out of remainder */ +- str_mid_to_end(&name_remain_str, &temp_str, +- indexx + str_getlen(&s_match_needed_str)); +- str_copy(&name_remain_str, &temp_str); +- } +- if (last_token == '?') +- { +- if (str_isempty(&name_remain_str)) +- { +- goto out; + } +- str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1); +- str_copy(&name_remain_str, &temp_str); +- must_match_at_current_pos = 1; +- } +- else if (last_token == '{') +- { +- struct str_locate_result end_brace = +- str_locate_char(&filter_remain_str, '}'); +- must_match_at_current_pos = 1; +- if (end_brace.found) ++ else if (last_token == '[') + { +- str_split_char(&filter_remain_str, &temp_str, '}'); +- str_copy(&brace_list_str, &filter_remain_str); +- str_copy(&filter_remain_str, &temp_str); +- str_split_char(&brace_list_str, &temp_str, ','); +- while (!str_isempty(&brace_list_str)) ++ struct str_locate_result end_sqb = ++ str_locate_char(&filter_remain_str, ']'); ++ must_match_at_current_pos = 1; ++ if (end_sqb.found) + { +- str_copy(&new_filter_str, &brace_list_str); +- str_append_str(&new_filter_str, &filter_remain_str); +- if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str, +- iters)) ++ unsigned int cur_pos; ++ char stch, ench; ++ const char *p_brace; ++ ++ str_split_char(&filter_remain_str, &temp_str, ']'); ++ str_copy(&brace_list_str, &filter_remain_str); ++ str_copy(&filter_remain_str, &temp_str); ++ p_brace = str_getbuf(&brace_list_str); ++ for (cur_pos = 0; cur_pos < str_getlen(&brace_list_str);) + { +- ret = 1; +- goto out; ++ stch = p_brace[cur_pos]; ++ // char vers. range ++ if (cur_pos + 2 < str_getlen(&brace_list_str) && ++ p_brace[cur_pos+1] == '-') ++ { ++ ench = p_brace[cur_pos+2]; ++ cur_pos += 3; ++ } ++ else ++ { ++ ench = stch; ++ cur_pos++; ++ } ++ // expand char[s] ++ for (;stch <= ench && !str_isempty(&brace_list_str); stch++) ++ { ++ str_empty(&new_filter_str); ++ if (!matched) ++ { ++ str_append_char(&new_filter_str, '*'); ++ } ++ str_append_char(&new_filter_str, stch); ++ str_append_str(&new_filter_str, &filter_remain_str); ++ if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str, ++ iters)) ++ { ++ ret = 1; ++ goto out; ++ } ++ } + } +- str_copy(&brace_list_str, &temp_str); +- str_split_char(&brace_list_str, &temp_str, ','); ++ goto out; ++ } ++ else if (str_isempty(&name_remain_str) || ++ str_get_char_at(&name_remain_str, 0) != '[') ++ { ++ goto out; ++ } ++ else ++ { ++ str_right(&name_remain_str, &temp_str, ++ str_getlen(&name_remain_str) - 1); ++ str_copy(&name_remain_str, &temp_str); + } +- goto out; +- } +- else if (str_isempty(&name_remain_str) || +- str_get_char_at(&name_remain_str, 0) != '{') +- { +- goto out; + } + else + { +- str_right(&name_remain_str, &temp_str, +- str_getlen(&name_remain_str) - 1); +- str_copy(&name_remain_str, &temp_str); ++ must_match_at_current_pos = 0; + } +- } +- else +- { +- must_match_at_current_pos = 0; +- } ++ } while (locate_result.found && ++ str_getlen(&name_remain_str) > 0 && last_token != '*'); + } + /* Any incoming string left means no match unless we ended on the correct + * type of wildcard. +-- +2.14.4 + diff --git a/0015-Listen-on-IPv6-by-default.patch b/0015-Listen-on-IPv6-by-default.patch new file mode 100644 index 0000000..b762b09 --- /dev/null +++ b/0015-Listen-on-IPv6-by-default.patch @@ -0,0 +1,55 @@ +From c5daaedf1efe23b397a5950f5503f5cbfac871c8 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 14:25:28 +0200 +Subject: [PATCH 15/59] Listen on IPv6 by default. + +--- + vsftpd.conf | 14 +++++++++----- + vsftpd.conf.5 | 5 +++-- + 2 files changed, 12 insertions(+), 7 deletions(-) + +diff --git a/vsftpd.conf b/vsftpd.conf +index db44170..ae6c6c9 100644 +--- a/vsftpd.conf ++++ b/vsftpd.conf +@@ -111,12 +111,16 @@ xferlog_std_format=YES + # When "listen" directive is enabled, vsftpd runs in standalone mode and + # listens on IPv4 sockets. This directive cannot be used in conjunction + # with the listen_ipv6 directive. +-listen=YES +-# +-# This directive enables listening on IPv6 sockets. To listen on IPv4 and IPv6 +-# sockets, you must run two copies of vsftpd with two configuration files. ++listen=NO ++# ++# This directive enables listening on IPv6 sockets. By default, listening ++# on the IPv6 "any" address (::) will accept connections from both IPv6 ++# and IPv4 clients. It is not necessary to listen on *both* IPv4 and IPv6 ++# sockets. If you want that (perhaps because you want to listen on specific ++# addresses) then you must run two copies of vsftpd with two configuration ++# files. + # Make sure, that one of the listen options is commented !! +-#listen_ipv6=YES ++listen_ipv6=YES + + pam_service_name=vsftpd + userlist_enable=YES +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 0744f85..72bb86f 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -297,8 +297,9 @@ Default: NO + .TP + .B listen_ipv6 + Like the listen parameter, except vsftpd will listen on an IPv6 socket instead +-of an IPv4 one. This parameter and the listen parameter are mutually +-exclusive. ++of an IPv4 one. Note that a socket listening on the IPv6 "any" address (::) ++will accept both IPv6 and IPv4 connections by default. This parameter and the ++listen parameter are mutually exclusive. + + Default: NO + .TP +-- +2.14.4 + diff --git a/0016-Increase-VSFTP_AS_LIMIT-from-200UL-to-400UL.patch b/0016-Increase-VSFTP_AS_LIMIT-from-200UL-to-400UL.patch new file mode 100644 index 0000000..fae6b9c --- /dev/null +++ b/0016-Increase-VSFTP_AS_LIMIT-from-200UL-to-400UL.patch @@ -0,0 +1,27 @@ +From 048208a4db5d7164d89ba5d7545e281d0a3472d3 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Wed, 7 Sep 2016 15:35:59 +0200 +Subject: [PATCH 16/59] Increase VSFTP_AS_LIMIT from 200UL to 400UL. + +When using a PAM module to get users from LDAP or database the old +limit was insufficient. +--- + defs.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/defs.h b/defs.h +index ca11eac..bde3232 100644 +--- a/defs.h ++++ b/defs.h +@@ -19,7 +19,7 @@ + /* Must be at least the size of VSFTP_MAX_COMMAND_LINE, VSFTP_DIR_BUFSIZE and + VSFTP_DATA_BUFSIZE*2 */ + #define VSFTP_PRIVSOCK_MAXSTR VSFTP_DATA_BUFSIZE * 2 +-#define VSFTP_AS_LIMIT 200UL * 1024 * 1024 ++#define VSFTP_AS_LIMIT 400UL * 1024 * 1024 + + #endif /* VSF_DEFS_H */ + +-- +2.14.4 + diff --git a/0017-Fix-an-issue-with-timestamps-during-DST.patch b/0017-Fix-an-issue-with-timestamps-during-DST.patch new file mode 100644 index 0000000..f331433 --- /dev/null +++ b/0017-Fix-an-issue-with-timestamps-during-DST.patch @@ -0,0 +1,161 @@ +From 5ec0b86e5c1ff060720b5a6cd1af9d93ec993650 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 29 Sep 2016 11:14:03 +0200 +Subject: [PATCH 17/59] Fix an issue with timestamps during DST. + +vsftpd now checks whether a file was uploaded during DST and +adjust the timestamp accordingly. +--- + sysutil.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++---------------- + 1 file changed, 77 insertions(+), 27 deletions(-) + +diff --git a/sysutil.c b/sysutil.c +index c848356..2abdd13 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -26,8 +26,10 @@ + /* For Linux, this adds nothing :-) */ + #include "port/porting_junk.h" + ++#define F_LOCALTIME "/etc/localtime" ++#define BUFTZSIZ 64 ++ + #include +-#include + #include + #include + #include +@@ -56,6 +58,11 @@ + #include + #include + ++#ifndef __USE_GNU ++ #define __USE_GNU ++#endif ++#include ++ + /* Private variables to this file */ + /* Current umask() */ + static unsigned int s_current_umask; +@@ -2574,49 +2581,92 @@ error: + die("reopening standard file descriptors to /dev/null failed"); + } + ++char* vsf_sysutil_get_tz() ++{ ++ char *ret_tz = NULL; ++ char buff[BUFTZSIZ]; ++ off_t s_pos, e_pos; ++ size_t rcnt, rest; ++ int fd; ++ ++ if ((fd = open(F_LOCALTIME, O_RDONLY)) > -1) ++ { ++ if ((e_pos = lseek(fd, 0, SEEK_END)) <= 0) ++ { ++ close(fd); ++ return NULL; ++ } ++ s_pos = e_pos > BUFTZSIZ ? e_pos - BUFTZSIZ : 0; ++ lseek(fd, s_pos, SEEK_SET); ++ rcnt = read(fd, buff, BUFTZSIZ); ++ ++ if (rcnt && buff[rcnt-1] == '\n') ++ { ++ buff[rcnt-1] = 0; ++ e_pos--; ++ } ++ ++ do { ++ char *nl = memrchr(buff, '\n', rcnt); ++ if (rcnt && nl) ++ { ++ int offset = (++nl) - buff; ++ int len = e_pos - s_pos - offset; ++ if (len) ++ { ++ lseek(fd, s_pos + offset, SEEK_SET); ++ ret_tz = calloc(1, len+4); ++ memcpy(ret_tz, "TZ=", 3); ++ rcnt = read(fd, ret_tz+3, len); ++ } ++ break; ++ } ++ if (!s_pos) ++ { ++ break; ++ } ++ rest = s_pos > BUFTZSIZ ? s_pos - BUFTZSIZ : 0; ++ s_pos -= rest; ++ lseek(fd, s_pos, SEEK_SET); ++ rcnt = read(fd, buff, rest); ++ } while (rcnt > 0); ++ ++ close (fd); ++ } ++ ++ return ret_tz; ++} ++ + void + vsf_sysutil_tzset(void) + { + int retval; +- char tzbuf[sizeof("+HHMM!")]; ++ char *tz=NULL, tzbuf[sizeof("+HHMM!")]; + time_t the_time = time(NULL); + struct tm* p_tm; ++ ++ /* Set our timezone in the TZ environment variable to cater for the fact ++ * that modern glibc does not cache /etc/localtime (which becomes inaccessible ++ * when we chroot(). ++ */ ++ tz = vsf_sysutil_get_tz();; ++ if (tz) ++ { ++ putenv(tz); ++ } + tzset(); + p_tm = localtime(&the_time); + if (p_tm == NULL) + { + die("localtime"); + } +- /* Set our timezone in the TZ environment variable to cater for the fact +- * that modern glibc does not cache /etc/localtime (which becomes inaccessible +- * when we chroot(). +- */ + retval = strftime(tzbuf, sizeof(tzbuf), "%z", p_tm); + tzbuf[sizeof(tzbuf) - 1] = '\0'; + if (retval == 5) + { +- /* Static because putenv() does not copy the string. */ +- static char envtz[sizeof("TZ=UTC-hh:mm")]; +- /* Insert a colon so we have e.g. -05:00 instead of -0500 */ +- tzbuf[5] = tzbuf[4]; +- tzbuf[4] = tzbuf[3]; +- tzbuf[3] = ':'; +- /* Invert the sign - we just got the offset _from_ UTC but for TZ, we need +- * the offset _to_ UTC. +- */ +- if (tzbuf[0] == '+') +- { +- tzbuf[0] = '-'; +- } +- else +- { +- tzbuf[0] = '+'; +- } +- snprintf(envtz, sizeof(envtz), "TZ=UTC%s", tzbuf); +- putenv(envtz); + s_timezone = ((tzbuf[1] - '0') * 10 + (tzbuf[2] - '0')) * 60 * 60; +- s_timezone += ((tzbuf[4] - '0') * 10 + (tzbuf[5] - '0')) * 60; +- if (tzbuf[0] == '-') ++ s_timezone += ((tzbuf[3] - '0') * 10 + (tzbuf[4] - '0')) * 60; ++ if (tzbuf[0] == '+') + { + s_timezone *= -1; + } +-- +2.14.4 + diff --git a/0018-Change-the-default-log-file-in-configuration.patch b/0018-Change-the-default-log-file-in-configuration.patch new file mode 100644 index 0000000..369a69c --- /dev/null +++ b/0018-Change-the-default-log-file-in-configuration.patch @@ -0,0 +1,43 @@ +From 61dac172bdb14c5a37713078828ea8c8f78c7eb6 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 29 Sep 2016 13:53:16 +0200 +Subject: [PATCH 18/59] Change the default log file in configuration. + +Previous "default" value was wrong. +tunables.c:262 => install_str_setting("/var/log/xferlog", +&tunable_xferlog_file); +--- + RedHat/vsftpd.log | 6 ++++++ + vsftpd.conf | 2 +- + 2 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/RedHat/vsftpd.log b/RedHat/vsftpd.log +index d338de8..14731c1 100644 +--- a/RedHat/vsftpd.log ++++ b/RedHat/vsftpd.log +@@ -3,3 +3,9 @@ + nocompress + missingok + } ++ ++/var/log/xferlog { ++ # ftpd doesn't handle SIGHUP properly ++ nocompress ++ missingok ++} +diff --git a/vsftpd.conf b/vsftpd.conf +index ae6c6c9..39d1955 100644 +--- a/vsftpd.conf ++++ b/vsftpd.conf +@@ -50,7 +50,7 @@ connect_from_port_20=YES + # + # You may override where the log file goes if you like. The default is shown + # below. +-#xferlog_file=/var/log/vsftpd.log ++#xferlog_file=/var/log/xferlog + # + # If you want, you can have your log file in standard ftpd xferlog format. + # Note that the default log file location is /var/log/xferlog in this case. +-- +2.14.4 + diff --git a/0019-Introduce-reverse_lookup_enable-option.patch b/0019-Introduce-reverse_lookup_enable-option.patch new file mode 100644 index 0000000..85023c1 --- /dev/null +++ b/0019-Introduce-reverse_lookup_enable-option.patch @@ -0,0 +1,109 @@ +From 721de88621100f6ed33f1602415bc249f3ed3219 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 10:22:32 +0100 +Subject: [PATCH 19/59] Introduce reverse_lookup_enable option. + +vsftpd can transform IP address into hostname before +PAM authentication. You can disable it to prevent +performance issues. +--- + parseconf.c | 1 + + sysdeputil.c | 14 +++++++++----- + tunables.c | 2 ++ + tunables.h | 1 + + vsftpd.conf.5 | 9 +++++++++ + 5 files changed, 22 insertions(+), 5 deletions(-) + +diff --git a/parseconf.c b/parseconf.c +index 30df598..3e0dba4 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -91,6 +91,7 @@ parseconf_bool_array[] = + { "mdtm_write", &tunable_mdtm_write }, + { "lock_upload_files", &tunable_lock_upload_files }, + { "pasv_addr_resolve", &tunable_pasv_addr_resolve }, ++ { "reverse_lookup_enable", &tunable_reverse_lookup_enable }, + { "userlist_log", &tunable_userlist_log }, + { "debug_ssl", &tunable_debug_ssl }, + { "require_cert", &tunable_require_cert }, +diff --git a/sysdeputil.c b/sysdeputil.c +index 3bbabaa..2063c87 100644 +--- a/sysdeputil.c ++++ b/sysdeputil.c +@@ -354,12 +354,16 @@ vsf_sysdep_check_auth(struct mystr* p_user_str, + return 0; + } + #ifdef PAM_RHOST +- sin.sin_addr.s_addr = inet_addr(str_getbuf(p_remote_host)); +- host = gethostbyaddr((char*)&sin.sin_addr.s_addr,sizeof(struct in_addr),AF_INET); +- if (host != (struct hostent*)0) +- retval = pam_set_item(s_pamh, PAM_RHOST, host->h_name); +- else ++ if (tunable_reverse_lookup_enable) { ++ sin.sin_addr.s_addr = inet_addr(str_getbuf(p_remote_host)); ++ host = gethostbyaddr((char*)&sin.sin_addr.s_addr,sizeof(struct in_addr),AF_INET); ++ if (host != (struct hostent*)0) ++ retval = pam_set_item(s_pamh, PAM_RHOST, host->h_name); ++ else ++ retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); ++ } else { + retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); ++ } + if (retval != PAM_SUCCESS) + { + (void) pam_end(s_pamh, retval); +diff --git a/tunables.c b/tunables.c +index b30fca1..c737465 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -72,6 +72,7 @@ int tunable_force_anon_data_ssl; + int tunable_mdtm_write; + int tunable_lock_upload_files; + int tunable_pasv_addr_resolve; ++int tunable_reverse_lookup_enable; + int tunable_userlist_log; + int tunable_debug_ssl; + int tunable_require_cert; +@@ -213,6 +214,7 @@ tunables_load_defaults() + tunable_mdtm_write = 1; + tunable_lock_upload_files = 1; + tunable_pasv_addr_resolve = 0; ++ tunable_reverse_lookup_enable = 1; + tunable_userlist_log = 0; + tunable_debug_ssl = 0; + tunable_require_cert = 0; +diff --git a/tunables.h b/tunables.h +index e44d64c..9553038 100644 +--- a/tunables.h ++++ b/tunables.h +@@ -73,6 +73,7 @@ extern int tunable_force_anon_data_ssl; /* Require anon data uses SSL */ + extern int tunable_mdtm_write; /* Allow MDTM to set timestamps */ + extern int tunable_lock_upload_files; /* Lock uploading files */ + extern int tunable_pasv_addr_resolve; /* DNS resolve pasv_addr */ ++extern int tunable_reverse_lookup_enable; /* Get hostname before pam auth */ + extern int tunable_userlist_log; /* Log every failed login attempt */ + extern int tunable_debug_ssl; /* Verbose SSL logging */ + extern int tunable_require_cert; /* SSL client cert required */ +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 72bb86f..fb6324e 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -423,6 +423,15 @@ so you may want to disable it. For a discussion of the consequences, see + http://scarybeastsecurity.blogspot.com/2009/02/vsftpd-210-released.html + (Added in v2.1.0). + ++Default: YES ++.TP ++.B reverse_lookup_enable ++Set to YES if you want vsftpd to transform the ip address into the hostname, ++before pam authentication. This is useful if you use pam_access including the ++hostname. If you want vsftpd to run on the environment where the reverse lookup ++for some hostname is available and the name server doesn't respond for a while, ++you should set this to NO to avoid a performance issue. ++ + Default: YES + .TP + .B run_as_launching_user +-- +2.14.4 + diff --git a/0020-Use-unsigned-int-for-uid-and-gid-representation.patch b/0020-Use-unsigned-int-for-uid-and-gid-representation.patch new file mode 100644 index 0000000..ac3ac1f --- /dev/null +++ b/0020-Use-unsigned-int-for-uid-and-gid-representation.patch @@ -0,0 +1,250 @@ +From dcaaf1e0dd3985e229a87de18b83f301d30b6ce9 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 10:31:39 +0100 +Subject: [PATCH 20/59] Use unsigned int for uid and gid representation. + +--- + ls.c | 4 ++-- + privops.c | 3 +-- + session.h | 6 +++--- + sysutil.c | 44 ++++++++++++++------------------------------ + sysutil.h | 20 ++++++++++---------- + 5 files changed, 30 insertions(+), 47 deletions(-) + +diff --git a/ls.c b/ls.c +index b840136..3c0988c 100644 +--- a/ls.c ++++ b/ls.c +@@ -503,7 +503,7 @@ build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str, + } + else + { +- int uid = vsf_sysutil_statbuf_get_uid(p_stat); ++ unsigned int uid = vsf_sysutil_statbuf_get_uid(p_stat); + struct vsf_sysutil_user* p_user = 0; + if (tunable_text_userdb_names) + { +@@ -528,7 +528,7 @@ build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str, + } + else + { +- int gid = vsf_sysutil_statbuf_get_gid(p_stat); ++ unsigned int gid = vsf_sysutil_statbuf_get_gid(p_stat); + struct vsf_sysutil_group* p_group = 0; + if (tunable_text_userdb_names) + { +diff --git a/privops.c b/privops.c +index 21d7267..f27c5c4 100644 +--- a/privops.c ++++ b/privops.c +@@ -236,8 +236,7 @@ vsf_privop_do_file_chown(struct vsf_session* p_sess, int fd) + /* Drop it like a hot potato unless it's a regular file owned by + * the the anonymous ftp user + */ +- if (p_sess->anon_upload_chown_uid == -1 || +- !vsf_sysutil_statbuf_is_regfile(s_p_statbuf) || ++ if (!vsf_sysutil_statbuf_is_regfile(s_p_statbuf) || + (vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->anon_ftp_uid && + vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->guest_user_uid)) + { +diff --git a/session.h b/session.h +index 27a488f..956bfb7 100644 +--- a/session.h ++++ b/session.h +@@ -54,9 +54,9 @@ struct vsf_session + struct mystr_list* p_visited_dir_list; + + /* Details of userids which are interesting to us */ +- int anon_ftp_uid; +- int guest_user_uid; +- int anon_upload_chown_uid; ++ unsigned int anon_ftp_uid; ++ unsigned int guest_user_uid; ++ unsigned int anon_upload_chown_uid; + + /* Things we need to cache before we chroot() */ + struct mystr banned_email_str; +diff --git a/sysutil.c b/sysutil.c +index 2abdd13..9881a66 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -1454,14 +1454,14 @@ vsf_sysutil_statbuf_get_size(const struct vsf_sysutil_statbuf* p_statbuf) + return p_stat->st_size; + } + +-int ++unsigned int + vsf_sysutil_statbuf_get_uid(const struct vsf_sysutil_statbuf* p_statbuf) + { + const struct stat* p_stat = (const struct stat*) p_statbuf; + return p_stat->st_uid; + } + +-int ++unsigned int + vsf_sysutil_statbuf_get_gid(const struct vsf_sysutil_statbuf* p_statbuf) + { + const struct stat* p_stat = (const struct stat*) p_statbuf; +@@ -1502,7 +1502,7 @@ vsf_sysutil_statbuf_get_sortkey_mtime( + } + + void +-vsf_sysutil_fchown(const int fd, const int uid, const int gid) ++vsf_sysutil_fchown(const int fd, const unsigned int uid, const unsigned int gid) + { + if (fchown(fd, uid, gid) != 0) + { +@@ -2320,13 +2320,9 @@ vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, + } + + struct vsf_sysutil_user* +-vsf_sysutil_getpwuid(const int uid) ++vsf_sysutil_getpwuid(const unsigned int uid) + { +- if (uid < 0) +- { +- bug("negative uid in vsf_sysutil_getpwuid"); +- } +- return (struct vsf_sysutil_user*) getpwuid((unsigned int) uid); ++ return (struct vsf_sysutil_user*) getpwuid(uid); + } + + struct vsf_sysutil_user* +@@ -2349,14 +2345,14 @@ vsf_sysutil_user_get_homedir(const struct vsf_sysutil_user* p_user) + return p_passwd->pw_dir; + } + +-int ++unsigned int + vsf_sysutil_user_getuid(const struct vsf_sysutil_user* p_user) + { + const struct passwd* p_passwd = (const struct passwd*) p_user; + return p_passwd->pw_uid; + } + +-int ++unsigned int + vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user) + { + const struct passwd* p_passwd = (const struct passwd*) p_user; +@@ -2364,13 +2360,9 @@ vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user) + } + + struct vsf_sysutil_group* +-vsf_sysutil_getgrgid(const int gid) ++vsf_sysutil_getgrgid(const unsigned int gid) + { +- if (gid < 0) +- { +- die("negative gid in vsf_sysutil_getgrgid"); +- } +- return (struct vsf_sysutil_group*) getgrgid((unsigned int) gid); ++ return (struct vsf_sysutil_group*) getgrgid(gid); + } + + const char* +@@ -2445,25 +2437,17 @@ vsf_sysutil_setgid_numeric(int gid) + } + } + +-int ++unsigned int + vsf_sysutil_geteuid(void) + { +- int retval = geteuid(); +- if (retval < 0) +- { +- die("geteuid"); +- } ++ unsigned int retval = geteuid(); + return retval; + } + +-int ++unsigned int + vsf_sysutil_getegid(void) + { +- int retval = getegid(); +- if (retval < 0) +- { +- die("getegid"); +- } ++ unsigned int retval = getegid(); + return retval; + } + +@@ -2854,7 +2838,7 @@ vsf_sysutil_ftruncate(int fd) + } + } + +-int ++unsigned int + vsf_sysutil_getuid(void) + { + return getuid(); +diff --git a/sysutil.h b/sysutil.h +index bfc92cb..79b5514 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -129,15 +129,15 @@ const char* vsf_sysutil_statbuf_get_numeric_date( + const struct vsf_sysutil_statbuf* p_stat, int use_localtime); + unsigned int vsf_sysutil_statbuf_get_links( + const struct vsf_sysutil_statbuf* p_stat); +-int vsf_sysutil_statbuf_get_uid(const struct vsf_sysutil_statbuf* p_stat); +-int vsf_sysutil_statbuf_get_gid(const struct vsf_sysutil_statbuf* p_stat); ++unsigned int vsf_sysutil_statbuf_get_uid(const struct vsf_sysutil_statbuf* p_stat); ++unsigned int vsf_sysutil_statbuf_get_gid(const struct vsf_sysutil_statbuf* p_stat); + int vsf_sysutil_statbuf_is_readable_other( + const struct vsf_sysutil_statbuf* p_stat); + const char* vsf_sysutil_statbuf_get_sortkey_mtime( + const struct vsf_sysutil_statbuf* p_stat); + + int vsf_sysutil_chmod(const char* p_filename, unsigned int mode); +-void vsf_sysutil_fchown(const int fd, const int uid, const int gid); ++void vsf_sysutil_fchown(const int fd, const unsigned int uid, const unsigned int gid); + void vsf_sysutil_fchmod(const int fd, unsigned int mode); + int vsf_sysutil_readlink(const char* p_filename, char* p_dest, + unsigned int bufsiz); +@@ -290,15 +290,15 @@ int vsf_sysutil_inet_aton( + struct vsf_sysutil_user; + struct vsf_sysutil_group; + +-struct vsf_sysutil_user* vsf_sysutil_getpwuid(const int uid); ++struct vsf_sysutil_user* vsf_sysutil_getpwuid(const unsigned int uid); + struct vsf_sysutil_user* vsf_sysutil_getpwnam(const char* p_user); + const char* vsf_sysutil_user_getname(const struct vsf_sysutil_user* p_user); + const char* vsf_sysutil_user_get_homedir( + const struct vsf_sysutil_user* p_user); +-int vsf_sysutil_user_getuid(const struct vsf_sysutil_user* p_user); +-int vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user); ++unsigned int vsf_sysutil_user_getuid(const struct vsf_sysutil_user* p_user); ++unsigned int vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user); + +-struct vsf_sysutil_group* vsf_sysutil_getgrgid(const int gid); ++struct vsf_sysutil_group* vsf_sysutil_getgrgid(const unsigned int gid); + const char* vsf_sysutil_group_getname(const struct vsf_sysutil_group* p_group); + + /* More random things */ +@@ -316,7 +316,7 @@ void vsf_sysutil_qsort(void* p_base, unsigned int num_elem, + char* vsf_sysutil_getenv(const char* p_var); + typedef void (*exitfunc_t)(void); + void vsf_sysutil_set_exit_func(exitfunc_t exitfunc); +-int vsf_sysutil_getuid(void); ++unsigned int vsf_sysutil_getuid(void); + + /* Syslogging (bah) */ + void vsf_sysutil_openlog(int force); +@@ -329,8 +329,8 @@ void vsf_sysutil_setuid(const struct vsf_sysutil_user* p_user); + void vsf_sysutil_setgid(const struct vsf_sysutil_user* p_user); + void vsf_sysutil_setuid_numeric(int uid); + void vsf_sysutil_setgid_numeric(int gid); +-int vsf_sysutil_geteuid(void); +-int vsf_sysutil_getegid(void); ++unsigned int vsf_sysutil_geteuid(void); ++unsigned int vsf_sysutil_getegid(void); + void vsf_sysutil_seteuid(const struct vsf_sysutil_user* p_user); + void vsf_sysutil_setegid(const struct vsf_sysutil_user* p_user); + void vsf_sysutil_seteuid_numeric(int uid); +-- +2.14.4 + diff --git a/0021-Introduce-support-for-DHE-based-cipher-suites.patch b/0021-Introduce-support-for-DHE-based-cipher-suites.patch new file mode 100644 index 0000000..3460c2a --- /dev/null +++ b/0021-Introduce-support-for-DHE-based-cipher-suites.patch @@ -0,0 +1,164 @@ +From 4eac1dbb5f70a652d31847eec7c28d245f36cdbb Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 10:48:28 +0100 +Subject: [PATCH 21/59] Introduce support for DHE based cipher suites. + +--- + parseconf.c | 1 + + ssl.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- + tunables.c | 5 +++- + tunables.h | 1 + + vsftpd.conf.5 | 6 ++++ + 5 files changed, 104 insertions(+), 2 deletions(-) + +diff --git a/parseconf.c b/parseconf.c +index 3e0dba4..38e3182 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -176,6 +176,7 @@ parseconf_str_array[] = + { "email_password_file", &tunable_email_password_file }, + { "rsa_cert_file", &tunable_rsa_cert_file }, + { "dsa_cert_file", &tunable_dsa_cert_file }, ++ { "dh_param_file", &tunable_dh_param_file }, + { "ssl_ciphers", &tunable_ssl_ciphers }, + { "rsa_private_key_file", &tunable_rsa_private_key_file }, + { "dsa_private_key_file", &tunable_dsa_private_key_file }, +diff --git a/ssl.c b/ssl.c +index c362983..22b69b3 100644 +--- a/ssl.c ++++ b/ssl.c +@@ -28,6 +28,8 @@ + #include + #include + #include ++#include ++#include + #include + #include + +@@ -58,6 +60,23 @@ + static int ssl_inited; + static struct mystr debug_str; + ++EVP_PKEY * ++DH_get_dh() ++{ ++ OSSL_PARAM dh_params[2]; ++ EVP_PKEY *dh_key = NULL; ++ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL); ++ ++ dh_params[0] = OSSL_PARAM_construct_utf8_string("group", "ffdhe2048", 0); ++ dh_params[1] = OSSL_PARAM_construct_end(); ++ ++ if (EVP_PKEY_keygen_init(pctx) <= 0 || EVP_PKEY_CTX_set_params(pctx, dh_params) <= 0) ++ return NULL; ++ EVP_PKEY_generate(pctx, &dh_key); ++ EVP_PKEY_CTX_free(pctx); ++ return dh_key; ++} ++ + void + ssl_init(struct vsf_session* p_sess) + { +@@ -72,7 +89,7 @@ + { + die("SSL: could not allocate SSL context"); + } +- options = SSL_OP_ALL; ++ options = SSL_OP_ALL | SSL_OP_SINGLE_DH_USE; + if (!tunable_sslv2) + { + options |= SSL_OP_NO_SSLv2; +@@ -149,8 +166,27 @@ + die("SSL: cannot load DSA private key"); + } + } ++ if (tunable_dh_param_file) ++ { ++ BIO *bio; ++ EVP_PKEY *dh_params = NULL; ++ if ((bio = BIO_new_file(tunable_dh_param_file, "r")) == NULL) ++ { ++ die("SSL: cannot load custom DH params"); ++ } ++ else ++ { ++ dh_params = PEM_read_bio_Parameters(bio, NULL); ++ BIO_free(bio); ++ ++ if (!SSL_CTX_set0_tmp_dh_pkey(p_ctx, dh_params)) ++ { ++ die("SSL: setting custom DH params failed"); ++ } ++ } ++ } + if (tunable_ssl_ciphers && + SSL_CTX_set_cipher_list(p_ctx, tunable_ssl_ciphers) != 1) + { + die("SSL: could not set cipher list"); + } +@@ -184,6 +226,9 @@ + /* Ensure cached session doesn't expire */ + SSL_CTX_set_timeout(p_ctx, INT_MAX); + } ++ ++ SSL_CTX_set0_tmp_dh_pkey(p_ctx, DH_get_dh()); ++ + /* Set up ALPN to check for FTP protocol intention of client. */ + SSL_CTX_set_alpn_select_cb(p_ctx, ssl_alpn_callback, p_sess); + /* Set up SNI callback for an optional hostname check. */ +diff --git a/tunables.c b/tunables.c +index c737465..1ea7227 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -140,6 +140,7 @@ const char* tunable_user_sub_token; + const char* tunable_email_password_file; + const char* tunable_rsa_cert_file; + const char* tunable_dsa_cert_file; ++const char* tunable_dh_param_file; + const char* tunable_ssl_ciphers; + const char* tunable_rsa_private_key_file; + const char* tunable_dsa_private_key_file; +@@ -288,7 +289,9 @@ tunables_load_defaults() + install_str_setting("/usr/share/ssl/certs/vsftpd.pem", + &tunable_rsa_cert_file); + install_str_setting(0, &tunable_dsa_cert_file); +- install_str_setting("ECDHE-RSA-AES256-GCM-SHA384", &tunable_ssl_ciphers); ++ install_str_setting(0, &tunable_dh_param_file); ++ install_str_setting("AES128-SHA:DES-CBC3-SHA:DHE-RSA-AES256-SHA", ++ &tunable_ssl_ciphers); + install_str_setting(0, &tunable_rsa_private_key_file); + install_str_setting(0, &tunable_dsa_private_key_file); + install_str_setting(0, &tunable_ca_certs_file); +diff --git a/tunables.h b/tunables.h +index 9553038..3995472 100644 +--- a/tunables.h ++++ b/tunables.h +@@ -142,6 +142,7 @@ extern const char* tunable_user_sub_token; + extern const char* tunable_email_password_file; + extern const char* tunable_rsa_cert_file; + extern const char* tunable_dsa_cert_file; ++extern const char* tunable_dh_param_file; + extern const char* tunable_ssl_ciphers; + extern const char* tunable_rsa_private_key_file; + extern const char* tunable_dsa_private_key_file; +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index fb6324e..ff94eca 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -893,6 +893,12 @@ to be in the same file as the certificate. + + Default: (none) + .TP ++.B dh_param_file ++This option specifies the location of the custom parameters used for ++ephemeral Diffie-Hellman key exchange in SSL. ++ ++Default: (none - use built in parameters appropriate for certificate key size) ++.TP + .B email_password_file + This option can be used to provide an alternate file for usage by the + .BR secure_email_list_enable +-- +2.14.4 + diff --git a/0022-Introduce-support-for-EDDHE-based-cipher-suites.patch b/0022-Introduce-support-for-EDDHE-based-cipher-suites.patch new file mode 100644 index 0000000..0a09a2c --- /dev/null +++ b/0022-Introduce-support-for-EDDHE-based-cipher-suites.patch @@ -0,0 +1,128 @@ +From a6d641a0ccba1033587f6faa0e5e6749fa35f5c4 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 10:49:22 +0100 +Subject: [PATCH 22/59] Introduce support for EDDHE based cipher suites. + +--- + parseconf.c | 1 + + ssl.c | 37 ++++++++++++++++++++++++++++++++++++- + tunables.c | 4 +++- + tunables.h | 1 + + vsftpd.conf.5 | 8 ++++++++ + 5 files changed, 49 insertions(+), 2 deletions(-) + +diff --git a/parseconf.c b/parseconf.c +index 38e3182..a2c715b 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -177,6 +177,7 @@ parseconf_str_array[] = + { "rsa_cert_file", &tunable_rsa_cert_file }, + { "dsa_cert_file", &tunable_dsa_cert_file }, + { "dh_param_file", &tunable_dh_param_file }, ++ { "ecdh_param_file", &tunable_ecdh_param_file }, + { "ssl_ciphers", &tunable_ssl_ciphers }, + { "rsa_private_key_file", &tunable_rsa_private_key_file }, + { "dsa_private_key_file", &tunable_dsa_private_key_file }, +diff --git a/ssl.c b/ssl.c +index 22b69b3..96bf8ad 100644 +--- a/ssl.c ++++ b/ssl.c +@@ -122,7 +122,7 @@ ssl_init(struct vsf_session* p_sess) + { + die("SSL: could not allocate SSL context"); + } +- options = SSL_OP_ALL | SSL_OP_SINGLE_DH_USE; ++ options = SSL_OP_ALL | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE; + if (!tunable_sslv2) + { + options |= SSL_OP_NO_SSLv2; +@@ -244,6 +244,33 @@ + + SSL_CTX_set0_tmp_dh_pkey(p_ctx, DH_get_dh()); + ++ if (tunable_ecdh_param_file) ++ { ++ BIO *bio; ++ EVP_PKEY *ec_params = NULL; ++ ++ if ((bio = BIO_new_file(tunable_ecdh_param_file, "r")) == NULL) ++ die("SSL: cannot load custom ec params"); ++ else ++ { ++ ec_params = PEM_read_bio_Parameters(bio, NULL); ++ BIO_free(bio); ++ ++ if (ec_params != NULL) ++ { ++ if (!SSL_CTX_set1_groups_list(p_ctx, ec_params)) ++ die("SSL: setting custom EC params failed"); ++ } ++ else ++ { ++ die("SSL: getting ec group or key failed"); ++ } ++ } ++ } ++ else ++ { ++ SSL_CTX_set1_groups_list(p_ctx, "P-256"); ++ } + /* Set up ALPN to check for FTP protocol intention of client. */ + SSL_CTX_set_alpn_select_cb(p_ctx, ssl_alpn_callback, p_sess); + /* Set up SNI callback for an optional hostname check. */ +diff --git a/tunables.c b/tunables.c +index 1ea7227..93f85b1 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -141,6 +141,7 @@ const char* tunable_email_password_file; + const char* tunable_rsa_cert_file; + const char* tunable_dsa_cert_file; + const char* tunable_dh_param_file; ++const char* tunable_ecdh_param_file; + const char* tunable_ssl_ciphers; + const char* tunable_rsa_private_key_file; + const char* tunable_dsa_private_key_file; +@@ -290,7 +291,8 @@ tunables_load_defaults() + &tunable_rsa_cert_file); + install_str_setting(0, &tunable_dsa_cert_file); + install_str_setting(0, &tunable_dh_param_file); +- install_str_setting("AES128-SHA:DES-CBC3-SHA:DHE-RSA-AES256-SHA", ++ install_str_setting(0, &tunable_ecdh_param_file); ++ install_str_setting("AES128-SHA:DES-CBC3-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA", + &tunable_ssl_ciphers); + install_str_setting(0, &tunable_rsa_private_key_file); + install_str_setting(0, &tunable_dsa_private_key_file); +diff --git a/tunables.h b/tunables.h +index 3995472..3e2d40c 100644 +--- a/tunables.h ++++ b/tunables.h +@@ -143,6 +143,7 @@ extern const char* tunable_email_password_file; + extern const char* tunable_rsa_cert_file; + extern const char* tunable_dsa_cert_file; + extern const char* tunable_dh_param_file; ++extern const char* tunable_ecdh_param_file; + extern const char* tunable_ssl_ciphers; + extern const char* tunable_rsa_private_key_file; + extern const char* tunable_dsa_private_key_file; +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index ff94eca..e242873 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -899,6 +899,14 @@ ephemeral Diffie-Hellman key exchange in SSL. + + Default: (none - use built in parameters appropriate for certificate key size) + .TP ++.B ecdh_param_file ++This option specifies the location of custom parameters for ephemeral ++Elliptic Curve Diffie-Hellman (ECDH) key exchange. ++ ++Default: (none - use built in parameters, NIST P-256 with OpenSSL 1.0.1 and ++automatically selected curve based on client preferences with OpenSSL 1.0.2 ++and later) ++.TP + .B email_password_file + This option can be used to provide an alternate file for usage by the + .BR secure_email_list_enable +-- +2.14.4 + diff --git a/0023-Add-documentation-for-isolate_-options.-Correct-defa.patch b/0023-Add-documentation-for-isolate_-options.-Correct-defa.patch new file mode 100644 index 0000000..7cc0bfa --- /dev/null +++ b/0023-Add-documentation-for-isolate_-options.-Correct-defa.patch @@ -0,0 +1,63 @@ +From 3d02ef3be17f37baf729e786a8f36af4982f70ad Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 10:52:16 +0100 +Subject: [PATCH 23/59] Add documentation for isolate_* options. Correct + default + +values of max_clients, max_per_ip. +--- + vsftpd.conf.5 | 22 +++++++++++++++++++--- + 1 file changed, 19 insertions(+), 3 deletions(-) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index e242873..31d317f 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -652,6 +652,21 @@ change it with the setting + .BR xferlog_file . + + Default: NO ++.TP ++.B isolate_network ++If enabled, use CLONE_NEWNET to isolate the untrusted processes so that ++they can't do arbitrary connect() and instead have to ask the privileged ++process for sockets ( ++.BR port_promiscuous ++have to be disabled). ++ ++Default: YES ++.TP ++.B isolate ++If enabled, use CLONE_NEWPID and CLONE_NEWIPC to isolate processes to their ++ipc and pid namespaces. So separated processes can not interact with each other. ++ ++Default: YES + + .SH NUMERIC OPTIONS + Below is a list of numeric options. A numeric option must be set to a non +@@ -749,8 +764,9 @@ Default: 077 + .B max_clients + If vsftpd is in standalone mode, this is the maximum number of clients which + may be connected. Any additional clients connecting will get an error message. ++The value 0 switches off the limit. + +-Default: 0 (unlimited) ++Default: 2000 + .TP + .B max_login_fails + After this many login failures, the session is killed. +@@ -760,9 +776,9 @@ Default: 3 + .B max_per_ip + If vsftpd is in standalone mode, this is the maximum number of clients which + may be connected from the same source internet address. A client will get an +-error message if they go over this limit. ++error message if they go over this limit. The value 0 switches off the limit. + +-Default: 0 (unlimited) ++Default: 50 + .TP + .B pasv_max_port + The maximum port to allocate for PASV style data connections. Can be used to +-- +2.14.4 + diff --git a/0024-Introduce-new-return-value-450.patch b/0024-Introduce-new-return-value-450.patch new file mode 100644 index 0000000..86c5f8e --- /dev/null +++ b/0024-Introduce-new-return-value-450.patch @@ -0,0 +1,77 @@ +From 1d5cdf309387ff92988ab17d746f015d833a4b92 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 11:08:52 +0100 +Subject: [PATCH 24/59] Introduce new return value 450: + + *450 Requested file action not taken. + File unavailable (e.g., file busy). +--- + ftpcodes.h | 1 + + postlogin.c | 9 ++++++++- + sysutil.c | 3 +++ + sysutil.h | 3 ++- + 4 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/ftpcodes.h b/ftpcodes.h +index 93290c0..81e25c5 100644 +--- a/ftpcodes.h ++++ b/ftpcodes.h +@@ -52,6 +52,7 @@ + #define FTP_TLS_FAIL 421 + #define FTP_BADSENDCONN 425 + #define FTP_BADSENDNET 426 ++#define FTP_FILETMPFAIL 450 + #define FTP_BADSENDFILE 451 + + #define FTP_BADCMD 500 +diff --git a/postlogin.c b/postlogin.c +index bf12970..29958c0 100644 +--- a/postlogin.c ++++ b/postlogin.c +@@ -679,7 +679,14 @@ handle_retr(struct vsf_session* p_sess, int is_http) + opened_file = str_open(&p_sess->ftp_arg_str, kVSFSysStrOpenReadOnly); + if (vsf_sysutil_retval_is_error(opened_file)) + { +- vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); ++ if (kVSFSysUtilErrAGAIN == vsf_sysutil_get_error()) ++ { ++ vsf_cmdio_write(p_sess, FTP_FILETMPFAIL, "Temporarily failed to open file"); ++ } ++ else ++ { ++ vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); ++ } + return; + } + /* Lock file if required */ +diff --git a/sysutil.c b/sysutil.c +index 9881a66..6d7cb3f 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -1632,6 +1632,9 @@ vsf_sysutil_get_error(void) + case ENOENT: + retval = kVSFSysUtilErrNOENT; + break; ++ case EAGAIN: ++ retval = kVSFSysUtilErrAGAIN; ++ break; + default: + break; + } +diff --git a/sysutil.h b/sysutil.h +index 79b5514..c145bdf 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -18,7 +18,8 @@ enum EVSFSysUtilError + kVSFSysUtilErrINVAL, + kVSFSysUtilErrOPNOTSUPP, + kVSFSysUtilErrACCES, +- kVSFSysUtilErrNOENT ++ kVSFSysUtilErrNOENT, ++ kVSFSysUtilErrAGAIN + }; + enum EVSFSysUtilError vsf_sysutil_get_error(void); + +-- +2.14.4 + diff --git a/0025-Improve-local_max_rate-option.patch b/0025-Improve-local_max_rate-option.patch new file mode 100644 index 0000000..2c74c7a --- /dev/null +++ b/0025-Improve-local_max_rate-option.patch @@ -0,0 +1,90 @@ +From 386db86fe865fb552b1867af4bf4b78dbf9080cf Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 12:44:26 +0100 +Subject: [PATCH 25/59] Improve local_max_rate option. + +Now it should work as expected. +--- + ftpdataio.c | 14 +++++++------- + main.c | 2 +- + session.h | 3 ++- + 3 files changed, 10 insertions(+), 9 deletions(-) + +diff --git a/ftpdataio.c b/ftpdataio.c +index 3e4e9c9..00f9021 100644 +--- a/ftpdataio.c ++++ b/ftpdataio.c +@@ -249,7 +249,7 @@ handle_io(int retval, int fd, void* p_private) + { + long curr_sec; + long curr_usec; +- unsigned int bw_rate; ++ unsigned long bw_rate; + double elapsed; + double pause_time; + double rate_ratio; +@@ -276,19 +276,16 @@ handle_io(int retval, int fd, void* p_private) + { + elapsed = (double) 0.01; + } +- bw_rate = (unsigned int) ((double) retval / elapsed); +- if (bw_rate <= p_sess->bw_rate_max) ++ p_sess->bw_retval += retval; ++ bw_rate = (unsigned long) ((double) p_sess->bw_retval / elapsed); ++ if (bw_rate <= p_sess->bw_rate_max || p_sess->bw_retval < (unsigned long)(10*retval)) + { +- p_sess->bw_send_start_sec = curr_sec; +- p_sess->bw_send_start_usec = curr_usec; + return; + } + /* Tut! Rate exceeded, calculate a pause to bring things back into line */ + rate_ratio = (double) bw_rate / (double) p_sess->bw_rate_max; + pause_time = (rate_ratio - (double) 1) * elapsed; + vsf_sysutil_sleep(pause_time); +- p_sess->bw_send_start_sec = vsf_sysutil_get_time_sec(); +- p_sess->bw_send_start_usec = vsf_sysutil_get_time_usec(); + } + + int +@@ -441,6 +438,9 @@ struct vsf_transfer_ret + vsf_ftpdataio_transfer_file(struct vsf_session* p_sess, int remote_fd, + int file_fd, int is_recv, int is_ascii) + { ++ p_sess->bw_send_start_sec = vsf_sysutil_get_time_sec(); ++ p_sess->bw_send_start_usec = vsf_sysutil_get_time_usec(); ++ p_sess->bw_retval = 0; + if (!is_recv) + { + if (is_ascii || p_sess->data_use_ssl) +diff --git a/main.c b/main.c +index eaba265..f1e2f69 100644 +--- a/main.c ++++ b/main.c +@@ -40,7 +40,7 @@ + /* Control connection */ + 0, 0, 0, 0, 0, 0, + /* Data connection */ +- -1, 0, -1, 0, 0, 0, 0, ++ -1, 0, -1, 0, 0, 0, 0, 0, + /* Login */ + 1, 0, INIT_MYSTR, INIT_MYSTR, + /* Protocol state */ +diff --git a/session.h b/session.h +index 956bfb7..3e8fdd5 100644 +--- a/session.h ++++ b/session.h +@@ -29,9 +29,10 @@ struct vsf_session + struct vsf_sysutil_sockaddr* p_port_sockaddr; + int data_fd; + int data_progress; +- unsigned int bw_rate_max; ++ unsigned long bw_rate_max; + long bw_send_start_sec; + long bw_send_start_usec; ++ unsigned long bw_retval; + + /* Details of the login */ + int is_anonymous; +-- +2.14.4 + diff --git a/0026-Prevent-hanging-in-SIGCHLD-handler.patch b/0026-Prevent-hanging-in-SIGCHLD-handler.patch new file mode 100644 index 0000000..f928cbc --- /dev/null +++ b/0026-Prevent-hanging-in-SIGCHLD-handler.patch @@ -0,0 +1,81 @@ +From 1e65a0a15f819b8bf1b551bd84f71d0da1f5a00c Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 13:02:27 +0100 +Subject: [PATCH 26/59] Prevent hanging in SIGCHLD handler. + +vsftpd can now handle pam_exec.so in pam.d config without hanging +in SIGCHLD handler. +--- + sysutil.c | 4 ++-- + sysutil.h | 2 +- + twoprocess.c | 13 +++++++++++-- + 3 files changed, 14 insertions(+), 5 deletions(-) + +diff --git a/sysutil.c b/sysutil.c +index 6d7cb3f..099748f 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -608,13 +608,13 @@ vsf_sysutil_exit(int exit_code) + } + + struct vsf_sysutil_wait_retval +-vsf_sysutil_wait(void) ++vsf_sysutil_wait(int hang) + { + struct vsf_sysutil_wait_retval retval; + vsf_sysutil_memclr(&retval, sizeof(retval)); + while (1) + { +- int sys_ret = wait(&retval.exit_status); ++ int sys_ret = waitpid(-1, &retval.exit_status, hang ? 0 : WNOHANG); + if (sys_ret < 0 && errno == EINTR) + { + vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); +diff --git a/sysutil.h b/sysutil.h +index c145bdf..13153cd 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -177,7 +177,7 @@ struct vsf_sysutil_wait_retval + int PRIVATE_HANDS_OFF_syscall_retval; + int PRIVATE_HANDS_OFF_exit_status; + }; +-struct vsf_sysutil_wait_retval vsf_sysutil_wait(void); ++struct vsf_sysutil_wait_retval vsf_sysutil_wait(int hang); + int vsf_sysutil_wait_reap_one(void); + int vsf_sysutil_wait_get_retval( + const struct vsf_sysutil_wait_retval* p_waitret); +diff --git a/twoprocess.c b/twoprocess.c +index 33d84dc..b1891e7 100644 +--- a/twoprocess.c ++++ b/twoprocess.c +@@ -47,8 +47,17 @@ static void + handle_sigchld(void* duff) + { + +- struct vsf_sysutil_wait_retval wait_retval = vsf_sysutil_wait(); ++ struct vsf_sysutil_wait_retval wait_retval = vsf_sysutil_wait(0); + (void) duff; ++ if (!vsf_sysutil_wait_get_exitcode(&wait_retval) && ++ !vsf_sysutil_wait_get_retval(&wait_retval)) ++ /* There was nobody to wait for, possibly caused by underlying library ++ * which created a new process through fork()/vfork() and already picked ++ * it up, e.g. by pam_exec.so or integrity check routines for libraries ++ * when FIPS mode is on (nss freebl), which can lead to calling prelink ++ * if the prelink package is installed. ++ */ ++ return; + /* Child died, so we'll do the same! Report it as an error unless the child + * exited normally with zero exit code + */ +@@ -390,7 +399,7 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str, + priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); + if (!p_sess->control_use_ssl) + { +- (void) vsf_sysutil_wait(); ++ (void) vsf_sysutil_wait(1); + } + else + { +-- +2.14.4 + diff --git a/0027-Delete-files-when-upload-fails.patch b/0027-Delete-files-when-upload-fails.patch new file mode 100644 index 0000000..94a00bf --- /dev/null +++ b/0027-Delete-files-when-upload-fails.patch @@ -0,0 +1,138 @@ +From 6224ecc5ac209323baa775880c0602c3fde3590a Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 13:10:41 +0100 +Subject: [PATCH 27/59] Delete files when upload fails. + +Previously the uploaded file wasn't removed when the network was +disconnected. Now it is successfully deleted. +--- + ftpcodes.h | 3 ++- + ftpdataio.c | 8 ++++++++ + main.c | 2 +- + postlogin.c | 9 ++++++++- + session.h | 1 + + sysutil.c | 10 ++++++++++ + sysutil.h | 1 + + 7 files changed, 31 insertions(+), 3 deletions(-) + +diff --git a/ftpcodes.h b/ftpcodes.h +index 81e25c5..54dfae7 100644 +--- a/ftpcodes.h ++++ b/ftpcodes.h +@@ -15,7 +15,8 @@ + #define FTP_PBSZOK 200 + #define FTP_PROTOK 200 + #define FTP_OPTSOK 200 +-#define FTP_ALLOOK 202 ++#define FTP_ALLOOK 200 ++#define FTP_ALLOIGN 202 + #define FTP_FEAT 211 + #define FTP_STATOK 211 + #define FTP_SIZEOK 213 +diff --git a/ftpdataio.c b/ftpdataio.c +index 00f9021..c859d80 100644 +--- a/ftpdataio.c ++++ b/ftpdataio.c +@@ -242,6 +242,10 @@ init_data_sock_params(struct vsf_session* p_sess, int sock_fd) + /* Start the timeout monitor */ + vsf_sysutil_install_io_handler(handle_io, p_sess); + start_data_alarm(p_sess); ++ if(tunable_delete_failed_uploads) ++ { ++ vsf_sysutil_rcvtimeo(sock_fd); ++ } + } + + static void +@@ -615,6 +619,10 @@ do_file_recv(struct vsf_session* p_sess, int file_fd, int is_ascii) + else if (retval == 0 && !prev_cr) + { + /* Transfer done, nifty */ ++ if (tunable_delete_failed_uploads && ++ !is_ascii && p_sess->upload_size > 0 && ++ p_sess->upload_size != ret_struct.transferred) ++ ret_struct.retval = -2; + return ret_struct; + } + num_to_write = (unsigned int) retval; +diff --git a/main.c b/main.c +index f1e2f69..f039081 100644 +--- a/main.c ++++ b/main.c +@@ -44,7 +44,7 @@ main(int argc, const char* argv[]) + /* Login */ + 1, 0, INIT_MYSTR, INIT_MYSTR, + /* Protocol state */ +- 0, 1, INIT_MYSTR, 0, 0, ++ 0, 0, 1, INIT_MYSTR, 0, 0, + /* HTTP hacks */ + 0, INIT_MYSTR, + /* Session state */ +diff --git a/postlogin.c b/postlogin.c +index 29958c0..e473c34 100644 +--- a/postlogin.c ++++ b/postlogin.c +@@ -356,7 +356,14 @@ process_post_login(struct vsf_session* p_sess) + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "ALLO")) + { +- vsf_cmdio_write(p_sess, FTP_ALLOOK, "ALLO command ignored."); ++ if (tunable_delete_failed_uploads && !p_sess->is_ascii) ++ { ++ p_sess->upload_size = (filesize_t)vsf_sysutil_atoi(str_getbuf(&p_sess->ftp_cmd_str)+5); ++ vsf_cmdio_write(p_sess, FTP_ALLOOK, "The filesize has been allocated."); ++ } ++ else { ++ vsf_cmdio_write(p_sess, FTP_ALLOIGN, "ALLO command ignored."); ++ } + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "REIN")) + { +diff --git a/session.h b/session.h +index 3e8fdd5..4eccf46 100644 +--- a/session.h ++++ b/session.h +@@ -41,6 +41,7 @@ struct vsf_session + struct mystr anon_pass_str; + + /* Details of the FTP protocol state */ ++ filesize_t upload_size; + filesize_t restart_pos; + int is_ascii; + struct mystr rnfr_filename_str; +diff --git a/sysutil.c b/sysutil.c +index 099748f..42bcdf8 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -680,6 +680,16 @@ vsf_sysutil_activate_keepalive(int fd) + } + } + ++void ++vsf_sysutil_rcvtimeo(int fd) ++{ ++ struct timeval tv; ++ ++ tv.tv_sec = tunable_data_connection_timeout; ++ tv.tv_usec = 0; ++ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)); ++} ++ + void + vsf_sysutil_activate_reuseaddr(int fd) + { +diff --git a/sysutil.h b/sysutil.h +index 13153cd..2886bbc 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -266,6 +266,7 @@ void vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, + const char* p_name); + /* Option setting on sockets */ + void vsf_sysutil_activate_keepalive(int fd); ++void vsf_sysutil_rcvtimeo(int fd); + void vsf_sysutil_set_iptos_throughput(int fd); + void vsf_sysutil_activate_reuseaddr(int fd); + void vsf_sysutil_set_nodelay(int fd); +-- +2.14.4 + diff --git a/0028-Fix-man-page-rendering.patch b/0028-Fix-man-page-rendering.patch new file mode 100644 index 0000000..e91d6dc --- /dev/null +++ b/0028-Fix-man-page-rendering.patch @@ -0,0 +1,26 @@ +From ea99be1a7a5973bbe8ed798b65abe5ce3b92f5df Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 13:12:52 +0100 +Subject: [PATCH 28/59] Fix man page rendering. + +--- + vsftpd.conf.5 | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 31d317f..cf1ae34 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -495,7 +495,8 @@ Default: NO + .TP + .B ssl_request_cert + If enabled, vsftpd will request (but not necessarily require; see +-.BR require_cert) a certificate on incoming SSL connections. Normally this ++.BR require_cert ) ++a certificate on incoming SSL connections. Normally this + should not cause any trouble at all, but IBM zOS seems to have issues. + (New in v2.0.7). + +-- +2.14.4 + diff --git a/0029-Fix-segfault-in-config-file-parser.patch b/0029-Fix-segfault-in-config-file-parser.patch new file mode 100644 index 0000000..65cb571 --- /dev/null +++ b/0029-Fix-segfault-in-config-file-parser.patch @@ -0,0 +1,25 @@ +From 34b9e1d10c6be736f1b20be8795c655446f38c5e Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 13:14:55 +0100 +Subject: [PATCH 29/59] Fix segfault in config file parser. + +--- + str.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/str.c b/str.c +index 41b27db..82b8ae4 100644 +--- a/str.c ++++ b/str.c +@@ -113,7 +113,7 @@ str_strdup_trimmed(const struct mystr* p_str) + for (h = 0; h < (int)str_getlen(p_str) && vsf_sysutil_isspace(p_trimmed[h]); h++) ; + for (t = str_getlen(p_str) - 1; t >= 0 && vsf_sysutil_isspace(p_trimmed[t]); t--) ; + newlen = t - h + 1; +- return newlen ? vsf_sysutil_strndup(p_trimmed+h, (unsigned int)newlen) : 0L; ++ return (newlen > 0) ? vsf_sysutil_strndup(p_trimmed+h, (unsigned int)newlen) : 0L; + } + + void +-- +2.14.4 + diff --git a/0030-Fix-logging-into-syslog-when-enabled-in-config.patch b/0030-Fix-logging-into-syslog-when-enabled-in-config.patch new file mode 100644 index 0000000..04669c7 --- /dev/null +++ b/0030-Fix-logging-into-syslog-when-enabled-in-config.patch @@ -0,0 +1,25 @@ +From 03ff061f18f555d7bec62fa6a597a275b4b3f1c7 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 13:18:22 +0100 +Subject: [PATCH 30/59] Fix logging into syslog when enabled in config. + +--- + logging.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/logging.c b/logging.c +index 99671b4..c4461f7 100644 +--- a/logging.c ++++ b/logging.c +@@ -32,7 +32,7 @@ vsf_log_init(struct vsf_session* p_sess) + { + if (tunable_syslog_enable || tunable_tcp_wrappers) + { +- vsf_sysutil_openlog(1); ++ vsf_sysutil_openlog(0); + } + if (!tunable_xferlog_enable && !tunable_dual_log_enable) + { +-- +2.14.4 + diff --git a/0031-Fix-question-mark-wildcard-withing-a-file-name.patch b/0031-Fix-question-mark-wildcard-withing-a-file-name.patch new file mode 100644 index 0000000..acc8f6d --- /dev/null +++ b/0031-Fix-question-mark-wildcard-withing-a-file-name.patch @@ -0,0 +1,28 @@ +From 0da42468ac9518a544aad57d22d7697d6bdfa969 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 13:25:12 +0100 +Subject: [PATCH 31/59] Fix question mark wildcard withing a file name. + +Previously '?' worked only at the end of a file name, now it can +be used anywhere. +--- + ls.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/ls.c b/ls.c +index 3c0988c..35c15c7 100644 +--- a/ls.c ++++ b/ls.c +@@ -459,7 +459,8 @@ vsf_filename_passes_filter(const struct mystr* p_filename_str, + must_match_at_current_pos = 0; + } + } while (locate_result.found && +- str_getlen(&name_remain_str) > 0 && last_token != '*'); ++ str_getlen(&name_remain_str) > 0 && ++ last_token != '*' && last_token != '?'); + } + /* Any incoming string left means no match unless we ended on the correct + * type of wildcard. +-- +2.14.4 + diff --git a/0032-Propagate-errors-from-nfs-with-quota-to-client.patch b/0032-Propagate-errors-from-nfs-with-quota-to-client.patch new file mode 100644 index 0000000..de56aa7 --- /dev/null +++ b/0032-Propagate-errors-from-nfs-with-quota-to-client.patch @@ -0,0 +1,147 @@ +From aa9cb48373018502ef99a57aad70b69c0c75ff65 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Thu, 17 Nov 2016 13:29:59 +0100 +Subject: [PATCH 32/59] Propagate errors from nfs with quota to client. + +vsftpd now checks for errors when closing newly uploaded file and +forward errors to the client (e.g. when file system quota was +exceeded) +--- + ftpcodes.h | 1 + + postlogin.c | 32 ++++++++++++++++++++++++++++++-- + sysutil.c | 21 +++++++++++++++++++++ + sysutil.h | 1 + + 4 files changed, 53 insertions(+), 2 deletions(-) + +diff --git a/ftpcodes.h b/ftpcodes.h +index 54dfae7..97801f3 100644 +--- a/ftpcodes.h ++++ b/ftpcodes.h +@@ -74,6 +74,7 @@ + #define FTP_NOHANDLEPROT 536 + #define FTP_FILEFAIL 550 + #define FTP_NOPERM 550 ++#define FTP_DISKQUOTA 552 + #define FTP_UPLOADFAIL 553 + + #endif /* VSF_FTPCODES_H */ +diff --git a/postlogin.c b/postlogin.c +index e473c34..8363c9c 100644 +--- a/postlogin.c ++++ b/postlogin.c +@@ -28,6 +28,8 @@ + #include "vsftpver.h" + #include "opts.h" + ++#include ++ + /* Private local functions */ + static void handle_pwd(struct vsf_session* p_sess); + static void handle_cwd(struct vsf_session* p_sess); +@@ -1035,8 +1037,10 @@ handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique) + struct vsf_transfer_ret trans_ret; + int new_file_fd; + int remote_fd; ++ int close_errno; + int success = 0; + int created = 0; ++ int closed = 0; + int do_truncate = 0; + filesize_t offset = p_sess->restart_pos; + p_sess->restart_pos = 0; +@@ -1149,6 +1153,18 @@ handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique) + trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, + new_file_fd, 1, 0); + } ++ ++ /* Need to check close operation here because some errors ++ * like EIO, EDQUOT, ENOSPC can be detected only on close ++ * when using NFS ++ */ ++ close_errno = vsf_sysutil_close_errno(new_file_fd); ++ closed = 1; ++ if (close_errno != 0) ++ { ++ trans_ret.retval = -1; ++ } ++ + if (vsf_ftpdataio_dispose_transfer_fd(p_sess) != 1 && trans_ret.retval == 0) + { + trans_ret.retval = -2; +@@ -1161,7 +1177,16 @@ handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique) + } + if (trans_ret.retval == -1) + { +- vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure writing to local file."); ++ /* Disk quota exceeded */ ++ if (close_errno == EDQUOT) ++ { ++ vsf_cmdio_write(p_sess, FTP_DISKQUOTA, "Disk quota exceeded."); ++ } ++ /* any other local error */ ++ else ++ { ++ vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure writing to local file."); ++ } + } + else if (trans_ret.retval == -2) + { +@@ -1183,7 +1208,10 @@ port_pasv_cleanup_out: + { + str_unlink(p_filename); + } +- vsf_sysutil_close(new_file_fd); ++ if (!closed) ++ { ++ vsf_sysutil_close(new_file_fd); ++ } + } + + static void +diff --git a/sysutil.c b/sysutil.c +index 42bcdf8..1c0422e 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -1268,6 +1268,27 @@ vsf_sysutil_close(int fd) + } + } + ++int ++vsf_sysutil_close_errno(int fd) ++{ ++ while (1) ++ { ++ int retval = close(fd); ++ if (retval != 0) ++ { ++ if (errno == EINTR) ++ { ++ vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); ++ continue; ++ } ++ else { ++ return errno; ++ } ++ } ++ return 0; ++ } ++} ++ + int + vsf_sysutil_close_failok(int fd) + { +diff --git a/sysutil.h b/sysutil.h +index 2886bbc..be727f5 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -92,6 +92,7 @@ int vsf_sysutil_create_or_open_file_append(const char* p_filename, + int vsf_sysutil_create_or_open_file(const char* p_filename, unsigned int mode); + void vsf_sysutil_dupfd2(int old_fd, int new_fd); + void vsf_sysutil_close(int fd); ++int vsf_sysutil_close_errno(int fd); + int vsf_sysutil_close_failok(int fd); + int vsf_sysutil_unlink(const char* p_dead); + int vsf_sysutil_write_access(const char* p_filename); +-- +2.14.4 + diff --git a/0034-Turn-off-seccomp-sandbox-because-it-is-too-strict.patch b/0034-Turn-off-seccomp-sandbox-because-it-is-too-strict.patch new file mode 100644 index 0000000..0c0bdb7 --- /dev/null +++ b/0034-Turn-off-seccomp-sandbox-because-it-is-too-strict.patch @@ -0,0 +1,25 @@ +From 4922e60589326540b2ee4f0bdfd6cb95f645f3d5 Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Fri, 18 Nov 2016 10:23:29 +0100 +Subject: [PATCH 34/59] Turn off seccomp sandbox, because it is too strict. + +--- + tunables.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tunables.c b/tunables.c +index 78f2bcd..5440c00 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -237,7 +237,7 @@ tunables_load_defaults() + tunable_isolate_network = 1; + tunable_ftp_enable = 1; + tunable_http_enable = 0; +- tunable_seccomp_sandbox = 1; ++ tunable_seccomp_sandbox = 0; + tunable_allow_writeable_chroot = 0; + + tunable_accept_timeout = 60; +-- +2.14.4 + diff --git a/0036-Redefine-VSFTP_COMMAND_FD-to-1.patch b/0036-Redefine-VSFTP_COMMAND_FD-to-1.patch new file mode 100644 index 0000000..4299b23 --- /dev/null +++ b/0036-Redefine-VSFTP_COMMAND_FD-to-1.patch @@ -0,0 +1,29 @@ +From 18e0ab25a0d66088728b506cf64f5545637eda26 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 5 Sep 2017 14:26:08 +0200 +Subject: [PATCH 36/59] Redefine VSFTP_COMMAND_FD to 1 + +Redefine VSFTP_COMMAND_FD to 1 (stdout) so that error messages generated +during startup are picked up by systemd. + +Resolves: rhbz#1443055 +--- + defs.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/defs.h b/defs.h +index bde3232..315f0f0 100644 +--- a/defs.h ++++ b/defs.h +@@ -3,7 +3,7 @@ + + #define VSFTP_DEFAULT_CONFIG "/etc/vsftpd/vsftpd.conf" + +-#define VSFTP_COMMAND_FD 0 ++#define VSFTP_COMMAND_FD 1 + + #define VSFTP_PASSWORD_MAX 128 + #define VSFTP_USERNAME_MAX 128 +-- +2.14.4 + diff --git a/0037-Document-the-relationship-of-text_userdb_names-and-c.patch b/0037-Document-the-relationship-of-text_userdb_names-and-c.patch new file mode 100644 index 0000000..ae188d7 --- /dev/null +++ b/0037-Document-the-relationship-of-text_userdb_names-and-c.patch @@ -0,0 +1,29 @@ +From 221f35f302d53f5a89f8e79592492e7cb322e81a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Thu, 26 Oct 2017 13:08:32 +0200 +Subject: [PATCH 37/59] Document the relationship of text_userdb_names and + chroot_local_user + +Note in vsftpd.conf(5) that text_userdb_names may not work when +chroot_local_user is set to YES. +--- + vsftpd.conf.5 | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index a3d569e..45b3f9c 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -578,6 +578,9 @@ Default: NO + By default, numeric IDs are shown in the user and group fields of directory + listings. You can get textual names by enabling this parameter. It is off + by default for performance reasons. ++Note that textual names are not guaranteed when ++.BR chroot_local_user ++is set to YES. + + Default: NO + .TP +-- +2.14.4 + diff --git a/0038-Document-allow_writeable_chroot-in-the-man-page.patch b/0038-Document-allow_writeable_chroot-in-the-man-page.patch new file mode 100644 index 0000000..ca073d3 --- /dev/null +++ b/0038-Document-allow_writeable_chroot-in-the-man-page.patch @@ -0,0 +1,32 @@ +From 35ec3be5427a54facd5f6299fda2da4c146d4846 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Fri, 24 Nov 2017 11:22:43 +0100 +Subject: [PATCH 38/59] Document allow_writeable_chroot in the man page + +--- + vsftpd.conf.5 | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 45b3f9c..d1f0db5 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -56,6 +56,15 @@ Only applies if + is active. If set to YES, anonymous users will be allowed to use secured SSL + connections. + ++Default: NO ++.TP ++.B allow_writeable_chroot ++Allow chroot()'ing a user to a directory writable by that user. Note that ++setting this to YES is potentially dangerous. For example, if the user ++creates an 'etc' directory in the new root directory, they could potentially ++trick the C library into loading a user-created configuration file from the ++/etc/ directory. ++ + Default: NO + .TP + .B anon_mkdir_write_enable +-- +2.14.4 + diff --git a/0039-Improve-documentation-of-ASCII-mode-in-the-man-page.patch b/0039-Improve-documentation-of-ASCII-mode-in-the-man-page.patch new file mode 100644 index 0000000..307ce35 --- /dev/null +++ b/0039-Improve-documentation-of-ASCII-mode-in-the-man-page.patch @@ -0,0 +1,34 @@ +From 7d4b76abb437184fa692533cb5537318026a30e8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Fri, 24 Nov 2017 11:26:37 +0100 +Subject: [PATCH 39/59] Improve documentation of ASCII mode in the man page + +--- + vsftpd.conf.5 | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index d1f0db5..3ca55e4 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -113,11 +113,17 @@ Default: YES + .TP + .B ascii_download_enable + When enabled, ASCII mode data transfers will be honoured on downloads. ++When disabled, the server will pretend to allow ASCII mode but in fact ++ignore the request. Turn this option on to have the server actually do ++ASCII mangling on files when in ASCII mode. + + Default: NO + .TP + .B ascii_upload_enable + When enabled, ASCII mode data transfers will be honoured on uploads. ++See also ++.BR ascii_download_enable ++for more details. + + Default: NO + .TP +-- +2.14.4 + diff --git a/0040-Use-system-wide-crypto-policy.patch b/0040-Use-system-wide-crypto-policy.patch new file mode 100644 index 0000000..940a5b2 --- /dev/null +++ b/0040-Use-system-wide-crypto-policy.patch @@ -0,0 +1,27 @@ +From b83be8b4f86bf1a8a6de4802a9486d084c4a46cd Mon Sep 17 00:00:00 2001 +From: Martin Sehnoutka +Date: Tue, 29 Aug 2017 10:32:16 +0200 +Subject: [PATCH 40/59] Use system wide crypto policy + +Resolves: rhbz# +--- + tunables.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/tunables.c b/tunables.c +index 5440c00..354251c 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -297,8 +297,7 @@ tunables_load_defaults() + install_str_setting(0, &tunable_dsa_cert_file); + install_str_setting(0, &tunable_dh_param_file); + install_str_setting(0, &tunable_ecdh_param_file); +- install_str_setting("AES128-SHA:DES-CBC3-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA", +- &tunable_ssl_ciphers); ++ install_str_setting("PROFILE=SYSTEM", &tunable_ssl_ciphers); + install_str_setting(0, &tunable_rsa_private_key_file); + install_str_setting(0, &tunable_dsa_private_key_file); + install_str_setting(0, &tunable_ca_certs_file); +-- +2.14.4 + diff --git a/0041-Document-the-new-default-for-ssl_ciphers-in-the-man-.patch b/0041-Document-the-new-default-for-ssl_ciphers-in-the-man-.patch new file mode 100644 index 0000000..93e2ce8 --- /dev/null +++ b/0041-Document-the-new-default-for-ssl_ciphers-in-the-man-.patch @@ -0,0 +1,31 @@ +From 2369d1ea5144d525d315aba90da528e7d9bfd1cc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Thu, 21 Dec 2017 14:19:18 +0100 +Subject: [PATCH 41/59] Document the new default for ssl_ciphers in the man + page + +Related: rhbz#1483970 +--- + vsftpd.conf.5 | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 3ca55e4..2a7662e 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -1078,7 +1078,11 @@ man page for further details. Note that restricting ciphers can be a useful + security precaution as it prevents malicious remote parties forcing a cipher + which they have found problems with. + +-Default: DES-CBC3-SHA ++By default, the system-wide crypto policy is used. See ++.BR update-crypto-policies(8) ++for further details. ++ ++Default: PROFILE=SYSTEM + .TP + .B ssl_sni_hostname + If set, SSL connections will be rejected unless the SNI hostname in the +-- +2.14.4 + diff --git a/0044-Disable-anonymous_enable-in-default-config-file.patch b/0044-Disable-anonymous_enable-in-default-config-file.patch new file mode 100644 index 0000000..4e62d76 --- /dev/null +++ b/0044-Disable-anonymous_enable-in-default-config-file.patch @@ -0,0 +1,26 @@ +From ffaeebcfdb56ba75392af21c68c0bac78a226b55 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 2 Jan 2018 09:54:43 +0100 +Subject: [PATCH 44/59] Disable anonymous_enable in default config file + +Resolves: rhbz#1338637 +--- + vsftpd.conf | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/vsftpd.conf b/vsftpd.conf +index 39d1955..4626c1b 100644 +--- a/vsftpd.conf ++++ b/vsftpd.conf +@@ -9,7 +9,7 @@ + # capabilities. + # + # Allow anonymous FTP? (Beware - allowed by default if you comment this out). +-anonymous_enable=YES ++anonymous_enable=NO + # + # Uncomment this to allow local users to log in. + # When SELinux is enforcing check for SE bool ftp_home_dir +-- +2.14.4 + diff --git a/0045-Expand-explanation-of-ascii_-options-behaviour-in-ma.patch b/0045-Expand-explanation-of-ascii_-options-behaviour-in-ma.patch new file mode 100644 index 0000000..2243790 --- /dev/null +++ b/0045-Expand-explanation-of-ascii_-options-behaviour-in-ma.patch @@ -0,0 +1,52 @@ +From 61327320b54a59e319c522151f7a61c74ec94f2f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 2 Jan 2018 16:25:55 +0100 +Subject: [PATCH 45/59] Expand explanation of ascii_* options behaviour in man + page + +--- + vsftpd.conf.5 | 22 ++++++++++++++++++---- + 1 file changed, 18 insertions(+), 4 deletions(-) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index df14027..a5abeb2 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -114,16 +114,30 @@ Default: YES + .B ascii_download_enable + When enabled, ASCII mode data transfers will be honoured on downloads. + When disabled, the server will pretend to allow ASCII mode but in fact +-ignore the request. Turn this option on to have the server actually do ++ignore requests to activate it. So the client will think the ASCII mode ++is active and therefore may still translate any ++.BR ++character sequences in the received file. See the following article for ++a detailed explanation of the behaviour: ++https://access.redhat.com/articles/3250241. ++ ++Turn this option on to have the server actually do + ASCII mangling on files when in ASCII mode. + + Default: NO + .TP + .B ascii_upload_enable + When enabled, ASCII mode data transfers will be honoured on uploads. +-See also +-.BR ascii_download_enable +-for more details. ++When disabled, the server will pretend to allow ASCII mode but in fact ++ignore requests to activate it. So the client will think the ASCII mode ++is active and will translate native line terminators to the standard ++.BR ++line terminators for transmission, but the server will not do ++any translation. See the following article for a detailed explanation ++of the behaviour: https://access.redhat.com/articles/3250241. ++ ++Turn this option on to have the server actually do ++ASCII mangling on files when in ASCII mode. + + Default: NO + .TP +-- +2.14.4 + diff --git a/0046-vsftpd.conf-Refer-to-the-man-page-regarding-the-asci.patch b/0046-vsftpd.conf-Refer-to-the-man-page-regarding-the-asci.patch new file mode 100644 index 0000000..61ed691 --- /dev/null +++ b/0046-vsftpd.conf-Refer-to-the-man-page-regarding-the-asci.patch @@ -0,0 +1,27 @@ +From 446f7c1ec54e06b5da2e890e0cd8fbd7308322c9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 2 Jan 2018 16:33:18 +0100 +Subject: [PATCH 46/59] vsftpd.conf: Refer to the man page regarding the + ascii_* options + +--- + vsftpd.conf | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/vsftpd.conf b/vsftpd.conf +index 4626c1b..e70bc6d 100644 +--- a/vsftpd.conf ++++ b/vsftpd.conf +@@ -73,7 +73,8 @@ xferlog_std_format=YES + # + # By default the server will pretend to allow ASCII mode but in fact ignore + # the request. Turn on the below options to have the server actually do ASCII +-# mangling on files when in ASCII mode. ++# mangling on files when in ASCII mode. The vsftpd.conf(5) man page explains ++# the behaviour when these options are disabled. + # Beware that on some FTP servers, ASCII support allows a denial of service + # attack (DoS) via the command "SIZE /big/file" in ASCII mode. vsftpd + # predicted this attack and has always been safe, reporting the size of the +-- +2.14.4 + diff --git a/0047-Disable-tcp_wrappers-support.patch b/0047-Disable-tcp_wrappers-support.patch new file mode 100644 index 0000000..f71aab0 --- /dev/null +++ b/0047-Disable-tcp_wrappers-support.patch @@ -0,0 +1,49 @@ +From b383ec42bb750419fea102fccf36af5216145eb2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Fri, 5 Jan 2018 09:17:13 +0100 +Subject: [PATCH 47/59] Disable tcp_wrappers support + +Resolves: rhbz#1518796 +--- + Makefile | 2 +- + builddefs.h | 1 - + vsftpd.conf | 1 - + 3 files changed, 1 insertion(+), 3 deletions(-) + +diff --git a/Makefile b/Makefile +index 98118dc..612994e 100644 +--- a/Makefile ++++ b/Makefile +@@ -8,7 +8,7 @@ CFLAGS = -O2 -fPIE -fstack-protector --param=ssp-buffer-size=4 \ + -D_FORTIFY_SOURCE=2 \ + #-pedantic -Wconversion + +-LIBS = -lwrap -lnsl -lpam -lcap -ldl -lcrypto ++LIBS = -lnsl -lpam -lcap -ldl -lcrypto + LINK = -Wl,-s + LDFLAGS = -fPIE -pie -Wl,-z,relro -Wl,-z,now + +diff --git a/builddefs.h b/builddefs.h +index 83de674..2aa3a4c 100644 +--- a/builddefs.h ++++ b/builddefs.h +@@ -1,7 +1,6 @@ + #ifndef VSF_BUILDDEFS_H + #define VSF_BUILDDEFS_H + +-#define VSF_BUILD_TCPWRAPPERS + #define VSF_BUILD_PAM + #define VSF_BUILD_SSL + +diff --git a/vsftpd.conf b/vsftpd.conf +index e70bc6d..6b8eebb 100644 +--- a/vsftpd.conf ++++ b/vsftpd.conf +@@ -125,4 +125,3 @@ listen_ipv6=YES + + pam_service_name=vsftpd + userlist_enable=YES +-tcp_wrappers=YES +-- +2.14.4 + diff --git a/0048-Fix-default-value-of-strict_ssl_read_eof-in-man-page.patch b/0048-Fix-default-value-of-strict_ssl_read_eof-in-man-page.patch new file mode 100644 index 0000000..513e128 --- /dev/null +++ b/0048-Fix-default-value-of-strict_ssl_read_eof-in-man-page.patch @@ -0,0 +1,29 @@ +From 9cba9e81aa96e1d64ae2eaaf88330e09dadfce79 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Fri, 5 Jan 2018 09:40:09 +0100 +Subject: [PATCH 48/59] Fix default value of strict_ssl_read_eof in man page + +--- + vsftpd.conf.5 | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index a5abeb2..43b0435 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -574,10 +574,9 @@ Default: YES + .B strict_ssl_read_eof + If enabled, SSL data uploads are required to terminate via SSL, not an + EOF on the socket. This option is required to be sure that an attacker did +-not terminate an upload prematurely with a faked TCP FIN. Unfortunately, it +-is not enabled by default because so few clients get it right. (New in v2.0.7). ++not terminate an upload prematurely with a faked TCP FIN. (New in v2.0.7). + +-Default: NO ++Default: YES + .TP + .B strict_ssl_write_shutdown + If enabled, SSL data downloads are required to terminate via SSL, not an +-- +2.14.4 + diff --git a/0049-Add-new-filename-generation-algorithm-for-STOU-comma.patch b/0049-Add-new-filename-generation-algorithm-for-STOU-comma.patch new file mode 100644 index 0000000..22745b5 --- /dev/null +++ b/0049-Add-new-filename-generation-algorithm-for-STOU-comma.patch @@ -0,0 +1,322 @@ +From 1203b943b369651d96d057f8190f14f015e6ff0b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 6 Feb 2018 13:30:44 +0100 +Subject: [PATCH 49/59] Add new filename generation algorithm for STOU command + +A new configuration option 'better_stou' can be used to enable +a better algorithm for generating unique filenames. + +Resolves: rhbz#1479237 +--- + parseconf.c | 1 + + postlogin.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++++--------- + sysutil.c | 3 + + sysutil.h | 3 +- + tunables.c | 2 + + tunables.h | 3 + + vsftpd.conf.5 | 5 ++ + 7 files changed, 166 insertions(+), 27 deletions(-) + +diff --git a/parseconf.c b/parseconf.c +index 33a1349..47b54f1 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -111,6 +111,7 @@ parseconf_bool_array[] = + { "http_enable", &tunable_http_enable }, + { "seccomp_sandbox", &tunable_seccomp_sandbox }, + { "allow_writeable_chroot", &tunable_allow_writeable_chroot }, ++ { "better_stou", &tunable_better_stou }, + { 0, 0 } + }; + +diff --git a/postlogin.c b/postlogin.c +index 8363c9c..7c749ef 100644 +--- a/postlogin.c ++++ b/postlogin.c +@@ -29,6 +29,7 @@ + #include "opts.h" + + #include ++#include + + /* Private local functions */ + static void handle_pwd(struct vsf_session* p_sess); +@@ -1028,6 +1029,114 @@ handle_stor(struct vsf_session* p_sess) + handle_upload_common(p_sess, 0, 0); + } + ++/* Based on __gen_tempname() from glibc - thanks, glibc! Relicensed ++ * from LGPL2.1+ to GPL2. ++ */ ++static int ++create_unique_file(struct vsf_session* p_sess, struct mystr* p_outstr, ++ const struct mystr* p_base_str, ++ int (*access_checker)(const struct mystr*)) ++{ ++ struct mystr s_result = INIT_MYSTR; ++ const int suffix_len = 6; ++ unsigned int count; ++ static unsigned long long int value; ++ unsigned long long int random_time_bits; ++ int fd = -1; ++ /* These are the characters used in temporary file names. */ ++ struct mystr s_letters = INIT_MYSTR; ++ unsigned int s_letters_len; ++ int base_len; ++ ++ str_alloc_text(&s_letters, ++ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); ++ s_letters_len = str_getlen(&s_letters); ++ ++ /* A lower bound on the number of temporary files to attempt to ++ generate. The maximum total number of temporary file names that ++ can exist for a given template is 62**6. It should never be ++ necessary to try all of these combinations. Instead if a reasonable ++ number of names is tried (we define reasonable as 62**3) fail to ++ give the system administrator the chance to remove the problems. */ ++#define ATTEMPTS_MIN (62 * 62 * 62) ++ ++ /* The number of times to attempt to generate a temporary file. */ ++#if ATTEMPTS_MIN < TMP_MAX ++ unsigned int attempts = TMP_MAX; ++#else ++ unsigned int attempts = ATTEMPTS_MIN; ++#endif ++#undef ATTEMPTS_MIN ++ ++ { ++ long sec = vsf_sysutil_get_time_sec(); ++ long usec = vsf_sysutil_get_time_usec(); ++ random_time_bits = ((unsigned long long int) usec << 16) ^ sec; ++ value += random_time_bits ^ vsf_sysutil_getpid(); ++ } ++ ++ if (str_isempty(p_base_str)) ++ { ++ const char *base = "STOU."; ++ base_len = vsf_sysutil_strlen(base); ++ str_reserve(&s_result, base_len + suffix_len); ++ str_alloc_text(&s_result, base); ++ } ++ else ++ { ++ str_reserve(&s_result, str_getlen(p_base_str) + suffix_len + 1); ++ str_copy(&s_result, p_base_str); ++ str_append_char(&s_result, '.'); ++ base_len = str_getlen(&s_result); ++ } ++ ++ for (count = 0; count < attempts; value += 7777, ++count) ++ { ++ unsigned long long v = value; ++ str_trunc(&s_result, base_len); ++ for (int i = 0; i < suffix_len; ++i) ++ { ++ char c; ++ c = str_get_char_at(&s_letters, v % s_letters_len); ++ v /= s_letters_len; ++ str_append_char(&s_result, c); ++ } ++ if (!access_checker(&s_result)) ++ { ++ /* If we generate a filename which is not allowed, we fail immediatelly, ++ * without trying any other possibilities. This is to prevent attackers ++ * from keeping us busy. ++ */ ++ vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); ++ break; ++ } ++ fd = str_create_exclusive(&s_result); ++ if (vsf_sysutil_retval_is_error(fd)) ++ { ++ if (kVSFSysUtilErrEXIST == vsf_sysutil_get_error()) ++ { ++ continue; ++ } ++ else ++ { ++ vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not create file."); ++ break; ++ } ++ } ++ else ++ { ++ break; ++ } ++ } ++ if (!vsf_sysutil_retval_is_error(fd)) ++ { ++ str_copy(p_outstr, &s_result); ++ } ++ str_free(&s_letters); ++ str_free(&s_result); ++ return fd; ++} ++ + static void + handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique) + { +@@ -1049,41 +1158,56 @@ handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique) + return; + } + resolve_tilde(&p_sess->ftp_arg_str, p_sess); +- p_filename = &p_sess->ftp_arg_str; +- if (is_unique) +- { +- get_unique_filename(&s_filename, p_filename); +- p_filename = &s_filename; +- } + vsf_log_start_entry(p_sess, kVSFLogEntryUpload); + str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&p_sess->log_str); +- if (!vsf_access_check_file(p_filename)) +- { +- vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); +- return; +- } +- /* NOTE - actual file permissions will be governed by the tunable umask */ +- /* XXX - do we care about race between create and chown() of anonymous +- * upload? +- */ +- if (is_unique || (p_sess->is_anonymous && !tunable_anon_other_write_enable)) ++ p_filename = &p_sess->ftp_arg_str; ++ if (is_unique && tunable_better_stou) + { +- new_file_fd = str_create_exclusive(p_filename); ++ new_file_fd = create_unique_file(p_sess, &s_filename, p_filename, ++ vsf_access_check_file); ++ if (vsf_sysutil_retval_is_error(new_file_fd)) ++ { ++ return; ++ } ++ p_filename = &s_filename; + } + else + { +- /* For non-anonymous, allow open() to overwrite or append existing files */ +- new_file_fd = str_create(p_filename); +- if (!is_append && offset == 0) ++ if (is_unique) + { +- do_truncate = 1; ++ get_unique_filename(&s_filename, p_filename); ++ p_filename = &s_filename; ++ } ++ if (!vsf_access_check_file(p_filename)) ++ { ++ vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); ++ return; ++ } ++ /* NOTE - actual file permissions will be governed by the tunable umask */ ++ /* XXX - do we care about race between create and chown() of anonymous ++ * upload? ++ */ ++ if (is_unique || (p_sess->is_anonymous && !tunable_anon_other_write_enable)) ++ { ++ new_file_fd = str_create_exclusive(p_filename); ++ } ++ else ++ { ++ /* For non-anonymous, allow open() to overwrite or append existing ++ * files ++ */ ++ new_file_fd = str_create(p_filename); ++ if (!is_append && offset == 0) ++ { ++ do_truncate = 1; ++ } ++ } ++ if (vsf_sysutil_retval_is_error(new_file_fd)) ++ { ++ vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not create file."); ++ return; + } +- } +- if (vsf_sysutil_retval_is_error(new_file_fd)) +- { +- vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not create file."); +- return; + } + created = 1; + vsf_sysutil_fstat(new_file_fd, &s_p_statbuf); +diff --git a/sysutil.c b/sysutil.c +index 1c0422e..e847650 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -1666,6 +1666,9 @@ vsf_sysutil_get_error(void) + case EAGAIN: + retval = kVSFSysUtilErrAGAIN; + break; ++ case EEXIST: ++ retval = kVSFSysUtilErrEXIST; ++ break; + default: + break; + } +diff --git a/sysutil.h b/sysutil.h +index be727f5..7a59f13 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -19,7 +19,8 @@ enum EVSFSysUtilError + kVSFSysUtilErrOPNOTSUPP, + kVSFSysUtilErrACCES, + kVSFSysUtilErrNOENT, +- kVSFSysUtilErrAGAIN ++ kVSFSysUtilErrAGAIN, ++ kVSFSysUtilErrEXIST + }; + enum EVSFSysUtilError vsf_sysutil_get_error(void); + +diff --git a/tunables.c b/tunables.c +index 9680528..5ec2bdc 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -92,6 +92,7 @@ int tunable_ftp_enable; + int tunable_http_enable; + int tunable_seccomp_sandbox; + int tunable_allow_writeable_chroot; ++int tunable_better_stou; + + unsigned int tunable_accept_timeout; + unsigned int tunable_connect_timeout; +@@ -239,6 +240,7 @@ tunables_load_defaults() + tunable_http_enable = 0; + tunable_seccomp_sandbox = 0; + tunable_allow_writeable_chroot = 0; ++ tunable_better_stou = 0; + + tunable_accept_timeout = 60; + tunable_connect_timeout = 60; +diff --git a/tunables.h b/tunables.h +index a466427..85ea1a8 100644 +--- a/tunables.h ++++ b/tunables.h +@@ -93,6 +93,9 @@ extern int tunable_ftp_enable; /* Allow FTP protocol */ + extern int tunable_http_enable; /* Allow HTTP protocol */ + extern int tunable_seccomp_sandbox; /* seccomp filter sandbox */ + extern int tunable_allow_writeable_chroot; /* Allow misconfiguration */ ++extern int tunable_better_stou; /* Use better file name generation ++ * algorithm for the STOU command ++ */ + + /* Integer/numeric defines */ + extern unsigned int tunable_accept_timeout; +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 43b0435..6911a73 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -65,6 +65,11 @@ creates an 'etc' directory in the new root directory, they could potentially + trick the C library into loading a user-created configuration file from the + /etc/ directory. + ++Default: NO ++.TP ++.B better_stou ++Use better file name generation algorithm for the STOU command. ++ + Default: NO + .TP + .B anon_mkdir_write_enable +-- +2.14.4 + diff --git a/0050-Don-t-link-with-libnsl.patch b/0050-Don-t-link-with-libnsl.patch new file mode 100644 index 0000000..8b626bb --- /dev/null +++ b/0050-Don-t-link-with-libnsl.patch @@ -0,0 +1,27 @@ +From f8663f35d5d150f0533bb052e48306b9a5111d87 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 6 Feb 2018 18:04:53 +0100 +Subject: [PATCH 50/59] Don't link with libnsl + +Don't link with libnsl. It builds just fine without it and +vsf_findlibs.sh enables it only when tcp_wrappers is enabled. +--- + Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/Makefile b/Makefile +index 612994e..0f7411c 100644 +--- a/Makefile ++++ b/Makefile +@@ -8,7 +8,7 @@ CFLAGS = -O2 -fPIE -fstack-protector --param=ssp-buffer-size=4 \ + -D_FORTIFY_SOURCE=2 \ + #-pedantic -Wconversion + +-LIBS = -lnsl -lpam -lcap -ldl -lcrypto ++LIBS = -lpam -lcap -ldl -lcrypto + LINK = -Wl,-s + LDFLAGS = -fPIE -pie -Wl,-z,relro -Wl,-z,now + +-- +2.14.4 + diff --git a/0051-Improve-documentation-of-better_stou-in-the-man-page.patch b/0051-Improve-documentation-of-better_stou-in-the-man-page.patch new file mode 100644 index 0000000..c2593be --- /dev/null +++ b/0051-Improve-documentation-of-better_stou-in-the-man-page.patch @@ -0,0 +1,30 @@ +From 765f99b26705c8d6fe2be4feb07f4c91e7eb96f9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Thu, 5 Apr 2018 12:29:03 +0200 +Subject: [PATCH 51/59] Improve documentation of better_stou in the man page + +--- + vsftpd.conf.5 | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index 6911a73..e9ae474 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -68,7 +68,12 @@ trick the C library into loading a user-created configuration file from the + Default: NO + .TP + .B better_stou +-Use better file name generation algorithm for the STOU command. ++Use a better file name generation algorithm for the STOU command. The default ++original algorithm simply adds an increasing number suffix to the file name, ++which is prone to race conditions if multiple uploaders use the STOU command ++with the same file name simultaneously, which can result in failure of the ++command. The new algorithm adds a unique random six character suffix to ++the file name, which works much better in face of concurrent uploads. + + Default: NO + .TP +-- +2.14.4 + diff --git a/0052-Fix-rDNS-with-IPv6.patch b/0052-Fix-rDNS-with-IPv6.patch new file mode 100644 index 0000000..eca9474 --- /dev/null +++ b/0052-Fix-rDNS-with-IPv6.patch @@ -0,0 +1,195 @@ +From 01b646d2af0ed885d01d31a6479898a3c423a630 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Thu, 26 Apr 2018 10:00:19 +0200 +Subject: [PATCH 52/59] Fix rDNS with IPv6 + +Previously IPv6 addresses were not translated to hostnames for PAM to use. +--- + privops.c | 3 ++- + sysdeputil.c | 28 +++++++++++++++------------- + sysdeputil.h | 5 ++++- + sysutil.c | 35 +++++++++++++++++++++++++++++++++++ + sysutil.h | 4 ++++ + 5 files changed, 60 insertions(+), 15 deletions(-) + +diff --git a/privops.c b/privops.c +index f27c5c4..e577a27 100644 +--- a/privops.c ++++ b/privops.c +@@ -383,7 +383,8 @@ handle_local_login(struct vsf_session* p_sess, + struct mystr* p_user_str, + const struct mystr* p_pass_str) + { +- if (!vsf_sysdep_check_auth(p_user_str, p_pass_str, &p_sess->remote_ip_str)) ++ if (!vsf_sysdep_check_auth(p_sess, p_user_str, p_pass_str, ++ &p_sess->remote_ip_str)) + { + return kVSFLoginFail; + } +diff --git a/sysdeputil.c b/sysdeputil.c +index 2063c87..4fe56c2 100644 +--- a/sysdeputil.c ++++ b/sysdeputil.c +@@ -16,10 +16,6 @@ + #include "tunables.h" + #include "builddefs.h" + +-/* For gethostbyaddr, inet_addr */ +-#include +-#include +- + /* For Linux, this adds nothing :-) */ + #include "port/porting_junk.h" + +@@ -242,13 +238,15 @@ void vsf_remove_uwtmp(void); + + #ifndef VSF_SYSDEP_HAVE_PAM + int +-vsf_sysdep_check_auth(struct mystr* p_user_str, ++vsf_sysdep_check_auth(struct vsf_session* p_sess, ++ struct mystr* p_user_str, + const struct mystr* p_pass_str, + const struct mystr* p_remote_host) + { + const char* p_crypted; + const struct passwd* p_pwd = getpwnam(str_getbuf(p_user_str)); + (void) p_remote_host; ++ (void) p_sess; + if (p_pwd == NULL) + { + return 0; +@@ -322,14 +320,14 @@ static int pam_conv_func(int nmsg, const struct pam_message** p_msg, + static void vsf_auth_shutdown(void); + + int +-vsf_sysdep_check_auth(struct mystr* p_user_str, ++vsf_sysdep_check_auth(struct vsf_session* p_sess, ++ struct mystr* p_user_str, + const struct mystr* p_pass_str, + const struct mystr* p_remote_host) + { + int retval = -1; + #ifdef PAM_RHOST +- struct sockaddr_in sin; +- struct hostent *host; ++ struct mystr hostname = INIT_MYSTR; + #endif + pam_item_t item; + const char* pam_user_name = 0; +@@ -354,13 +352,17 @@ vsf_sysdep_check_auth(struct mystr* p_user_str, + return 0; + } + #ifdef PAM_RHOST +- if (tunable_reverse_lookup_enable) { +- sin.sin_addr.s_addr = inet_addr(str_getbuf(p_remote_host)); +- host = gethostbyaddr((char*)&sin.sin_addr.s_addr,sizeof(struct in_addr),AF_INET); +- if (host != (struct hostent*)0) +- retval = pam_set_item(s_pamh, PAM_RHOST, host->h_name); ++ if (tunable_reverse_lookup_enable) ++ { ++ if (vsf_sysutil_get_hostname(p_sess->p_remote_addr, &hostname) == 0) ++ { ++ retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(&hostname)); ++ str_free(&hostname); ++ } + else ++ { + retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); ++ } + } else { + retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); + } +diff --git a/sysdeputil.h b/sysdeputil.h +index 3b6b30a..6f2aa0a 100644 +--- a/sysdeputil.h ++++ b/sysdeputil.h +@@ -5,6 +5,8 @@ + #include "filesize.h" + #endif + ++#include "session.h" ++ + /* VSF_SYSDEPUTIL_H: + * Support for highly system dependent features, and querying for support + * or lack thereof +@@ -15,7 +17,8 @@ struct mystr; + + /* Authentication of local users */ + /* Return 0 for fail, 1 for success */ +-int vsf_sysdep_check_auth(struct mystr* p_user, ++int vsf_sysdep_check_auth(struct vsf_session* p_sess, ++ struct mystr* p_user, + const struct mystr* p_pass, + const struct mystr* p_remote_host); + +diff --git a/sysutil.c b/sysutil.c +index e847650..b68583b 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -2356,6 +2356,41 @@ vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, + } + } + ++int ++vsf_sysutil_get_hostname(struct vsf_sysutil_sockaddr *p_addr, ++ struct mystr* p_str) ++{ ++ struct sockaddr *sa; ++ socklen_t sa_len = 0; ++ char hostname[NI_MAXHOST]; ++ int res; ++ ++ sa = &p_addr->u.u_sockaddr; ++ if (sa->sa_family == AF_INET) ++ { ++ sa_len = sizeof(struct sockaddr_in); ++ } ++ else if (sa->sa_family == AF_INET6) ++ { ++ sa_len = sizeof(struct sockaddr_in6); ++ } ++ else ++ { ++ die("can only support ipv4 and ipv6 currently"); ++ } ++ res = getnameinfo(sa, sa_len, hostname, sizeof(hostname), NULL, 0, ++ NI_NAMEREQD); ++ if (res == 0) ++ { ++ str_alloc_text(p_str, hostname); ++ return 0; ++ } ++ else ++ { ++ return -1; ++ } ++} ++ + struct vsf_sysutil_user* + vsf_sysutil_getpwuid(const unsigned int uid) + { +diff --git a/sysutil.h b/sysutil.h +index 7a59f13..2df14ed 100644 +--- a/sysutil.h ++++ b/sysutil.h +@@ -7,6 +7,8 @@ + #include "filesize.h" + #endif + ++#include "str.h" ++ + /* Return value queries */ + int vsf_sysutil_retval_is_error(int retval); + enum EVSFSysUtilError +@@ -266,6 +268,8 @@ int vsf_sysutil_connect_timeout(int fd, + unsigned int wait_seconds); + void vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, + const char* p_name); ++int vsf_sysutil_get_hostname(struct vsf_sysutil_sockaddr *p_addr, ++ struct mystr* p_str); + /* Option setting on sockets */ + void vsf_sysutil_activate_keepalive(int fd); + void vsf_sysutil_rcvtimeo(int fd); +-- +2.14.4 + diff --git a/0053-Always-do-chdir-after-chroot.patch b/0053-Always-do-chdir-after-chroot.patch new file mode 100644 index 0000000..e1c0105 --- /dev/null +++ b/0053-Always-do-chdir-after-chroot.patch @@ -0,0 +1,32 @@ +From 315f9720db94af3319c9550feaf473b9cf09aeac Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Thu, 3 May 2018 13:20:28 +0200 +Subject: [PATCH 53/59] Always do chdir("/") after chroot() + +Always do chdir("/") after chroot() to be more sure we'll never get out +of it. This will not affect the working directory after calling +vsf_sysutil_chroot(), because in the current state vsftpd always calls +vsf_sysutil_chroot("."). +--- + sysutil.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/sysutil.c b/sysutil.c +index b68583b..3014c05 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -2588,6 +2588,11 @@ vsf_sysutil_chroot(const char* p_root_path) + { + die("chroot"); + } ++ retval = chdir("/"); ++ if (retval != 0) ++ { ++ die("chdir"); ++ } + } + + unsigned int +-- +2.14.4 + diff --git a/0054-vsf_sysutil_rcvtimeo-Check-return-value-of-setsockop.patch b/0054-vsf_sysutil_rcvtimeo-Check-return-value-of-setsockop.patch new file mode 100644 index 0000000..d67db00 --- /dev/null +++ b/0054-vsf_sysutil_rcvtimeo-Check-return-value-of-setsockop.patch @@ -0,0 +1,33 @@ +From ca27e6e34d89fc247a164ed7330735644f97d7d8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Wed, 9 May 2018 20:15:29 +0200 +Subject: [PATCH 54/59] vsf_sysutil_rcvtimeo: Check return value of setsockopt + +--- + sysutil.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/sysutil.c b/sysutil.c +index 3014c05..de5f876 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -684,10 +684,15 @@ void + vsf_sysutil_rcvtimeo(int fd) + { + struct timeval tv; ++ int retval; + + tv.tv_sec = tunable_data_connection_timeout; + tv.tv_usec = 0; +- setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)); ++ retval = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)); ++ if (retval != 0) ++ { ++ die("setsockopt: rcvtimeo"); ++ } + } + + void +-- +2.14.4 + diff --git a/0055-vsf_sysutil_get_tz-Check-the-return-value-of-syscall.patch b/0055-vsf_sysutil_get_tz-Check-the-return-value-of-syscall.patch new file mode 100644 index 0000000..85d4f2f --- /dev/null +++ b/0055-vsf_sysutil_get_tz-Check-the-return-value-of-syscall.patch @@ -0,0 +1,108 @@ +From c7ac05fdf2a7b53d901bfc3afeb9a61916aaaaf1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Wed, 9 May 2018 20:26:37 +0200 +Subject: [PATCH 55/59] vsf_sysutil_get_tz: Check the return value of syscalls + +Check the return value of syscalls. There's always the possibility that +they'll fail. (Failure of close() is not handled though, apart from EINTR. +The file is open read-only so it shouldn't fail, and even if it does, +it's not tragic.) + +We return NULL in case of syscall failure. One might be tempted to simply +call die() when any kind of error occurs when parsing the timezone data, +but I think it's more in line with the behaviour of tzset(3) not to do +anything drastic in such a case (tzset() will silently use UTC when +the value given in the TZ environment variable is invalid). +--- + sysutil.c | 46 +++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 37 insertions(+), 9 deletions(-) + +diff --git a/sysutil.c b/sysutil.c +index de5f876..fd07d99 100644 +--- a/sysutil.c ++++ b/sysutil.c +@@ -2647,12 +2647,12 @@ error: + die("reopening standard file descriptors to /dev/null failed"); + } + +-char* vsf_sysutil_get_tz() ++char* vsf_sysutil_get_tz(void) + { + char *ret_tz = NULL; + char buff[BUFTZSIZ]; + off_t s_pos, e_pos; +- size_t rcnt, rest; ++ ssize_t rcnt, rest; + int fd; + + if ((fd = open(F_LOCALTIME, O_RDONLY)) > -1) +@@ -2663,8 +2663,12 @@ char* vsf_sysutil_get_tz() + return NULL; + } + s_pos = e_pos > BUFTZSIZ ? e_pos - BUFTZSIZ : 0; +- lseek(fd, s_pos, SEEK_SET); +- rcnt = read(fd, buff, BUFTZSIZ); ++ if (lseek(fd, s_pos, SEEK_SET) == -1 || ++ (rcnt = vsf_sysutil_read(fd, buff, BUFTZSIZ)) == -1) ++ { ++ close(fd); ++ return NULL; ++ } + + if (rcnt && buff[rcnt-1] == '\n') + { +@@ -2680,10 +2684,25 @@ char* vsf_sysutil_get_tz() + int len = e_pos - s_pos - offset; + if (len) + { +- lseek(fd, s_pos + offset, SEEK_SET); ++ if (lseek(fd, s_pos + offset, SEEK_SET) == -1) ++ { ++ close(fd); ++ return NULL; ++ } + ret_tz = calloc(1, len+4); ++ if (ret_tz == NULL) ++ { ++ close(fd); ++ return NULL; ++ } + memcpy(ret_tz, "TZ=", 3); +- rcnt = read(fd, ret_tz+3, len); ++ rcnt = vsf_sysutil_read(fd, ret_tz+3, len); ++ if (rcnt == -1) ++ { ++ free(ret_tz); ++ close(fd); ++ return NULL; ++ } + } + break; + } +@@ -2693,11 +2712,20 @@ char* vsf_sysutil_get_tz() + } + rest = s_pos > BUFTZSIZ ? s_pos - BUFTZSIZ : 0; + s_pos -= rest; +- lseek(fd, s_pos, SEEK_SET); +- rcnt = read(fd, buff, rest); ++ if (lseek(fd, s_pos, SEEK_SET) == -1) ++ { ++ close(fd); ++ return NULL; ++ } ++ rcnt = vsf_sysutil_read(fd, buff, rest); ++ if (rcnt == -1) ++ { ++ close(fd); ++ return NULL; ++ } + } while (rcnt > 0); + +- close (fd); ++ (void) vsf_sysutil_close_errno(fd); + } + + return ret_tz; +-- +2.14.4 + diff --git a/0056-Log-die-calls-to-syslog.patch b/0056-Log-die-calls-to-syslog.patch new file mode 100644 index 0000000..46b93f6 --- /dev/null +++ b/0056-Log-die-calls-to-syslog.patch @@ -0,0 +1,206 @@ +From ee6af258e8cb1a7fada5e6d3e54429b89f12b158 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Fri, 15 Jun 2018 12:02:21 +0200 +Subject: [PATCH 56/59] Log die() calls to syslog + +Pass messages given to die(), die2() and bug() to syslog. Currently this +functionality requires waiting for a short amount of time (1 second is +used) after logging the message and before exiting. This is a workaround +for the following systemd bug: +https://github.com/systemd/systemd/issues/2913 + +The need for this workaround is the main reason why I decided not to +enable this functionality by default. + +Resolves: rhbz#1318198 +Resolves: rhbz#1582672 +--- + logging.c | 13 +++++++++---- + logging.h | 2 ++ + main.c | 4 ++++ + parseconf.c | 1 + + tcpwrap.c | 3 --- + tunables.c | 2 ++ + tunables.h | 2 ++ + utility.c | 11 +++++++++++ + vsftpd.conf.5 | 10 ++++++++++ + 9 files changed, 41 insertions(+), 7 deletions(-) + +diff --git a/logging.c b/logging.c +index c4461f7..9e86808 100644 +--- a/logging.c ++++ b/logging.c +@@ -30,10 +30,6 @@ static void vsf_log_do_log_to_file(int fd, struct mystr* p_str); + void + vsf_log_init(struct vsf_session* p_sess) + { +- if (tunable_syslog_enable || tunable_tcp_wrappers) +- { +- vsf_sysutil_openlog(0); +- } + if (!tunable_xferlog_enable && !tunable_dual_log_enable) + { + return; +@@ -389,3 +385,12 @@ vsf_log_do_log_vsftpd_format(struct vsf_session* p_sess, struct mystr* p_str, + } + } + ++void ++vsf_log_die(const char* p_text) ++{ ++ struct mystr log_str = INIT_MYSTR; ++ ++ str_append_text(&log_str, "ERROR: "); ++ str_append_text(&log_str, p_text); ++ str_syslog(&log_str, 1); ++} +diff --git a/logging.h b/logging.h +index 1ff57d1..75f06c1 100644 +--- a/logging.h ++++ b/logging.h +@@ -91,5 +91,7 @@ void vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, + void vsf_log_failed_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, + struct mystr* p_str); + ++void vsf_log_die(const char* p_text); ++ + #endif /* VSF_LOGGING_H */ + +diff --git a/main.c b/main.c +index f039081..1178d44 100644 +--- a/main.c ++++ b/main.c +@@ -120,6 +120,10 @@ main(int argc, const char* argv[]) + } + vsf_sysutil_free(p_statbuf); + } ++ if (tunable_log_die || tunable_syslog_enable || tunable_tcp_wrappers) ++ { ++ vsf_sysutil_openlog(0); ++ } + /* Resolve pasv_address if required */ + if (tunable_pasv_address && tunable_pasv_addr_resolve) + { +diff --git a/parseconf.c b/parseconf.c +index 47b54f1..aeb401a 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -112,6 +112,7 @@ parseconf_bool_array[] = + { "seccomp_sandbox", &tunable_seccomp_sandbox }, + { "allow_writeable_chroot", &tunable_allow_writeable_chroot }, + { "better_stou", &tunable_better_stou }, ++ { "log_die", &tunable_log_die }, + { 0, 0 } + }; + +diff --git a/tcpwrap.c b/tcpwrap.c +index 5bf57d3..132b771 100644 +--- a/tcpwrap.c ++++ b/tcpwrap.c +@@ -27,15 +27,12 @@ int + vsf_tcp_wrapper_ok(int remote_fd) + { + struct request_info req; +- vsf_sysutil_openlog(0); + request_init(&req, RQ_DAEMON, "vsftpd", RQ_FILE, remote_fd, 0); + fromhost(&req); + if (!hosts_access(&req)) + { +- vsf_sysutil_closelog(); + return 0; + } +- vsf_sysutil_closelog(); + return 1; + } + +diff --git a/tunables.c b/tunables.c +index 5ec2bdc..63de8e6 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -93,6 +93,7 @@ int tunable_http_enable; + int tunable_seccomp_sandbox; + int tunable_allow_writeable_chroot; + int tunable_better_stou; ++int tunable_log_die; + + unsigned int tunable_accept_timeout; + unsigned int tunable_connect_timeout; +@@ -241,6 +242,7 @@ tunables_load_defaults() + tunable_seccomp_sandbox = 0; + tunable_allow_writeable_chroot = 0; + tunable_better_stou = 0; ++ tunable_log_die = 0; + + tunable_accept_timeout = 60; + tunable_connect_timeout = 60; +diff --git a/tunables.h b/tunables.h +index 85ea1a8..8a4b8b2 100644 +--- a/tunables.h ++++ b/tunables.h +@@ -96,6 +96,8 @@ extern int tunable_allow_writeable_chroot; /* Allow misconfiguration */ + extern int tunable_better_stou; /* Use better file name generation + * algorithm for the STOU command + */ ++extern int tunable_log_die; /* Log calls to die(), die2() ++ * and bug() */ + + /* Integer/numeric defines */ + extern unsigned int tunable_accept_timeout; +diff --git a/utility.c b/utility.c +index 5fd714d..75e5bdd 100644 +--- a/utility.c ++++ b/utility.c +@@ -9,6 +9,8 @@ + #include "sysutil.h" + #include "str.h" + #include "defs.h" ++#include "logging.h" ++#include "tunables.h" + + #define DIE_DEBUG + +@@ -41,11 +43,20 @@ void + bug(const char* p_text) + { + /* Rats. Try and write the reason to the network for diagnostics */ ++ if (tunable_log_die) ++ { ++ vsf_log_die(p_text); ++ } + vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); + (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "500 OOPS: ", 10); + (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text, + vsf_sysutil_strlen(p_text)); + (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "\r\n", 2); ++ if (tunable_log_die) ++ { ++ /* Workaround for https://github.com/systemd/systemd/issues/2913 */ ++ vsf_sysutil_sleep(1.0); ++ } + vsf_sysutil_exit(2); + } + +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index e9ae474..f246906 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -358,6 +358,16 @@ wanting to e.g. append a file. + + Default: YES + .TP ++.B log_die ++Log an error to syslog when some error condition occurs and vsftpd decides ++to quit. Internally, the error messages given to the functions die(), die2() ++and bug() are passed to syslog. Currently this functionality requires waiting ++for a short amount of time (1 second is used) after logging the message and ++before exiting. This is a workaround for the following systemd bug: ++https://github.com/systemd/systemd/issues/2913 ++ ++Default: NO ++.TP + .B log_ftp_protocol + When enabled, all FTP requests and responses are logged, providing the option + xferlog_std_format is not enabled. Useful for debugging. +-- +2.14.4 + diff --git a/0057-Improve-error-message-when-max-number-of-bind-attemp.patch b/0057-Improve-error-message-when-max-number-of-bind-attemp.patch new file mode 100644 index 0000000..3a0effe --- /dev/null +++ b/0057-Improve-error-message-when-max-number-of-bind-attemp.patch @@ -0,0 +1,27 @@ +From 380e40930661d643c865bace4e1791ca8f9d74cf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Mon, 18 Jun 2018 14:01:46 +0200 +Subject: [PATCH 57/59] Improve error message when max number of bind attempts + is exceeded + +Resolves: rhbz#1318198 +--- + privops.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/privops.c b/privops.c +index e577a27..010d28d 100644 +--- a/privops.c ++++ b/privops.c +@@ -183,7 +183,7 @@ vsf_privop_pasv_listen(struct vsf_session* p_sess) + } + if (!bind_retries) + { +- die("vsf_sysutil_bind"); ++ die("vsf_sysutil_bind, maximum number of attempts to find a listening port exceeded"); + } + return the_port; + } +-- +2.14.4 + diff --git a/0058-Make-the-max-number-of-bind-retries-tunable.patch b/0058-Make-the-max-number-of-bind-retries-tunable.patch new file mode 100644 index 0000000..1350470 --- /dev/null +++ b/0058-Make-the-max-number-of-bind-retries-tunable.patch @@ -0,0 +1,103 @@ +From be7c2d639127dd8af0139caf94f8c29f431d3753 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Mon, 18 Jun 2018 10:13:48 +0200 +Subject: [PATCH 58/59] Make the max number of bind retries tunable + +Resolves: rhbz#1318198 +--- + parseconf.c | 1 + + privops.c | 8 ++++++-- + tunables.c | 2 ++ + tunables.h | 1 + + vsftpd.conf.5 | 5 +++++ + 5 files changed, 15 insertions(+), 2 deletions(-) + +diff --git a/parseconf.c b/parseconf.c +index aeb401a..3cfe7da 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -143,6 +143,7 @@ parseconf_uint_array[] = + { "delay_successful_login", &tunable_delay_successful_login }, + { "max_login_fails", &tunable_max_login_fails }, + { "chown_upload_mode", &tunable_chown_upload_mode }, ++ { "bind_retries", &tunable_bind_retries }, + { 0, 0 } + }; + +diff --git a/privops.c b/privops.c +index 010d28d..83b25c7 100644 +--- a/privops.c ++++ b/privops.c +@@ -120,8 +120,8 @@ unsigned short + vsf_privop_pasv_listen(struct vsf_session* p_sess) + { + static struct vsf_sysutil_sockaddr* s_p_sockaddr; +- int bind_retries = 10; +- unsigned short the_port; ++ int bind_retries = tunable_bind_retries + 1; ++ unsigned short the_port = 0; + /* IPPORT_RESERVED */ + unsigned short min_port = 1024; + unsigned short max_port = 65535; +@@ -131,6 +131,10 @@ vsf_privop_pasv_listen(struct vsf_session* p_sess) + die("listed fd already active"); + } + ++ if (bind_retries < 2) ++ { ++ bind_retries = 2; ++ } + if (tunable_pasv_min_port > min_port && tunable_pasv_min_port <= max_port) + { + min_port = (unsigned short) tunable_pasv_min_port; +diff --git a/tunables.c b/tunables.c +index 63de8e6..a7ce9c8 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -115,6 +115,7 @@ unsigned int tunable_delay_failed_login; + unsigned int tunable_delay_successful_login; + unsigned int tunable_max_login_fails; + unsigned int tunable_chown_upload_mode; ++unsigned int tunable_bind_retries; + + const char* tunable_secure_chroot_dir; + const char* tunable_ftp_username; +@@ -268,6 +269,7 @@ tunables_load_defaults() + tunable_max_login_fails = 3; + /* -rw------- */ + tunable_chown_upload_mode = 0600; ++ tunable_bind_retries = 9; + + install_str_setting("/usr/share/empty", &tunable_secure_chroot_dir); + install_str_setting("ftp", &tunable_ftp_username); +diff --git a/tunables.h b/tunables.h +index 8a4b8b2..029d645 100644 +--- a/tunables.h ++++ b/tunables.h +@@ -120,6 +120,7 @@ extern unsigned int tunable_delay_failed_login; + extern unsigned int tunable_delay_successful_login; + extern unsigned int tunable_max_login_fails; + extern unsigned int tunable_chown_upload_mode; ++extern unsigned int tunable_bind_retries; + + /* String defines */ + extern const char* tunable_secure_chroot_dir; +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index f246906..ce3fba3 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -760,6 +760,11 @@ value will be treated as a base 10 integer! + + Default: 077 + .TP ++.B bind_retries ++Maximum number of attempts to find a free listening port in passive mode. ++ ++Default: 9 ++.TP + .B chown_upload_mode + The file mode to force for chown()ed anonymous uploads. (Added in v2.0.6). + +-- +2.14.4 + diff --git a/0059-Fix-SEGFAULT-when-running-in-a-container-as-PID-1.patch b/0059-Fix-SEGFAULT-when-running-in-a-container-as-PID-1.patch new file mode 100644 index 0000000..3adbd4c --- /dev/null +++ b/0059-Fix-SEGFAULT-when-running-in-a-container-as-PID-1.patch @@ -0,0 +1,58 @@ +From 970711fde95bee3de1e4a5e0b557c3132d0c3e3f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= +Date: Tue, 6 Feb 2018 11:39:01 +0100 +Subject: [PATCH 59/59] Fix SEGFAULT when running in a container as PID 1 + +When vsftpd is running in a container as PID 1, it is possible +that it will get SIGCHILD for processes, which were not directly +created by it, but by some of its children. These processes will +not be in the s_p_pid_ip_hash hash table, and thus trying to +delete the entry from the hash table in standalone.c:handle_sigchld() +will result in segmentation fault. + +I can quite easily reproduce it with the upstream vsftpd and default +configuration, except for isolate=NO and isolate_network=NO being set +(it seems to me that network namespaces take a long time to create +and destroy, which hides the race condition), on a quad-core machine. +When connecting to vsftpd in a loop like this: +$ while true; do echo -en '' | nc localhost 21; done + +vsftpd crashes after a couple of seconds. +--- + standalone.c | 18 +++++++++++++----- + 1 file changed, 13 insertions(+), 5 deletions(-) + +diff --git a/standalone.c b/standalone.c +index 3b65ea2..3f35e9e 100644 +--- a/standalone.c ++++ b/standalone.c +@@ -270,13 +270,21 @@ handle_sigchld(void* duff) + if (reap_one) + { + struct vsf_sysutil_ipaddr* p_ip; +- /* Account total number of instances */ +- --s_children; +- /* Account per-IP limit */ + p_ip = (struct vsf_sysutil_ipaddr*) + hash_lookup_entry(s_p_pid_ip_hash, (void*)&reap_one); +- drop_ip_count(p_ip); +- hash_free_entry(s_p_pid_ip_hash, (void*)&reap_one); ++ /* If we are running in a container as PID 1, it is possible ++ * that we will get SIGCHILD for processes, which were not ++ * created directly by our process and which are not in the ++ * s_p_pid_ip_hash hash table. ++ */ ++ if (p_ip) ++ { ++ /* Account total number of instances */ ++ --s_children; ++ /* Account per-IP limit */ ++ drop_ip_count(p_ip); ++ hash_free_entry(s_p_pid_ip_hash, (void*)&reap_one); ++ } + } + } + } +-- +2.14.4 + diff --git a/fix-str_open.patch b/fix-str_open.patch new file mode 100644 index 0000000..e5d5bd9 --- /dev/null +++ b/fix-str_open.patch @@ -0,0 +1,27 @@ +--- sysstr-orig.c 2022-07-27 09:44:52.606408000 +0200 ++++ sysstr.c 2022-07-27 09:54:24.043081352 +0200 +@@ -74,19 +74,11 @@ + int + str_open(const struct mystr* p_str, const enum EVSFSysStrOpenMode mode) + { +- enum EVSFSysUtilOpenMode open_mode = kVSFSysUtilOpenUnknown; +- switch (mode) +- { +- case kVSFSysStrOpenReadOnly: +- open_mode = kVSFSysUtilOpenReadOnly; +- break; +- case kVSFSysStrOpenUnknown: +- /* Fall through */ +- default: +- bug("unknown mode value in str_open"); +- break; +- } +- return vsf_sysutil_open_file(str_getbuf(p_str), open_mode); ++ if (mode == kVSFSysStrOpenReadOnly) ++ return vsf_sysutil_open_file(str_getbuf(p_str), kVSFSysUtilOpenReadOnly); ++ ++ bug("unknown mode value in str_open"); ++ return -1; + } + + int diff --git a/vsftpd-3.0.5-add-option-for-tlsv1.3-ciphersuites.patch b/vsftpd-3.0.5-add-option-for-tlsv1.3-ciphersuites.patch new file mode 100644 index 0000000..1f1925e --- /dev/null +++ b/vsftpd-3.0.5-add-option-for-tlsv1.3-ciphersuites.patch @@ -0,0 +1,79 @@ +diff -urN a/parseconf.c b/parseconf.c +--- a/parseconf.c 2021-05-29 23:39:19.000000000 +0200 ++++ b/parseconf.c 2023-03-03 10:22:38.256439634 +0100 +@@ -185,6 +185,7 @@ + { "dsa_cert_file", &tunable_dsa_cert_file }, + { "dh_param_file", &tunable_dh_param_file }, + { "ecdh_param_file", &tunable_ecdh_param_file }, ++ { "ssl_ciphersuites", &tunable_ssl_ciphersuites }, + { "ssl_ciphers", &tunable_ssl_ciphers }, + { "rsa_private_key_file", &tunable_rsa_private_key_file }, + { "dsa_private_key_file", &tunable_dsa_private_key_file }, +diff -urN a/ssl.c b/ssl.c +--- a/ssl.c 2021-08-02 08:24:35.000000000 +0200 ++++ b/ssl.c 2023-03-03 10:28:05.989757655 +0100 +@@ -135,6 +135,11 @@ + { + die("SSL: could not set cipher list"); + } ++ if (tunable_ssl_ciphersuites && ++ SSL_CTX_set_ciphersuites(p_ctx, tunable_ssl_ciphersuites) != 1) ++ { ++ die("SSL: could not set ciphersuites"); ++ } + if (RAND_status() != 1) + { + die("SSL: RNG is not seeded"); +diff -urN a/tunables.c b/tunables.c +--- a/tunables.c 2021-05-29 23:39:00.000000000 +0200 ++++ b/tunables.c 2023-03-03 10:13:30.566868026 +0100 +@@ -154,6 +154,7 @@ + const char* tunable_dsa_cert_file; + const char* tunable_dh_param_file; + const char* tunable_ecdh_param_file; + const char* tunable_ssl_ciphers; ++const char* tunable_ssl_ciphersuites; + const char* tunable_rsa_private_key_file; + const char* tunable_dsa_private_key_file; +@@ -293,6 +293,7 @@ + install_str_setting(0, &tunable_dh_param_file); + install_str_setting(0, &tunable_ecdh_param_file); + install_str_setting("PROFILE=SYSTEM", &tunable_ssl_ciphers); ++ install_str_setting("TLS_AES_256_GCM_SHA384", &tunable_ssl_ciphersuites); + install_str_setting(0, &tunable_rsa_private_key_file); + install_str_setting(0, &tunable_dsa_private_key_file); + install_str_setting(0, &tunable_ca_certs_file); +diff -urN a/tunables.h b/tunables.h +--- a/tunables.h ++++ b/tunables.h +@@ -144,6 +144,7 @@ + extern const char* tunable_dsa_cert_file; + extern const char* tunable_dh_param_file; + extern const char* tunable_ecdh_param_file; + extern const char* tunable_ssl_ciphers; ++extern const char* tunable_ssl_ciphersuites; + extern const char* tunable_rsa_private_key_file; + extern const char* tunable_dsa_private_key_file; +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -1009,6 +1009,20 @@ + + Default: PROFILE=SYSTEM + .TP ++.B ssl_ciphersuites ++This option can be used to select which SSL cipher suites vsftpd will allow for ++encrypted SSL connections with TLSv1.3. See the ++.BR ciphers ++man page for further details. Note that restricting ciphers can be a useful ++security precaution as it prevents malicious remote parties forcing a cipher ++which they have found problems with. ++ ++By default, the system-wide crypto policy is used. See ++.BR update-crypto-policies(8) ++for further details. ++ ++Default: TLS_AES_256_GCM_SHA384 ++.TP + .B ssl_sni_hostname + If set, SSL connections will be rejected unless the SNI hostname in the + incoming handshakes matches this value. diff --git a/vsftpd-3.0.5-enable_wc_logs-replace_unprintable_with_hex.patch b/vsftpd-3.0.5-enable_wc_logs-replace_unprintable_with_hex.patch new file mode 100644 index 0000000..914aebd --- /dev/null +++ b/vsftpd-3.0.5-enable_wc_logs-replace_unprintable_with_hex.patch @@ -0,0 +1,215 @@ +diff --git a/logging.c b/logging.c +index 9e86808..613ff4b 100644 +--- a/logging.c ++++ b/logging.c +@@ -171,7 +171,14 @@ vsf_log_do_log_to_file(int fd, struct mystr* p_str) + return; + } + } +- str_replace_unprintable(p_str, '?'); ++ if (tunable_wc_logs_enable) ++ { ++ str_replace_unprintable_with_hex_wc(p_str); ++ } ++ else ++ { ++ str_replace_unprintable_with_hex(p_str); ++ } + str_append_char(p_str, '\n'); + /* Ignore write failure; maybe the disk filled etc. */ + (void) str_write_loop(p_str, fd); +diff --git a/parseconf.c b/parseconf.c +index 3cfe7da..3729818 100644 +--- a/parseconf.c ++++ b/parseconf.c +@@ -113,6 +113,7 @@ parseconf_bool_array[] = + { "allow_writeable_chroot", &tunable_allow_writeable_chroot }, + { "better_stou", &tunable_better_stou }, + { "log_die", &tunable_log_die }, ++ { "wc_logs_enable", &tunable_wc_logs_enable }, + { 0, 0 } + }; + +diff --git a/str.c b/str.c +index 82b8ae4..c03e7d8 100644 +--- a/str.c ++++ b/str.c +@@ -20,6 +20,11 @@ + #include "utility.h" + #include "sysutil.h" + ++#include ++#include ++#include ++#include ++ + /* File local functions */ + static void str_split_text_common(struct mystr* p_src, struct mystr* p_rhs, + const char* p_text, int is_reverse); +@@ -723,6 +728,102 @@ str_replace_unprintable(struct mystr* p_str, char new_char) + } + } + ++void ++str_replace_unprintable_with_hex(struct mystr* p_str) ++{ ++ unsigned int ups_size = sizeof(unsigned int) * (p_str->len); ++ if (ups_size < p_str->len) ++ { ++ str_replace_unprintable(p_str, '?'); ++ str_append_text(p_str, ": BUG: string is too long"); ++ bug(p_str->p_buf); ++ } ++ unsigned int* ups = vsf_sysutil_malloc(ups_size); ++ unsigned int up_count = 0; ++ for (unsigned int i=0; i < p_str->len; i++) ++ { ++ if (!vsf_sysutil_isprint(p_str->p_buf[i])) ++ { ++ ups[up_count++] = i; ++ } ++ } ++ str_replace_positions_with_hex(p_str, ups, up_count); ++ vsf_sysutil_free(ups); ++} ++ ++void str_replace_unprintable_with_hex_wc(struct mystr* p_str) ++{ ++ unsigned int ups_size = sizeof(unsigned int) * (p_str->len); ++ if (ups_size < p_str->len) ++ { ++ str_replace_unprintable(p_str, '?'); ++ str_append_text(p_str, ": BUG: string is too long"); ++ bug(p_str->p_buf); ++ } ++ unsigned int* ups = vsf_sysutil_malloc(ups_size); ++ unsigned int up_count = 0; ++ ++ size_t current = 0; ++ wchar_t pwc; ++ mbstate_t ps; ++ memset(&ps, 0, sizeof(ps)); ++ ssize_t len = 0; ++ while ((len = mbrtowc(&pwc, p_str->p_buf, p_str->len - current, &ps)) > 0) ++ { ++ if (!iswprint(pwc)) ++ { ++ for (int i = 0; i < len; i++) ++ { ++ ups[up_count++] = current++; ++ } ++ } ++ else ++ { ++ current += len; ++ } ++ } ++ if (len < 0) ++ { ++ while (current < p_str->len) ++ { ++ ups[up_count++] = current++; ++ } ++ } ++ str_replace_positions_with_hex(p_str, ups, up_count); ++ vsf_sysutil_free(ups); ++} ++ ++void ++str_replace_positions_with_hex(struct mystr* p_str, const unsigned int* poss, const unsigned int pos_count) ++{ ++ if (pos_count == 0) ++ return; ++ ++ struct mystr tmp_str = INIT_MYSTR; ++ str_reserve(&tmp_str, p_str->len + 3 * pos_count); ++ unsigned int current = 0; ++ ++ for (unsigned int i=0; i < pos_count; i++) ++ { ++ unsigned int pos = poss[i]; ++ ++ if (current < pos) ++ private_str_append_memchunk(&tmp_str, p_str->p_buf + current, pos - current); ++ ++ char hex_buf[5]; ++ memset(hex_buf, 0, sizeof(hex_buf)); ++ sprintf(hex_buf, "\\x%02X", (unsigned char) p_str->p_buf[pos]); ++ str_append_text(&tmp_str, hex_buf); ++ current = pos + 1; ++ } ++ ++ if (current < p_str->len) ++ private_str_append_memchunk(&tmp_str, p_str->p_buf + current, p_str->len - current); ++ ++ str_copy(p_str, &tmp_str); ++ str_free(&tmp_str); ++} ++ + void + str_basename (struct mystr* d_str, const struct mystr* path) + { +diff --git a/str.h b/str.h +index 44270da..95a83b5 100644 +--- a/str.h ++++ b/str.h +@@ -98,6 +98,10 @@ int str_contains_space(const struct mystr* p_str); + int str_all_space(const struct mystr* p_str); + int str_contains_unprintable(const struct mystr* p_str); + void str_replace_unprintable(struct mystr* p_str, char new_char); ++void str_replace_unprintable_with_hex(struct mystr* p_str); ++void str_replace_unprintable_with_hex_wc(struct mystr* p_str); ++void str_replace_positions_with_hex(struct mystr* p_str, const unsigned int* poss, ++ const unsigned int pos_count); + int str_atoi(const struct mystr* p_str); + filesize_t str_a_to_filesize_t(const struct mystr* p_str); + unsigned int str_octal_to_uint(const struct mystr* p_str); +diff --git a/tunables.c b/tunables.c +index a7ce9c8..c96c1ac 100644 +--- a/tunables.c ++++ b/tunables.c +@@ -94,6 +94,7 @@ int tunable_seccomp_sandbox; + int tunable_allow_writeable_chroot; + int tunable_better_stou; + int tunable_log_die; ++int tunable_wc_logs_enable; + + unsigned int tunable_accept_timeout; + unsigned int tunable_connect_timeout; +@@ -244,6 +245,7 @@ tunables_load_defaults() + tunable_allow_writeable_chroot = 0; + tunable_better_stou = 0; + tunable_log_die = 0; ++ tunable_wc_logs_enable = 0; + + tunable_accept_timeout = 60; + tunable_connect_timeout = 60; +diff --git a/tunables.h b/tunables.h +index 029d645..8d50150 100644 +--- a/tunables.h ++++ b/tunables.h +@@ -98,6 +98,7 @@ extern int tunable_better_stou; /* Use better file name generation + */ + extern int tunable_log_die; /* Log calls to die(), die2() + * and bug() */ ++extern int tunable_wc_logs_enable; /* Allow non ASCII characters in logs */ + + /* Integer/numeric defines */ + extern unsigned int tunable_accept_timeout; +diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 +index ce3fba3..815773f 100644 +--- a/vsftpd.conf.5 ++++ b/vsftpd.conf.5 +@@ -735,6 +735,12 @@ If enabled, use CLONE_NEWPID and CLONE_NEWIPC to isolate processes to their + ipc and pid namespaces. So separated processes can not interact with each other. + + Default: YES ++.TP ++.B wc_logs_enable ++If enabled, logs will be treated as wide-character strings and not just ++ASCII strings when filtering out non-printable characters. ++ ++Default: NO + + .SH NUMERIC OPTIONS + Below is a list of numeric options. A numeric option must be set to a non diff --git a/vsftpd-3.0.5-replace-deprecated-openssl-functions.patch b/vsftpd-3.0.5-replace-deprecated-openssl-functions.patch new file mode 100644 index 0000000..8e3792b --- /dev/null +++ b/vsftpd-3.0.5-replace-deprecated-openssl-functions.patch @@ -0,0 +1,70 @@ +diff --git a/ssl.c b/ssl.c +--- ssl.c ++++ ssl.c +@@ -28,17 +28,17 @@ + #include + #include + #include + #include + #include + #include + #include + + static char* get_ssl_error(); + static SSL* get_ssl(struct vsf_session* p_sess, int fd); + static int ssl_session_init(struct vsf_session* p_sess); + static void setup_bio_callbacks(); + static long bio_callback( +- BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long retval); ++ BIO* p_bio, int oper, const char* p_arg, size_t len, int argi, long argl, int ret, size_t *processed); + static int ssl_verify_callback(int verify_ok, X509_STORE_CTX* p_ctx); + static int ssl_alpn_callback(SSL* p_ssl, + const unsigned char** p_out, +@@ -88,7 +88,7 @@ + long options; + int verify_option = 0; + SSL_library_init(); +- p_ctx = SSL_CTX_new(SSLv23_server_method()); ++ p_ctx = SSL_CTX_new_ex(NULL, NULL, TLS_server_method()); + if (p_ctx == NULL) + { + die("SSL: could not allocate SSL context"); +@@ -180,13 +180,10 @@ + die("SSL: RNG is not seeded"); + } + { +- EC_KEY* key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); +- if (key == NULL) ++ if (!SSL_CTX_set1_groups_list(p_ctx, "P-256")) + { + die("SSL: failed to get curve p256"); + } +- SSL_CTX_set_tmp_ecdh(p_ctx, key); +- EC_KEY_free(key); + } + if (tunable_ssl_request_cert) + { +@@ -692,17 +689,19 @@ + static void setup_bio_callbacks(SSL* p_ssl) + { + BIO* p_bio = SSL_get_rbio(p_ssl); +- BIO_set_callback(p_bio, bio_callback); ++ BIO_set_callback_ex(p_bio, bio_callback); + p_bio = SSL_get_wbio(p_ssl); +- BIO_set_callback(p_bio, bio_callback); ++ BIO_set_callback_ex(p_bio, bio_callback); + } + + static long + bio_callback( +- BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long ret) ++ BIO* p_bio, int oper, const char* p_arg, size_t len, int argi, long argl, int ret, size_t *processed) + { + int retval = 0; + int fd = 0; ++ (void) len; ++ (void) processed; + (void) p_arg; + (void) argi; + (void) argl; + diff --git a/vsftpd-3.0.5-replace-old-network-addr-functions.patch b/vsftpd-3.0.5-replace-old-network-addr-functions.patch new file mode 100644 index 0000000..89e6257 --- /dev/null +++ b/vsftpd-3.0.5-replace-old-network-addr-functions.patch @@ -0,0 +1,139 @@ +diff -urN vsftpd-3.0.5-orig/postlogin.c vsftpd-3.0.5/postlogin.c +--- vsftpd-3.0.5-orig/postlogin.c 2015-07-22 21:03:22.000000000 +0200 ++++ vsftpd-3.0.5/postlogin.c 2023-02-13 16:34:05.244467476 +0100 +@@ -27,4 +27,6 @@ + #include "ssl.h" + #include "vsftpver.h" ++#include ++#include + #include "opts.h" + +@@ -628,9 +629,10 @@ + else + { + const void* p_v4addr = vsf_sysutil_sockaddr_ipv6_v4(s_p_sockaddr); ++ static char result[INET_ADDRSTRLEN]; + if (p_v4addr) + { +- str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntoa(p_v4addr)); ++ str_append_text(&s_pasv_res_str, inet_ntop(AF_INET, p_v4addr, result, INET_ADDRSTRLEN)); + } + else + { +diff -urN vsftpd-3.0.5-orig/sysutil.c vsftpd-3.0.5/sysutil.c +--- vsftpd-3.0.5-orig/sysutil.c 2012-09-16 09:07:38.000000000 +0200 ++++ vsftpd-3.0.5/sysutil.c 2023-02-13 16:08:58.557153109 +0100 +@@ -2205,20 +2205,13 @@ + const struct sockaddr* p_sockaddr = &p_sockptr->u.u_sockaddr; + if (p_sockaddr->sa_family == AF_INET) + { +- return inet_ntoa(p_sockptr->u.u_sockaddr_in.sin_addr); ++ static char result[INET_ADDRSTRLEN]; ++ return inet_ntop(AF_INET, &p_sockptr->u.u_sockaddr_in.sin_addr, result, INET_ADDRSTRLEN); + } + else if (p_sockaddr->sa_family == AF_INET6) + { +- static char inaddr_buf[64]; +- const char* p_ret = inet_ntop(AF_INET6, +- &p_sockptr->u.u_sockaddr_in6.sin6_addr, +- inaddr_buf, sizeof(inaddr_buf)); +- inaddr_buf[sizeof(inaddr_buf) - 1] = '\0'; +- if (p_ret == NULL) +- { +- inaddr_buf[0] = '\0'; +- } +- return inaddr_buf; ++ static char result[INET6_ADDRSTRLEN]; ++ return inet_ntop(AF_INET6, &p_sockptr->u.u_sockaddr_in6.sin6_addr, result, INET6_ADDRSTRLEN); + } + else + { +@@ -2227,12 +2220,6 @@ + } + } + +-const char* +-vsf_sysutil_inet_ntoa(const void* p_raw_addr) +-{ +- return inet_ntoa(*((struct in_addr*)p_raw_addr)); +-} +- + int + vsf_sysutil_inet_aton(const char* p_text, struct vsf_sysutil_sockaddr* p_addr) + { +@@ -2241,7 +2228,7 @@ + { + bug("bad family"); + } +- if (inet_aton(p_text, &sin_addr)) ++ if (inet_pton(AF_INET, p_text, &sin_addr)) + { + vsf_sysutil_memcpy(&p_addr->u.u_sockaddr_in.sin_addr, + &sin_addr, sizeof(p_addr->u.u_sockaddr_in.sin_addr)); +@@ -2257,37 +2244,46 @@ + vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, + const char* p_name) + { +- struct hostent* hent = gethostbyname(p_name); +- if (hent == NULL) ++ struct addrinfo *result; ++ struct addrinfo hints; ++ int ret; ++ ++ memset(&hints, 0, sizeof(struct addrinfo)); ++ hints.ai_family = AF_UNSPEC; ++ ++ if ((ret = getaddrinfo(p_name, NULL, &hints, &result)) != 0) + { ++ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); + die2("cannot resolve host:", p_name); + } + vsf_sysutil_sockaddr_clear(p_sockptr); +- if (hent->h_addrtype == AF_INET) ++ if (result->ai_family == AF_INET) + { +- unsigned int len = hent->h_length; ++ unsigned int len = result->ai_addrlen; + if (len > sizeof((*p_sockptr)->u.u_sockaddr_in.sin_addr)) + { + len = sizeof((*p_sockptr)->u.u_sockaddr_in.sin_addr); + } + vsf_sysutil_sockaddr_alloc_ipv4(p_sockptr); + vsf_sysutil_memcpy(&(*p_sockptr)->u.u_sockaddr_in.sin_addr, +- hent->h_addr_list[0], len); ++ &result->ai_addrlen, len); + } +- else if (hent->h_addrtype == AF_INET6) ++ else if (result->ai_family == AF_INET6) + { +- unsigned int len = hent->h_length; ++ unsigned int len = result->ai_addrlen; + if (len > sizeof((*p_sockptr)->u.u_sockaddr_in6.sin6_addr)) + { + len = sizeof((*p_sockptr)->u.u_sockaddr_in6.sin6_addr); + } + vsf_sysutil_sockaddr_alloc_ipv6(p_sockptr); + vsf_sysutil_memcpy(&(*p_sockptr)->u.u_sockaddr_in6.sin6_addr, +- hent->h_addr_list[0], len); ++ &result->ai_addrlen, len); + } + else + { +- die("gethostbyname(): neither IPv4 nor IPv6"); ++ freeaddrinfo(result); ++ die("getaddrinfo(): neither IPv4 nor IPv6"); + } ++ freeaddrinfo(result); + } + +diff -urN vsftpd-3.0.5-orig/sysutil.h vsftpd-3.0.5/sysutil.h +--- vsftpd-3.0.5-orig/sysutil.h 2021-05-18 08:50:21.000000000 +0200 ++++ vsftpd-3.0.5/sysutil.h 2023-02-13 15:59:22.088331075 +0100 +@@ -277,7 +277,6 @@ + + const char* vsf_sysutil_inet_ntop( + const struct vsf_sysutil_sockaddr* p_sockptr); +-const char* vsf_sysutil_inet_ntoa(const void* p_raw_addr); + int vsf_sysutil_inet_aton( + const char* p_text, struct vsf_sysutil_sockaddr* p_addr); + diff --git a/vsftpd-3.0.5-use-old-tlsv-options.patch b/vsftpd-3.0.5-use-old-tlsv-options.patch new file mode 100644 index 0000000..7c37ce9 --- /dev/null +++ b/vsftpd-3.0.5-use-old-tlsv-options.patch @@ -0,0 +1,15 @@ +--- parseconf-orig.c 2022-10-25 15:17:18.990701984 +0200 ++++ parseconf.c 2022-10-25 15:12:44.213480000 +0200 +@@ -85,9 +85,9 @@ + { "ssl_sslv2", &tunable_sslv2 }, + { "ssl_sslv3", &tunable_sslv3 }, + { "ssl_tlsv1", &tunable_tlsv1 }, +- { "ssl_tlsv11", &tunable_tlsv1_1 }, +- { "ssl_tlsv12", &tunable_tlsv1_2 }, +- { "ssl_tlsv13", &tunable_tlsv1_3 }, ++ { "ssl_tlsv1_1", &tunable_tlsv1_1 }, ++ { "ssl_tlsv1_2", &tunable_tlsv1_2 }, ++ { "ssl_tlsv1_3", &tunable_tlsv1_3 }, + { "tilde_user_enable", &tunable_tilde_user_enable }, + { "force_anon_logins_ssl", &tunable_force_anon_logins_ssl }, + { "force_anon_data_ssl", &tunable_force_anon_data_ssl }, diff --git a/vsftpd-3.0.5.tar.gz b/vsftpd-3.0.5.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..7ed2a0815c5460d440f7d175da77215be22ff066 GIT binary patch literal 197778 zcmV(xKvP*ilK0n}zhYLYT%?3A z@FCjrdiQ8cj&+e_l_+QL?h9)O3`s;FfWrW!nN;$(->-WB5`0-o;%GNlVkI((dG++W zdwMW4qcrxaty-hjsXzN;J`H@j9f$sPx}8S(-{SMkX*+m`zpd7@hSO{~$IoQv*&}>1 zld43@XYM5N%_sWZ*ZwE{%*ynS6Mq)PrZ)Nf^gnL5_tC%8b-K?Sr`2h+TCL-56ZCJ< zBiVSO{~!MO!XJg69?9?fZwJ>m=ik4-?hoFHFYw3@^~O_S(j;?JIbGn(%ivRDA8pKhO4ou|U9Uck!wY>gtbN#-5Mqh7qnkAuBGSuljO70}} z(y5H2REMdrf?y$sQ5JeKjnIqc^#eWDo}@YDVStroI#D4_<}{k^BdO5Yjlxj7spetn zM9V}^F=LHa@7?q+&u-6d`tΠAQDk{Ps`wGsBYg$t|Z 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%0Aoy=sxN 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 Br?xU_Wr;fbL!&vFv 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 /dev/null 2>&1 +done + +exit 0 diff --git a/vsftpd.ftpusers b/vsftpd.ftpusers new file mode 100644 index 0000000..096142f --- /dev/null +++ b/vsftpd.ftpusers @@ -0,0 +1,15 @@ +# Users that are not allowed to login via ftp +root +bin +daemon +adm +lp +sync +shutdown +halt +mail +news +uucp +operator +games +nobody diff --git a/vsftpd.pam b/vsftpd.pam new file mode 100644 index 0000000..080abc3 --- /dev/null +++ b/vsftpd.pam @@ -0,0 +1,8 @@ +#%PAM-1.0 +session optional pam_keyinit.so force revoke +auth required pam_listfile.so item=user sense=deny file=/etc/vsftpd/ftpusers onerr=succeed +auth required pam_shells.so +auth include password-auth +account include password-auth +session required pam_loginuid.so +session include password-auth diff --git a/vsftpd.service b/vsftpd.service new file mode 100644 index 0000000..4a41b72 --- /dev/null +++ b/vsftpd.service @@ -0,0 +1,10 @@ +[Unit] +Description=Vsftpd ftp daemon +After=network-online.target + +[Service] +Type=forking +ExecStart=/usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf + +[Install] +WantedBy=multi-user.target diff --git a/vsftpd.spec b/vsftpd.spec new file mode 100644 index 0000000..c69a6fd --- /dev/null +++ b/vsftpd.spec @@ -0,0 +1,183 @@ +%define anolis_release 1 + +%global _generatorsdir %{_prefix}/lib/systemd/system-generators + +Name: vsftpd +Version: 3.0.5 +Release: %{anolis_release}%{?dist} +Summary: Very Secure Ftp Daemon + +# OpenSSL link exception +License: GPL-2.0-only WITH vsftpd-openssl-exception +URL: https://security.appspot.com/vsftpd.html +Source0: https://security.appspot.com/downloads/%{name}-%{version}.tar.gz +Source1: vsftpd.xinetd +Source2: vsftpd.pam +Source3: vsftpd.ftpusers +Source4: vsftpd.user_list +Source6: vsftpd_conf_migrate.sh +Source7: vsftpd.service +Source8: vsftpd@.service +Source9: vsftpd.target +Source10: vsftpd-generator + +BuildRequires: make +BuildRequires: pam-devel +BuildRequires: libcap-devel +BuildRequires: openssl-devel +BuildRequires: systemd +BuildRequires: git +BuildRequires: gcc + +Requires: logrotate + +Patch1: 0001-Don-t-use-the-provided-script-to-locate-libraries.patch +Patch2: 0002-Enable-build-with-SSL.patch +Patch3: 0003-Enable-build-with-TCP-Wrapper.patch +Patch4: 0004-Use-etc-vsftpd-dir-for-config-files-instead-of-etc.patch +Patch5: 0005-Use-hostname-when-calling-PAM-authentication-module.patch +Patch6: 0006-Close-stdin-out-err-before-listening-for-incoming-co.patch +Patch7: 0007-Make-filename-filters-smarter.patch +Patch8: 0008-Write-denied-logins-into-the-log.patch +Patch9: 0009-Trim-whitespaces-when-reading-configuration.patch +Patch10: 0010-Improve-daemonizing.patch +Patch11: 0011-Fix-listing-with-more-than-one-star.patch +Patch12: 0012-Replace-syscall-__NR_clone-.-with-clone.patch +Patch13: 0013-Extend-man-pages-with-systemd-info.patch +Patch14: 0014-Add-support-for-square-brackets-in-ls.patch +Patch15: 0015-Listen-on-IPv6-by-default.patch +Patch16: 0016-Increase-VSFTP_AS_LIMIT-from-200UL-to-400UL.patch +Patch17: 0017-Fix-an-issue-with-timestamps-during-DST.patch +Patch18: 0018-Change-the-default-log-file-in-configuration.patch +Patch19: 0019-Introduce-reverse_lookup_enable-option.patch +Patch20: 0020-Use-unsigned-int-for-uid-and-gid-representation.patch +Patch21: 0021-Introduce-support-for-DHE-based-cipher-suites.patch +Patch22: 0022-Introduce-support-for-EDDHE-based-cipher-suites.patch +Patch23: 0023-Add-documentation-for-isolate_-options.-Correct-defa.patch +Patch24: 0024-Introduce-new-return-value-450.patch +Patch25: 0025-Improve-local_max_rate-option.patch +Patch26: 0026-Prevent-hanging-in-SIGCHLD-handler.patch +Patch27: 0027-Delete-files-when-upload-fails.patch +Patch28: 0028-Fix-man-page-rendering.patch +Patch29: 0029-Fix-segfault-in-config-file-parser.patch +Patch30: 0030-Fix-logging-into-syslog-when-enabled-in-config.patch +Patch31: 0031-Fix-question-mark-wildcard-withing-a-file-name.patch +Patch32: 0032-Propagate-errors-from-nfs-with-quota-to-client.patch +#Patch33: 0033-Introduce-TLSv1.1-and-TLSv1.2-options.patch +Patch34: 0034-Turn-off-seccomp-sandbox-because-it-is-too-strict.patch +Patch36: 0036-Redefine-VSFTP_COMMAND_FD-to-1.patch +Patch37: 0037-Document-the-relationship-of-text_userdb_names-and-c.patch +Patch38: 0038-Document-allow_writeable_chroot-in-the-man-page.patch +Patch39: 0039-Improve-documentation-of-ASCII-mode-in-the-man-page.patch +Patch40: 0040-Use-system-wide-crypto-policy.patch +Patch41: 0041-Document-the-new-default-for-ssl_ciphers-in-the-man-.patch +#Patch42: 0042-When-handling-FEAT-command-check-ssl_tlsv1_1-and-ssl.patch +#Patch43: 0043-Enable-only-TLSv1.2-by-default.patch +Patch44: 0044-Disable-anonymous_enable-in-default-config-file.patch +Patch45: 0045-Expand-explanation-of-ascii_-options-behaviour-in-ma.patch +Patch46: 0046-vsftpd.conf-Refer-to-the-man-page-regarding-the-asci.patch +Patch47: 0047-Disable-tcp_wrappers-support.patch +Patch48: 0048-Fix-default-value-of-strict_ssl_read_eof-in-man-page.patch +Patch49: 0049-Add-new-filename-generation-algorithm-for-STOU-comma.patch +Patch50: 0050-Don-t-link-with-libnsl.patch +Patch51: 0051-Improve-documentation-of-better_stou-in-the-man-page.patch +Patch52: 0052-Fix-rDNS-with-IPv6.patch +Patch53: 0053-Always-do-chdir-after-chroot.patch +Patch54: 0054-vsf_sysutil_rcvtimeo-Check-return-value-of-setsockop.patch +Patch55: 0055-vsf_sysutil_get_tz-Check-the-return-value-of-syscall.patch +Patch56: 0056-Log-die-calls-to-syslog.patch +Patch57: 0057-Improve-error-message-when-max-number-of-bind-attemp.patch +Patch58: 0058-Make-the-max-number-of-bind-retries-tunable.patch +Patch59: 0059-Fix-SEGFAULT-when-running-in-a-container-as-PID-1.patch +Patch61: 0001-Move-closing-standard-FDs-after-listen.patch +Patch62: 0002-Prevent-recursion-in-bug.patch +Patch63: 0001-Set-s_uwtmp_inserted-only-after-record-insertion-rem.patch +Patch64: 0002-Repeat-pututxline-if-it-fails-with-EINTR.patch +Patch65: 0001-Repeat-pututxline-until-it-succeeds-if-it-fails-with.patch +Patch67: 0001-Fix-timestamp-handling-in-MDTM.patch +Patch68: 0002-Drop-an-unused-global-variable.patch +Patch69: 0001-Remove-a-hint-about-the-ftp_home_dir-SELinux-boolean.patch +Patch70: fix-str_open.patch +Patch71: vsftpd-3.0.5-enable_wc_logs-replace_unprintable_with_hex.patch +Patch72: vsftpd-3.0.5-replace-old-network-addr-functions.patch +Patch73: vsftpd-3.0.5-replace-deprecated-openssl-functions.patch +Patch74: vsftpd-3.0.5-add-option-for-tlsv1.3-ciphersuites.patch +Patch75: vsftpd-3.0.5-use-old-tlsv-options.patch + +%description +vsftpd is a Very Secure FTP daemon. It was written completely from +scratch. + +%package doc +Summary: Documentation files for %{name} +Requires: %{name} = %{version}-%{release} +BuildArch: noarch + +%description doc +The %{name}-doc package contains documentation files for %{name}. + +%prep +%autosetup -S git +cp %{SOURCE1} . + +%build + +%make_build CFLAGS="$RPM_OPT_FLAGS -fpie -pipe -Wextra -Werror" \ + LINK="-pie -lssl $RPM_LD_FLAGS" %{?_smp_mflags} + +%install +mkdir -p $RPM_BUILD_ROOT%{_sbindir} +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir} +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/{vsftpd,pam.d,logrotate.d} +mkdir -p $RPM_BUILD_ROOT%{_mandir}/man{5,8} +mkdir -p $RPM_BUILD_ROOT%{_unitdir} +mkdir -p $RPM_BUILD_ROOT%{_generatorsdir} +install -m 755 vsftpd $RPM_BUILD_ROOT%{_sbindir}/vsftpd +install -m 600 vsftpd.conf $RPM_BUILD_ROOT%{_sysconfdir}/vsftpd/vsftpd.conf +install -m 644 vsftpd.conf.5 $RPM_BUILD_ROOT/%{_mandir}/man5/ +install -m 644 vsftpd.8 $RPM_BUILD_ROOT/%{_mandir}/man8/ +install -m 644 RedHat/vsftpd.log $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/vsftpd +install -m 644 %{SOURCE2} $RPM_BUILD_ROOT%{_sysconfdir}/pam.d/vsftpd +install -m 600 %{SOURCE3} $RPM_BUILD_ROOT%{_sysconfdir}/vsftpd/ftpusers +install -m 600 %{SOURCE4} $RPM_BUILD_ROOT%{_sysconfdir}/vsftpd/user_list +install -m 744 %{SOURCE6} $RPM_BUILD_ROOT%{_sysconfdir}/vsftpd/vsftpd_conf_migrate.sh +install -m 644 %{SOURCE7} $RPM_BUILD_ROOT%{_unitdir} +install -m 644 %{SOURCE8} $RPM_BUILD_ROOT%{_unitdir} +install -m 644 %{SOURCE9} $RPM_BUILD_ROOT%{_unitdir} +install -m 755 %{SOURCE10} $RPM_BUILD_ROOT%{_generatorsdir} + +mkdir -p $RPM_BUILD_ROOT/%{_var}/ftp/pub + +%post +%systemd_post vsftpd.service + +%preun +%systemd_preun vsftpd.service +%systemd_preun vsftpd.target + +%postun +%systemd_postun_with_restart vsftpd.service + +%files +%{_unitdir}/* +%{_generatorsdir}/* +%{_sbindir}/vsftpd +%dir %{_sysconfdir}/vsftpd +%{_sysconfdir}/vsftpd/vsftpd_conf_migrate.sh +%config(noreplace) %{_sysconfdir}/vsftpd/ftpusers +%config(noreplace) %{_sysconfdir}/vsftpd/user_list +%config(noreplace) %{_sysconfdir}/vsftpd/vsftpd.conf +%config(noreplace) %{_sysconfdir}/pam.d/vsftpd +%config(noreplace) %{_sysconfdir}/logrotate.d/vsftpd +%doc FAQ INSTALL BUGS AUDIT LICENSE README.security REWARD +%doc SPEED BENCHMARKS COPYING SECURITY/ EXAMPLE/ TUNING SIZE vsftpd.xinetd +%{_mandir}/man5/vsftpd.conf.* +%{_mandir}/man8/vsftpd.* +%{_var}/ftp + +%files doc +%doc Changelog README TODO + +%changelog +* Mon Jun 12 2023 mgb01105731 - 3.0.5-1 +- Init upstream from version 3.0.5 diff --git a/vsftpd.target b/vsftpd.target new file mode 100644 index 0000000..3828bf8 --- /dev/null +++ b/vsftpd.target @@ -0,0 +1,6 @@ +[Unit] +Description=FTP daemon +After=network-online.target + +[Install] +WantedBy=multi-user.target diff --git a/vsftpd.user_list b/vsftpd.user_list new file mode 100644 index 0000000..3e2760f --- /dev/null +++ b/vsftpd.user_list @@ -0,0 +1,20 @@ +# vsftpd userlist +# If userlist_deny=NO, only allow users in this file +# If userlist_deny=YES (default), never allow users in this file, and +# do not even prompt for a password. +# Note that the default vsftpd pam config also checks /etc/vsftpd/ftpusers +# for users that are denied. +root +bin +daemon +adm +lp +sync +shutdown +halt +mail +news +uucp +operator +games +nobody diff --git a/vsftpd.xinetd b/vsftpd.xinetd new file mode 100644 index 0000000..9b22b5b --- /dev/null +++ b/vsftpd.xinetd @@ -0,0 +1,14 @@ +# default: off +# description: The vsftpd FTP server serves FTP connections. It uses \ +# normal, unencrypted usernames and passwords for authentication. +service ftp +{ + socket_type = stream + wait = no + user = root + server = /usr/sbin/vsftpd + server_args = /etc/vsftpd/vsftpd.conf + nice = 10 + disable = yes + flags = IPv4 +} diff --git a/vsftpd@.service b/vsftpd@.service new file mode 100644 index 0000000..b063f8f --- /dev/null +++ b/vsftpd@.service @@ -0,0 +1,11 @@ +[Unit] +Description=Vsftpd ftp daemon +After=network-online.target +PartOf=vsftpd.target + +[Service] +Type=forking +ExecStart=/usr/sbin/vsftpd /etc/vsftpd/%i.conf + +[Install] +WantedBy=vsftpd.target diff --git a/vsftpd_conf_migrate.sh b/vsftpd_conf_migrate.sh new file mode 100755 index 0000000..582c20d --- /dev/null +++ b/vsftpd_conf_migrate.sh @@ -0,0 +1,13 @@ +#!/bin/bash +#move old config files and symlink them +#shipped with vsftpd-2.0.1-6 +shopt -s nullglob +PREFIX="vsftpd" +for file in /etc/${PREFIX}.*; do + if [ ! -L $file ]; then + new=`echo $file | sed s/${PREFIX}\./${PREFIX}\\\\//g | sed s/\.rpmsave//g` + mv -f ${file} ${new} + ln -s ${new} ${file} + echo $file moved to $new + fi +done -- Gitee