commit a3d2b9e0d41c2d5c0487dba0ea5d5178cdfae9c7
Author: Sergey Poznyakoff <gray@gnu.org>
Date:   Tue Aug 13 17:48:39 2024 +0300
Forwarded: not-needed

    Don't use pcreposix nor pcre2posix.
    
    Pcre2posix is not thread safe. Provide custom wrapper data types
    and functions that make it possible to use pcre2 API directly.
    
    * am/pcre.m4: New file.
    * am/pcreposix.m4: Delete.
    * configure.ac: Use PND_PCRE macro.
    * src/regex_std.c: New file.
    * src/regex_pcre.c: New file.
    * src/regex_pcre2.c: New file.
    * src/Makefile.am: Add new sources.
    * src/extern.h: Use new regez wrapper types.
    * src/pound.h: Likewise.
    * src/config.c: Use new regex wrapper functions.
    * src/http.c: Likewise.
    * src/pound.c: Likewise.
    * src/svc.c: Likewise.

diff --git a/am/pcre.m4 b/am/pcre.m4
new file mode 100644
index 0000000..d975f1d
--- /dev/null
+++ b/am/pcre.m4
@@ -0,0 +1,69 @@
+# SYNOPSIS
+#
+#   PND_PCRE
+#
+# DESCRIPTION
+#
+#   Checks whether the pcre library and its headers are available.
+#   Prefers libpcre2 over libpcre.  The --enable-pcre option can
+#   be used to enable, disable, or force the use of libpcre version 1
+#   (--enable-pcre=1).  Upon return, the status_pcre shell variable is
+#   set to indicate the result:
+#
+#   .  no - neither library has been found
+#   .  1  - libpcre is found
+#   .  2  - libpcre2 is found
+#
+#   On success, the HAVE_LIBPCRE m4 macro is defined to the version
+#   of the library used (1 or 2).
+#
+#   Substitution variables PCRE_CFLAGS and PCRE_LIBS are defined
+#   to compiler and loader flags needed in order to build with the version
+#   of the library located.
+#
+# LICENSE
+#
+# Copyright (C) 2023-2024 Sergey Poznyakoff
+#
+# 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
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+AC_DEFUN([PND_PCRE],
+[AC_ARG_ENABLE([pcre],
+ [AS_HELP_STRING([--enable-pcre],[enable or disable using the pcre library (default: enabled if available)])],
+ [status_pcre=${enableval}],
+ [status_pcre=yes])
+
+ AH_TEMPLATE([HAVE_LIBPCRE],[Define to the version of libpcreposix to use])
+
+ AC_SUBST([PCRE_CFLAGS])
+ AC_SUBST([PCRE_LIBS])
+ if test "$status_pcre" != no; then
+   AC_PATH_PROG([PCRE2_CONFIG],[pcre2-config],[])
+   if test "$status_pcre" != 1 && test -n "$PCRE2_CONFIG"; then
+     PCRE_CFLAGS=$($PCRE2_CONFIG --cflags)
+     PCRE_LIBS=$($PCRE2_CONFIG --libs8)
+     status_pcre=2
+   else
+     AC_CHECK_HEADERS([pcre.h pcre/pcre.h])
+     AC_CHECK_LIB([pcre],[pcre_compile],
+       [PCRE_LIBS=-lpcre
+	status_pcre=1],
+       [status_pcre=no])
+   fi
+
+   case "$status_pcre" in
+   1|2)  AC_DEFINE_UNQUOTED([HAVE_LIBPCRE],[$status_pcre])
+   esac
+ fi
+])
diff --git a/am/pcreposix.m4 b/am/pcreposix.m4
deleted file mode 100644
index e088934..0000000
--- a/am/pcreposix.m4
+++ /dev/null
@@ -1,99 +0,0 @@
-# SYNOPSIS
-#
-#   PND_PCREPOSIX
-#
-# DESCRIPTION
-#
-#   Checks whether the pcreposix library and its headers are available.
-#   Prefers libpcre2 over libpcre.  The --enable-pcreposix option can
-#   be used to enable, disable, or force the use of libpcre version 1
-#   (--enable-pcreposix=pcre1).  Upon return, the status_pcreposix shell
-#   variable is set to indicate the result:
-#
-#   .  no - neither library has been found
-#   .  1  - libpcre is found
-#   .  2  - libpcre2 is found
-#
-#   On success, the HAVE_LIBPCREPOSIX m4 macro is defined to the version
-#   of the library used (1 or 2).
-#
-#   Substitution variables PCREPOSIX_CFLAGS and PCREPOSIX_LIBS are defined
-#   to compiler and loader flags needed in order to build with the version
-#   of the library located.
-#
-# LICENSE
-#
-# Copyright (C) 2023-2024 Sergey Poznyakoff
-#
-# 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
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-AC_DEFUN([PND_PCREPOSIX],
-[AC_ARG_ENABLE([pcreposix],
- [AS_HELP_STRING([--enable-pcreposix],[enable or disable using the pcreposix library (default: enabled if available)])],
- [status_pcreposix=${enableval}],
- [status_pcreposix=yes])
-
- AH_TEMPLATE([HAVE_LIBPCREPOSIX],[Define to the version of libpcreposix to use])
-
- AC_SUBST([PCREPOSIX_CFLAGS])
- AC_SUBST([PCREPOSIX_LIBS])
- if test "$status_pcreposix" != no; then
-   AC_PATH_PROG([PCRE2_CONFIG],[pcre2-config],[])
-   if test "$status_pcreposix" != pcre1 && test -n "$PCRE2_CONFIG"; then
-     PCREPOSIX_CFLAGS=$($PCRE2_CONFIG --cflags-posix)
-     PCREPOSIX_LIBS=$($PCRE2_CONFIG --libs-posix)
-     status_pcreposix=2
-     # Debian build of pcre2posix is badly broken.  For some obscure
-     # reason its maintainer decided to rename reg* functions by
-     # prefixing them with PCRE2, which defeats the main purpose of the
-     # library.  To make matters even worse, he didn't do the same to the
-     # regex_t type, which means that linking with Debian build of
-     # libpcre2posix results in memory overrruns when regcomp is called
-     # (pcre2posix definition of regex_t is smaller than the one in libc).
-     #
-     # The code below attempts to detect the deficiency and install a
-     # workaround.
-     saved_CFLAGS=$CFLAGS
-     CFLAGS="$CFLAGS $PCREPOSIX_CFLAGS"
-     saved_LIBS=$LIBS
-     LIBS="$LIBS $PCREPOSIX_LIBS"
-     AC_LINK_IFELSE(
-         [AC_LANG_PROGRAM([], [PCRE2regcomp()])],
-	 [AC_DEFINE([regcomp],[PCRE2regcomp],
-	            [Compensate for Debian deficiency])
-	  AC_DEFINE([regexec],[PCRE2regexec],
-	            [Compensate for Debian deficiency])
-	  AC_DEFINE([regerror],[PCRE2regerror],
-	            [Compensate for Debian deficiency])
-	  AC_DEFINE([regfree],[PCRE2regfree],
-	            [Compensate for Debian deficiency])])
-     LIBS=$saved_LIBS
-     CFLAGS=$saved_CFLAGS
-   else
-     AC_CHECK_HEADERS([pcreposix.h pcre/pcreposix.h])
-     AC_CHECK_LIB([pcre],[pcre_compile],
-       [PCREPOSIX_LIBS=-lpcre
-        AC_CHECK_LIB([pcreposix],[regcomp],
-	   [PCREPOSIX_LIBS="$PCREPOSIX_LIBS -lpcreposix"
-	    status_pcreposix=1],
-	   [status_pcreposix=no],
-	   [$PCREPOSIX_LIBS])],
-       [status_pcreposix=no])
-   fi       
-
-   case "$status_pcreposix" in
-   1|2)  AC_DEFINE_UNQUOTED([HAVE_LIBPCREPOSIX],[$status_pcreposix])
-   esac
- fi
-])
diff --git a/configure.ac b/configure.ac
index dded9c8..d5c568f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -160,7 +160,11 @@ AM_CONDITIONAL([SET_DH_AUTO],[test "$SET_DH_AUTO" = 1])
 AC_DEFINE_UNQUOTED([SET_DH_AUTO],[$SET_DH_AUTO],
   [Define to 1 of *set_dh_auto macros are available])
 
