[Git][debian-proftpd-team/proftpd][upstream] New upstream version 1.3.8.c+dfsg

Hilmar Preuße (@hilmar) gitlab at salsa.debian.org
Fri Dec 13 22:07:00 GMT 2024



Hilmar Preuße pushed to branch upstream at Debian ProFTPD Team / proftpd


Commits:
91f64bbe by Hilmar Preuße at 2024-12-12T23:29:48+01:00
New upstream version 1.3.8.c+dfsg
- - - - -


24 changed files:

- .cirrus.yml
- .github/workflows/ci.yml
- .github/workflows/rpm.yml
- NEWS
- RELEASE_NOTES
- configure
- configure.in
- contrib/dist/rpm/proftpd.spec
- contrib/mod_radius.c
- contrib/mod_sftp/auth.c
- contrib/mod_sftp/fxp.c
- contrib/mod_sftp/keys.c
- contrib/mod_sftp/mod_sftp.h.in
- contrib/mod_sftp_sql.c
- contrib/mod_tls.c
- include/version.h
- modules/mod_auth.c
- src/auth.c
- src/event.c
- src/fsio.c
- tests/api/inet.c
- tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
- tests/t/lib/ProFTPD/Tests/Modules/mod_sftp_sql.pm
- tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm


Changes:

=====================================
.cirrus.yml
=====================================
@@ -3,8 +3,7 @@ task:
   name: build
   freebsd_instance:
     matrix:
-      image_family: freebsd-13-2
-      image_family: freebsd-12-4
+      image_family: freebsd-14-0
 
   env:
     CIRRUS_CLONE_DEPTH: 10
@@ -20,12 +19,12 @@ task:
     - pkg install -y libsodium
     - pkg install -y libmaxminddb
     - pkg install -y libmemcached
-    - pkg install -y mysql57-client
+    - pkg install -y mysql80-client
     - pkg install -y ncurses
     - pkg install -y openldap26-client
     - pkg install -y openssl
     - pkg install -y pcre
-    - pkg install -y postgresql11-client
+    - pkg install -y postgresql13-client
     - pkg install -y sqlite3
     - pkg install -y unixODBC
     # For the Redis unit tests


=====================================
.github/workflows/ci.yml
=====================================
@@ -7,6 +7,7 @@ on:
     paths-ignore:
       - NEWS
       - RELEASE_NOTES
+      - 'doc/**'
       - '**/*.html'
       - '**/*.md'
   pull_request:
@@ -15,6 +16,7 @@ on:
     paths-ignore:
       - NEWS
       - RELEASE_NOTES
+      - 'doc/**'
       - '**/*.html'
       - '**/*.md'
 
@@ -36,7 +38,12 @@ jobs:
     environment: CI
 
     env:
-      PACKAGE_VERSION: 1.3.8rc5
+      # We need to avoid using NodeJS v20, because it doesn't work with
+      # older glibc versions.  See:
+      #  https://github.com/actions/checkout/issues/1809.
+      ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
+
+      PACKAGE_VERSION: 1.3.8c
       REDIS_HOST: redis
 
     strategy:
@@ -46,8 +53,8 @@ jobs:
           - gcc
         container:
           - almalinux:8
-          - alpine:3.14
-          - ubuntu:18.04
+          - alpine:3.18
+          - ubuntu:22.04
 
     container: ${{ matrix.container }}
 
@@ -56,7 +63,7 @@ jobs:
         uses: actions/checkout at v2
 
       - name: Whitespace check
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           apt-get update -qq
           apt-get install -y git
@@ -68,11 +75,11 @@ jobs:
           fi
 
       - name: Install Alpine packages
-        if: ${{ matrix.container == 'alpine:3.14' }}
+        if: ${{ matrix.container == 'alpine:3.18' }}
         run: |
           apk update
           # for builds
-          apk add bash build-base clang compiler-rt-static gcc make zlib-dev
+          apk add bash build-base clang compiler-rt gcc make zlib-dev
           # for unit tests
           apk add check check-dev subunit subunit-dev
 
@@ -161,8 +168,6 @@ jobs:
           yum install -y libsodium-devel
           # for mod_sql_sqlite
           yum install -y sqlite-devel
-          # for mod_wrap
-          yum install -y libnsl2-devel https://rpmfind.net/linux/centos/7.9.2009/os/x86_64/Packages/tcp_wrappers-libs-7.6-77.el7.x86_64.rpm https://rpmfind.net/linux/centos/7.9.2009/os/x86_64/Packages/tcp_wrappers-devel-7.6-77.el7.x86_64.rpm
           # for PCRE2 support
           yum install -y pcre2-devel
           # for ftptop
@@ -179,7 +184,7 @@ jobs:
           yum install -y clang
 
           # The modules to build are distro-specific
