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

Hilmar Preuße (@hilmar-guest) gitlab at salsa.debian.org
Mon Aug 30 09:41:21 BST 2021



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


Commits:
34847869 by Hilmar Preusse at 2021-08-30T10:29:22+02:00
New upstream version 1.3.7c+dfsg
- - - - -


16 changed files:

- + .github/workflows/rpm.yml
- NEWS
- README.md
- RELEASE_NOTES
- contrib/dist/rpm/proftpd.spec
- contrib/mod_radius.c
- contrib/mod_tls.c
- include/regexp.h
- include/version.h
- modules/mod_auth_file.c
- src/bindings.c
- src/dirtree.c
- src/regexp.c
- tests/api/regexp.c
- tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm
- tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm


Changes:

=====================================
.github/workflows/rpm.yml
=====================================
@@ -0,0 +1,92 @@
+name: RPM
+
+on:
+  push:
+    branches:
+      - master
+      - 1.3.7
+  pull_request:
+    branches:
+      - master
+      - 1.3.7
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        container:
+          - centos:7
+          - centos:8
+
+    container: ${{ matrix.container }}
+
+    steps:
+      - name: Checkout source code
+        uses: actions/checkout at v2
+
+      - name: Configure Centos 7 repos
+        if: ${{ matrix.container == 'centos:7' }}
+        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
+          # 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
+
+      - name: Install packages
+        run: |
+          # for builds
+          yum install -y bash gcc make imake
+          # for rpm builds
+          yum install -y rpm-build rpmlint redhat-rpm-config
+          # for dependencies
+          yum install -y GeoIP-devel
+          yum install -y gettext
+          yum install -y hiredis-devel
+          yum install -y libacl-devel
+          yum install -y libcap-devel
+          yum install -y libmemcached-devel
+          yum install -y libsodium-devel
+          yum install -y mysql-devel
+          yum install -y ncurses-devel
+          yum install -y openldap-devel cyrus-sasl-devel
+          yum install -y openssl-devel
+          yum install -y pam-devel
+          yum install -y pcre-devel
+          yum install -y postgresql-devel
+          yum install -y sqlite-devel
+          yum install -y zlib-devel
+
+      - name: Generate RPM spec
+        run: |
+          ./configure
+          make
+          # To properly name the tarball later with the version suffix, we need
+          # to query the built `proftpd` binary for its version.
+          echo "release_version=$(./proftpd -v | cut -f3 -d' ')" >> $GITHUB_ENV
+          make dist
+
+      - name: Check RPM spec
+        run: |
+          rpmlint proftpd.spec
+
+      - name: Build release tarball
+        run: |
+          cd ..
+          mv proftpd "proftpd-${{ env.release_version }}"
+          tar zcf "/tmp/proftpd-${{ env.release_version }}.tar.gz" "proftpd-${{ env.release_version }}"
+          mv "proftpd-${{ env.release_version }}" proftpd
+
+      - name: Build RPM from release tarball
+        run: |
+          rpmbuild -tb -vvv --with everything "/tmp/proftpd-${{ env.release_version }}.tar.gz"


