Submitted By:            Douglas R. Reno <renodr at linuxfromscratch dot org>
Date:                    2019-08-29
Initial Package Version: 1.3.6
Upstream Status:         Applied
Origin:                  Upstream
Description:             Contains several backported security fixes for ProFTPD
                         version 1.3.6.

diff -Naurp proftpd-1.3.6.orig/config.h.in proftpd-1.3.6/config.h.in
--- proftpd-1.3.6.orig/config.h.in	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/config.h.in	2019-08-29 14:13:08.701910513 -0500
@@ -1056,6 +1056,9 @@
 /* Define if ncursesw support, if available, should be used.  */
 #undef PR_USE_NCURSESW
 
+/* Define if non-local network tests are enabled. */
+#undef PR_USE_NETWORK_TESTS
+
 /* Define if using nonblocking open of log files.  */
 #undef PR_USE_NONBLOCKING_LOG_OPEN
 
@@ -1075,6 +1078,10 @@
  */
 #undef PR_USE_OPENSSL_ECC
 
+/* Define if OpenSSL EVP_CipherInit_ex support, if available, should be used.
+ */
+#undef PR_USE_OPENSSL_EVP_CIPHERINIT_EX
+
 /* Define if OpenSSL support (with FIPS enabled), if available, should be
  * used.
  */
diff -Naurp proftpd-1.3.6.orig/configure proftpd-1.3.6/configure
--- proftpd-1.3.6.orig/configure	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/configure	2019-08-29 17:16:57.627354281 -0500
@@ -16933,6 +16933,149 @@ fi
 
 
 if test x"$enable_memcache" = xyes; then
+
+{ echo "$as_me:$LINENO: checking for memcached_create in -lmemcached" >&5
+echo $ECHO_N "checking for memcached_create in -lmemcached... $ECHO_C" >&6; }
+if test "${ac_cv_lib_memcached_memcached_create+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lmemcached  $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char memcached_create ();
+int
+main ()
+{
+return memcached_create ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+  ac_cv_lib_memcached_memcached_create=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_cv_lib_memcached_memcached_create=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_lib_memcached_memcached_create" >&5
+echo "${ECHO_T}$ac_cv_lib_memcached_memcached_create" >&6; }
+if test $ac_cv_lib_memcached_memcached_create = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBMEMCACHED 1
+_ACEOF
+
+  LIBS="-lmemcached $LIBS"
+
+fi
+
+
+{ echo "$as_me:$LINENO: checking for libmemcached_util_ping in -lmemcachedutil" >&5
+echo $ECHO_N "checking for libmemcached_util_ping in -lmemcachedutil... $ECHO_C" >&6; }
+if test "${ac_cv_lib_memcachedutil_libmemcached_util_ping+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lmemcachedutil  $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char libmemcached_util_ping ();
+int
+main ()
+{
+return libmemcached_util_ping ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+  ac_cv_lib_memcachedutil_libmemcached_util_ping=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_cv_lib_memcachedutil_libmemcached_util_ping=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_lib_memcachedutil_libmemcached_util_ping" >&5
+echo "${ECHO_T}$ac_cv_lib_memcachedutil_libmemcached_util_ping" >&6; }
+if test $ac_cv_lib_memcachedutil_libmemcached_util_ping = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBMEMCACHEDUTIL 1
+_ACEOF
+
+  LIBS="-lmemcachedutil $LIBS"
+
+fi
+
+
   # Yes, we DO want mod_memcache AFTER the other modules in the static
   # module list. Otherwise, the module load ordering will be such that
   # memcache support will not work as expected
@@ -16941,6 +17084,78 @@ if test x"$enable_memcache" = xyes; then
 fi
 
 if test x"$enable_redis" = xyes; then
+
+{ echo "$as_me:$LINENO: checking for redisConnect in -lhiredis" >&5
+echo $ECHO_N "checking for redisConnect in -lhiredis... $ECHO_C" >&6; }
+if test "${ac_cv_lib_hiredis_redisConnect+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lhiredis  $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char redisConnect ();
+int
+main ()
+{
+return redisConnect ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+  ac_cv_lib_hiredis_redisConnect=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_cv_lib_hiredis_redisConnect=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_lib_hiredis_redisConnect" >&5
+echo "${ECHO_T}$ac_cv_lib_hiredis_redisConnect" >&6; }
+if test $ac_cv_lib_hiredis_redisConnect = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBHIREDIS 1
+_ACEOF
+
+  LIBS="-lhiredis $LIBS"
+
+fi
+
+
   # Yes, we DO want mod_redis AFTER the other modules in the static
   # module list. Otherwise, the module load ordering will be such that
   # Redis support will not work as expected.
@@ -18145,7 +18360,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 18148 "configure"
+#line 18363 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -20423,7 +20638,8 @@ fi
 ENABLE_TESTS="\"\""
 # Check whether --enable-tests was given.
 if test "${enable_tests+set}" = set; then
-  enableval=$enable_tests;  if test x"$enableval" = x"yes" ; then
+  enableval=$enable_tests;
+    if test "$enableval" != xno ; then
 
 for ac_header in check.h
 do
@@ -20648,6 +20864,14 @@ echo "$as_me: error: libcheck support, r
 
 fi
 
+
+      if test x"$enableval" != x"nonetwork" ; then
+
+cat >>confdefs.h <<\_ACEOF
+#define PR_USE_NETWORK_TESTS 1
+_ACEOF
+
+      fi
     fi
 
 fi
@@ -22461,6 +22685,8 @@ cat >>conftest.$ac_ext <<_ACEOF
   #include <sys/types.h>
   #ifdef HAVE_USERSEC_H
   # include <usersec.h>
+  #else
+  # error "we are not on AIX"
   #endif
 
 int
@@ -22473,14 +22699,14 @@ main ()
   return 0;
 }
 _ACEOF
-rm -f conftest.$ac_objext
-if { (ac_try="$ac_compile"
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
 case "(($ac_try" in
   *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
   *) ac_try_echo=$ac_try;;
 esac
 eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
-  (eval "$ac_compile") 2>conftest.er1
+  (eval "$ac_link") 2>conftest.er1
   ac_status=$?
   grep -v '^ *+' conftest.er1 >conftest.err
   rm -f conftest.er1
@@ -22489,7 +22715,8 @@ eval "echo \"\$as_me:$LINENO: $ac_try_ec
   (exit $ac_status); } && {
 	 test -z "$ac_c_werror_flag" ||
 	 test ! -s conftest.err
-       } && test -s conftest.$ac_objext; then
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
 
 
 cat >>confdefs.h <<\_ACEOF
@@ -22510,7 +22737,8 @@ echo "${ECHO_T}no" >&6; }
 
 fi
 
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
 
 
 { echo "$as_me:$LINENO: checking for AIX loginfailed" >&5
@@ -22525,6 +22753,8 @@ cat >>conftest.$ac_ext <<_ACEOF
   #include <sys/types.h>
   #ifdef HAVE_USERSEC_H
   # include <usersec.h>
+  #else
+  # error "we are not on AIX"
   #endif
 
 int
@@ -22537,14 +22767,14 @@ main ()
   return 0;
 }
 _ACEOF
-rm -f conftest.$ac_objext
-if { (ac_try="$ac_compile"
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
 case "(($ac_try" in
   *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
   *) ac_try_echo=$ac_try;;
 esac
 eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
-  (eval "$ac_compile") 2>conftest.er1
+  (eval "$ac_link") 2>conftest.er1
   ac_status=$?
   grep -v '^ *+' conftest.er1 >conftest.err
   rm -f conftest.er1
@@ -22553,7 +22783,8 @@ eval "echo \"\$as_me:$LINENO: $ac_try_ec
   (exit $ac_status); } && {
 	 test -z "$ac_c_werror_flag" ||
 	 test ! -s conftest.err
-       } && test -s conftest.$ac_objext; then
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
 
 
 cat >>confdefs.h <<\_ACEOF
@@ -22574,7 +22805,8 @@ echo "${ECHO_T}no" >&6; }
 
 fi
 
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
 
 { echo "$as_me:$LINENO: checking for AIX loginsuccess" >&5
 echo $ECHO_N "checking for AIX loginsuccess... $ECHO_C" >&6; }
@@ -22588,6 +22820,8 @@ cat >>conftest.$ac_ext <<_ACEOF
   #include <sys/types.h>
   #ifdef HAVE_USERSEC_H
   # include <usersec.h>
+  #else
+  # error "we are not on AIX"
   #endif
 
 int
@@ -22600,14 +22834,14 @@ main ()
   return 0;
 }
 _ACEOF
-rm -f conftest.$ac_objext
-if { (ac_try="$ac_compile"
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
 case "(($ac_try" in
   *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
   *) ac_try_echo=$ac_try;;
 esac
 eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
-  (eval "$ac_compile") 2>conftest.er1
+  (eval "$ac_link") 2>conftest.er1
   ac_status=$?
   grep -v '^ *+' conftest.er1 >conftest.err
   rm -f conftest.er1
@@ -22616,7 +22850,8 @@ eval "echo \"\$as_me:$LINENO: $ac_try_ec
   (exit $ac_status); } && {
 	 test -z "$ac_c_werror_flag" ||
 	 test ! -s conftest.err
-       } && test -s conftest.$ac_objext; then
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
 
 
 cat >>confdefs.h <<\_ACEOF
@@ -22637,7 +22872,8 @@ echo "${ECHO_T}no" >&6; }
 
 fi
 
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
 
 
 if test x"$install_user" = x; then
@@ -40543,6 +40779,7 @@ cat confdefs.h >>conftest.$ac_ext
 cat >>conftest.$ac_ext <<_ACEOF
 /* end confdefs.h.  */
 