-          echo "PROFTPD_MODULES=mod_sql:mod_sql_mysql:mod_sql_odbc:mod_sql_postgres:mod_sql_sqlite:mod_sql_passwd:mod_sftp:mod_sftp_sql:mod_sftp_pam:mod_tls:mod_tls_fscache:mod_tls_shmcache:mod_tls_memcache:mod_tls_redis:mod_ban:mod_copy:mod_ctrls_admin:mod_deflate:mod_dnsbl:mod_dynmasq:mod_exec:mod_facl:mod_geoip:mod_ifversion:mod_ldap:mod_load:mod_log_forensic:mod_qos:mod_quotatab:mod_quotatab_file:mod_quotatab_ldap:mod_quotatab_radius:mod_quotatab_sql:mod_radius:mod_readme:mod_rewrite:mod_shaper:mod_site_misc:mod_snmp:mod_wrap:mod_wrap2:mod_wrap2_file:mod_wrap2_redis:mod_wrap2_sql:mod_digest:mod_auth_otp:mod_statcache:mod_unique_id:mod_ifsession" >> $GITHUB_ENV
+          echo "PROFTPD_MODULES=mod_sql:mod_sql_mysql:mod_sql_odbc:mod_sql_postgres:mod_sql_sqlite:mod_sql_passwd:mod_sftp:mod_sftp_sql:mod_sftp_pam:mod_tls:mod_tls_fscache:mod_tls_shmcache:mod_tls_memcache:mod_tls_redis:mod_ban:mod_copy:mod_ctrls_admin:mod_deflate:mod_dnsbl:mod_dynmasq:mod_exec:mod_facl:mod_geoip:mod_ifversion:mod_ldap:mod_load:mod_log_forensic:mod_qos:mod_quotatab:mod_quotatab_file:mod_quotatab_ldap:mod_quotatab_radius:mod_quotatab_sql:mod_radius:mod_readme:mod_rewrite:mod_shaper:mod_site_misc:mod_snmp:mod_wrap2:mod_wrap2_file:mod_wrap2_redis:mod_wrap2_sql:mod_digest:mod_auth_otp:mod_statcache:mod_unique_id:mod_ifsession" >> $GITHUB_ENV
 
           # for debugging
           clang --version
@@ -187,7 +192,7 @@ jobs:
           openssl version -a
 
       - name: Install Ubuntu packages
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           # Need to add other repos for e.g. libsodium
           apt-get update -qq
@@ -230,7 +235,7 @@ jobs:
           # for mod_wrap
           apt-get install -y libwrap0-dev
           # for PCRE2 support
-          apt-get install -y libpcre2-dev libpcre2-posix0
+          apt-get install -y libpcre2-dev libpcre2-posix3
           # for ftptop
           apt-get install -y ncurses-dev
           # for zlib support
@@ -238,7 +243,6 @@ jobs:
 
           # for logging
           apt-get install -y rsyslog
-          service rsyslog start
 
           # for static code analysis
           # - sudo apt-get install -y cppcheck
@@ -261,7 +265,7 @@ jobs:
           openssl version -a
 
       - name: Prepare code coverage
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           lcov --directory . --zerocounters
 
@@ -280,14 +284,14 @@ jobs:
         # On recent (as of 2022-11-13) AlmaLinux with clang and the
         # `--enable-devel=fortify` configure settings, some of the unit tests
         # inexplicably fail.  So skip clang unit tests, for now.
-        if: ${{ matrix.compiler == 'gcc' && matrix.container != 'alpine:3.14' }}
+        if: ${{ matrix.compiler == 'gcc' && matrix.container != 'alpine:3.18' }}
         run: |
           make TEST_VERBOSE=1 check-api
 
       - name: Upload code coverage
         env:
           COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        if: ${{ matrix.compiler == 'gcc' && matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.compiler == 'gcc' && matrix.container == 'ubuntu:22.04' }}
         run: |
           lcov --ignore-errors gcov,source --directory . --capture --output-file all.info
           # filter out system and test code
@@ -313,7 +317,7 @@ jobs:
           PROFTPD_TEST_PIDFILE: /usr/local/var/proftpd.pid
         # Note that the default config assumes user 'nobody', group 'nogroup',
         # which do not exist in the default images of all distributions.
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           test -e $PROFTPD_TEST_BIN
           $PROFTPD_TEST_BIN
@@ -358,7 +362,7 @@ jobs:
           CC: ${{ matrix.compiler }}
           CFLAGS: -fsanitize=address,undefined -DPR_DEVEL_NO_POOL_FREELIST
           LDFLAGS: -fsanitize=address,undefined
-        if: ${{ matrix.compiler == 'clang' && matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.compiler == 'clang' && matrix.container == 'ubuntu:22.04' }}
         run: |
           make clean
           ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel --enable-ctrls --enable-facl --enable-memcache --enable-nls --enable-pcre2 --enable-redis --enable-tests
@@ -367,12 +371,12 @@ jobs:
           make check-api
 
       - name: Check Perl scripts
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           perl -cw contrib/ftpasswd
           perl -cw contrib/ftpquota
 
       - name: Check HTML docs