=====================================
NEWS
=====================================
@@ -15,6 +15,18 @@
   where `N' is the issue number.
 -----------------------------------------------------------------------------
 
+1.3.7c - Released 29-Aug-2021
+--------------------------------
+- Issue 1273 - Improve mod_tls log messages for unsupported older TLS protocol
+  requests.
+- Issue 1284 - Fix memory disclosure to RADIUS servers by mod_radius.
+- Issue 1282 - Properly handle <VirtualHost> sections that use interface/device
+  names.
+- Issue 1300 - PCRE expressions with capture groups are not being handled
+  properly.
+- Issue 1307 - AuthUserFile permissions check fails during SIGHUP, causing
+  ProFTPD to stop.
+
 1.3.7b - Released 13-Jun-2021
 --------------------------------
 - Issue 1063 - FTPS data transfers using TLSv1.3 might segfault when session


=====================================
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.7-brightgreen)](https://github.com/proftpd/proftpd/releases/latest)
+[![Release](https://img.shields.io/badge/release-1.3.7b-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,15 @@ 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.7c
+---------
+
+  + Fix memory disclosure to RADIUS servers by mod_radius (Issue #1284).
+
+  + PCRE expressions with capture groups were not being handled properly
+    (Issue #1300).
+
+
 1.3.7b
 ---------
 


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


=====================================
contrib/mod_radius.c
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD: mod_radius -- a module for RADIUS authentication and accounting
- * Copyright (c) 2001-2020 TJ Saunders
+ * Copyright (c) 2001-2021 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
@@ -2319,21 +2319,28 @@ static void radius_add_passwd(radius_packet_t *packet, unsigned char type,
 
   pwlen = strlen((const char *) passwd);
 
+  /* Clear the buffers. */
+  memset(pwhash, '\0', sizeof(pwhash));
+
   if (pwlen == 0) {
     pwlen = RADIUS_PASSWD_LEN;
 
-  } if ((pwlen & (RADIUS_PASSWD_LEN - 1)) != 0) {
+  } else if ((pwlen & (RADIUS_PASSWD_LEN - 1)) != 0) {
+    /* pwlen is not a multiple of RADIUS_PASSWD_LEN, need to prepare a proper
+     * buffer.
+     */
+    memcpy(pwhash, passwd, pwlen);
 
     /* Round up the length. */
     pwlen += (RADIUS_PASSWD_LEN - 1);
 
     /* Truncate the length, as necessary. */
     pwlen &= ~(RADIUS_PASSWD_LEN - 1);
-  }
 
-  /* Clear the buffers. */
-  memset(pwhash, '\0', sizeof(pwhash));
-  memcpy(pwhash, passwd, pwlen);
+  } else {
+    /* pwlen is a multiple of RADIUS_PASSWD_LEN, we can just use it. */
+    memcpy(pwhash, passwd, pwlen);
+  }
 
   /* Find the password attribute. */
   attrib = radius_get_attrib(packet, RADIUS_PASSWORD);


=====================================
contrib/mod_tls.c
=====================================
@@ -7597,7 +7597,7 @@ static int tls_accept(conn_t *conn, unsigned char on_data) {
 
             ssl_opts = SSL_get_options(ssl);
 
-#ifdef SSL_OP_NO_SSLv2
+#if SSL_OP_NO_SSLv2
             if (ssl_opts & SSL_OP_NO_SSLv2) {
               proto_str = pstrcat(tmp_pool, proto_str, *proto_str ? ", " : "",
                 "SSLv2", NULL);
@@ -7655,6 +7655,60 @@ static int tls_accept(conn_t *conn, unsigned char on_data) {
             break;
           }
 
+#if defined(SSL_R_VERSION_TOO_LOW)
+          case SSL_R_VERSION_TOO_LOW: {
+            int client_version;
+
+            client_version = SSL_client_version(ssl);
+            switch (client_version) {
+# if defined(SSL3_VERSION) && defined(OPENSSL_NO_SSL3)
+              case SSL3_VERSION:
+                tls_log("%s: %s lacks support for client requested TLS "
+                  "protocol version: %s", msg, OPENSSL_VERSION_TEXT,
+                  SSL_get_version(ssl));
+                break;
+# endif /* SSLv3 and OPENSSL_NO_SSL3 */
+
+# if defined(TLS1_VERSION) && defined(OPENSSL_NO_TLS1)
+              case TLS1_VERSION:
+                tls_log("%s: %s lacks support for client requested TLS "
+                  "protocol version: %s", msg, OPENSSL_VERSION_TEXT,
+                  SSL_get_version(ssl));
+                break;
+# endif /* TLSv1 and OPENSSL_NO_TLS1 */
+
+# if defined(TLS1_1_VERSION) && defined(OPENSSL_NO_TLS1_1)
+              case TLS1_1_VERSION:
+                tls_log("%s: %s lacks support for client requested TLS "
+                  "protocol version: %s", msg, OPENSSL_VERSION_TEXT,
+                  SSL_get_version(ssl));
+                break;
+# endif /* TLSv1.1 and OPENSSL_NO_TLS1_1 */
+
+# if defined(TLS1_2_VERSION) && defined(OPENSSL_NO_TLS1_2)
+              case TLS1_2_VERSION:
+                tls_log("%s: %s lacks support for client requested TLS "
+                  "protocol version: %s", msg, OPENSSL_VERSION_TEXT,
+                  SSL_get_version(ssl));
+                break;
+# endif /* TLSv1.2 and OPENSSL_NO_TLS1_2 */
+
+# if defined(TLS1_3_VERSION) && defined(OPENSSL_NO_TLS1_3)
+              case TLS1_3_VERSION:
+                tls_log("%s: %s lacks support for client requested TLS "
+                  "protocol version: %s", msg, OPENSSL_VERSION_TEXT,
+                  SSL_get_version(ssl));
+                break;
+# endif /* TLSv1.3 and OPENSSL_NO_TLS1_3 */
+
+              default:
+                tls_log("%s: perhaps client requested unsupported TLS protocol "
+                  "version: %s", msg, SSL_get_version(ssl));
+            }
+            break;
+          }
+#endif /* SSL_R_VERSION_TOO_LOW */
+
           default:
             break;
         }


=====================================
include/regexp.h
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2001-2016 The ProFTPD Project team
+ * Copyright (c) 2001-2021 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
@@ -32,7 +32,7 @@
  * code.
  */
 
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
 # include <pcre.h>
 # include <pcreposix.h>
 


=====================================
include/version.h
=====================================
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2020 The ProFTPD Project team
+ * Copyright (c) 2020-2021 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		0x0001030707
-#define PROFTPD_VERSION_TEXT		"1.3.7b"
+#define PROFTPD_VERSION_NUMBER		0x0001030708
+#define PROFTPD_VERSION_TEXT		"1.3.7c"
 
 /* Module API version */
 #define PR_MODULE_API_VERSION		0x20
@@ -39,6 +39,6 @@ unsigned long pr_version_get_number(void);
 const char *pr_version_get_str(void);
 
 /* PR_STATUS is reported by --version-status -- don't ask why */
-#define PR_STATUS          		"(git)"
+#define PR_STATUS          		"(maint)"
 
 #endif /* PR_VERSION_H */


=====================================
modules/mod_auth_file.c
=====================================
@@ -1422,14 +1422,22 @@ MODRET set_authgroupfile(cmd_rec *cmd) {
   }
 
   if (!(auth_file_opts & AUTH_FILE_OPT_INSECURE_PERMS)) {
+    int res, xerrno;
+
     /* Make sure the configured file has the correct permissions.  Note that
      * AuthGroupFiles, unlike AuthUserFiles, do not contain any sensitive
      * information, and can thus be world-readable.
      */
     flags = PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE;
-    if (af_check_file(cmd->tmp_pool, cmd->argv[0], cmd->argv[1], flags) < 0) {
+
+    PRIVS_ROOT
+    res = af_check_file(cmd->tmp_pool, cmd->argv[0], cmd->argv[1], flags);
+    xerrno = errno;
+    PRIVS_RELINQUISH
+
+    if (res < 0) {
       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
-        "unable to use ", path, ": ", strerror(errno), NULL));
+        "unable to use ", path, ": ", strerror(xerrno), NULL));
     }
   }
 
@@ -1546,12 +1554,20 @@ MODRET set_authuserfile(cmd_rec *cmd) {
   }
 
   if (!(auth_file_opts & AUTH_FILE_OPT_INSECURE_PERMS)) {
+    int res, xerrno;
+
     /* Make sure the configured file has the correct permissions.  Note that
      * AuthUserFiles, unlike AuthGroupFiles, DO contain any sensitive
      * information, and thus CANNOT be world-readable.
      */
     flags = 0;
-    if (af_check_file(cmd->tmp_pool, cmd->argv[0], cmd->argv[1], flags) < 0) {
+
+    PRIVS_ROOT
+    res = af_check_file(cmd->tmp_pool, cmd->argv[0], cmd->argv[1], flags);
+    xerrno = errno;
+    PRIVS_RELINQUISH
+
+    if (res < 0) {
       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
         "unable to use ", path, ": ", strerror(errno), NULL));
     }


=====================================
src/bindings.c
=====================================
@@ -31,12 +31,25 @@ extern xaset_t *server_list;
 extern server_rec *main_server;
 
 static pr_ipbind_t *ipbind_table[PR_BINDINGS_TABLE_SIZE];
+static int ipbind_table_initialized = FALSE;
+
 static pool *binding_pool = NULL;
 static pr_ipbind_t *ipbind_default_server = NULL,
                    *ipbind_localhost_server = NULL;
 
 static const char *trace_channel = "binding";
 
+static void trace_ipbind_table(void);
+
+static void init_ipbind_table(void) {
+  if (ipbind_table_initialized == TRUE) {
+    return;
+  }
+
+  memset(ipbind_table, 0, sizeof(ipbind_table));
+  ipbind_table_initialized = TRUE;
+}
+
 /* Server cleanup callback function */
 static void server_cleanup_cb(void *conn) {
   *((conn_t **) conn) = NULL;
@@ -425,6 +438,9 @@ int pr_ipbind_create(server_rec *server, const pr_netaddr_t *addr,
     return -1;
   }
 
+  /* Ensure the ipbind table has been initialized. */
+  init_ipbind_table();
+
   i = ipbind_hash_addr(addr);
   pr_trace_msg(trace_channel, 29, "hashed address '%s' to index %u",
     pr_netaddr_get_ipstr(addr), i);
@@ -540,6 +556,9 @@ pr_ipbind_t *pr_ipbind_find(const pr_netaddr_t *addr, unsigned int port,
     return NULL;
   }
 
+  /* Ensure the ipbind table has been initialized. */
+  init_ipbind_table();
+
   i = ipbind_hash_addr(addr);
 
   for (ipbind = ipbind_table[i]; ipbind; ipbind = ipbind->ib_next) {
@@ -1147,6 +1166,7 @@ void free_bindings(void) {
   }
 
   memset(ipbind_table, 0, sizeof(ipbind_table));
+  ipbind_table_initialized = FALSE;
 
   /* Mark all listening conns as "unclaimed"; any that remaining unclaimed
    * after init_bindings() can be closed.
@@ -1378,7 +1398,8 @@ static int init_standalone_bindings(void) {
   server_rec *serv = NULL;
   unsigned char *default_server = NULL, is_default = FALSE;
 
-  memset(ipbind_table, 0, sizeof(ipbind_table));
+  /* Ensure the ipbind table has been initialized. */
+  init_ipbind_table();
 
   /* If a port is set to zero, the address/port is not bound to a socket
    * at all.


=====================================
src/dirtree.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-2021 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
@@ -2545,7 +2545,12 @@ int fixup_servers(xaset_t *list) {
       }
  
     } else {
-      s->addr = pr_netaddr_get_addr(s->pool, s->ServerAddress, NULL);
+      int flags = PR_NETADDR_GET_ADDR_FL_INCL_DEVICE;
+
+      /* Make sure we properly handle a ServerAddress that is an
+       * interface/device name here (Issue #1282).
+       */
+      s->addr = pr_netaddr_get_addr2(s->pool, s->ServerAddress, NULL, flags);
     }
 
     if (s->addr == NULL) {


=====================================
src/regexp.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-2021 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
@@ -30,7 +30,7 @@
 
 #ifdef PR_USE_REGEX
 
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
 #include <pcre.h>
 
 struct regexp_rec {
@@ -77,7 +77,7 @@ static array_header *regexp_list = NULL;
 static const char *trace_channel = "regexp";
 
 static void regexp_free(pr_regex_t *pre) {
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
   if (pre->pcre != NULL) {
 # if defined(HAVE_PCRE_PCRE_FREE_STUDY)
     pcre_free_study(pre->pcre_extra);
@@ -176,7 +176,7 @@ void pr_regexp_free(module *m, pr_regex_t *pre) {
   }
 }
 
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
 static int regexp_compile_pcre(pr_regex_t *pre, const char *pattern,
     int flags) {
   int err_offset, study_flags = 0;
@@ -237,6 +237,11 @@ int pr_regexp_compile_posix(pr_regex_t *pre, const char *pattern, int flags) {
     pattern);
   pre->pattern = pstrdup(pre->regex_pool, pattern);
 
+#if defined(REG_EXTENDED)
+  /* Enable modern ("extended") POSIX regular expressions by default. */
+  flags |= REG_EXTENDED;
+#endif /* REG_EXTENDED */
+
   pre->re = pcalloc(pre->regex_pool, sizeof(regex_t));
   res = regcomp(pre->re, pattern, flags);
 
@@ -244,7 +249,7 @@ int pr_regexp_compile_posix(pr_regex_t *pre, const char *pattern, int flags) {
 }
 
 int pr_regexp_compile(pr_regex_t *pre, const char *pattern, int flags) {
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
   int pcre_flags = 0;
 
   /* Provide a simple mapping of POSIX regcomp(3) flags to
@@ -271,7 +276,7 @@ size_t pr_regexp_error(int errcode, const pr_regex_t *pre, char *buf,
     return 0;
   }
 
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
   if (pre->pcre_errstr != NULL) {
     sstrncpy(buf, pre->pcre_errstr, bufsz);
     return strlen(pre->pcre_errstr) + 1; 
@@ -301,130 +306,184 @@ const char *pr_regexp_get_pattern(const pr_regex_t *pre) {
   return pre->pattern;
 }
 
-#ifdef PR_USE_PCRE
-static int regexp_exec_pcre(pr_regex_t *pre, const char *str,
+#if defined(PR_USE_PCRE)
+static int regexp_exec_pcre(pr_regex_t *pre, const char *text,
     size_t nmatches, regmatch_t *matches, int flags, unsigned long match_limit,
     unsigned long match_limit_recursion) {
+  int res, ovector_count = 0, *ovector = NULL;
+  size_t text_len;
+  pool *tmp_pool = NULL;
 
-  if (pre->pcre != NULL) {
-    int res;
-    size_t str_len;
+  if (pre->pcre == NULL) {
+    errno = EINVAL;
+    return -1;
+  }
 
-    str_len = strlen(str);
+  text_len = strlen(text);
 
-    /* Use the default match limits, if set and if the caller did not
-     * explicitly provide limits.
-     */
-    if (match_limit == 0) {
-      match_limit = pcre_match_limit;
-    }
+  /* Use the default match limits, if set and if the caller did not
+   * explicitly provide limits.
+   */
+  if (match_limit == 0) {
+    match_limit = pcre_match_limit;
+  }
 
-    if (match_limit_recursion == 0) {
-      match_limit_recursion = pcre_match_limit_recursion;
+  if (match_limit_recursion == 0) {
+    match_limit_recursion = pcre_match_limit_recursion;
+  }
+
+  if (match_limit > 0) {
+    if (pre->pcre_extra == NULL) {
+      pre->pcre_extra = pcalloc(pre->regex_pool, sizeof(pcre_extra));
     }
 
-    if (match_limit > 0) {
-      if (pre->pcre_extra == NULL) {
-        pre->pcre_extra = pcalloc(pre->regex_pool, sizeof(pcre_extra));
-      }
+    pre->pcre_extra->flags |= PCRE_EXTRA_MATCH_LIMIT;
+    pre->pcre_extra->match_limit = match_limit;
+  }
 
-      pre->pcre_extra->flags |= PCRE_EXTRA_MATCH_LIMIT;
-      pre->pcre_extra->match_limit = match_limit;
+  if (match_limit_recursion > 0) {
+    if (pre->pcre_extra == NULL) {
+      pre->pcre_extra = pcalloc(pre->regex_pool, sizeof(pcre_extra));
     }
 
-    if (match_limit_recursion > 0) {
-      if (pre->pcre_extra == NULL) {
-        pre->pcre_extra = pcalloc(pre->regex_pool, sizeof(pcre_extra));
-      }
+    pre->pcre_extra->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION;
+    pre->pcre_extra->match_limit_recursion = match_limit_recursion;
+  }
+
+  if (nmatches > 0 &&
+      matches != NULL) {
+    tmp_pool = make_sub_pool(pre->regex_pool);
+    pr_pool_tag(tmp_pool, "regexp tmp pool");
+
+    ovector_count = nmatches;
+    ovector = pcalloc(tmp_pool, sizeof(int) * nmatches * 3);
+  }
 
-      pre->pcre_extra->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION;
-      pre->pcre_extra->match_limit_recursion = match_limit_recursion;
+  pr_trace_msg(trace_channel, 9,
+    "executing PCRE regex '%s' against subject '%s'",
+    pr_regexp_get_pattern(pre), text);
+  res = pcre_exec(pre->pcre, pre->pcre_extra, text, text_len, 0, flags,
+    ovector, ovector_count);
+
+  if (res < 0) {
+    if (tmp_pool != NULL) {
+      destroy_pool(tmp_pool);
     }
 
-    pr_trace_msg(trace_channel, 9,
-      "executing PCRE regex '%s' against subject '%s'",
-      pr_regexp_get_pattern(pre), str);
-    res = pcre_exec(pre->pcre, pre->pcre_extra, str, str_len, 0, flags,
-      NULL, 0);
-
-    if (res < 0) {
-      if (pr_trace_get_level(trace_channel) >= 9) {
-        const char *reason = "unknown";
-
-        switch (res) {
-          case PCRE_ERROR_NOMATCH:
-            reason = "subject did not match pattern";
-            break;
-
-          case PCRE_ERROR_NULL:
-            reason = "null regex or subject";
-            break;
-
-          case PCRE_ERROR_BADOPTION:
-            reason = "unsupported options bit";
-            break;
-
-          case PCRE_ERROR_BADMAGIC:
-            reason = "bad magic number in regex";
-            break;
-
-          case PCRE_ERROR_UNKNOWN_OPCODE:
-          case PCRE_ERROR_INTERNAL:
-            reason = "internal PCRE error or corrupted regex";
-            break;
-
-          case PCRE_ERROR_NOMEMORY:
-            reason = "not enough memory for backreferences";
-            break;
-
-          case PCRE_ERROR_MATCHLIMIT:
-            reason = "match limit reached/exceeded";
-            break;
-
-          case PCRE_ERROR_RECURSIONLIMIT:
-            reason = "match limit recursion reached/exceeded";
-            break;
-
-          case PCRE_ERROR_BADUTF8:
-            reason = "invalid UTF8 subject used";
-            break;
-
-          case PCRE_ERROR_PARTIAL:
-            reason = "subject matched only partially; PCRE_PARTIAL flag not used";
-            break;
-        }
-
-        pr_trace_msg(trace_channel, 9,
-          "PCRE regex '%s' failed to match subject '%s': %s",
-          pr_regexp_get_pattern(pre), str, reason);
-
-      } else {
-        pr_trace_msg(trace_channel, 9,
-          "PCRE regex '%s' successfully matched subject '%s'",
-          pr_regexp_get_pattern(pre), str);
+    if (pr_trace_get_level(trace_channel) >= 9) {
+      const char *reason = "unknown";
+
+      switch (res) {
+        case PCRE_ERROR_NOMATCH:
+          reason = "subject did not match pattern";
+          break;
+
+        case PCRE_ERROR_NULL:
+          reason = "null regex or subject";
+          break;
+
+        case PCRE_ERROR_BADOPTION:
+          reason = "unsupported options bit";
+          break;
+
+        case PCRE_ERROR_BADMAGIC:
+          reason = "bad magic number in regex";
+          break;
+
+        case PCRE_ERROR_UNKNOWN_OPCODE:
+        case PCRE_ERROR_INTERNAL:
+          reason = "internal PCRE error or corrupted regex";
+          break;
+
+        case PCRE_ERROR_NOMEMORY:
+          reason = "not enough memory for backreferences";
+          break;
+
+        case PCRE_ERROR_MATCHLIMIT:
+          reason = "match limit reached/exceeded";
+          break;
+
+        case PCRE_ERROR_RECURSIONLIMIT:
+          reason = "match limit recursion reached/exceeded";
+          break;
+
+        case PCRE_ERROR_BADUTF8:
+          reason = "invalid UTF8 subject used";
+          break;
+
+        case PCRE_ERROR_PARTIAL:
+          reason = "subject matched only partially; PCRE_PARTIAL flag not used";
+          break;
       }
+
+      pr_trace_msg(trace_channel, 9,
+        "PCRE regex '%s' failed to match subject '%s': %s",
+        pr_regexp_get_pattern(pre), text, reason);
     }
 
     return res;
   }
 
-  errno = EINVAL;
-  return -1;
+  pr_trace_msg(trace_channel, 9,
+    "PCRE regex '%s' successfully matched subject '%s'",
+    pr_regexp_get_pattern(pre), text);
+
+  if (ovector_count > 0) {
+    /* Populate the provided POSIX regmatch_t array with the PCRE data. */
+    register unsigned int i;
+
+    for (i = 0; i < res; i++) {
+      matches[i].rm_so = ovector[i * 2];
+      matches[i].rm_eo = ovector[(i * 2) + 1];
+    }
+
+    /* Ensure the remaining items are set to proper defaults as well. */
+    for (; i < nmatches; i++) {
+      matches[i].rm_so = matches[i].rm_eo = -1;
+    }
+  }
+
+  destroy_pool(tmp_pool);
+
+  if (matches != NULL &&
+      pr_trace_get_level(trace_channel) >= 20) {
+    register unsigned int i;
+
+    for (i = 0; i < nmatches; i++) {
+      int match_len;
+      const char *match_text;
+
+      if (matches[i].rm_so == -1 ||
+          matches[i].rm_eo == -1) {
+        break;
+      }
+
+      match_text = &(text[matches[i].rm_so]);
+      match_len = matches[i].rm_eo - matches[i].rm_so;
+
+      pr_trace_msg(trace_channel, 20,
+        "PCRE regex '%s' match #%u: %.*s (start %ld, len %d)",
+        pr_regexp_get_pattern(pre), i, (int) match_len, match_text,
+        (long) matches[i].rm_so, match_len);
+    }
+  }
+
+  return 0;
 }
 #endif /* PR_USE_PCRE */
 
-static int regexp_exec_posix(pr_regex_t *pre, const char *str,
+static int regexp_exec_posix(pr_regex_t *pre, const char *text,
     size_t nmatches, regmatch_t *matches, int flags) {
   int res;
 
   pr_trace_msg(trace_channel, 9,
     "executing POSIX regex '%s' against subject '%s'",
-    pr_regexp_get_pattern(pre), str);
-  res = regexec(pre->re, str, nmatches, matches, flags);
+    pr_regexp_get_pattern(pre), text);
+  res = regexec(pre->re, text, nmatches, matches, flags);
   if (res == 0) {
     pr_trace_msg(trace_channel, 9,
       "POSIX regex '%s' successfully matched subject '%s'",
-      pr_regexp_get_pattern(pre), str);
+      pr_regexp_get_pattern(pre), text);
 
      if (matches != NULL &&
          pr_trace_get_level(trace_channel) >= 20) {
@@ -439,7 +498,7 @@ static int regexp_exec_posix(pr_regex_t *pre, const char *str,
            break;
          }
 
-         match_text = &(str[matches[i].rm_so]);
+         match_text = &(text[matches[i].rm_so]);
          match_len = matches[i].rm_eo - matches[i].rm_so;
 
          pr_trace_msg(trace_channel, 20,
@@ -450,43 +509,41 @@ static int regexp_exec_posix(pr_regex_t *pre, const char *str,
      }
 
   } else {
-    const char *reason = "unknown";
-
     if (pr_trace_get_level(trace_channel) >= 9) {
-      switch (res) {
-        case REG_NOMATCH:
-          reason = "subject did not match pattern";
-          break;
-      }
-    }
+      const char *reason = "subject did not match pattern";
 
-    pr_trace_msg(trace_channel, 9,
-      "POSIX regex '%s' failed to match subject '%s': %s",
-       pr_regexp_get_pattern(pre), str, reason);
+      /* NOTE: Expectation of `res` values here are mixed when PCRE
+       * support, and the <pcreposix.h> header, are involved.
+       */
+
+      pr_trace_msg(trace_channel, 9,
+        "POSIX regex '%s' failed to match subject '%s': %s (%d)",
+         pr_regexp_get_pattern(pre), text, reason, res);
+    }
   }
 
   return res;
 }
 
-int pr_regexp_exec(pr_regex_t *pre, const char *str, size_t nmatches,
+int pr_regexp_exec(pr_regex_t *pre, const char *text, size_t nmatches,
     regmatch_t *matches, int flags, unsigned long match_limit,
     unsigned long match_limit_recursion) {
   int res;
 
   if (pre == NULL ||
-      str == NULL) {
+      text == NULL) {
     errno = EINVAL;
     return -1;
   }
 
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
   if (pre->pcre != NULL) {
-    return regexp_exec_pcre(pre, str, nmatches, matches, flags, match_limit,
+    return regexp_exec_pcre(pre, text, nmatches, matches, flags, match_limit,
       match_limit_recursion);
   }
 #endif /* PR_USE_PCRE */
 
-  res = regexp_exec_posix(pre, str, nmatches, matches, flags);
+  res = regexp_exec_posix(pre, text, nmatches, matches, flags);
 
   /* Make sure that we return a negative value to indicate a failed match;
    * PCRE already does this.
@@ -501,7 +558,7 @@ int pr_regexp_exec(pr_regex_t *pre, const char *str, size_t nmatches,
 int pr_regexp_set_limits(unsigned long match_limit,
     unsigned long match_limit_recursion) {
 
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
   pcre_match_limit = match_limit;
   pcre_match_limit_recursion = match_limit_recursion;
 #endif
@@ -521,7 +578,7 @@ void init_regexp(void) {
   pr_event_register(NULL, "core.restart", regexp_restart_ev, NULL);
   pr_event_register(NULL, "core.exit", regexp_exit_ev, NULL);
 
-#ifdef PR_USE_PCRE
+#if defined(PR_USE_PCRE)
   pr_log_debug(DEBUG2, "using PCRE %s", pcre_version());
 #endif /* PR_USE_PCRE */
 }


=====================================
tests/api/regexp.c
=====================================
@@ -242,7 +242,7 @@ START_TEST (regexp_exec_test) {
   pre = pr_regexp_alloc(NULL);
 
   pattern = "^foo";
-  res = pr_regexp_compile(pre, pattern, 0);
+  res = pr_regexp_compile(pre, pattern, REG_ICASE);
   fail_unless(res == 0, "Failed to compile regex pattern '%s'", pattern);
 
   res = pr_regexp_exec(pre, NULL, 0, NULL, 0, 0, 0);
@@ -256,8 +256,11 @@ START_TEST (regexp_exec_test) {
   res = pr_regexp_exec(pre, str, 0, NULL, 0, 0, 0);
   fail_unless(res == 0, "Failed to match string");
 
-  pr_regexp_free(NULL, pre);
+  str = "FOOBAR";
+  res = pr_regexp_exec(pre, str, 0, NULL, 0, 0, 0);
+  fail_unless(res == 0, "Failed to match string");
 
+  pr_regexp_free(NULL, pre);
   pre = pr_regexp_alloc(NULL);
 
   pattern = "^foo";
@@ -271,13 +274,122 @@ START_TEST (regexp_exec_test) {
   res = pr_regexp_exec(pre, str, 0, NULL, 0, 0, 0);
   fail_unless(res != 0, "Matched string unexpectedly");
 
+  str = "foobar";
+  res = pr_regexp_exec(pre, str, 0, NULL, 0, 0, 0);
+  fail_unless(res == 0, "Failed to match string");
+
+#if !defined(PR_USE_PCRE)
+  /* Note that when PCRE support is used, behavior of POSIX matching may be
+   * surprising; I suspect it relates to the overrides in <pcreposix.h>.
+   */
   str = "FOOBAR";
   res = pr_regexp_exec(pre, str, 0, NULL, 0, 0, 0);
   fail_unless(res == 0, "Failed to match string");
+#endif /* PR_USE_PCRE */
+
+  pr_regexp_free(NULL, pre);
+}
+END_TEST
+
+#if !defined(PR_USE_PCRE)
+START_TEST (regexp_capture_posix_test) {
+  register unsigned int i;
+  pr_regex_t *pre = NULL;
+  int captured = FALSE, res;
+  char *pattern, *str;
+  size_t nmatches;
+  regmatch_t *matches;
+
+  pre = pr_regexp_alloc(NULL);
+
+  pattern = "(.*)";
+  res = pr_regexp_compile_posix(pre, pattern, 0);
+  fail_unless(res == 0, "Failed to compile regex pattern '%s'", pattern);
+
+  nmatches = 10;
+  matches = pcalloc(p, sizeof(regmatch_t) * nmatches);
+
+  str = "foobar";
+  res = pr_regexp_exec(pre, str, nmatches, matches, 0, 0, 0);
+  fail_unless(res == 0, "Failed to match string");
+
+  for (i = 0; i < nmatches; i++) {
+    int match_len;
+    const char *match_text;
+
+    if (matches[i].rm_so == -1 ||
+        matches[i].rm_eo == -1) {
+      break;
+    }
+
+    match_text = &(str[matches[i].rm_so]);
+    match_len = matches[i].rm_eo - matches[i].rm_so;
+
+    fail_unless(strcmp(match_text, str) == 0,
+      "Expected matched text '%s', got '%s'", str, match_text);
+    fail_unless(match_len == 6,
+      "Expected match text len 6, got %d", match_len);
+
+    captured = TRUE;
+  }
+
+  fail_unless(captured == TRUE,
+    "POSIX regex failed to capture expected groups");
+
+  pr_regexp_free(NULL, pre);
+}
+END_TEST
+#endif /* PR_USE_PCRE */
+
+#if defined(PR_USE_PCRE)
+START_TEST (regexp_capture_pcre_test) {
+  register unsigned int i;
+  pr_regex_t *pre = NULL;
+  int captured = FALSE, res;
+  char *pattern, *str;
+  size_t nmatches;
+  regmatch_t *matches;
+
+  pre = pr_regexp_alloc(NULL);
+
+  pattern = "(.*)";
+  res = pr_regexp_compile(pre, pattern, 0);
+  fail_unless(res == 0, "Failed to compile regex pattern '%s'", pattern);
+
+  nmatches = 10;
+  matches = pcalloc(p, sizeof(regmatch_t) * nmatches);
+
+  str = "foobar";
+  res = pr_regexp_exec(pre, str, nmatches, matches, 0, 0, 0);
+  fail_unless(res == 0, "Failed to match string");
+
+  for (i = 0; i < nmatches; i++) {
+    int match_len;
+    const char *match_text;
+
+    if (matches[i].rm_so == -1 ||
+        matches[i].rm_eo == -1) {
+      break;
+    }
+
+    match_text = &(str[matches[i].rm_so]);
+    match_len = matches[i].rm_eo - matches[i].rm_so;
+
+    fail_unless(strcmp(match_text, str) == 0,
+      "Expected matched text '%s', got '%s' (i = %u)", str, match_text, i);
+    fail_unless(match_len == 6,
+      "Expected match text len 6, got %d (i = %u)", match_len, i);
+
+    captured = TRUE;
+  }
+
+  fail_unless(captured == TRUE,
+    "PCRE regex failed to capture expected groups");
 
   pr_regexp_free(NULL, pre);
 }
 END_TEST
+#endif /* PR_USE_PCRE */
 
 START_TEST (regexp_cleanup_test) {
   pr_regex_t *pre, *pre2, *pre3;
@@ -329,6 +441,12 @@ Suite *tests_get_regexp_suite(void) {
   tcase_add_test(testcase, regexp_compile_test);
   tcase_add_test(testcase, regexp_compile_posix_test);
   tcase_add_test(testcase, regexp_exec_test);
+#if !defined(PR_USE_PCRE)
+  tcase_add_test(testcase, regexp_capture_posix_test);
+#endif /* !PR_USE_PCRE */
+#if defined(PR_USE_PCRE)
+  tcase_add_test(testcase, regexp_capture_pcre_test);
+#endif /* PR_USE_PCRE */
   tcase_add_test(testcase, regexp_get_pattern_test);
   tcase_add_test(testcase, regexp_set_limits_test);
   tcase_add_test(testcase, regexp_cleanup_test);


=====================================
tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm
=====================================
@@ -213,6 +213,11 @@ my $TESTS = {
     test_class => [qw(bug feature_pcre forking rootprivs)],
   },
 
+  rewrite_using_pcre_issue1300 => {
+    order => ++$order,
+    test_class => [qw(bug feature_pcre forking)],
+  },
+
 };
 
 sub new {
@@ -2966,23 +2971,8 @@ sub rewrite_cond_nc_flags {
 sub rewrite_map_fifo_bug3611 {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'rewrite');
 
-  my $config_file = "$tmpdir/rewrite.conf";
-  my $pid_file = File::Spec->rel2abs("$tmpdir/rewrite.pid");
-  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/rewrite.scoreboard");
-
-  my $log_file = test_get_logfile();
-
-  my $auth_user_file = File::Spec->rel2abs("$tmpdir/rewrite.passwd");
-  my $auth_group_file = File::Spec->rel2abs("$tmpdir/rewrite.group");
-
-  my $user = 'proftpd';
-  my $passwd = 'test';
-  my $group = 'ftpd';
-  my $home_dir = File::Spec->rel2abs($tmpdir);
-  my $uid = 500;
-  my $gid = 500;
- 
   my $sub_dir = File::Spec->rel2abs("$tmpdir/tmp");
   mkpath($sub_dir);
 
@@ -3003,37 +2993,33 @@ sub rewrite_map_fifo_bug3611 {
   # 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_dir)) {
-      die("Can't set perms on $home_dir to 0755: $!");
+    unless (chmod(0755, $sub_dir)) {
+      die("Can't set perms on $sub_dir to 0755: $!");
     }
 
-    unless (chown($uid, $gid, $home_dir, $sub_dir)) {
-      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    unless (chown($setup->{uid}, $setup->{gid}, $sub_dir)) {
+      die("Can't set owner of $sub_dir 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 $fifo_script = File::Spec->rel2abs('t/etc/modules/mod_rewrite/reverse.pl');
 
-  my $fifo = File::Spec->rel2abs("$home_dir/test.fifo");
+  my $fifo = File::Spec->rel2abs("$setup->{home_dir}/test.fifo");
   unless (POSIX::mkfifo($fifo, 0666)) {
     die("Can't create fifo $fifo: $!");
   }
 
-  my $fifo_pidfile = File::Spec->rel2abs("$home_dir/fifo.pid");
+  my $fifo_pidfile = File::Spec->rel2abs("$setup->{home_dir}/fifo.pid");
 
   my $config = {
-    PidFile => $pid_file,
-    ScoreboardFile => $scoreboard_file,
-    SystemLog => $log_file,
-    TraceLog => $log_file,
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
     Trace => 'DEFAULT:10',
 
-    AuthUserFile => $auth_user_file,
-    AuthGroupFile => $auth_group_file,
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
 
     IfModules => {
       'mod_delay.c' => {
@@ -3042,7 +3028,7 @@ sub rewrite_map_fifo_bug3611 {
 
       'mod_rewrite.c' => [
         'RewriteEngine on',
-        "RewriteLog $log_file",
+        "RewriteLog $setup->{log_file}",
 
         'RewriteCondition %m !PASS',
         "RewriteMap reverse fifo:$fifo",
@@ -3051,7 +3037,8 @@ sub rewrite_map_fifo_bug3611 {
     },
   };
 
-  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
@@ -3068,26 +3055,24 @@ sub rewrite_map_fifo_bug3611 {
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
+      # Allow for server startup
+      sleep(1);
+
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
 
-      my $name = join('', reverse(split//, $user));
-      $client->login($name, $passwd);
+      my $name = join('', reverse(split//, $setup->{user}));
+      $client->login($name, $setup->{passwd});
       $client->type('binary');
 
       # Send the path in reverse; the rewrite FIFO should reverse
       # everything.
-      my ($resp_code, $resp_msg);
-
       my $path = join('', reverse(split(//, $test_file)));
-      ($resp_code, $resp_msg) = $client->stat($path);
+      my ($resp_code, $resp_msg) = $client->stat($path);
       
-      my $expected;
-
-      $expected = 211;
+      my $expected = 213;
       $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
+        test_msg("Expected response code $expected, got $resp_code"));
     };
-
     if ($@) {
       $ex = $@;
     }
@@ -3097,9 +3082,9 @@ sub rewrite_map_fifo_bug3611 {
 
   } else {
     # Start the FIFO script
-    `$fifo_script --verbose --fifo $fifo --pidfile $fifo_pidfile >> $log_file 2>&1 &`;
+    `$fifo_script --verbose --fifo $fifo --pidfile $fifo_pidfile >> $setup->{log_file} 2>&1 &`;
 
-    eval { server_wait($config_file, $rfh) };
+    eval { server_wait($setup->{config_file}, $rfh) };
     if ($@) {
       warn($@);
 
@@ -3125,9 +3110,6 @@ sub rewrite_map_fifo_bug3611 {
     exit 0;
   }
 
-  # Stop server
-  server_stop($pid_file);
-
   if (open(my $fh, "< $fifo_pidfile")) {
     my $fifo_pid = <$fh>;
     chomp($fifo_pid);
@@ -3136,16 +3118,11 @@ sub rewrite_map_fifo_bug3611 {
     close($fh);
   }
 
+  # Stop server
+  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 rewrite_rule_replaceall_backslash_with_slash {
@@ -3439,23 +3416,8 @@ sub rewrite_map_max_replace_bug3721 {
 sub rewrite_cond_time_var_bug3673 {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'rewrite');
 
-  my $config_file = "$tmpdir/rewrite.conf";
-  my $pid_file = File::Spec->rel2abs("$tmpdir/rewrite.pid");
-  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/rewrite.scoreboard");
-
-  my $log_file = test_get_logfile();
-
-  my $auth_user_file = File::Spec->rel2abs("$tmpdir/rewrite.passwd");
-  my $auth_group_file = File::Spec->rel2abs("$tmpdir/rewrite.group");
-
-  my $user = 'proftpd';
-  my $passwd = 'test';
-  my $group = 'ftpd';
-  my $home_dir = File::Spec->rel2abs($tmpdir);
-  my $uid = 500;
-  my $gid = 500;
- 
   my $sub_dir = File::Spec->rel2abs("$tmpdir/tmp");
   mkpath($sub_dir);
 
@@ -3476,34 +3438,32 @@ sub rewrite_cond_time_var_bug3673 {
   # 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_dir)) {
-      die("Can't set perms on $home_dir to 0755: $!");
+    unless (chmod(0755, $sub_dir)) {
+      die("Can't set perms on $sub_dir to 0755: $!");
     }
 
-    unless (chown($uid, $gid, $home_dir, $sub_dir)) {
-      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    unless (chown($setup->{uid}, $setup->{gid}, $sub_dir)) {
+      die("Can't set owner of $sub_dir 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);
-
   require DateTime;
 
   my $dt = DateTime->now();
   $dt->set_time_zone('America/Los_Angeles');
-  $dt->subtract(seconds => 3);
+  $dt->add(seconds => 30);
 
   my $timestamp = ($dt->ymd('') . $dt->hms(''));
 
   my $config = {
-    PidFile => $pid_file,
-    ScoreboardFile => $scoreboard_file,
-    SystemLog => $log_file,
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'regexp:20 rewrite:20',
 
-    AuthUserFile => $auth_user_file,
-    AuthGroupFile => $auth_group_file,
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
 
     IfModules => {
       'mod_delay.c' => {
@@ -3512,7 +3472,7 @@ sub rewrite_cond_time_var_bug3673 {
 
       'mod_rewrite.c' => [
         'RewriteEngine on',
-        "RewriteLog $log_file",
+        "RewriteLog $setup->{log_file}",
 
         'RewriteMap replace int:replaceall',
 
@@ -3523,7 +3483,8 @@ sub rewrite_cond_time_var_bug3673 {
     },
   };
 
-  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
@@ -3540,27 +3501,26 @@ sub rewrite_cond_time_var_bug3673 {
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
-      my ($resp_code, $resp_msg);
+      # Allow for server startup
+      sleep(1);
 
-      $client->login($user, $passwd);
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($setup->{user}, $setup->{passwd});
       $client->type('binary');
 
       # Send the path with spaces; the rewrite rules should handle it
-      ($resp_code, $resp_msg) = $client->size("tmp/test file here.txt");
-
-      my $expected;
+      my ($resp_code, $resp_msg) = $client->size("tmp/test file here.txt");
 
-      $expected = 213;
+      my $expected = 213;
       $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
+        test_msg("Expected response code $expected, got $resp_code"));
 
       $expected = '14';
       $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
-    };
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
 
+      $client->quit();
+    };
     if ($@) {
       $ex = $@;
     }
@@ -3569,7 +3529,7 @@ sub rewrite_cond_time_var_bug3673 {
     $wfh->flush();
 
   } else {
-    eval { server_wait($config_file, $rfh) };
+    eval { server_wait($setup->{config_file}, $rfh) };
     if ($@) {
       warn($@);
       exit 1;
@@ -3579,18 +3539,10 @@ sub rewrite_cond_time_var_bug3673 {
   }
 
   # 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 rewrite_cond_time_year_var_bug3673 {
@@ -5996,4 +5948,97 @@ sub rewrite_using_pcre_bug4017 {
   unlink($log_file);
 }
 
+sub rewrite_using_pcre_issue1300 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'rewrite');
+
+  my $test_dir = File::Spec->rel2abs("$tmpdir/folder1/folder2");
+  mkpath($test_dir);
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'regexp:20 rewrite:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+    DefaultChdir => '~',
+
+    DenyFilter => '\*.*/',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_rewrite.c' => [
+        'RewriteEngine on',
+        "RewriteLog $setup->{log_file}",
+        'RewriteMap replace int:replaceall',
+
+        'RewriteCondition %m RETR|SIZE|LIST|CWD',
+        'RewriteRule (.*) "${replace:!$1!\\\\!/}"',
+      ],
+    },
+  };
+
+  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 {
+      # Allow server to start up
+      sleep(1);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($setup->{user}, $setup->{passwd});
+
+      my ($resp_code, $resp_msg) = $client->cwd('folder1\folder2');
+
+      my $expected = 250;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $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/Modules/mod_tls.pm
=====================================
@@ -9,6 +9,7 @@ use File::Copy;
 use File::Path qw(mkpath);
 use File::Spec;
 use IO::Handle;
+use IPC::Open3;
 use Socket;
 
 use ProFTPD::TestSuite::FTP;
@@ -474,6 +475,10 @@ my $TESTS = {
     test_class => [qw(bug forking)],
   },
 
+  tls_old_protocols_issue1273 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
 };
 
 sub new {
@@ -13489,4 +13494,175 @@ sub tls_fxp_issue618 {
   test_cleanup($setup->{log_file}, $ex);
 }
 
+sub tls_old_protocols_issue1273 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'tls');
+
+  my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem');
+  my $ca_file = File::Spec->rel2abs('t/etc/modules/mod_tls/ca-cert.pem');
+
+  my $tls_opts = 'NoSessionReuseRequired UseImplicitSSL';
+  if ($ENV{TEST_VERBOSE}) {
+    $tls_opts .= ' EnableDiags';
+  }
+
+  my $timeout_idle = 15;
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'command:20 response:20 data:20 netio:20 tls:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+
+    AllowForeignAddress => 'on',
+    AllowOverwrite => 'on',
+    TimeoutIdle => $timeout_idle,
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_tls.c' => {
+        TLSEngine => 'on',
+        TLSLog => $setup->{log_file},
+        TLSProtocol => 'SSLv23',
+        TLSRequired => 'on',
+        TLSRSACertificateFile => $cert_file,
+        TLSCACertificateFile => $ca_file,
+        TLSOptions => $tls_opts,
+      },
+    },
+  };
+
+  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);
+
+      # We use an older OpenSSL version for the older protocols.
+      # Allow server to start up
+      my $openssl = '/Users/tj/local/openssl-0.9.8d/bin/openssl';
+
+      # Explicitly use SSLv3, which has been disabled by default in
+      # OpenSSL-1.1.x; see:
+      #   https://github.com/openssl/openssl/issues/4989
+
+      my @cmd = (
+        $openssl,
+        's_client',
+        '-connect',
+        "127.0.0.1:$port",
+        '-ssl3',
+      );
+
+      my $tls_rh = IO::Handle->new();
+      my $tls_wh = IO::Handle->new();
+      my $tls_eh = IO::Handle->new();
+
+      $tls_wh->autoflush(1);
+
+      local $SIG{CHLD} = 'DEFAULT';
+
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "Executing: ", join(' ', @cmd), "\n";
+      }
+
+      my $tls_pid = open3($tls_wh, $tls_rh, $tls_eh, @cmd);
+      print $tls_wh "quit\n";
+      waitpid($tls_pid, 0);
+
+      my ($res, $cipher_str, $err_str, $out_str);
+      if ($? >> 8) {
+        $err_str = join('', <$tls_eh>);
+        $res = 0;
+
+      } else {
+        my $output = [<$tls_rh>];
+
+        if ($ENV{TEST_VERBOSE}) {
+          $out_str = join('', @$output);
+          print STDERR "Stdout: $out_str\n";
+
+          $err_str = join('', <$tls_eh>);
+          print STDERR "Stderr: $err_str\n";
+        }
+
+        $res = 1;
+      }
+
+      unless ($res) {
+        die("Can't talk to server: $err_str");
+      }
+    };
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 5) };
+    if ($@) {
+      warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($setup->{pid_file});
+  $self->assert_child_ok($pid);
+  test_cleanup($setup->{log_file}, $ex) if $ex;
+
+  eval {
+    if (open(my $fh, "< $setup->{log_file}")) {
+      my $seen = 0;
+
+      while (my $line = <$fh>) {
+        chomp($line);
+
+        if ($line =~ /OpenSSL.*?lacks support for client requested/) {
+          $seen = 1;
+          last;
+        }
+      }
+
+      close($fh);
+
+      $self->assert($seen, test_msg("Did not see expected log message"));
+
+    } else {
+      die("Can't read $setup->{log_file}: $!");
+    }
+  };
+  if ($@) {
+    $ex = $@;
+  }
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 1;



View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/348478693ccb37d8a3c0f35172bb037826cd7acc

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




More information about the Pkg-proftpd-maintainers mailing list