+      #include <openssl/crypto.h>
       #include <openssl/fips.h>
       int main(int argc, char *argv[]) {
       # ifdef OPENSSL_FIPS
@@ -40847,6 +41084,74 @@ cat >>confdefs.h <<\_ACEOF
 _ACEOF
 
 
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+
+      { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+
+
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+  LIBS="$saved_libs"
+
+  { echo "$as_me:$LINENO: checking whether OpenSSL has EVP_CipherInit_ex support" >&5
+echo $ECHO_N "checking whether OpenSSL has EVP_CipherInit_ex support... $ECHO_C" >&6; }
+  saved_libs="$LIBS"
+
+    LIBS=`echo "$LIBS" | sed -e 's/-lsupp//g'`;
+  LIBS="-lcrypto $LIBS"
+
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+      #include <openssl/evp.h>
+
+int
+main ()
+{
+
+      (void) EVP_CipherInit_ex(NULL, NULL, NULL, NULL, NULL, 1);
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+
+      { echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6; }
+
+cat >>confdefs.h <<\_ACEOF
+#define PR_USE_OPENSSL_EVP_CIPHERINIT_EX 1
+_ACEOF
+
+
 else
   echo "$as_me: failed program was:" >&5
 sed 's/^/| /' conftest.$ac_ext >&5
diff -Naurp proftpd-1.3.6.orig/configure.in proftpd-1.3.6/configure.in
--- proftpd-1.3.6.orig/configure.in	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/configure.in	2019-08-29 14:25:00.831783116 -0500
@@ -1,7 +1,7 @@
 dnl ProFTPD - FTP server daemon
 dnl Copyright (c) 1997, 1998 Public Flood Software
 dnl Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
-dnl Copyright (c) 2001-2017 The ProFTPD Project team
+dnl Copyright (c) 2001-2018 The ProFTPD Project team
 dnl
 dnl This program is free software; you can redistribute it and/or modify
 dnl it under the terms of the GNU General Public License as published by
@@ -985,7 +985,8 @@ AC_ARG_ENABLE(tests,
     [--enable-tests],
     [enable unit tests (default=no)])
   ],
-  [ if test x"$enableval" = x"yes" ; then
+  [ 
+   if test "$enableval" != xno; then
       AC_CHECK_HEADERS(check.h)
 
       AC_CHECK_LIB(check, tcase_create,
@@ -997,6 +998,10 @@ AC_ARG_ENABLE(tests,
          AC_MSG_ERROR([libcheck support, required for tests, not present -- aborting])
         ]
       )
+
+      if test x"$enableval" != x"nonetwork" ; then
+         AC_DEFINE(PR_USE_NETWORK_TESTS, 1, [Define if non-local network tests are enabled.])
+      fi
     fi
   ])
 
@@ -1385,10 +1390,12 @@ AC_CHECK_HEADERS(hpsecurity.h, [
 dnl AIX's "lastlog" support is done via specific functions, rather than a
 dnl struct (Bug#4285).
 AC_MSG_CHECKING(for AIX authenticate)
-AC_TRY_COMPILE([
+AC_TRY_LINK([
   #include <sys/types.h>
   #ifdef HAVE_USERSEC_H
   # include <usersec.h>
+  #else
+  # error "we are not on AIX"
   #endif
   ],
   [
@@ -1404,10 +1411,12 @@ AC_TRY_COMPILE([
 
 
 AC_MSG_CHECKING(for AIX loginfailed)
-AC_TRY_COMPILE([
+AC_TRY_LINK([
   #include <sys/types.h>
   #ifdef HAVE_USERSEC_H
   # include <usersec.h>
+  #else
+  # error "we are not on AIX"
   #endif
   ],
   [
@@ -1422,10 +1431,12 @@ AC_TRY_COMPILE([
 )
 
 AC_MSG_CHECKING(for AIX loginsuccess)
-AC_TRY_COMPILE([
+AC_TRY_LINK([
   #include <sys/types.h>
   #ifdef HAVE_USERSEC_H
   # include <usersec.h>
+  #else
+  # error "we are not on AIX"
   #endif
   ],
   [
@@ -3215,6 +3226,7 @@ if test x"$pr_use_openssl" = xyes; then
 
   AC_TRY_RUN(
     [
+      #include <openssl/crypto.h>
       #include <openssl/fips.h>
       int main(int argc, char *argv[]) {
       # ifdef OPENSSL_FIPS
@@ -3329,6 +3341,30 @@ if test x"$pr_use_openssl" = xyes; then
     ],
     [
       AC_MSG_RESULT(no)
+    ]
+  )
+  LIBS="$saved_libs"
+
+  AC_MSG_CHECKING([whether OpenSSL has EVP_CipherInit_ex support])
+  saved_libs="$LIBS"
+
+  dnl Splice out -lsupp, since that library hasn't been built yet.
+  LIBS=`echo "$LIBS" | sed -e 's/-lsupp//g'`;
+  LIBS="-lcrypto $LIBS"
+
+  AC_TRY_LINK(
+    [
+      #include <openssl/evp.h>
+    ],
+    [
+      (void) EVP_CipherInit_ex(NULL, NULL, NULL, NULL, NULL, 1);
+    ],
+    [
+      AC_MSG_RESULT(yes)
+      AC_DEFINE(PR_USE_OPENSSL_EVP_CIPHERINIT_EX, 1, [Define if your OpenSSL supports EVP_CipherInit_ex])
+    ],
+    [
+      AC_MSG_RESULT(no)
     ]
   )
   LIBS="$saved_libs"
diff -Naurp proftpd-1.3.6.orig/contrib/dist/rpm/proftpd.service proftpd-1.3.6/contrib/dist/rpm/proftpd.service
--- proftpd-1.3.6.orig/contrib/dist/rpm/proftpd.service	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/dist/rpm/proftpd.service	2019-08-29 12:52:38.833520095 -0500
@@ -1,16 +1,16 @@
 [Unit]
 Description = ProFTPD FTP Server
-After = network.target nss-lookup.target local-fs.target remote-fs.target
+Wants = network-online.target
+After = network-online.target nss-lookup.target local-fs.target remote-fs.target
 
 [Service]
-Type = forking
-PIDFile = /run/proftpd/proftpd.pid
+Type = simple
 Environment = PROFTPD_OPTIONS=
 EnvironmentFile = -/etc/sysconfig/proftpd
-ExecStart = /usr/sbin/proftpd $PROFTPD_OPTIONS
-ExecStartPost = touch /var/lock/subsys/proftpd
-ExecStopPost = rm -f /var/lock/subsys/proftpd
+ExecStartPre = /usr/sbin/proftpd --configtest
+ExecStart = /usr/sbin/proftpd --nodaemon $PROFTPD_OPTIONS
 ExecReload = /bin/kill -HUP $MAINPID
+PIDFile = /run/proftpd/proftpd.pid
 
 [Install]
 WantedBy = multi-user.target
diff -Naurp proftpd-1.3.6.orig/contrib/mod_auth_otp/crypto.c proftpd-1.3.6/contrib/mod_auth_otp/crypto.c
--- proftpd-1.3.6.orig/contrib/mod_auth_otp/crypto.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_auth_otp/crypto.c	2019-08-29 14:04:07.234829469 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_auth_otp OpenSSL interface
- * Copyright (c) 2015-2017 TJ Saunders
+ * Copyright (c) 2015-2018 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -50,10 +50,7 @@ void auth_otp_crypto_free(int flags) {
     ERR_free_strings();
 
 #if OPENSSL_VERSION_NUMBER >= 0x10000001L
-# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
-     !defined(HAVE_LIBRESSL)
-    ERR_remove_thread_state();
-# else
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
     /* The ERR_remove_state(0) usage is deprecated due to thread ID
      * differences among platforms; see the OpenSSL-1.0.0c CHANGES file
      * for details.  So for new enough OpenSSL installations, use the
diff -Naurp proftpd-1.3.6.orig/contrib/mod_copy.c proftpd-1.3.6/contrib/mod_copy.c
--- proftpd-1.3.6.orig/contrib/mod_copy.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_copy.c	2019-08-29 16:52:27.612449736 -0500
@@ -1,7 +1,7 @@
 /*
  * ProFTPD: mod_copy -- a module supporting copying of files on the server
  *                      without transferring the data to the client and back
- * Copyright (c) 2009-2016 TJ Saunders
+ * Copyright (c) 2009-2019 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -657,7 +657,7 @@ MODRET copy_copy(cmd_rec *cmd) {
 MODRET copy_cpfr(cmd_rec *cmd) {
   register unsigned int i;
   int res;
-  char *path = "";
+  char *cmd_name, *path = "";
   unsigned char *authenticated = NULL;
 
   if (copy_engine == FALSE) {
@@ -705,6 +705,21 @@ MODRET copy_cpfr(cmd_rec *cmd) {
     path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", decoded_path, NULL);
   }
 
+  cmd_name = cmd->argv[0];
+  pr_cmd_set_name(cmd, "SITE_CPFR");
+  if (!dir_check(cmd->tmp_pool, cmd, G_READ, path, NULL)) {
+     int xerrno = EPERM;
+
+     pr_cmd_set_name(cmd, cmd_name);
+     pr_response_add_error(R_550, "%s: %s", (char *) cmd->argv[3],
+       strerror(xerrno));
+
+     pr_cmd_set_errno(cmd, xerrno);
+     errno = xerrno;
+     return PR_ERROR(cmd);
+  }
+  pr_cmd_set_name(cmd, cmd_name);
+
   res = pr_filter_allow_path(CURRENT_CONF, path);
   switch (res) {
     case 0:
@@ -758,6 +773,7 @@ MODRET copy_cpfr(cmd_rec *cmd) {
 MODRET copy_cpto(cmd_rec *cmd) {
   register unsigned int i;
   const char *from, *to = "";
+  char *cmd_name;
   unsigned char *authenticated = NULL;
 
   if (copy_engine == FALSE) {
@@ -816,6 +832,20 @@ MODRET copy_cpto(cmd_rec *cmd) {
 
   to = dir_canonical_vpath(cmd->tmp_pool, to);
 
+  cmd_name = cmd->argv[0];
+  pr_cmd_set_name(cmd, "SITE_CPTO");
+  if (!dir_check(cmd->tmp_pool, cmd, G_WRITE, to, NULL)) {
+     int xerrno = EPERM;
+
+     pr_cmd_set_name(cmd, cmd_name);
+     pr_response_add_err(R_500, "%s: %s", to, sterror(xerrno));
+
+     pr_cmd_set_errno(cmd, xerrno);
+     errno = xerrno;
+     return PR_ERROR(cmd);
+  }
+  pr_cmd_set_name(cmd, cmd_name);
+
   if (copy_paths(cmd->tmp_pool, from, to) < 0) {
     int xerrno = errno;
     const char *err_code = R_550;
@@ -940,7 +970,7 @@ static conftable copy_conftab[] = {
 
 static cmdtable copy_cmdtab[] = {
   { CMD, 	C_SITE, G_WRITE,	copy_copy,	FALSE,	FALSE, CL_MISC },
-  { CMD, 	C_SITE, G_DIRS,		copy_cpfr,	FALSE,	FALSE, CL_MISC },
+  { CMD, 	C_SITE, G_READ,	copy_cpfr,	FALSE,	FALSE, CL_MISC },
   { CMD, 	C_SITE, G_WRITE,	copy_cpto,	FALSE,	FALSE, CL_MISC },
   { POST_CMD,	C_PASS,	G_NONE,		copy_post_pass, FALSE,	FALSE },
   { LOG_CMD, 	C_SITE, G_NONE,		copy_log_site,	FALSE,	FALSE },
diff -Naurp proftpd-1.3.6.orig/contrib/mod_digest.c proftpd-1.3.6/contrib/mod_digest.c
--- proftpd-1.3.6.orig/contrib/mod_digest.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_digest.c	2019-08-29 14:12:26.813380398 -0500
@@ -1,7 +1,7 @@
 /*
  * ProFTPD: mod_digest - File hashing/checksumming module
  * Copyright (c) Mathias Berchtold <mb@smartftp.com>
- * Copyright (c) 2016-2017 TJ Saunders <tj@castaglia.org>
+ * Copyright (c) 2016-2018 TJ Saunders <tj@castaglia.org>
  * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -77,6 +77,11 @@
 # include <openssl/err.h>
 #endif
 
+/* Define if you have the LibreSSL library */
+#if defined(LIBRESSL_VERSION_NUMBER)
+# define HAVE_LIBRESSL 1
+#endif
+
 module digest_module;
 
 static int digest_caching = TRUE;
@@ -314,21 +319,60 @@ static int CRC32_Free(CRC32_CTX *ctx) {
 }
 
 static int crc32_init(EVP_MD_CTX *ctx) {
-  return CRC32_Init(ctx->md_data);
+  void *md_data;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+  !defined (HAVE_LIBRESSL)
+  md_data = EVP_MD_CTX_md_data(ctx);
+#else
+  md_data = ctx->md_data;
+#endif /* prior to OpenSSL-1.1.0 */
+
+  return CRC32_Init(md_data);
 }
 
 static int crc32_update(EVP_MD_CTX *ctx, const void *data, size_t datasz) {
-  return CRC32_Update(ctx->md_data, data, datasz);
+  void *md_data;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+  !defined (HAVE_LIBRESSL)
+  md_data = EVP_MD_CTX_md_data(ctx);
+#else
+  md_data = ctx->md_data;
+#endif /* prior to OpenSSL-1.1.0 */
+
+  return CRC32_Update(md_data, data, datasz);
 }
 
 static int crc32_final(EVP_MD_CTX *ctx, unsigned char *md) {
-  return CRC32_Final(md, ctx->md_data);
+  void *md_data;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+  !defined (HAVE_LIBRESSL)
+
+  md_data = EVP_MD_CTX_md_data(ctx);
+#else
+  md_data = ctx->md_data;
+#endif /* prior to OpenSSL-1.1.0 */
+
+  return CRC32_Final(md, md_data);
 }
 
 static int crc32_free(EVP_MD_CTX *ctx) {
-  return CRC32_Free(ctx->md_data);
+  void *md_data;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+    !defined(HAVE_LIBRESSL)
+  md_data = EVP_MD_CTX_md_data(ctx);
+#else
+  md_data = ctx->md_data;
+#endif /* prior to OpenSSL-1.1.0 */
+
+  return CRC32_Free(md_data);
 }
 
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
+   defined (HAVE_LIBRESSL)
 static const EVP_MD crc32_md = {
   NID_undef,
   NID_undef,
@@ -343,9 +387,30 @@ static const EVP_MD crc32_md = {
   CRC32_BLOCK,
   sizeof(EVP_MD *) + sizeof(CRC32_CTX)
 };
+#endif /* Older OpenSSLs */
 
 static const EVP_MD *EVP_crc32(void) {
-  return &crc32_md;
+  const EVP_MD *md;
+
+#if OPENSSL_VERSION_NUMBER >= 0x1010000L && \
+  !defined (HAVE_LIBRESSL)
+  /* XXX TODO: At some point, we need to call EVP_MD_meth_free() on
+   * this, to avoid a resource leak.
+   */
+  md = EVP_MD_meth_new(NID_undef, NID_undef);
+  EVP_MD_meth_set_input_blocksize(md, CRC32_BLOCK);
+  EVP_MD_meth_set_result_size(md, CRC32_DIGEST_LENGTH);
+  EVP_MD_meth_set_app_datasize(md, sizeof(EVP_MD *) + sizeof(CRC32_CTX));
+  EVP_MD_meth_set_init(md, crc32_init);
+  EVP_MD_meth_set_update(md, crc32_update);
+  EVP_MD_meth_set_final(md, crc32_final);
+  EVP_MD_meth_set_cleanup(md, crc32_free);
+  EVP_MD_meth_set_flags(md, 0);
+#else
+  md = &crc32_md;
+#endif /* Prior to OpenSSL-1.1.0 */
+  
+  return md;
 }
 
 static const char *get_errors(void) {
@@ -974,7 +1039,11 @@ static int compute_digest(pool *p, const
   struct stat st;
   unsigned char *buf;
   size_t bufsz, readsz, iter_count;
-  EVP_MD_CTX md_ctx;
+  #if OPENSSL_VERSION_NUMBER < 0x1010000L || \
+      defined(HAVE_LIBRESSL)
+  EVP_MD_CTX ctx;
+  #endif /* prior to OpenSSL-1.1.0 */
+  EVP_MD_CTX *pctx;
 
   fh = pr_fsio_open(path, O_RDONLY);
   if (fh == NULL) {
@@ -1028,12 +1097,22 @@ static int compute_digest(pool *p, const
     return -1;
   }
 
-  EVP_MD_CTX_init(&md_ctx);
-  if (EVP_DigestInit_ex(&md_ctx, md, NULL) != 1) {
+  #if OPENSSL_VERSION_NUMBER < 0x1010000L || \
+      defined(HAVE_LIBRESSL)
+    pctx = &ctx;
+  #else
+    pctx = EVP_MD_CTX_new();
+  #endif /* prior to OpenSSL-1.1.0 */
+
+    EVD_MD_CTX_init(pctx);
+    if (EVP_DigestInit_ex(pctx, md, NULL) != 1) {
     pr_log_debug(DEBUG1, MOD_DIGEST_VERSION
       ": error preparing digest context: %s", get_errors());
     (void) pr_fsio_close(fh);
-    EVP_MD_CTX_cleanup(&md_ctx);
+   #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+       !defined(HAVE_LIBRESSL)
+    EVP_MD_CTX_free(pctx);
+   #endif /* OpenSSL-1.1.0 and later */
     errno = EPERM;
     return -1;
   }
@@ -1067,7 +1146,7 @@ static int compute_digest(pool *p, const
       continue;
     }
 
-    if (EVP_DigestUpdate(&md_ctx, buf, res) != 1) {
+    if (EVP_DigestUpdate(pctx, buf, res) != 1) {
       pr_log_debug(DEBUG1, MOD_DIGEST_VERSION
         ": error updating digest: %s", get_errors());
     }
@@ -1091,7 +1170,10 @@ static int compute_digest(pool *p, const
   (void) pr_fsio_close(fh);
 
   if (len != 0) {
-    EVP_MD_CTX_cleanup(&md_ctx);
+# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+    !defined (HAVE_LIBRESSL)
+     EVP_MD_CTX_free(pctx);
+# endif /* OpenSSL-1.1.0 and later */
     pr_log_debug(DEBUG3, MOD_DIGEST_VERSION
       ": failed to read all %" PR_LU " bytes of '%s' (premature EOF?)",
       (pr_off_t) len, path);
@@ -1099,15 +1181,21 @@ static int compute_digest(pool *p, const
     return -1;
   }
 
-  if (EVP_DigestFinal_ex(&md_ctx, digest, digest_len) != 1) {
+  if (EVP_DigestFinal_ex(pctx, digest, digest_len) != 1) {
     pr_log_debug(DEBUG1, MOD_DIGEST_VERSION
       ": error finishing digest: %s", get_errors());
-    EVP_MD_CTX_cleanup(&md_ctx);
+# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+    !defined(HAVE_LIBRESSL)
+    EVP_MD_CTX_free(pctx);
+# endif /* OpenSSL-1.1.0 and later */
     errno = EPERM;
     return -1;
   }
 
-  EVP_MD_CTX_cleanup(&md_ctx);
+# if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+  !defined(HAVE_LIBRESSL)
+  EVP_MD_CTX_free(pctx);
+# endif /* OpenSSL-1.1.0 and later */
   return 0;
 }
 
diff -Naurp proftpd-1.3.6.orig/contrib/mod_exec.c proftpd-1.3.6/contrib/mod_exec.c
--- proftpd-1.3.6.orig/contrib/mod_exec.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_exec.c	2019-08-29 12:21:26.485951548 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD: mod_exec -- a module for executing external scripts
- * Copyright (c) 2002-2016 TJ Saunders
+ * Copyright (c) 2002-2017 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -31,17 +31,15 @@
 # include <sys/resource.h>
 #endif
 
-#define MOD_EXEC_VERSION	"mod_exec/0.9.14"
+#define MOD_EXEC_VERSION	"mod_exec/0.9.16"
 
 /* Make sure the version of proftpd is as necessary. */
-#if PROFTPD_VERSION_NUMBER < 0x0001030402
-# error "ProFTPD 1.3.4rc2 or later required"
+#if PROFTPD_VERSION_NUMBER < 0x0001030605
+# error "ProFTPD 1.3.6 or later required"
 #endif
 
 module exec_module;
 
-#define EXEC_MAX_FD_COUNT		1024
-
 static pool *exec_pool = NULL;
 static int exec_engine = FALSE;
 static unsigned int exec_nexecs = 0;
@@ -263,9 +261,6 @@ static char **exec_prepare_environ(pool
 }
 
 static void exec_prepare_fds(int stdin_fd, int stdout_fd, int stderr_fd) {
-  long nfiles = 0;
-  register unsigned int i = 0;
-  struct rlimit rlim;
 
   if (stdin_fd < 0) {
     stdin_fd = open("/dev/null", O_RDONLY);
@@ -314,59 +309,9 @@ static void exec_prepare_fds(int stdin_f
    * dup /dev/null.  For stdout and stderr, we dup some pipes, so that
    * we can capture what the command may write to stdout or stderr.  The
    * stderr output will be logged to the ExecLog.
-   *
-   * First, use getrlimit() to obtain the maximum number of open files
-   * for this process -- then close that number.
    */
-#if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE)
-# if defined(RLIMIT_NOFILE)
-  if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
-# elif defined(RLIMIT_OFILE)
-  if (getrlimit(RLIMIT_OFILE, &rlim) < 0) {
-# endif
-    /* Ignore ENOSYS (and EPERM, since some libc's use this as ENOSYS). */
-    if (errno != ENOSYS &&
-        errno != EPERM) {
-      exec_log("getrlimit() error: %s", strerror(errno));
-    }
-
-    /* Pick some arbitrary high number. */
-    nfiles = EXEC_MAX_FD_COUNT;
-
-  } else {
-    nfiles = rlim.rlim_max;
-  }
 
-#else /* no RLIMIT_NOFILE or RLIMIT_OFILE */
-   nfiles = EXEC_MAX_FD_COUNT;
-#endif
-
-  /* Yes, using a long for the nfiles variable is not quite kosher; it should
-   * be an unsigned type, otherwise a large limit (say, RLIMIT_INFINITY)
-   * might overflow the data type.  In that case, though, we want to know
-   * about it -- and using a signed type, we will know if the overflowed
-   * value is a negative number.  Chances are we do NOT want to be closing
-   * fds whose value is as high as they can possibly get; that's too many
-   * fds to iterate over.  Long story short, using a long int is just fine.
-   * (Plus it makes mod_exec work on Mac OSX 10.4; without this tweak,
-   * mod_exec's forked processes never return/exit.)
-   */
-
-  if (nfiles < 0 ||
-      nfiles > EXEC_MAX_FD_COUNT) {
-    nfiles = EXEC_MAX_FD_COUNT;
-  }
-
-  /* Close the "non-standard" file descriptors. */
-  for (i = 3; i < nfiles; i++) {
-
-    /* This is a potentially long-running loop, so handle signals. */
-    pr_signals_handle();
-
-    close(i);
-  }
-
-  return;
+  pr_fs_close_extra_fds();
 }
 
 static void exec_prepare_pipes(void) {
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/auth-password.c proftpd-1.3.6/contrib/mod_sftp/auth-password.c
--- proftpd-1.3.6.orig/contrib/mod_sftp/auth-password.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/auth-password.c	2019-08-29 10:58:00.044571617 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp 'password' user authentication
- * Copyright (c) 2008-2015 TJ Saunders
+ * Copyright (c) 2008-2017 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -37,6 +37,7 @@ int sftp_auth_password(struct ssh2_packe
   char *passwd;
   int have_new_passwd, res;
   struct passwd *pw;
+  size_t passwd_len;
 
   cipher_algo = sftp_cipher_get_read_algo();
   mac_algo = sftp_mac_get_read_algo();
@@ -77,6 +78,7 @@ int sftp_auth_password(struct ssh2_packe
 
   passwd = sftp_msg_read_string(pkt->pool, buf, buflen);
   passwd = sftp_utf8_decode_str(pkt->pool, passwd);
+  passwd_len = strlen(passwd);
 
   pass_cmd->arg = passwd;
 
@@ -92,7 +94,7 @@ int sftp_auth_password(struct ssh2_packe
     pr_cmd_dispatch_phase(pass_cmd, POST_CMD_ERR, 0);
     pr_cmd_dispatch_phase(pass_cmd, LOG_CMD_ERR, 0);
 
-    pr_memscrub(passwd, strlen(passwd));
+    pr_memscrub(passwd, passwd_len);
 
     *send_userauth_fail = TRUE;
     errno = EPERM;
@@ -109,15 +111,46 @@ int sftp_auth_password(struct ssh2_packe
       session.c->remote_name, pr_netaddr_get_ipstr(session.c->remote_addr),
       pr_netaddr_get_ipstr(session.c->local_addr), session.c->local_port);
 
-    pr_memscrub(passwd, strlen(passwd));
+    pr_memscrub(passwd, passwd_len);
 
     *send_userauth_fail = TRUE;
     errno = ENOENT;
     return 0;
   }
 
+  if (passwd_len == 0) {
+     config_rec *c;
+     int allow_empty_passwords = TRUE;
+
+     c = find_config(main_server->conf, CONF_PARAM, "AllowEmptyPasswords",
+       FALSE);
+     if (c != NULL) {
+        allow_empty_passwords = *((int *) c->argv[0]);
+     }
+
+     if (allow_empty_passwords == FALSE) {
+        pr_log_debug(DEBUG5, 
+          "Refusing empty password from user '%s' (AllowEmptyPasswords false)",
+          user);
+        pr_log_auth(PR_LOG_NOTICE,
+          "Refusing empty password from user '%s'", user);
+
+        pr_event_generate("mod_auth.empty-password", user);
+        pr_response_add_err(R_501, "Login incorrect.");
+
+        pr_cmd_dispatch_phase(pass_cmd, POST_CMD_ERR, 0);
+        pr_cmd_dispatch_phase(pass_cmd, LOG_CMD_ERR, 0);
+
+        pr_memscrub(passwd, passwd_len);
+
+        *send_userauth_fail = TRUE;
+        errno = EPERM;
+        return 0;
+     }
+  }
+
   res = pr_auth_authenticate(pkt->pool, user, passwd);
-  pr_memscrub(passwd, strlen(passwd));
+  pr_memscrub(passwd, passwd_len);
 
   switch (res) {
     case PR_AUTH_OK:
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/cipher.c proftpd-1.3.6/contrib/mod_sftp/cipher.c
--- proftpd-1.3.6.orig/contrib/mod_sftp/cipher.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/cipher.c	2019-08-29 14:29:34.889618326 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp ciphers
- * Copyright (c) 2008-2017 TJ Saunders
+ * Copyright (c) 2008-2018 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -74,6 +74,8 @@ static size_t cipher_blockszs[2] = {
 static unsigned int read_cipher_idx = 0;
 static unsigned int write_cipher_idx = 0;
 
+static const char *trace_channel = "ssh2";
+
 static void clear_cipher(struct sftp_cipher *);
 
 static unsigned int get_next_read_index(void) {
@@ -244,7 +246,6 @@ static int set_cipher_key(struct sftp_ci
   key_sz = sftp_crypto_get_size(cipher->key_len > 0 ?
       cipher->key_len : EVP_CIPHER_key_length(cipher->cipher),
     EVP_MD_size(hash));
-
   if (key_sz == 0) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "unable to determine key length for cipher '%s'", cipher->algo);
@@ -252,6 +253,9 @@ static int set_cipher_key(struct sftp_ci
     return -1;
   }
 
+  pr_trace_msg(trace_channel, 19, "setting key (%lu bytes) for cipher %s",
+    (unsigned long) key_sz, cipher->algo);
+
   key = malloc(key_sz);
   if (key == NULL) {
     pr_log_pri(PR_LOG_ALERT, MOD_SFTP_VERSION ": Out of memory!");
@@ -267,6 +271,9 @@ static int set_cipher_key(struct sftp_ci
   EVP_DigestFinal(ctx, key, &key_len);
   EVP_MD_CTX_destroy(ctx);
 
+  pr_trace_msg(trace_channel, 19, "hashed data to produce key (%lu bytes)",
+    (unsigned long) key_len);
+
   /* If we need more, keep hashing, as per RFC, until we have enough
    * material.
    */
@@ -287,8 +294,6 @@ static int set_cipher_key(struct sftp_ci
   }
 
   cipher->key = key;
-  cipher->key_len = key_len;
-
   return 0;
 }
 
@@ -311,22 +316,24 @@ static int set_cipher_discarded(struct s
 
   garbage_out = malloc(cipher->discard_len);
   if (garbage_out == NULL) {
-    pr_log_pri(PR_LOG_ALERT, MOD_SFTP_VERSION ": Out of memory!");
     free(garbage_in);
+    pr_log_pri(PR_LOG_ALERT, MOD_SFTP_VERSION ": Out of memory!");
     _exit(1);
   }
 
   if (EVP_Cipher(cipher_ctx, garbage_out, garbage_in,
       cipher->discard_len) != 1) {
-    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-      "error ciphering discard data: %s", sftp_crypto_get_errors());
     free(garbage_in);
     pr_memscrub(garbage_out, cipher->discard_len);
     free(garbage_out);
+    (void) pr_log_writefiles(sftp_logfd, MOD_SFTP_VERSION,
+      "error ciphering discard data: %s", sftp_crypto_get_errors());
 
     return -1;
   }
 
+  pr_trace_msg(trace_channel, 19, "discarded %lu bytes of cipher data",
+    (unsigned long) cipher->discard_len);
   free(garbage_in);
   pr_memscrub(garbage_out, cipher->discard_len);
   free(garbage_out);
@@ -355,7 +362,7 @@ const char *sftp_cipher_get_read_algo(vo
 
 int sftp_cipher_set_read_algo(const char *algo) {
   unsigned int idx = read_cipher_idx;
-  size_t key_len, discard_len;
+  size_t key_len = 0, discard_len = 0;
 
   if (read_ciphers[idx].key) {
     /* If we have an existing key, it means that we are currently rekeying. */
@@ -368,6 +375,18 @@ int sftp_cipher_set_read_algo(const char
     return -1;
   }
 
+  if (key_len > 0) {
+     pr_trace_msg(trace_channel, 19,
+       "setting read key for cipher %s: key len = %lu", algo,
+       (unsigned long) key_len);
+  }
+
+  if (discard_len > 0) {
+     pr_trace_msg(trace_channel, 19,
+       "setting read key for cipher %s: discard len = %lu", algo,
+       (unsigned long) discard_len);
+  }
+
   read_ciphers[idx].algo = algo;
   read_ciphers[idx].key_len = (uint32_t) key_len;
   read_ciphers[idx].discard_len = discard_len;
@@ -419,8 +438,6 @@ int sftp_cipher_set_read_key(pool *p, co
     return -1;
   }
 
-  key_len = (int) cipher->key_len;
-
   /* client-to-server key: HASH(K || H || "C" || session_id)
    * server-to-client key: HASH(K || H || "D" || session_id)
    */
@@ -431,8 +448,12 @@ int sftp_cipher_set_read_key(pool *p, co
     return -1;
   }
 
-  if (EVP_CipherInit(cipher_ctx, cipher->cipher, cipher->key,
+#if defined (PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
+  if (EVP_CipherInit_ex(cipher_ctx, cipher->cipher, NULL, NULL,
       cipher->iv, 0) != 1) {
+#else 
+  if (EVP_CipherInit(cipher_ctx, cipher->cipher, NNULL, cipher->iv, 0) != 1) {
+#endif
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "error initializing %s cipher for decryption: %s", cipher->algo,
       sftp_crypto_get_errors());
@@ -440,8 +461,9 @@ int sftp_cipher_set_read_key(pool *p, co
     return -1;
   }
 
+  /* Next, set the key length */
+  key_len = (int) cipher->key_len;
   if (key_len > 0) {
-    /* Next, set the key length. */
     if (EVP_CIPHER_CTX_set_key_length(cipher_ctx, key_len) != 1) {
       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
         "error setting key length (%d bytes) for %s cipher for decryption: %s",
@@ -449,6 +471,22 @@ int sftp_cipher_set_read_key(pool *p, co
       pr_memscrub(ptr, bufsz);
       return -1;
     }
+    
+    pr_trace_msg(trace_channel, 19,
+      "set key length (%d) for %s cipher key decryption", key_len,
+      cipher->algo);
+  }
+
+#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
+  if (EVP_CipherInit_ex(cipher_ctx, NULL, NULL, cipher->key, NULL, -1) != 1) {
+#else
+  if (EVP_CipherInit(cipher_ctx, NULL, cipher-key, NULL, -1) != 1) {
+#endif
+     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, 
+       "error re-initializing %s chiper for decryption: %s", cipher->algo,
+       sftp_crypto_get_errors());
+     pm_memscrub(ptr, bufsz);
+     return -1;
   }
 
   if (set_cipher_discarded(cipher, cipher_ctx) < 0) {
@@ -522,7 +560,7 @@ const char *sftp_cipher_get_write_algo(v
 
 int sftp_cipher_set_write_algo(const char *algo) {
   unsigned int idx = write_cipher_idx;
-  size_t key_len, discard_len;
+  size_t key_len = 0, discard_len = 0;
 
   if (write_ciphers[idx].key) {
     /* If we have an existing key, it means that we are currently rekeying. */
@@ -535,6 +573,18 @@ int sftp_cipher_set_write_algo(const cha
     return -1;
   }
 
+  if (key_len > 0) {
+     pr_trace_msg(trace_channel, 19,
+       "setting write key for cipher %s: key len %lu", algo,
+       (unsigned long) key_len);
+  }
+
+  if (discard_len > 0) {
+     pr_trace_msg(trace_channel, 19,
+       "setting write key for cipher %s, discard len = %lu", algo,
+       (unsigned long) discard_len);
+  }
+
   write_ciphers[idx].algo = algo;
   write_ciphers[idx].key_len = (uint32_t) key_len;
   write_ciphers[idx].discard_len = discard_len;
@@ -586,8 +636,6 @@ int sftp_cipher_set_write_key(pool *p, c
     return -1;
   }
 
-  key_len = (int) cipher->key_len;
-
   /* client-to-server key: HASH(K || H || "C" || session_id)
    * server-to-client key: HASH(K || H || "D" || session_id)
    */
@@ -597,9 +645,12 @@ int sftp_cipher_set_write_key(pool *p, c
     pr_memscrub(ptr, bufsz);
     return -1;
   }
-
-  if (EVP_CipherInit(cipher_ctx, cipher->cipher, cipher->key,
-      cipher->iv, 1) != 1) {
+#if defined (PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
+  if (EVP_CipherInit_ex(cipher_ctx, cipher->cipher, NULL, NULL,
+     cipher->iv, 1) != 1) {
+#else
+  if (EVP_CipherInit(cipher_ctx, cipher->cipher, NULL, cipher->iv, 1) != 1) {
+#endif
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "error initializing %s cipher for encryption: %s", cipher->algo,
       sftp_crypto_get_errors());
@@ -607,8 +658,9 @@ int sftp_cipher_set_write_key(pool *p, c
     return -1;
   }
 
+  /* Next, set the key length. */
+  key_len = (int) cipher->key_len;
   if (key_len > 0) {
-    /* Next, set the key length. */
     if (EVP_CIPHER_CTX_set_key_length(cipher_ctx, key_len) != 1) {
       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
         "error setting key length (%d bytes) for %s cipher for decryption: %s",
@@ -616,6 +668,22 @@ int sftp_cipher_set_write_key(pool *p, c
       pr_memscrub(ptr, bufsz);
       return -1;
     }
+
+    pr_trace_msg(trace_channel, 19,
+      "set key length (%d) for %s cipher for encryption", key_len,
+      cipher->algo);
+  }
+
+#if defined(PR_USE_OPENSSL_EVP_CIPHERINIT_EX)
+   if (EVP_CipherInit_ex(cipher_ctx, NULL, NULL, cipher->key, NULL, -1) != 1) {
+#else
+   if (EVP_CipherInit(cipher_ctx, NULL, cipher->key, NULL, -1) != 1) {
+#endif
+      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+        "error re-initializing %s cipher for encryption: %s", cipher->algo,
+        sftp_crypto_get_errors());
+      pr_memscrub(ptr, bufsz);
+      return -1
   }
 
   if (set_cipher_discarded(cipher, cipher_ctx) < 0) {
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/fxp.c proftpd-1.3.6/contrib/mod_sftp/fxp.c
--- proftpd-1.3.6.orig/contrib/mod_sftp/fxp.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/fxp.c	2019-08-29 12:47:42.654193723 -0500
@@ -297,6 +297,8 @@ struct fxp_extpair {
 static pool *fxp_pool = NULL;
 static int fxp_use_gmt = TRUE;
 
+/* FSOptions */
+static unsigned long fxp_fsio_opts = 0UL;
 static unsigned int fxp_min_client_version = 1;
 static unsigned int fxp_max_client_version = 6;
 static unsigned int fxp_utf8_protocol_version = 4;
@@ -1327,6 +1329,44 @@ static void fxp_msg_write_extpair(unsign
     TRUE);
 }
 
+static uint32_t fxp_attrs_clear_unsupported(unit32_t attr_flags) {
+
+   /* Clear and unsupported flags. */
+   if (attr_flags & SSH2_FX_ATTR_ALLOCATION_SIZE) {
+      attr_flags &= ~SSH2_FX_ATTR_ALLOCATION_SIZE;
+   }
+
+   if (attr_flags & SSH2_FX_ATTR_SUBSECOND_TIMES) {
+      attr_flags &= ~SSH2_FX_ATTR_SUBSECOND_TIMES;
+   }
+
+   if (attr_flags & SSH2_FX_ATTR_ACL) {
+      attr_flags &= ~SSH2_FX_ATTR_ACL;
+   }
+
+   if (attr_flags & SSH2_FX_ATTR_BITS) {
+      attr_flags &= ~SSH2_FX_ATTR_BITS;
+   }
+
+   if (attr_flags & SSH2_FX_ATTR_TEXT_HINT) {
+      attr_flags &= ~SSH2_FX_ATTR_TEXT_HINT;
+   }
+
+   if (attr_flags & SSH2_FX_ATTR_MIME_TYPE) {
+      attr_flags &= ~SSH2_FX_ATTR_MIME_TYPE;
+   }
+
+   if (attr_flags & SSH2_FX_ATTR_UNTRANSLATED_NAME) {
+      attr_flags &= ~SSH2_FX_ATTR_UNTRANSLATED_NAME;
+   }
+
+   if (attr_flags & SSH2_FX_ATTR_CTIME) {
+      attr_flags &= ~SSH2_FX_ATTR_CTIME;
+   }
+
+   return attr_flags;
+}
+
 static int fxp_attrs_set(pr_fh_t *fh, const char *path, struct stat *attrs,
     uint32_t attr_flags, array_header *xattrs, unsigned char **buf,
     uint32_t *buflen, struct fxp_packet *fxp) {
@@ -7325,6 +7365,8 @@ static int fxp_handle_fsetstat(struct fx
   }
   pr_cmd_set_name(cmd, cmd_name);
 
+  attr_flags = fxp_attrs_clear_unsupported(attr_flags);
+
   /* If the SFTPOption for ignoring the owners for SFTP setstat requests is set,
    * handle it by clearing the SSH2_FX_ATTR_UIDGID and SSH2_FX_ATTR_OWNERGROUP
    * flags.
@@ -7453,6 +7495,11 @@ static int fxp_handle_fstat(struct fxp_p
     pr_trace_msg(trace_channel, 7, "received request: FSTAT %s", name);
     attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS|
       SSH2_FX_ATTR_ACMODTIME;
+#ifdef PR_USE_XATTR
+    if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) {
+       attr_flags |= SSH2_FX_ATTR_EXTENDED;
+    }
+#endif /* PR_USE_XATTR */
   }
 
   fxb = pcalloc(fxp->pool, sizeof(struct fxp_buffer));
@@ -7581,6 +7628,7 @@ static int fxp_handle_fstat(struct fxp_p
   fxb->buf = buf;
   fxb->buflen = buflen;
 
+  attr_flags = fxp_attrs_clear_unsupported(attr_flags);
   fxp_attrs_write(fxp->pool, fxb, fxh->fh->fh_path, &st, attr_flags, fake_user,
     fake_group);
 
@@ -7603,6 +7651,7 @@ static int fxp_handle_init(struct fxp_pa
   uint32_t buflen, bufsz;
   struct fxp_packet *resp;
   cmd_rec *cmd;
+  config_rec *c;
 
   fxp_session->client_version = sftp_msg_read_int(fxp->pool, &fxp->payload,
     &fxp->payload_sz);
@@ -7692,6 +7741,22 @@ static int fxp_handle_init(struct fxp_pa
 
   fxp_version_add_openssh_exts(fxp->pool, &buf, &buflen);
 
+  /* Look up the FSOptions here, for use later (issue #593). We do not need
+   * set these for the FSIO API; that is already done by mod_core. Instead,
+   * we look them up for ourselves, for our own consumption/use.
+   */
+  c = find_config(main_server->conf, CONF_PARAM, "FSOptions", FALSE);
+  while (c != NULL) {
+     unsigned long opts = 0;
+
+     pr_signals_handle();
+
+     opts = *((unsigned long *) c->argv[0]);
+     fxp_fsio_opts |= opts;
+
+     c = find_config_next(c, c->next, CONF_PARAM, "FSOptions", FALSE);
+  }
+
   pr_event_generate("mod_sftp.sftp.protocol-version",
     &(fxp_session->client_version));
 
@@ -8142,7 +8207,9 @@ static int fxp_handle_lstat(struct fxp_p
     attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS|
       SSH2_FX_ATTR_ACMODTIME;
 #ifdef PR_USE_XATTR
-    attr_flags |= SSH2_FX_ATTR_EXTENDED;
+    if(!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) {
+       attr_flags |= SSH2_FX_ATTR_EXTENDED;
+    }
 #endif /* PR_USE_XATTR */
   }
 
@@ -8287,6 +8354,7 @@ static int fxp_handle_lstat(struct fxp_p
   fxb->buf = buf;
   fxb->buflen = buflen;
 
+  attr_flags = fxp_attrs_clear_unsupported(attr_flags);
   fxp_attrs_write(fxp->pool, fxb, path, &st, attr_flags, fake_user, fake_group);
 
   /* fxp_attrs_write will have changed the buf/buflen fields in the buffer. */
@@ -9130,6 +9198,8 @@ static int fxp_handle_open(struct fxp_pa
       fh->fh_path, strerror(errno));
   }
  
+  attr_flags = fxp_attrs_clear_unsupported(attr_flags);
+
   /* If the SFTPOption for ignoring perms for SFTP uploads is set, handle
    * it by clearing the SSH2_FX_ATTR_PERMISSIONS flag.
    */
@@ -10208,7 +10278,15 @@ static int fxp_handle_readdir(struct fxp
 
     /* How much non-path data do we expect to be associated with this entry? */
 #ifdef PR_USE_XATTR
-    max_entry_metadata = (1024 * 4);
+    /* Note that the "extra space" to allocate for extended attributes is
+     * currently a bit of a guess. Initially, this was 4K; that was causing
+     * slower directory listings due to the need for more READDIR requests,
+     * since we were sending fewer entries back (limited by the max packet
+     * size) per READDIR request.
+     *
+     * Now we are trying 1K, and will see how that does.
+     */
+    max_entry_metadata = 1024;
 #else
     max_entry_metadata = 256;
 #endif /* PR_USE_XATTR */
@@ -10352,7 +10430,9 @@ static int fxp_handle_readdir(struct fxp
      * to protocol version 6 clients.
      */
 #ifdef PR_USE_XATTR
-    attr_flags |= SSH2_FX_ATTR_EXTENDED;
+    if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) {
+       attr_flags |= SSH2_FX_ATTR_EXTENDED;
+    }
 #endif /* PR_USE_XATTR */
   }
 
@@ -12099,6 +12179,8 @@ static int fxp_handle_setstat(struct fxp
   }
   pr_cmd_set_name(cmd, cmd_name);
 
+  attr_flags = fxp_attrs_clear_unsupported(attr_flags);
+
   /* If the SFTPOption for ignoring the owners for SFTP setstat requests is set,
    * handle it by clearing the SSH2_FX_ATTR_UIDGID and SSH2_FX_ATTR_OWNERGROUP
    * flags.
@@ -12208,7 +12290,9 @@ static int fxp_handle_stat(struct fxp_pa
     attr_flags = SSH2_FX_ATTR_SIZE|SSH2_FX_ATTR_UIDGID|SSH2_FX_ATTR_PERMISSIONS|
       SSH2_FX_ATTR_ACMODTIME;
 #ifdef PR_USE_XATTR
-    attr_flags |= SSH2_FX_ATTR_EXTENDED;
+    if (!(fxp_fsio_opts & PR_FSIO_OPT_IGNORE_XATTR)) {
+       attr_flags |= SSH2_FX_ATTR_EXTENDED;
+    }
 #endif /* PR_USE_XATTR */
   }
 
@@ -12373,6 +12457,7 @@ static int fxp_handle_stat(struct fxp_pa
   fxb->buf = buf;
   fxb->buflen = buflen;
 
+  attr_flags = fxp_attrs_clear_unsupported(attr_flags);
   fxp_attrs_write(fxp->pool, fxb, path, &st, attr_flags, fake_user, fake_group);
 
   buf = fxb->buf;
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/kbdint.c proftpd-1.3.6/contrib/mod_sftp/kbdint.c
--- proftpd-1.3.6.orig/contrib/mod_sftp/kbdint.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/kbdint.c	2019-08-29 13:47:17.833137851 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp keyboard-interactive driver mgmt
- * Copyright (c) 2008-2016 TJ Saunders
+ * Copyright (c) 2008-2017 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -264,6 +264,7 @@ int sftp_kbdint_recv_response(pool *p, u
   struct ssh2_packet *pkt;
   char mesg_type;
   int res;
+  pool *resp_pool = NULL;
 
   if (p == NULL ||
       rcvd_count == NULL ||
@@ -282,6 +283,9 @@ int sftp_kbdint_recv_response(pool *p, u
 
   pr_response_clear(&resp_list);
   pr_response_clear(&resp_err_list);
+
+  /* Cache a reference to the current response pool used. */
+  resp_pool = pr_response_get_pool();
   pr_response_set_pool(pkt->pool);
 
   mesg_type = sftp_ssh2_packet_get_mesg_type(pkt);
@@ -290,6 +294,7 @@ int sftp_kbdint_recv_response(pool *p, u
       "expecting USER_AUTH_INFO_RESP message, received %s (%d)",
       sftp_ssh2_packet_get_mesg_type_desc(mesg_type), mesg_type);
     destroy_pool(pkt->pool);
+    pr_response_set_pool(resp_pool);
     errno = EPERM;
     return -1;
   }
@@ -315,6 +320,7 @@ int sftp_kbdint_recv_response(pool *p, u
       expected_count != 1 ? "challenges" : "challenge",
       (unsigned long) resp_count, resp_count != 1 ? "responses" : "response");
     destroy_pool(pkt->pool);
+    pr_response_set_pool(resp_pool);
     errno = EPERM;
     return -1;
   }
@@ -324,6 +330,7 @@ int sftp_kbdint_recv_response(pool *p, u
       "received too many responses (%lu > max %lu), rejecting",
       (unsigned long) resp_count, (unsigned long) SFTP_KBDINT_MAX_RESPONSES);
     destroy_pool(pkt->pool);
+    pr_response_set_pool(resp_pool);
     errno = EPERM;
     return -1;
   }
@@ -339,6 +346,7 @@ int sftp_kbdint_recv_response(pool *p, u
   *rcvd_count = resp_count;
   *responses = ((const char **) list->elts);
   destroy_pool(pkt->pool);
+  pr_response_set_pool(resp_pool);
 
   return 0;
 }
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/kex.c proftpd-1.3.6/contrib/mod_sftp/kex.c
--- proftpd-1.3.6.orig/contrib/mod_sftp/kex.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/kex.c	2019-08-29 11:32:45.716097897 -0500
@@ -3732,14 +3732,22 @@ static int read_curve25519_init(struct s
   char *data;
 
   buf = pkt->payload;
-  buflen = pkt->payload_len;
+  buflen = data_len = pkt->payload_len;
 
   data = sftp_msg_read_string(pkt->pool, &buf, &buflen);
-  data_len = strlen(data);
+
+  /* The "string" we read MIGHT contain NULs, thus using strlen(3) to determine
+   * the length of data is a Bad Idea (Issue #556). Thus instead, we track
+   * the packet payload length remaining after the read; the data length is
+   * the difference, including the length value prefix of 4 bytes.
+   */
+  data_len -= (buflen + sizeof(uint32_t));
+
   if (data_len != CURVE25519_SIZE) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-      "rejecting invalid length (%lu bytes) client Curve25519 key",
-      (unsigned long) data_len);
+      "rejecting invalid length (%lu %s, wanted %d) client Curve25519 key",
+      (unsigned long) data_len, data_len != 1 ? "bytes" : "byte",
+      CURVE25519_SIZE);
     errno = EINVAL;
     return -1;
   }
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/keys.c proftpd-1.3.6/contrib/mod_sftp/keys.c
--- proftpd-1.3.6.orig/contrib/mod_sftp/keys.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/keys.c	2019-08-29 15:08:32.528179919 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp key mgmt (keys)
- * Copyright (c) 2008-2017 TJ Saunders
+ * Copyright (c) 2008-2019 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -837,9 +837,15 @@ static int has_req_perms(int fd, const c
 static EVP_PKEY *get_pkey_from_data(pool *p, unsigned char *pkey_data,
     uint32_t pkey_datalen) {
   EVP_PKEY *pkey = NULL;
-  char *pkey_type;
+  char *pkey_type = NULL;
+  uint32_t len;
 
-  pkey_type = sftp_msg_read_string(p, &pkey_data, &pkey_datalen);
+  len = sftp_msg_read_string2(p, &pkey_data, &pkey_datalen, &pkey_type);
+  if (len == 0) {
+     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+       "error reading key: invalid/unsupported key format");
+     return NULL;
+  }
 
   if (strncmp(pkey_type, "ssh-rsa", 8) == 0) {
     RSA *rsa;
@@ -860,8 +866,23 @@ static EVP_PKEY *get_pkey_from_data(pool
       return NULL;
     }
 
-    rsa_e = sftp_msg_read_mpint(p, &pkey_data, &pkey_datalen);
-    rsa_n = sftp_msg_read_mpint(p, &pkey_data, &pkey_datalen);
+    len = sftp_msg_read_mpine2(p, &pkey_data, &pkey_datalen, &rsa_e);
+    if (len == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+         "error reading key: invalid/unsupported key format");
+       RSA_free(rsa);
+       EVP_PKEY_FREE(pkey);
+       return NULL;
+    }
+
+    len = sftp_msg_read_mpint2(p, &pkey_data, &pkey_datalen, &rsa_n);
+    if (len == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+         "error reading key: invalid/unsupported key format");
+       RSA_free(rsa);
+       EVP_PKEY_free(pkey);
+       return NULL;
+    }
 
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
     !defined(HAVE_LIBRESSL)
@@ -899,10 +920,41 @@ static EVP_PKEY *get_pkey_from_data(pool
       return NULL;
     }
 
-    dsa_p = sftp_msg_read_mpint(p, &pkey_data, &pkey_datalen);
-    dsa_q = sftp_msg_read_mpint(p, &pkey_data, &pkey_datalen);
-    dsa_g = sftp_msg_read_mpint(p, &pkey_data, &pkey_datalen);
-    dsa_pub_key = sftp_msg_read_mpint(p, &pkey_data, &pkey_datalen);
+    len = sftp_msg_read_mpint2(p, &pkey_data, &pkey_datalen, &dsa_p);
+    if (len == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+         "error reading key: invalid/unsupported key format");
+       DSA_free(dsa);
+       EVP_PKEY_free(pkey);
+       return NULL;
+    }
+
+    len = sftp_msg_read_mpint2(p, &pkey_data, &pkey_datalen, &dsa_q);
+    if (len == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+         "error reading key: invalid/unsupported key format");
+       DSA_free(dsa);
+       EVP_PKEY_free(pkey);
+       return NULL;
+    }
+
+    len = sftp_msg_read_mpint2(p, &pkey_data, &pkey_datalen, &dsa_g);
+    if (len == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, 
+         "error reading key: invalid/unsupported key format");
+       DSA_free(dsa);
+       EVP_PKEY_free(pkey);
+       return NULL;
+    }
+
+    len = sftp_msg_read_mpint2(p, &pkey_data, &pkey_datalen, &dsa_public_key);
+    if (len == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+         "error reading key: invalid/unsupported key format");
+       DSA_free(dsa);
+       EVP_PKEY_free(pkey);
+       return NULL;
+    }
 
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
     !defined(HAVE_LIBRESSL)
@@ -938,8 +990,16 @@ static EVP_PKEY *get_pkey_from_data(pool
     const EC_GROUP *curve;
     EC_POINT *point;
     int ec_nid;
+    char *ptr = NULL;
 
-    curve_name = sftp_msg_read_string(p, &pkey_data, &pkey_datalen);
+    len = sftp_msg_read_string2(p, &pkey_data, &pkey_datalen, &ptr);
+    if (len == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+         "error reading key: invalid/unsupported key format");
+       return NULL;
+    }
+
+    curve_name = (const char *) ptr;
 
     /* If the curve name does not match the last 8 characters of the
      * public key type (which, in the case of ECDSA keys, contains the
@@ -984,7 +1044,14 @@ static EVP_PKEY *get_pkey_from_data(pool
       return NULL;
     }
 
-    point = sftp_msg_read_ecpoint(p, &pkey_data, &pkey_datalen, curve, point);
+    len = sftp_msg_read_ecpoint2(p, &pkey_data, &pkey_datalen, curve, &point);
+    if (len == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+         "error reading key: invalid/unsupported key format");
+       EC_KEY_free(ec);
+       return NULL;
+    }
+
     if (point == NULL) {
       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
         "error reading EC_POINT from public key data: %s", strerror(errno));
@@ -2780,7 +2847,7 @@ static const unsigned char *dsa_sign_dat
 
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
     !defined(HAVE_LIBRESSL)
-  DSA_SIG_get0(&sig_r, &sig_s, sig);
+  DSA_SIG_get0(sig, &sig_r, &sig_s);
 #else
   sig_r = sig->r;
   sig_s = sig->s;
@@ -2960,7 +3027,7 @@ static const unsigned char *ecdsa_sign_d
 
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
     !defined(HAVE_LIBRESSL)
-  ECDSA_SIG_get0(&sig_r, &sig_s, sig);
+  ECDSA_SIG_get0(sig, &sig_r, &sig_s);
 #else
   sig_r = sig->r;
   sig_s = sig->s;
@@ -3134,7 +3201,7 @@ int sftp_keys_verify_signed_data(pool *p
 #endif /* prior to OpenSSL-1.1.0 */
   EVP_MD_CTX *pctx;
   unsigned char *sig;
-  uint32_t sig_len;
+  uint32_t len, sig_len;
   unsigned char digest[EVP_MAX_MD_SIZE];
   char *sig_type;
   unsigned int digestlen = 0;
@@ -3156,7 +3223,11 @@ int sftp_keys_verify_signed_data(pool *p
 
   if (strncmp(pubkey_algo, "ssh-dss", 8) == 0) {
     if (sftp_interop_supports_feature(SFTP_SSH2_FEAT_HAVE_PUBKEY_ALGO_IN_DSA_SIG)) {
-      sig_type = sftp_msg_read_string(p, &signature, &signaturelen);
+      len = sftp_msg_read_string2(p, &signature, &signaturelen, &sig_type);
+      if (len == 0) {
+         errno = EINVAL;
+         return -1;
+      }
 
     } else {
       /* The client did not prepend the public key algorithm name to their
@@ -3170,13 +3241,26 @@ int sftp_keys_verify_signed_data(pool *p
     }
 
   } else {
-    sig_type = sftp_msg_read_string(p, &signature, &signaturelen);
+    len = sftp_msg_read_string2(p, &signature, &signaturelen, &sig_type);
+    if (len == 0) {
+       error = EINVAL;
+       return -1;
+    }
   }
 
   if (strncmp(sig_type, "ssh-rsa", 8) == 0) {
-    sig_len = sftp_msg_read_int(p, &signature, &signaturelen);
-    sig = (unsigned char *) sftp_msg_read_data(p, &signature, &signaturelen,
-      sig_len);
+    len = sftp_msg_read_int2(p, &signature, &signaturelen, &sig_len);
+    if (len == 0) {
+       errno = EINVAL;
+       return -1;
+    }
+
+    len = sftp_msg_read_data2(p, &signature, &signaturelen, sig_len, &sig);
+    if (len == 0) {
+       errno = EINVAL;
+       return -1;
+    }
+
     if (sig != NULL) {
       RSA *rsa;
       unsigned int modulus_len;
@@ -3271,7 +3355,11 @@ int sftp_keys_verify_signed_data(pool *p
 
 #if !defined(OPENSSL_NO_DSA)
   } else if (strncmp(sig_type, "ssh-dss", 8) == 0) {
-    sig_len = sftp_msg_read_int(p, &signature, &signaturelen);
+    len = sftp_msg_read_int2(p, &signature, &signature_len, &sig_len);
+    if (len == 0) {
+       errno = EINVAL;
+       return -1;
+    }
 
     /* A DSA signature string is composed of 2 20 character parts. */
     if (sig_len != 40) {
@@ -3279,8 +3367,12 @@ int sftp_keys_verify_signed_data(pool *p
         "bad DSA signature len (%lu)", (unsigned long) sig_len);
     }
 
-    sig = (unsigned char *) sftp_msg_read_data(p, &signature, &signaturelen,
-      sig_len);
+    len = sftp_msg_read_data2(p, &signature, &signaturelen, sig_len, &sig);
+    if (len == 0) {
+       errno = EINVAL;
+       return -1;
+    }
+
     if (sig != NULL) {
       DSA *dsa;
       DSA_SIG *dsa_sig;
@@ -3307,7 +3399,7 @@ int sftp_keys_verify_signed_data(pool *p
       dsa_sig = DSA_SIG_new();
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
     !defined(HAVE_LIBRESSL)
-      DSA_SIG_get0(&sig_r, &sig_s, dsa_sig);
+      DSA_SIG_get0(sig, &sig_r, &sig_s);
 #else
       sig_r = dsa_sig->r;
       sig_s = dsa_sig->s;
@@ -3407,9 +3499,18 @@ int sftp_keys_verify_signed_data(pool *p
       }
     }
 
-    sig_len = sftp_msg_read_int(p, &signature, &signaturelen);
-    sig = (unsigned char *) sftp_msg_read_data(p, &signature, &signaturelen,
-      sig_len);
+    len = sftp_msg_read_int2(p, &signature, &signaturelen, &sig_len);
+    if (len == 0) {
+       errno = EINVAL;
+       return -1;
+    }
+
+    len = sftp_msg_read_data2(p, &signature, &signaturelen, sig_len, &sig);
+    if (len == 0) {
+       errno = EINVAL;
+       return -1;
+    }
+
     if (sig != NULL) {
       EC_KEY *ec;
       ECDSA_SIG *ecdsa_sig;
@@ -3426,13 +3527,19 @@ int sftp_keys_verify_signed_data(pool *p
 
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
     !defined(HAVE_LIBRESSL)
-      ECDSA_SIG_get0(&sig_r, &sig_s, ecdsa_sig);
+      ECDSA_SIG_get0(sig, &sig_r, &sig_s);
 #else
       sig_r = ecdsa_sig->r;
       sig_s = ecdsa_sig->s;
 #endif /* prior to OpenSSL-1.1.0 */
 
-      sig_r = sftp_msg_read_mpint(p, &sig, &sig_len);
+      len = sftp_msg_read_mpint2(p, &sig, &sig_len, &sig_r);
+      if (len == 0) {
+         ECDSA_SIG_free(ecdsa_sig);
+         errno = EINVAL;
+         return -1;
+      }
+
       if (sig_r == NULL) {
         (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
           "error reading 'r' ECDSA signature component: %s",
@@ -3441,7 +3548,13 @@ int sftp_keys_verify_signed_data(pool *p
         return -1;
       }
 
-      sig_s = sftp_msg_read_mpint(p, &sig, &sig_len);
+      len = sftp_msg_read_mpint2(p, &sig, &sig_len, &sig_s);
+      if (len == 0) {
+         ECDSA_SIG_free(ecdsa_sig);
+         errno = EINVAL;
+         return -1;
+      }
+
       if (sig_s == NULL) {
         (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
           "error reading 's' ECDSA signature component: %s",
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/mod_sftp.c proftpd-1.3.6/contrib/mod_sftp/mod_sftp.c
--- proftpd-1.3.6.orig/contrib/mod_sftp/mod_sftp.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/mod_sftp.c	2019-08-29 14:11:37.489931823 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp
- * Copyright (c) 2008-2017 TJ Saunders
+ * Copyright (c) 2008-2018 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -1211,6 +1211,8 @@ MODRET set_sftphostkey(cmd_rec *cmd) {
           insecure_hostkey_perms = TRUE;
           break;
         }
+
+        c = find_config_next(c, c->next, CONF_PARAM, "SFTPOptions", FALSE);
       }
 
       if (insecure_hostkey_perms) {
@@ -2032,11 +2034,12 @@ static int sftp_sess_init(void) {
     sftp_max_conns_ev, NULL);
 
   c = find_config(main_server->conf, CONF_PARAM, "SFTPLog", FALSE);
-  if (c) {
-    int res, xerrno;
-
+  if (c != NULL) {
     sftp_logname = c->argv[0];
 
+    if (strcasecmp(sftp_logname, "none") != 0) {
+       int res, xerrno;
+
     pr_signals_block();
     PRIVS_ROOT
     res = pr_log_openfile(sftp_logname, &sftp_logfd, PR_LOG_SYSTEM_MODE);
@@ -2045,20 +2048,21 @@ static int sftp_sess_init(void) {
     pr_signals_unblock();
 
     if (res < 0) {
-      if (res == -1) {
+      if (res == -1 ) {
         pr_log_pri(PR_LOG_NOTICE, MOD_SFTP_VERSION
-          ": notice: unable to open SFTPLog '%s': %s", sftp_logname,
-          strerror(xerrno));
+         ": notice: unable to open SFTPLog '%s': %s", sftp_logname,
+         strerror(xerrno));
 
       } else if (res == PR_LOG_WRITABLE_DIR) {
         pr_log_pri(PR_LOG_WARNING, MOD_SFTP_VERSION
-          ": notice: unable to open SFTPLog '%s': parent directory is "
-          "world-writable", sftp_logname);
+        ": notice: unable to open SFTPLog '%s': parent directory is "
+        "world-writable", sftp_logname);
 
       } else if (res == PR_LOG_SYMLINK) {
         pr_log_pri(PR_LOG_WARNING, MOD_SFTP_VERSION
-          ": notice: unable to open SFTPLog '%s': cannot log to a symlink",
-          sftp_logname);
+        ": notice: unable to open SFTPLog '%s': cannot log to a symlink",
+        sftp_logname);
+      }
       }
     }
   }
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/msg.c proftpd-1.3.6/contrib/mod_sftp/msg.c
--- proftpd-1.3.6.orig/contrib/mod_sftp/msg.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/msg.c	2019-08-29 15:45:06.456678279 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp message format
- * Copyright (c) 2008-2017 TJ Saunders
+ * Copyright (c) 2008-2018 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -51,65 +51,117 @@ unsigned char *sftp_msg_getbuf(pool *p,
   return palloc(p, sz);
 }
 
-char sftp_msg_read_byte(pool *p, unsigned char **buf, uint32_t *buflen) {
-  char byte = 0;
-
+uint32_t sftp_msg_read_byte2(pool *p, unsighed char **buf, uint32_t *buflen,
+    char *byte) {
   (void) p;
 
   if (*buflen < sizeof(char)) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to read byte (buflen = %lu)",
       (unsigned long) *buflen);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
-  memcpy(&byte, *buf, sizeof(char));
+  memcpy(byte, *buf, sizeof(char));
   (*buf) += sizeof(char);
   (*buflen) -= sizeof(char);
 
+  return sizeof(char);
+}
+
+char sftp_msg_read_byte(pool *p, unsigned char **buf, uint32_t *buflen) {
+  char byte = 0;
+  uint32_t len;
+
+  len = sftp_msg_read_byte2(p, buf, buflen, &byte);
+  if (len == 0) {
+    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
+    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+  }
+
   return byte;
 }
 
-int sftp_msg_read_bool(pool *p, unsigned char **buf, uint32_t *buflen) {
-  char bool = 0;
+uint32_t sftp_msg_read_bool2(pool *p, unsigned char **buf, uint32_t buflen,
+    int *bool) {
+   char byte = 0;
+   uint32_t len;
 
   (void) p;
 
-  bool = sftp_msg_read_byte(p, buf, buflen);
-  if (bool == 0)
+  len = sftp_msg_read_byte2(p, buf, buflen, &byte);
+  if (len == 0) {
     return 0;
+  }
 
-  return 1;
+  *bool = byte;
+  return len;
 }
 
-unsigned char *sftp_msg_read_data(pool *p, unsigned char **buf,
-    uint32_t *buflen, size_t datalen) {
-  unsigned char *data = NULL;
+int sftp_msg_read_bool(pool *p, unsigned char **buf, uint32_t *buflen) {
+   int bool = 0;
+   uint32_t len;
 
-  if (*buflen < datalen) {
-    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-      "message format error: unable to read %lu bytes of raw data "
-      "(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen);
+  len = sftp_msg_read_bool2(p, buf, buflen, &bool);
+  if (len == 0) {
     pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
     SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
   }
 
+  if (bool == 0) {
+     return FALSE;
+  }
+
+  return TRUE;
+}
+
+uint32_t sftp_msg_read_data2(pool *p, unsigned char **buf,
+    uint32_t *buflen, size_t datalen, unsigned char **data) {
   if (datalen == 0) {
-    return NULL;
+    return 0;
+  }
+
+  if (*buflen < datalen) {
+    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+      "message format error: unable to read %lu bytes of raw data "
+      "(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen);
+    return 0;
   }
 
-  data = palloc(p, datalen);
+  *data = palloc(p, datalen);
 
-  memcpy(data, *buf, datalen);
+  memcpy(*data, *buf, datalen);
   (*buf) += datalen;
   (*buflen) -= datalen;
 
+  return datalen;
+}
+
+unsigned char *sftp_msg_read_data(pool *p, unsigned chat **buf,
+  uint32_t *buflen, size_t datalen) {
+
+  unsigned char *data = NULL;
+  uint32_t len;
+
+  if (datalen == 0) {
+     errno = EINVAL;
+     return NULL;
+  }
+
+  len = sftp_msg_read_data2(p, buf, buflen, datalen, &data);
+  if (len == 0) {
+    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+     "message format error: unable to read %lu bytes of raw data "
+     "(buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen);
+     pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
+     SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+  }
+
   return data;
 }
 
-uint32_t sftp_msg_read_int(pool *p, unsigned char **buf, uint32_t *buflen) {
-  uint32_t val = 0;
+uint32_t sftp_msg_read_int2(pool *p, unsigned char **buf, uint32_t *buflen,
+    uint32_t *val) {
 
   (void) p;
 
@@ -117,88 +169,113 @@ uint32_t sftp_msg_read_int(pool *p, unsi
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to read int (buflen = %lu)",
       (unsigned long) *buflen);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
-  memcpy(&val, *buf, sizeof(uint32_t));
+  memcpy(val, *buf, sizeof(uint32_t));
   (*buf) += sizeof(uint32_t);
   (*buflen) -= sizeof(uint32_t);
 
-  val = ntohl(val);
-  return val;
+  *val = ntohl(val);
+  return sizeof(uint32_t);
 }
 
-uint64_t sftp_msg_read_long(pool *p, unsigned char **buf, uint32_t *buflen) {
-  uint64_t val = 0;
+uint32_t sftp_msg_read_int(pool *p, unsigned char **buf, uint32_t *buflen) {
+   uint32_t val = 0;
+   uint32_t len;
+
+   len = sftp_msg_read_int2(p, buf, buflen, &val);
+   if (len == 0) {
+      pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
+      SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+   }
+
+   return val;
+}
+
+uint32_t sftp_msg_read_long2(pool *p, unsigned char **buf, uint32_t *buflen,
+    uint64_t *val) {
   unsigned char data[8];
 
   if (*buflen < sizeof(data)) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to read long (buflen = %lu)",
       (unsigned long) *buflen);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
   memcpy(data, *buf, sizeof(data));
   (*buf) += sizeof(data);
   (*buflen) -= sizeof(data);
 
-  val = (uint64_t) data[0] << 56;
-  val |= (uint64_t) data[1] << 48;
-  val |= (uint64_t) data[2] << 40;
-  val |= (uint64_t) data[3] << 32;
-  val |= (uint64_t) data[4] << 24;
-  val |= (uint64_t) data[5] << 16;
-  val |= (uint64_t) data[6] << 8;
-  val |= (uint64_t) data[7];
+  (*val) = (uint64_t) data[0] << 56;
+  (*val) |= (uint64_t) data[1] << 48;
+  (*val) |= (uint64_t) data[2] << 40;
+  (*val) |= (uint64_t) data[3] << 32;
+  (*val) |= (uint64_t) data[4] << 24;
+  (*val) |= (uint64_t) data[5] << 16;
+  (*val) |= (uint64_t) data[6] << 8;
+  (*val) |= (uint64_t) data[7];
+
+  return sizeof(data);
+}
+uint64_t sftp_msg_read_long(pool *p, unsigned char **buf, uint32_t *buflen) {
+   uint64_t val = 0;
+   uint32_t len;
 
-  return val;
+   len = sftp_msg_read_long2(p, buf, buflen, &val);
+   if (len == 0) {
+      pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
+      SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+   }
+
+   return val;
 }
 
-BIGNUM *sftp_msg_read_mpint(pool *p, unsigned char **buf, uint32_t *buflen) {
-  BIGNUM *mpint = NULL;
+uint32_t sftp_msg_read_mpint2(pool *p, unsigned char **buf, uint32_t *buflen, 
+   BIGNUM **mpint) {
+  unsigned char *mpint_data = NULL;
   const unsigned char *data = NULL, *ptr = NULL;
-  uint32_t datalen = 0, len = 0;
+  uint32_t datalen = 0, mpint_len = 0, len = 0, total_len = 0;
 
-  len = sftp_msg_read_int(p, buf, buflen);
+  len = sftp_msg_read_int(p, buf, buflen, &mpint_len);
+  if (len == 0) {
+     return 0;
+  }
 
-  if (*buflen < len) {
+  if (*buflen < mpint_len) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to read %lu bytes of mpint (buflen = %lu)",
       (unsigned long) len, (unsigned long) *buflen);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
   if (len > (1024 * 16)) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to handle mpint of %lu bytes",
       (unsigned long) len);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
-  ptr = (const unsigned char *) sftp_msg_read_data(p, buf, buflen, len);
-  if (ptr == NULL) {
-    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-      "message format error: unable to read %lu bytes of mpint data",
-      (unsigned long) len);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+  total_len += len;
+
+  len = sftp_msg_read_data2(p, buf, buflen, mpint_len, &mpint_data);
+  if (len == 0) {
+     return 0;
   }
 
+  total_len += len;
+
+  ptr = (const unsigned char *) mpint_data;
   if ((ptr[0] & 0x80) != 0) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: negative mpint numbers not supported");
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
   /* Trim any leading zeros. */
   data = ptr;
-  datalen = len;
+  datalen = mpint_len;
   while (datalen > 0 &&
          *data == 0x00) {
     pr_signals_handle();
@@ -206,11 +283,23 @@ BIGNUM *sftp_msg_read_mpint(pool *p, uns
     datalen--;
   }
 
-  mpint = BN_bin2bn(data, (int) datalen, NULL);
-  if (mpint == NULL) {
+  *mpint = BN_bin2bn(data, (int) datalen, NULL);
+  if (*mpint == NULL) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to convert binary mpint: %s",
       sftp_crypto_get_errors());
+      return 0;
+  }
+
+  return total_len; 
+}
+
+BIGNUM *sftp_msg_read_mpint(pool *p, unsigned char **buf, uint32_t *buflen) {
+   BIGNUM *mpint = NULL;
+   uint32_t len;
+
+   len = sftp_msg_read_mpint2(p, buf, buflen, &mpint);
+   if (len == 0) {
     pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
     SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
   }
@@ -218,9 +307,9 @@ BIGNUM *sftp_msg_read_mpint(pool *p, uns
   return mpint;
 }
 
-char *sftp_msg_read_string(pool *p, unsigned char **buf, uint32_t *buflen) {
-  uint32_t len = 0;
-  char *str = NULL;
+uint32_t sftp_msg_read_string2(pool *p, unsigned char **buf, uint32_t *buflen,
+    char **str) {
+   uint32_t data_len = 0, len = 0;
 
   /* If there is no data remaining, treat this as if the string is empty
    * (see Bug#4093).
@@ -229,99 +318,134 @@ char *sftp_msg_read_string(pool *p, unsi
     pr_trace_msg(trace_channel, 9,
       "malformed message format (buflen = %lu) for reading string, using \"\"",
       (unsigned long) *buflen);
-    return "";
+    *str = pstrdup(p, "");
+    return 1;
   }
 
-  len = sftp_msg_read_int(p, buf, buflen);
+  len = sftp_msg_read_int2(p, buf, buflen, &data_len);
+  if (len == 0) {
+     return 0;
+  }
 
   /* We can't use sftp_msg_read_data() here, since we need to allocate and
    * populate a buffer that is one byte longer than the len just read in,
    * for the terminating NUL.
    */
 
-  if (*buflen < len) {
+  if (*buflen < data_len) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to read %lu bytes of string data "
-      "(buflen = %lu)", (unsigned long) len, (unsigned long) *buflen);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+      "(buflen = %lu)", (unsigned long) data_len, (unsigned long) *buflen);
+    return 0;
   }
 
-  str = palloc(p, len + 1);
+  *str = palloc(p, data_len + 1);
+
+  if (data_len > 0) {
+     memcpy(*str, *buf, data_len);
+     (*buf) += data_len;
+     (*buflen) -= data_len;
+  }
+  (*str)[data_len] = '\0';
+
+  return len + data_len;
+}
+
+char *sftp_msg_read_string(pool *p, unsigned char **buf, uint32_t *buflen) {
+  char *str = NULL;
+  uint32_t len;
 
-  if (len > 0) {
-    memcpy(str, *buf, len);
-    (*buf) += len;
-    (*buflen) -= len;
+  len = sftp_msg_read_string2(p, buf, buflen, &str);
+  if (len == 0) {
+     pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
+     SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
   }
-  str[len] = '\0';
 
   return str;
 }
 
 #ifdef PR_USE_OPENSSL_ECC
-EC_POINT *sftp_msg_read_ecpoint(pool *p, unsigned char **buf, uint32_t *buflen,
-    const EC_GROUP *curve, EC_POINT *point) {
+uint32_t sftp_msg_read_ecpoint2(pool *p, unsigned char **buf, uint32_t *buflen,
+    const EC_GROUP *curve, EC_POINT **point) {
   BN_CTX *bn_ctx;
   unsigned char *data = NULL;
-  uint32_t datalen = 0;
+  uint32_t datalen = 0, len = 0, total_len = 0;
 
-  datalen = sftp_msg_read_int(p, buf, buflen);
+  len = sftp_msg_read_int2(p, buf, buflen, &datalen);
+  if (len == 0) {
+     return 0;
+  }
 
   if (*buflen < datalen) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to read %lu bytes of EC point"
       " (buflen = %lu)", (unsigned long) datalen, (unsigned long) *buflen);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
   if (datalen > MAX_ECPOINT_LEN) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: EC point length too long (%lu > max %lu)",
       (unsigned long) datalen, (unsigned long) MAX_ECPOINT_LEN);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
+  }
+
+  total_len += len;
+
+  len = sftp_msg_read_data2(p, buf, buflen, datalen, &data);
+  if (len == 0) {
+     return 0;
   }
 
-  data = sftp_msg_read_data(p, buf, buflen, datalen); 
   if (data == NULL) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to read %lu bytes of EC point data",
       (unsigned long) datalen);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
+  total_len += len;
+
   if (data[0] != POINT_CONVERSION_UNCOMPRESSED) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: EC point data formatted incorrectly "
       "(leading byte 0x%02x should be 0x%02x)", data[0],
       POINT_CONVERSION_UNCOMPRESSED);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
   bn_ctx = BN_CTX_new();
   if (bn_ctx == NULL) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "error allocating new BN_CTX: %s", sftp_crypto_get_errors());
-    return NULL;
+    return 0;
   }
 
-  if (EC_POINT_oct2point(curve, point, data, datalen, bn_ctx) != 1) {
+  if (EC_POINT_oct2point(curve, *point, data, datalen, bn_ctx) != 1) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "message format error: unable to convert binary EC point data: %s",
       sftp_crypto_get_errors());
     BN_CTX_free(bn_ctx);
-    pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
-    SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+    return 0;
   }
 
   BN_CTX_free(bn_ctx);
-
   pr_memscrub(data, datalen);
-  return point;
+
+  return total_len;
+}
+
+EC_POINT *sftp_msg_read_ecpoint(pool *p, unsigned char **buf, uint32_t *buflen,
+     const EC_GROUP *curve, EC_POINT *point) {
+   uint32_t len;
+
+   len = sftp_msg_read_ecpoint2(p, buf, buflen, curve, &point);
+   if (len == 0) {
+      pr_log_stacktrace(sftp_logfd, MOD_SFTP_VERSION);
+      SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
+   }
+
+   return point;
 }
 #endif /* PR_USE_OPENSSL_ECC */
 
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sftp/msg.h proftpd-1.3.6/contrib/mod_sftp/msg.h
--- proftpd-1.3.6.orig/contrib/mod_sftp/msg.h	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sftp/msg.h	2019-08-29 15:49:50.500402841 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp message format
- * Copyright (c) 2008-2016 TJ Saunders
+ * Copyright (c) 2008-2019 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -39,6 +39,22 @@ uint64_t sftp_msg_read_long(pool *, unsi
 BIGNUM *sftp_msg_read_mpint(pool *, unsigned char **, uint32_t *);
 char *sftp_msg_read_string(pool *, unsigned char **, uint32_t *);
 
+/* Variant of the Message Read API whose return value indicates the number
+ * of bytes of the message actually read. A zero-length return value indicates
+ * failure to read the requested data type.
+ */
+uint32_t sftp_msg_read_byte2(pool *, unsigned char **, uint32_t *, char *);
+uint32_t sftp_msg_read_bool2(pool *, unsigned char **, uint32_t *, int *);
+uint32_t sftp_msg_read_data2(pool *, unsigned char **, uint32_t *, size_t, unsigned char ** );
+#ifdef PR_USE_OPENSSL_ECC
+uint32_t sftp_msg_read_ecpoint2(pool *, unsigned char **, uint32_t *,
+      const EC_GROUP *, EC_POINT **);
+#endif /* PR_USE_OPENSSL_ECC */
+uint32_t sftp_msg_read_int2(pool *, unsigned char **, uint32_t *, uint32_t *);
+uint32_t sftp_msg_read_long2(pool *, unsigned char **, uint32_t *, uint64_t *);
+uint32_t sftp_msg_read_mpint2(pool *, unsigned char **, uint32_t *, BIGNUM **);
+uint32_t sftp_msg_read_string2(pool *, unsigned char **, uint32_t *, char **);
+
 uint32_t sftp_msg_write_byte(unsigned char **, uint32_t *, char);
 uint32_t sftp_msg_write_bool(unsigned char **, uint32_t *, char);
 uint32_t sftp_msg_write_data(unsigned char **, uint32_t *,
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sql.c proftpd-1.3.6/contrib/mod_sql.c
--- proftpd-1.3.6.orig/contrib/mod_sql.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sql.c	2019-08-29 13:31:15.334573515 -0500
@@ -261,10 +261,23 @@ static const char *get_named_conn_backen
     struct sql_named_conn *snc;
 
     for (snc = sql_named_conns; snc; snc = snc->next) {
+      pr_trace_msg(trace_channel, 17,
+        "comparing requested named connection '%s' with '%s'", conn_name,
+        snc->conn_name);
+
       if (strcmp(snc->conn_name, conn_name) == 0) {
         return snc->backend;
       }
     }
+
+    pr_trace_msg(trace_channel, 17,
+      "unable to find named connection '%s': no such named connection found",
+      conn_name);
+
+  } else {
+    pr_trace_msg(trace_channel, 17,
+      "unable to find named connection '%s': no named connections registered.",
+      conn_name);
   }
 
   errno = ENOENT;
@@ -519,14 +532,24 @@ static modret_t *sql_dispatch(cmd_rec *c
 static struct sql_backend *sql_get_backend(const char *backend) {
   struct sql_backend *sb;
 
-  if (!sql_backends)
+  if (sql_backends == NULL) {
+    pr_trace_msg(trace_channel, 17,
+      "unable to find '%s' backend: no backends registered", backend);
     return NULL;
+  }
 
   for (sb = sql_backends; sb; sb = sb->next) {
-    if (strcasecmp(sb->backend, backend) == 0)
+    pr_trace_msg(trace_channel, 17,
+      "comparing requested backend '%s' with '%s'", backend, sb->backend);
+
+    if (strcasecmp(sb->backend, backend) == 0) {
       return sb;
+    }
   }
 
+  pr_trace_msg(trace_channel, 17,
+    "unable to find '%s' backend: no such backend found", backend);
+  errno = ENOENT;
   return NULL;
 }
 
@@ -564,6 +587,7 @@ int sql_register_backend(const char *bac
 
   sql_backends = sb;
   sql_nbackends++;
+  pr_trace_msg(trace_channel, 8, "registered '%s' backend", backend);
 
   return 0;
 }
@@ -7050,7 +7074,7 @@ static int sql_init(void) {
 static int sql_sess_init(void) {
   char *authstr = NULL;
   config_rec *c = NULL;
-  void *ptr = NULL;
+  void *default_backend = NULL, *ptr = NULL;
   unsigned char *negative_cache = NULL;
   cmd_rec *cmd = NULL;
   modret_t *mr = NULL;
@@ -7082,12 +7106,12 @@ static int sql_sess_init(void) {
     }
   }
 
-  ptr = get_param_ptr(main_server->conf, "SQLBackend", FALSE);
-  sql_default_cmdtable = sql_set_backend(ptr);
+  default_backend = get_param_ptr(main_server->conf, "SQLBackend", FALSE);
+  sql_default_cmdtable = sql_set_backend(default_backend);
   if (sql_default_cmdtable == NULL) {
-    if (ptr != NULL) {
+    if (default_backend != NULL) {
       sql_log(DEBUG_INFO, "unable to load '%s' SQL backend: %s",
-        (char *) ptr, strerror(errno));
+        (char *) default_backend, strerror(errno));
 
     } else {
       sql_log(DEBUG_INFO, "unable to load SQL backend: %s", strerror(errno));
@@ -7097,8 +7121,9 @@ static int sql_sess_init(void) {
     return -1;
   }
 
-  if (ptr != NULL) {
-    pr_trace_msg(trace_channel, 9, "loaded '%s' SQL backend", (char *) ptr);
+  if (default_backend != NULL) {
+    pr_trace_msg(trace_channel, 9, "loaded '%s' SQL backend", 
+      (char *) default_backend);
   }
 
   /* Construct our internal cache structure for this session. */
@@ -7523,6 +7548,12 @@ static int sql_sess_init(void) {
           pr_sql_conn_policy = SQL_CONN_POLICY_PERCALL;
         }
 
+        /* Make sure we set the correct backend driver here, so that we
+         * dispatch to the correct module's command table when defining the
+         * connection.
+         */
+        sql_set_backend(c->argv[1]);
+
         if (sql_define_conn(tmp_pool, c->argv[0], c->argv[3], c->argv[4],
             c->argv[2], c->argv[5], c->argv[6], c->argv[7], c->argv[8],
             c->argv[9], c->argv[10]) < 0) {
@@ -7565,6 +7596,13 @@ static int sql_sess_init(void) {
     }
   }
 
+  /* Make sure we set the default SQLBackend here, after processing any
+   * SQLNamedConnectInfos.
+   */
+  if (default_backend != NULL) {
+     sql_set_backend(default_backend);
+  }
+
   c = find_config(main_server->conf, CONF_PARAM, "SQLLogOnEvent", FALSE);
   while (c != NULL) {
     char *event_name;
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sql_mysql.c proftpd-1.3.6/contrib/mod_sql_mysql.c
--- proftpd-1.3.6.orig/contrib/mod_sql_mysql.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sql_mysql.c	2019-08-29 13:35:48.810329970 -0500
@@ -434,7 +434,8 @@ MODRET cmd_open(cmd_rec *cmd) {
   entry = sql_get_connection(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tmysql cmd_open");
-    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION,
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   } 
 
   conn = (db_conn_t *) entry->data;
@@ -737,7 +738,8 @@ MODRET cmd_close(cmd_rec *cmd) {
   entry = sql_get_connection(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tmysql cmd_close");
-    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1081,7 +1083,8 @@ MODRET cmd_select(cmd_rec *cmd) {
   entry = sql_get_connection(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tmysql cmd_select");
-    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
  
   conn = (db_conn_t *) entry->data;
@@ -1217,7 +1220,8 @@ MODRET cmd_insert(cmd_rec *cmd) {
   entry = sql_get_connection(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tmysql cmd_insert");
-    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1313,7 +1317,8 @@ MODRET cmd_update(cmd_rec *cmd) {
   entry = sql_get_connection(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tmysql cmd_update");
-    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1437,7 +1442,8 @@ MODRET cmd_query(cmd_rec *cmd) {
   entry = sql_get_connection(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tmysql cmd_query");
-    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1536,7 +1542,8 @@ MODRET cmd_escapestring(cmd_rec * cmd) {
   entry = sql_get_connection(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tmysql cmd_escapestring");
-    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_MYSQL_VERSION, 
+       pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sql_odbc.c proftpd-1.3.6/contrib/mod_sql_odbc.c
--- proftpd-1.3.6.orig/contrib/mod_sql_odbc.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sql_odbc.c	2019-08-29 13:39:02.952023794 -0500
@@ -801,7 +801,8 @@ MODRET sqlodbc_open(cmd_rec *cmd) {
   entry = sqlodbc_get_conn(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \todbc cmd_open");
-    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, 
+       pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   } 
 
   conn = (db_conn_t *) entry->data;
@@ -1009,7 +1010,8 @@ MODRET sqlodbc_close(cmd_rec *cmd) {
   entry = sqlodbc_get_conn(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \todbc cmd_close");
-    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1137,7 +1139,8 @@ MODRET sqlodbc_select(cmd_rec *cmd) {
   entry = sqlodbc_get_conn(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \todbc cmd_select");
-    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
  
   conn = (db_conn_t *) entry->data;
@@ -1286,7 +1289,8 @@ MODRET sqlodbc_insert(cmd_rec *cmd) {
   entry = sqlodbc_get_conn(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \todbc cmd_insert");
-    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1377,7 +1381,8 @@ MODRET sqlodbc_update(cmd_rec *cmd) {
   entry = sqlodbc_get_conn(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \todbc cmd_update");
-    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1484,7 +1489,8 @@ MODRET sqlodbc_query(cmd_rec *cmd) {
   entry = sqlodbc_get_conn(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \todbc cmd_query");
-    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1587,7 +1593,8 @@ MODRET sqlodbc_quote(cmd_rec *cmd) {
   entry = sqlodbc_get_conn(cmd->argv[0]);
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \todbc cmd_escapestring");
-    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, "unknown named connection");
+    return PR_ERROR_MSG(cmd, MOD_SQL_ODBC_VERSION, 
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   /* Make sure the connection is open. */
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sql_postgres.c proftpd-1.3.6/contrib/mod_sql_postgres.c
--- proftpd-1.3.6.orig/contrib/mod_sql_postgres.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sql_postgres.c	2019-08-29 13:41:47.833063651 -0500
@@ -389,7 +389,7 @@ MODRET cmd_open(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_open");
     return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   } 
 
   conn = (db_conn_t *) entry->data;
@@ -610,7 +610,7 @@ MODRET cmd_close(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_close");
     return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -952,7 +952,7 @@ MODRET cmd_select(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_select");
     return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
   
   conn = (db_conn_t *) entry->data;
@@ -1097,7 +1097,7 @@ MODRET cmd_insert(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_insert");
     return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1201,7 +1201,7 @@ MODRET cmd_update(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_update");
     return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1332,7 +1332,7 @@ MODRET cmd_query(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_query");
     return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -1443,7 +1443,7 @@ MODRET cmd_escapestring(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tpostgres cmd_escapestring");
     return PR_ERROR_MSG(cmd, MOD_SQL_POSTGRES_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
diff -Naurp proftpd-1.3.6.orig/contrib/mod_sql_sqlite.c proftpd-1.3.6/contrib/mod_sql_sqlite.c
--- proftpd-1.3.6.orig/contrib/mod_sql_sqlite.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_sql_sqlite.c	2019-08-29 13:44:56.697817202 -0500
@@ -84,16 +84,20 @@ static void db_trace(void *user_data, co
 static conn_entry_t *sql_sqlite_get_conn(char *name) {
   register unsigned int i = 0;
 
-  if (!name)
+  if (name == NULL) {
+    errno = EINVAL;
     return NULL;
+  }
 
   for (i = 0; i < conn_cache->nelts; i++) {
     conn_entry_t *entry = ((conn_entry_t **) conn_cache->elts)[i];
 
-    if (strcmp(name, entry->name) == 0)
+    if (strcmp(name, entry->name) == 0) {
       return entry;
+    }
   }
 
+  errno = ENOENT;
   return NULL;
 }
 
@@ -301,7 +305,7 @@ MODRET sql_sqlite_open(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tsqlite cmd_open");
     return PR_ERROR_MSG(cmd, MOD_SQL_SQLITE_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   } 
 
   conn = (db_conn_t *) entry->data;
@@ -434,7 +438,7 @@ MODRET sql_sqlite_close(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tsqlite cmd_close");
     return PR_ERROR_MSG(cmd, MOD_SQL_SQLITE_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -591,7 +595,7 @@ MODRET sql_sqlite_select(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tsqlite cmd_select");
     return PR_ERROR_MSG(cmd, MOD_SQL_SQLITE_VERSION,
-      "unknown named connection");
+      pstrcat(cmp->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
  
   conn = (db_conn_t *) entry->data;
@@ -699,7 +703,7 @@ MODRET sql_sqlite_insert(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tsqlite cmd_insert");
     return PR_ERROR_MSG(cmd, MOD_SQL_SQLITE_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -788,7 +792,7 @@ MODRET sql_sqlite_update(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tsqlite cmd_update");
     return PR_ERROR_MSG(cmd, MOD_SQL_SQLITE_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -909,7 +913,7 @@ MODRET sql_sqlite_query(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tsqlite cmd_query");
     return PR_ERROR_MSG(cmd, MOD_SQL_SQLITE_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   conn = (db_conn_t *) entry->data;
@@ -987,7 +991,7 @@ MODRET sql_sqlite_quote(cmd_rec *cmd) {
   if (entry == NULL) {
     sql_log(DEBUG_FUNC, "%s", "exiting \tsqlite cmd_escapestring");
     return PR_ERROR_MSG(cmd, MOD_SQL_SQLITE_VERSION,
-      "unknown named connection");
+      pstrcat(cmd->tmp_pool, "unknown named connection: ", cmd->argv[0], NULL));
   }
 
   /* Make sure the connection is open. */
diff -Naurp proftpd-1.3.6.orig/contrib/mod_tls.c proftpd-1.3.6/contrib/mod_tls.c
--- proftpd-1.3.6.orig/contrib/mod_tls.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/contrib/mod_tls.c	2019-08-29 16:54:05.494312929 -0500
@@ -2,7 +2,7 @@
  * mod_tls - An RFC2228 SSL/TLS module for ProFTPD
  *
  * Copyright (c) 2000-2002 Peter 'Luna' Runestig <peter@runestig.com>
- * Copyright (c) 2002-2017 TJ Saunders <tj@castaglia.org>
+ * Copyright (c) 2002-2019 TJ Saunders <tj@castaglia.org>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without modifi-
@@ -1187,6 +1187,9 @@ static struct tls_label tls_ciphersuite_
   { 0x00C4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
   { 0x00C5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256" },
   { 0x00FF, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" },
+  { 0x1301, "TLS_AES_128_GCM_SHA256" },
+  { 0x1302, "TLS_AES_256_GCM_SHA384" },
+  { 0x1303, "TLS_CHACHA20_POLY1305_SHA256" },
   { 0xC001, "TLS_ECDH_ECDSA_WITH_NULL_SHA" },
   { 0xC002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" },
   { 0xC003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" },
@@ -1271,7 +1274,18 @@ static struct tls_label tls_extension_la
   { 15, "heartbeat" },
   { 16, "application_layer_protocol_negotiation" },
   { 21, "padding" },
+  { 22, "encrypt_then_mac" },
+  { 23, "extended_master_secret" },
   { 35, "session_ticket" },
+  { 41, "psk" },
+  { 42, "early_data" },
+  { 43, "supported_versions" },
+  { 44, "cookie" },
+  { 45, "psk_kex_modes" },
+  { 47, "certificate_authorities" },
+  { 49, "post_handshake_auth" },
+  { 50, "signature_algorithms_cert" },
+  { 51, "key_share" },
   { 0xFF01, "renegotiate" },
   { 13172, "next_proto_neg" },
 
@@ -1550,6 +1564,12 @@ static void tls_msg_cb(int io_flag, int
     case TLS1_2_VERSION:
       version_str = "TLSv1.2";
       break;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+    case TLS1_3_VERSION:
+      version_str = "TLSv1.3";
+      break;
+#endif
 #endif
 
     default:
@@ -1572,6 +1592,9 @@ static void tls_msg_cb(int io_flag, int
 #if OPENSSL_VERSION_NUMBER >= 0x10001000L
       version == TLS1_1_VERSION ||
       version == TLS1_2_VERSION ||
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
+      version == TLS1_3_VERSION ||
+#endif
 #endif
       version == TLS1_VERSION) {
 
@@ -1736,11 +1759,13 @@ static void tls_msg_cb(int io_flag, int
                 action_str, version_str, (unsigned int) buflen, bytes_str);
               break;
 
+#ifdef SSL3_MT_NEWSESSION_TICKET
             case SSL3_MT_NEWSESSION_TICKET:
               tls_log("[msg] %s %s 'NewSessionTicket' Handshake message "
                 "(%u %s)", action_str, version_str, (unsigned int) buflen,
                 bytes_str);
               break;
+#endif /* SSL3_MT_NEWSESSION_TICKET */
 
             case SSL3_MT_CERTIFICATE:
               tls_log("[msg] %s %s 'Certificate' Handshake message (%u %s)",
@@ -3731,29 +3756,41 @@ static void tls_tlsext_cb(SSL *ssl, int
    *  http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1
    */
   switch (type) {
+#ifdef TLSEXT_TYPE_server_name
     case TLSEXT_TYPE_server_name:
         extension_name = "server name";
         break;
+#endif
 
+#ifdef TLSEXT_TYPE_max_fragment_length
     case TLSEXT_TYPE_max_fragment_length:
         extension_name = "max fragment length";
         break;
+#endif
 
+#ifdef TLSEXT_TYPE_client_certificate_url
     case TLSEXT_TYPE_client_certificate_url:
         extension_name = "client certificate URL";
         break;
+#endif
 
+#ifdef TLSEXT_TYPE_trusted_ca_keys
     case TLSEXT_TYPE_trusted_ca_keys:
         extension_name = "trusted CA keys";
         break;
+#endif
 
+#ifdef TLSEXT_TYPE_truncated_hmac
     case TLSEXT_TYPE_truncated_hmac:
         extension_name = "truncated HMAC";
         break;
+#endif
 
+#ifdef TLSEXT_TYPE_status_request
     case TLSEXT_TYPE_status_request:
         extension_name = "status request";
         break;
+#endif
 
 # ifdef TLSEXT_TYPE_user_mapping
     case TLSEXT_TYPE_user_mapping:
@@ -3815,12 +3852,54 @@ static void tls_tlsext_cb(SSL *ssl, int
         break;
 # endif
 
+# ifdef TLSEXT_TYPE_signed_certificate_timestamp
+    case TLSEXT_TYPE_signed_certificate_timestamp:
+        extension_name = "signed certificate timestamp";
+        break;
+#endif
+
+#ifdef TLSEXT_TYPE_encrypt_then_mac
+    case TLSEXT_TYPE_encrypt_then_mac:
+        extension_name = "encrypt then mac";
+        break;
+#endif
+
+#ifdef TLSEXT_TYPE_extended_master_secret
+   case TLSEXT_TYPE_extended_master_secret:
+        extension_name = "extended master secret";
+        break;
+#endif
+
 # ifdef TLSEXT_TYPE_session_ticket
     case TLSEXT_TYPE_session_ticket:
         extension_name = "session ticket";
         break;
 # endif
 
+#ifdef TLSEXT_TYPE_psk
+    case TLSEXT_TYPE_psk:
+        extension_name = "PSK";
+        break;
+#endif
+
+#ifdef TLSEXT_TYPE_supported_versions
+    case TLSEXT_TYPE_supported_versions:
+        extension_name = "supported versions";
+        break;
+#endif
+
+#ifdef TLSEXT_TYPE_psk_kex_modes
+    case TLSEXT_TYPE_psk_kex_modes:
+        extension_name = "PSK KEX modes";
+        break;
+#endif
+
+#ifdef TLSEXT_TYPE_key_share
+    case TLSEXT_TYPE_key_share:
+        extension_name = "key share";
+        break;
+#endif
+
 # ifdef TLSEXT_TYPE_renegotiate
     case TLSEXT_TYPE_renegotiate:
         extension_name = "renegotiation info";
@@ -5079,7 +5158,7 @@ static int tls_ticket_key_cb(SSL *ssl, u
         SSL_get_cipher_name(ssl), sess_key_len);
     }
 
-    if (RAND_bytes(iv, 16) != 1) {
+    if (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) != 1) {
       pr_trace_msg(trace_channel, 3,
         "unable to initialize session ticket key IV: %s", tls_get_errors());
       return -1;
@@ -5102,7 +5181,7 @@ static int tls_ticket_key_cb(SSL *ssl, u
 # endif /* OpenSSL-1.0.0 and later */
 
     memcpy(key_name, k->key_name, 16);
-    return 0;
+    return 1;
   }
 
   if (mode == 0) {
@@ -6541,7 +6620,12 @@ static int tls_accept(conn_t *conn, unsi
 
 #if !defined(OPENSSL_NO_TLSEXT)
   if (tls_opts & TLS_OPT_ENABLE_DIAGS) {
+    /* note that older OpenSSL verions, e.g. 0.9.8, do not implement this
+     * callback. Newer versions which DO implement it do it as a macro.
+     */
+#ifdef SSL_set_tlsext_debug_callback
     SSL_set_tlsext_debug_callback(ssl, tls_tlsext_cb);
+#endif /* SSL_set_tlsext_debug_callback */
   }
 #endif /* !OPENSSL_NO_TLSEXT */
 
@@ -7107,7 +7191,7 @@ static int tls_connect(conn_t *conn) {
   SSL *ssl = NULL;
   BIO *rbio = NULL, *wbio = NULL;
 
-  if (!ssl_ctx) {
+  if (ssl_ctx == NULL) {
     tls_log("%s", "unable to start session: null SSL_CTX");
     return -1;
   }
@@ -7119,6 +7203,14 @@ static int tls_connect(conn_t *conn) {
     return -2;
   }
 
+  /* Make sure our SSL object uses client methods (Issue#618). */
+  if (SSL_set_ssl_method(ssl, SSLv23_client_method()) != 1) {
+     tls_log("error: unable to set client methods: %s",
+       ERR_error_string(ERR_get_error(), NULL));
+     SSL_free(ssl);
+     return -2;
+  }
+
   /* We deliberately set SSL_VERIFY_NONE here, so that we get to
    * determine how to handle the server cert verification result ourselves.
    */
@@ -7154,9 +7246,7 @@ static int tls_connect(conn_t *conn) {
 
   pr_signals_handle();
   res = SSL_connect(ssl);
-  if (res == -1) {
-    xerrno = errno;
-  }
+  xerrno = errno;
 
   if (blocking) {
     /* Return the connection to blocking mode. */
@@ -7314,9 +7404,6 @@ static int tls_connect(conn_t *conn) {
 
 static void tls_cleanup(int flags) {
 
-  tls_sess_cache_close();
-  tls_ocsp_cache_close();
-
 #if OPENSSL_VERSION_NUMBER > 0x000907000L
   if (tls_crypto_device) {
     ENGINE_cleanup();
@@ -7334,6 +7421,12 @@ static void tls_cleanup(int flags) {
     ssl_ctx = NULL;
   }
 
+  /* Close the session cache only AFTER the SSL context has been
+   * freed/deleted (Issue #795).
+   */
+  tls_sess_cache_close();
+  tls_ocsp_cache_close();
+
   if (tls_tmp_dhs) {
     register unsigned int i;
     DH **dhs;
@@ -8070,10 +8163,13 @@ static int tls_writemore(int wfd) {
 
 static ssize_t tls_read(SSL *ssl, void *buf, size_t len) {
   ssize_t count;
+  int xerrno = 0;
 
   retry:
   pr_signals_handle();
   count = SSL_read(ssl, buf, len);
+  xerrno = errno;
+
   if (count < 0) {
     long err;
     int fd;
@@ -8099,7 +8195,7 @@ static ssize_t tls_read(SSL *ssl, void *
         } else if (err == 0) {
           /* Still missing data after timeout. Simulate an EINTR and return.
            */
-          errno = EINTR;
+          xerrno = EINTR;
 
           /* If err < 0, i.e. some error from the select(), everything is
            * already in place; errno is properly set and this function
@@ -8122,7 +8218,7 @@ static ssize_t tls_read(SSL *ssl, void *
         } else if (err == 0) {
           /* Still missing data after timeout. Simulate an EINTR and return.
            */
-          errno = EINTR;
+          xerrno = EINTR;
 
           /* If err < 0, i.e. some error from the select(), everything is
            * already in place; errno is properly set and this function
@@ -8141,6 +8237,7 @@ static ssize_t tls_read(SSL *ssl, void *
     }
   }
 
+  errno = xerrno;
   return count;
 }
 
@@ -9479,8 +9576,11 @@ static int tls_verify_ocsp(int ok, X509_
 
 static ssize_t tls_write(SSL *ssl, const void *buf, size_t len) {
   ssize_t count;
+  int xerrno = 0;
 
   count = SSL_write(ssl, buf, len);
+  xerrno = errno;
+
   if (count < 0) {
     long err = SSL_get_error(ssl, count);
 
@@ -9491,7 +9591,7 @@ static ssize_t tls_write(SSL *ssl, const
       case SSL_ERROR_WANT_READ:
       case SSL_ERROR_WANT_WRITE:
         /* Simulate an EINTR in case OpenSSL wants to write more. */
-        errno = EINTR;
+        xerrno = EINTR;
         break;
 
       default:
@@ -9529,6 +9629,7 @@ static ssize_t tls_write(SSL *ssl, const
     tls_data_adaptive_bytes_written_ms = now;
   }
 
+  errno = xerrno;
   return count;
 }
 
@@ -10718,7 +10819,7 @@ static int tls_netio_read_cb(pr_netio_st
   ssl = (SSL *) pr_table_get(nstrm->notes, TLS_NETIO_NOTE, NULL);
   if (ssl != NULL) {
     BIO *rbio, *wbio;
-    int bread = 0, bwritten = 0;
+    int bread = 0, bwritten = 0, xerrno = 0;
     ssize_t res = 0;
     unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
 
@@ -10731,6 +10832,7 @@ static int tls_netio_read_cb(pr_netio_st
     wbio_wbytes = BIO_number_written(wbio);
 
     res = tls_read(ssl, buf, buflen);
+    xerrno = errno;
 
     bread = (BIO_number_read(rbio) - rbio_rbytes) +
       (BIO_number_read(wbio) - wbio_rbytes);
@@ -10752,6 +10854,7 @@ static int tls_netio_read_cb(pr_netio_st
       session.total_raw_out += bwritten;
     }
 
+    errno = xerrno;
     return res;
   }
 
@@ -10858,7 +10961,7 @@ static int tls_netio_write_cb(pr_netio_s
   ssl = (SSL *) pr_table_get(nstrm->notes, TLS_NETIO_NOTE, NULL);
   if (ssl != NULL) {
     BIO *rbio, *wbio;
-    int bread = 0, bwritten = 0;
+    int bread = 0, bwritten = 0, xerrno = 0;
     ssize_t res = 0;
     unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
 
@@ -10899,6 +11002,7 @@ static int tls_netio_write_cb(pr_netio_s
 #endif
 
     res = tls_write(ssl, buf, buflen);
+    xerrno = 0;
 
     bread = (BIO_number_read(rbio) - rbio_rbytes) +
       (BIO_number_read(wbio) - wbio_rbytes);
@@ -10920,6 +11024,7 @@ static int tls_netio_write_cb(pr_netio_s
       session.total_raw_out += (bwritten - res);
     }
 
+    errno = xerrno;
     return res;
   }
 
@@ -11980,9 +12085,17 @@ MODRET set_tlsciphersuite(cmd_rec *cmd)
   ciphersuite = cmd->argv[1];
   c = add_config_param(cmd->argv[0], 1, NULL);
 
-  /* Make sure that EXPORT ciphers cannot be used, per Bug#4163. */
-  c->argv[0] = pstrcat(c->pool, "!EXPORT:", ciphersuite, NULL);
+  /* Make sure that EXPORT ciphers cannot be used, per Bug#4163. Note that
+   * this could break system profiles, so handle them specially.
+   */
+  if (strncmp(ciphersuite, "PROFILE=", 8) == 0) {
+     ciphersuite = pstrdup(c->pool, ciphersuite);
+
+  } else {
+     ciphersuite = pstrcat(c->pool, "!EXPORT:", ciphersuite, NULL);
+  }
 
+  c->argv[0] = ciphersuite;
   return PR_HANDLED(cmd);
 }
 
@@ -12583,6 +12696,12 @@ MODRET set_tlsprotocol(cmd_rec *cmd) {
       if (strncasecmp(cmd->argv[i], "SSLv23", 7) == 0) {
         tls_protocol |= TLS_PROTO_SSL_V3;
         tls_protocol |= TLS_PROTO_TLS_V1;
+#ifdef SSL_OP_NO_TLSv1_1
+        tls_protocol |= TLS_PROTO_TLS_V1_1;
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+        tls_protocol |= TLS_PROTO_TLS_V1_2;
+#endif
 
       } else if (strncasecmp(cmd->argv[i], "SSLv3", 6) == 0) {
         tls_protocol |= TLS_PROTO_SSL_V3;
@@ -13650,7 +13769,7 @@ static tls_pkey_t *tls_find_pkey(server_
 
 static tls_pkey_t *tls_get_key_passphrase(server_rec *s, const char *path,
     int flags) {
-  int res, *pass_len;
+  int res, *pass_len = NULL;
   tls_pkey_t *k = NULL;
   const char *key_type = "unsupported";
   char buf[256], **key_data = NULL;
diff -Naurp proftpd-1.3.6.orig/doc/modules/mod_facts.html proftpd-1.3.6/doc/modules/mod_facts.html
--- proftpd-1.3.6.orig/doc/modules/mod_facts.html	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/doc/modules/mod_facts.html	2019-08-29 16:12:45.807547951 -0500
@@ -107,15 +107,15 @@ The currently implemented options are:
   </li>
 
   <p>
-  <li><code>NoAdjustedSymlinks</code><br>
+  <li><code>AdjustedSymlinks</code><br>
     <p>
-    By default, <code>mod_facts</code> tries to automatically adjust any
-    symlink destination paths when the FTP session is chrooted, so that
-    the adjusted symlinks work properly <i>e.g.</i> for FTP clients.
+    Using this option will cause <code>mod_facts</code> to try to automatically
+    adjust any symlink destination paths when the FTP session is chrooted, so
+    that the adjusted symlinks work properly <i>e.g.</i> for FTP clients.
 
     <p>
     <b>Note</b> that this option first appeared in
-    <code>proftpd-1.3.6rc2</code>.
+    <code>proftpd-1.3.6a</code>.
   </li>
 
   <p>
@@ -287,7 +287,7 @@ Then follow the usual steps:
 <p>
 <hr>
 <font size=2><b><i>
-&copy; Copyright 2007-2016 The ProFTPD Project<br>
+&copy; Copyright 2007-2019 The ProFTPD Project<br>
  All Rights Reserved<br>
 </i></b></font>
 <hr>
diff -Naurp proftpd-1.3.6.orig/doc/modules/mod_ls.html proftpd-1.3.6/doc/modules/mod_ls.html
--- proftpd-1.3.6.orig/doc/modules/mod_ls.html	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/doc/modules/mod_ls.html	2019-08-29 16:14:23.116426239 -0500
@@ -111,7 +111,7 @@ and neither directive affects permission
 <p>
 <hr>
 <h3><a name="ListOptions">ListOptions</a></h3>
-<strong>Syntax:</strong> ListOptions <em>options [strict [maxdepth depth] [maxfiles count] [maxdirs count] [LISTOnly] [NLSTOnly] [NoErrorIfAbsent] [NoAdjustedSymlinks] [SortedNLST]</em><br>
+<strong>Syntax:</strong> ListOptions <em>options [strict [maxdepth depth] [maxfiles count] [maxdirs count] [LISTOnly] [NLSTOnly] [NoErrorIfAbsent] [AdjustedSymlinks] [SortedNLST]</em><br>
 <strong>Default:</strong> None<br>
 <strong>Context:</strong> server config, <code>&lt;VirtualHost&gt;</code>, <code>&lt;Global&gt;</code>, <code>&lt;Anonymous&gt;</code>, <code>&lt;Directory&gt;</code>, .ftpaccess<br>
 <strong>Module:</strong> mod_ls<br>
@@ -154,15 +154,15 @@ The currently supported <em>flags</em> a
   </li>
 
   <p>
-  <li><code>NoAdjustedSymlinks</code><br>
+  <li><code>AdjustedSymlinks</code><br>
     <p>
-    By default, <code>mod_ls</code> tries to automatically adjust any
-    symlink destination paths when the FTP session is chrooted, so that
-    the adjusted symlinks work properly <i>e.g.</i> for FTP clients.
+    This <em>flag</em> tells <code>mod_ls</code> to try to automatically
+    adjust any symlink destination paths when the FTP session is chrooted,
+    so that the adjusted symlinks work properly <i>e.g.</i> for FTP clients.
 
     <p>
     <b>Note</b> that this <em>flag</em> first appeared in
-    <code>proftpd-1.3.6rc2</code>.
+    <code>proftpd-1.3.6a</code>.
   </li>
 
   <p>
@@ -233,7 +233,7 @@ The <code>mod_ls</code>module is <b>alwa
 <hr><br>
 
 <font size=2><b><i>
-&copy; Copyright 2000-2016 The ProFTPD Project<br>
+&copy; Copyright 2000-2019 The ProFTPD Project<br>
  All Rights Reserved<br>
 </i></b></font>
 
diff -Naurp proftpd-1.3.6.orig/include/ascii.h proftpd-1.3.6/include/ascii.h
--- proftpd-1.3.6.orig/include/ascii.h	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/include/ascii.h	2019-08-29 14:31:26.881322387 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2013-2015 The ProFTPD Project team
+ * Copyright (c) 2013-2018 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -56,6 +56,9 @@ int pr_ascii_ftp_from_crlf(pool *p, char
 /*
  * Returns the number N on success, and -1 on error, setting errno
  * appropriately.
+ *
+ * NOTE: this function uses malloc(3) deliberately (see Bug #4352), and thus
+ * the caller MUST call free(3) on the **out pointer.
  */
 int pr_ascii_ftp_to_crlf(pool *p, char *in, size_t inlen, char **out,
   size_t *outlen);
diff -Naurp proftpd-1.3.6.orig/include/fsio.h proftpd-1.3.6/include/fsio.h
--- proftpd-1.3.6.orig/include/fsio.h	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/include/fsio.h	2019-08-29 11:39:45.019182158 -0500
@@ -413,6 +413,9 @@ int pr_fs_glob(const char *, int, int (*
 void pr_fs_globfree(glob_t *);
 void pr_resolve_fs_map(void);
 
+/* Close al but the three main fds. */
+void pr_fs_close_extra_fds(void);
+
 /* The main three fds (stdin, stdout, stderr) need to be protected, reserved
  * for use.  This function uses dup(2) to open new fds on the given fd
  * until the new fd is not one of the big three.
diff -Naurp proftpd-1.3.6.orig/Makefile.in proftpd-1.3.6/Makefile.in
--- proftpd-1.3.6.orig/Makefile.in	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/Makefile.in	2019-08-29 14:41:58.648385430 -0500
@@ -127,8 +127,8 @@ install-headers: $(DESTDIR)$(includedir)
 install-pkgconfig: $(DESTDIR)$(pkgconfigdir)
 	@echo 'prefix=$(prefix)' > proftpd.pc
 	@echo 'exec_prefix=$${prefix}' >> proftpd.pc
-	@echo 'libdir=$${prefix}/lib/proftpd' >> proftpd.pc
-	@echo 'includedir=$${prefix}/include/proftpd' >> proftpd.pc
+	@echo 'libdir=${libdir}/proftpd' >> proftpd.pc
+	@echo 'includedir=${includedir}/proftpd' >> proftpd.pc
 	@echo '' >> proftpd.pc
 	@echo 'Name: ProFTPD' >> proftpd.pc
 	@echo 'Description: Professional FTP Daemon' >> proftpd.pc
diff -Naurp proftpd-1.3.6.orig/modules/mod_auth.c proftpd-1.3.6/modules/mod_auth.c
--- proftpd-1.3.6.orig/modules/mod_auth.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_auth.c	2019-08-29 17:25:55.110339444 -0500
@@ -936,9 +936,13 @@ static int get_default_root(pool *p, int
           path[pathlen-1] = '\0';
         }
 
+        PRIVS_USER
         res = is_symlink_path(p, path, pathlen);
+        xerrno = errno;
+        PRIVS_RELINQUISH
+
         if (res < 0) {
-          if (errno == EPERM) {
+          if (xerrno == EPERM) {
             pr_log_pri(PR_LOG_WARNING, "error: DefaultRoot %s is a symlink "
               "(denied by AllowChrootSymlinks config)", path);
           }
@@ -1039,6 +1043,11 @@ static int setup_env(pool *p, cmd_rec *c
   origuser = user;
   c = pr_auth_get_anon_config(p, &user, &ourname, &anonname);
   if (c != NULL) {
+    pr_trace_msg("auth", 13,
+      "found <Anonymous> config: login user = %s, config user = %s, "
+      "anon name = %s", user != NULL ? user : "(null)",
+      ourname != NULL ? ourname : "(null)",
+      anonname != NULL ? anonname : "(null)");
     session.anon_config = c;
   }
 
@@ -2137,18 +2146,10 @@ static int auth_count_scoreboard(cmd_rec
       /* Make sure it matches our current server. */
       if (strcmp(score->sce_server_addr, curr_server_addr) == 0) {
 
-        if ((c != NULL && c->config_type == CONF_ANON &&
-            !strcmp(score->sce_user, user)) || c == NULL) {
-
-          /* This small hack makes sure that cur is incremented properly
-           * when dealing with anonymous logins (the timing of anonymous
-           * login updates to the scoreboard makes this...odd).
-           */
-          if (c != NULL &&
-              c->config_type == CONF_ANON &&
-              cur == 0) {
-              cur = 1;
-          }
+        if ((c != NULL &&
+             c->config_type == CONF_ANON &&
+             strcmp(score->sce_user, user) == 0) ||
+             c == NULL) {
 
           /* Only count authenticated clients, as per the documentation. */
           if (strncmp(score->sce_user, "(none)", 7) == 0) {
@@ -2159,20 +2160,10 @@ static int auth_count_scoreboard(cmd_rec
 
           /* Count up sessions on a per-host basis. */
 
-          if (!strcmp(score->sce_client_addr,
-              pr_netaddr_get_ipstr(session.c->remote_addr))) {
+          if (strcmp(score->sce_client_addr,
+              pr_netaddr_get_ipstr(session.c->remote_addr)) == 0) {
             same_host = TRUE;
 
-            /* This small hack makes sure that hcur is incremented properly
-             * when dealing with anonymous logins (the timing of anonymous
-             * login updates to the scoreboard makes this...odd).
-             */
-            if (c != NULL &&
-                c->config_type == CONF_ANON &&
-                hcur == 0) {
-              hcur = 1;
-            }
-
             hcur++;
           }
 
@@ -2181,7 +2172,7 @@ static int auth_count_scoreboard(cmd_rec
             usersessions++;
 
             /* Count up unique hosts. */
-            if (!same_host) {
+            if (same_host == FALSE) {
               hostsperuser++;
             }
           }
@@ -2277,7 +2268,8 @@ static int auth_count_scoreboard(cmd_rec
       maxstr = maxc->argv[1];
     }
 
-    if (*max && hcur > *max) {
+    if (*max && 
+        hcur > *max) {
       char maxn[20] = {'\0'};
 
       pr_event_generate("mod_auth.max-clients-per-host", session.c);
@@ -2305,7 +2297,8 @@ static int auth_count_scoreboard(cmd_rec
       maxstr = maxc->argv[1];
     }
 
-    if (*max && usersessions > *max) {
+    if (*max && 
+        usersessions > *max) {
       char maxn[20] = {'\0'};
 
       pr_event_generate("mod_auth.max-clients-per-user", user);
@@ -2332,7 +2325,8 @@ static int auth_count_scoreboard(cmd_rec
       maxstr = maxc->argv[1];
     }
 
-    if (*max && cur > *max) {
+    if (*max 
+        && cur > *max) {
       char maxn[20] = {'\0'};
 
       pr_event_generate("mod_auth.max-clients", NULL);
@@ -2632,35 +2626,52 @@ MODRET auth_pre_pass(cmd_rec *cmd) {
 
       allow_empty_passwords = *((int *) c->argv[0]);
       if (allow_empty_passwords == FALSE) {
+        const char *proto;
+        int reject_empty_passwd = FALSE, using_ssh2 = FALSE;
         size_t passwd_len = 0;
  
+        proto = pr_session_get_protocol(0);
+        if (strcmp(proto, "ssh2") == 0) {
+           using_ssh2 = TRUE;
+        }
+
         if (cmd->argc > 1) {
           if (cmd->arg != NULL) {
             passwd_len = strlen(cmd->arg);
           }
         }
 
-        /* Make sure to NOT enforce 'AllowEmptyPasswords off' if e.g.
-         * the AllowDotLogin TLSOption is in effect.
-         */
-        if (cmd->argc == 1 ||
-            passwd_len == 0) {
+        if (passwd_len == 0) {
+           reject_empty_passwd = TRUE;
 
-          if (session.auth_mech == NULL ||
-              strcmp(session.auth_mech, "mod_tls.c") != 0) {
-            pr_log_debug(DEBUG5,
-              "Refusing empty password from user '%s' (AllowEmptyPasswords "
-              "false)", user);
-            pr_log_auth(PR_LOG_NOTICE,
-              "Refusing empty password from user '%s'", user);
-
-            pr_event_generate("mod_auth.empty-password", user);
-            pr_response_add_err(R_501, _("Login incorrect."));
-            return PR_ERROR(cmd);
+           /* Make sure to NOT enforce 'AllowEmptyPasswords off' if e.g.
+            * the AllowDotLogin TLSOption is in effect, or if the protocol is
+            * SSH2 (for mod_sftp uses "fake" PASS commands for the SSH login
+            * protocol).
+            */
+
+           if (session.auth_mech != NULL &&
+               strcmp(session.auth_mech, "mod_tls.c") == 0) {
+              pr_log_debug (DEBUG9, "%s", "'AllowEmptyPasswords off' in effect, "
+               "BUT client authenticated via the AllowDotLogin TLSOption");
+              reject_empty_passwd = FALSE;
           }
 
-          pr_log_debug(DEBUG9, "%s", "'AllowEmptyPasswords off' in effect, "
-            "BUT client authenticated via the AllowDotLogin TLSOption");
+          if (using_ssh2 == TRUE) {
+             reject_empty_passwd = FALSE;
+          }
+        }
+
+        if (reject_empty_passwd == TRUE) {
+           pr_log_debug(DEBUG5,
+             "Refusing empty password from user '%s' (AllowEmptyPasswords "
+             "false)", user);
+           pr_log_auth(PR_LOG_NOTICE,
+             "Refusing empty password from user '%s'", user);
+
+           pr_event_generate("mod_auth.empty-password", user);
+           pr_response_add_err(R_501, _("Login incorrect."));
+           return PR_ERROR(cmd);
         }
       }
     }
diff -Naurp proftpd-1.3.6.orig/modules/mod_auth_unix.c proftpd-1.3.6/modules/mod_auth_unix.c
--- proftpd-1.3.6.orig/modules/mod_auth_unix.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_auth_unix.c	2019-08-29 17:24:05.163486509 -0500
@@ -715,34 +715,40 @@ static char *get_pwd_info(pool *p, const
 MODRET pw_auth(cmd_rec *cmd) {
   int res;
   time_t now;
-  char *cpw;
-  time_t lstchg = -1, max = -1, inact = -1, disable = -1;
+  char *cleartxt_passwd;
+  time_t lstchg = -1, max = -1, inact = -1, expire = -1;
   const char *name;
+  size_t cleartxt_passwdlen;
 
   name = cmd->argv[0];
-  time(&now);
 
-  cpw = get_pwd_info(cmd->tmp_pool, name, &lstchg, NULL, &max, NULL, &inact,
-    &disable);
-  if (cpw == NULL) {
+  cleartxt_passwd = get_pwd_info(cmd->tmp_pool, name, &lstchg, NULL, &max,
+    NULL, &inact, &expire);
+  if (cleartxt_passwd == NULL) {
     return PR_DECLINED(cmd);
   }
 
-  res = pr_auth_check(cmd->tmp_pool, cpw, cmd->argv[0], cmd->argv[1]);
+  res = pr_auth_check(cmd->tmp_pool, cleartxt_passwd, cmd->argv[0],
+    cmd->argv[1]);
+  cleartxt_passwdlen = strlen(cleartxt_passwd);
+  pr_memscrub(cleartxt_passwd, cleartxt_passwdlen);
+
   if (res < PR_AUTH_OK) {
     return PR_ERROR_INT(cmd, res);
   }
 
+  time(&now);
+
   if (lstchg > (time_t) 0 &&
       max > (time_t) 0 &&
       inact > (time_t) 0) {
-    if (now > lstchg + max + inact) {
+    if (now > (lstchg + max + inact)) {
       return PR_ERROR_INT(cmd, PR_AUTH_AGEPWD);
     }
   }
 
-  if (disable > (time_t) 0 &&
-      now > disable) {
+  if (expire > (time_t) 0 &&
+      now > expire) {
     return PR_ERROR_INT(cmd, PR_AUTH_DISABLEDPWD);
   }
 
@@ -751,14 +757,49 @@ MODRET pw_auth(cmd_rec *cmd) {
 }
 
 MODRET pw_authz(cmd_rec *cmd) {
+  time_t now;
+  char *user, *cleartxt_passwd;
+  time_t lstchg = -1, max = -1, inact = -1, expire = -1;
+  size_t cleartxt_passwdlen;
+
+  user = cmd->argv[0];
+
+  cleartxt_passwd = get_pwd_info(cmd->tmp_pool, user, &lstchg, NULL, &max,
+     NULL, &inact, &expire);
+  if (cleartxt_passwd == NULL) {
+     pr_log_auth(LOG_WARNING, "no password information found for user '%.100s'",
+       user);
+     return PR_ERROR_INT(cmd, PR_AUTH_NOPWD);
+  }
+
+  cleartxt_passwdlen = strlen(cleartxt_passwd);
+  pr_memscrub(cleartxt_passwd, cleartxt_passwdlen);
+
+  time(&now);
+
+  if (lstchg > (time_t) 0 &&
+      max > (time_t) 0 &&
+      inact > (time_t) 0) {
+     if (now > (lstchg + max + inact)) {
+      pr_log_auth(LOG_WARNING,
+        "account for user '%.100s' disabled due to inactivity", user);
+      return PR_ERROR_INT(cmd, PR_AUTH_AGEPWD);
+     }
+  }
+
+  if (expire > (time_t) 0 &&
+      now > expire) {
+     pr_log_auth(LOG_WARNING,
+       "account for user '%.100s' disabled due to password expiration", user);
+     return PR_ERROR_INT(cmd, PR_AUTH_DISABLEDPWD);
+  }
+
   /* XXX Any other implementations here? */
 
 #ifdef HAVE_LOGINRESTRICTIONS
   if (!(auth_unix_opts & AUTH_UNIX_OPT_AIX_NO_RLOGIN)) {
     int res, xerrno, code = 0;
-    char *user = NULL, *reason = NULL;
-
-    user = cmd->argv[0];
+    char *reason = NULL;
 
     /* Check for account login restrictions and such using AIX-specific
      * functions.
diff -Naurp proftpd-1.3.6.orig/modules/mod_core.c proftpd-1.3.6/modules/mod_core.c
--- proftpd-1.3.6.orig/modules/mod_core.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_core.c	2019-08-29 16:17:27.645165215 -0500
@@ -2,7 +2,7 @@
  * ProFTPD - FTP server daemon
  * Copyright (c) 1997, 1998 Public Flood Software
  * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
- * Copyright (c) 2001-2017 The ProFTPD Project team
+ * Copyright (c) 2001-2019 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -5065,21 +5065,6 @@ MODRET core_chdir(cmd_rec *cmd, char *nd
 
   orig_dir = ndir;
 
-  pr_fs_clear_cache2(ndir);
-  if (pr_fsio_lstat(ndir, &st) == 0) {
-    if (S_ISLNK(st.st_mode)) {
-      char buf[PR_TUNABLE_PATH_MAX];
-      int len;
-
-      len = dir_readlink(cmd->tmp_pool, ndir, buf, sizeof(buf)-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0) {
-        buf[len] = '\0';
-        ndir = pstrdup(cmd->tmp_pool, buf);
-      }
-    }
-  }
-
   ptr = get_param_ptr(TOPLEVEL_CONF, "ShowSymlinks", FALSE);
   if (ptr != NULL) {
     show_symlinks = *ptr;
@@ -5268,7 +5253,6 @@ MODRET core_chdir(cmd_rec *cmd, char *nd
 MODRET core_rmd(cmd_rec *cmd) {
   int res;
   char *decoded_path, *dir;
-  struct stat st;
 
   CHECK_CMD_MIN_ARGS(cmd, 2);
 
@@ -5313,31 +5297,7 @@ MODRET core_rmd(cmd_rec *cmd) {
       return PR_ERROR(cmd);
   }
 
-  pr_fs_clear_cache2(dir);
-  if (pr_fsio_lstat(dir, &st) == 0) {
-    if (S_ISLNK(st.st_mode)) {
-      char buf[PR_TUNABLE_PATH_MAX];
-      int len;
-
-      memset(buf, '\0', sizeof(buf));
-      len = dir_readlink(cmd->tmp_pool, dir, buf, sizeof(buf)-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0) {
-        buf[len] = '\0';
-        dir = pstrdup(cmd->tmp_pool, buf);
-
-      } else {
-        dir = dir_canonical_path(cmd->tmp_pool, dir);
-      }
-
-    } else {
-      dir = dir_canonical_path(cmd->tmp_pool, dir);
-    }
-
-  } else {
-    dir = dir_canonical_path(cmd->tmp_pool, dir);
-  }
-
+  dir = dir_canonical_path(cmd->tmp_pool, dir);
   if (dir == NULL) {
     int xerrno = EINVAL;
 
@@ -5532,27 +5492,7 @@ MODRET core_mdtm(cmd_rec *cmd) {
   path = decoded_path;
 
   pr_fs_clear_cache2(path);
-  if (pr_fsio_lstat(path, &st) == 0) {
-    if (S_ISLNK(st.st_mode)) {
-      char buf[PR_TUNABLE_PATH_MAX];
-      int len;
-
-      memset(buf, '\0', sizeof(buf));
-      len = dir_readlink(cmd->tmp_pool, path, buf, sizeof(buf)-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0) {
-        buf[len] = '\0';
-        path = pstrdup(cmd->tmp_pool, buf);
-
-      } else {
-        path = dir_realpath(cmd->tmp_pool, decoded_path);
-      }
-
-    } else {
-      path = dir_realpath(cmd->tmp_pool, decoded_path);
-    }
-  }
-
+  path = dir_realpath(cmd->tmp_pool, decoded_path);
   if (!path ||
       !dir_check(cmd->tmp_pool, cmd, cmd->group, path, NULL) ||
       pr_fsio_stat(path, &st) == -1) {
@@ -5642,21 +5582,7 @@ MODRET core_size(cmd_rec *cmd) {
   }
 
   pr_fs_clear_cache2(decoded_path);
-  if (pr_fsio_lstat(decoded_path, &st) == 0) {
-    if (S_ISLNK(st.st_mode)) {
-      char buf[PR_TUNABLE_PATH_MAX];
-      int len;
-
-      memset(buf, '\0', sizeof(buf));
-      len = dir_readlink(cmd->tmp_pool, decoded_path, buf, sizeof(buf)-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0) {
-        buf[len] = '\0';
-        decoded_path = pstrdup(cmd->tmp_pool, buf);
-      }
-    }
-  }
-
+ 
   path = dir_realpath(cmd->tmp_pool, decoded_path);
   if (path != NULL) {
     pr_fs_clear_cache2(path);
diff -Naurp proftpd-1.3.6.orig/modules/mod_ctrls.c proftpd-1.3.6/modules/mod_ctrls.c
--- proftpd-1.3.6.orig/modules/mod_ctrls.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_ctrls.c	2019-08-29 10:06:12.111221941 -0500
@@ -3,7 +3,7 @@
  *          server, as well as several utility functions for other Controls
  *          modules
  *
- * Copyright (c) 2000-2016 TJ Saunders
+ * Copyright (c) 2000-2017 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -81,8 +81,6 @@ static ctrls_acl_t ctrls_sock_acl;
 
 static unsigned char ctrls_engine = TRUE;
 
-#define CTRLS_LISTEN_FL_REMOVE_SOCKET	0x0001
-
 /* Necessary prototypes */
 static int ctrls_setblock(int sockfd);
 static int ctrls_setnonblock(int sockfd);
@@ -437,7 +435,7 @@ static int ctrls_cls_write(void) {
 }
 
 /* Create a listening local socket */
-static int ctrls_listen(const char *sock_file, int flags) {
+static int ctrls_listen(const char *sock_file) {
   int sockfd = -1, len = 0;
   struct sockaddr_un sock;
 #if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \
@@ -497,12 +495,10 @@ static int ctrls_listen(const char *sock
     return -1;
   }
 
-  if (flags & CTRLS_LISTEN_FL_REMOVE_SOCKET) {
-    /* Make sure the path to which we want to bind this socket doesn't already
-     * exist.
-     */
-    (void) unlink(sock_file);
-  }
+  /* Make sure the path to which we want to bind this socket doesn't already
+   * exist.
+   */
+  (void) unlink(sock_file);
 
   /* Fill in the socket structure fields */
   memset(&sock, 0, sizeof(sock));
@@ -1206,7 +1202,7 @@ static void ctrls_postparse_ev(const voi
 
   /* Start listening on the ctrl socket */
   PRIVS_ROOT
-  ctrls_sockfd = ctrls_listen(ctrls_sock_file, CTRLS_LISTEN_FL_REMOVE_SOCKET);
+  ctrls_sockfd = ctrls_listen(ctrls_sock_file);
   PRIVS_RELINQUISH
 
   /* Start a timer for the checking/processing of the ctrl socket.  */
@@ -1298,9 +1294,6 @@ static int ctrls_init(void) {
   memset(&ctrls_sock_acl, '\0', sizeof(ctrls_acl_t));
   ctrls_sock_acl.acl_usrs.allow = ctrls_sock_acl.acl_grps.allow = FALSE;
 
-  /* Start listening on the ctrl socket */
-  ctrls_sockfd = ctrls_listen(ctrls_sock_file, 0);
-
   pr_event_register(&ctrls_module, "core.restart", ctrls_restart_ev, NULL);
   pr_event_register(&ctrls_module, "core.shutdown", ctrls_shutdown_ev, NULL);
   pr_event_register(&ctrls_module, "core.postparse", ctrls_postparse_ev, NULL);
diff -Naurp proftpd-1.3.6.orig/modules/mod_facl.c proftpd-1.3.6/modules/mod_facl.c
--- proftpd-1.3.6.orig/modules/mod_facl.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_facl.c	2019-08-29 11:29:41.684255080 -0500
@@ -1260,16 +1260,32 @@ MODRET set_faclengine(cmd_rec *cmd) {
   return PR_HANDLED(cmd);
 }
 
+/* Event listeners
+ */
+
+static void unmount_facl(void) {
+   pr_fs_t %fs;
+
+   fs = pr_unmount_fs("/", "facl");
+   if (fs != NULL) {
+      destroy_pool(fs->fs_pool);
+      fs->fs_pool = NULL;
+      return;
+   }
+
+   if (errno != ENOENT) {
+      pr_log_debug(DEBUG0, MOD_FACL_VERSION
+        ": error unmounting 'facl' FS: %s", strerror(errno));
+   }
+}
+
 #if defined(PR_SHARED_MODULE) && \
     defined(PR_USE_FACL) && \
     defined(HAVE_POSIX_ACL)
 static void facl_mod_unload_ev(const void *event_data, void *user_data) {
   if (strcmp("mod_facl.c", (const char *) event_data) == 0) {
     pr_event_unregister(&facl_module, NULL, NULL);
-    if (pr_unregister_fs("/") < 0) {
-      pr_log_debug(DEBUG0, MOD_FACL_VERSION
-        ": error unregistering 'facl' FS: %s", strerror(errno));
-    }
+    unmount_facl();
   }
 }
 #endif /* !PR_SHARED_MODULE */
@@ -1303,6 +1319,14 @@ static void facl_postparse_ev(const void
 #endif /* PR_USE_FACL and HAVE_POSIX_ACL */
 }
 
+static void facl_restart_ev(const void *event_data, void *user_data) {
+   if (facl_engine == FALSE) {
+      return;
+   }
+
+   unmount_facl();
+}
+
 /* Initialization routines
  */
 
@@ -1314,7 +1338,7 @@ static int facl_init(void) {
     NULL);
 # endif /* !PR_SHARED_MODULE */
 #endif /* PR_USE_FACL and HAVE_POSIX_ACL */
-  pr_event_register(&facl_module, "core.postparse", facl_postparse_ev, NULL);
+  pr_event_register(&facl_module, "core.restart", facl_restart_ev, NULL);
 
   return 0;
 }
diff -Naurp proftpd-1.3.6.orig/modules/mod_facts.c proftpd-1.3.6/modules/mod_facts.c
--- proftpd-1.3.6.orig/modules/mod_facts.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_facts.c	2019-08-29 17:30:04.340664083 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD: mod_facts -- a module for handling "facts" [RFC3659]
- * Copyright (c) 2007-2017 The ProFTPD Project
+ * Copyright (c) 2007-2019 The ProFTPD Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -51,7 +51,7 @@ static unsigned long facts_mlinfo_opts =
 #define FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK		0x00002
 #define FACTS_MLINFO_FL_NO_CDIR				0x00004
 #define FACTS_MLINFO_FL_APPEND_CRLF			0x00008
-#define FACTS_MLINFO_FL_NO_ADJUSTED_SYMLINKS		0x00010
+#define FACTS_MLINFO_FL_ADJUSTED_SYMLINKS		0x00010
 #define FACTS_MLINFO_FL_NO_NAMES			0x00020
 
 struct mlinfo {
@@ -469,9 +469,7 @@ static int facts_mlinfo_get(struct mlinf
     if (S_ISLNK(info->st.st_mode)) {
       struct stat target_st;
       const char *dst_path;
-      char *link_path;
-      size_t link_pathsz;
-      int len;
+      int len = 0;
 
       /* Now we need to use stat(2) on the path (versus lstat(2)) to get the
        * info for the target, and copy its st_dev and st_ino values to our
@@ -482,28 +480,38 @@ static int facts_mlinfo_get(struct mlinf
        * absolute path.
        */
 
-      link_pathsz = PR_TUNABLE_PATH_MAX;
-      link_path = pcalloc(info->pool, link_pathsz);
-      len = dir_readlink(info->pool, path, link_path, link_pathsz-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0 &&
-          (size_t) len < link_pathsz) {
-        char *best_path;
-
-        best_path = dir_best_path(info->pool, link_path);
-        if (best_path != NULL) {
-          dst_path = best_path;
+      if (flags & FACTS_MLINFO_FL_ADJUSTED_SYMLINKS) {
+         char *link_path;
+         size_t link_pathsz;
+
+         link_pathsz = PR_TUNABLE_PATH_MAX;
+         link_path = pcalloc(info->pool, link_pathsz);
+         len = dir_readlink(info->pool, path, link_path, link_pathsz-1,
+          PR_DIR_READLINK_FL_HANDLE_REL_PATH);
+         if (len > 0 &&
+             (size_t) len < link_pathsz) {
+            char *best_path;
+
+            best_path = dir_best_path(info->pool, link_path);
+            if (best_path != NULL) {
+               dst_path = best_path;
+
+            } else {
+               dst_path = link_path;
+            }
 
         } else {
-          dst_path = link_path;
+          dst_path = path;
         }
 
+        pr_fs_clear_cache2(dst_path);
+        res = pr_fsio_stat(dst_path, &target_st);
+
       } else {
         dst_path = path;
+        res = pr_fsio_stat(dst_path, &target_st);
       }
 
-      pr_fs_clear_cache2(dst_path);
-      res = pr_fsio_stat(dst_path, &target_st);
       if (res < 0) {
         int xerrno = errno;
 
@@ -542,12 +550,12 @@ static int facts_mlinfo_get(struct mlinf
           char target[PR_TUNABLE_PATH_MAX+1];
           int targetlen;
 
-          if (flags & FACTS_MLINFO_FL_NO_ADJUSTED_SYMLINKS) {
-            targetlen = pr_fsio_readlink(path, target, sizeof(target)-1);
-
-          } else {
+          if (flags & FACTS_MLINFO_FL_ADJUSTED_SYMLINKS) {
             sstrncpy(target, dst_path, sizeof(target)-1);
             targetlen = len;
+
+          } else {
+            targetlen = pr_fsio_readlink(path, target, sizeof(target)-1);
           }
 
           if (targetlen < 0) { 
@@ -1875,8 +1883,11 @@ MODRET set_factsoptions(cmd_rec *cmd) {
     if (strcmp(cmd->argv[i], "UseSlink") == 0) {
       opts |= FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK;
 
+    } else if (strcmp(cmd->argv[i], "AdjustedSymlinks") == 0) {
+      opts |= FACTS_MLINFO_FL_ADJUSTED_SYMLINKS;
+
     } else if (strcmp(cmd->argv[i], "NoAdjustedSymlinks") == 0) {
-      opts |= FACTS_MLINFO_FL_NO_ADJUSTED_SYMLINKS;
+      /* Ignore; retained for backward compatibility. */
 
     } else if (strcmp(cmd->argv[i], "NoNames") == 0) {
       opts |= FACTS_MLINFO_FL_NO_NAMES;
diff -Naurp proftpd-1.3.6.orig/modules/mod_ls.c proftpd-1.3.6/modules/mod_ls.c
--- proftpd-1.3.6.orig/modules/mod_ls.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_ls.c	2019-08-29 16:31:18.030273143 -0500
@@ -2,7 +2,7 @@
  * ProFTPD - FTP server daemon
  * Copyright (c) 1997, 1998 Public Flood Software
  * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
- * Copyright (c) 2001-2016 The ProFTPD Project
+ * Copyright (c) 2001-2019 The ProFTPD Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -55,7 +55,7 @@ static int sendline(int flags, char *fmt
 #define LS_FL_NO_ERROR_IF_ABSENT	0x0001
 #define LS_FL_LIST_ONLY			0x0002
 #define LS_FL_NLST_ONLY			0x0004
-#define LS_FL_NO_ADJUSTED_SYMLINKS	0x0008
+#define LS_FL_ADJUSTED_SYMLINKS	0x0008
 #define LS_FL_SORTED_NLST		0x0010
 static unsigned long list_flags = 0UL;
 
@@ -572,13 +572,14 @@ static int listfile(cmd_rec *cmd, pool *
           return 0;
         }
 
-        if (list_flags & LS_FL_NO_ADJUSTED_SYMLINKS) {
-          len = pr_fsio_readlink(name, m, sizeof(m) - 1);
-
-        } else {
+        if (list_flags & LS_FL_ADJUSTED_SYMLINKS) {
           len = dir_readlink(p, name, m, sizeof(m) - 1,
             PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-        }
+
+    
+          } else {
+            len = pr_fsio_readlink(name, m, sizeof(m) - 1);
+          }
 
         if (len < 0) {
           return 0;
@@ -615,12 +616,11 @@ static int listfile(cmd_rec *cmd, pool *
         return 0;
       }
 
-      if (list_flags & LS_FL_NO_ADJUSTED_SYMLINKS) {
-        len = pr_fsio_readlink(name, l, sizeof(l) - 1);
-
-      } else {
+      if (list_flags & LS_FL_ADJUSTED_SYMLINKS) {
         len = dir_readlink(p, name, l, sizeof(l) - 1,
-          PR_DIR_READLINK_FL_HANDLE_REL_PATH);
+           PR_DIR_READLINK_FL_HANDLE_REL_PATH);
+      } else {
+        len = pr_fsio_readlink(name, l, sizeof(l) - 1);
       }
 
       if (len < 0) {
@@ -2441,12 +2441,12 @@ static int nlstdir(cmd_rec *cmd, const c
       }
     }
 
-    if (list_flags & LS_FL_NO_ADJUSTED_SYMLINKS) {
-      i = pr_fsio_readlink(p, file, sizeof(file) - 1);
+    if (list_flags & LS_FL_ADJUSTED_SYMLINKS) {
+       i = dir_readlink(cmd->tmp_pool, p, file, sizeof(file) - 1,
+         PR_DIR_READLINK_FL_HANDLE_REL_PATH);
 
     } else {
-      i = dir_readlink(cmd->tmp_pool, p, file, sizeof(file) - 1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
+      i = pr_fsio_readlink(p, file, sizeof(file) - 1);
     }
 
     if (i > 0) {
@@ -3564,8 +3564,11 @@ MODRET set_listoptions(cmd_rec *cmd) {
       } else if (strcasecmp(cmd->argv[i], "NoErrorIfAbsent") == 0) {
         flags |= LS_FL_NO_ERROR_IF_ABSENT;
 
+      } else if (strcasecmp(cmd->argv[i], "AdjustedSymlinks") == 0) {
+        flags |= LS_FL_ADJUSTED_SYMLINKS;
+
       } else if (strcasecmp(cmd->argv[i], "NoAdjustedSymlinks") == 0) {
-        flags |= LS_FL_NO_ADJUSTED_SYMLINKS;
+        /* Ignore, retained for backwards compatibility. */
 
       } else if (strcasecmp(cmd->argv[i], "SortedNLST") == 0) {
         flags |= LS_FL_SORTED_NLST;
diff -Naurp proftpd-1.3.6.orig/modules/mod_site.c proftpd-1.3.6/modules/mod_site.c
--- proftpd-1.3.6.orig/modules/mod_site.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_site.c	2019-08-29 16:32:55.434129379 -0500
@@ -1,7 +1,7 @@
 /*
  * ProFTPD - FTP server daemon
  * Copyright (c) 1997, 1998 Public Flood Software
- * Copyright (c) 2001-2016 The ProFTPD Project team
+ * Copyright (c) 2001-2019 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -67,7 +67,6 @@ MODRET site_chgrp(cmd_rec *cmd) {
   int res;
   gid_t gid;
   char *path = NULL, *tmp = NULL, *arg = "";
-  struct stat st;
   register unsigned int i = 0;
 #ifdef PR_USE_REGEX
   pr_regex_t *pre;
@@ -129,22 +128,6 @@ MODRET site_chgrp(cmd_rec *cmd) {
   }
 #endif
 
-  pr_fs_clear_cache2(arg);
-  if (pr_fsio_lstat(arg, &st) == 0) {
-    if (S_ISLNK(st.st_mode)) {
-      char link_path[PR_TUNABLE_PATH_MAX];
-      int len;
-
-      memset(link_path, '\0', sizeof(link_path));
-      len = dir_readlink(cmd->tmp_pool, arg, link_path, sizeof(link_path)-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0) {
-        link_path[len] = '\0';
-        arg = pstrdup(cmd->tmp_pool, link_path);
-      }
-    }
-  }
-
   path = dir_realpath(cmd->tmp_pool, arg);
   if (path == NULL) {
     int xerrno = errno;
@@ -267,22 +250,6 @@ MODRET site_chmod(cmd_rec *cmd) {
   }
 #endif
 
-  pr_fs_clear_cache2(arg);
-  if (pr_fsio_lstat(arg, &st) == 0) {
-    if (S_ISLNK(st.st_mode)) {
-      char link_path[PR_TUNABLE_PATH_MAX];
-      int len;
-
-      memset(link_path, '\0', sizeof(link_path));
-      len = dir_readlink(cmd->tmp_pool, arg, link_path, sizeof(link_path)-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0) {
-        link_path[len] = '\0';
-        arg = pstrdup(cmd->tmp_pool, link_path);
-      }
-    }
-  }
-
   dir = dir_realpath(cmd->tmp_pool, arg);
   if (dir == NULL) {
     int xerrno = errno;
diff -Naurp proftpd-1.3.6.orig/modules/mod_xfer.c proftpd-1.3.6/modules/mod_xfer.c
--- proftpd-1.3.6.orig/modules/mod_xfer.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/modules/mod_xfer.c	2019-08-29 17:22:34.742431273 -0500
@@ -2,7 +2,7 @@
  * ProFTPD - FTP server daemon
  * Copyright (c) 1997, 1998 Public Flood Software
  * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
- * Copyright (c) 2001-2016 The ProFTPD Project team
+ * Copyright (c) 2001-2019 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -1217,7 +1217,6 @@ MODRET xfer_pre_stor(cmd_rec *cmd) {
   unsigned char *allow_overwrite = NULL, *allow_restart = NULL;
   config_rec *c;
   int res;
-  struct stat st;
 
   if (cmd->argc < 2) {
     pr_response_add_err(R_500, _("'%s' not understood"),
@@ -1243,22 +1242,6 @@ MODRET xfer_pre_stor(cmd_rec *cmd) {
     return PR_ERROR(cmd);
   }
 
-  pr_fs_clear_cache2(decoded_path);
-  if (pr_fsio_lstat(decoded_path, &st) == 0) {
-    if (S_ISLNK(st.st_mode)) {
-      char buf[PR_TUNABLE_PATH_MAX];
-      int len;
-
-      memset(buf, '\0', sizeof(buf));
-      len = dir_readlink(cmd->tmp_pool, decoded_path, buf, sizeof(buf)-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0) {
-        buf[len] = '\0';
-        decoded_path = pstrdup(cmd->tmp_pool, buf);
-      }
-    }
-  }
-
   path = dir_best_path(cmd->tmp_pool, decoded_path);
 
   if (path == NULL ||
@@ -1737,29 +1720,7 @@ MODRET xfer_stor(cmd_rec *cmd) {
     }
 
   } else if (session.xfer.xfer_type == STOR_APPEND) {
-    const char *appe_path;
-
-    /* Need to handle the case where the path may be a symlink, and we are
-     * chrooted (Bug#4219).
-     */
-    appe_path = session.xfer.path;
-
-    pr_fs_clear_cache2(appe_path);
-    if (pr_fsio_lstat(appe_path, &st) == 0) {
-      if (S_ISLNK(st.st_mode)) {
-        char buf[PR_TUNABLE_PATH_MAX];
-
-        memset(buf, '\0', sizeof(buf));
-        len = dir_readlink(cmd->tmp_pool, appe_path, buf, sizeof(buf)-1,
-          PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-        if (len > 0) {
-          buf[len] = '\0';
-          appe_path = pstrdup(cmd->pool, buf);
-        }
-      }
-    }
-
-    stor_fh = pr_fsio_open(appe_path, O_CREAT|O_WRONLY);
+    stor_fh = pr_fsio_open(session.xfer.path, O_CREAT|O_WRONLY);
     if (stor_fh != NULL) {
       if (pr_fsio_lseek(stor_fh, 0, SEEK_END) == (off_t) -1) {
         pr_log_debug(DEBUG4, "unable to seek to end of '%s' for appending: %s",
@@ -1774,7 +1735,8 @@ MODRET xfer_stor(cmd_rec *cmd) {
       (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
         "error opening '%s': %s", (char *) cmd->argv[0], session.user,
         pr_uid2str(cmd->tmp_pool, session.uid),
-        pr_gid2str(cmd->tmp_pool, session.gid), appe_path, strerror(xerrno));
+        pr_gid2str(cmd->tmp_pool, session.gid), session.xfer.path,
+         strerror(xerrno));
     }
 
   } else {
@@ -2170,7 +2132,6 @@ MODRET xfer_pre_retr(cmd_rec *cmd) {
   mode_t fmode;
   unsigned char *allow_restart = NULL;
   config_rec *c;
-  struct stat st;
 
   xfer_logged_sendfile_decline_msg = FALSE;
 
@@ -2199,30 +2160,7 @@ MODRET xfer_pre_retr(cmd_rec *cmd) {
   }
 
   pr_fs_clear_cache2(decoded_path);
-  if (pr_fsio_lstat(decoded_path, &st) == 0) {
-    if (S_ISLNK(st.st_mode)) {
-      char buf[PR_TUNABLE_PATH_MAX];
-      int len;
-
-      memset(buf, '\0', sizeof(buf));
-      len = dir_readlink(cmd->tmp_pool, decoded_path, buf, sizeof(buf)-1,
-        PR_DIR_READLINK_FL_HANDLE_REL_PATH);
-      if (len > 0) {
-        buf[len] = '\0';
-        dir = pstrdup(cmd->tmp_pool, buf);
-
-      } else {
-        dir = dir_realpath(cmd->tmp_pool, decoded_path);
-      }
-
-    } else {
-      dir = dir_realpath(cmd->tmp_pool, decoded_path);
-    }
-
-  } else {
-    dir = dir_realpath(cmd->tmp_pool, decoded_path);
-  }
-
+  dir = dir_realpath(cmd->tmp_pool, decoded_path);
   if (dir == NULL ||
       !dir_check(cmd->tmp_pool, cmd, cmd->group, dir, NULL)) {
     int xerrno = errno;
diff -Naurp proftpd-1.3.6.orig/src/ascii.c proftpd-1.3.6/src/ascii.c
--- proftpd-1.3.6.orig/src/ascii.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/ascii.c	2019-08-29 14:32:45.475448637 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2015-2016 The ProFTPD Project team
+ * Copyright (c) 2015-2018 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -130,8 +130,17 @@ found_lf:
 
   if (lf_pos == src_len) {
     /* No translation needed. */
-    *out = in;
     *outlen = inlen;
+
+    dst = malloc(inlen);
+    if (dst == NULL) {
+       pr_log_pri(PR_LOG_ALERT, "Out of memory!");
+       exit(1);
+    }
+
+    memcpy(dst, in, inlen);
+    *out = dst;
+
     return 0;
   }
 
@@ -166,11 +175,7 @@ found_lf:
   pr_signals_handle();
 
   *outlen = i;
-  *out = pcalloc(p, *outlen);
-  memcpy(*out, dst, i);
-
-  free(dst);
-  dst = NULL;
+  *out = dst;
 
   return i - j;
 }
diff -Naurp proftpd-1.3.6.orig/src/auth.c proftpd-1.3.6/src/auth.c
--- proftpd-1.3.6.orig/src/auth.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/auth.c	2019-08-29 11:52:28.066410114 -0500
@@ -1784,6 +1784,14 @@ config_rec *pr_auth_get_anon_config(pool
     }
   }
 
+  if (anon_config != NULL) {
+     config_user_name = get_param_ptr(anon_config->subset, "UserName", FALSE);
+     if (config_user_name != NULL &&
+         real_user != NULL) {
+        *real_user = config_user_name;
+     }
+  }
+
   return anon_config;
 }
 
diff -Naurp proftpd-1.3.6.orig/src/data.c proftpd-1.3.6/src/data.c
--- proftpd-1.3.6.orig/src/data.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/data.c	2019-08-29 14:37:06.326626106 -0500
@@ -2,7 +2,7 @@
  * ProFTPD - FTP server daemon
  * Copyright (c) 1997, 1998 Public Flood Software
  * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
- * Copyright (c) 2001-2016 The ProFTPD Project team
+ * Copyright (c) 2001-2018 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -1135,7 +1135,8 @@ int pr_data_xfer(char *cl_buf, size_t cl
         while (len < 0) {
           int xerrno = errno;
  
-          if (xerrno == EAGAIN) {
+          if (xerrno == EAGAIN ||
+              xerrno == EINTR) {
             /* Since our socket is in non-blocking mode, read(2) can return
              * EAGAIN if there is no data yet for us.  Handle this by
              * delaying temporarily, then trying again.
@@ -1197,12 +1198,10 @@ int pr_data_xfer(char *cl_buf, size_t cl
               buflen > 1) {
             size_t outlen = 0;
 
-            /* Use a temporary pool for the CRLF conversion, lest the
-             * session.xfer.p pool grow quite large while downloading a large
-             * file for ASCII conversion (Bug#4277).
-             */
-            tmp_pool = make_sub_pool(session.xfer.p);
-            pr_pool_tag(tmp_pool, "ASCII download");
+            if (tmp_pool == NULL) {
+               tmp_pool = make_sub_pool(session.xfer.p);
+               pr_pool_tag(tmp_pool, "ASCII download");
+            }
 
             res = pr_ascii_ftp_from_crlf(tmp_pool, buf, buflen, &buf, &outlen);
             if (res < 0) {
@@ -1257,7 +1256,8 @@ int pr_data_xfer(char *cl_buf, size_t cl
       while (len < 0) {
         int xerrno = errno;
 
-        if (xerrno == EAGAIN) {
+        if (xerrno == EAGAIN ||
+            xerrno == EINTR) {
           /* Since our socket is in non-blocking mode, read(2) can return
            * EAGAIN if there is no data yet for us.  Handle this by
            * delaying temporarily, then trying again.
@@ -1303,6 +1303,7 @@ int pr_data_xfer(char *cl_buf, size_t cl
       int bwrote = 0;
       int buflen = cl_size;
       unsigned int xferbuflen;
+      char *xfer_buf = NULL;
 
       pr_signals_handle();
 
@@ -1326,12 +1327,10 @@ int pr_data_xfer(char *cl_buf, size_t cl
         char *out = NULL;
         size_t outlen = 0;
 
-        /* Use a temporary pool for the CRLF conversion, lest the
-         * session.xfer.p pool grow quite large while downloading a large
-         * file for ASCII conversion (Bug#4277).
-         */
-        tmp_pool = make_sub_pool(session.xfer.p);
-        pr_pool_tag(tmp_pool, "ASCII upload");
+        if (tmp_pool == NULL) {
+           tmp_pool = make_sub_pool(session.xfer.p);
+           pr_pool_tag(tmp_pool, "ASCII upload");
+        }
 
         /* Scan the internal buffer, looking for LFs with no preceding CRs.
          * Add CRs (and expand the internal buffer) as necessary. xferbuflen
@@ -1345,6 +1344,7 @@ int pr_data_xfer(char *cl_buf, size_t cl
             strerror(errno));
 
         } else {
+          xfer_buf = session.xfer.buf;
           session.xfer.buf = out;
           session.xfer.buflen = xferbuflen = outlen;
         }
@@ -1354,7 +1354,8 @@ int pr_data_xfer(char *cl_buf, size_t cl
       while (bwrote < 0) {
         int xerrno = errno;
 
-        if (xerrno == EAGAIN) {
+        if (xerrno == EAGAIN ||
+            xerrno == EINTR) {
           /* Since our socket is in non-blocking mode, write(2) can return
            * EAGAIN if there is not enough from for our data yet.  Handle
            * this by delaying temporarily, then trying again.
@@ -1368,6 +1369,12 @@ int pr_data_xfer(char *cl_buf, size_t cl
         }
 
         destroy_pool(tmp_pool);
+        if (xfer_buf != NULL) {
+           /* Free up the malloc'ed memory. */
+           free(session.xfer.buf);
+           session.xfer.buf = xfer_buf;
+        }
+
         errno = xerrno;
         return -1;
       }
@@ -1396,6 +1403,14 @@ int pr_data_xfer(char *cl_buf, size_t cl
         cl_buf += buflen;
         total += buflen;
       }
+
+      if (xfer_buf != NULL) {
+         /* Yes, we're using malloc et al here, rather than the memory pools.
+          * See Bug #4352 for details.
+          */
+         free(session.xfer.buf);
+         session.xfer.buf = xfer_buf;
+      }
     }
 
     len = total;
@@ -1499,7 +1514,21 @@ pr_sendfile_t pr_data_sendfile(int retr_
 # endif
 #endif /* !HAVE_SOLARIS_SENDFILE */
 
+    errno = 0;
     len = sendfile(PR_NETIO_FD(session.d->outstrm), retr_fd, offset, count);
+
+    /* If no data could be written (e.g. the file was truncated), we're
+     * done (Bug #4318).
+     */
+    if (len == 0) {
+       if (errno != EINTR) {
+          break ;
+       }
+
+       /* Handle our interrupting signal and continue. */
+       pr_signals_handle();
+    }
+
     if (len != -1 &&
         len < count) {
       /* Under Linux semantics, this occurs when a signal has interrupted
@@ -1605,7 +1634,6 @@ pr_sendfile_t pr_data_sendfile(int retr_
     len = (int) parms.bytes_sent;
 
     if (rc < -1 || rc == 1) {
-
 #endif /* HAVE_AIX_SENDFILE */
 
       /* IMO, BSD's semantics are warped.  Apparently, since we have our
diff -Naurp proftpd-1.3.6.orig/src/fsio.c proftpd-1.3.6/src/fsio.c
--- proftpd-1.3.6.orig/src/fsio.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/fsio.c	2019-08-29 11:47:17.683989523 -0500
@@ -2063,7 +2063,7 @@ int pr_fs_copy_file2(const char *src, co
     const char **names;
 
     names = xattrs->elts;
-    for (i = 0; xattrs->nelts; i++) {
+    for (i = 0; i < xattrs->nelts; i++) {
       ssize_t valsz;
 
       /* First, find out how much memory we need for this attribute's
@@ -6478,6 +6478,61 @@ char *pr_fsio_getline(char *buf, size_t
   return (buf > start ? start : NULL);
 }
 
+#define FSIO_MAX_FD_COUNT 1024
+
+void pr_fs_close_extra_fds(void) {
+   register unsigned int i;
+   long nfiles = 0;
+   struct rlimit rlim;
+
+   /* Close any but the big three open fds.
+    *
+    * First, use getrlimit() to obtain the maximum number of open files
+    * for this process, and then close that number.
+    */
+
+#if defined(RLIMIT_NOFILE) || defined (RLIMIT_OFILE)
+# if defined (RLIMIT_NOFILE)
+  if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
+# elif defined (RLIMIT_OFILE)
+  if (getrlimit(RLIMIT_OFILE, &rlim) < 0) {
+# endif
+     /* Ignore ENOSYS (and EPERM, since some libc's use this as ENOSYS); pick
+      * some arbitrary high number.
+      */
+     nfiles = FSIO_MAX_FD_COUNT;
+
+  } else {
+     nfiles = rlim.rlim_max;
+  }
+
+#else /* no RLIMIT_NOFILE or RLIMIT_OFILE */
+  nfiles = FSIO_MAX_FD_COUNT;
+#endif
+
+  /* Yes, using a long for the nfiles variable is not quite kosher; it should
+   * be an unsigned type, otherwise a large limit (say, RLIMIT_INFINITY)
+   * might overflow the data type. in that case, though, we want to know
+   * about it -- and using a signed type, we will know if the overflowed
+   * value is a negative number. Chances are we DO NOT want to be closing
+   * fds whose value is as high as they can possibly get; that's too many
+   * fds to iterate over. Long story short, using a long int is just fine. Plus
+   * it makes mod_exec work on macOS. Without this tweak,
+   * mod_exec's forked processes never return/exit.)
+   */
+
+  if (nfiles < 0 ||
+      nfiles > FSIO_MAX_FD_COUNT) {
+     nfiles = FSIO_MAX_FD_COUNT;
+  }
+
+  /* Close the "non-standard" file descriptors. */
+  for (i = 3; i < nfiles; i++) {
+     /* This is a potentially long-running loop, so handle signals. */
+     pr_signals_handle();
+     (void) close(i);
+  }
+  }
 /* Be generous in the maximum allowed number of dup fds, in our search for
  * one that is outside the big three.
  *
diff -Naurp proftpd-1.3.6.orig/src/inet.c proftpd-1.3.6/src/inet.c
--- proftpd-1.3.6.orig/src/inet.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/inet.c	2019-08-29 14:08:20.338111464 -0500
@@ -253,7 +253,7 @@ static conn_t *init_conn(pool *p, int fd
 #if defined(SOLARIS2) || defined(FREEBSD2) || defined(FREEBSD3) || \
     defined(FREEBSD4) || defined(FREEBSD5) || defined(FREEBSD6) || \
     defined(FREEBSD7) || defined(FREEBSD8) || defined(FREEBSD9) || \
-    defined(FREEBSD10) || \
+    defined(FREEBSD10) || defined(FREEBSD11) || \
     defined(__OpenBSD__) || defined(__NetBSD__) || \
     defined(DARWIN6) || defined(DARWIN7) || defined(DARWIN8) || \
     defined(DARWIN9) || defined(DARWIN10) || defined(DARWIN11) || \
@@ -278,7 +278,7 @@ static conn_t *init_conn(pool *p, int fd
 #if defined(SOLARIS2) || defined(FREEBSD2) || defined(FREEBSD3) || \
     defined(FREEBSD4) || defined(FREEBSD5) || defined(FREEBSD6) || \
     defined(FREEBSD7) || defined(FREEBSD8) || defined(FREEBSD9) || \
-    defined(FREEBSD10) || \
+    defined(FREEBSD10) || defined(FREEBSD11) || \
     defined(__OpenBSD__) || defined(__NetBSD__) || \
     defined(DARWIN6) || defined(DARWIN7) || defined(DARWIN8) || \
     defined(DARWIN9) || defined(DARWIN10) || defined(DARWIN11) || \
diff -Naurp proftpd-1.3.6.orig/src/json.c proftpd-1.3.6/src/json.c
--- proftpd-1.3.6.orig/src/json.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/json.c	2019-08-29 14:30:20.981085114 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2017 The ProFTPD Project team
+ * Copyright (c) 2017-2018 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -288,7 +288,7 @@ static int get_val_from_node(pool *p, Js
        * as a string, then decode it again.
        */
       if (node->children.head != NULL) {
-        array->array = json_decode(json_encode(node->children.head));
+        array->array = json_decode(json_encode(node));
 
       } else {
         array->array = json_mkarray();
@@ -312,7 +312,7 @@ static int get_val_from_node(pool *p, Js
        * as a string, then decode it again.
        */
       if (node->children.head != NULL) {
-        object->object = json_decode(json_encode(node->children.head));
+        object->object = json_decode(json_encode(node));
 
       } else {
         object->object = json_mkobject();
diff -Naurp proftpd-1.3.6.orig/src/main.c proftpd-1.3.6/src/main.c
--- proftpd-1.3.6.orig/src/main.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/main.c	2019-08-29 11:47:36.911768649 -0500
@@ -2232,6 +2232,7 @@ int main(int argc, char *argv[], char **
 
   memset(&session, 0, sizeof(session));
 
+  pr_fs_close_extra_fds();
   pr_proctitle_init(argc, argv, envp);
 
   /* Seed rand */
diff -Naurp proftpd-1.3.6.orig/src/mkhome.c proftpd-1.3.6/src/mkhome.c
--- proftpd-1.3.6.orig/src/mkhome.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/mkhome.c	2019-08-29 12:07:37.434835785 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2003-2016 The ProFTPD Project team
+ * Copyright (c) 2003-2017 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -312,7 +312,14 @@ int create_home(pool *p, const char *hom
 
   dst_mode = *((mode_t *) c->argv[1]);
 
-  if (!(flags & PR_MKHOME_FL_USE_USER_PRIVS)) {
+  if (flags & PR_MKHOME_FL_USE_USER_PRIVS) {
+     /* Make sure we are the actual end user here (Issue #568). Without this,
+      * we will not be using root privs, true, but we will not be creating
+      * the directory as the logging-in user, we will be creating the directory
+      * using the User/Group identity, which is not expected.
+      */
+    PRIVS_USER
+  } else {
     PRIVS_ROOT
   }
 
diff -Naurp proftpd-1.3.6.orig/src/netaddr.c proftpd-1.3.6/src/netaddr.c
--- proftpd-1.3.6.orig/src/netaddr.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/netaddr.c	2019-08-29 11:49:31.076454587 -0500
@@ -793,7 +793,7 @@ static pr_netaddr_t *get_addr_by_name(po
 static pr_netaddr_t *get_addr_by_device(pool *p, const char *name,
     array_header **addrs) {
 #ifdef HAVE_GETIFADDRS
-  struct ifaddrs *ifaddr;
+  struct ifaddrs *ifaddr = NULL;
   pr_netaddr_t *na = NULL;
   int res, xerrno;
 
@@ -875,6 +875,10 @@ static pr_netaddr_t *get_addr_by_device(
       }
     }
 
+    if (ifaddr != NULL) {
+       freeifaddrs(ifaddr);
+    }
+
     if (found_device) {
       return na;
     }
diff -Naurp proftpd-1.3.6.orig/src/parser.c proftpd-1.3.6/src/parser.c
--- proftpd-1.3.6.orig/src/parser.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/parser.c	2019-08-29 10:08:20.496103028 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2004-2016 The ProFTPD Project team
+ * Copyright (c) 2004-2017 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -101,28 +101,10 @@ static char *get_config_word(pool *p, ch
   if (wordlen > 7) {
     char *ptr = NULL;
 
-    /* Does the given word use the environment syntax?
-     *
-     * In the simple (and most common) case, the entire word is the variable
-     * syntax.  But we also need to check for cases where the environment
-     * variable syntax is embedded within the word string.
-     */
-
-    if (strncmp(word, "%{env:", 6) == 0 &&
-        word[wordlen-1] == '}') {
-      char *env;
-
-      word[wordlen-1] = '\0';
-
-      env = pr_env_get(p, word + 6);
-
-      return env ? pstrdup(p, env) : "";
-    }
-
-    /* This is in a while loop in order to handle a) multiple different
-     * variables, and b) cases where the substituted value is itself a
-     * variable.   (Hopefully no one is so clever as to want to actually
-     * _use_ the latter approach.)
+    /* Does the given word use the environment syntax? We handle this in a
+     * while loop in order to handle a) multiple different variables, b)
+     * cases where the substituted value is itself a variable. Hopefully no
+     * one is clever enough to actually use the latter approach.
      */
     ptr = strstr(word, "%{env:");
     while (ptr != NULL) {
diff -Naurp proftpd-1.3.6.orig/src/str.c proftpd-1.3.6/src/str.c
--- proftpd-1.3.6.orig/src/str.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/str.c	2019-08-29 10:40:58.684986491 -0500
@@ -725,11 +725,11 @@ static int distance_cmp(const void *a, c
   const char *s1, *s2;
   int distance1, distance2;
 
-  cand1 = a;
+  cand1 = *((const struct candidate **) a);
   s1 = cand1->s;
   distance1 = cand1->distance;
 
-  cand2 = b;
+  cand2 = *((const struct candidate **) b);
   s2 = cand2->s;
   distance2 = cand2->distance;
 
diff -Naurp proftpd-1.3.6.orig/src/support.c proftpd-1.3.6/src/support.c
--- proftpd-1.3.6.orig/src/support.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/support.c	2019-08-29 14:08:36.348936142 -0500
@@ -2,7 +2,7 @@
  * ProFTPD - FTP server daemon
  * Copyright (c) 1997, 1998 Public Flood Software
  * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
- * Copyright (c) 2001-2016 The ProFTPD Project team
+ * Copyright (c) 2001-2017 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -440,6 +440,9 @@ int dir_readlink(pool *p, const char *pa
     return -1;
   }
 
+  pr_trace_msg("fsio", 9,
+    "dir_readlink() read link '%.*s' for path '%s'", (int) len, buf, path);
+
   if (len == 0 ||
       (size_t) len == bufsz) {
     /* If we read nothing in, OR if the given buffer was completely
@@ -530,15 +533,16 @@ int dir_readlink(pool *p, const char *pa
      */
 
     ptr = strrchr(path, '/');
-    if (ptr != NULL &&
-        ptr != path) {
-      char *parent_dir;
+    if (ptr != NULL) {
+        if (ptr != path) {
+          char *parent_dir;
 
-      parent_dir = pstrndup(tmp_pool, path, (ptr - path));
-      dst_path = pdircat(tmp_pool, parent_dir, dst_path, NULL);
+         parent_dir = pstrndup(tmp_pool, path, (ptr - path));
+         dst_path = pdircat(tmp_pool, parent_dir, dst_path, NULL);
 
-    } else {
-      dst_path = pdircat(tmp_pool, path, dst_path, NULL);
+      } else {
+        dst_path = pdircat(tmp_pool, "/", dst_path, NULL);
+      }
     }
   }
 
@@ -997,7 +1001,7 @@ void pr_memscrub(void *ptr, size_t ptrle
 void pr_getopt_reset(void) {
 #if defined(FREEBSD4) || defined(FREEBSD5) || defined(FREEBSD6) || \
     defined(FREEBSD7) || defined(FREEBSD8) || defined(FREEBSD9) || \
-    defined(FREEBSD10) || \
+    defined(FREEBSD10) || defined(FREEBSD11) || \
     defined(DARWIN7) || defined(DARWIN8) || defined(DARWIN9) || \
     defined(DARWIN10) || defined(DARWIN11) || defined(DARWIN12) || \
     defined(DARWIN13) || defined(DARWIN14)
diff -Naurp proftpd-1.3.6.orig/src/trace.c proftpd-1.3.6/src/trace.c
--- proftpd-1.3.6.orig/src/trace.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/trace.c	2019-08-29 10:35:19.450435412 -0500
@@ -273,7 +273,13 @@ int pr_trace_parse_levels(char *str, int
   ptr = strchr(str, '-');
   if (ptr == NULL) {
     /* Just a single value. */
+    errno = 0;
     high = (int) strtol(str, &ptr, 10);
+    if (errno == ERANGE) {
+       errno = EINVAL;
+       return -1;
+    }
+
     if (ptr && *ptr) {
       errno = EINVAL;
       return -1;
@@ -302,6 +308,11 @@ int pr_trace_parse_levels(char *str, int
   *ptr = '\0';
 
   low = (int) strtol(str, &tmp, 10);
+  if (errno == ERANGE) {
+     errno = EINVAL;
+     return -1;
+  }
+
   if (tmp && *tmp) {
     *ptr = '-';
     errno = EINVAL;
@@ -316,6 +327,11 @@ int pr_trace_parse_levels(char *str, int
 
   tmp = NULL;
   high = (int) strtol(ptr + 1, &tmp, 10);
+  if (errno == ERANGE) {
+     errno = EINVAL;
+     return -1;
+  }
+
   if (tmp && *tmp) {
     errno = EINVAL;
     return -1;
diff -Naurp proftpd-1.3.6.orig/src/wtmp.c proftpd-1.3.6/src/wtmp.c
--- proftpd-1.3.6.orig/src/wtmp.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/src/wtmp.c	2019-08-29 09:47:30.542276779 -0500
@@ -40,7 +40,7 @@ int log_wtmp(const char *line, const cha
     !(defined(LINUX) || defined(__hpux) || defined (_AIX))
   /* This "auxilliary" utmp doesn't exist under linux. */
 
-#if defined(__sparcv9) && !defined(__NetBSD__) && !defined(__FreeBSD__)
+#if (defined(__sparcv9) || defined(__sun)) && !defined(__NetBSD__) && !defined (__FreeBSD__)
   struct futmpx utx;
   time_t t;
 
@@ -102,7 +102,7 @@ int log_wtmp(const char *line, const cha
 #else /* SVR4 */
     utx.ut_syslen = strlen(utx.ut_host)+1;
 
-#  if defined(__sparcv9) && !defined(__FreeBSD__)
+#  if (defined(__sparcv9) || defined(__sun)) && !defined(__FreeBSD__)
     time(&t);
     utx.ut_tv.tv_sec = (time32_t)t;
 #  else
diff -Naurp proftpd-1.3.6.orig/tests/api/ascii.c proftpd-1.3.6/tests/api/ascii.c
--- proftpd-1.3.6.orig/tests/api/ascii.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/ascii.c	2019-08-29 14:39:45.538868692 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server testsuite
- * Copyright (c) 2015-2016 The ProFTPD Project team
+ * Copyright (c) 2015-2018 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -203,6 +203,7 @@ START_TEST (ascii_ftp_to_crlf_test) {
   char *src, *dst, *expected;
   size_t src_len, dst_len, expected_len;
 
+  mark_point();
   pr_ascii_ftp_reset();
   res = pr_ascii_ftp_to_crlf(NULL, NULL, 0, NULL, NULL);
   fail_unless(res == -1, "Failed to handle null arguments");
@@ -210,6 +211,7 @@ START_TEST (ascii_ftp_to_crlf_test) {
     strerror(errno), errno);
 
   /* Handle empty input buffer. */
+  mark_point();
   pr_ascii_ftp_reset();
   src = "";
   src_len = 0;
@@ -235,8 +237,10 @@ START_TEST (ascii_ftp_to_crlf_test) {
     (unsigned long) dst_len);
   fail_unless(strncmp(dst, expected, dst_len) == 0,
     "Expected output buffer '%s', got '%s'", expected, dst);
+  free(dst);
 
   /* Handle input buffer with CRs, no LFs. */
+  mark_point();
   pr_ascii_ftp_reset();
   src = "he\rl\rlo";
   src_len = 7;
@@ -251,8 +255,10 @@ START_TEST (ascii_ftp_to_crlf_test) {
     (unsigned long) dst_len);
   fail_unless(strncmp(dst, expected, dst_len) == 0,
     "Expected output buffer '%s', got '%s'", expected, dst);
+  free(dst);
 
   /* Handle input buffer with LFs, no CRs. */
+  mark_point();
   pr_ascii_ftp_reset();
   src = "he\nl\nlo";
   src_len = 7;
@@ -267,8 +273,10 @@ START_TEST (ascii_ftp_to_crlf_test) {
     (unsigned long) dst_len);
   fail_unless(strncmp(dst, expected, dst_len) == 0,
     "Expected output buffer '%s', got '%s'", expected, dst);
+  free(dst);
 
   /* Handle input buffer CRLFs. */
+  mark_point();
   pr_ascii_ftp_reset();
   src = "he\r\nl\r\nlo";
   src_len = 9;
@@ -283,8 +291,10 @@ START_TEST (ascii_ftp_to_crlf_test) {
     (unsigned long) dst_len);
   fail_unless(strncmp(dst, expected, dst_len) == 0,
     "Expected output buffer '%s', got '%s'", expected, dst);
+  free(dst);
 
   /* Handle input buffer with leading LF. */
+  mark_point();
   pr_ascii_ftp_reset();
   src = "\nhello";
   src_len = 6;
@@ -299,8 +309,10 @@ START_TEST (ascii_ftp_to_crlf_test) {
     (unsigned long) dst_len);
   fail_unless(strncmp(dst, expected, dst_len) == 0,
     "Expected output buffer '%s', got '%s'", expected, dst);
+  free(dst);
 
   /* Handle input buffer with trailing CR. */
+  mark_point();
   pr_ascii_ftp_reset();
   src = "hel\r";
   src_len = 4;
@@ -315,7 +327,9 @@ START_TEST (ascii_ftp_to_crlf_test) {
     (unsigned long) dst_len);
   fail_unless(strncmp(dst, expected, dst_len) == 0,
     "Expected output buffer '%s', got '%s'", expected, dst);
+  free(dst);
 
+  mark_point();
   src = "\nlo\n";
   src_len = 4;
   dst = NULL;
@@ -329,6 +343,7 @@ START_TEST (ascii_ftp_to_crlf_test) {
     (unsigned long) dst_len);
   fail_unless(strncmp(dst, expected, dst_len) == 0,
     "Expected output buffer '%s', got '%s'", expected, dst);
+  free(dst);
 }
 END_TEST
 
diff -Naurp proftpd-1.3.6.orig/tests/api/data.c proftpd-1.3.6/tests/api/data.c
--- proftpd-1.3.6.orig/tests/api/data.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/data.c	2019-08-29 14:40:58.074061772 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server testsuite
- * Copyright (c) 2015-2016 The ProFTPD Project team
+ * Copyright (c) 2015-2018 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -313,8 +313,9 @@ START_TEST (data_sendfile_test) {
   mark_point();
   res = pr_data_sendfile(fd, &offset, strlen(text));
   if (res < 0) {
-    fail_unless(errno == ENOTSOCK, "Expected ENOTSOCK (%d), got %s (%d)",
-      ENOTSOCK, strerror(errno), errno);
+    fail_unless(errno == ENOTSOCK || errno == EINVAL,
+     "Expected ENOTSOCK (%d) or EINVAL (%d), got %s (%d)", ENOTSOCK, EINVAL,
+     strerror(errno), errno);
   }
 
   (void) close(fd);
@@ -1004,8 +1005,6 @@ START_TEST (data_xfer_write_ascii_test)
   fail_unless(session.xfer.buflen == ascii_buflen,
     "Expected session.xfer.buflen %lu, got %lu", (unsigned long) ascii_buflen,
     (unsigned long) session.xfer.buflen);
-  fail_unless(strncmp(session.xfer.buf, ascii_buf, ascii_buflen) == 0,
-    "Expected '%s', got '%.100s'", ascii_buf, session.xfer.buf);
 
   mark_point();
   cmd = pr_cmd_alloc(p, 1, pstrdup(p, "noop"));
@@ -1023,8 +1022,6 @@ START_TEST (data_xfer_write_ascii_test)
   fail_unless(session.xfer.buflen == ascii_buflen,
     "Expected session.xfer.buflen %lu, got %lu", (unsigned long) ascii_buflen,
     (unsigned long) session.xfer.buflen);
-  fail_unless(strncmp(session.xfer.buf, ascii_buf, ascii_buflen) == 0,
-    "Expected '%s', got '%.100s'", ascii_buf, session.xfer.buf);
 
   session.xfer.p = make_sub_pool(p);
   mark_point();
@@ -1043,8 +1040,6 @@ START_TEST (data_xfer_write_ascii_test)
   fail_unless(session.xfer.buflen == ascii_buflen,
     "Expected session.xfer.buflen %lu, got %lu", (unsigned long) ascii_buflen,
     (unsigned long) session.xfer.buflen);
-  fail_unless(strncmp(session.xfer.buf, ascii_buf, ascii_buflen) == 0,
-    "Expected '%s', got '%.100s'", ascii_buf, session.xfer.buf);
 
   mark_point();
   pr_ascii_ftp_reset();
@@ -1060,8 +1055,6 @@ START_TEST (data_xfer_write_ascii_test)
   fail_unless(session.xfer.buflen == ascii_buflen,
     "Expected session.xfer.buflen %lu, got %lu", (unsigned long) ascii_buflen,
     (unsigned long) session.xfer.buflen);
-  fail_unless(strncmp(session.xfer.buf, ascii_buf, ascii_buflen) == 0,
-    "Expected '%s', got '%.100s'", ascii_buf, session.xfer.buf);
 }
 END_TEST
 
diff -Naurp proftpd-1.3.6.orig/tests/api/fsio.c proftpd-1.3.6/tests/api/fsio.c
--- proftpd-1.3.6.orig/tests/api/fsio.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/fsio.c	2019-08-29 11:48:34.387107629 -0500
@@ -34,6 +34,8 @@ static const char *fsio_test2_path = "/t
 static const char *fsio_unlink_path = "/tmp/prt-fsio-link.dat";
 static const char *fsio_link_path = "/tmp/prt-fsio-symlink.lnk";
 static const char *fsio_testdir_path = "/tmp/prt-fsio-test.d";
+static const char *fsio_copy_src_path = "/tmp/prt-fs-src.dat";
+static const char *fsio_copy_dst_path = "/tmp/prt-fs-dst.dat";
 
 /* Fixtures */
 
@@ -117,8 +119,8 @@ START_TEST (fsio_sys_open_test) {
 
   mark_point();
   flags = O_RDONLY;
-  fh = pr_fsio_open("/etc/resolv.conf", flags);
-  fail_unless(fh != NULL, "Failed to /etc/resolv.conf: %s", strerror(errno));
+  fh = pr_fsio_open("/etc/hosts", flags);
+  fail_unless(fh != NULL, "Failed to /etc/hosts: %s", strerror(errno));
 
   (void) pr_fsio_close(fh);
 }
@@ -142,8 +144,8 @@ START_TEST (fsio_sys_open_canon_test) {
     strerror(errno), errno);
 
   flags = O_RDONLY;
-  fh = pr_fsio_open_canon("/etc/resolv.conf", flags);
-  fail_unless(fh != NULL, "Failed to /etc/resolv.conf: %s", strerror(errno));
+  fh = pr_fsio_open_canon("/etc/hosts", flags);
+  fail_unless(fh != NULL, "Failed to /etc/hosts: %s", strerror(errno));
 
   (void) pr_fsio_close(fh);
 }
@@ -157,7 +159,7 @@ START_TEST (fsio_sys_open_chroot_guard_t
   res = pr_fsio_guard_chroot(TRUE);
   fail_unless(res == FALSE, "Expected FALSE (%d), got %d", FALSE, res);
 
-  path = "/etc/resolv.conf";
+  path = "/etc/hosts";
   flags = O_CREAT|O_RDONLY;
   fh = pr_fsio_open(path, flags);
   if (fh != NULL) {
@@ -201,7 +203,7 @@ START_TEST (fsio_sys_open_chroot_guard_t
 
   (void) pr_fsio_guard_chroot(FALSE);
 
-  path = "/etc/resolv.conf";
+  path = "/etc/hosts";
   flags = O_RDONLY;
   fh = pr_fsio_open(path, flags);
   fail_unless(fh != NULL, "Failed to open '%s': %s", path, strerror(errno));
@@ -218,8 +220,8 @@ START_TEST (fsio_sys_close_test) {
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s %d", EINVAL,
     strerror(errno), errno);
 
-  fh = pr_fsio_open("/etc/resolv.conf", O_RDONLY);
-  fail_unless(fh != NULL, "Failed to open /etc/resolv.conf: %s",
+  fh = pr_fsio_open("/etc/hosts", O_RDONLY);
+  fail_unless(fh != NULL, "Failed to open /etc/hosts: %s",
     strerror(errno));
 
   res = pr_fsio_close(fh);
@@ -283,8 +285,8 @@ START_TEST (fsio_sys_unlink_chroot_guard
   res = pr_fsio_guard_chroot(TRUE);
   fail_unless(res == FALSE, "Expected FALSE (%d), got %d", FALSE, res);
 
-  res = pr_fsio_unlink("/etc/resolv.conf");
-  fail_unless(res < 0, "Deleted /etc/resolv.conf unexpectedly");
+  res = pr_fsio_unlink("/etc/hosts");
+  fail_unless(res < 0, "Deleted /etc/hosts unexpectedly");
   fail_unless(errno == EACCES, "Expected EACCES (%d), got %s %d", EACCES,
     strerror(errno), errno);
 
@@ -422,12 +424,12 @@ START_TEST (fsio_sys_fstat_test) {
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
 
-  fh = pr_fsio_open("/etc/resolv.conf", O_RDONLY);
-  fail_unless(fh != NULL, "Failed to open /etc/resolv.conf: %s",
+  fh = pr_fsio_open("/etc/hosts", O_RDONLY);
+  fail_unless(fh != NULL, "Failed to open /etc/hosts: %s",
     strerror(errno));
 
   res = pr_fsio_fstat(fh, &st);
-  fail_unless(res == 0, "Failed to fstat /etc/resolv.conf: %s",
+  fail_unless(res == 0, "Failed to fstat /etc/hosts: %s",
     strerror(errno));
   (void) pr_fsio_close(fh);
 }
@@ -444,8 +446,8 @@ START_TEST (fsio_sys_read_test) {
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
 
-  fh = pr_fsio_open("/etc/resolv.conf", O_RDONLY);
-  fail_unless(fh != NULL, "Failed to open /etc/resolv.conf: %s",
+  fh = pr_fsio_open("/etc/hosts", O_RDONLY);
+  fail_unless(fh != NULL, "Failed to open /etc/hosts: %s",
     strerror(errno));
 
   res = pr_fsio_read(fh, NULL, 0);
@@ -513,8 +515,8 @@ START_TEST (fsio_sys_lseek_test) {
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
 
-  fh = pr_fsio_open("/etc/resolv.conf", O_RDONLY);
-  fail_unless(fh != NULL, "Failed to open /etc/resolv.conf: %s",
+  fh = pr_fsio_open("/etc/hosts", O_RDONLY);
+  fail_unless(fh != NULL, "Failed to open /etc/hosts: %s",
     strerror(errno));
 
   res = pr_fsio_lseek(fh, 0, 0);
@@ -1010,8 +1012,12 @@ START_TEST (fsio_sys_access_dir_test) {
     strerror(errno));
 
   if (getenv("TRAVIS") == NULL) {
-    uid_t other_uid = 1000;
-    gid_t other_gid = 1000;
+    uid_t other_uid;
+    gid_t other_gid;
+
+    /* Deliberately use IDs other than the current ones. */
+    other_uid = uid - 1;
+    other_gid = gid - 1;
 
     /* Next, check that others can access the directory. */
     pr_fs_clear_cache2(fsio_testdir_path);
@@ -2449,7 +2455,7 @@ START_TEST (fsio_sys_chdir_test) {
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
 
-  res = pr_fsio_chdir("/etc/resolv.conf", FALSE);
+  res = pr_fsio_chdir("/etc/hosts", FALSE);
   fail_unless(res < 0, "Failed to handle file argument");
   fail_unless(errno == EINVAL || errno == ENOTDIR,
     "Expected EINVAL (%d) or ENOTDIR (%d), got %s (%d)", EINVAL, ENOTDIR,
@@ -2511,7 +2517,7 @@ START_TEST (fsio_sys_opendir_test) {
     strerror(errno), errno); 
 
   mark_point();
-  path = "/etc/resolv.conf";
+  path = "/etc/hosts";
   res = pr_fsio_opendir(path);
   fail_unless(res == NULL, "Failed to handle file argument");
   fail_unless(errno == ENOTDIR, "Expected ENOTDIR (%d), got %s (%d)", ENOTDIR,
@@ -2541,7 +2547,7 @@ START_TEST (fsio_sys_readdir_test) {
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
 
-  dent = pr_fsio_readdir("/etc/resolv.conf");
+  dent = pr_fsio_readdir("/etc/hosts");
   fail_unless(dent == NULL, "Failed to handle file argument");
   fail_unless(errno == ENOTDIR, "Expected ENOTDIR (%d), got %s (%d)", ENOTDIR,
     strerror(errno), errno);
@@ -3297,7 +3303,7 @@ END_TEST
 
 START_TEST (fs_copy_file_test) {
   int res;
-  char *src_path, *dst_path, *text;
+  char *src_path = NULL, *dst_path = NULL, *text;
   pr_fh_t *fh;
 
   res = pr_fs_copy_file(NULL, NULL);
@@ -3305,15 +3311,15 @@ START_TEST (fs_copy_file_test) {
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
 
-  src_path = "/tmp/prt-fs-src.dat";
+  src_path = fsio_copy_src_path;
   res = pr_fs_copy_file(src_path, NULL);
   fail_unless(res < 0, "Failed to handle null destination path");
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
 
-  dst_path = "/tmp/prt-fs-dst.dat";
+  dst_path = fsio_copy_dst_path;
   res = pr_fs_copy_file(src_path, dst_path);
-  fail_unless(res < 0, "Failed to handle null destination path");
+  fail_unless(res < 0, "Failed to handle nonexistent source path");
   fail_unless(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT,
     strerror(errno), errno);
 
@@ -3322,6 +3328,7 @@ START_TEST (fs_copy_file_test) {
   fail_unless(errno == EISDIR, "Expected EISDIR (%d), got %s (%d)", EISDIR,
     strerror(errno), errno);
 
+  (void) unlink(src_path);
   fh = pr_fsio_open(src_path, O_CREAT|O_EXCL|O_WRONLY);
   fail_unless(fh != NULL, "Failed to open '%s': %s", src_path, strerror(errno));
 
@@ -3347,6 +3354,8 @@ START_TEST (fs_copy_file_test) {
   res = pr_fs_copy_file(src_path, src_path);
   fail_unless(res == 0, "Failed to copy file to itself: %s", strerror(errno));
 
+  (void) unlink(dst_path);
+
   mark_point();
   res = pr_fs_copy_file(src_path, dst_path);
   fail_unless(res == 0, "Failed to copy file: %s", strerror(errno));
@@ -3366,10 +3375,13 @@ START_TEST (fs_copy_file2_test) {
   char *src_path, *dst_path, *text;
   pr_fh_t *fh;
 
-  src_path = "/tmp/prt-fs-src.dat";
-  dst_path = "/tmp/prt-fs-dst.dat";
+  src_path = fsio_copy_src_path;
+  dst_path = fsio_copy_dst_path;
   flags = PR_FSIO_COPY_FILE_FL_NO_DELETE_ON_FAILURE;
 
+  (void) unlink(src_path);
+  (void) unlink(dst_path);
+
   fh = pr_fsio_open(src_path, O_CREAT|O_EXCL|O_WRONLY);
   fail_unless(fh != NULL, "Failed to open '%s': %s", src_path, strerror(errno));
 
@@ -3888,6 +3900,12 @@ START_TEST (fs_virtual_path_test) {
 }
 END_TEST
 
+START_TEST (fs_close_extra_fds_test) {
+   mark_point();
+   pr_fs_close_extra_fds();
+}
+END_TEST
+
 START_TEST (fs_get_usable_fd_test) {
   int fd, res;
 
@@ -4630,6 +4648,7 @@ Suite *tests_get_fsio_suite(void) {
   tcase_add_test(testcase, fs_split_path_test);
   tcase_add_test(testcase, fs_join_path_test);
   tcase_add_test(testcase, fs_virtual_path_test);
+  tcase_add_test(testcase, fs_close_extra_fds_test);
   tcase_add_test(testcase, fs_get_usable_fd_test);
   tcase_add_test(testcase, fs_get_usable_fd2_test);
   tcase_add_test(testcase, fs_getsize_test);
diff -Naurp proftpd-1.3.6.orig/tests/api/inet.c proftpd-1.3.6/tests/api/inet.c
--- proftpd-1.3.6.orig/tests/api/inet.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/inet.c	2019-08-29 09:43:52.096341256 -0500
@@ -508,7 +508,7 @@ START_TEST (inet_connect_ipv4_test) {
   conn = pr_inet_create_conn(p, sockfd, NULL, port, FALSE);
   fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno));
 
-  res = pr_inet_connect(p, conn, NULL, 80);
+  res = pr_inet_connect(p, conn, NULL, 180);
   fail_unless(res < 0, "Failed to handle null address");
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
@@ -517,8 +517,8 @@ START_TEST (inet_connect_ipv4_test) {
   fail_unless(addr != NULL, "Failed to resolve '127.0.0.1': %s",
     strerror(errno));
 
-  res = pr_inet_connect(p, conn, addr, 80);
-  fail_unless(res < 0, "Connected to 127.0.0.1#80 unexpectedly");
+  res = pr_inet_connect(p, conn, addr, 180);
+  fail_unless(res < 0, "Connected to 127.0.0.1#180 unexpectedly");
   fail_unless(errno == ECONNREFUSED, "Expected ECONNREFUSED (%d), got %s (%d)",
     ECONNREFUSED, strerror(errno), errno);
 
@@ -573,8 +573,8 @@ START_TEST (inet_connect_ipv6_test) {
   fail_unless(addr != NULL, "Failed to resolve '::1': %s",
     strerror(errno));
 
-  res = pr_inet_connect(p, conn, addr, 80);
-  fail_unless(res < 0, "Connected to ::1#80 unexpectedly");
+  res = pr_inet_connect(p, conn, addr, 180);
+  fail_unless(res < 0, "Connected to ::1#180 unexpectedly");
   fail_unless(errno == ECONNREFUSED || errno == ENETUNREACH || errno == EADDRNOTAVAIL,
     "Expected ECONNREFUSED (%d), ENETUNREACH (%d), or EADDRNOTAVAIL (%d), got %s (%d)",
     ECONNREFUSED, ENETUNREACH, EADDRNOTAVAIL, strerror(errno), errno);
@@ -637,7 +637,7 @@ START_TEST (inet_connect_nowait_test) {
   conn = pr_inet_create_conn(p, sockfd, NULL, port, FALSE);
   fail_unless(conn != NULL, "Failed to create conn: %s", strerror(errno));
 
-  res = pr_inet_connect_nowait(p, conn, NULL, 80);
+  res = pr_inet_connect_nowait(p, conn, NULL, 180);
   fail_unless(res < 0, "Failed to handle null address");
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
@@ -646,8 +646,8 @@ START_TEST (inet_connect_nowait_test) {
   fail_unless(addr != NULL, "Failed to resolve '127.0.0.1': %s",
     strerror(errno));
 
-  res = pr_inet_connect_nowait(p, conn, addr, 80);
-  fail_unless(res != -1, "Connected to 127.0.0.1#80 unexpectedly");
+  res = pr_inet_connect_nowait(p, conn, addr, 180);
+  fail_unless(res != -1, "Connected to 127.0.0.1#180 unexpectedly");
 
   /* Try connecting to Google's DNS server. */
 
@@ -657,7 +657,8 @@ START_TEST (inet_connect_nowait_test) {
 
   res = pr_inet_connect_nowait(p, conn, addr, 53);
   if (res < 0 &&
-      errno != ECONNREFUSED) {
+      errno != ECONNREFUSED &&
+      errno != EBADF) {
     fail_unless(res != -1, "Failed to connect to 8.8.8.8#53: %s",
       strerror(errno));
   }
diff -Naurp proftpd-1.3.6.orig/tests/api/misc.c proftpd-1.3.6/tests/api/misc.c
--- proftpd-1.3.6.orig/tests/api/misc.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/misc.c	2019-08-29 10:35:35.761213701 -0500
@@ -702,7 +702,7 @@ START_TEST (check_shutmsg_test) {
 
   (void) unlink(path);
   res = write_shutmsg(path,
-    "2340 1 1 0 0 0 0000 0000\nGoodbye, cruel world!\n");
+    "2037 1 1 0 0 0 0000 0000\nGoodbye, cruel world!\n");
   fail_unless(res == 0, "Failed to write '%s': %s", path, strerror(errno));
 
   mark_point();
diff -Naurp proftpd-1.3.6.orig/tests/api/netaddr.c proftpd-1.3.6/tests/api/netaddr.c
--- proftpd-1.3.6.orig/tests/api/netaddr.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/netaddr.c	2019-08-29 09:56:54.994725762 -0500
@@ -146,6 +146,7 @@ START_TEST (netaddr_get_addr_test) {
   fail_unless(res->na_family == AF_INET, "Expected family %d, got %d",
     AF_INET, res->na_family);
 
+#if defined(PR_USE_NETWORK_TESTS)
   /* Google: the Dial Tone of the Internet. */
   name = "www.google.com";
 
@@ -161,6 +162,7 @@ START_TEST (netaddr_get_addr_test) {
     strerror(errno));
   fail_unless(res->na_family == AF_INET, "Expected family %d, got %d",
     AF_INET, res->na_family);
+#endif
 
   name = "127.0.0.1";
 
@@ -903,6 +905,7 @@ START_TEST (netaddr_get_dnsstr_list_test
 
   pr_netaddr_clear_cache();
 
+#if defined(PR_USE_NETWORK_TESTS)
   addr = pr_netaddr_get_addr(p, "www.google.com", &addrs);
   fail_unless(addr != NULL, "Failed to resolve 'www.google.com': %s",
     strerror(errno));
@@ -921,6 +924,7 @@ START_TEST (netaddr_get_dnsstr_list_test
   /* Ideally we would check that res->nelts > 0, BUT this turns out to
    * a fragile test condition, dependent on DNS vagaries.
    */
+#endif
 
   pr_netaddr_set_reverse_dns(reverse_dns);
 }
@@ -1082,6 +1086,7 @@ START_TEST (netaddr_is_loopback_test) {
   fail_unless(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
     strerror(errno), errno);
 
+#if defined(PR_USE_NETWORK_TESTS)
   name = "www.google.com";
   addr = pr_netaddr_get_addr(p, name, NULL);
   fail_unless(addr != NULL, "Failed to resolve '%s': %s", name,
@@ -1089,6 +1094,7 @@ START_TEST (netaddr_is_loopback_test) {
 
   res = pr_netaddr_is_loopback(addr);
   fail_unless(res == FALSE, "Expected FALSE, got %d", res);
+#endif
 
   name = "127.0.0.1";
   addr = pr_netaddr_get_addr(p, name, NULL);
diff -Naurp proftpd-1.3.6.orig/tests/api/parser.c proftpd-1.3.6/tests/api/parser.c
--- proftpd-1.3.6.orig/tests/api/parser.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/parser.c	2019-08-29 10:15:09.005105650 -0500
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server testsuite
- * Copyright (c) 2014-2016 The ProFTPD Project team
+ * Copyright (c) 2014-2017 The ProFTPD Project team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -319,6 +319,23 @@ START_TEST (parser_parse_line_test) {
   lineno = pr_parser_get_lineno();
   fail_unless(lineno != 3, "Expected lineno 3, got %u", lineno);
 
+  /* This time, with a single word containing multiple environment variables.
+   * Issue #507.
+   */
+  pr_env_set(p, "FOO_TEST", "Foo");
+  pr_env_set(p, "BAR_TEST", "baR");
+  text = pstrdup(p, "BarBaz %{env;FOO_TEST}@%{env:BAR_TEST}");
+  cmd = pr_parser_parse_line(p, text, 0);
+  fail_unless(cmd != NULL, "Failed to parse text '%s': %s", text,
+    strerror(errno));
+  fail_unless(cmd->argc == 2, "Expected 2, got %d", cmd->argc);
+  fail_unless(strcmp(cmd->argv[0], "BarBaz") == 0,
+    "Expected 'BarBaz', got '%s'", (char *) cmd->argv[0]);
+  fail_unless(strcmp(cmd->arg, "Foo@baR") == 0,
+    "Expected 'Foo@baR', got '%s'", cmd->arg);
+  lineno = pr_parser_get_lineno();
+  fail_unless(lineno != 3, "Expected lineno 3, got %u", lineno);
+
   text = pstrdup(p, "<FooBar baz>");
   cmd = pr_parser_parse_line(p, text, 0);
   fail_unless(cmd != NULL, "Failed to parse text '%s': %s", text,
diff -Naurp proftpd-1.3.6.orig/tests/api/pool.c proftpd-1.3.6/tests/api/pool.c
--- proftpd-1.3.6.orig/tests/api/pool.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/pool.c	2019-08-29 09:45:05.414299296 -0500
@@ -52,12 +52,17 @@ START_TEST (pool_destroy_pool_test) {
   p = make_sub_pool(permanent_pool);
   destroy_pool(p);
 
-#if !defined(PR_USE_DEVEL)
   /* What happens if we destroy an already-destroyed pool?  Answer: IFF
    * --enable-devel was used, THEN destroying an already-destroyed pool
    * will result in an exit(2) call from within pool.c, via the
    * chk_on_blk_list() function.  How impolite.
+   *
+   * And if --enable-devel was NOT used, on some systems, this test tickles
+   * other libc/malloc/free behaviors, which are unsettling.
+   *
+   * Leave it commented out for now.
    */
+#if 0
   mark_point();
   destroy_pool(p);
 #endif
diff -Naurp proftpd-1.3.6.orig/tests/api/str.c proftpd-1.3.6/tests/api/str.c
--- proftpd-1.3.6.orig/tests/api/str.c	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/api/str.c	2019-08-29 10:40:21.629458697 -0500
@@ -1469,25 +1469,23 @@ START_TEST (similars_test) {
   mark_point();
   similars = (const char **) res->elts;
 
-  /* Note: We see different results here due to (I think) different
-   * qsort(3) implementations.
+  /*
+   * Note: expected distances are as follows:
+   *
+   * Candidate       Case-Sensitive       Case-Insensitive
+   * fools                 0                       0
+   * odd                   5                       5
+   * bar                   5                       5
+   * FOO                   5                       0
    */
 
-  expected = "FOO";
-  if (strcmp(similars[0], expected) != 0) {
-    expected = "fools";
-  }
+  expected = "fools";
 
   fail_unless(strcmp(similars[0], expected) == 0,
     "Expected similar '%s', got '%s'", expected, similars[0]);
 
-  expected = "fools";
-  if (strcmp(similars[1], expected) != 0) {
-    expected = "FOO";
-  }
-
-  fail_unless(strcmp(similars[1], expected) == 0,
-    "Expected similar '%s', got '%s'", expected, similars[1]);
+  fail_unless(strcmp(similars[1], expected) != 0,
+    "Unexpectedly got similar '%s'", similars[1]);
 
   mark_point();
   res = pr_str_get_similars(p, s, candidates, 0, PR_STR_FL_IGNORE_CASE);
@@ -1499,18 +1497,22 @@ START_TEST (similars_test) {
   mark_point();
   similars = (const char **) res->elts;
 
+  /*
+   * similars[0] and similars[1] should be "FOO" and "fools", but
+   * not necessarily in that order.
+   */
   expected = "FOO";
   if (strcmp(similars[0], expected) != 0) {
-    expected = "fools";
+    expected = similars[0];
+    similars[0] = similars[1];
+    similars[1] = expected;
+    expected = "FOO";
   }
 
   fail_unless(strcmp(similars[0], expected) == 0,
     "Expected similar '%s', got '%s'", expected, similars[0]);
 
   expected = "fools";
-  if (strcmp(similars[1], expected) != 0) {
-    expected = "FOO";
-  }
 
   fail_unless(strcmp(similars[1], expected) == 0,
     "Expected similar '%s', got '%s'", expected, similars[1]);
diff -Naurp proftpd-1.3.6.orig/tests/t/lib/ProFTPD/Tests/Config/AuthAliasOnly.pm proftpd-1.3.6/tests/t/lib/ProFTPD/Tests/Config/AuthAliasOnly.pm
--- proftpd-1.3.6.orig/tests/t/lib/ProFTPD/Tests/Config/AuthAliasOnly.pm	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/t/lib/ProFTPD/Tests/Config/AuthAliasOnly.pm	2019-08-29 12:05:33.919275666 -0500
@@ -40,6 +40,10 @@ my $TESTS = {
     test_class => [qw(bug forking rootprivs)],
   },
 
+  authaliasonly_on_anon_bug4314 => {
+    order => ++$order,
+    test_class => [qw(bug forking rootprivs)],
+  },
 };
 
 sub new {
@@ -647,4 +651,111 @@ sub authaliasonly_on_anon_bug4255 {
   test_cleanup($setup->{log_file}, $ex);
 }
 
+sub authaliasonly_on_anon_bug4314 {
+   my $self = shift;
+   my $tmpdir = $self->{tmpdir};
+   my $setup = test_setup($tmpdir, 'config');
+
+   my ($config_user, $config_group) = config_get_identity();
+
+   my $config = {
+     PidFile => $setup->{pid_file},
+     ScoreboardFile => $setup->{scoreboard_file},
+     SystemLog => $setup->{log_file},
+     TraceLog => $setup->{log_file},
+     Trace => 'auth:20'
+
+     User => $config_user,
+     Group => $config_group,
+
+     AuthUserFile => $setup->{auth_user_file},
+     AuthGroupFile => $setup->{auth_group_file},
+     AuthOrder => 'mod_auth_file.c',
+
+     Anonymous => {
+       $setup->{home_dir} => {
+         User => $setup->{user},
+         Group => $setup->{group},
+         RequireValidShell => 'off',
+         UserAlias => "anonymous $setup->{user}",
+         AuthAliasOnly => 'on',
+         AnonRequirePassword => 'off',
+      };
+   },
+
+   IfModules => {
+      'mod_delay.c' => {
+         DelayEngine => 'off',
+      },
+   },
+};
+
+my $port;
+($port, $config_user, $config_group) = config_write($setup->{config_file},
+   $config);
+
+# Open pipes, for use between the parent and child processes. Specifically,
+# the child will indicate when it's done with it's test by writing a message
+# to the parent.
+my ($rfh, $wfh);
+unless (pipe($rfh, $wfh)) {
+   die("Can't open pipe: $!");
+}
+
+my $ex;
+
+# Fork child
+$self->handle_sigchld();
+defined(my $pid = fork()) or die("Can't fork: $!");
+if ($pid) {
+   eval {
+      sleep(1);
+
+      # First, try logging in as user 'anonymous', i.e. the alias.
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
+      my ($resp_code, $resp_msg) = $client->user("anonymous");
+
+      my $expected = 331;
+      $self->assert($expected == $resp_code,
+         "Expected response code $expected, got $resp_code");
+
+      $expected = 'Anonymous login ok, send your complete email address as your password';
+      $self->assert($expected eq $resp_msg,
+         "Expected response message '$expected', got '$resp_msg'");
+
+      ($resp_code, $resp_msg) = $client->pass('ftp@nospam.org');
+
+      $expected = 230;
+      $self->assert($expected == $resp_code,
+         "Expected response code $expected, got $resp_code");
+
+      $expected = "Anonymous access granted, restrictions apply";
+      $self->assert($expected eq $resp_msg,
+         "Expected response message '$expected', got '$resp_msg'");
+
+      $client->quit();
+   };
+   if ($@) {
+      $ex = $@;
+   }
+
+   $wfh->print("done\n");
+   $wfh->flush();
+
+} else {
+   eval { sever_wait($setup->{config_file}, $rfh) };
+   if ($@) {
+      warn($@);
+      exit 1;
+   }
+
+   exit 0;
+}
+
+# Stop server
+server_stop($setup->{pid_file});
+$self->assert_child_ok($pid);
+
+test_cleanup($setup->{log_file}, $ex);
+}
 1;
diff -Naurp proftpd-1.3.6.orig/tests/t/lib/ProFTPD/Tests/Config/MaxClientsPerHost.pm proftpd-1.3.6/tests/t/lib/ProFTPD/Tests/Config/MaxClientsPerHost.pm
--- proftpd-1.3.6.orig/tests/t/lib/ProFTPD/Tests/Config/MaxClientsPerHost.pm	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/t/lib/ProFTPD/Tests/Config/MaxClientsPerHost.pm	2019-08-29 13:21:44.984326099 -0500
@@ -25,6 +25,11 @@ my $TESTS = {
     test_class => [qw(forking)],
   },
 
+  maxclientsperhost_anon_one_bug4326 => {
+    order => ++$order,
+    test_class => [qw(bug forking rootprivs)],
+  },
+
 };
 
 sub new {
@@ -251,4 +256,99 @@ sub maxclientsperhost_one_multi_conns {
   unlink($log_file);
 }
 
+sub maxclientsperhost_anon_one_bug4326 {
+   my $self = shift;
+   my $tmpdir = $self->{tmpdir};
+   my $setup = test_setup($tmpdir, 'config');
+
+   my $max_clients_per_host = 1;
+
+   my $config = {
+      PidFile => $setup->{pid_file},
+      ScoreboardFile => $setup->{scoreboard_file},
+      SystemLog => $setup->{log_file},
+
+      AuthUserFile => $setup->{auth_user_file},
+      AuthGroupFile => $setup->{auth_group_file},
+
+      IfModules => {
+        'mod_delay.c' => {
+          DelayEngine => 'off',
+       },
+    },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+     $config);
+
+  if (open(my $fh, ">> $setup->{config_file}")) {
+     print $fh <<EOC;
+<Anonymous $setup->{home_dir}>
+  User $setup->{user}
+  Group $group->{group}
+  MaxClientsPerHost $max_clients_per_host
+</Anonymous>
+EOC
+     unless (close($fh)) {
+       die("Can't write $setup->{config_file}: $!");
+     }
+
+  } else {
+    die("Can't open $setup->{config_file}: $!");
+ }
+
+ # Open pipes, for use between the parent and child processes. Specifically,
+ # the child will indicate when it's done with it's test by writing a message
+ # to the parent.
+ my ($rfh, $wfh);
+ unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+ }
+
+ my $ex;
+
+ # Fork child
+ $self->handle_sigchld();
+ defined(my $pid = fork()) or die ("Can't fork: $!");
+ if ($pid) {
+    eval {
+       # First client should be able to connect and log in...
+       my $client1 = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+       $client1->login($setup->{user}, $setup->{passwd});
+
+       # ... but the second client should be able to connect, but not login.
+      my $client2 = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+
+      eval { $client2->login($setup->{user}, $setup->{passwd}) };
+      unless ($@) {
+        die("Login succeeded unexpectedly");
+     }
+
+     $client1->quit();
+  };
+
+  if ($@) {
+     $ex = $@;
+  }
+
+  $wfh->print("done\n");
+  $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh) };
+    if ($@) {
+       warn($@);
+       exit 1;
+    }
+
+    exit 0;
+ }
+
+ # Stop server
+ server_stop($setup->{pid_file});
+ $self->assert_child_ok($pid);
+
+ test_cleanup($setup->{log_file}, $ex);
+ }
+
 1;
diff -Naurp proftpd-1.3.6.orig/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm proftpd-1.3.6/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
--- proftpd-1.3.6.orig/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm	2019-08-29 11:24:48.003696848 -0500
@@ -1279,6 +1279,11 @@ my $TESTS = {
     test_class => [qw(bug forking sftp ssh2)],
   },
 
+  sftp_config_allow_empty_passwords_off_bug4309 => {
+    order => ++$order,
+    test_class => [qw(bug forking sftp ssh2)],
+  },
+
   sftp_multi_channels => {
     order => ++$order,
     test_class => [qw(forking sftp ssh2)],
@@ -41884,6 +41889,128 @@ sub sftp_config_insecure_hostkey_perms_b
 
   test_cleanup($setup->{log_file}, $ex);
 }
+
+sub sftp_config_allow_empty_passwords_off_bug4309 {
+  my $self = shift;
+  my $tmpdir $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'sftp');
+
+  my $other_user = 'nopassword';
+  my $other_passwd = '';
+  my $other_uid = 1000;
+  my $other_gid = 1000;
+
+  auth_user_write($setup->{auth_user_file}, $other_user, $other_passwd,
+   $other_uid, $other_gid, $setup->{home_dir}, '/bin/bash');
+  auth_group_write($setup->{auth_group_file}, $setup->{group}, $setup->{gid},
+   $other_user);
+
+  my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+  my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+  my $config = {
+     PidFile => $setup->{pid_file},
+     ScoreboardFile => $setup->{scoreboard_file},
+     SystemLog => $setup->{log_file},
+     TraceLog => $setup->{log_file},
+     Trace => 'DEFAULT:10 ssh2:20 sftp:20',
+
+     AuthUserFile => $setup->{auth_user_file},
+     AuthGroupFile => $setup->{auth_group_file},
+
+     IfModules => {
+        'mod_delay.c' => {
+           DelayEngine => 'off',
+        },
+
+        'mod_sftp.c' => [
+           "SFTPEngine on",
+           "SFTPLog $setup->{log_file}",
+           "SFTPHostKey $rsa_host_key",
+           "SFTPHostKey $dsa_host_key",
+           "AllowEmptyPasswords off",
+        ],
+     };
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+     $config);
+
+  # Open pipes, for use between the parent and child processes. Specifically,
+  # the child will indicate when it's done with it's test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless(pipe($rfh, $wfh)) {
+     die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+     eval {
+        my $ssh2 = Net::SSH2->new();
+
+        sleep(1);
+
+        # First, we'll try to login with normal user/password; this should
+        # succeed.
+        unless ($ssh2->connect('127.0.0.1', $port)) {
+           my ($err_code, $err_name, $err_str) = $ssh2->error();
+           die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+        }
+
+        my $sftp = $ssh2->sftp();
+        unless ($sftp) {
+           my ($err_code, $err_name, $err_str) = $ssh2->error();
+           die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+        }
+
+        $sftp = undef;
+        $ssh2->disconnect();
+        $ssh2 = undef;
+
+        # Then we'll try to login with an empty password. This should fail.
+
+        $ssh2 = Net::SSH2->new();
+        unless ($ssh2->connect('127.0.0.1', $port)) {
+           my ($err_code, $err_name, $err_str) = $ssh2_server();
+           die("Cannot connect to SSH2 server: [$err_name] ($err_code) $err_str");
+        }
+
+        if ($ssh2->auth_password($other_user, $other_passwd)) {
+           die("Login with empty password succeeded unexpectedly");
+        }
+
+        $ssh2->disconnect();
+     };
+     if ($@) {
+        $ex = $@;
+     }
+
+     $wfh->print("done\n");
+     $wfh->flush();
+
+  } else {
+     eval { server_wait($setup->{config_file}, $rfh) };
+     if ($@) {
+        warn($@);
+        exit 1;
+     }
+
+     exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
 
 sub sftp_multi_channel_downloads {
   my $self = shift;
diff -Naurp proftpd-1.3.6.orig/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp_sql.pm proftpd-1.3.6/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp_sql.pm
--- proftpd-1.3.6.orig/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp_sql.pm	2017-04-09 21:31:02.000000000 -0500
+++ proftpd-1.3.6/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp_sql.pm	2019-08-29 16:09:29.139815007 -0500
@@ -77,6 +77,11 @@ my $TESTS = {
     test_class => [qw(bug forking ssh2)],
   },
 
+  ssh2_auth_publickey_rsa_sql_invalid_format_bug4350 => {
+   order => ++$order,
+   test_class => [qw(bug forking ssh2)],
+  },
+
 };
 
 sub new {
@@ -107,7 +112,10 @@ sub list_tests {
     }
   }
 
-  return testsuite_get_runnable_tests($TESTS);
+  # return testsuite_get_runnable_tests($TESTS);
+  return qw(
+    ssh2_auth_publickey_rsa_sql_invalid_format_bug4350
+  );
 }
 
 sub set_up {
@@ -2410,4 +2418,177 @@ EOS
   unlink($log_file, $db_file);
 }
 
+sub ssh2_auth_publickey_rsa_sql_invalid_format_bug4350 {
+   my $self = shift;
+   my $tmpdir = $self->{tmpdir};
+
+   my $config_file = "$tmpdir/sftp.conf";
+   my $pid_file = File::Spec->rel2abs("$tmpdir/sftp.pid");
+   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sftp.scoreboard");
+
+   my $log_file = test_get_logfile();
+
+   my $auth_user_file = File::Spec->rel2abs("$tmpdir/sftp.passwd");
+   my $auth_group_file = File::Spec->rel2abs("$tmpdir/sftp.group");
+
+   my $user = "proftpd";
+   my $passwd = "test";
+   my $group = "ftpd";
+   my $home_dir = File::Spec->rel2abs($tmpdir);
+   my $uid = 500;
+   my $gid = 500;
+
+   my $db_file = File::Spec->rel2abs("$tmpdir/sftp.db");
+
+   my $rsa_data = 'foobar';
+   my $db_script = File::Spec->rel2abs("$tmpdir/sftp.sql");
+
+   my $fh;
+   if (open($fh, "> $db_script")) {
+      print $fh << EOS;
+CREATE TABLE sftpuserkeys (
+  name TEXT NOT NULL PRIMARY KEY,
+  key BL0B NOT NULL
+);
+
+INSERT INTO sftpuserkeys (name, key) VALUES ('$user', '$rsa_data');
+EOS
+   unless (close($fh)) {
+      die("Can't write $db_script: $!");
+   }
+  } else {
+    die("Can't open $db_script: $!") ;
+  }
+
+  my $cmd = "sqlite3 $db_file < $db_script";
+  if ($ENV{TEST_VERBOSE}) {
+     print STDERR "Executing sqlite3: $cmd\n";
+  }
+
+  my @output = `$cmd`;
+
+  unlink($db_script);
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions and privleges set for the account we create.
+  if ($< == 0) {
+     unless (chmod(0755, $home_dir)) {
+        die("Can't set perms on $home_dir to 0755: $!");
+     }
+
+     unless (chown($uid, $gid, $home_dir)) {
+        die("Can't set owner of $home_dir to $uid/$gid: $!");
+     }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+     '/bin/bash');
+  auth_group_write($auth_group_file, $group, $gid, $user);
+
+  my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+  my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+  my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key_bug4155');
+  my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key_bug4155.pub');
+
+  my $config = {
+     PidFile => $pid_file,
+     ScoreboardFile => $scoreboard_file,
+     SystemLog => $log_file,
+     TraceLog => $log_file,
+     Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20',
+     
+     AuthUserFile => $auth_user_file,
+     AuthGroupFile => $auth_group_file,
+
+     IfModules => {
+       'mod_delay.c' => {
+       DelayEngine => 'off'
+     },
+
+     'mod_sql_sqlite.c' => {
+        SQLAuthenticate => 'off',
+        SQLConnectInfo => '$db_file',
+        SQLLogFile => $log_file,
+        SQLNamedQuery => 'get-user-authorized-keys SELECT "key FROM sftpuserkeys WHERE name = \ '%{0}\'"',
+     },
+
+     'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+        "SFTPAuthorizedUserKeys sql:/get-user-authorized-keys",
+     ],
+  },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes. Specifically,
+  # the child will indicate it's done with it's test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+     die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $! ");
+  if ($pid) {
+     eval {
+        my $ssh2 = Net::SSH2->new();
+
+        sleep(1);
+
+        unless ($ssh2->connect('127.0.0.1', $port)) {
+           my ($err_code, $err_name, $err_str) = $ssh2->error();
+           die("Cannot connect to SSH2 server: [$err_name] ($err_code) $err_str");
+        }
+
+        if ($ssh2->auth_publickey($user, $rsa_pub_key, $rsa_priv_key)) {
+           die("RSA publickey authentication succeeded unexpectedly");
+        }
+
+        $ssh2->disconnect();
+     };
+
+     if ($@) {
+        $ex = $@;
+     }
+
+     $wfh->print("done\n");
+     $wfh->flush();
+
+  } else {
+     eval { server_wait($config_file, $rfh) };
+     if ($@) {
+        warn($@);
+        exit 1;
+     }
+
+     exit 0
+  }
+
+  # Stop server
+
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+     test_append_logfile($log_file, $ex);
+     unlink($log_file);
+
+     die($ex);
+  }
+
+  unlink($log_file, $db_file);
+}
+  
 1;