-        if: ${{ matrix.container == 'ubuntu:18.04' }}
+        if: ${{ matrix.container == 'ubuntu:22.04' }}
         run: |
           for f in $(/bin/ls doc/contrib/*.html doc/directives/*.html doc/howto/*.html doc/modules/*.html doc/utils/*.html); do echo "Processing $f"; tidy -errors -omit -q $f; done || exit 0


=====================================
.github/workflows/rpm.yml
=====================================
@@ -7,6 +7,7 @@ on:
     paths-ignore:
       - NEWS
       - RELEASE_NOTES
+      - 'doc/**'
       - '**/*.html'
       - '**/*.md'
   pull_request:
@@ -15,6 +16,7 @@ on:
     paths-ignore:
       - NEWS
       - RELEASE_NOTES
+      - 'doc/**'
       - '**/*.html'
       - '**/*.md'
 
@@ -22,6 +24,12 @@ jobs:
   build:
     runs-on: ubuntu-latest
 
+    env:
+      # We need to avoid using NodeJS v20, because it doesn't work with
+      # older glibc versions.  See:
+      #  https://github.com/actions/checkout/issues/1809.
+      ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
+
     strategy:
       matrix:
         container:
@@ -40,8 +48,6 @@ jobs:
           yum install -y dnf-plugins-core epel-release yum-utils
           dnf config-manager --enable epel
           dnf config-manager --set-enabled powertools
-          # for mod_wrap
-          yum install -y libnsl2-devel https://rpmfind.net/linux/centos/7.9.2009/os/x86_64/Packages/tcp_wrappers-libs-7.6-77.el7.x86_64.rpm https://rpmfind.net/linux/centos/7.9.2009/os/x86_64/Packages/tcp_wrappers-devel-7.6-77.el7.x86_64.rpm
 
       - name: Install packages
         run: |


=====================================
NEWS
=====================================
@@ -15,6 +15,20 @@
   where `N' is the issue number.
 -----------------------------------------------------------------------------
 
+1.3.8c - Released 11-Dec-2024
+--------------------------------
+- Issue 1770 - Using FTPS after upgrading from 1.3.8a to 1.3.8b leads to crash.
+- Issue 1785 - Bad handling of lack of extended attributes leads to SFTP out of
+  memory error.
+- Issue 1529 - mod_sftp_sql logs "header value too long" due to unexpected key
+  header text.
+- Issue 1839 - SSH ECDSA host key algorithms not be used as expected despite
+  configuring appropriate key.
+- Issue 1840 - RADIUS Message-Authenticator verification failed with ProFTPD
+  mod_radius.
+- Issue 1830 - Supplemental group inheritance grants unintended access to
+  GID 0 due to lack of supplemental groups from mod_sql.
+
 1.3.8b - Released 19-Dec-2023
 --------------------------------
 - Issue 1735 - Compiling ProFTPD 1.3.8a mod_sftp, mod_tls using libressl 3.7.3


=====================================
RELEASE_NOTES
=====================================
@@ -6,6 +6,19 @@ This file contains a description of the major changes to ProFTPD for the
 releases.  More information on these changes can be found in the NEWS and
 ChangeLog files.
 
+1.3.8c
+------
+
+  + Fixed segfault when TLSOptions StdEnvVars is used in conjunction with
+    an FTPS session.
+
+  + Properly use all configured ECDSA SSH hostkeys, not just the first
+    configured ECSA SSH hostkey.
+
+  + Fixed issue with RADIUS authentication when using mod_radius and newer
+    FreeRADIUS, due to BlastRadius mitigation.
+
+
 1.3.8b
 ------
 


=====================================
configure
=====================================
@@ -21419,7 +21419,7 @@ main ()
 {
 
     size_t res, in_len = 0, out_len = 0;
-    const char *in = NULL;
+    char *in = NULL;
     char *out = NULL;
     res = iconv((iconv_t)-1, &in, &in_len, &out, &out_len);
 


=====================================
configure.in
=====================================
@@ -2044,7 +2044,7 @@ AC_TRY_LINK(
   ],
   [ 
     size_t res, in_len = 0, out_len = 0;
-    const char *in = NULL;
+    char *in = NULL;
     char *out = NULL;
     res = iconv((iconv_t)-1, &in, &in_len, &out, &out_len);
   ],


=====================================
contrib/dist/rpm/proftpd.spec
=====================================
@@ -53,7 +53,7 @@
 # RHEL5 and clones don't have suitably recent versions of pcre/libmemcached
 # so use --with rhel5 to inhibit those features when using --with everything
 
-%global proftpd_version			1.3.8b
+%global proftpd_version			1.3.8c
 
 # rc_version should be incremented for each RC release, and reset back to 1
 # AFTER each stable release.
@@ -61,7 +61,7 @@
 
 # release_version should be incremented for each maint release, and reset back
 # to 1 BEFORE starting new release cycle.
-%global release_version			3
+%global release_version			4
 
 %if %(echo %{proftpd_version} | grep rc >/dev/null 2>&1 && echo 1 || echo 0)
 %global rpm_version %(echo %{proftpd_version} | sed -e 's/rc.*//')
@@ -84,7 +84,7 @@
 
 # Handle optional functionality
 #
-# --with everything (for all optional functionality)
+# --with everything (for all optional functionality EXCEPT mod_wrap)
 # --with rhel5 inhibits features not available on RHEL5 and clones
 # --with rhel6 inhibits features not available on RHEL6 and clones
 %if 0%{?_with_everything:1}
@@ -102,8 +102,12 @@
 %global _with_postgresql 1
 %global _with_ssl 1
 %global _with_sodium 1
+#
+# --with wrap (for mod_wrap)
+%if 0%{?_with_wrap:1}
 %global _with_wrap 1
 %endif
+%endif
 #
 # --with geoip (for mod_geoip)
 %if 0%{?_with_geoip:1}


=====================================
contrib/mod_radius.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD: mod_radius -- a module for RADIUS authentication and accounting
- * Copyright (c) 2001-2022 TJ Saunders
+ * Copyright (c) 2001-2024 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
@@ -2266,8 +2266,11 @@ static int radius_verify_auth_mac(radius_packet_t *pkt, const char *pkt_type,
       memset(replied, '\0', sizeof(replied));
       memcpy(replied, attrib->data, attrib_len);
 
-      /* Next, zero out the value so that we can calculate it ourselves. */
-      memset(attrib->data, '\0', attrib_len);
+      /* Next, zero out the value so that we can calculate it ourselves.
+       *
+       * Note that we only want to zero out the first 16 bytes, per RFC 2869.
+       */
+      memset(attrib->data, '\0', expected_len);
 
       memset(digest, '\0', sizeof(digest));
       md = EVP_md5();


=====================================
contrib/mod_sftp/auth.c
=====================================
@@ -388,8 +388,20 @@ static int setup_env(pool *p, const char *user) {
       session.groups == NULL) {
     res = pr_auth_getgroups(p, pw->pw_name, &session.gids, &session.groups);
     if (res < 1) {
+      /* If no supplemental groups are provided, default to using the process
+       * primary GID as the supplemental group.  This prevents access
+       * regressions as seen in Issue #1830.
+       */
       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-        "no supplemental groups found for user '%s'", pw->pw_name);
+        "no supplemental groups found for user '%s', "
+        "using primary group %s (GID %lu)", pw->pw_name, session.group,
+        (unsigned long) session.login_gid);
+
+      session.gids = make_array(p, 2, sizeof(gid_t));
+      session.groups = make_array(p, 2, sizeof(char *));
+
+      *((gid_t *) push_array(session.gids)) = session.login_gid;
+      *((char **) push_array(session.groups)) = pstrdup(p, session.group);
     }
   }
 


=====================================
contrib/mod_sftp/fxp.c
=====================================
@@ -2593,6 +2593,7 @@ static uint32_t fxp_xattrs_write(pool *p, struct fxp_buffer *fxb,
         if (valsz > 0) {
           *((pr_buffer_t **) push_array(vals)) = val;
         }
+
       } else {
         /* Push the empty buffer into the list, so that the vals list
          * lines up with the names list.


=====================================
contrib/mod_sftp/keys.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp key mgmt (keys)
- * Copyright (c) 2008-2023 TJ Saunders
+ * Copyright (c) 2008-2024 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
@@ -4143,8 +4143,9 @@ int sftp_keys_have_ecdsa_hostkey(pool *p, int **nids) {
     }
     count++;
     EC_KEY_free(ec);
+  }
 
-  } else if (sftp_ecdsa384_hostkey != NULL) {
+  if (sftp_ecdsa384_hostkey != NULL) {
     EC_KEY *ec;
 
     ec = EVP_PKEY_get1_EC_KEY(sftp_ecdsa384_hostkey->pkey);
@@ -4153,8 +4154,9 @@ int sftp_keys_have_ecdsa_hostkey(pool *p, int **nids) {
     }
     count++;
     EC_KEY_free(ec);
+  }
 
-  } else if (sftp_ecdsa521_hostkey != NULL) {
+  if (sftp_ecdsa521_hostkey != NULL) {
     EC_KEY *ec;
 
     ec = EVP_PKEY_get1_EC_KEY(sftp_ecdsa521_hostkey->pkey);


=====================================
contrib/mod_sftp/mod_sftp.h.in
=====================================
@@ -101,7 +101,9 @@
 #include <openssl/rsa.h>
 #if OPENSSL_VERSION_NUMBER > 0x000907000L
 # include <openssl/aes.h>
-# include <openssl/engine.h>
+# ifdef PR_USE_OPENSSL_ENGINE
+#  include <openssl/engine.h>
+# endif /* PR_USE_OPENSSL_ENGINE */
 # include <openssl/ocsp.h>
 #endif
 #if defined(PR_USE_OPENSSL_ECC)


=====================================
contrib/mod_sftp_sql.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD: mod_sftp_sql -- SQL backend module for retrieving authorized keys
- * Copyright (c) 2008-2022 TJ Saunders
+ * Copyright (c) 2008-2024 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
@@ -93,7 +93,8 @@ static char *sqlstore_getline(pool *p, char **blob, size_t *bloblen) {
     return NULL;
   }
 
-  while (data != NULL && datalen > 0) {
+  while (data != NULL &&
+         datalen > 0) {
     char *ptr;
     size_t delimlen, linelen;
     int have_line_continuation = FALSE;
@@ -187,13 +188,28 @@ static char *sqlstore_getline(pool *p, char **blob, size_t *bloblen) {
       }
 
       /* Header value starts at 2 after the ':' (one for the mandatory
-       * space character.
+       * space character.  Make sure to check that we actually have text
+       * after the ':' character (see Issue #1529).
        */
-      header_valuelen = linelen - (header_taglen + 2);
-      if (header_valuelen > 1024) {
+      if (header_taglen + 2 < linelen) {
+        header_valuelen = linelen - (header_taglen + 2);
+        if (header_valuelen > 1024) {
+          (void) pr_log_writefile(sftp_logfd, MOD_SFTP_SQL_VERSION,
+            "header value too long (%u) in retrieved SQL data for '%s'",
+            header_valuelen, sqlstore_user);
+          errno = EINVAL;
+          return NULL;
+        }
+
+      } else {
         (void) pr_log_writefile(sftp_logfd, MOD_SFTP_SQL_VERSION,
-          "header value too long (%u) in retrieved SQL data for '%s'",
-          header_valuelen, sqlstore_user);
+          "empty/missing '%.*s' header value, ignoring", (int) header_taglen,
+          line);
+
+        /* Make sure we advance past this line. */
+        *blob = data;
+        *bloblen = datalen;
+
         errno = EINVAL;
         return NULL;
       }


=====================================
contrib/mod_tls.c
=====================================
@@ -69,7 +69,9 @@
 #include <openssl/pkcs12.h>
 #include <openssl/rand.h>
 #if OPENSSL_VERSION_NUMBER > 0x000907000L
-# include <openssl/engine.h>
+# ifdef PR_USE_OPENSSL_ENGINE
+#  include <openssl/engine.h>
+# endif /* PR_USE_OPENSSL_ENGINE */
 # ifdef PR_USE_OPENSSL_OCSP
 #  include <openssl/ocsp.h>
 # endif /* PR_USE_OPENSSL_OCSP */
@@ -10054,6 +10056,7 @@ static void tls_setup_cert_environ(pool *p, const char *env_prefix,
     bio = BIO_new(BIO_s_mem());
 #if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(HAVE_LIBRESSL)) || \
     (defined(HAVE_LIBRESSL) && LIBRESSL_VERSION_NUMBER >= 0x3050000L)
+    pubkey = X509_get_X509_PUBKEY(cert);
     X509_PUBKEY_get0_param(NULL, NULL, NULL, (X509_ALGOR **) &algo, pubkey);
 #else
     pubkey = cert->cert_info->key;


=====================================
include/version.h
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2020-2023 The ProFTPD Project team
+ * Copyright (c) 2020-2024 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
@@ -28,8 +28,8 @@
 #include "buildstamp.h"
 
 /* Application version (in various forms) */
-#define PROFTPD_VERSION_NUMBER		0x0001030807
-#define PROFTPD_VERSION_TEXT		"1.3.8b"
+#define PROFTPD_VERSION_NUMBER		0x0001030808
+#define PROFTPD_VERSION_TEXT		"1.3.8c"
 
 /* Module API version */
 #define PR_MODULE_API_VERSION		0x20


=====================================
modules/mod_auth.c
=====================================
@@ -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 at tos.net>
- * Copyright (c) 2001-2022 The ProFTPD Project team
+ * Copyright (c) 2001-2024 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
@@ -1113,8 +1113,8 @@ static int setup_env(pool *p, cmd_rec *cmd, const char *user, char *pass) {
     session.groups = NULL;
   }
 
-  if (!session.gids &&
-      !session.groups) {
+  if (session.gids == NULL &&
+      session.groups == NULL) {
     /* Get the supplemental groups.  Note that we only look up the
      * supplemental group credentials if we have not cached the group
      * credentials before, in session.gids and session.groups.  
@@ -1124,8 +1124,19 @@ static int setup_env(pool *p, cmd_rec *cmd, const char *user, char *pass) {
      */
      res = pr_auth_getgroups(p, pw->pw_name, &session.gids, &session.groups);
      if (res < 1) {
-       pr_log_debug(DEBUG5, "no supplemental groups found for user '%s'",
-         pw->pw_name);
+       /* If no supplemental groups are provided, default to using the process
+        * primary GID as the supplemental group.  This prevents access
+        * regressions as seen in Issue #1830.
+        */
+       pr_log_debug(DEBUG5, "no supplemental groups found for user '%s', "
+         "using primary group %s (GID %lu)", pw->pw_name, session.group,
+         (unsigned long) session.login_gid);
+
+       session.gids = make_array(p, 2, sizeof(gid_t));
+       session.groups = make_array(p, 2, sizeof(char *));
+
+       *((gid_t *) push_array(session.gids)) = session.login_gid;
+       *((char **) push_array(session.groups)) = pstrdup(p, session.group);
      }
   }
 


=====================================
src/auth.c
=====================================
@@ -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 at tos.net>
- * Copyright (c) 2001-2022 The ProFTPD Project team
+ * Copyright (c) 2001-2024 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
@@ -1471,12 +1471,12 @@ int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
   }
 
   /* Allocate memory for the array_headers of GIDs and group names. */
-  if (group_ids) {
-    *group_ids = make_array(permanent_pool, 2, sizeof(gid_t));
+  if (group_ids != NULL) {
+    *group_ids = make_array(p, 2, sizeof(gid_t));
   }
 
-  if (group_names) {
-    *group_names = make_array(permanent_pool, 2, sizeof(char *));
+  if (group_names != NULL) {
+    *group_names = make_array(p, 2, sizeof(char *));
   }
 
   cmd = make_cmd(p, 3, name, group_ids ? *group_ids : NULL,
@@ -1495,7 +1495,7 @@ int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
      * for the benefit of auth_getgroup() implementors.
      */
 
-    if (group_ids) {
+    if (group_ids != NULL) {
       register unsigned int i;
       char *strgids = "";
       gid_t *gids = (*group_ids)->elts;
@@ -1511,7 +1511,7 @@ int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
         *strgids ? strgids : "(None; corrupted group file?)");
     }
 
-    if (group_names) {
+    if (group_names != NULL) {
       register unsigned int i;
       char *strgroups = ""; 
       char **groups = (*group_names)->elts;
@@ -1527,7 +1527,7 @@ int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
     }
   }
 
-  if (cmd->tmp_pool) {
+  if (cmd->tmp_pool != NULL) {
     destroy_pool(cmd->tmp_pool);
     cmd->tmp_pool = NULL;
   }


=====================================
src/event.c
=====================================
@@ -160,8 +160,8 @@ int pr_event_register(module *m, const char *event,
         }
 
         if (evh->module != NULL) {
-          if (evl->handlers->next != NULL) {
-            evl->handlers->next->prev = evh;
+          if (evl->handlers != NULL) {
+            evl->handlers->prev = evh;
           }
 
           evh->next = evl->handlers;


=====================================
src/fsio.c
=====================================
@@ -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 at tos.net>
- * Copyright (c) 2001-2022 The ProFTPD Project
+ * Copyright (c) 2001-2024 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
@@ -699,11 +699,11 @@ static ssize_t unix_flistxattr(int fd, char *namelist, size_t len) {
 
 static int sys_listxattr(pool *p, pr_fs_t *fs, const char *path,
     array_header **names) {
-  ssize_t res;
+  ssize_t res = 0;
   char *namelist = NULL;
   size_t len = 0;
 
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
   /* We need to handle the different formats of namelists that listxattr et al
    * can provide.  On *BSDs, the namelist buffer uses length prefixes and no
    * terminating NULs; on Linux/Mac, the namelist buffer uses ONLY
@@ -720,6 +720,13 @@ static int sys_listxattr(pool *p, pr_fs_t *fs, const char *path,
     return -1;
   }
 
+  if (res == 0) {
+    /* No extended attributes found. */
+    pr_trace_msg(trace_channel, 15, "listxattr: found 0 xattr names for '%s'",
+      path);
+    return 0;
+  }
+
   len = res;
   namelist = palloc(p, len);
 
@@ -728,6 +735,13 @@ static int sys_listxattr(pool *p, pr_fs_t *fs, const char *path,
     return -1;
   }
 
+  if (res == 0) {
+    /* No extended attributes found. */
+    pr_trace_msg(trace_channel, 15, "listxattr: found 0 xattr names for '%s'",
+      path);
+    return 0;
+  }
+
   *names = parse_xattr_namelist(p, namelist, len);
   if (pr_trace_get_level(trace_channel) >= 15) {
     register unsigned int i;
@@ -765,13 +779,20 @@ static int sys_llistxattr(pool *p, pr_fs_t *fs, const char *path,
   char *namelist = NULL;
   size_t len = 0;
 
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
   /* See sys_listxattr for a description of why we use this approach. */
   res = unix_llistxattr(path, NULL, 0);
   if (res < 0) {
     return -1;
   }
 
+  if (res == 0) {
+    /* No extended attributes found. */
+    pr_trace_msg(trace_channel, 15, "llistxattr: found 0 xattr names for '%s'",
+      path);
+    return 0;
+  }
+
   len = res;
   namelist = palloc(p, len);
 
@@ -780,6 +801,13 @@ static int sys_llistxattr(pool *p, pr_fs_t *fs, const char *path,
     return -1;
   }
 
+  if (res == 0) {
+    /* No extended attributes found. */
+    pr_trace_msg(trace_channel, 15, "llistxattr: found 0 xattr names for '%s'",
+      path);
+    return 0;
+  }
+
   *names = parse_xattr_namelist(p, namelist, len);
   if (pr_trace_get_level(trace_channel) >= 15) {
     register unsigned int i;
@@ -816,13 +844,20 @@ static int sys_flistxattr(pool *p, pr_fh_t *fh, int fd, array_header **names) {
   char *namelist = NULL;
   size_t len = 0;
 
-#ifdef PR_USE_XATTR
+#if defined(PR_USE_XATTR)
   /* See sys_listxattr for a description of why we use this approach. */
   res = unix_flistxattr(fd, NULL, 0);
   if (res < 0) {
     return -1;
   }
 
+  if (res == 0) {
+    /* No extended attributes found. */
+    pr_trace_msg(trace_channel, 15, "flistxattr: found 0 xattr names for '%s'",
+      fh->fh_path);
+    return 0;
+  }
+
   len = res;
   namelist = palloc(p, len);
 
@@ -831,6 +866,13 @@ static int sys_flistxattr(pool *p, pr_fh_t *fh, int fd, array_header **names) {
     return -1;
   }
 
+  if (res == 0) {
+    /* No extended attributes found. */
+    pr_trace_msg(trace_channel, 15, "flistxattr: found 0 xattr names for '%s'",
+      fh->fh_path);
+    return 0;
+  }
+
   *names = parse_xattr_namelist(p, namelist, len);
   if (pr_trace_get_level(trace_channel) >= 15) {
     register unsigned int i;


=====================================
tests/api/inet.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server testsuite
- * Copyright (c) 2014-2021 The ProFTPD Project team
+ * Copyright (c) 2014-2024 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
@@ -974,8 +974,13 @@ START_TEST (inet_connect_nowait_test) {
   ck_assert_msg(addr != NULL, "Failed to resolve '127.0.0.1': %s",
     strerror(errno));
 
-  res = pr_inet_connect_nowait(p, conn, addr, 180);
-  ck_assert_msg(res != -1, "Connected to 127.0.0.1#180 unexpectedly");
+  if (getenv("CIRRUS_CLONE_DEPTH") == NULL) {
+    /* On CirrusCI VMs, this succeeds unexpectedly, so run it only when we
+     * are NOT running in the Cirrus CI.
+     */
+    res = pr_inet_connect_nowait(p, conn, addr, 180);
+    ck_assert_msg(res != -1, "Connected to 127.0.0.1#180 unexpectedly");
+  }
 
 #if defined(PR_USE_NETWORK_TESTS)
   /* Try connecting to Google's DNS server. */


=====================================
tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
=====================================
@@ -5647,11 +5647,7 @@ EOC
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-
-      # libssh2, and thus Net::SSH2, don't support ECC/ECDH yet.  So we
-      # use the external sftp(1) client (e.g. OpenSSH-5.9p1) to test.
-
-      my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp';
+      my $sftp = 'sftp';
 
       my @cmd = (
         $sftp,
@@ -5725,9 +5721,7 @@ EOC
       my $expected_sz = $src_sz;
       $self->assert($expected_sz == $sz,
         test_msg("Expected file size $expected_sz, got $sz"));
-
     };
-
     if ($@) {
       $ex = $@;
     }
@@ -5898,11 +5892,7 @@ EOC
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-
-      # libssh2, and thus Net::SSH2, don't support ECC/ECDH yet.  So we
-      # use the external sftp(1) client (e.g. OpenSSH-5.9p1) to test.
-
-      my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp';
+      my $sftp = 'sftp';
 
       my @cmd = (
         $sftp,
@@ -6149,11 +6139,7 @@ EOC
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-
-      # libssh2, and thus Net::SSH2, don't support ECC/ECDH yet.  So we
-      # use the external sftp(1) client (e.g. OpenSSH-5.9p1) to test.
-
-      my $sftp = '/Users/tj/local/openssh-7.9p1/bin/sftp';
+      my $sftp = 'sftp';
 
       my @cmd = (
         $sftp,


=====================================
tests/t/lib/ProFTPD/Tests/Modules/mod_sftp_sql.pm
=====================================
@@ -98,6 +98,11 @@ my $TESTS = {
     test_class => [qw(bug forking ssh2)],
   },
 
+  ssh2_auth_publickey_empty_comment_issue1529 => {
+    order => ++$order,
+    test_class => [qw(bug forking ssh2)],
+  },
+
 };
 
 sub new {
@@ -3170,4 +3175,150 @@ EOS
   test_cleanup($setup->{log_file}, $ex);
 }
 
+sub ssh2_auth_publickey_empty_comment_issue1529 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'sftp_sql');
+
+  my $db_file = File::Spec->rel2abs("$tmpdir/sftp.db");
+
+  my $rsa_rfc4716_data = '---- BEGIN SSH2 PUBLIC KEY ----
+Comment:
+AAAAB3NzaC1yc2EAAAABIwAAAQEAzJ1CLwnVP9mUa8uyM+XBzxLxsRvGz4cS59aPTgdw7j
+Gx1jCvC9ya400x7ej5Q4ubwlAAPblXzG5GYv2ROmYQ1DIjrhmR/61tDKUvAAZIgtvLZ00y
+dqqpq5lG4ubVJ4gW6sxbPfq/X12kV1gxGsFLUJCgoYInZGyIONrnvmQjFIfIx+mQXaK84u
+O6w0CT6KhRWgonajMrlO6P8O7qr80rFmOZsBNIMooyYrGTaMyxVsQK2SY+VKbXWFC+2HMm
+ef62n+02ohAOBKtOsSOn8HE2wi7yMA0g8jRTd8kZcWBIkAhizPvl8pqG1F0DCmLn00rhPk
+Byq2pv4VBo953gK7f1AQ==
+---- END SSH2 PUBLIC KEY ----';
+
+  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 BLOB NOT NULL
+);
+
+INSERT INTO sftpuserkeys (name, key) VALUES ('$setup->{user}', '$rsa_rfc4716_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);
+
+  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');
+  my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub');
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'ssh2:20 sftp:20 sql:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    AuthOrder => 'mod_auth_file.c',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_sql_sqlite.c' => {
+        SQLAuthenticate => 'off',
+        SQLConnectInfo => $db_file,
+        SQLLogFile => $setup->{log_file},
+        SQLNamedQuery => 'get-user-authorized-keys SELECT "key FROM sftpuserkeys WHERE name = \'%{0}\'"',
+      },
+
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $setup->{log_file}",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+        "SFTPAuthorizedUserKeys sql:/get-user-authorized-keys",
+      ],
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      # Allow for server startup
+      sleep(2);
+
+      my $ssh2 = Net::SSH2->new();
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_publickey($setup->{user}, $rsa_pub_key, $rsa_priv_key)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("RSA publickey authentication failed: [$err_name] ($err_code) $err_str");
+      }
+
+      $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);
+}
+
 1;


=====================================
tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm
=====================================
@@ -467,6 +467,11 @@ my $TESTS = {
     order => ++$order,
     test_class => [qw(forking bug mod_tls)],
   },
+
+  sql_user_info_no_suppl_groups_issue1830 => {
+    order => ++$order,
+    test_class => [qw(forking bug rootprivs)],
+  },
 };
 
 sub new {
@@ -15764,4 +15769,173 @@ EOC
   test_cleanup($setup->{log_file}, $ex);
 }
 
+sub sql_user_info_no_suppl_groups_issue1830 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'sqlite');
+
+  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
+
+  # Build up sqlite3 command to create users, groups tables and populate them
+  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
+
+  if (open(my $fh, "> $db_script")) {
+    print $fh <<EOS;
+CREATE TABLE users (
+  userid TEXT,
+  passwd TEXT,
+  uid INTEGER,
+  gid INTEGER,
+  homedir TEXT,
+  shell TEXT
+);
+INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$setup->{user}', '$setup->{passwd}', $setup->{uid}, $setup->{gid}, '$setup->{home_dir}', '/bin/bash');
+
+CREATE TABLE groups (
+  groupname TEXT,
+  gid INTEGER,
+  members TEXT
+);
+INSERT INTO groups (groupname, gid, members) VALUES ('$setup->{group}', $setup->{gid}, '$setup->{user}');
+EOS
+
+    unless (close($fh)) {
+      die("Can't write $db_script: $!");
+    }
+
+  } else {
+    die("Can't open $db_script: $!");
+  }
+
+  my $cmd = "sqlite3 $db_file < $db_script";
+  build_db($cmd, $db_script);
+
+  # Make sure that, if we're running as root, the database file has
+  # the permissions/privs set for use by proftpd
+  if ($< == 0) {
+    unless (chmod(0666, $db_file)) {
+      die("Can't set perms on $db_file to 0666: $!");
+    }
+  }
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'auth:20 sql:20',
+
+    # Required for logging the expected message
+    DebugLevel => 5,
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_sql.c' => {
+        AuthOrder => 'mod_sql.c',
+
+        SQLAuthenticate => 'users',
+        SQLAuthTypes => 'plaintext',
+        SQLBackend => 'sqlite3',
+        SQLConnectInfo => $db_file,
+        SQLLogFile => $setup->{log_file},
+
+        # Set these, so that our lower UID/GID will be used
+        SQLMinUserUID => 100,
+        SQLMinUserGID => 100,
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      sleep(2);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($setup->{user}, $setup->{passwd});
+
+      my $resp_msgs = $client->response_msgs();
+      my $nmsgs = scalar(@$resp_msgs);
+
+      my $expected = 1;
+      $self->assert($expected == $nmsgs,
+        test_msg("Expected $expected, got $nmsgs"));
+
+      $expected = "User $setup->{user} logged in";
+      $self->assert($expected eq $resp_msgs->[0],
+        test_msg("Expected response '$expected', got '$resp_msgs->[0]'"));
+
+      $client->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);
+
+  eval {
+    if (open(my $fh, "< $setup->{log_file}")) {
+      my $ok = 0;
+
+      while (my $line = <$fh>) {
+        chomp($line);
+
+        if ($ENV{TEST_VERBOSE}) {
+          print STDERR "# $line\n";
+        }
+
+        if ($line =~ /no supplemental groups found for user '$setup->{user}', using primary group/) {
+          $ok = 1;
+          last;
+        }
+      }
+
+      close($fh);
+
+      $self->assert($ok, test_msg("Did not see expected log message"));
+
+    } else {
+      die("Can't read $setup->{log_file}: $!");
+    }
+  };
+  if ($@) {
+    $ex = $@ unless $ex;
+  }
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 1;



View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/91f64bbe1f6597e058f4880c530d5c24c278ee64

-- 
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/91f64bbe1f6597e058f4880c530d5c24c278ee64
You're receiving this email because of your account on salsa.debian.org.




More information about the Pkg-proftpd-maintainers mailing list