[Git][debian-proftpd-team/proftpd][upstream] New upstream version 1.3.7d+dfsg

Hilmar Preuße (@hilmar-guest) gitlab at salsa.debian.org
Sun Apr 24 22:49:40 BST 2022



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


Commits:
38d1167f by Hilmar Preusse at 2022-04-24T23:29:24+02:00
New upstream version 1.3.7d+dfsg
- - - - -


21 changed files:

- .cirrus.yml
- .github/workflows/ci.yml
- .github/workflows/rpm.yml
- NEWS
- README.md
- RELEASE_NOTES
- contrib/dist/rpm/proftpd.spec
- include/inet.h
- include/version.h
- modules/mod_auth_file.c
- modules/mod_core.c
- modules/mod_ls.c
- modules/mod_xfer.c
- src/bindings.c
- src/inet.c
- src/random.c
- tests/t/lib/ProFTPD/TestSuite/Utils.pm
- tests/t/lib/ProFTPD/Tests/Commands/NLST.pm
- tests/t/lib/ProFTPD/Tests/Config/AllowForeignAddress.pm
- tests/t/lib/ProFTPD/Tests/Config/PassivePorts.pm
- tests/t/lib/ProFTPD/Tests/Modules/mod_auth_file.pm


Changes:

=====================================
.cirrus.yml
=====================================
@@ -3,11 +3,8 @@ task:
   name: build
   freebsd_instance:
     matrix:
-      # NOTE: Currently disabled due to this error:
-      #  pkg: repository FreeBSD contains packages for wrong OS version: FreeBSD:13:amd64
-      # image_family: freebsd-13-0-snap
-      image_family: freebsd-12-1
-      image_family: freebsd-11-3-snap
+      image_family: freebsd-13-0
+      image_family: freebsd-12-2
 
   env:
     CIRRUS_CLONE_DEPTH: 10
@@ -25,7 +22,7 @@ task:
     - pkg install -y libmemcached
     - pkg install -y mysql57-client
     - pkg install -y ncurses
-    - pkg install -y openldap-client
+    - pkg install -y openldap24-client
     - pkg install -y openssl
     - pkg install -y pcre
     - pkg install -y postgresql10-client


=====================================
.github/workflows/ci.yml
=====================================
@@ -3,11 +3,9 @@ name: CI
 on:
   push:
     branches:
-      - master
       - 1.3.7
   pull_request:
     branches:
-      - master
       - 1.3.7
 
 jobs:
@@ -30,7 +28,7 @@ jobs:
     environment: CI
 
     env:
-      PACKAGE_VERSION: 1.3.7b
+      PACKAGE_VERSION: 1.3.7d
       REDIS_HOST: redis
 
     strategy:


=====================================
.github/workflows/rpm.yml
=====================================
@@ -3,11 +3,9 @@ name: RPM
 on:
   push:
     branches:
-      - master
       - 1.3.7
   pull_request:
     branches:
-      - master
       - 1.3.7
 
 jobs:
@@ -17,8 +15,7 @@ jobs:
     strategy:
       matrix:
         container:
-          - centos:7
-          - centos:8
+          - almalinux:8
 
     container: ${{ matrix.container }}
 
@@ -26,20 +23,13 @@ jobs:
       - name: Checkout source code
         uses: actions/checkout at v2
 
-      - name: Configure Centos 7 repos
-        if: ${{ matrix.container == 'centos:7' }}
+      - name: Configure AlmaLinux 8 repos
+        if: ${{ matrix.container == 'almalinux:8' }}
         run: |
           # Need to add other repos for e.g. libsodium
-          yum install -y dnf-plugins-core epel-release
-          # for mod_wrap
-          yum install -y libnsl2-devel tcp_wrappers-devel
-
-      - name: Configure Centos 8 repos
-        if: ${{ matrix.container == 'centos:8' }}
-        run: |
-          # Need to add other repos for e.g. libsodium
-          yum install -y dnf-plugins-core epel-release
-          yum config-manager --set-enabled powertools
+          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://pkgs.dyn.su/el8/extras/x86_64/tcp_wrappers-libs-7.6-77.el8.x86_64.rpm https://pkgs.dyn.su/el8/extras/x86_64/tcp_wrappers-devel-7.6-77.el8.x86_64.rpm
 