-PND_PCREPOSIX
+PND_PCRE
+AM_CONDITIONAL([COND_REGEX], [test $status_pcre = no])
+AM_CONDITIONAL([COND_PCRE], [test $status_pcre = 1])
+AM_CONDITIONAL([COND_PCRE2], [test $status_pcre = 2])
+
 
 AC_CHECK_HEADERS([getopt.h pthread.h crypt.h openssl/ssl.h openssl/engine.h])
 
@@ -220,7 +224,7 @@ Pound configuration parameters:
 Buffer size ................................... $bufsize
 Owner user .................................... $owner_user
 Owner group ................................... $owner_group
-PCRE POSIX library ............................ $status_pcreposix
+Regular expressions ........................... $status_pcre
 Memory allocator .............................. $memory_allocator
 Early pthread_cancel probe .................... $status_pthread_cancel_probe
 *******************************************************************
@@ -230,10 +234,10 @@ EOF
 [bufsize=$MAXBUF
 owner_user=$I_OWNER
 owner_group=$I_GRP
-if test $status_pcreposix != no; then
-  status_pcreposix=pcre$status_pcreposix
+if test $status_pcre != no; then
+  status_pcre=pcre$status_pcre
 else
-  status_pcreposix=$status_pcreposix
+  status_pcre="POSIX"
 fi
 memory_allocator=$memory_allocator
 if test "$early_pthread_cancel_probe" = 1; then
diff --git a/src/Makefile.am b/src/Makefile.am
index 836540a..68c8b2b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -17,12 +17,12 @@
 
 AM_CFLAGS = @PTHREAD_CFLAGS@
 
-AM_CPPFLAGS = @SSL_CPPFLAGS@ @PCREPOSIX_CFLAGS@ \
+AM_CPPFLAGS = @SSL_CPPFLAGS@ @PCRE_CFLAGS@ \
  -DSYSCONFDIR=\"$(sysconfdir)\"\
  -DLOCALSTATEDIR=\"$(localstatedir)\"\
  -DPKGDATADIR=\"$(pkgdatadir)\"
 AM_LDFLAGS  = @SSL_LDFLAGS@
-LDADD = libpound.a @PCREPOSIX_LIBS@ @PTHREAD_LIBS@
+LDADD = libpound.a @PCRE_LIBS@ @PTHREAD_LIBS@
 
 sbin_PROGRAMS=pound
 pound_SOURCES=\
@@ -34,6 +34,16 @@ pound_SOURCES=\
  pound.c\
  svc.c
 
+if COND_REGEX
+  pound_SOURCES += regex_std.c
+endif
+if COND_PCRE
+  pound_SOURCES += regex_pcre.c
+endif
+if COND_PCRE2
+  pound_SOURCES += regex_pcre2.c
+endif
+
 noinst_LIBRARIES = libpound.a
 libpound_a_SOURCES = json.c json.h mem.c progname.c tmpl.c
 
diff --git a/src/config.c b/src/config.c
index 7561f30..0dfa55d 100644
--- a/src/config.c
+++ b/src/config.c
@@ -309,12 +309,16 @@ conf_error_at_locus_point (struct locus_point const *loc, char const *fmt, ...)
 }
 
 static void
-regcomp_error_at_locus_range (struct locus_range const *loc, int rc, regex_t *rx,
+regcomp_error_at_locus_range (struct locus_range const *loc, POUND_REGEX rx,
 			      char const *expr)
 {
-  char errbuf[512];
-  regerror (rc, rx, errbuf, sizeof (errbuf));
-  conf_error_at_locus_range (loc, "%s", errbuf);
+  size_t off;
+  char const *errmsg = regex_error (rx, &off);
+
+  if (off)
+    conf_error_at_locus_range (loc, "%s at byte %zu", errmsg, off);
+  else
+    conf_error_at_locus_range (loc, "%s", errmsg);
   if (expr)
     conf_error_at_locus_range (loc, "regular expression: %s", expr);
 }
@@ -825,7 +829,7 @@ last_token_locus_range (void)
   conf_error_at_locus_range (last_token_locus_range (), fmt, __VA_ARGS__)
 
 #define conf_regcomp_error(rc, rx, expr) \
-  regcomp_error_at_locus_range (last_token_locus_range (), rc, rx, expr)
+  regcomp_error_at_locus_range (last_token_locus_range (), rx, expr)
 
 #define conf_openssl_error(file, msg)				\
   openssl_error_at_locus_range (last_token_locus_range (), file, msg)
@@ -2563,11 +2567,11 @@ parse_match_mode (int *mode, int *re_flags, int *from_file)
       switch (n)
 	{
 	case MATCH_CASE:
-	  *re_flags &= ~REG_ICASE;
+	  *re_flags &= ~POUND_REGEX_ICASE;
 	  break;
 
 	case MATCH_ICASE:
-	  *re_flags |= REG_ICASE;
+	  *re_flags |= POUND_REGEX_ICASE;
 	  break;
 
 	case MATCH_FILE:
@@ -2634,7 +2638,7 @@ build_regex (struct stringbuf *sb, int mode, char const *expr, char const *pfx)
 }
 
 static int
-parse_regex_compat (regex_t *regex, int flags)
+parse_regex_compat (POUND_REGEX *regex, int flags)
 {
   struct token *tok;
   int mode = MATCH_RE;
@@ -2650,11 +2654,12 @@ parse_regex_compat (regex_t *regex, int flags)
 
   xstringbuf_init (&sb);
   p = build_regex (&sb, mode, tok->str, NULL);
-  rc = regcomp (regex, p, flags);
+  rc = regex_compile (regex, p, flags);
   stringbuf_free (&sb);
   if (rc)
     {
-      conf_regcomp_error (rc, regex, NULL);
+      conf_regcomp_error (rc, *regex, NULL);
+      regex_free (*regex);
       return PARSER_FAIL;
     }
 
@@ -2747,10 +2752,11 @@ parse_cond_matcher_0 (SERVICE_COND *top_cond, enum service_cond_type type,
 	  stringbuf_reset (&sb);
 	  expr = build_regex (&sb, mode, p, type == COND_HOST ? host_pfx : NULL);
 	  hc = service_cond_append (cond, type);
-	  rc = regcomp (&hc->re, expr, flags);
+	  rc = regex_compile (&hc->re, expr, flags);
 	  if (rc)
 	    {
-	      conf_regcomp_error (rc, &hc->re, NULL);
+	      conf_regcomp_error (rc, hc->re, NULL);
+	      // FIXME: regex_free (hc->re);
 	      return PARSER_FAIL;
 	    }
 	  switch (type)
@@ -2772,10 +2778,11 @@ parse_cond_matcher_0 (SERVICE_COND *top_cond, enum service_cond_type type,
     {
       cond = service_cond_append (top_cond, type);
       expr = build_regex (&sb, mode, tok->str, type == COND_HOST ? host_pfx : NULL);
-      rc = regcomp (&cond->re, expr, flags);
+      rc = regex_compile (&cond->re, expr, flags);
       if (rc)
 	{
-	  conf_regcomp_error (rc, &cond->re, NULL);
+	  conf_regcomp_error (rc, cond->re, NULL);
+	  // FIXME: regex_free (cond->re);
 	  return PARSER_FAIL;
 	}
       switch (type)
@@ -2822,7 +2829,7 @@ parse_cond_url_matcher (void *call_data, void *section_data)
 {
   POUND_DEFAULTS *dfl = section_data;
   return parse_cond_matcher (call_data, COND_URL, MATCH_RE,
-			     REG_EXTENDED | (dfl->ignore_case ? REG_ICASE : 0),
+			     (dfl->ignore_case ? POUND_REGEX_ICASE : 0),
 			     NULL);
 }
 
@@ -2831,7 +2838,7 @@ parse_cond_path_matcher (void *call_data, void *section_data)
 {
   POUND_DEFAULTS *dfl = section_data;
   return parse_cond_matcher (call_data, COND_PATH, MATCH_RE,
-			     REG_EXTENDED | (dfl->ignore_case ? REG_ICASE : 0),
+			     (dfl->ignore_case ? POUND_REGEX_ICASE : 0),
 			     NULL);
 }
 
@@ -2840,7 +2847,7 @@ parse_cond_query_matcher (void *call_data, void *section_data)
 {
   POUND_DEFAULTS *dfl = section_data;
   return parse_cond_matcher (call_data, COND_QUERY, MATCH_RE,
-			     REG_EXTENDED | (dfl->ignore_case ? REG_ICASE : 0),
+			     (dfl->ignore_case ? POUND_REGEX_ICASE : 0),
 			     NULL);
 }
 
@@ -2849,7 +2856,7 @@ parse_cond_query_param_matcher (void *call_data, void *section_data)
 {
   SERVICE_COND *top_cond = call_data;
   POUND_DEFAULTS *dfl = section_data;
-  int flags = REG_EXTENDED | (dfl->ignore_case ? REG_ICASE : 0);
+  int flags = (dfl->ignore_case ? POUND_REGEX_ICASE : 0);
   struct token *tok;
   char *string;
   int rc;
@@ -2868,7 +2875,7 @@ parse_cond_string_matcher (void *call_data, void *section_data)
 {
   SERVICE_COND *top_cond = call_data;
   POUND_DEFAULTS *dfl = section_data;
-  int flags = REG_EXTENDED | (dfl->ignore_case ? REG_ICASE : 0);
+  int flags = (dfl->ignore_case ? POUND_REGEX_ICASE : 0);
   struct token *tok;
   char *string;
   int rc;
@@ -2886,7 +2893,7 @@ static int
 parse_cond_hdr_matcher (void *call_data, void *section_data)
 {
   return parse_cond_matcher (call_data, COND_HDR, MATCH_RE,
-			     REG_NEWLINE | REG_EXTENDED | REG_ICASE,
+			     POUND_REGEX_MULTILINE | POUND_REGEX_ICASE,
 			     NULL);
 }
 
@@ -2896,7 +2903,7 @@ parse_cond_head_deny_matcher (void *call_data, void *section_data)
   SERVICE_COND *cond = service_cond_append (call_data, COND_BOOL);
   cond->bool.op = BOOL_NOT;
   return parse_cond_matcher (cond, COND_HDR, MATCH_RE,
-			     REG_NEWLINE | REG_EXTENDED | REG_ICASE,
+			     POUND_REGEX_MULTILINE | POUND_REGEX_ICASE,
 			     NULL);
 }
 
@@ -2904,7 +2911,7 @@ static int
 parse_cond_host (void *call_data, void *section_data)
 {
   return parse_cond_matcher (call_data, COND_HOST, MATCH_EXACT,
-			     REG_EXTENDED | REG_ICASE, NULL);
+			     POUND_REGEX_ICASE, NULL);
 }
 
 static int
@@ -2927,7 +2934,7 @@ parse_redirect_backend (void *call_data, void *section_data)
   struct token *tok;
   int code = 302;
   BACKEND *be;
-  regmatch_t matches[5];
+  POUND_REGMATCH matches[5];
   struct locus_range range;
 
   range.beg = last_token_locus_range ()->beg;
@@ -2974,7 +2981,7 @@ parse_redirect_backend (void *call_data, void *section_data)
   be->v.redirect.status = code;
   be->v.redirect.url = xstrdup (tok->str);
 
-  if (regexec (&LOCATION, be->v.redirect.url, 4, matches, 0))
+  if (regex_exec (LOCATION, be->v.redirect.url, 4, matches))
     {
       conf_error ("%s", "Redirect bad URL");
       return PARSER_FAIL;
@@ -3446,7 +3453,7 @@ parse_delete_header (void *call_data, void *section_data)
 
   XZALLOC (op->v.hdrdel);
   return parse_regex_compat (&op->v.hdrdel->pat,
-			     REG_EXTENDED | (dfl->ignore_case ? REG_ICASE : 0));
+			     (dfl->ignore_case ? POUND_REGEX_ICASE : 0));
 }
 
 static int
@@ -3713,7 +3720,7 @@ parse_header_remove (void *call_data, void *section_data)
   REWRITE_OP *op = rewrite_op_alloc (&rule->ophead, REWRITE_HDR_DEL);
   XZALLOC (op->v.hdrdel);
   return parse_regex_compat (&op->v.hdrdel->pat,
-			     REG_EXTENDED | REG_ICASE | REG_NEWLINE);
+			     POUND_REGEX_ICASE | POUND_REGEX_MULTILINE);
 }
 
 static int
@@ -4076,10 +4083,10 @@ parse_acme (void *call_data, void *section_data)
 
   /* Create a URL matcher */
   cond = service_cond_append (&svc->cond, COND_URL);
-  rc = regcomp (&cond->re, re_acme, REG_EXTENDED);
+  rc = regex_compile (&cond->re, re_acme, 0);
   if (rc)
     {
-      conf_regcomp_error (rc, &cond->re, NULL);
+      conf_regcomp_error (rc, cond->re, NULL);
       return PARSER_FAIL;
     }
 
@@ -4134,11 +4141,8 @@ listener_parse_checkurl (void *call_data, void *section_data)
   if ((tok = gettkn_expect (T_STRING)) == NULL)
     return PARSER_FAIL;
 
-  XZALLOC (lst->url_pat);
-
-  rc = regcomp (lst->url_pat, tok->str,
-		REG_NEWLINE | REG_EXTENDED |
-		(dfl->ignore_case ? REG_ICASE : 0));
+  rc = regex_compile (&lst->url_pat, tok->str,
+		      (dfl->ignore_case ? POUND_REGEX_ICASE : 0));
   if (rc)
     {
       conf_regcomp_error (rc, lst->url_pat, NULL);
@@ -6153,6 +6157,16 @@ struct string_value pound_settings[] = {
   { "Include directory",   STRING_CONSTANT, { .s_const = SYSCONFDIR } },
   { "PID file",   STRING_CONSTANT,  { .s_const = POUND_PID } },
   { "Buffer size",STRING_INT, { .s_int = MAXBUF } },
+  { "Regex flavor", STRING_CONSTANT, { .s_const =
+#if !defined(HAVE_LIBPCRE)
+				       "POSIX"
+#elif HAVE_LIBPCRE == 1
+				       "pcre"
+#elif HAVE_LIBPCRE == 2
+				       "pcre2"
+#endif
+    }
+  },
 #if ! SET_DH_AUTO
   { "DH bits",         STRING_INT, { .s_int = DH_LEN } },
   { "RSA regeneration interval", STRING_INT, { .s_int = T_RSA_KEYS } },
diff --git a/src/extern.h b/src/extern.h
index 58edc7f..d4d2011 100644
--- a/src/extern.h
+++ b/src/extern.h
@@ -25,7 +25,7 @@ extern int print_log;           /* print log messages to stdout/stderr during
 				   startup */
 extern int enable_backend_stats;
 
-extern regex_t HEADER,		/* Allowed header */
+extern POUND_REGEX HEADER,	/* Allowed header */
   CONN_UPGRD,			/* upgrade in connection header */
   LOCATION;			/* the host we are redirected to */
 
diff --git a/src/http.c b/src/http.c
index af855ee..3e3bbcf 100644
--- a/src/http.c
+++ b/src/http.c
@@ -243,12 +243,12 @@ isws (int c)
 }
 
 static int
-submatch_realloc (struct submatch *sm, regex_t const *re)
+submatch_realloc (struct submatch *sm, POUND_REGEX re)
 {
-  size_t n = re->re_nsub + 1;
+  size_t n = regex_num_submatch (re);
   if (n > sm->matchmax)
     {
-      regmatch_t *p = realloc (sm->matchv, n * sizeof (p[0]));
+      POUND_REGMATCH *p = realloc (sm->matchv, n * sizeof (p[0]));
       if (!p)
 	return -1;
       sm->matchmax = n;
@@ -302,7 +302,7 @@ submatch_queue_push (struct submatch_queue *smq)
 }
 
 static int
-submatch_exec (regex_t const *re, char const *subject, struct submatch *sm)
+submatch_exec (POUND_REGEX re, char const *subject, struct submatch *sm)
 {
   int res;
 
@@ -312,7 +312,7 @@ submatch_exec (regex_t const *re, char const *subject, struct submatch *sm)
       lognomem ();
       return 0;
     }
-  res = regexec (re, subject, sm->matchn, sm->matchv, 0) == 0;
+  res = regex_exec (re, subject, sm->matchn, sm->matchv) == 0;
   if (res)
     {
       if ((sm->subject = strdup (subject)) == NULL)
@@ -1864,7 +1864,7 @@ cs_locate_token (char const *subj, char const *tok, int ci, char **nextp)
 static int
 qualify_header (struct http_header *hdr)
 {
-  regmatch_t matches[4];
+  POUND_REGMATCH matches[4];
   static struct
   {
     char const *header;
@@ -1889,7 +1889,7 @@ qualify_header (struct http_header *hdr)
   };
   int i;
 
-  if (regexec (&HEADER, hdr->header, 4, matches, 0) == 0)
+  if (regex_exec (HEADER, hdr->header, 4, matches) == 0)
     {
       hdr->name_start = matches[1].rm_so;
       hdr->name_end = matches[1].rm_eo;
@@ -2139,7 +2139,7 @@ http_header_list_filter (HTTP_HEADER_LIST *head, MATCHER *m)
 
   DLIST_FOREACH_SAFE (hdr, tmp, head, link)
     {
-      if (regexec (&m->pat, hdr->header, 0, NULL, 0) == 0)
+      if (regex_exec (m->pat, hdr->header, 0, NULL) == 0)
 	{
 	  http_header_list_remove (head, hdr);
 	}
@@ -2961,7 +2961,7 @@ parse_http_request (struct http_request *req, int group)
 }
 
 static int
-match_headers (HTTP_HEADER_LIST *headers, regex_t const *re,
+match_headers (HTTP_HEADER_LIST *headers, POUND_REGEX re,
 	       struct submatch *sm)
 {
   struct http_header *hdr;
@@ -2992,21 +2992,21 @@ match_cond (SERVICE_COND *cond, POUND_HTTP *phttp,
       if (http_request_get_url (req, &str) == -1)
 	res = -1;
       else
-	res = submatch_exec (&cond->re, str, submatch_queue_push (&phttp->smq));
+	res = submatch_exec (cond->re, str, submatch_queue_push (&phttp->smq));
       break;
 
     case COND_PATH:
       if (http_request_get_path (req, &str) == -1)
 	res = -1;
       else
-	res = submatch_exec (&cond->re, str, submatch_queue_push (&phttp->smq));
+	res = submatch_exec (cond->re, str, submatch_queue_push (&phttp->smq));
       break;
 
     case COND_QUERY:
       if (http_request_get_query (req, &str) == -1)
 	res = -1;
       else
-	res = submatch_exec (&cond->re, str, submatch_queue_push (&phttp->smq));
+	res = submatch_exec (cond->re, str, submatch_queue_push (&phttp->smq));
       break;
 
     case COND_QUERY_PARAM:
@@ -3025,18 +3025,18 @@ match_cond (SERVICE_COND *cond, POUND_HTTP *phttp,
 	  if (str == NULL)
 	    res = 0;
 	  else
-	    res = submatch_exec (&cond->sm.re, str,
+	    res = submatch_exec (cond->sm.re, str,
 				 submatch_queue_push (&phttp->smq));
 	}
       break;
 
     case COND_HDR:
-      res = match_headers (&req->headers, &cond->re,
+      res = match_headers (&req->headers, cond->re,
 			   submatch_queue_push (&phttp->smq));
       break;
 
     case COND_HOST:
-      res = match_headers (&req->headers, &cond->re,
+      res = match_headers (&req->headers, cond->re,
 			   submatch_queue_push (&phttp->smq));
       if (res)
 	{
@@ -3047,7 +3047,7 @@ match_cond (SERVICE_COND *cond, POUND_HTTP *phttp,
 	  struct submatch *sm = submatch_queue_get (&phttp->smq, 0);
 	  int n, i;
 	  char const *s = sm->subject;
-	  regmatch_t *mv = sm->matchv;
+	  POUND_REGMATCH *mv = sm->matchv;
 	  int mc = sm->matchn;
 	  char *p;
 
@@ -3091,7 +3091,7 @@ match_cond (SERVICE_COND *cond, POUND_HTTP *phttp,
 			      "string_match");
 	if (subj)
 	  {
-	    res = submatch_exec (&cond->sm.re, subj,
+	    res = submatch_exec (cond->sm.re, subj,
 				 submatch_queue_push (&phttp->smq));
 	    free (subj);
 	  }
@@ -3695,7 +3695,7 @@ backend_response (POUND_HTTP *phttp)
 	      /*
 	       * Connection: upgrade
 	       */
-	      else if (!regexec (&CONN_UPGRD, val, 0, NULL, 0))
+	      else if (regex_exec (CONN_UPGRD, val, 0, NULL) == 0)
 		phttp->ws_state |= WSS_RESP_HEADER_CONNECTION_UPGRADE;
 	      break;
 
@@ -4129,12 +4129,12 @@ send_to_backend (POUND_HTTP *phttp, int chunked, CONTENT_LENGTH content_length)
       (hdr = http_header_list_locate (&phttp->request.headers,
 				      HEADER_DESTINATION)) != NULL)
     {
-      regmatch_t matches[4];
+      POUND_REGMATCH matches[4];
 
       if ((val = http_header_get_value (hdr)) == NULL)
 	return HTTP_STATUS_INTERNAL_SERVER_ERROR;
 
-      if (regexec (&LOCATION, val, 4, matches, 0))
+      if (regex_exec (LOCATION, val, 4, matches))
 	{
 	  logmsg (LOG_NOTICE, "(%"PRItid") can't parse Destination %s",
 		  POUND_TID (), val);
@@ -4600,7 +4600,7 @@ do_http (POUND_HTTP *phttp)
 	phttp->ws_state |= WSS_REQ_GET;
 
       if (phttp->lstn->url_pat &&
-	  regexec (phttp->lstn->url_pat, phttp->request.url, 0, NULL, 0))
+	  regex_exec (phttp->lstn->url_pat, phttp->request.url, 0, NULL))
 	{
 	  log_error (phttp, HTTP_STATUS_NOT_IMPLEMENTED, 0,
 		     "bad URL \"%s\"", phttp->request.url);
@@ -4625,7 +4625,7 @@ do_http (POUND_HTTP *phttp)
 	      /*
 	       * Connection: upgrade
 	       */
-	      else if (!regexec (&CONN_UPGRD, val, 0, NULL, 0))
+	      else if (regex_exec (CONN_UPGRD, val, 0, NULL) == 0)
 		phttp->ws_state |= WSS_REQ_HEADER_CONNECTION_UPGRADE;
 	      break;
 
diff --git a/src/pound.c b/src/pound.c
index 8e4156f..4d46b86 100644
--- a/src/pound.c
+++ b/src/pound.c
@@ -43,7 +43,7 @@ LISTENER_HEAD listeners = SLIST_HEAD_INITIALIZER (listeners);
 				/* all available listeners */
 int n_listeners;                /* Number of listeners */
 
-regex_t HEADER,			/* Allowed header */
+POUND_REGEX HEADER, 	        /* Allowed header */
   CONN_UPGRD,			/* upgrade in connection header */
   LOCATION;			/* the host we are redirected to */
 
@@ -987,12 +987,12 @@ main (const int argc, char **argv)
   CRYPTO_set_locking_callback (l_lock);
 
   /* prepare regular expressions */
-  if (regcomp (&HEADER, "^([a-z0-9!#$%&'*+.^_`|~-]+):[ \t]*(.*)[ \t]*$",
-	       REG_ICASE | REG_NEWLINE | REG_EXTENDED)
-      || regcomp (&CONN_UPGRD, "(^|[ \t,])upgrade([ \t,]|$)",
-		  REG_ICASE | REG_NEWLINE | REG_EXTENDED)
-      || regcomp (&LOCATION, "(http|https)://([^/]+)(.*)",
-		  REG_ICASE | REG_NEWLINE | REG_EXTENDED))
+  if (regex_compile (&HEADER, "^([a-z0-9!#$%&'*+.^_`|~-]+):[ \t]*(.*)[ \t]*$",
+		     POUND_REGEX_ICASE | POUND_REGEX_MULTILINE)
+      || regex_compile (&CONN_UPGRD, "(^|[ \t,])upgrade([ \t,]|$)",
+		  POUND_REGEX_ICASE | POUND_REGEX_MULTILINE)
+      || regex_compile (&LOCATION, "(http|https)://([^/]+)(.*)",
+		  POUND_REGEX_ICASE | POUND_REGEX_MULTILINE))
     abend ("bad essential Regex");
 
 #ifndef SOL_TCP
diff --git a/src/pound.h b/src/pound.h
index 0d75baf..da4f3f9 100644
--- a/src/pound.h
+++ b/src/pound.h
@@ -91,19 +91,22 @@
 # include <openssl/engine.h>
 #endif
 
-#if HAVE_LIBPCREPOSIX == 2
-# include <pcre2posix.h>
-#elif HAVE_LIBPCREPOSIX == 1
-# if HAVE_PCREPOSIX_H
-#  include <pcreposix.h>
-# elif HAVE_PCRE_PCREPOSIX_H
-#  include <pcre/pcreposix.h>
-# else
-#  error "You have libpcreposix, but the header files are missing. Use --disable-pcreposix"
-# endif
-#else
-# include <regex.h>
-#endif
+typedef struct pound_regex *POUND_REGEX;
+
+typedef struct {
+  int rm_so;
+  int rm_eo;
+} POUND_REGMATCH;
+
+#define POUND_REGEX_DEFAULT   0
+#define POUND_REGEX_ICASE     0x1
+#define POUND_REGEX_MULTILINE 0x2
+
+int regex_compile (POUND_REGEX *, const char *, int);
+int regex_exec (POUND_REGEX, const char *, size_t, POUND_REGMATCH *);
+void regex_free (POUND_REGEX);
+char const *regex_error (POUND_REGEX pre, size_t *off);
+size_t regex_num_submatch (POUND_REGEX pre);
 
 #ifdef  HAVE_LONG_LONG_INT
 typedef long long CONTENT_LENGTH;
@@ -391,7 +394,7 @@ int acl_match (ACL *acl, struct sockaddr *sa);
 /* matcher chain */
 typedef struct _matcher
 {
-  regex_t pat;		/* pattern to match the request/header against */
+  POUND_REGEX pat;		/* pattern to match the request/header against */
   SLIST_ENTRY (_matcher) next;
 } MATCHER;
 
@@ -553,7 +556,7 @@ typedef struct string_ref
 struct string_match
 {
   STRING_REF *string;
-  regex_t re;
+  POUND_REGEX re;
 };
 
 struct user_pass
@@ -580,7 +583,7 @@ typedef struct _service_cond
   union
   {
     ACL *acl;
-    regex_t re;
+    POUND_REGEX re;
     struct bool_service_cond bool;
     struct _service_cond *cond;
     struct string_match sm;  /* COND_QUERY_PARAM and COND_STRING_MATCH */
@@ -733,7 +736,7 @@ typedef struct _listener
   REWRITE_RULE_HEAD rewrite[2];
   int verb;			/* allowed HTTP verb group */
   unsigned to;			/* client time-out */
-  regex_t *url_pat;		/* pattern to match the request URL against */
+  POUND_REGEX url_pat;		/* pattern to match the request URL against */
   char *http_err[HTTP_STATUS_MAX];	/* error messages */
   CONTENT_LENGTH max_req_size;	/* max. request size */
   unsigned max_uri_length;      /* max. URI length */
@@ -758,7 +761,7 @@ struct submatch
 {
   size_t matchn;
   size_t matchmax;
-  regmatch_t *matchv;
+  POUND_REGMATCH *matchv;
   char *subject;
 };
 
diff --git a/src/regex_pcre.c b/src/regex_pcre.c
new file mode 100644
index 0000000..563e068
--- /dev/null
+++ b/src/regex_pcre.c
@@ -0,0 +1,116 @@
+/*
+ * Pound - the reverse-proxy load-balancer
+ * Copyright (C) 2023-2024 Sergey Poznyakoff
+ *
+ * Pound is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pound is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with pound.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "pound.h"
+#if HAVE_PCRE_H
+# include <pcre.h>
+#elif HAVE_PCRE_PCRE_H
+# include <pcre/pcre.h>
+#else
+#  error "You have libpcre, but the header files are missing. Use --disable-pcre"
+#endif
+
+struct pound_regex
+{
+  pcre *pcre;
+  size_t nsub;
+  char const *errmsg;
+  int erroff;
+};
+
+int
+regex_compile (POUND_REGEX *retval, const char *pattern, int pflags)
+{
+  struct pound_regex *pre;
+  int flags = 0;
+  int nsub;
+
+  if (pflags & POUND_REGEX_ICASE)
+    flags |= PCRE_CASELESS;
+  if (pflags & POUND_REGEX_MULTILINE)
+    flags |= PCRE_MULTILINE;
+
+  XZALLOC (pre);
+  *retval = pre;
+  pre->pcre = pcre_compile (pattern, flags, &pre->errmsg, &pre->erroff, 0);
+
+  if (pre->pcre == NULL)
+    return -1;
+
+  if (pcre_fullinfo (pre->pcre, NULL, PCRE_INFO_CAPTURECOUNT, &nsub))
+    nsub = 0;
+  else
+    nsub++;
+  pre->nsub = nsub;
+
+  return 0;
+}
+
+char const *
+regex_error (POUND_REGEX pre, size_t *off)
+{
+  *off = pre->erroff;
+  return pre->errmsg;
+}
+
+int
+regex_exec (POUND_REGEX pre, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  int rc;
+  int ovsize;
+  int *ovector;
+
+  ovsize = pre->nsub * 3;
+  ovector = calloc (ovsize, sizeof (ovector[0]));
+  if (!ovector)
+    return -1;
+
+  rc = pcre_exec (pre->pcre, 0, subj, strlen (subj), 0, 0, ovector, ovsize);
+  if (rc > 0)
+    {
+      size_t i, j;
+
+      /* Collect captured substrings */
+      if (n > rc)
+	n = rc;
+
+      for (i = j = 0; i < n; i++, j += 2)
+	{
+	  prm[i].rm_so = ovector[j];
+	  prm[i].rm_eo = ovector[j+1];
+	}
+    }
+  free (ovector);
+
+  return rc < 0;
+}
+
+size_t
+regex_num_submatch (POUND_REGEX pre)
+{
+  return pre->nsub;
+}
+
+void
+regex_free (POUND_REGEX pre)
+{
+  if (pre)
+    {
+      pcre_free (pre->pcre);
+      free (pre);
+    }
+}
diff --git a/src/regex_pcre2.c b/src/regex_pcre2.c
new file mode 100644
index 0000000..3c3500d
--- /dev/null
+++ b/src/regex_pcre2.c
@@ -0,0 +1,117 @@
+#include "pound.h"
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
+
+struct pound_regex
+{
+  pcre2_code *code;
+  size_t nsub;
+  char *errmsg;
+  size_t erroff;
+};
+
+int
+regex_compile (POUND_REGEX *retval, const char *pattern, int pflags)
+{
+  struct pound_regex *pre;
+  int flags = 0;
+  int error_code;
+
+  if (pflags & POUND_REGEX_ICASE)
+    flags |= PCRE2_CASELESS;
+  if (pflags & POUND_REGEX_MULTILINE)
+    flags |= PCRE2_MULTILINE;
+
+  XZALLOC (pre);
+  *retval = pre;
+  pre->code = pcre2_compile ((PCRE2_SPTR8) pattern, strlen (pattern), flags,
+			     &error_code, &pre->erroff, NULL);
+  if (pre->code == NULL)
+    {
+      size_t errsize = 32;
+      int rc;
+
+      pre->errmsg = malloc (errsize);
+      if (!pre->errmsg)
+	return -1;
+
+      while ((rc = pcre2_get_error_message (error_code, (PCRE2_UCHAR*) pre->errmsg, errsize)) ==
+	     PCRE2_ERROR_NOMEMORY)
+	{
+	  char *p = mem2nrealloc (pre->errmsg, &errsize, 1);
+	  if (!p)
+	    break;
+	  pre->errmsg = p;
+	}
+
+      return -1;
+    }
+  else
+    {
+      uint32_t nsub;
+      if (pcre2_pattern_info (pre->code, PCRE2_INFO_CAPTURECOUNT, &nsub))
+	nsub = 0;
+      else
+	nsub++;
+      pre->nsub = nsub;
+    }
+
+  return 0;
+}
+
+void
+regex_free (POUND_REGEX pre)
+{
+  if (pre)
+    {
+      pcre2_code_free (pre->code);
+      free (pre->errmsg);
+      free (pre);
+    }
+}
+
+char const *
+regex_error (POUND_REGEX pre, size_t *off)
+{
+  *off = pre->erroff;
+  return pre->errmsg;
+}
+
+size_t
+regex_num_submatch (POUND_REGEX pre)
+{
+  return pre->nsub;
+}
+
+int
+regex_exec (POUND_REGEX pre, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  int rc;
+  PCRE2_SIZE *ovector;
+  size_t i, j;
+  pcre2_match_data *md;
+
+  md = pcre2_match_data_create_from_pattern (pre->code, NULL);
+  if (!md)
+    return -1;
+
+  rc = pcre2_match (pre->code, (PCRE2_SPTR8)subj, strlen (subj), 0, 0, md, NULL);
+  if (rc < 0)
+    {
+      pcre2_match_data_free (md);
+      return rc;
+    }
+
+  if (n > rc)
+    n = rc;
+
+  ovector = pcre2_get_ovector_pointer (md);
+  for (i = j = 0; i < n; i++, j += 2)
+    {
+      prm[i].rm_so = ovector[j];
+      prm[i].rm_eo = ovector[j+1];
+    }
+
+  pcre2_match_data_free (md);
+  return 0;
+}
diff --git a/src/regex_std.c b/src/regex_std.c
new file mode 100644
index 0000000..dc96ede
--- /dev/null
+++ b/src/regex_std.c
@@ -0,0 +1,100 @@
+/*
+ * Pound - the reverse-proxy load-balancer
+ * Copyright (C) 2023-2024 Sergey Poznyakoff
+ *
+ * Pound is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pound is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with pound.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "pound.h"
+#include <regex.h>
+
+struct pound_regex
+{
+  regex_t re;
+  char *errmsg;
+};
+
+int
+regex_compile (POUND_REGEX *retval, const char *pattern, int pflags)
+{
+  struct pound_regex *pre;
+  int flags = REG_EXTENDED;
+  int rc;
+
+  if (pflags & POUND_REGEX_ICASE)
+    flags |= REG_ICASE;
+  if (pflags & POUND_REGEX_MULTILINE)
+    flags |= REG_NEWLINE;
+  
+  XZALLOC (pre);
+  *retval = pre;
+  if ((rc = regcomp (&pre->re, pattern, flags)) != 0)
+    {
+      char errbuf[128];
+      regerror (rc, &pre->re, errbuf, sizeof (errbuf));
+      pre->errmsg = xstrdup (errbuf);
+    }
+  return rc;
+}
+
+char const *
+regex_error (POUND_REGEX pre, size_t *off)
+{
+  *off = 0;
+  return pre->errmsg;
+}
+
+int
+regex_exec (POUND_REGEX pre, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  int rc;
+  regmatch_t *rm = NULL;
+
+  if (n > 0)
+    {
+      rm = calloc (n, sizeof (rm[0]));
+      if (rm == NULL)
+	return -1;
+      if (n > pre->re.re_nsub + 1)
+	n = pre->re.re_nsub + 1;
+    }
+
+  if ((rc = regexec (&pre->re, subj, n, rm, 0)) == 0)
+    {
+      size_t i;
+      for (i = 0; i < n; i++)
+	{
+	  prm[i].rm_so = rm[i].rm_so;
+	  prm[i].rm_eo = rm[i].rm_eo;
+	}
+    }
+  free (rm);
+  return rc == REG_NOMATCH;
+}
+
+size_t
+regex_num_submatch (POUND_REGEX pre)
+{
+  return pre->re.re_nsub + 1;
+}
+
+void
+regex_free (POUND_REGEX pre)
+{
+  if (pre)
+    {
+      regfree (&pre->re);
+      free (pre->errmsg);
+      free (pre);
+    }
+}
diff --git a/src/svc.c b/src/svc.c
index bfe3c6c..4f56c53 100644
--- a/src/svc.c
+++ b/src/svc.c
@@ -994,7 +994,7 @@ need_rewrite (const char *location, const char *v_host,
   struct addrinfo addr;
   struct sockaddr_in in_addr, be_addr;
   struct sockaddr_in6 in6_addr, be6_addr;
-  regmatch_t matches[4];
+  POUND_REGMATCH matches[4];
   char const *proto;
   char const *path;
   char *host, *vhost, *port, *cp;
@@ -1008,7 +1008,7 @@ need_rewrite (const char *location, const char *v_host,
     return 0;
 
   /* split the location into its fields */
-  if (regexec (&LOCATION, location, 4, matches, 0))
+  if (regex_exec (LOCATION, location, 4, matches))
     return 0;
   proto = location + matches[1].rm_so;
 