=====================================
NEWS
=====================================
@@ -15,6 +15,23 @@
   where `N' is the issue number.
 -----------------------------------------------------------------------------
 
+1.3.7d - Released 23-Apr-2022
+--------------------------------
+- Issue 1321 - Crash with long lines in AuthGroupFile due to large realloc(3).
+- Issue 1325 - NLST does not behave consistently for relative paths.
+- Issue 1346 - Implement AllowForeignAddress class matching for passive data
+  transfers.
+- Bug 4467 - DeleteAbortedStores removes successfully transferred files
+  unexpectedly.
+- Issue 1401 - Keepalive socket options should be set using IPPROTO_TCP, not
+  SOL_SOCKET.
+- Issue 1402 - TCP keepalive SocketOptions should apply to control as well as
+  data connection.
+- Issue 1396 - ProFTPD always uses the same PassivePorts port for first
+  transfer.
+- Issue 1369 - Name-based virtual hosts not working as expected after upgrade
+  from 1.3.7a to 1.3.7b.
+
 1.3.7c - Released 29-Aug-2021
 --------------------------------
 - Issue 1273 - Improve mod_tls log messages for unsupported older TLS protocol


=====================================
README.md
=====================================
@@ -7,7 +7,7 @@
 [![Coverage Status](https://coveralls.io/repos/proftpd/proftpd/badge.svg?branch=master&service=github)](https://coveralls.io/github/proftpd/proftpd?branch=master)
 [![Coverity Scan Status](https://scan.coverity.com/projects/198/badge.svg)](https://scan.coverity.com/projects/198)
 [![C/C++ Language Grade](https://img.shields.io/lgtm/grade/cpp/g/proftpd/proftpd.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/proftpd/proftpd/context:cpp)
-[![Release](https://img.shields.io/badge/release-1.3.7b-brightgreen)](https://github.com/proftpd/proftpd/releases/latest)
+[![Release](https://img.shields.io/badge/release-1.3.7d-brightgreen)](https://github.com/proftpd/proftpd/releases/latest)
 [![License](https://img.shields.io/badge/license-GPL-brightgreen.svg)](https://img.shields.io/badge/license-GPL-brightgreen.svg)
 
 ## Introduction


=====================================
RELEASE_NOTES
=====================================
@@ -6,6 +6,14 @@ 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.7d
+---------
+
+  + Improved consistency/support for name-based virtual hosts.
+
+  + Fixed crashes due to very long lines in AuthGroupFiles (Issue #1321)..
+
+
 1.3.7c
 ---------
 


=====================================
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.7c
+%global proftpd_version			1.3.7d
 
 # 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			5
+%global release_version			6
 
 %if %(echo %{proftpd_version} | grep rc >/dev/null 2>&1 && echo 1 || echo 0)
 %global rpm_version %(echo %{proftpd_version} | sed -e 's/rc.*//')


=====================================
include/inet.h
=====================================
@@ -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-2021 The ProFTPD Project team
+ * Copyright (c) 2001-2022 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
@@ -149,6 +149,7 @@ int pr_inet_set_async(pool *, conn_t *);
 int pr_inet_set_block(pool *, conn_t *);
 int pr_inet_set_nonblock(pool *, conn_t *);
 int pr_inet_set_proto_cork(int, int);
+int pr_inet_set_proto_keepalive(pool *, conn_t *, struct tcp_keepalive *);
 int pr_inet_set_proto_nodelay(pool *, conn_t *, int);
 int pr_inet_set_proto_opts(pool *, conn_t *, int, int, int, int);
 int pr_inet_set_socket_opts(pool *, conn_t *, int, int, struct tcp_keepalive *);


=====================================
include/version.h
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2020-2021 The ProFTPD Project team
+ * Copyright (c) 2020-2022 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		0x0001030708
-#define PROFTPD_VERSION_TEXT		"1.3.7c"
+#define PROFTPD_VERSION_NUMBER		0x0001030709
+#define PROFTPD_VERSION_TEXT		"1.3.7d"
 
 /* Module API version */
 #define PR_MODULE_API_VERSION		0x20


=====================================
modules/mod_auth_file.c
=====================================
@@ -1,7 +1,7 @@
 /*
  * ProFTPD: mod_auth_file - file-based authentication module that supports
  *                          restrictions on the file contents
- * Copyright (c) 2002-2021 The ProFTPD Project team
+ * Copyright (c) 2002-2022 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
@@ -325,45 +325,74 @@ static struct passwd *af_getpasswd(const char *buf, unsigned int lineno) {
 #define NGRPFIELDS      4
 
 static char *grpbuf = NULL;
+static size_t grpbufsz = 0;
 static struct group grent;
 static char *grpfields[NGRPFIELDS];
 static char *members[MAXMEMBERS+1];
 
-static char *af_getgrentline(char **buf, int *buflen, pr_fh_t *fh,
+static char *af_getgrentline(char **buf, size_t *bufsz, pr_fh_t *fh,
     unsigned int *lineno) {
-  char *cp = *buf;
+  char *ptr, *res;
+  size_t original_bufsz, buflen;
 
-  while (pr_fsio_gets(cp, (*buflen) - (cp - *buf), fh) != NULL) {
-    pr_signals_handle();
+  original_bufsz = *bufsz;
+  buflen = *bufsz;
 
-    (*lineno)++;
+  /* Try to keep our unfilled buffer zeroed out, so that strlen(3) et al
+   * work as expected.
+   */
+  memset(*buf, '\0', *bufsz);
+
+  ptr = *buf;
+  res = pr_fsio_gets(ptr, buflen, fh);
+  while (res != NULL) {
+    pr_signals_handle();
 
     /* Is this a full line? */
-    if (strchr(cp, '\n')) {
+    if (strchr(*buf, '\n') != NULL) {
+      pr_trace_msg(trace_channel, 25,
+        "found LF, returning line: '%s' (%lu bytes)", *buf,
+        (unsigned long) strlen(*buf));
+      (*lineno)++;
       return *buf;
     }
 
-    /* No -- allocate a larger buffer, doubling buflen. */
-    *buflen += *buflen;
-
+    /* No -- allocate a larger buffer.  Note that doubling the buflen
+     * each time may cause issues; fgetgrent(3) would increment the
+     * allocated buffer by the original buffer length each time.  So we
+     * do the same (Issue #1321).
+     */
     {
+      size_t new_bufsz;
       char *new_buf;
 
-      new_buf = realloc(*buf, *buflen);
+      pr_trace_msg(trace_channel, 25, "getgrentline() buffer (%lu bytes): "
+        "'%.*s'", (unsigned long) *bufsz, (int) *bufsz, *buf);
+
+      pr_trace_msg(trace_channel, 19,
+        "no LF found in group line, increasing buffer (%lu bytes) by %lu bytes",
+        (unsigned long) *bufsz, (unsigned long) original_bufsz);
+      new_bufsz = *bufsz + original_bufsz;
+
+      new_buf = realloc(*buf, new_bufsz);
       if (new_buf == NULL) {
         break;
       }
 
+      ptr = new_buf + *bufsz;
       *buf = new_buf;
+      *bufsz = new_bufsz;
+      buflen = original_bufsz;
+
+      memset(ptr, '\0', buflen);
     }
 
-    cp = *buf + (cp - *buf);
-    cp = strchr(cp, '\0');
+    res = pr_fsio_gets(ptr, buflen, fh);
   }
 
   free(*buf);
   *buf = NULL;
-  *buflen = 0;
+  *bufsz = 0;
 
   return NULL;
 }
@@ -394,22 +423,29 @@ static struct group *af_getgrp(const char *buf, unsigned int lineno) {
 
   i = strlen(buf) + 1;
 
-  if (!grpbuf) {
+  if (grpbuf == NULL) {
+    grpbufsz = i;
     grpbuf = malloc(i);
 
-  } else {
+  } else if (grpbufsz < (size_t) i) {
     char *new_buf;
 
+    pr_trace_msg(trace_channel, 19,
+      "parsing group line '%s' (%lu bytes), allocating %lu bytes via "
+      "realloc(3)", buf, (unsigned long) i, (unsigned long) i);
+
     new_buf = realloc(grpbuf, i);
     if (new_buf == NULL) {
       return NULL;
     }
 
     grpbuf = new_buf;
+    grpbufsz = i;
   }
 
-  if (!grpbuf)
+  if (grpbuf == NULL) {
     return NULL;
+  }
 
   sstrncpy(grpbuf, buf, i);
 
@@ -517,7 +553,16 @@ static struct group *af_getgrent(pool *p) {
 
   while (TRUE) {
     char *cp = NULL, *buf = NULL;
-    int buflen = PR_TUNABLE_BUFFER_SIZE;
+    size_t buflen;
+
+    buflen = PR_TUNABLE_BUFFER_SIZE;
+
+    if (af_group_file->af_file_fh->fh_iosz > 0) {
+      /* This aligns our group(5) buffer with the preferred filesystem read
+       * block size.
+       */
+      buflen = af_group_file->af_file_fh->fh_iosz;
+    }
 
     pr_signals_handle();
 
@@ -526,6 +571,11 @@ static struct group *af_getgrent(pool *p) {
       pr_log_pri(PR_LOG_ALERT, "Out of memory!");
       _exit(1);
     }
+
+    pr_trace_msg(trace_channel, 19,
+      "getgrent(3): allocated buffer %p (%lu bytes)", buf,
+      (unsigned long) buflen);
+
     grp = NULL;
 
     while (af_getgrentline(&buf, &buflen, af_group_file->af_file_fh,
@@ -629,6 +679,12 @@ static int af_setgrent(pool *p) {
         pbuf->remaining = pbuf->buflen;
       }
 
+      if (grpbuf != NULL) {
+        free(grpbuf);
+        grpbuf = NULL;
+      }
+      grpbufsz = 0;
+
       return 0;
     }
 


=====================================
modules/mod_core.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-2020 The ProFTPD Project team
+ * Copyright (c) 2001-2022 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
@@ -6780,6 +6780,19 @@ static int core_sess_init(void) {
 
   init_auth();
 
+  /* Enable any TCP keepalive options on the control connection (Issue #1402).
+   * Note that ctrl conns do not have listening fds, but this function uses
+   * that fd (due to compatibility with data conns), so we temporarily assign
+   * that struct member.
+   */
+  session.c->listen_fd = session.c->wfd;
+  if (pr_inet_set_proto_keepalive(session.pool, session.c,
+      main_server->tcp_keepalive) < 0) {
+    pr_log_debug(DEBUG9, "error setting ctrl conn TCP keepalive: %s",
+      strerror(errno));
+  }
+  session.c->listen_fd = -1;
+
   c = find_config(main_server->conf, CONF_PARAM, "MultilineRFC2228", FALSE);
   if (c != NULL) {
     session.multiline_rfc2228 = *((int *) c->argv[0]);


=====================================
modules/mod_ls.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-2020 The ProFTPD Project
+ * Copyright (c) 2001-2021 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
@@ -3163,10 +3163,6 @@ MODRET ls_nlst(cmd_rec *cmd) {
       p = *path;
       path++;
 
-      if (*p == '.' && (!opt_A || is_dotdir(p))) {
-        continue;
-      }
-
       pr_fs_clear_cache2(p);
       if (pr_fsio_stat(p, &st) == 0) {
         /* If it's a directory... */


=====================================
modules/mod_xfer.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-2020 The ProFTPD Project team
+ * Copyright (c) 2001-2022 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
@@ -1014,9 +1014,8 @@ static void stor_abort(pool *p) {
         } 
       }
     }
-  }
 
-  if (session.xfer.path != NULL) {
+  } else if (session.xfer.path != NULL) {
     if (delete_stores != NULL &&
         *delete_stores == TRUE) {
       pr_log_debug(DEBUG5, "removing aborted file '%s'", session.xfer.path);
@@ -2492,6 +2491,16 @@ MODRET xfer_pre_retr(cmd_rec *cmd) {
 
   pr_fs_clear_cache2(decoded_path);
   dir = dir_realpath(cmd->tmp_pool, decoded_path);
+  if (dir == NULL) {
+    /* Try using dir_best_path(), as xfer_pre_stor() does.
+     *
+     * Without this fallback, certain use cases (such as SFTP downloads using
+     * mod_sftp + mod_vroot) fail unexpectedly, with misleading
+     * "denied by <Limit> configuration" errors.
+     */
+    dir = dir_best_path(cmd->tmp_pool, decoded_path);
+  }
+
   if (dir == NULL ||
       !dir_check(cmd->tmp_pool, cmd, cmd->group, dir, NULL)) {
     int xerrno = errno;


=====================================
src/bindings.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2001-2020 The ProFTPD Project team
+ * Copyright (c) 2001-2022 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
@@ -58,9 +58,10 @@ static void server_cleanup_cb(void *conn) {
 /* The hashing function for the hash table of bindings.  This algorithm
  * is stolen from Apache's http_vhost.c
  */
-static unsigned int ipbind_hash_addr(const pr_netaddr_t *addr) {
+static unsigned int ipbind_hash_addr(const pr_netaddr_t *addr,
+    unsigned int port) {
   size_t offset;
-  unsigned int key;
+  unsigned int key, idx;
 
   offset = pr_netaddr_get_inaddr_len(addr);
 
@@ -70,8 +71,14 @@ static unsigned int ipbind_hash_addr(const pr_netaddr_t *addr) {
    */
   key = *(unsigned *) ((char *) pr_netaddr_get_inaddr(addr) + offset - 4);
 
+  /* Add in the port number, to give better spread of key values when many
+   * different port values are used.
+   */
+  key += port;
+
   key ^= (key >> 16);
-  return ((key >> 8) ^ key) % PR_BINDINGS_TABLE_SIZE;
+  idx = ((key >> 8) ^ key) % PR_BINDINGS_TABLE_SIZE;
+  return idx;
 }
 
 static pool *listening_conn_pool = NULL;
@@ -88,7 +95,7 @@ struct listener_rec {
 
 conn_t *pr_ipbind_get_listening_conn(server_rec *server,
     const pr_netaddr_t *addr, unsigned int port) {
-  conn_t *l;
+  conn_t *l, *sl;
   pool *p;
   struct listener_rec *lr;
 
@@ -152,9 +159,15 @@ conn_t *pr_ipbind_get_listening_conn(server_rec *server,
     return NULL;
   }
 
-  /* Inform any interested listeners that this socket was opened. */
+  /* Inform any interested listeners that this socket was opened.  In order
+   * to convey the discovered conn_t `l` to the event listener, we set it
+   * on the server `s` temporarily.
+   */
+  sl = server->listen;
+  server->listen = l;
   pr_inet_generate_socket_event("core.ctrl-listen", server, l->local_addr,
     l->listen_fd);
+  server->listen = sl;
 
   lr = pcalloc(p, sizeof(struct listener_rec));
   lr->pool = p;
@@ -269,13 +282,36 @@ int pr_ipbind_add_binds(server_rec *serv) {
         return -1;
       }
 
-      PR_CREATE_IPBIND(serv, addr, serv->ServerPort);
-      PR_OPEN_IPBIND(addr, serv->ServerPort, listen_conn, FALSE, FALSE, TRUE);
+      res = pr_ipbind_create(serv, addr, serv->ServerPort);
+      if (res < 0) {
+        pr_log_pri(PR_LOG_NOTICE,
+          "%s:%d: notice: unable to create ipbind '%s#%u': %s", __FILE__,
+          __LINE__, serv->ServerAddress, serv->ServerPort, strerror(errno));
+      }
+
+      res = pr_ipbind_open(addr, serv->ServerPort, listen_conn, FALSE, FALSE,
+        TRUE);
+      if (res < 0) {
+        pr_log_pri(PR_LOG_NOTICE,
+          "%s:%d: notice: unable to open ipbind '%s': %s", __FILE__, __LINE__,
+          pr_netaddr_get_ipstr(addr), strerror(errno));
+      }
 
     } else {
+      res = pr_ipbind_create(serv, addr, serv->ServerPort);
+      if (res < 0) {
+        pr_log_pri(PR_LOG_NOTICE,
+          "%s:%d: notice: unable to create ipbind '%s#%u': %s", __FILE__,
+          __LINE__, serv->ServerAddress, serv->ServerPort, strerror(errno));
+      }
 
-      PR_CREATE_IPBIND(serv, addr, serv->ServerPort);
-      PR_OPEN_IPBIND(addr, serv->ServerPort, serv->listen, FALSE, FALSE, TRUE);
+      res = pr_ipbind_open(addr, serv->ServerPort, serv->listen, FALSE, FALSE,
+        TRUE);
+      if (res < 0) {
+        pr_log_pri(PR_LOG_NOTICE,
+          "%s:%d: notice: unable to open ipbind '%s': %s", __FILE__, __LINE__,
+          pr_netaddr_get_ipstr(addr), strerror(errno));
+      }
     }
 
     c = find_config_next(c, c->next, CONF_PARAM, "_bind_", FALSE);
@@ -292,7 +328,7 @@ int pr_ipbind_close(const pr_netaddr_t *addr, unsigned int port,
     pr_ipbind_t *ipbind = NULL;
     unsigned char have_ipbind = FALSE;
 
-    i = ipbind_hash_addr(addr);
+    i = ipbind_hash_addr(addr, port);
 
     if (ipbind_table[i] == NULL) {
       pr_log_pri(PR_LOG_NOTICE, "notice: no ipbind found for %s:%d",
@@ -302,6 +338,8 @@ int pr_ipbind_close(const pr_netaddr_t *addr, unsigned int port,
     }
 
     for (ipbind = ipbind_table[i]; ipbind; ipbind = ipbind->ib_next) {
+      pr_signals_handle();
+
       if (pr_netaddr_cmp(ipbind->ib_addr, addr) == 0 &&
           (!ipbind->ib_port || ipbind->ib_port == port)) {
         have_ipbind = TRUE;
@@ -309,7 +347,7 @@ int pr_ipbind_close(const pr_netaddr_t *addr, unsigned int port,
       }
     }
 
-    if (!have_ipbind) {
+    if (have_ipbind == FALSE) {
       pr_log_pri(PR_LOG_NOTICE, "notice: no ipbind found for %s:%d",
         pr_netaddr_get_ipstr(addr), port);
       errno = ENOENT;
@@ -317,7 +355,7 @@ int pr_ipbind_close(const pr_netaddr_t *addr, unsigned int port,
     }
 
     /* If already closed, exit now. */
-    if (!ipbind->ib_isactive) {
+    if (ipbind->ib_isactive == FALSE) {
       errno = EPERM;
       return -1;
     }
@@ -331,7 +369,8 @@ int pr_ipbind_close(const pr_netaddr_t *addr, unsigned int port,
      * for the master daemon in inetd mode, in which case virtual servers
      * can't be shutdown via ftpdctl, anyway.
      */
-    if (SocketBindTight && ipbind->ib_listener != NULL) {
+    if (SocketBindTight &&
+        ipbind->ib_listener != NULL) {
       pr_inet_close(ipbind->ib_server->pool, ipbind->ib_listener);
       ipbind->ib_listener = ipbind->ib_server->listen = NULL;
     }
@@ -344,14 +383,18 @@ int pr_ipbind_close(const pr_netaddr_t *addr, unsigned int port,
      */
     ipbind->ib_isactive = FALSE;
 
-    if (close_namebinds && ipbind->ib_namebinds) {
+    if (close_namebinds == TRUE &&
+        ipbind->ib_namebinds != NULL) {
       register unsigned int j = 0;
       pr_namebind_t **namebinds = NULL;
 
       namebinds = (pr_namebind_t **) ipbind->ib_namebinds->elts;
       for (j = 0; j < ipbind->ib_namebinds->nelts; j++) {
-        pr_namebind_t *nb = namebinds[j];
+        pr_namebind_t *nb;
 
+        pr_signals_handle();
+
+        nb = namebinds[j];
         if (pr_namebind_close(nb->nb_name, nb->nb_server->addr) < 0) {
           pr_trace_msg(trace_channel, 7,
             "notice: error closing namebind '%s' for address %s: %s",
@@ -366,6 +409,8 @@ int pr_ipbind_close(const pr_netaddr_t *addr, unsigned int port,
     for (i = 0; i < PR_BINDINGS_TABLE_SIZE; i++) {
       pr_ipbind_t *ipbind = NULL;
 
+      pr_signals_handle();
+
       for (ipbind = ipbind_table[i]; ipbind; ipbind = ipbind->ib_next) {
         if (SocketBindTight &&
             ipbind->ib_listener != NULL) {
@@ -386,8 +431,11 @@ int pr_ipbind_close(const pr_netaddr_t *addr, unsigned int port,
 
           namebinds = (pr_namebind_t **) ipbind->ib_namebinds->elts;
           for (j = 0; j < ipbind->ib_namebinds->nelts; j++) {
-            pr_namebind_t *nb = namebinds[j];
+            pr_namebind_t *nb;
 
+            pr_signals_handle();
+
+            nb = namebinds[j];
             if (pr_namebind_close(nb->nb_name, nb->nb_server->addr) < 0) {
               pr_trace_msg(trace_channel, 7,
                 "notice: error closing namebind '%s' for address %s: %s",
@@ -408,18 +456,20 @@ int pr_ipbind_close_listeners(void) {
   conn_t **listeners;
   register unsigned int i = 0;
 
-  if (!listener_list ||
-      listener_list->nelts == 0)
+  if (listener_list == NULL ||
+      listener_list->nelts == 0) {
     return 0;
+  }
 
   listeners = listener_list->elts;
   for (i = 0; i < listener_list->nelts; i++) {
-    conn_t *listener = listeners[i];
+    conn_t *listener;
 
     pr_signals_handle();
 
+    listener = listeners[i];
     if (listener->listen_fd != -1) {
-      close(listener->listen_fd);
+      (void) close(listener->listen_fd);
       listener->listen_fd = -1;
     }
   }
@@ -441,12 +491,14 @@ int pr_ipbind_create(server_rec *server, const pr_netaddr_t *addr,
   /* Ensure the ipbind table has been initialized. */
   init_ipbind_table();
 
-  i = ipbind_hash_addr(addr);
+  i = ipbind_hash_addr(addr, port);
   pr_trace_msg(trace_channel, 29, "hashed address '%s' to index %u",
     pr_netaddr_get_ipstr(addr), i);
 
   /* Make sure the address is not already in use */
   for (ipbind = ipbind_table[i]; ipbind; ipbind = ipbind->ib_next) {
+    pr_signals_handle();
+
     if (pr_netaddr_cmp(ipbind->ib_addr, addr) == 0 &&
         ipbind->ib_port == port) {
       existing = ipbind;
@@ -468,14 +520,14 @@ int pr_ipbind_create(server_rec *server, const pr_netaddr_t *addr,
     if (c == NULL) {
       pr_log_pri(PR_LOG_WARNING, "notice: '%s' (%s:%u) already bound to '%s'",
         server->ServerName, pr_netaddr_get_ipstr(addr), port,
-        ipbind->ib_server->ServerName);
+        existing->ib_server->ServerName);
       errno = EADDRINUSE;
       return -1;
     }
 
     pr_log_debug(DEBUG9, "notice: '%s' (%s:%u) already bound to '%s'",
       server->ServerName, pr_netaddr_get_ipstr(addr), port,
-      ipbind->ib_server->ServerName);
+      existing->ib_server->ServerName);
   }
 
   if (binding_pool == NULL) {
@@ -492,19 +544,27 @@ int pr_ipbind_create(server_rec *server, const pr_netaddr_t *addr,
   ipbind->ib_islocalhost = FALSE;
   ipbind->ib_isactive = FALSE;
 
-  pr_trace_msg(trace_channel, 8, "created ipbind %p for %s#%u, server %p",
+  pr_trace_msg(trace_channel, 8, "created ipbind %p for %s#%u, server %p (%s)",
     ipbind, pr_netaddr_get_ipstr(ipbind->ib_addr), ipbind->ib_port,
-    ipbind->ib_server);
+    ipbind->ib_server, ipbind->ib_server->ServerName);
 
-  /* Add the ipbind to the table. */
-  if (ipbind_table[i] != NULL) {
+  /* Add the ipbind to the table, maintaining insertion order. */
+  if (existing != NULL) {
     pr_trace_msg(trace_channel, 19,
       "found existing ipbind %p (server %p) at index %u in iptable table, "
-      "adding to %p", ipbind_table[i], ipbind_table[i]->ib_server, i, ipbind);
-    ipbind->ib_next = ipbind_table[i];
+      "adding to %p", existing, existing->ib_server, i, existing);
+    if (existing->ib_next != NULL) {
+      ipbind->ib_next = existing->ib_next;
+    }
+    existing->ib_next = ipbind;
+
+  } else {
+    if (ipbind_table[i] != NULL) {
+      ipbind->ib_next = ipbind_table[i];
+    }
+    ipbind_table[i] = ipbind;
   }
 
-  ipbind_table[i] = ipbind;
   return 0;
 }
 
@@ -559,13 +619,13 @@ pr_ipbind_t *pr_ipbind_find(const pr_netaddr_t *addr, unsigned int port,
   /* Ensure the ipbind table has been initialized. */
   init_ipbind_table();
 
-  i = ipbind_hash_addr(addr);
+  i = ipbind_hash_addr(addr, port);
 
   for (ipbind = ipbind_table[i]; ipbind; ipbind = ipbind->ib_next) {
     pr_signals_handle();
 
-    if (skip_inactive &&
-        !ipbind->ib_isactive) {
+    if (ipbind->ib_isactive == FALSE &&
+        skip_inactive == TRUE) {
       continue;
     }
 
@@ -585,10 +645,9 @@ pr_ipbind_t *pr_ipbind_find(const pr_netaddr_t *addr, unsigned int port,
 pr_ipbind_t *pr_ipbind_get(pr_ipbind_t *prev) {
   static unsigned int i = 0;
 
-  if (prev) {
-
+  if (prev != NULL) {
     /* If there's another ipbind in this chain, simply return that. */
-    if (prev->ib_next) {
+    if (prev->ib_next != NULL) {
       return prev->ib_next;
     }
 
@@ -613,6 +672,8 @@ pr_ipbind_t *pr_ipbind_get(pr_ipbind_t *prev) {
 
   /* Search for the next non-empty chain in the table. */
   for (; i < PR_BINDINGS_TABLE_SIZE; i++) {
+    pr_signals_handle();
+
     if (ipbind_table[i] != NULL) {
       return ipbind_table[i];
     }
@@ -633,6 +694,8 @@ server_rec *pr_ipbind_get_server(const pr_netaddr_t *addr, unsigned int port) {
    */
   ipbind = pr_ipbind_find(addr, port, TRUE);
   if (ipbind != NULL) {
+    pr_log_debug(DEBUG7, "matching vhost found for %s#%u, using '%s'",
+      pr_netaddr_get_ipstr(addr), port, ipbind->ib_server->ServerName);
     return ipbind->ib_server;
   }
 
@@ -750,9 +813,11 @@ int pr_ipbind_listen(fd_set *readfds) {
     pr_ipbind_t *ipbind = NULL;
 
     for (ipbind = ipbind_table[i]; ipbind; ipbind = ipbind->ib_next) {
+      pr_signals_handle();
+
       /* Skip inactive bindings, but only if SocketBindTight is in effect. */
       if (SocketBindTight &&
-          !ipbind->ib_isactive) {
+          ipbind->ib_isactive == FALSE) {
         continue;
       }
 
@@ -774,8 +839,9 @@ int pr_ipbind_listen(fd_set *readfds) {
 
         if (ipbind->ib_listener->mode == CM_LISTEN) {
           FD_SET(ipbind->ib_listener->listen_fd, readfds);
-          if (ipbind->ib_listener->listen_fd > maxfd)
+          if (ipbind->ib_listener->listen_fd > maxfd) {
             maxfd = ipbind->ib_listener->listen_fd;
+          }
 
           /* Add this to the listener list as well. */
           *((conn_t **) push_array(listener_list)) = ipbind->ib_listener;
@@ -790,7 +856,6 @@ int pr_ipbind_listen(fd_set *readfds) {
 int pr_ipbind_open(const pr_netaddr_t *addr, unsigned int port,
     conn_t *listen_conn, unsigned char isdefault, unsigned char islocalhost,
     unsigned char open_namebinds) {
-  int res = 0;
   pr_ipbind_t *ipbind = NULL;
 
   if (addr == NULL) {
@@ -805,8 +870,9 @@ int pr_ipbind_open(const pr_netaddr_t *addr, unsigned int port,
     return -1;
   }
 
-  if (listen_conn)
+  if (listen_conn != NULL) {
     listen_conn->next = NULL;
+  }
 
   ipbind->ib_listener = ipbind->ib_server->listen = listen_conn;
   ipbind->ib_listener = listen_conn;
@@ -822,11 +888,13 @@ int pr_ipbind_open(const pr_netaddr_t *addr, unsigned int port,
    * - It's the default server (specified via the DefaultServer directive)
    * - It handles connections to the loopback interface
    */
-  if (isdefault)
+  if (isdefault) {
     ipbind_default_server = ipbind;
+  }
 
-  if (islocalhost)
+  if (islocalhost) {
     ipbind_localhost_server = ipbind;
+  }
 
   /* If requested, look for any namebinds for this ipbind, and open them. */
   if (open_namebinds &&
@@ -840,8 +908,12 @@ int pr_ipbind_open(const pr_netaddr_t *addr, unsigned int port,
      */
     namebinds = (pr_namebind_t **) ipbind->ib_namebinds->elts;
     for (i = 0; i < ipbind->ib_namebinds->nelts; i++) {
+      int res;
       pr_namebind_t *nb = namebinds[i];
 
+      pr_signals_handle();
+
+      nb = namebinds[i];
       res = pr_namebind_open(nb->nb_name, nb->nb_server->addr,
         nb->nb_server_port);
       if (res < 0) {
@@ -942,6 +1014,8 @@ int pr_namebind_create(server_rec *server, const char *name,
 
     /* See if there is already a namebind for the given name. */
     for (i = 0; i < ipbind->ib_namebinds->nelts; i++) {
+      pr_signals_handle();
+
       namebind = namebinds[i];
       if (namebind != NULL &&
           namebind->nb_name != NULL) {
@@ -972,8 +1046,9 @@ int pr_namebind_create(server_rec *server, const char *name,
   }
 
   pr_trace_msg(trace_channel, 8,
-    "created namebind '%s' for %s#%u, server %p", name,
-    pr_netaddr_get_ipstr(server->addr), server->ServerPort, server);
+    "created namebind '%s' for %s#%u, server '%s' [%p]", name,
+    pr_netaddr_get_ipstr(server->addr), server->ServerPort, server->ServerName,
+    server);
 
   /* The given server should already have the following populated:
    *
@@ -988,8 +1063,7 @@ int pr_namebind_create(server_rec *server, const char *name,
 
 pr_namebind_t *pr_namebind_find(const char *name, const pr_netaddr_t *addr,
     unsigned int port, unsigned char skip_inactive) {
-  pr_ipbind_t *ipbind = NULL;
-  pr_namebind_t *namebind = NULL;
+  pr_ipbind_t *ipbind = NULL, *iter;
 
   if (name == NULL ||
       addr == NULL) {
@@ -1026,7 +1100,8 @@ pr_namebind_t *pr_namebind_find(const char *name, const pr_netaddr_t *addr,
 #endif /* PR_USE_IPV6 */
   } else {
     pr_trace_msg(trace_channel, 17,
-      "found ipbind %p (server %p) for %s#%u", ipbind, ipbind->ib_server,
+      "found ipbind %p (server '%s' [%p]) for %s#%u", ipbind,
+      ipbind->ib_server->ServerName, ipbind->ib_server,
       pr_netaddr_get_ipstr(addr), port);
   }
 
@@ -1035,22 +1110,32 @@ pr_namebind_t *pr_namebind_find(const char *name, const pr_netaddr_t *addr,
     return NULL;
   }
 
-  if (ipbind->ib_namebinds == NULL) {
-    pr_trace_msg(trace_channel, 17,
-      "ipbind %p (server %p) for %s#%u has no namebinds", ipbind,
-      ipbind->ib_server, pr_netaddr_get_ipstr(addr), port);
-    return NULL;
+  /* Now we need to search this ipbind list, to see if any of them have a
+   * matching namebind.
+   */
 
-  } else {
+  for (iter = ipbind; iter; iter = iter->ib_next) {
     register unsigned int i = 0;
-    pr_namebind_t **namebinds = (pr_namebind_t **) ipbind->ib_namebinds->elts;
+    pr_namebind_t *namebind = NULL, **namebinds = NULL;
+
+    pr_signals_handle();
 
+    if (iter->ib_namebinds == NULL) {
+      pr_trace_msg(trace_channel, 17,
+        "ipbind %p (server %p) for %s#%u has no namebinds", iter,
+        iter->ib_server, pr_netaddr_get_ipstr(addr), port);
+      continue;
+    }
+
+    namebinds = (pr_namebind_t **) iter->ib_namebinds->elts;
     pr_trace_msg(trace_channel, 17,
-      "ipbind %p (server %p) for %s#%u has namebinds (%d)", ipbind,
-      ipbind->ib_server, pr_netaddr_get_ipstr(addr), port,
-      ipbind->ib_namebinds->nelts);
+      "ipbind %p (server %p) for %s#%u has namebinds (%d)", iter,
+      iter->ib_server, pr_netaddr_get_ipstr(addr), port,
+      iter->ib_namebinds->nelts);
+
+    for (i = 0; i < iter->ib_namebinds->nelts; i++) {
+      pr_signals_handle();
 
-    for (i = 0; i < ipbind->ib_namebinds->nelts; i++) {
       namebind = namebinds[i];
       if (namebind == NULL) {
         continue;
@@ -1171,10 +1256,11 @@ void free_bindings(void) {
   /* Mark all listening conns as "unclaimed"; any that remaining unclaimed
    * after init_bindings() can be closed.
    */
-  if (listening_conn_list) {
+  if (listening_conn_list != NULL) {
     struct listener_rec *lr;
 
     for (lr = (struct listener_rec *) listening_conn_list->xas_list; lr; lr = lr->next) {
+      pr_signals_handle();
       lr->claimed = FALSE;
     }
   }
@@ -1223,10 +1309,28 @@ static int init_inetd_bindings(void) {
     is_default = TRUE;
   }
 
-  PR_CREATE_IPBIND(main_server, main_server->addr, main_server->ServerPort);
-  PR_OPEN_IPBIND(main_server->addr, main_server->ServerPort,
+  res = pr_ipbind_create(main_server, main_server->addr,
+    main_server->ServerPort);
+  if (res < 0) {
+    pr_log_pri(PR_LOG_NOTICE,
+      "%s:%d: notice: unable to create ipbind '%s#%u': %s", __FILE__, __LINE__,
+      main_server->ServerAddress, main_server->ServerPort, strerror(errno));
+  }
+
+  res = pr_ipbind_open(main_server->addr, main_server->ServerPort,
     main_server->listen, is_default, TRUE, TRUE);
-  PR_ADD_IPBINDS(main_server);
+  if (res < 0) {
+    pr_log_pri(PR_LOG_NOTICE,
+      "%s:%d: notice: unable to open ipbind '%s': %s", __FILE__, __LINE__,
+      pr_netaddr_get_ipstr(main_server->addr), strerror(errno));
+  }
+
+  res = pr_ipbind_add_binds(main_server);
+  if (res < 0) {
+    pr_log_pri(PR_LOG_NOTICE,
+      "%s:%d: notice: unable to add binds to ipbind '%s': %s", __FILE__,
+      __LINE__, main_server->ServerAddress, strerror(errno));
+  }
 
   /* Now attach the faked connection to all virtual servers. */
   for (serv = main_server->next; serv; serv = serv->next) {
@@ -1247,10 +1351,27 @@ static int init_inetd_bindings(void) {
       is_default = TRUE;
     }
 
-    PR_CREATE_IPBIND(serv, serv->addr, serv->ServerPort);
-    PR_OPEN_IPBIND(serv->addr, serv->ServerPort, serv->listen, is_default,
-      FALSE, TRUE);
-    PR_ADD_IPBINDS(serv);
+    res = pr_ipbind_create(serv, serv->addr, serv->ServerPort);
+    if (res < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "%s:%d: notice: unable to create ipbind '%s#%u': %s", __FILE__,
+        __LINE__, serv->ServerAddress, serv->ServerPort, strerror(errno));
+    }
+
+    res = pr_ipbind_open(serv->addr, serv->ServerPort, serv->listen,
+      is_default, FALSE, TRUE);
+    if (res < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "%s:%d: notice: unable to open ipbind '%s': %s", __FILE__, __LINE__,
+        pr_netaddr_get_ipstr(serv->addr), strerror(errno));
+    }
+
+    res = pr_ipbind_add_binds(serv);
+    if (res < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "%s:%d: notice: unable to add binds to ipbind '%s': %s", __FILE__,
+        __LINE__, serv->ServerAddress, strerror(errno));
+    }
   }
 
   return 0;
@@ -1264,6 +1385,8 @@ static array_header *find_server_ipbinds(pool *p, server_rec *s) {
     pr_ipbind_t *ipbind;
 
     for (ipbind = ipbind_table[i]; ipbind != NULL; ipbind = ipbind->ib_next) {
+      pr_signals_handle();
+
       if (ipbind->ib_server == s) {
         if (ipbinds == NULL) {
           ipbinds = make_array(p, 16, sizeof(pr_ipbind_t *));
@@ -1280,6 +1403,7 @@ static array_header *find_server_ipbinds(pool *p, server_rec *s) {
 static unsigned int process_serveralias(server_rec *s) {
   unsigned namebind_count = 0;
   config_rec *c;
+  pr_ipbind_t *ipbind;
   array_header *ipbinds;
   pool *tmp_pool;
 
@@ -1291,15 +1415,34 @@ static unsigned int process_serveralias(server_rec *s) {
    *  <VirtualHost 1.2.3.4 5.6.7.8>
    *    ServerAlias alias
    *  </VirtualHost>
+   *
+   * And that multiple namebinds can point to the same ipbind for this server:
+   *
+   *  <VirtualHost 1.2.3.4>
+   *    ServerAlias first
+   *  </VirtualHost>
+   *
+   *  <VirtualHost 2.3.4.5>
+   *    ServerAlias second
+   *  </VirtualHost>
    */
 
   tmp_pool = make_sub_pool(s->pool);
   pr_pool_tag(tmp_pool, "ServerAlias Processing Pool");
 
-  ipbinds = find_server_ipbinds(tmp_pool, s);
-  if (ipbinds == NULL) {
-    destroy_pool(tmp_pool);
-    return 0;
+  /* Remember that this will return cases where port is zero, too. */
+  ipbind = pr_ipbind_find(s->addr, s->ServerPort, FALSE);
+  if (ipbind != NULL &&
+      ipbind->ib_server->ServerPort == s->ServerPort) {
+    ipbinds = make_array(tmp_pool, 1, sizeof(pr_ipbind_t *));
+    *((pr_ipbind_t **) push_array(ipbinds)) = ipbind;
+
+  } else {
+    ipbinds = find_server_ipbinds(tmp_pool, s);
+    if (ipbinds == NULL) {
+      destroy_pool(tmp_pool);
+      return 0;
+    }
   }
 
   c = find_config(s->conf, CONF_PARAM, "ServerAlias", FALSE);
@@ -1310,11 +1453,17 @@ static unsigned int process_serveralias(server_rec *s) {
 
     pr_signals_handle();
 
+    pr_trace_msg(trace_channel, 7, "handling ipbinds (%d) for ServerAlias '%s'",
+      ipbinds->nelts, (char *) c->argv[0]);
+
     elts = ipbinds->elts;
     for (i = 0; i < ipbinds->nelts; i++) {
-      pr_ipbind_t *ipbind;
+      pr_signals_handle();
 
       ipbind = elts[i];
+      pr_trace_msg(trace_channel, 7, "adding ServerAlias '%s' to server '%s'",
+        (char *) c->argv[0], s->ServerName);
+
       res = pr_namebind_create(s, c->argv[0], ipbind, s->addr, s->ServerPort);
       if (res == 0) {
         namebind_count++;
@@ -1355,22 +1504,26 @@ static void trace_ipbind_table(void) {
     register unsigned int j;
     pr_ipbind_t *ipbind;
 
+    pr_signals_handle();
+
     if (ipbind_table[i] == NULL) {
       continue;
     }
 
-    pr_signals_handle();
-
     pr_trace_msg(trace_channel, 25, "  index %u:", i);
     for (j = 0, ipbind = ipbind_table[i]; ipbind; j++, ipbind = ipbind->ib_next) {
       array_header *namebinds;
 
+      pr_signals_handle();
       namebinds = ipbind->ib_namebinds;
 
       pr_trace_msg(trace_channel, 25, "    ipbind %p:", ipbind);
       pr_trace_msg(trace_channel, 25, "      address: %s#%u",
         pr_netaddr_get_ipstr(ipbind->ib_addr), ipbind->ib_port);
-      pr_trace_msg(trace_channel, 25, "      server: %p", ipbind->ib_server);
+      pr_trace_msg(trace_channel, 25, "      server: %s (%p)",
+        ipbind->ib_server->ServerName, ipbind->ib_server);
+      pr_trace_msg(trace_channel, 25, "      active: %s",
+        ipbind->ib_isactive ? "TRUE" : "FALSE");
 
       if (namebinds != NULL) {
         register unsigned int k;
@@ -1381,12 +1534,15 @@ static void trace_ipbind_table(void) {
         for (k = 0; k < namebinds->nelts; k++) {
           pr_namebind_t *namebind;
 
+          pr_signals_handle();
           namebind = elts[k];
           pr_trace_msg(trace_channel, 25, "      #%u: %p", k+1, namebind);
           pr_trace_msg(trace_channel, 25, "        name: %s",
             namebind->nb_name);
           pr_trace_msg(trace_channel, 25, "        server: %p",
             namebind->nb_server);
+          pr_trace_msg(trace_channel, 25, "        active: %s",
+            namebind->nb_isactive ? "TRUE" : "FALSE");
         }
       }
     }
@@ -1410,7 +1566,7 @@ static int init_standalone_bindings(void) {
      * IPv4 or an IPv6 wildcard socket?
      */
     if (!SocketBindTight) {
-#ifdef PR_USE_IPV6
+#if defined(PR_USE_IPV6)
       if (pr_netaddr_use_ipv6()) {
         pr_inet_set_default_family(NULL, AF_INET6);
 
@@ -1442,17 +1598,39 @@ static int init_standalone_bindings(void) {
     is_default = TRUE;
   }
 
-  if (main_server->ServerPort ||
-      is_default) {
-    PR_CREATE_IPBIND(main_server, main_server->addr, main_server->ServerPort);
-    PR_OPEN_IPBIND(main_server->addr, main_server->ServerPort,
+  if (main_server->ServerPort > 0 ||
+      is_default == TRUE) {
+
+    res = pr_ipbind_create(main_server, main_server->addr,
+      main_server->ServerPort);
+    if (res < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "%s:%d: notice: unable to create ipbind '%s#%u': %s", __FILE__,
+        __LINE__, main_server->ServerAddress, main_server->ServerPort,
+        strerror(errno));
+    }
+
+    res = pr_ipbind_open(main_server->addr, main_server->ServerPort,
       main_server->listen, is_default, TRUE, TRUE);
-    PR_ADD_IPBINDS(main_server);
+    if (res < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "%s:%d: notice: unable to open ipbind '%s': %s", __FILE__, __LINE__,
+        pr_netaddr_get_ipstr(main_server->addr), strerror(errno));
+    }
+
+    res = pr_ipbind_add_binds(main_server);
+    if (res < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "%s:%d: notice: unable to add binds to ipbind '%s': %s", __FILE__,
+        __LINE__, main_server->ServerAddress, strerror(errno));
+    }
   }
 
   for (serv = main_server->next; serv; serv = serv->next) {
     unsigned int namebind_count;
 
+    pr_signals_handle();
+
     namebind_count = process_serveralias(serv);
     if (namebind_count > 0) {
       /* If we successfully added ServerAlias namebinds, move on to the next
@@ -1472,8 +1650,8 @@ static int init_standalone_bindings(void) {
         is_default = TRUE;
       }
 
-      if (serv->ServerPort) {
-        if (!SocketBindTight) {
+      if (serv->ServerPort > 0) {
+        if (SocketBindTight == FALSE) {
 #ifdef PR_USE_IPV6
           if (pr_netaddr_use_ipv6()) {
             pr_inet_set_default_family(NULL, AF_INET6);
@@ -1492,18 +1670,52 @@ static int init_standalone_bindings(void) {
           return -1;
         }
 
-        PR_CREATE_IPBIND(serv, serv->addr, serv->ServerPort);
-        PR_OPEN_IPBIND(serv->addr, serv->ServerPort, serv->listen, is_default,
-          FALSE, TRUE);
-        PR_ADD_IPBINDS(serv);
+        res = pr_ipbind_create(serv, serv->addr, serv->ServerPort);
+        if (res < 0) {
+          pr_log_pri(PR_LOG_NOTICE,
+            "%s:%d: notice: unable to create ipbind '%s#%u': %s", __FILE__,
+            __LINE__, serv->ServerAddress, serv->ServerPort, strerror(errno));
+        }
+
+        res = pr_ipbind_open(serv->addr, serv->ServerPort, serv->listen,
+          is_default, FALSE, TRUE);
+        if (res < 0) {
+          pr_log_pri(PR_LOG_NOTICE,
+            "%s:%d: notice: unable to open ipbind '%s': %s", __FILE__, __LINE__,
+            pr_netaddr_get_ipstr(serv->addr), strerror(errno));
+        }
+
+        res = pr_ipbind_add_binds(serv);
+        if (res < 0) {
+          pr_log_pri(PR_LOG_NOTICE,
+            "%s:%d: notice: unable to add binds to ipbind '%s': %s", __FILE__,
+            __LINE__, serv->ServerAddress, strerror(errno));
+        }
 
       } else if (is_default) {
         serv->listen = NULL;
 
-        PR_CREATE_IPBIND(serv, serv->addr, serv->ServerPort);
-        PR_OPEN_IPBIND(serv->addr, serv->ServerPort, serv->listen, is_default,
-          FALSE, TRUE);
-        PR_ADD_IPBINDS(serv);
+        res = pr_ipbind_create(serv, serv->addr, serv->ServerPort);
+        if (res < 0) {
+          pr_log_pri(PR_LOG_NOTICE,
+            "%s:%d: notice: unable to create ipbind '%s#%u': %s", __FILE__,
+            __LINE__, serv->ServerAddress, serv->ServerPort, strerror(errno));
+        }
+
+        res = pr_ipbind_open(serv->addr, serv->ServerPort, serv->listen,
+          is_default, FALSE, TRUE);
+        if (res < 0) {
+          pr_log_pri(PR_LOG_NOTICE,
+            "%s:%d: notice: unable to open ipbind '%s': %s", __FILE__, __LINE__,
+            pr_netaddr_get_ipstr(serv->addr), strerror(errno));
+        }
+
+        res = pr_ipbind_add_binds(serv);
+        if (res < 0) {
+          pr_log_pri(PR_LOG_NOTICE,
+            "%s:%d: notice: unable to add binds to ipbind '%s': %s", __FILE__,
+            __LINE__, serv->ServerAddress, strerror(errno));
+        }
 
       } else {
         serv->listen = NULL;
@@ -1525,10 +1737,27 @@ static int init_standalone_bindings(void) {
       serv->listen = main_server->listen;
       register_cleanup2(serv->listen->pool, &serv->listen, server_cleanup_cb);
 
-      PR_CREATE_IPBIND(serv, serv->addr, serv->ServerPort);
-      PR_OPEN_IPBIND(serv->addr, serv->ServerPort, NULL, is_default, FALSE,
-        TRUE);
-      PR_ADD_IPBINDS(serv);
+      res = pr_ipbind_create(serv, serv->addr, serv->ServerPort);
+      if (res < 0) {
+        pr_log_pri(PR_LOG_NOTICE,
+          "%s:%d: notice: unable to create ipbind '%s#%u': %s", __FILE__,
+          __LINE__, serv->ServerAddress, serv->ServerPort, strerror(errno));
+      }
+
+      res = pr_ipbind_open(serv->addr, serv->ServerPort, NULL, is_default,
+        FALSE, TRUE);
+      if (res < 0) {
+        pr_log_pri(PR_LOG_NOTICE,
+          "%s:%d: notice: unable to open ipbind '%s': %s", __FILE__, __LINE__,
+          pr_netaddr_get_ipstr(serv->addr), strerror(errno));
+      }
+
+      res = pr_ipbind_add_binds(serv);
+      if (res < 0) {
+        pr_log_pri(PR_LOG_NOTICE,
+          "%s:%d: notice: unable to add binds to ipbind '%s': %s", __FILE__,
+          __LINE__, serv->ServerAddress, strerror(errno));
+      }
     }
 
     /* Process any ServerAlias directives AFTER the server's ipbind has been
@@ -1540,13 +1769,14 @@ static int init_standalone_bindings(void) {
   trace_ipbind_table();
 
   /* Any "unclaimed" listening conns can be removed and closed. */
-  if (listening_conn_list) {
+  if (listening_conn_list != NULL) {
     struct listener_rec *lr, *lrn;
 
     for (lr = (struct listener_rec *) listening_conn_list->xas_list; lr; lr = lrn) {
-      lrn = lr->next;
+      pr_signals_handle();
 
-      if (!lr->claimed) {
+      lrn = lr->next;
+      if (lr->claimed == FALSE) {
         xaset_remove(listening_conn_list, (xasetmember_t *) lr);
         destroy_pool(lr->pool);
       }
@@ -1585,4 +1815,3 @@ void init_bindings(void) {
     exit(1);
   }
 }
-


=====================================
src/inet.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-2021 The ProFTPD Project team
+ * Copyright (c) 2001-2022 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
@@ -925,110 +925,139 @@ int pr_inet_set_proto_opts(pool *p, conn_t *c, int mss, int nodelay,
   return 0;
 }
 
-/* Set socket options on a connection.  */
-int pr_inet_set_socket_opts2(pool *p, conn_t *c, int rcvbuf, int sndbuf,
-    struct tcp_keepalive *tcp_keepalive, int reuse_port) {
+int pr_inet_set_proto_keepalive(pool *p, conn_t *c,
+    struct tcp_keepalive *tcp_keepalive) {
+  int keepalive = 1, val = -1;
 
-  if (c == NULL) {
+  if (p == NULL ||
+      c == NULL ||
+      tcp_keepalive == NULL) {
     errno = EINVAL;
     return -1;
   }
 
-  /* Linux and "most" newer networking OSes probably use a highly adaptive
-   * window size system, which generally wouldn't require user-space
-   * modification at all.  Thus, check the current sndbuf and rcvbuf sizes
-   * before changing them, and only change them if we are making them larger
-   * than their current size.
-   */
+  if (c->listen_fd < 0) {
+    errno = EINVAL;
+    return -1;
+  }
 
-  if (c->listen_fd != -1) {
-    int keepalive = 1;
-    int crcvbuf = 0, csndbuf = 0;
-    socklen_t len;
+  keepalive = tcp_keepalive->keepalive_enabled;
 
-    if (tcp_keepalive != NULL) {
-      keepalive = tcp_keepalive->keepalive_enabled;
-    }
+  pr_trace_msg(trace_channel, 17, "%s SO_KEEPALIVE on socket fd %d",
+    keepalive ? "enabling" : "disabling", c->listen_fd);
+  if (setsockopt(c->listen_fd, SOL_SOCKET, SO_KEEPALIVE, (void *) &keepalive,
+      sizeof(int)) < 0) {
+    pr_log_pri(PR_LOG_NOTICE, "error setting listen fd SO_KEEPALIVE: %s",
+      strerror(errno));
+    return 0;
+  }
 
-    pr_trace_msg(trace_channel, 17, "%s SO_KEEPALIVE on socket fd %d",
-      keepalive ? "enabling" : "disabling", c->listen_fd);
-    if (setsockopt(c->listen_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)
-        &keepalive, sizeof(int)) < 0) {
-      pr_log_pri(PR_LOG_NOTICE, "error setting listen fd SO_KEEPALIVE: %s",
-        strerror(errno));
+  if (keepalive == 0) {
+    return 0;
+  }
 
-    } else {
-      /* We only try to set the TCP keepalive specifics if SO_KEEPALIVE was
-       * set successfully.
-       */
-      pr_trace_msg(trace_channel, 15,
-        "enabled SO_KEEPALIVE on socket fd %d", c->listen_fd);
+  /* We only try to set the TCP keepalive specifics if SO_KEEPALIVE was
+   * enabled successfully.
+   */
+  pr_trace_msg(trace_channel, 15, "enabled SO_KEEPALIVE on socket fd %d",
+    c->listen_fd);
 
-      if (tcp_keepalive != NULL) {
-        int val = 0;
+  /* On Mac OS, the socket option is TCP_KEEPALIVE rather than
+   * TCP_KEEPIDLE.
+   */
+#if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
+  val = tcp_keepalive->keepalive_idle;
+  if (val != -1) {
+    int option_name;
+
+# if defined(TCP_KEEPALIVE)
+    option_name = TCP_KEEPALIVE;
+# else
+    option_name = TCP_KEEPIDLE;
+# endif /* TCP_KEEPALIVE or TCP_KEEPIDLE */
 
-#if defined(TCP_KEEPIDLE)
-        val = tcp_keepalive->keepalive_idle;
-        if (val != -1) {
 # ifdef __DragonFly__
-          /* DragonFly BSD uses millsecs as the KEEPIDLE unit. */
-          val *= 1000;
+    /* DragonFly BSD uses millsecs as the KEEPIDLE unit. */
+    val *= 1000;
 # endif /* DragonFly BSD */
-          if (setsockopt(c->listen_fd, SOL_SOCKET, TCP_KEEPIDLE, (void *)
-              &val, sizeof(int)) < 0) {
-            pr_log_pri(PR_LOG_NOTICE,
-              "error setting TCP_KEEPIDLE %d on fd %d: %s", val, c->listen_fd,
-              strerror(errno));
+    if (setsockopt(c->listen_fd, IPPROTO_TCP, option_name, (void *) &val,
+        sizeof(int)) < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "error setting TCP_KEEPIDLE %d on fd %d: %s", val, c->listen_fd,
+       strerror(errno));
 
-          } else {
-            pr_trace_msg(trace_channel, 15,
-              "enabled TCP_KEEPIDLE %d on socket fd %d", val, c->listen_fd);
-          }
-        }
+    } else {
+      pr_trace_msg(trace_channel, 15,
+        "enabled TCP_KEEPIDLE %d on socket fd %d", val, c->listen_fd);
+    }
+  }
 #endif /* TCP_KEEPIDLE */
 
 #if defined(TCP_KEEPCNT)
-        val = tcp_keepalive->keepalive_count;
-        if (val != -1) {
-          if (setsockopt(c->listen_fd, SOL_SOCKET, TCP_KEEPCNT, (void *)
-              &val, sizeof(int)) < 0) {
-            pr_log_pri(PR_LOG_NOTICE,
-              "error setting TCP_KEEPCNT %d on fd %d: %s", val, c->listen_fd,
-              strerror(errno));
+  val = tcp_keepalive->keepalive_count;
+  if (val != -1) {
+    if (setsockopt(c->listen_fd, IPPROTO_TCP, TCP_KEEPCNT, (void *) &val,
+        sizeof(int)) < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "error setting TCP_KEEPCNT %d on fd %d: %s", val, c->listen_fd,
+        strerror(errno));
 
-          } else {
-            pr_trace_msg(trace_channel, 15,
-              "enabled TCP_KEEPCNT %d on socket fd %d", val, c->listen_fd);
-          }
-        }
+    } else {
+      pr_trace_msg(trace_channel, 15,
+        "enabled TCP_KEEPCNT %d on socket fd %d", val, c->listen_fd);
+    }
+  }
 #endif /* TCP_KEEPCNT */
 
 #if defined(TCP_KEEPINTVL)
-        val = tcp_keepalive->keepalive_intvl;
-        if (val != -1) {
+  val = tcp_keepalive->keepalive_intvl;
+  if (val != -1) {
 # ifdef __DragonFly__
-          /* DragonFly BSD uses millsecs as the KEEPINTVL unit. */
-          val *= 1000;
+    /* DragonFly BSD uses millsecs as the KEEPINTVL unit. */
+    val *= 1000;
 # endif /* DragonFly BSD */
-          if (setsockopt(c->listen_fd, SOL_SOCKET, TCP_KEEPINTVL, (void *)
-              &val, sizeof(int)) < 0) {
-            pr_log_pri(PR_LOG_NOTICE,
-              "error setting TCP_KEEPINTVL %d on fd %d: %s", val, c->listen_fd,
-              strerror(errno));
+    if (setsockopt(c->listen_fd, IPPROTO_TCP, TCP_KEEPINTVL, (void *) &val,
+        sizeof(int)) < 0) {
+      pr_log_pri(PR_LOG_NOTICE,
+        "error setting TCP_KEEPINTVL %d on fd %d: %s", val, c->listen_fd,
+        strerror(errno));
 
-          } else {
-            pr_trace_msg(trace_channel, 15,
-              "enabled TCP_KEEPINTVL %d on socket fd %d", val, c->listen_fd);
-          }
-        }
+    } else {
+      pr_trace_msg(trace_channel, 15,
+        "enabled TCP_KEEPINTVL %d on socket fd %d", val, c->listen_fd);
+    }
+  }
 #endif /* TCP_KEEPINTVL */
 
-        /* Avoid compiler warnings on platforms which do not support any
-         * of the above TCP keepalive macros.
-         */
-        (void) val;
-      }
-    }
+  /* Avoid compiler warnings on platforms which do not support any
+   * of the above TCP keepalive macros.
+   */
+  (void) val;
+
+  return 0;
+}
+
+/* Set socket options on a connection.  */
+int pr_inet_set_socket_opts2(pool *p, conn_t *c, int rcvbuf, int sndbuf,
+    struct tcp_keepalive *tcp_keepalive, int reuse_port) {
+
+  if (c == NULL) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  /* Linux and "most" newer networking OSes probably use a highly adaptive
+   * window size system, which generally wouldn't require user-space
+   * modification at all.  Thus, check the current sndbuf and rcvbuf sizes
+   * before changing them, and only change them if we are making them larger
+   * than their current size.
+   */
+
+  if (c->listen_fd != -1) {
+    int crcvbuf = 0, csndbuf = 0;
+    socklen_t len;
+
+    (void) pr_inet_set_proto_keepalive(p, c, tcp_keepalive);
 
     if (sndbuf > 0) {
       len = sizeof(csndbuf);
@@ -1522,9 +1551,9 @@ int pr_inet_accept_nowait(pool *p, conn_t *c) {
  */
 conn_t *pr_inet_accept(pool *p, conn_t *d, conn_t *c, int rfd, int wfd,
     unsigned char resolve) {
+  config_rec *allow_foreign_addr_config = NULL;
   conn_t *res = NULL;
-  unsigned char *foreign_addr = NULL;
-  int fd = -1, allow_foreign_address = FALSE;
+  int fd = -1;
   pr_netaddr_t na;
   socklen_t nalen;
 
@@ -1540,13 +1569,10 @@ conn_t *pr_inet_accept(pool *p, conn_t *d, conn_t *c, int rfd, int wfd,
   pr_netaddr_set_family(&na, pr_netaddr_get_family(c->remote_addr));
   nalen = pr_netaddr_get_sockaddr_len(&na);
 
+  allow_foreign_addr_config = find_config(TOPLEVEL_CONF, CONF_PARAM,
+    "AllowForeignAddress", FALSE);
   d->mode = CM_ACCEPT;
 
-  foreign_addr = get_param_ptr(TOPLEVEL_CONF, "AllowForeignAddress", FALSE);
-  if (foreign_addr != NULL) {
-    allow_foreign_address = *foreign_addr;
-  }
-
   /* A directive could enforce only IPv4 or IPv6 connections here, by
    * actually using a sockaddr argument to accept(2), and checking the
    * family of the connecting entity.
@@ -1566,28 +1592,79 @@ conn_t *pr_inet_accept(pool *p, conn_t *d, conn_t *c, int rfd, int wfd,
       break;
     }
 
-    if (allow_foreign_address == FALSE) {
-      /* If foreign addresses (i.e. IP addresses that do not match the
-       * control connection's remote IP address) are not allowed, we
-       * need to see just what our remote address IS.
-       */
-      if (getpeername(fd, pr_netaddr_get_sockaddr(&na), &nalen) < 0) {
-        /* If getpeername(2) fails, should we still allow this connection?
-         * Caution (and the AllowForeignAddress setting say "no".
+    if (allow_foreign_addr_config != NULL) {
+      int allowed;
+
+      allowed = *((int *) allow_foreign_addr_config->argv[0]);
+      if (allowed != TRUE) {
+        /* If foreign addresses (i.e. IP addresses that do not match the
+         * control connection's remote IP address) are not allowed, we
+         * need to see just what our remote address IS.
          */
-        pr_log_pri(PR_LOG_DEBUG, "rejecting passive connection; "
-          "failed to get address of remote peer: %s", strerror(errno));
-        (void) close(fd);
-        continue;
-      }
 
-      if (pr_netaddr_cmp(&na, c->remote_addr) != 0) {
-        pr_log_pri(PR_LOG_NOTICE, "SECURITY VIOLATION: Passive connection "
-          "from foreign IP address %s rejected (does not match client "
-          "IP address %s).", pr_netaddr_get_ipstr(&na),
-          pr_netaddr_get_ipstr(c->remote_addr));
-        (void) close(fd);
-        continue;
+        if (getpeername(fd, pr_netaddr_get_sockaddr(&na), &nalen) < 0) {
+          /* If getpeername(2) fails, should we still allow this connection?
+           * Caution (and the AllowForeignAddress setting) say "no".
+           */
+          pr_log_pri(PR_LOG_DEBUG, "rejecting passive connection; "
+            "failed to get address of remote peer: %s", strerror(errno));
+          (void) close(fd);
+          continue;
+        }
+
+        if (allowed == FALSE) {
+          if (pr_netaddr_cmp(&na, c->remote_addr) != 0) {
+            pr_log_pri(PR_LOG_NOTICE, "SECURITY VIOLATION: Passive connection "
+              "from foreign IP address %s rejected (does not match client "
+              "IP address %s).", pr_netaddr_get_ipstr(&na),
+              pr_netaddr_get_ipstr(c->remote_addr));
+
+            (void) close(fd);
+            d->mode = CM_ERROR;
+            d->xerrno = EACCES;
+
+            return NULL;
+          }
+
+        } else {
+          char *class_name;
+
+          /* Check the data connection remote address against BOTH the
+           * control connection remote address AND the configured <Class>.
+           */
+          class_name = allow_foreign_addr_config->argv[1];
+
+          if (pr_netaddr_cmp(&na, c->remote_addr) != 0) {
+            const pr_class_t *cls;
+
+            cls = pr_class_find(class_name);
+            if (cls != NULL) {
+              if (pr_class_satisfied(p, cls, &na) != TRUE) {
+                pr_log_debug(DEBUG8, "<Class> '%s' not satisfied by foreign "
+                  "address '%s'", class_name, pr_netaddr_get_ipstr(&na));
+
+                pr_log_pri(PR_LOG_NOTICE,
+                  "SECURITY VIOLATION: Passive connection from foreign IP "
+                  "address %s rejected (does not match <Class %s>).",
+                  pr_netaddr_get_ipstr(&na), class_name);
+
+                (void) close(fd);
+                d->mode = CM_ERROR;
+                d->xerrno = EACCES;
+                return NULL;
+              }
+
+            } else {
+              pr_log_debug(DEBUG8, "<Class> '%s' not found for filtering "
+                "AllowForeignAddress", class_name);
+            }
+
+          } else {
+            pr_log_debug(DEBUG9, "Passive connection from IP address '%s' "
+              "matches control connection address; skipping <Class> '%s'",
+              pr_netaddr_get_ipstr(&na), class_name);
+          }
+        }
       }
     }
 


=====================================
src/random.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2017 The ProFTPD Project team
+ * Copyright (c) 2017-2022 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
@@ -24,23 +24,25 @@
 
 #include "conf.h"
 
+/* Note: Make sure that we initialize the state for both random(3) and rand(3),
+ * as modules/code may make use of either (or both) of them; see Issue #1396.
+ */
 int pr_random_init(void) {
-#ifdef HAVE_RANDOM
+#if defined(HAVE_RANDOM)
   struct timeval tv;
 
   gettimeofday(&tv, NULL);
   srandom(getpid() ^ tv.tv_usec);
-#else
-  srand((unsigned int) (getpid() * time(NULL)));
 #endif /* HAVE_RANDOM */
 
+  srand((unsigned int) (getpid() * time(NULL)));
   return 0;
 }
 
 long pr_random_next(long min, long max) {
   long r, scaled;
 
-#ifdef HAVE_RANDOM
+#if defined(HAVE_RANDOM)
   r = random();
 #else
   r = (long) rand();


=====================================
tests/t/lib/ProFTPD/TestSuite/Utils.pm
=====================================
@@ -1210,6 +1210,8 @@ sub test_setup {
   my $gid = shift;
   $gid = 500 unless defined($gid);
   my $home_dir = shift;
+  my $groups = shift;
+  $groups = $user unless defined($groups);
 
   my $config_file = "$tmpdir/$name.conf";
   my $pid_file = File::Spec->rel2abs("$tmpdir/$name.pid");
@@ -1238,7 +1240,7 @@ sub test_setup {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, $group, $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $groups);
 
   my $setup = {
     auth_user_file => $auth_user_file,


=====================================
tests/t/lib/ProFTPD/Tests/Commands/NLST.pm
=====================================
@@ -157,6 +157,16 @@ my $TESTS = {
     test_class => [qw(forking rootprivs)],
   },
 
+  nlst_glob_with_rel_path_issue1325 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
+
+  nlst_glob_with_rel_path_dotdir_issue1325 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
+
 };
 
 sub new {
@@ -3616,22 +3626,7 @@ sub nlst_rel_path_chrooted_bug2496 {
 sub nlst_parent_dir_bug4011 {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
-
-  my $config_file = "$tmpdir/cmds.conf";
-  my $pid_file = File::Spec->rel2abs("$tmpdir/cmds.pid");
-  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/cmds.scoreboard");
-
-  my $log_file = test_get_logfile();
-
-  my $auth_user_file = File::Spec->rel2abs("$tmpdir/cmds.passwd");
-  my $auth_group_file = File::Spec->rel2abs("$tmpdir/cmds.group");
-
-  my $user = 'proftpd';
-  my $passwd = 'test';
-  my $group = 'ftpd';
-  my $home_dir = File::Spec->rel2abs($tmpdir);
-  my $uid = 500;
-  my $gid = 500;
+  my $setup = test_setup($tmpdir, 'cmds');
 
   my $sub_dir1 = File::Spec->rel2abs("$tmpdir/dir1");
   my $sub_dir2 = File::Spec->rel2abs("$tmpdir/dir1/dir2");
@@ -3662,26 +3657,22 @@ sub nlst_parent_dir_bug4011 {
   # Make sure that, if we're running as root, that the home directory has
   # permissions/privs set for the account we create
   if ($< == 0) {
-    unless (chmod(0755, $home_dir, $sub_dir1)) {
-      die("Can't set perms on $home_dir to 0755: $!");
+    unless (chmod(0755, $sub_dir1)) {
+      die("Can't set perms on $sub_dir1 to 0755: $!");
     }
 
-    unless (chown($uid, $gid, $home_dir, $sub_dir1)) {
-      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    unless (chown($setup->{uid}, $setup->{gid}, $sub_dir1)) {
+      die("Can't set owner of $sub_dir1 to $setup->{uid}/$setup->{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 $config = {
-    PidFile => $pid_file,
-    ScoreboardFile => $scoreboard_file,
-    SystemLog => $log_file,
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
 
-    AuthUserFile => $auth_user_file,
-    AuthGroupFile => $auth_group_file,
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
 
     IfModules => {
       'mod_delay.c' => {
@@ -3690,7 +3681,8 @@ sub nlst_parent_dir_bug4011 {
     },
   };
 
-  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+  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
@@ -3708,7 +3700,7 @@ sub nlst_parent_dir_bug4011 {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-      $client->login($user, $passwd);
+      $client->login($setup->{user}, $setup->{passwd});
       $client->cwd("dir1");
       $client->cwd("dir2");
 
@@ -3722,6 +3714,12 @@ sub nlst_parent_dir_bug4011 {
       $conn->read($buf, 8192, 25);
       eval { $conn->close() };
 
+      $client->quit();
+
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "# response:\n$buf\n";
+      }
+
       # We have to be careful of the fact that readdir returns directory
       # entries in an unordered fashion.
       my $res = {};
@@ -3749,7 +3747,6 @@ sub nlst_parent_dir_bug4011 {
         die("Unexpected name '$mismatch' appeared in NLST data")
       }
     };
-
     if ($@) {
       $ex = $@;
     }
@@ -3758,7 +3755,7 @@ sub nlst_parent_dir_bug4011 {
     $wfh->flush();
 
   } else {
-    eval { server_wait($config_file, $rfh) };
+    eval { server_wait($setup->{config_file}, $rfh) };
     if ($@) {
       warn($@);
       exit 1;
@@ -3768,18 +3765,10 @@ sub nlst_parent_dir_bug4011 {
   }
 
   # Stop server
-  server_stop($pid_file);
-
+  server_stop($setup->{pid_file});
   $self->assert_child_ok($pid);
 
-  if ($ex) {
-    test_append_logfile($log_file, $ex);
-    unlink($log_file);
-
-    die($ex);
-  }
-
-  unlink($log_file);
+  test_cleanup($setup->{log_file}, $ex);
 }
 
 sub nlst_opt_a_root_dir_bug4069 {
@@ -4080,4 +4069,382 @@ sub nlst_opt_1_with_chroot {
   unlink($log_file);
 }
 
+sub nlst_glob_with_rel_path_issue1325 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'cmds');
+
+  my $test_path = File::Spec->rel2abs("$tmpdir/test.d");
+  mkpath($test_path);
+
+  for (my $i = 0; $i < 10; $i++) {
+    my $test_file = File::Spec->rel2abs("$test_path/TEST000$i.dat");
+    if (open(my $fh, "> $test_file")) {
+      print $fh "Hello, World!\n";
+      unless (close($fh)) {
+        die("Can't write $test_file: $!");
+      }
+
+    } else {
+      die("Can't open $test_file: $!");
+    }
+  }
+
+  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);
+
+  # 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 {
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($setup->{user}, $setup->{passwd});
+
+      my $conn = $client->nlst_raw('test.d/TEST????.dat');
+      unless ($conn) {
+        die("Failed to NLST: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      my $buf;
+      $conn->read($buf, 8192, 25);
+      eval { $conn->close() };
+
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "# response:\n$buf\n";
+      }
+
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      my $res = {};
+      my $names = [split(/\n/, $buf)];
+      foreach my $name (@$names) {
+        $res->{$name} = 1;
+      }
+
+      $self->assert(scalar(@$names) > 0,
+        test_msg("Expected multiple names, got 0"));
+
+      my $expected = {
+        'test.d/TEST0000.dat' => 1,
+        'test.d/TEST0001.dat' => 1,
+        'test.d/TEST0002.dat' => 1,
+        'test.d/TEST0003.dat' => 1,
+        'test.d/TEST0004.dat' => 1,
+        'test.d/TEST0005.dat' => 1,
+        'test.d/TEST0006.dat' => 1,
+        'test.d/TEST0007.dat' => 1,
+        'test.d/TEST0008.dat' => 1,
+        'test.d/TEST0009.dat' => 1,
+      };
+
+      my $ok = 1;
+      my $mismatch;
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
+      }
+
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in NLST data")
+      }
+
+      # Now do it again, this time using an explicit relative path.
+
+      $conn = $client->nlst_raw('./test.d/TEST????.dat');
+      unless ($conn) {
+        die("Failed to NLST: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      $buf = '';
+      $conn->read($buf, 8192, 25);
+      eval { $conn->close() };
+
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "# response:\n$buf\n";
+      }
+
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      $res = {};
+      $names = [split(/\n/, $buf)];
+      foreach my $name (@$names) {
+        $res->{$name} = 1;
+      }
+
+      $self->assert(scalar(@$names) > 0,
+        test_msg("Expected multiple names, got 0"));
+
+      $expected = {
+        './test.d/TEST0000.dat' => 1,
+        './test.d/TEST0001.dat' => 1,
+        './test.d/TEST0002.dat' => 1,
+        './test.d/TEST0003.dat' => 1,
+        './test.d/TEST0004.dat' => 1,
+        './test.d/TEST0005.dat' => 1,
+        './test.d/TEST0006.dat' => 1,
+        './test.d/TEST0007.dat' => 1,
+        './test.d/TEST0008.dat' => 1,
+        './test.d/TEST0009.dat' => 1,
+      };
+
+      $ok = 1;
+      $mismatch = '';
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
+      }
+
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in NLST data")
+      }
+
+      $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);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
+sub nlst_glob_with_rel_path_dotdir_issue1325 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'cmds');
+
+  my $test_path = File::Spec->rel2abs("$tmpdir/.test.d");
+  mkpath($test_path);
+
+  for (my $i = 0; $i < 10; $i++) {
+    my $test_file = File::Spec->rel2abs("$test_path/TEST000$i.dat");
+    if (open(my $fh, "> $test_file")) {
+      print $fh "Hello, World!\n";
+      unless (close($fh)) {
+        die("Can't write $test_file: $!");
+      }
+
+    } else {
+      die("Can't open $test_file: $!");
+    }
+  }
+
+  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);
+
+  # 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 {
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($setup->{user}, $setup->{passwd});
+
+      my $conn = $client->nlst_raw('.test.d/TEST????.dat');
+      unless ($conn) {
+        die("Failed to NLST: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      my $buf;
+      $conn->read($buf, 8192, 25);
+      eval { $conn->close() };
+
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "# response:\n$buf\n";
+      }
+
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      my $res = {};
+      my $names = [split(/\n/, $buf)];
+      foreach my $name (@$names) {
+        $res->{$name} = 1;
+      }
+
+      $self->assert(scalar(@$names) > 0,
+        test_msg("Expected multiple names, got 0"));
+
+      my $expected = {
+        '.test.d/TEST0000.dat' => 1,
+        '.test.d/TEST0001.dat' => 1,
+        '.test.d/TEST0002.dat' => 1,
+        '.test.d/TEST0003.dat' => 1,
+        '.test.d/TEST0004.dat' => 1,
+        '.test.d/TEST0005.dat' => 1,
+        '.test.d/TEST0006.dat' => 1,
+        '.test.d/TEST0007.dat' => 1,
+        '.test.d/TEST0008.dat' => 1,
+        '.test.d/TEST0009.dat' => 1,
+      };
+
+      my $ok = 1;
+      my $mismatch;
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
+      }
+
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in NLST data")
+      }
+
+      # Now do it again, this time using an explicit relative path.
+
+      $conn = $client->nlst_raw('./.test.d/TEST????.dat');
+      unless ($conn) {
+        die("Failed to NLST: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      $buf = '';
+      $conn->read($buf, 8192, 25);
+      eval { $conn->close() };
+
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "# response:\n$buf\n";
+      }
+
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      $res = {};
+      $names = [split(/\n/, $buf)];
+      foreach my $name (@$names) {
+        $res->{$name} = 1;
+      }
+
+      $self->assert(scalar(@$names) > 0,
+        test_msg("Expected multiple names, got 0"));
+
+      $expected = {
+        './.test.d/TEST0000.dat' => 1,
+        './.test.d/TEST0001.dat' => 1,
+        './.test.d/TEST0002.dat' => 1,
+        './.test.d/TEST0003.dat' => 1,
+        './.test.d/TEST0004.dat' => 1,
+        './.test.d/TEST0005.dat' => 1,
+        './.test.d/TEST0006.dat' => 1,
+        './.test.d/TEST0007.dat' => 1,
+        './.test.d/TEST0008.dat' => 1,
+        './.test.d/TEST0009.dat' => 1,
+      };
+
+      $ok = 1;
+      $mismatch = '';
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
+      }
+
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in NLST data")
+      }
+
+      $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);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 1;


=====================================
tests/t/lib/ProFTPD/Tests/Config/AllowForeignAddress.pm
=====================================
@@ -26,11 +26,16 @@ my $TESTS = {
     test_class => [qw(forking)],
   },
 
-  fxp_denied_by_class => {
+  fxp_port_denied_by_class => {
     order => ++$order,
     test_class => [qw(forking)],
   },
 
+  fxp_pasv_denied_by_class_issue1346 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
+
   fxp_allowed => {
     order => ++$order,
     test_class => [qw(forking)],
@@ -41,11 +46,16 @@ my $TESTS = {
     test_class => [qw(forking)],
   },
 
-  fxp_allowed_by_class => {
+  fxp_port_allowed_by_class => {
     order => ++$order,
     test_class => [qw(forking)],
   },
 
+  fxp_pasv_allowed_by_class_issue1346 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
+
   fxp_allowed_2gb => {
     order => ++$order,
     test_class => [qw(forking)],
@@ -353,7 +363,7 @@ sub fxp_denied_eprt {
   test_cleanup($setup->{log_file}, $ex);
 }
 
-sub fxp_denied_by_class {
+sub fxp_port_denied_by_class {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
   my $setup = test_setup($tmpdir, 'config');
@@ -519,6 +529,142 @@ EOC
   test_cleanup($setup->{log_file}, $ex);
 }
 
+sub fxp_pasv_denied_by_class_issue1346 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'config');
+
+  my $class_name = 'allowed_fxp';
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+
+    AllowForeignAddress => $class_name,
+
+    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;
+<Class $class_name>
+  From none
+</Class>
+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 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 {
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 3);
+      $client->login($setup->{user}, $setup->{passwd});
+
+      # Attemping a data transfer should fail, due to the AllowForeignAddress
+      # class restriction.
+
+      my $conn = $client->list_raw();
+      unless ($conn) {
+        die("LIST failed: " . $client->response_code() . ' ' .
+          $client->response_msg());
+      }
+
+      my $buf;
+      $conn->read($buf, 8192, 30);
+      eval { $conn->close() };
+
+      my ($resp_code, $resp_msg);
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+      $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 = 1;
+
+      while (my $line = <$fh>) {
+        chomp($line);
+
+        if ($ENV{TEST_VERBOSE}) {
+          print STDERR "$line\n";
+        }
+
+        if ($line =~ /SECURITY VIOLATION/) {
+          $ok = 0;
+          last;
+        }
+
+        if ($line =~ /Passive connection from IP address \S+ matches control connection address; skipping <Class> '\S+'/) {
+          last;
+        }
+      }
+
+      close($fh);
+
+      $self->assert($ok, "Did not see expected log messages");
+
+    } else {
+      die("Can't read $setup->{log_file}: $!");
+    }
+  };
+  if ($@) {
+    $ex = $@;
+  }
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 sub fxp_allowed {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
@@ -781,7 +927,7 @@ sub fxp_allowed_eprt {
   test_cleanup($setup->{log_file}, $ex);
 }
 
-sub fxp_allowed_by_class {
+sub fxp_port_allowed_by_class {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
   my $setup = test_setup($tmpdir, 'config');
@@ -926,6 +1072,110 @@ EOC
   test_cleanup($setup->{log_file}, $ex);
 }
 
+sub fxp_pasv_allowed_by_class_issue1346 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'config');
+
+  my $class_name = 'allowed_fxp';
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'class:20 inet:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+
+    AllowForeignAddress => $class_name,
+
+    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;
+<Class $class_name>
+  From 127.0.0.0/8
+</Class>
+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 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 {
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 3);
+      $client->login($setup->{user}, $setup->{passwd});
+
+      # Attemping a data transfer should succeed, due to the AllowForeignAddress
+      # class restriction.
+      my $conn = $client->list_raw();
+      unless ($conn) {
+        die("Failed to LIST: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      my $buf;
+      $conn->read($buf, 8192, 30);
+      eval { $conn->close() };
+
+      my ($resp_code, $resp_msg);
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+      $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);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 sub fxp_allowed_2gb {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};


=====================================
tests/t/lib/ProFTPD/Tests/Config/PassivePorts.pm
=====================================
@@ -30,6 +30,10 @@ my $TESTS = {
     test_class => [qw(forking)],
   },
 
+  pasv_ports_random_issue1396 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
 };
 
 sub new {
@@ -493,4 +497,112 @@ EOC
   unlink($log_file);
 }
 
+sub pasv_ports_random_issue1396 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'config');
+
+  my $min_port = 30000;
+  my $max_port = 31000;
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:0 data:10',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    AuthOrder => 'mod_auth_file.c',
+
+    PassivePorts => "$min_port $max_port",
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => '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 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 {
+      my $first_pasv_port;
+
+      for (my $i = 0; $i < 3; $i++) {
+        my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+        $client->login($setup->{user}, $setup->{passwd});
+
+        my ($resp_code, $resp_msg) = $client->pasv();
+        $client->quit();
+
+        if ($ENV{TEST_VERBOSE}) {
+          print STDERR "# $resp_code $resp_msg\n";
+        }
+
+        my $expected = 227;
+        $self->assert($expected == $resp_code,
+          test_msg("Expected response code $expected, got $resp_code"));
+
+        $expected = '\(\d+,\d+,\d+,\d+,\d+,\d+\)';
+        $self->assert(qr/$expected/, $resp_msg,
+          test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+        unless ($resp_msg =~ /\(\d+,\d+,\d+,\d+,(\d+),(\d+)\)/) {
+          die("Response '$resp_msg' does not match expected pattern");
+        }
+
+        my $pasv_port = ($1 * 256) + $2;
+        $self->assert($min_port <= $pasv_port && $max_port >= $pasv_port,
+          test_msg("Expected port from $min_port to $max_port, got $pasv_port"));
+
+        if (defined($first_pasv_port)) {
+          $self->assert($pasv_port != $first_pasv_port,
+            test_msg("Expected different port than $first_pasv_port for subsequent sessions ($pasv_port)"));
+
+        } else {
+          $first_pasv_port = $pasv_port;
+        }
+      }
+    };
+    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_auth_file.pm
=====================================
@@ -122,6 +122,10 @@ my $TESTS = {
     test_class => [qw(bug forking)],
   },
 
+  auth_file_line_too_long_issue1321 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
 };
 
 sub new {
@@ -2271,4 +2275,80 @@ sub auth_file_symlink_segfault_bug4145 {
   unlink($log_file);
 }
 
+sub auth_file_line_too_long_issue1321 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  # For Issue #1321, we create a very long AuthGroupFile entry with many
+  # group names.
+
+  my $groups = 'proftpd';
+  for (my $i = 0; $i < 200; $i++) {
+    $groups .= ",quite.long.example.group.$i";
+  }
+
+  my $setup = test_setup($tmpdir, 'authfile', undef, undef, undef, undef, undef,
+    undef, $groups);
+
+  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);
+
+  # 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 {
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($setup->{user}, $setup->{passwd});
+      $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);
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 1;



View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/38d1167fa88e41a7ff792fe901dc014867d7f418

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




More information about the Pkg-proftpd-maintainers mailing list