[exim-cvs] pcre2

Page principale
Supprimer ce message
Répondre à ce message
Auteur: Exim Git Commits Mailing List
Date:  
À: exim-cvs
Sujet: [exim-cvs] pcre2
Gitweb: https://git.exim.org/exim.git/commitdiff/22ed7a5295f196fce32563f6e9c669110dd36f4d
Commit:     22ed7a5295f196fce32563f6e9c669110dd36f4d
Parent:     8271f864edaf7fb2db0eb3aaa0c4789f55125978
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Sun Sep 12 15:42:51 2021 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Sun Sep 12 15:42:51 2021 +0100


    pcre2
---
 doc/doc-txt/ChangeLog           |   3 +
 src/OS/Makefile-Default         |   2 +-
 src/exim_monitor/em_globals.c   |   4 +-
 src/exim_monitor/em_hdr.h       |   7 +-
 src/exim_monitor/em_init.c      |  26 ++++---
 src/exim_monitor/em_log.c       |  23 +++---
 src/scripts/Configure-Makefile  |  28 ++++++--
 src/src/EDITME                  |  10 +--
 src/src/dns.c                   |   5 +-
 src/src/drtables.c              |   6 +-
 src/src/exim.c                  | 156 ++++++++++++++++++++++++++--------------
 src/src/exim.h                  |   3 +-
 src/src/expand.c                |  91 +++++++++++++----------
 src/src/filter.c                |  45 ++++++------
 src/src/functions.h             |   5 +-
 src/src/globals.c               |  36 +++++-----
 src/src/globals.h               |  36 +++++-----
 src/src/header.c                |  16 ++---
 src/src/macros.h                |   3 +-
 src/src/malware.c               |  94 ++++++++++++------------
 src/src/match.c                 |   4 +-
 src/src/queue.c                 |  11 ++-
 src/src/regex.c                 |  34 +++++----
 src/src/routers/iplookup.c      |   2 +-
 src/src/routers/iplookup.h      |   2 +-
 src/src/routers/redirect.c      |  10 ++-
 src/src/smtp_in.c               |  19 +++--
 src/src/transports/appendfile.c |  56 ++++++++-------
 src/src/transports/appendfile.h |   2 +-
 src/src/transports/lmtp.c       |  16 ++---
 src/src/transports/smtp.c       |  94 ++++++++++++------------
 src/src/transports/tf_maildir.c |  25 +++----
 src/src/transports/tf_maildir.h |   8 +--
 33 files changed, 498 insertions(+), 384 deletions(-)


diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 8e7749b..63f3578 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -10,6 +10,9 @@ JH/01 Move the wait-for-next-tick (needed for unique messmage IDs) from
       mean slightly faster delivery, and also confirmation of reception
       to senders.


+JH/02 Move from using the pcre library to pcre2.  The former is no longer
+      being developed or supported (by the original developer).
+


Exim version 4.95
-----------------
diff --git a/src/OS/Makefile-Default b/src/OS/Makefile-Default
index 41a4dbb..6e42db4 100644
--- a/src/OS/Makefile-Default
+++ b/src/OS/Makefile-Default
@@ -89,7 +89,7 @@ CC=gcc

# PCRE_LIBS contains the library to be linked for PCRE

-PCRE_LIBS=-lpcre
+PCRE_LIBS=-lpcre2-8


 # LIBS and EXTRALIBS contain library settings that are used on linking
diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c
index 88d5103..e311aef 100644
--- a/src/exim_monitor/em_globals.c
+++ b/src/exim_monitor/em_globals.c
@@ -81,7 +81,7 @@ uschar *queue_stripchart_name = NULL;
 int     queue_update = 60;
 int     queue_width = 600;


-pcre *yyyymmdd_regex;
+pcre2_code *yyyymmdd_regex;

 uschar *size_stripchart = NULL;
 uschar *size_stripchart_name = NULL;
@@ -89,7 +89,7 @@ int     spool_is_split = FALSE;
 int     start_small = FALSE;
 int     stripchart_height = 90;
 int     stripchart_number = 1;
-pcre  **stripchart_regex;
+pcre2_code  **stripchart_regex;
 uschar **stripchart_title;
 int    *stripchart_total;
 int     stripchart_update = 60;
diff --git a/src/exim_monitor/em_hdr.h b/src/exim_monitor/em_hdr.h
index c45f9fc..6d8b7e2 100644
--- a/src/exim_monitor/em_hdr.h
+++ b/src/exim_monitor/em_hdr.h
@@ -85,7 +85,8 @@ anything. */


/* Regular expression include */

-#include <pcre.h>
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>

 /* Includes from the main source of Exim.  One of these days I should tidy up
 this interface so that this kind of kludge isn't needed. */
@@ -273,7 +274,7 @@ extern uschar *queue_stripchart_name; /* sic */
 extern int     queue_update;        /* update interval */
 extern int     queue_width;         /* width of queue window */


-extern pcre   *yyyymmdd_regex;    /* for matching yyyy-mm-dd */
+extern pcre2_code   *yyyymmdd_regex;    /* for matching yyyy-mm-dd */


 extern uschar *size_stripchart;     /* path for size monitoring */
 extern uschar *size_stripchart_name; /* name for size stripchart */
@@ -282,7 +283,7 @@ extern int     spool_is_split;      /* True if detected split spool */
 extern int     start_small;         /* True to start with small window */
 extern int     stripchart_height;   /* height of stripcharts */
 extern int     stripchart_number;   /* number of stripcharts */
-extern pcre  **stripchart_regex;  /* vector of regexps */
+extern pcre2_code  **stripchart_regex;  /* vector of regexps */
 extern uschar **stripchart_title;    /* vector of titles */
 extern int    *stripchart_total;    /* vector of accumulating values */
 extern int     stripchart_update;   /* update interval */
diff --git a/src/exim_monitor/em_init.c b/src/exim_monitor/em_init.c
index 56cb298..f85f858 100644
--- a/src/exim_monitor/em_init.c
+++ b/src/exim_monitor/em_init.c
@@ -32,7 +32,6 @@ int i;
 work. */


 for (i = 0; i <= 1; i++)
-
   {
   int first = 1;
   int count = 0;
@@ -69,13 +68,18 @@ for (i = 0; i <= 1; i++)
       buffer[p-pp] = 0;
       if (first)
         {
-        int offset;
-        const uschar *error;
-        if (!(stripchart_regex[indx] = pcre_compile(CS buffer, PCRE_COPT,
-          CCSS &error, &offset, NULL)))
+        size_t offset;
+        int err;
+
+        if (!(stripchart_regex[indx] =
+        pcre2_compile((PCRE2_SPTR)buffer,
+              PCRE2_ZERO_TERMINATED, PCRE_COPT,
+              &err, &offset, NULL)))
           {
-          printf("regular expression error: %s at offset %d "
-            "while compiling %s\n", error, offset, buffer);
+      uschar errbuf[128];
+      pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+          printf("regular expression error: %s at offset %l "
+            "while compiling %s\n", errbuf, (long)offset, buffer);
           exit(99);
           }
         }
@@ -95,7 +99,7 @@ for (i = 0; i <= 1; i++)
   if (i == 0)
     {
     stripchart_number += count;
-    stripchart_regex = (pcre **)store_malloc(stripchart_number * sizeof(pcre *));
+    stripchart_regex = (pcre2_code **)store_malloc(stripchart_number * sizeof(pcre2_code *));
     stripchart_title = (uschar **)store_malloc(stripchart_number * sizeof(uschar *));
     }
   }
@@ -109,7 +113,7 @@ for (i = 0; i <= 1; i++)
 void init(int argc, uschar **argv)
 {
 int x;
-int erroroffset;
+size_t erroroffset;
 uschar *s;
 const uschar *error;


@@ -230,8 +234,8 @@ queue_stripchart_name = (s != NULL)? string_copy(s) : US"queue";

/* Compile the regex for matching yyyy-mm-dd at the start of a string. */

-yyyymmdd_regex = pcre_compile("^\\d{4}-\\d\\d-\\d\\d\\s", PCRE_COPT,
- CCSS &error, &erroroffset, NULL);
+yyyymmdd_regex = pcre2_compile((PCRE2_SPTR)"^\\d{4}-\\d\\d-\\d\\d\\s",
+ PCRE2_ZERO_TERMINATED, PCRE_COPT, &x, &erroroffset, NULL);
}

 /* End of em_init.c */
diff --git a/src/exim_monitor/em_log.c b/src/exim_monitor/em_log.c
index 1e1dc7c..662595b 100644
--- a/src/exim_monitor/em_log.c
+++ b/src/exim_monitor/em_log.c
@@ -229,7 +229,7 @@ if (LOG != NULL)
     uschar *p = buffer;
     rmark reset_point;
     int length = Ustrlen(buffer);
-    int i;
+    pcre2_match_data * md = pcre2_match_data_create(1, NULL);


     /* Skip totally blank lines (paranoia: there shouldn't be any) */


@@ -246,27 +246,25 @@ if (LOG != NULL)
     stripchart is the queue length, which is handled elsewhere, and the
     1st may the a size monitor. */


-    for (i = stripchart_varstart; i < stripchart_number; i++)
-      {
-      if (pcre_exec(stripchart_regex[i], NULL, CS buffer, length, 0, PCRE_EOPT,
-            NULL, 0) >= 0)
+    for (int i = stripchart_varstart; i < stripchart_number; i++)
+      if (pcre2_match(stripchart_regex[i], (PCRE2_SPTR)buffer, length,
+            0, PCRE_EOPT, md, NULL) >= 0)
         stripchart_total[i]++;
-      }


     /* Munge the log entry and display shortened form on one line.
     We omit the date and show only the time. Remove any time zone offset.
     Take note of the presence of [pid]. */


-    if (pcre_exec(yyyymmdd_regex,NULL,CS buffer,length,0,PCRE_EOPT,NULL,0) >= 0)
+    if (pcre2_match(yyyymmdd_regex, (PCRE2_SPTR) buffer, length, 0, PCRE_EOPT,
+              md, NULL) >= 0)
       {
       int pidlength = 0;
-      if ((buffer[20] == '+' || buffer[20] == '-') &&
-          isdigit(buffer[21]) && buffer[25] == ' ')
+      if (  (buffer[20] == '+' || buffer[20] == '-')
+         && isdigit(buffer[21]) && buffer[25] == ' ')
         memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1);
       if (buffer[20] == '[')
-        {
-        while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL);
-        }
+        while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL)
+      ;
       id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH);
       show_log("%s", buffer+11);
       }
@@ -275,6 +273,7 @@ if (LOG != NULL)
       id = US"";
       show_log("%s", buffer);
       }
+    pcre2_match_data_free(md);


     /* Deal with frozen and unfrozen messages */


diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile
index 61368ec..df597a8 100755
--- a/src/scripts/Configure-Makefile
+++ b/src/scripts/Configure-Makefile
@@ -150,7 +150,7 @@ rm -f ./$mftepcp ./$mftepcp2
 egrep "^[$st]*(AUTH|LOOKUP)_[A-Z0-9_]*[$st]*=[$st]*" $mft | \
   sed "s/[$st]*=/='/" | \
   sed "s/\$/'/" > $mftt
-egrep "^[$st]*((USE_(OPENSSL|GNUTLS)_PC)|SUPPORT_TLS|USE_GNUTLS|PCRE_CONFIG|AVOID_GNUTLS_PKCS11)[$st]*=[$st]*" $mft | \
+egrep "^[$st]*((USE_(OPENSSL|GNUTLS)_PC)|SUPPORT_TLS|USE_GNUTLS|PCRE2?_CONFIG|AVOID_GNUTLS_PKCS11)[$st]*=[$st]*" $mft | \
   sed "s/[$st]*=/='/" | \
   sed "s/\$/'/" >> $mftt
 if test -s $mftt
@@ -233,12 +233,32 @@ then
       PCRE_CONFIG)
         case $PCRE_CONFIG in
           yes|YES|y|Y)
-            cflags=`pcre-config --cflags`
+         echo >&2 "pcre is no longer supported; migrate to pcre2"
+         exit 1
+
+#            cflags=`pcre-config --cflags`
+#            if [ $? -ne 0 ]; then
+#              echo >&2 "*** Missing pcre-config for regular expression support"
+#              exit 1
+#            fi
+#            libs=`pcre-config --libs`
+#            if [ ".$cflags" != "." ]; then
+#              echo "INCLUDE += $cflags"
+#            fi
+#            echo "PCRE_LIBS=$libs"
+            ;;
+        esac
+        ;;
+
+      PCRE2_CONFIG)
+        case $PCRE2_CONFIG in
+          yes|YES|y|Y)
+            cflags=`pcre2-config --cflags`
             if [ $? -ne 0 ]; then
-              echo >&2 "*** Missing pcre-config for regular expression support"
+              echo >&2 "*** Missing pcre2-config for regular expression support"
               exit 1
             fi
-            libs=`pcre-config --libs`
+            libs=`pcre2-config --libs8`
             if [ ".$cflags" != "." ]; then
               echo "INCLUDE += $cflags"
             fi
diff --git a/src/src/EDITME b/src/src/EDITME
index f4329fa..44c30ce 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -457,19 +457,19 @@ LOOKUP_DNSDB=yes



#------------------------------------------------------------------------------
-# The PCRE library is required for Exim. There is no longer an embedded
+# The PCRE2 library is required for Exim. There is no longer an embedded
# version of the PCRE library included with the source code, instead you
-# must use a system library or build your own copy of PCRE.
+# must use a system library or build your own copy of PCRE2.
# In either case you must specify the library link info here. If the
-# PCRE header files are not in the standard search path you must also
+# PCRE2 header files are not in the standard search path you must also
# modify the INCLUDE path (above)
#
# Use PCRE_CONFIG to query the pcre-config command (first found in $PATH)
# to find the include files and libraries, else use PCRE_LIBS and set INCLUDE
# too if needed.

-PCRE_CONFIG=yes
-# PCRE_LIBS=-lpcre
+PCRE2_CONFIG=yes
+# PCRE_LIBS=-lpcre2


#------------------------------------------------------------------------------
diff --git a/src/src/dns.c b/src/src/dns.c
index 490eb88..63856ea 100644
--- a/src/src/dns.c
+++ b/src/src/dns.c
@@ -850,11 +850,8 @@ more.) */

 if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
   {
-  int ovector[3*(EXPAND_MAXN+1)];
-
   dns_pattern_init();
-  if (pcre_exec(regex_check_dns_names, NULL, CCS name, Ustrlen(name),
-      0, PCRE_EOPT, ovector, nelem(ovector)) < 0)
+  if (!regex_match(regex_check_dns_names, name, -1, NULL))
     {
     DEBUG(D_dns)
       debug_printf("DNS name syntax check failed: %s (%s)\n", name,
diff --git a/src/src/drtables.c b/src/src/drtables.c
index 72f47c1..02f014e 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -728,15 +728,15 @@ if (!(dd = exim_opendir(LOOKUP_MODULE_DIR)))
   }
 else
   {
-  const pcre *regex_islookupmod = regex_must_compile(
+  const pcre2_code *regex_islookupmod = regex_must_compile(
     US"\\." DYNLIB_FN_EXT "$", FALSE, TRUE);


   DEBUG(D_lookup) debug_printf("Loading lookup modules from %s\n", LOOKUP_MODULE_DIR);
   while ((ent = readdir(dd)))
     {
-    char *name = ent->d_name;
+    char * name = ent->d_name;
     int len = (int)strlen(name);
-    if (pcre_exec(regex_islookupmod, NULL, name, len, 0, PCRE_EOPT, NULL, 0) >= 0)
+    if (regex_match(regex_islookupmod, US name, len, NUL))
       {
       int pathnamelen = len + (int)strlen(LOOKUP_MODULE_DIR) + 2;
       void *dl;
diff --git a/src/src/exim.c b/src/src/exim.c
index e15d5e4..27f1e9b 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -45,7 +45,7 @@ are two sets of functions; one for use when we want to retain the compiled
 regular expression for a long time; the other for short-term use. */


static void *
-function_store_get(size_t size)
+function_store_get(PCRE2_SIZE size, void * tag)
{
/* For now, regard all RE results as potentially tainted. We might need
more intelligence on this point. */
@@ -53,16 +53,16 @@ return store_get((int)size, TRUE);
}

static void
-function_dummy_free(void * block) {}
+function_dummy_free(void * block, void * tag) {}

static void *
-function_store_malloc(size_t size)
+function_store_malloc(PCRE2_SIZE size, void * tag)
{
return store_malloc((int)size);
}

 static void
-function_store_free(void * block)
+function_store_free(void * block, void * tag)
 {
 store_free(block);
 }
@@ -98,29 +98,51 @@ Argument:
 Returns:      pointer to the compiled pattern
 */


-const pcre *
-regex_must_compile(const uschar *pattern, BOOL caseless, BOOL use_malloc)
+const pcre2_code *
+regex_must_compile(const uschar * pattern, BOOL caseless, BOOL use_malloc)
 {
-int offset;
-int options = PCRE_COPT;
-const pcre *yield;
-const uschar *error;
+size_t offset;
+int options = caseless ? PCRE_COPT|PCRE2_CASELESS : PCRE_COPT;
+const pcre2_code * yield;
+int err;
+pcre2_general_context * gctx;
+pcre2_compile_context * cctx;
+
 if (use_malloc)
   {
-  pcre_malloc = function_store_malloc;
-  pcre_free = function_store_free;
+  gctx = pcre2_general_context_create(function_store_malloc, function_store_free, NULL);
+  cctx = pcre2_compile_context_create(gctx);
   }
-if (caseless) options |= PCRE_CASELESS;
-yield = pcre_compile(CCS pattern, options, CCSS &error, &offset, NULL);
-pcre_malloc = function_store_get;
-pcre_free = function_dummy_free;
-if (yield == NULL)
+else
+  cctx = pcre_cmp_ctx;
+
+if (!(yield = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, options,
+  &err, &offset, cctx)))
+  {
+  uschar errbuf[128];
+  pcre2_get_error_message(err, errbuf, sizeof(errbuf));
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "regular expression error: "
-    "%s at offset %d while compiling %s", error, offset, pattern);
+    "%s at offset %d while compiling %s", errbuf, (long)offset, pattern);
+  }
+
+if (use_malloc)
+  {
+  pcre2_compile_context_free(cctx);
+  pcre2_general_context_free(gctx);
+  }
 return yield;
 }



+static void
+pcre_init(void)
+{
+pcre_gen_ctx = pcre2_general_context_create(function_store_malloc, function_store_free, NULL);
+pcre_cmp_ctx = pcre2_compile_context_create(pcre_gen_ctx);
+pcre_mtc_ctx = pcre2_match_context_create(pcre_gen_ctx);
+}
+
+


/*************************************************
@@ -128,7 +150,12 @@ return yield;
*************************************************/

/* This function runs a regular expression match, and sets up the pointers to
-the matched substrings.
+the matched substrings. The matched strings are copied.
+
+We might consider tracing the uses of expand_nstring to see if consitification
+is viable, and save the copy cost by just using the pointers into the subject string.
+Pre-pcre2 we did that without noticing, so it might just work - or might have been
+a bug. It was certainly a risk in the implemenation.

 Arguments:
   re          the compiled expression
@@ -138,32 +165,67 @@ Arguments:
               if >= 0 setup from setup+1 onwards,
                 excluding the full matched string


-Returns:      TRUE or FALSE
+Returns:      TRUE if matched, or FALSE
 */


 BOOL
-regex_match_and_setup(const pcre *re, const uschar *subject, int options, int setup)
+regex_match_and_setup(const pcre2_code * re, const uschar * subject, int options, int setup)
 {
-int ovector[3*(EXPAND_MAXN+1)];
-uschar * s = string_copy(subject);    /* de-constifying */
-int n = pcre_exec(re, NULL, CS s, Ustrlen(s), 0,
-  PCRE_EOPT | options, ovector, nelem(ovector));
-BOOL yield = n >= 0;
-if (n == 0) n = EXPAND_MAXN + 1;
-if (yield)
+pcre2_match_data * md = pcre2_match_data_create_from_pattern(re, pcre_gen_ctx);
+int res = pcre2_match(re, (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED, 0,
+            PCRE_EOPT | options, md, pcre_mtc_ctx);
+BOOL yield;
+
+if ((yield = (res >= 0)))
   {
+  res = pcre2_get_ovector_count(md);
   expand_nmax = setup < 0 ? 0 : setup + 1;
-  for (int nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2)
+  for (int matchnum = setup < 0 ? 0 : 1; matchnum < res; matchnum++)
     {
-    expand_nstring[expand_nmax] = s + ovector[nn];
-    expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
+    PCRE2_SIZE len;
+    pcre2_substring_get_bynumber(md, matchnum,
+      (PCRE2_UCHAR **)&expand_nstring[expand_nmax], &len);
+    expand_nlength[expand_nmax++] = (int)len;
     }
   expand_nmax--;
   }
+else if (res != PCRE2_ERROR_NOMATCH) DEBUG(D_any)
+  {
+  uschar errbuf[128];
+  pcre2_get_error_message(res, errbuf, sizeof(errbuf));
+  debug_printf_indent("pcre2: %s\n", errbuf);
+  }
+pcre2_match_data_free(md);
 return yield;
 }



+/* Check just for match with regex.  Uses the common memory-handling.
+
+Arguments:
+    re    compiled regex
+    subject    string to be checked
+    slen    length of subject; -1 for nul-terminated
+    rptr    pointer for matched string, copied, or NULL
+
+Return: TRUE for a match.
+*/
+
+BOOL
+regex_match(const pcre2_code * re, const uschar * subject, int slen, uschar ** rptr)
+{
+pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx);
+int rc = pcre2_match(re, (PCRE2_SPTR)subject,
+              slen >= 0 ? slen : PCRE2_ZERO_TERMINATED,
+              0, PCRE_EOPT, md, pcre_mtc_ctx);
+PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md);
+if (rc < 0)
+  return FALSE;
+if (rptr)
+  *rptr = string_copyn(subject + ovec[0], ovec[1] - ovec[0]);
+return TRUE;
+}
+



 /*************************************************
@@ -1181,11 +1243,15 @@ show_db_version(fp);
 #endif
 #define QUOTE(X) #X
 #define EXPAND_AND_QUOTE(X) QUOTE(X)
-  fprintf(fp, "Library version: PCRE: Compile: %d.%d%s\n"
+  {
+  uschar buf[24];
+  pcre2_config(PCRE2_CONFIG_VERSION, buf);
+  fprintf(fp, "Library version: PCRE2: Compile: %d.%d%s\n"
              "                       Runtime: %s\n",
-          PCRE_MAJOR, PCRE_MINOR,
-          EXPAND_AND_QUOTE(PCRE_PRERELEASE) "",
-          pcre_version());
+          PCRE2_MAJOR, PCRE2_MINOR,
+          EXPAND_AND_QUOTE(PCRE2_PRERELEASE) "",
+          buf);
+  }
 #undef QUOTE
 #undef EXPAND_AND_QUOTE


@@ -1538,14 +1604,8 @@ for (macro_item * m = macros_user; m; m = m->next) if (m->command_line)
     continue;
   if ((len = m->replen) == 0)
     continue;
-  n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
-   0, PCRE_EOPT, NULL, 0);
-  if (n < 0)
-    {
-    if (n != PCRE_ERROR_NOMATCH)
-      debug_printf("macros_trusted checking %s returned %d\n", m->name, n);
+  if (!regex_match(regex_whitelisted_macro, m->replacement, len, NULL))
     return FALSE;
-    }
   }
 DEBUG(D_any) debug_printf("macros_trusted overridden to true by whitelisting\n");
 return TRUE;
@@ -1700,6 +1760,7 @@ extern char **environ;
 #endif


 store_init();    /* Initialise the memory allocation susbsystem */
+pcre_init();    /* Set up memory handling for pcre */


/* If the Exim user and/or group and/or the configuration file owner/group were
defined by ref:name at build time, we must now find the actual uid/gid values.
@@ -1800,15 +1861,6 @@ indirection, because some systems don't allow writing to the variable "stderr".

if (fstat(fileno(stderr), &statbuf) >= 0) log_stderr = stderr;

-/* Arrange for the PCRE regex library to use our store functions. Note that
-the normal calls are actually macros that add additional arguments for
-debugging purposes so we have to assign specially constructed functions here.
-The default is to use store in the stacking pool, but this is overridden in the
-regex_must_compile() function. */
-
-pcre_malloc = function_store_get;
-pcre_free = function_dummy_free;
-
/* Ensure there is a big buffer for temporary use in several places. It is put
in malloc store so that it can be freed for enlargement if necessary. */

@@ -4845,7 +4897,7 @@ for (i = 0;;)

         if (gecos_pattern && gecos_name)
           {
-          const pcre *re;
+          const pcre2_code *re;
           re = regex_must_compile(gecos_pattern, FALSE, TRUE); /* Use malloc */


           if (regex_match_and_setup(re, name, 0, -1))
diff --git a/src/src/exim.h b/src/src/exim.h
index 484276b..b0906d3 100644
--- a/src/src/exim.h
+++ b/src/src/exim.h
@@ -522,7 +522,8 @@ extern int ferror(FILE *);


/* The header from the PCRE regex package */

-#include <pcre.h>
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>

/* Exim includes are in several files. Note that local_scan.h #includes
config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
diff --git a/src/src/expand.c b/src/src/expand.c
index 83c0ad0..4e1ffbb 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -2543,16 +2543,13 @@ BOOL tempcond, combined_cond;
BOOL *subcondptr;
BOOL sub2_honour_dollar = TRUE;
BOOL is_forany, is_json, is_jsons;
-int rc, cond_type, roffset;
+int rc, cond_type;
int_eximarith_t num[2];
struct stat statbuf;
uschar * opname;
uschar name[256];
const uschar *sub[10];

-const pcre *re;
-const uschar *rerror;
-
for (;;)
if (Uskip_whitespace(&s) == '!') { testfor = !testfor; s++; } else break;

@@ -2974,15 +2971,24 @@ switch(cond_type = identify_operator(&s, &opname))
     break;


     case ECOND_MATCH:   /* Regular expression match */
-    if (!(re = pcre_compile(CS sub[1], PCRE_COPT, CCSS &rerror,
-                &roffset, NULL)))
       {
-      expand_string_message = string_sprintf("regular expression error in "
-        "\"%s\": %s at offset %d", sub[1], rerror, roffset);
-      return NULL;
+      const pcre2_code * re;
+      PCRE2_SIZE offset;
+      int err;
+
+      if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED,
+                PCRE_COPT, &err, &offset, pcre_cmp_ctx)))
+    {
+    uschar errbuf[128];
+    pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+    expand_string_message = string_sprintf("regular expression error in "
+      "\"%s\": %s at offset %d", sub[1], errbuf, offset);
+    return NULL;
+    }
+
+      tempcond = regex_match_and_setup(re, sub[0], 0, -1);
+      break;
       }
-    tempcond = regex_match_and_setup(re, sub[0], 0, -1);
-    break;


     case ECOND_MATCH_ADDRESS:  /* Match in an address list */
     rc = match_address_list(sub[0], TRUE, FALSE, &(sub[1]), NULL, -1, 0, NULL);
@@ -3448,9 +3454,10 @@ switch(cond_type = identify_operator(&s, &opname))
     /* ${if inbound_srs {local_part}{secret}  {yes}{no}} */
     {
     uschar * sub[2];
-    const pcre * re;
-    int ovec[3*(4+1)];
-    int n, quoting = 0;
+    const pcre2_code * re;
+    pcre2_match_data * md;
+    PCRE2_SIZE * ovec;
+    int quoting = 0;
     uschar cksum[4];
     BOOL boolvalue = FALSE;


@@ -3466,12 +3473,14 @@ switch(cond_type = identify_operator(&s, &opname))

     re = regex_must_compile(US"^(?i)SRS0=([^=]+)=([A-Z2-7]+)=([^=]*)=(.*)$",
                 TRUE, FALSE);
-    if (pcre_exec(re, NULL, CS sub[0], Ustrlen(sub[0]), 0, PCRE_EOPT,
-          ovec, nelem(ovec)) < 0)
+    md = pcre2_match_data_create(4+1, pcre_gen_ctx);
+    if (pcre2_match(re, sub[0], PCRE2_ZERO_TERMINATED, 0, PCRE_EOPT,
+            md, pcre_mtc_ctx) < 0)
       {
       DEBUG(D_expand) debug_printf("no match for SRS'd local-part pattern\n");
       goto srs_result;
       }
+    ovec = pcre2_get_ovector_pointer(md);


     if (sub[0][0] == '"')
       quoting = 1;
@@ -3503,6 +3512,7 @@ switch(cond_type = identify_operator(&s, &opname))
       struct timeval now;
       uschar * ss = sub[0] + ovec[4];    /* substring 2, the timestamp */
       long d;
+      int n;


       gettimeofday(&now, NULL);
       now.tv_sec /= 86400;        /* days since epoch */
@@ -5189,7 +5199,7 @@ while (*s)
       {
       uschar *sub_arg[3];
       gstring * g;
-      const pcre *re;
+      const pcre2_code *re;
       uschar *p;


       /* TF: Ugliness: We want to expand parameter 1 first, then set
@@ -5829,11 +5839,11 @@ while (*s)


     case EITEM_SG:
       {
-      const pcre *re;
+      const pcre2_code * re;
       int moffset, moffsetextra, slen;
-      int roffset;
-      int emptyopt;
-      const uschar *rerror;
+      PCRE2_SIZE roffset;
+      pcre2_match_data * md;
+      int err, emptyopt;
       uschar *subject;
       uschar *sub[3];
       int save_expand_nmax =
@@ -5848,13 +5858,16 @@ while (*s)


       /* Compile the regular expression */


-      if (!(re = pcre_compile(CS sub[1], PCRE_COPT, CCSS &rerror,
-                  &roffset, NULL)))
+      if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED,
+          PCRE_COPT, &err, &roffset, pcre_cmp_ctx)))
         {
+        uschar errbuf[128];
+    pcre2_get_error_message(err, errbuf, sizeof(errbuf));
         expand_string_message = string_sprintf("regular expression error in "
-          "\"%s\": %s at offset %d", sub[1], rerror, roffset);
+          "\"%s\": %s at offset %l", sub[1], errbuf, (long)roffset);
         goto EXPAND_FAILED;
         }
+      md = pcre2_match_data_create(EXPAND_MAXN + 1, pcre_gen_ctx);


       /* Now run a loop to do the substitutions as often as necessary. It ends
       when there are no more matches. Take care over matches of the null string;
@@ -5867,9 +5880,9 @@ while (*s)


       for (;;)
         {
-        int ovector[3*(EXPAND_MAXN+1)];
-        int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra,
-          PCRE_EOPT | emptyopt, ovector, nelem(ovector));
+    PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md);
+    int n = pcre2_match(re, (PCRE2_SPTR)subject, slen, moffset + moffsetextra,
+      PCRE_EOPT | emptyopt, md, pcre_mtc_ctx);
         uschar *insert;


         /* No match - if we previously set PCRE_NOTEMPTY after a null match, this
@@ -5897,19 +5910,19 @@ while (*s)
         expand_nmax = 0;
         for (int nn = 0; nn < n*2; nn += 2)
           {
-          expand_nstring[expand_nmax] = subject + ovector[nn];
-          expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
+          expand_nstring[expand_nmax] = subject + ovec[nn];
+          expand_nlength[expand_nmax++] = ovec[nn+1] - ovec[nn];
           }
         expand_nmax--;


         /* Copy the characters before the match, plus the expanded insertion. */


-        yield = string_catn(yield, subject + moffset, ovector[0] - moffset);
+        yield = string_catn(yield, subject + moffset, ovec[0] - moffset);
         if (!(insert = expand_string(sub[2])))
       goto EXPAND_FAILED;
         yield = string_cat(yield, insert);


-        moffset = ovector[1];
+        moffset = ovec[1];
         moffsetextra = 0;
         emptyopt = 0;


@@ -5920,10 +5933,10 @@ while (*s)
         string at the same point. If this fails (picked up above) we advance to
         the next character. */


-        if (ovector[0] == ovector[1])
+        if (ovec[0] == ovec[1])
           {
-          if (ovector[0] == slen) break;
-          emptyopt = PCRE_NOTEMPTY | PCRE_ANCHORED;
+          if (ovec[0] == slen) break;
+          emptyopt = PCRE2_NOTEMPTY | PCRE2_ANCHORED;
           }
         }


@@ -8608,11 +8621,11 @@ if (e.var_name)


 BOOL
-regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup)
+regex_match_and_setup(const pcre2_code *re, uschar *subject, int options, int setup)
 {
-int ovector[3*(EXPAND_MAXN+1)];
+int ovec[3*(EXPAND_MAXN+1)];
 int n = pcre_exec(re, NULL, subject, Ustrlen(subject), 0, PCRE_EOPT|options,
-  ovector, nelem(ovector));
+  ovec, nelem(ovec));
 BOOL yield = n >= 0;
 if (n == 0) n = EXPAND_MAXN + 1;
 if (yield)
@@ -8620,8 +8633,8 @@ if (yield)
   expand_nmax = setup < 0 ? 0 : setup + 1;
   for (int nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2)
     {
-    expand_nstring[expand_nmax] = subject + ovector[nn];
-    expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
+    expand_nstring[expand_nmax] = subject + ovec[nn];
+    expand_nlength[expand_nmax++] = ovec[nn+1] - ovec[nn];
     }
   expand_nmax--;
   }
diff --git a/src/src/filter.c b/src/src/filter.c
index 3f9f750..7c9e963 100644
--- a/src/src/filter.c
+++ b/src/src/filter.c
@@ -1425,10 +1425,7 @@ static BOOL
 test_condition(condition_block *c, BOOL toplevel)
 {
 BOOL yield = FALSE;
-const pcre *re;
 uschar *exp[2], *p, *pp;
-const uschar *regcomp_error = NULL;
-int regcomp_error_offset;
 int val[2];
 int i;


@@ -1588,26 +1585,34 @@ switch (c->type)

     case cond_matches:
     case cond_MATCHES:
-    if ((filter_test != FTEST_NONE && debug_selector != 0) ||
-        (debug_selector & D_filter) != 0)
       {
-      debug_printf_indent("Match expanded arguments:\n");
-      debug_printf_indent("  Subject = %s\n", exp[0]);
-      debug_printf_indent("  Pattern = %s\n", exp[1]);
-      }
+      const pcre2_code *re;
+      int err;
+      PCRE2_SIZE offset;


-    if (!(re = pcre_compile(CS exp[1],
-      PCRE_COPT | ((c->type == cond_matches)? PCRE_CASELESS : 0),
-      CCSS &regcomp_error, &regcomp_error_offset, NULL)))
-      {
-      *error_pointer = string_sprintf("error while compiling "
-        "regular expression \"%s\": %s at offset %d",
-        exp[1], regcomp_error, regcomp_error_offset);
-      return FALSE;
-      }
+      if ((filter_test != FTEST_NONE && debug_selector != 0) ||
+      (debug_selector & D_filter) != 0)
+    {
+    debug_printf_indent("Match expanded arguments:\n");
+    debug_printf_indent("  Subject = %s\n", exp[0]);
+    debug_printf_indent("  Pattern = %s\n", exp[1]);
+    }


-    yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1);
-    break;
+      if (!(re = pcre2_compile((PCRE2_SPTR)exp[1], PCRE2_ZERO_TERMINATED,
+          PCRE_COPT | (c->type == cond_matches ? PCRE2_CASELESS : 0),
+          &err, &offset, pcre_cmp_ctx)))
+    {
+    uschar errbuf[128];
+    pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+    *error_pointer = string_sprintf("error while compiling "
+      "regular expression \"%s\": %s at offset %l",
+      exp[1], errbuf, (long)offset);
+    return FALSE;
+    }
+
+      yield = regex_match_and_setup(re, exp[0], PCRE_EOPT, -1);
+      break;
+      }


     /* For above and below, convert the strings to numbers */


diff --git a/src/src/functions.h b/src/src/functions.h
index 0744697..a519988 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -426,8 +426,9 @@ extern void    receive_swallow_smtp(void);
 #ifdef WITH_CONTENT_SCAN
 extern int     regex(const uschar **);
 #endif
-extern BOOL    regex_match_and_setup(const pcre *, const uschar *, int, int);
-extern const pcre *regex_must_compile(const uschar *, BOOL, BOOL);
+extern BOOL    regex_match(const pcre2_code *, const uschar *, int, uschar **);
+extern BOOL    regex_match_and_setup(const pcre2_code *, const uschar *, int, int);
+extern const pcre2_code *regex_must_compile(const uschar *, BOOL, BOOL);
 extern void    retry_add_item(address_item *, uschar *, int);
 extern BOOL    retry_check_address(const uschar *, host_item *, uschar *, BOOL,
                  uschar **, uschar **);
diff --git a/src/src/globals.c b/src/src/globals.c
index 50b9c2f..f54a252 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -118,7 +118,7 @@ tls_support tls_out = {


 uschar *dsn_envid              = NULL;
 int     dsn_ret                = 0;
-const pcre  *regex_DSN         = NULL;
+const pcre2_code  *regex_DSN         = NULL;
 uschar *dsn_advertise_hosts    = NULL;


 #ifndef DISABLE_TLS
@@ -126,7 +126,7 @@ BOOL    gnutls_compat_mode     = FALSE;
 BOOL    gnutls_allow_auto_pkcs11 = FALSE;
 uschar *hosts_require_alpn     = NULL;
 uschar *openssl_options        = NULL;
-const pcre *regex_STARTTLS     = NULL;
+const pcre2_code *regex_STARTTLS     = NULL;
 uschar *tls_advertise_hosts    = US"*";
 uschar *tls_alpn           = US"smtp:esmtp";
 uschar *tls_certificate        = NULL;
@@ -159,11 +159,11 @@ uschar *tls_advertise_hosts    = NULL;
 /* Per Recipient Data Response variables */
 BOOL    prdr_enable            = FALSE;
 BOOL    prdr_requested         = FALSE;
-const pcre *regex_PRDR         = NULL;
+const pcre2_code *regex_PRDR         = NULL;
 #endif


 #ifdef SUPPORT_I18N
-const pcre *regex_UTF8         = NULL;
+const pcre2_code *regex_UTF8         = NULL;
 #endif


 /* Input-reading functions for messages, so we can use special ones for
@@ -711,10 +711,10 @@ uschar *chunking_advertise_hosts = US"*";
 unsigned chunking_datasize     = 0;
 unsigned chunking_data_left    = 0;
 chunking_state_t chunking_state= CHUNKING_NOT_OFFERED;
-const pcre *regex_CHUNKING     = NULL;
+const pcre2_code *regex_CHUNKING     = NULL;


 #ifdef EXPERIMENTAL_ESMTP_LIMITS
-const pcre *regex_LIMITS        = NULL;
+const pcre2_code *regex_LIMITS        = NULL;
 #endif


uschar *client_authenticator = NULL;
@@ -1212,6 +1212,10 @@ uid_t originator_uid;
uschar *override_local_interfaces = NULL;
uschar *override_pid_file_path = NULL;

+pcre2_general_context * pcre_gen_ctx = NULL;
+pcre2_compile_context * pcre_cmp_ctx = NULL;
+pcre2_match_context * pcre_mtc_ctx = NULL;
+
 uschar *percent_hack_domains   = NULL;
 uschar *pid_file_path          = US PID_FILE_PATH
                            "\0<--------------Space to patch pid_file_path->";
@@ -1305,20 +1309,20 @@ int     recipients_count       = 0;
 recipient_item  *recipients_list = NULL;
 int     recipients_list_max    = 0;
 int     recipients_max         = 50000;
-const pcre *regex_AUTH         = NULL;
-const pcre *regex_check_dns_names = NULL;
-const pcre *regex_From         = NULL;
-const pcre *regex_IGNOREQUOTA  = NULL;
-const pcre *regex_PIPELINING   = NULL;
-const pcre *regex_SIZE         = NULL;
+const pcre2_code *regex_AUTH         = NULL;
+const pcre2_code *regex_check_dns_names = NULL;
+const pcre2_code *regex_From         = NULL;
+const pcre2_code *regex_IGNOREQUOTA  = NULL;
+const pcre2_code *regex_PIPELINING   = NULL;
+const pcre2_code *regex_SIZE         = NULL;
 #ifndef DISABLE_PIPE_CONNECT
-const pcre *regex_EARLY_PIPE   = NULL;
+const pcre2_code *regex_EARLY_PIPE   = NULL;
 #endif
-const pcre *regex_ismsgid      = NULL;
-const pcre *regex_smtp_code    = NULL;
+const pcre2_code *regex_ismsgid      = NULL;
+const pcre2_code *regex_smtp_code    = NULL;
 uschar *regex_vars[REGEX_VARS];
 #ifdef WHITELIST_D_MACROS
-const pcre *regex_whitelisted_macro = NULL;
+const pcre2_code *regex_whitelisted_macro = NULL;
 #endif
 #ifdef WITH_CONTENT_SCAN
 uschar *regex_match_string     = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 731408b..193eed6 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -127,7 +127,7 @@ extern BOOL    gnutls_compat_mode;     /* Less security, more compatibility */
 extern BOOL    gnutls_allow_auto_pkcs11; /* Let GnuTLS autoload PKCS11 modules */
 extern uschar *hosts_require_alpn;     /* Mandatory ALPN successful nogitiation */
 extern uschar *openssl_options;        /* OpenSSL compatibility options */
-extern const pcre *regex_STARTTLS;     /* For recognizing STARTTLS settings */
+extern const pcre2_code *regex_STARTTLS;     /* For recognizing STARTTLS settings */
 extern uschar *tls_alpn;           /* ALPN names acceptable */
 extern uschar *tls_certificate;        /* Certificate file */
 extern uschar *tls_crl;                /* CRL File */
@@ -153,7 +153,7 @@ extern uschar *tls_advertise_hosts;    /* host for which TLS is advertised */


 extern uschar  *dsn_envid;             /* DSN envid string */
 extern int      dsn_ret;               /* DSN ret type*/
-extern const pcre  *regex_DSN;         /* For recognizing DSN settings */
+extern const pcre2_code  *regex_DSN;         /* For recognizing DSN settings */
 extern uschar  *dsn_advertise_hosts;   /* host for which TLS is advertised */


 /* Input-reading functions for messages, so we can use special ones for
@@ -320,7 +320,7 @@ extern uschar *acl_smtp_connect;       /* ACL run on SMTP connection */
 extern uschar *acl_smtp_data;          /* ACL run after DATA received */
 #ifndef DISABLE_PRDR
 extern uschar *acl_smtp_data_prdr;     /* ACL run after DATA received if in PRDR mode*/
-const extern pcre *regex_PRDR;         /* For recognizing PRDR settings */
+const extern pcre2_code *regex_PRDR;         /* For recognizing PRDR settings */
 #endif
 #ifndef DISABLE_DKIM
 extern uschar *acl_smtp_dkim;          /* ACL run for DKIM signatures / domains */
@@ -731,7 +731,7 @@ extern uschar *message_size_limit;     /* As it says */
 #ifdef SUPPORT_I18N
 extern BOOL    message_smtputf8;       /* Internationalized mail handling */
 extern int     message_utf8_downconvert; /* convert from utf8 */
-const extern pcre *regex_UTF8;         /* For recognizing SMTPUTF8 settings */
+const extern pcre2_code *regex_UTF8;         /* For recognizing SMTPUTF8 settings */
 #endif
 extern uschar  message_subdir[];       /* Subdirectory for messages */
 extern uschar *message_reference;      /* Reference for error messages */
@@ -779,6 +779,10 @@ extern uid_t   originator_uid;         /* Uid of ditto */
 extern uschar *override_local_interfaces; /* Value of -oX argument */
 extern uschar *override_pid_file_path; /* Value of -oP argument */


+extern pcre2_general_context * pcre_gen_ctx;    /* pcre memory management */
+extern pcre2_compile_context * pcre_cmp_ctx;
+extern pcre2_match_context *   pcre_mtc_ctx;
+
 extern uschar *percent_hack_domains;   /* Local domains for which '% operates */
 extern uschar *pid_file_path;          /* For writing daemon pids */
 #ifndef DISABLE_PIPE_CONNECT
@@ -865,24 +869,24 @@ extern uschar *recipient_verify_failure; /* What went wrong */
 extern int     recipients_list_max;    /* Maximum number fitting in list */
 extern int     recipients_max;         /* Max permitted */
 extern BOOL    recipients_max_reject;  /* If TRUE, reject whole message */
-extern const pcre *regex_AUTH;         /* For recognizing AUTH settings */
-extern const pcre  *regex_check_dns_names; /* For DNS name checking */
-extern const pcre  *regex_From;        /* For recognizing "From_" lines */
-extern const pcre  *regex_CHUNKING;    /* For recognizing CHUNKING (RFC 3030) */
-extern const pcre  *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */
+extern const pcre2_code *regex_AUTH;         /* For recognizing AUTH settings */
+extern const pcre2_code  *regex_check_dns_names; /* For DNS name checking */
+extern const pcre2_code  *regex_From;        /* For recognizing "From_" lines */
+extern const pcre2_code  *regex_CHUNKING;    /* For recognizing CHUNKING (RFC 3030) */
+extern const pcre2_code  *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */
 #ifdef EXPERIMENTAL_ESMTP_LIMITS
-extern const pcre  *regex_LIMITS; /* For recognizing LIMITS */
+extern const pcre2_code  *regex_LIMITS; /* For recognizing LIMITS */
 #endif
-extern const pcre  *regex_PIPELINING;  /* For recognizing PIPELINING */
-extern const pcre  *regex_SIZE;        /* For recognizing SIZE settings */
+extern const pcre2_code  *regex_PIPELINING;  /* For recognizing PIPELINING */
+extern const pcre2_code  *regex_SIZE;        /* For recognizing SIZE settings */
 #ifndef DISABLE_PIPE_CONNECT
-extern const pcre  *regex_EARLY_PIPE;  /* For recognizing PIPE_CONNCT */
+extern const pcre2_code  *regex_EARLY_PIPE;  /* For recognizing PIPE_CONNCT */
 #endif
-extern const pcre  *regex_ismsgid;     /* Compiled r.e. for message it */
-extern const pcre  *regex_smtp_code;   /* For recognizing SMTP codes */
+extern const pcre2_code  *regex_ismsgid;     /* Compiled r.e. for message it */
+extern const pcre2_code  *regex_smtp_code;   /* For recognizing SMTP codes */
 extern uschar *regex_vars[];           /* $regexN variables */
 #ifdef WHITELIST_D_MACROS
-extern const pcre  *regex_whitelisted_macro; /* For -D macro values */
+extern const pcre2_code  *regex_whitelisted_macro; /* For -D macro values */
 #endif
 #ifdef WITH_CONTENT_SCAN
 extern uschar *regex_match_string;     /* regex that matched a line (regex ACL condition) */
diff --git a/src/src/header.c b/src/src/header.c
index 720446b..e334c69 100644
--- a/src/src/header.c
+++ b/src/src/header.c
@@ -371,7 +371,7 @@ static BOOL
 one_pattern_match(uschar *name, int slen, BOOL has_addresses, uschar *pattern)
 {
 BOOL yield = FALSE;
-const pcre *re = NULL;
+const pcre2_code *re = NULL;


/* If the pattern is a regex, compile it. Bomb out if compiling fails; these
patterns are all constructed internally and should be valid. */
@@ -419,10 +419,9 @@ for (header_line * h = header_list; !yield && h; h = h->next)

       /* Otherwise, test for the pattern; a non-regex must be an exact match */


-      yield = !re
-        ? (strcmpic(next, pattern) == 0)
-        : (pcre_exec(re, NULL, CS next, Ustrlen(next), 0, PCRE_EOPT, NULL, 0)
-          >= 0);
+      yield = re
+    ? regex_match(re, next, -1, NULL)
+        : (strcmpic(next, pattern) == 0);
       }
     }


@@ -431,10 +430,9 @@ for (header_line * h = header_list; !yield && h; h = h->next)

   else
     {
-    yield = (re == NULL)?
-      (strstric(h->text, pattern, FALSE) != NULL)
-      :
-      (pcre_exec(re, NULL, CS h->text, h->slen, 0, PCRE_EOPT, NULL, 0) >= 0);
+    yield = re
+      ? regex_match(re, h->text, h->slen, NULL)
+      : (strstric(h->text, pattern, FALSE) != NULL);
     }
   }


diff --git a/src/src/macros.h b/src/src/macros.h
index ccdcc45..4263bc4 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -202,8 +202,7 @@ record, and the max number of continuation records allowed. */

/* Macros for trivial functions */

-#define mac_ismsgid(s) \
-  (pcre_exec(regex_ismsgid,NULL,CS s,Ustrlen(s),0,PCRE_EOPT,NULL,0) >= 0)
+#define mac_ismsgid(s)    (regex_match(regex_ismsgid, (s), -1, NULL))



 /* Options for dns_next_rr */
diff --git a/src/src/malware.c b/src/src/malware.c
index a6e354b..cd80cd4 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -129,7 +129,7 @@ for (const struct scan * sc = m_scans; sc->scancode != -1; sc++)
 #define MALWARE_TIMEOUT 120    /* default timeout, seconds */


static const uschar * malware_regex_default = US ".+";
-static const pcre * malware_default_re = NULL;
+static const pcre2_code * malware_default_re = NULL;


 #ifndef DISABLE_MAL_CLAM
@@ -156,35 +156,35 @@ typedef struct clamd_address {
 # define DERR_BAD_CALL               (1<<15)  /* wrong command */


static const uschar * drweb_re_str = US "infected\\swith\\s*(.+?)$";
-static const pcre * drweb_re = NULL;
+static const pcre2_code * drweb_re = NULL;
#endif

#ifndef DISABLE_MAL_FSECURE
static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$";
-static const pcre * fsec_re = NULL;
+static const pcre2_code * fsec_re = NULL;
#endif

#ifndef DISABLE_MAL_KAV
static const uschar * kav_re_sus_str = US "suspicion:\\s*(.+?)\\s*$";
static const uschar * kav_re_inf_str = US "infected:\\s*(.+?)\\s*$";
-static const pcre * kav_re_sus = NULL;
-static const pcre * kav_re_inf = NULL;
+static const pcre2_code * kav_re_sus = NULL;
+static const pcre2_code * kav_re_inf = NULL;
#endif

#ifndef DISABLE_MAL_AVAST
static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]";
static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d+\\.0\\t0\\s(.*)";
static const uschar * ava_re_error_str = US "(?!\\\\)\\t\\[E\\]\\d+\\.0\\tError\\s\\d+\\s(.*)";
-static const pcre * ava_re_clean = NULL;
-static const pcre * ava_re_virus = NULL;
-static const pcre * ava_re_error = NULL;
+static const pcre2_code * ava_re_clean = NULL;
+static const pcre2_code * ava_re_virus = NULL;
+static const pcre2_code * ava_re_error = NULL;
#endif

#ifndef DISABLE_MAL_FFROT6D
static const uschar * fprot6d_re_error_str = US "^\\d+\\s<(.+?)>$";
static const uschar * fprot6d_re_virus_str = US "^\\d+\\s<infected:\\s+(.+?)>\\s+.+$";
-static const pcre * fprot6d_re_error = NULL;
-static const pcre * fprot6d_re_virus = NULL;
+static const pcre2_code * fprot6d_re_error = NULL;
+static const pcre2_code * fprot6d_re_virus = NULL;
#endif


@@ -301,37 +301,43 @@ if (send(sock, buf, cnt, 0) < 0)
return sock;
}

-static const pcre *
+static const pcre2_code *
m_pcre_compile(const uschar * re, uschar ** errstr)
{
-const uschar * rerror;
-int roffset;
-const pcre * cre;
+int err;
+PCRE2_SIZE roffset;
+const pcre2_code * cre;

-if (!(cre = pcre_compile(CS re, PCRE_COPT, CCSS &rerror, &roffset, NULL)))
-  *errstr= string_sprintf("regular expression error in '%s': %s at offset %d",
-      re, rerror, roffset);
+if (!(cre = pcre2_compile((PCRE2_SPTR)re, PCRE2_ZERO_TERMINATED,
+          PCRE_COPT, &err, &roffset, pcre_cmp_ctx)))
+  {
+  uschar errbuf[128];
+  pcre2_get_error_message(err, errbuf, sizeof(errbuf));
+  *errstr= string_sprintf("regular expression error in '%s': %s at offset %l",
+      re, errbuf, (long)roffset);
+  }
 return cre;
 }


 uschar *
-m_pcre_exec(const pcre * cre, uschar * text)
+m_pcre_exec(const pcre2_code * cre, uschar * text)
 {
-int ovector[10*3];
-int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0,
-          ovector, nelem(ovector));
-uschar * substr = NULL;
+pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx);
+int i = pcre2_match(cre, text, PCRE2_ZERO_TERMINATED, 0, 0, md, pcre_mtc_ctx);
+PCRE2_UCHAR * substr = NULL;
+PCRE2_SIZE slen;
+
 if (i >= 2)                /* Got it */
-  pcre_get_substring(CS text, ovector, i, 1, CCSS &substr);
-return substr;
+  pcre2_substring_get_bynumber(md, 1, &substr, &slen);
+return US substr;
 }


-static const pcre *
+static const pcre2_code *
m_pcre_nextinlist(const uschar ** list, int * sep,
char * listerr, uschar ** errstr)
{
const uschar * list_ele;
-const pcre * cre = NULL;
+const pcre2_code * cre = NULL;

 if (!(list_ele = string_nextinlist(list, sep, NULL, 0)))
   *errstr = US listerr;
@@ -581,7 +587,7 @@ const uschar *av_scanner_work = av_scanner;
 uschar *scanner_name;
 unsigned long mbox_size;
 FILE *mbox_file;
-const pcre *re;
+const pcre2_code *re;
 uschar * errstr;
 struct scan * scanent;
 const uschar * scanner_options;
@@ -923,7 +929,7 @@ badseek:  err = errno;
     /* read and concatenate virus names into one string */
     for (int i = 0; i < drweb_vnum; i++)
       {
-      int ovector[10*3];
+      pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx);


       /* read the size of report */
       if (!recv_len(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), tmo))
@@ -941,22 +947,20 @@ badseek:  err = errno;
       tmpbuf[drweb_slen] = '\0';


       /* try matcher on the line, grab substring */
-      result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0,
-                  ovector, nelem(ovector));
+      result = pcre2_match(drweb_re, (PCRE2_SPTR)tmpbuf, PCRE2_ZERO_TERMINATED,
+                0, 0, md, pcre_mtc_ctx);
       if (result >= 2)
         {
-        const char * pre_malware_nb;
-
-        pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb);
+        PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md);


         if (i==0)    /* the first name we just copy to malware_name */
-          g = string_cat(NULL, US pre_malware_nb);
+          g = string_catn(NULL, US ovec[2], ovec[3] - ovec[2]);


-        /*XXX could be string_append_listele? */
         else    /* concatenate each new virus name to previous */
-          g = string_append(g, 2, "/", pre_malware_nb);
-
-        pcre_free_substring(pre_malware_nb);
+          {
+          g = string_catn(g, US"/", 1);
+          g = string_catn(g, US ovec[2], ovec[3] - ovec[2]);
+          }
         }
       }
       malware_name = string_from_gstring(g);
@@ -1149,7 +1153,7 @@ badseek:  err = errno;
       int kav_rc;
       unsigned long kav_reportlen;
       int bread;
-      const pcre *kav_re;
+      const pcre2_code *kav_re;
       uschar *p;


       /* get current date and time, build scan request */
@@ -1258,8 +1262,8 @@ badseek:  err = errno;
     case M_CMDL: /* "cmdline" scanner type ---------------------------------- */
       {
       const uschar *cmdline_scanner = scanner_options;
-      const pcre *cmdline_trigger_re;
-      const pcre *cmdline_regex_re;
+      const pcre2_code *cmdline_trigger_re;
+      const pcre2_code *cmdline_regex_re;
       uschar * file_name;
       uschar * commandline;
       void (*eximsigchld)(int);
@@ -1880,8 +1884,8 @@ badseek:  err = errno;
       uschar * linebuffer;
       uschar * sockline_scanner;
       uschar sockline_scanner_default[] = "%s\n";
-      const pcre *sockline_trig_re;
-      const pcre *sockline_name_re;
+      const pcre2_code *sockline_trig_re;
+      const pcre2_code *sockline_name_re;


       /* find scanner command line */
       if (  (sockline_scanner = string_nextinlist(&av_scanner_work, &sep,
@@ -2120,7 +2124,7 @@ badseek:  err = errno;
               if (malware_name)     /* Nothing else matters, just read on */
                 break;


-          if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+          if (regex_match(ava_re_clean, buf, slen, NULL))
         break;


               if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
@@ -2141,7 +2145,7 @@ badseek:  err = errno;
                   break;
                   }
                 }
-              else if (pcre_exec(ava_re_error, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+              else if (regex_match(ava_re_error, buf, slen, NULL))
                 {
                 log_write(0, LOG_MAIN, "internal scanner error (ignored): %s", buf);
                 break;
diff --git a/src/src/match.c b/src/src/match.c
index 5f6c1fb..46dd2ee 100644
--- a/src/src/match.c
+++ b/src/src/match.c
@@ -128,9 +128,9 @@ required. */


 if (pattern[0] == '^')
   {
-  const pcre * re = regex_must_compile(pattern, cb->caseless, FALSE);
+  const pcre2_code * re = regex_must_compile(pattern, cb->caseless, FALSE);
   if (expand_setup < 0
-      ? pcre_exec(re, NULL, CCS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) < 0
+      ? !regex_match(re, s, -1, NULL)
       : !regex_match_and_setup(re, s, 0, expand_setup)
      )
     return FAIL;
diff --git a/src/src/queue.c b/src/src/queue.c
index 5677845..8fb82b4 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -350,8 +350,8 @@ queue_run(uschar *start_id, uschar *stop_id, BOOL recurse)
 {
 BOOL force_delivery = f.queue_run_force || deliver_selectstring != NULL ||
   deliver_selectstring_sender != NULL;
-const pcre *selectstring_regex = NULL;
-const pcre *selectstring_regex_sender = NULL;
+const pcre2_code *selectstring_regex = NULL;
+const pcre2_code *selectstring_regex_sender = NULL;
 uschar *log_detail = NULL;
 int subcount = 0;
 uschar subdirs[64];
@@ -569,9 +569,7 @@ for (int i = queue_run_in_order ? -1 : 0;


       else if (  deliver_selectstring_sender
           && !(f.deliver_selectstring_sender_regex
-          ? (pcre_exec(selectstring_regex_sender, NULL,
-              CS sender_address, Ustrlen(sender_address), 0, PCRE_EOPT,
-              NULL, 0) >= 0)
+          ? regex_match(selectstring_regex_sender, sender_address, -1, NULL)
           : (strstric(sender_address, deliver_selectstring_sender, FALSE)
               != NULL)
           )   )
@@ -590,8 +588,7 @@ for (int i = queue_run_in_order ? -1 : 0;
           {
           uschar *address = recipients_list[i].address;
           if (  (f.deliver_selectstring_regex
-        ? (pcre_exec(selectstring_regex, NULL, CS address,
-             Ustrlen(address), 0, PCRE_EOPT, NULL, 0) >= 0)
+        ? regex_match(selectstring_regex, address, -1, NULL)
                 : (strstric(address, deliver_selectstring, FALSE) != NULL)
         )
              && tree_search(tree_nonrecipients, address) == NULL
diff --git a/src/src/regex.c b/src/src/regex.c
index f9c06b9..35af5e1 100644
--- a/src/src/regex.c
+++ b/src/src/regex.c
@@ -17,7 +17,7 @@


/* Structure to hold a list of Regular expressions */
typedef struct pcre_list {
- pcre *re;
+ pcre2_code *re;
uschar *pcre_text;
struct pcre_list *next;
} pcre_list;
@@ -32,8 +32,6 @@ compile(const uschar * list)
{
int sep = 0;
uschar *regex_string;
-const char *pcre_error;
-int pcre_erroffset;
pcre_list *re_list_head = NULL;
pcre_list *ri;

@@ -41,15 +39,19 @@ pcre_list *ri;
 while ((regex_string = string_nextinlist(&list, &sep, NULL, 0)))
   if (strcmpic(regex_string, US"false") != 0 && Ustrcmp(regex_string, "0") != 0)
     {
-    pcre *re;
+    pcre2_code * re;
+    int err;
+    PCRE2_SIZE pcre_erroffset;


     /* compile our regular expression */
-    if (!(re = pcre_compile( CS regex_string,
-               0, &pcre_error, &pcre_erroffset, NULL )))
+    if (!(re = pcre2_compile( (PCRE2_SPTR) regex_string, PCRE2_ZERO_TERMINATED,
+          0, &err, &pcre_erroffset, pcre_cmp_ctx)))
       {
+      uschar errbuf[128];
+      pcre2_get_error_message(err, errbuf, sizeof(errbuf));
       log_write(0, LOG_MAIN,
-       "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.",
-       regex_string, pcre_error, pcre_erroffset);
+       "regex acl condition warning - error in regex '%s': %s at offset %l, skipped.",
+       regex_string, errbuf, (long)pcre_erroffset);
       continue;
       }


@@ -65,25 +67,31 @@ return re_list_head;
static int
matcher(pcre_list * re_list_head, uschar * linebuffer, int len)
{
-for(pcre_list * ri = re_list_head; ri; ri = ri->next)
+pcre2_match_data * md = pcre2_match_data_create(REGEX_VARS + 1, pcre_gen_ctx);
+
+for (pcre_list * ri = re_list_head; ri; ri = ri->next)
{
- int ovec[3*(REGEX_VARS+1)];
int n;

   /* try matcher on the line */
-  if ((n = pcre_exec(ri->re, NULL, CS linebuffer, len, 0, 0, ovec, nelem(ovec))) > 0)
+  if ((n = pcre2_match(ri->re, (PCRE2_SPTR)linebuffer, len, 0, 0, md, pcre_mtc_ctx)) > 0)
     {
     Ustrncpy(regex_match_string_buffer, ri->pcre_text,
           sizeof(regex_match_string_buffer)-1);
     regex_match_string = regex_match_string_buffer;


     for (int nn = 1; nn < n; nn++)
-      regex_vars[nn-1] =
-    string_copyn(linebuffer + ovec[nn*2], ovec[nn*2+1] - ovec[nn*2]);
+      {
+      PCRE2_UCHAR * cstr;
+      PCRE2_SIZE cslen;
+      pcre2_substring_get_bynumber(md, nn, &cstr, &cslen);
+      regex_vars[nn-1] = US cstr;
+      }


     return OK;
     }
   }
+pcre2_match_data_free(md);
 return FAIL;
 }


diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c
index 3035b88..04bc8e2 100644
--- a/src/src/routers/iplookup.c
+++ b/src/src/routers/iplookup.c
@@ -165,7 +165,7 @@ host_item *host = store_get(sizeof(host_item), FALSE);
address_item *new_addr;
iplookup_router_options_block *ob =
(iplookup_router_options_block *)(rblock->options_block);
-const pcre *re = ob->re_response_pattern;
+const pcre2_code *re = ob->re_response_pattern;
int count, query_len, rc;
int sep = 0;

diff --git a/src/src/routers/iplookup.h b/src/src/routers/iplookup.h
index d5217ef..9b2bf0f 100644
--- a/src/src/routers/iplookup.h
+++ b/src/src/routers/iplookup.h
@@ -17,7 +17,7 @@ typedef struct {
uschar *query;
uschar *response_pattern;
uschar *reroute;
- const pcre *re_response_pattern;
+ const pcre2_code *re_response_pattern;
BOOL optional;
} iplookup_router_options_block;

diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c
index 0090dda..71769db 100644
--- a/src/src/routers/redirect.c
+++ b/src/src/routers/redirect.c
@@ -734,15 +734,13 @@ switch (frc)
       addr->message = yield == FAIL ? US"forced rejection" : US"forced defer";
     else
       {
-      int ovector[3];
-      if (ob->forbid_smtp_code &&
-      pcre_exec(regex_smtp_code, NULL, CS addr->message,
-        Ustrlen(addr->message), 0, PCRE_EOPT,
-        ovector, sizeof(ovector)/sizeof(int)) >= 0)
+      uschar * matched;
+      if (  ob->forbid_smtp_code
+     && regex_match(regex_smtp_code, addr->message, -1, &matched))
     {
     DEBUG(D_route) debug_printf("SMTP code at start of error message "
       "is ignored because forbid_smtp_code is set\n");
-    addr->message += ovector[1];
+    addr->message += Ustrlen(matched);
     }
       addr->user_message = addr->message;
       setflag(addr, af_pass_message);
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index ee248c5..5e75e2c 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -3266,27 +3266,26 @@ void
 smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg,
   BOOL check_valid)
 {
-int n;
-int ovector[3];
+uschar * match;
+int len;


-if (!msg || !*msg) return;
-
-if ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0,
- PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return;
+if (!msg || !*msg || !regex_match(regex_smtp_code, *msg, -1, &match))
+ return;

+len = Ustrlen(match);
 if (check_valid && (*msg)[0] != (*code)[0])
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
     "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg);
-  if (log_msg != NULL && *log_msg == *msg)
-    *log_msg = string_sprintf("%s %s", *code, *log_msg + ovector[1]);
+  if (log_msg && *log_msg == *msg)
+    *log_msg = string_sprintf("%s %s", *code, *log_msg + len);
   }
 else
   {
   *code = *msg;
-  *codelen = ovector[1];    /* Includes final space */
+  *codelen = len;    /* Includes final space */
   }
-*msg += ovector[1];         /* Chop the code off the message */
+*msg += len;         /* Chop the code off the message */
 return;
 }


diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index 2d008d9..bbd4a16 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -662,14 +662,14 @@ the log, because we are running as an unprivileged user here.
 Arguments:
   dirname       the name of the directory
   countptr      where to add the file count (because this function recurses)
-  regex         a compiled regex to get the size from a name
+  re            a compiled regex to get the size from a name


 Returns:        the sum of the sizes of the stattable files
                 zero if the directory cannot be opened
 */


off_t
-check_dir_size(const uschar * dirname, int *countptr, const pcre *regex)
+check_dir_size(const uschar * dirname, int * countptr, const pcre2_code * re)
{
DIR *dir;
off_t sum = 0;
@@ -688,14 +688,18 @@ for (struct dirent *ent; ent = readdir(dir); )

/* If there's a regex, try to find the size using it */

-  if (regex)
+  if (re)
     {
-    int ovector[6];
-    if (pcre_exec(regex, NULL, CS name, Ustrlen(name), 0, 0, ovector,6) >= 2)
+    pcre2_match_data * md = pcre2_match_data_create(2, pcre_gen_ctx);
+    int rc = pcre2_match(re, (PCRE2_SPTR)name, PCRE2_ZERO_TERMINATED,
+              0, 0, md, pcre_mtc_ctx);
+    PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md);
+    if (  rc >= 0
+       && (rc = pcre2_get_ovector_count(md)) >= 2)
       {
       uschar *endptr;
-      off_t size = (off_t)Ustrtod(name + ovector[2], &endptr);
-      if (endptr == name + ovector[3])
+      off_t size = (off_t)Ustrtod(name + ovec[2], &endptr);
+      if (endptr == name + ovec[3])
         {
         sum += size;
         DEBUG(D_transport)
@@ -722,7 +726,7 @@ for (struct dirent *ent; ent = readdir(dir); )
     if ((statbuf.st_mode & S_IFMT) == S_IFREG)
       sum += statbuf.st_size / statbuf.st_nlink;
     else if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
-      sum += check_dir_size(path, &count, regex);
+      sum += check_dir_size(path, &count, re);
   }


 closedir(dir);
@@ -2175,7 +2179,7 @@ scanning is expensive; for maildirs some fudges have been invented:
 else
   {
   uschar *check_path;        /* Default quota check path */
-  const pcre *regex = NULL;     /* Regex for file size from file name */
+  const pcre2_code * re = NULL;     /* Regex for file size from file name */


   if (!check_creation(string_sprintf("%s/any", path),
               ob->create_file, deliver_dir))
@@ -2218,18 +2222,20 @@ else


   if (ob->quota_value > 0 || THRESHOLD_CHECK || ob->maildir_use_size_file)
     {
-    const uschar *error;
-    int offset;
+    PCRE2_SIZE offset;
+    int err;


     /* Compile the regex if there is one. */


     if (ob->quota_size_regex)
       {
-      if (!(regex = pcre_compile(CS ob->quota_size_regex, PCRE_COPT,
-      CCSS &error, &offset, NULL)))
+      if (!(re = pcre2_compile((PCRE2_SPTR)ob->quota_size_regex,
+          PCRE2_ZERO_TERMINATED, PCRE_COPT, &err, &offset, pcre_cmp_ctx)))
         {
+    uschar errbuf[128];
+    pcre2_get_error_message(err, errbuf, sizeof(errbuf));
         addr->message = string_sprintf("appendfile: regular expression "
-          "error: %s at offset %d while compiling %s", error, offset,
+          "error: %s at offset %l while compiling %s", errbuf, (long)offset,
           ob->quota_size_regex);
         return FALSE;
         }
@@ -2304,19 +2310,21 @@ else
   #ifdef SUPPORT_MAILDIR
   if (ob->maildir_use_size_file)
     {
-    const pcre *dir_regex = NULL;
-    const uschar *error;
-    int offset;
+    const pcre2_code * dir_regex = NULL;
+    PCRE2_SIZE offset;
+    int err;


     if (ob->maildir_dir_regex)
       {
       int check_path_len = Ustrlen(check_path);


-      if (!(dir_regex = pcre_compile(CS ob->maildir_dir_regex, PCRE_COPT,
-      CCSS &error, &offset, NULL)))
+      if (!(dir_regex = pcre2_compile((PCRE2_SPTR)ob->maildir_dir_regex,
+        PCRE2_ZERO_TERMINATED, PCRE_COPT, &err, &offset, pcre_cmp_ctx)))
         {
+    uschar errbuf[128];
+    pcre2_get_error_message(err, errbuf, sizeof(errbuf));
         addr->message = string_sprintf("appendfile: regular expression "
-          "error: %s at offset %d while compiling %s", error, offset,
+          "error: %s at offset %l while compiling %s", errbuf, (long)offset,
           ob->maildir_dir_regex);
         return FALSE;
         }
@@ -2335,7 +2343,7 @@ else
         uschar *s = path + check_path_len;
         while (*s == '/') s++;
         s = *s ? string_sprintf("%s/new", s) : US"new";
-        if (pcre_exec(dir_regex, NULL, CS s, Ustrlen(s), 0, 0, NULL, 0) < 0)
+    if (!regex_match(dir_regex, s, -1, NULL))
           {
           disable_quota = TRUE;
           DEBUG(D_transport) debug_printf("delivery directory does not match "
@@ -2356,7 +2364,7 @@ else
       off_t size;
       int filecount;


-      if ((maildirsize_fd = maildir_ensure_sizefile(check_path, ob, regex, dir_regex,
+      if ((maildirsize_fd = maildir_ensure_sizefile(check_path, ob,  re, dir_regex,
       &size, &filecount)) == -1)
         {
         addr->basic_errno = errno;
@@ -2381,7 +2389,7 @@ else
  *    (void)unlink(CS string_sprintf("%s/maildirsize", check_path));
  *    if (THRESHOLD_CHECK)
  *      mailbox_size = maildir_compute_size(check_path, &mailbox_filecount, &old_latest,
- *        regex, dir_regex, FALSE);
+ *         re, dir_regex, FALSE);
  *    }
 */


@@ -2403,7 +2411,7 @@ else
     int filecount = 0;
     DEBUG(D_transport)
       debug_printf("quota checks on directory %s\n", check_path);
-    size = check_dir_size(check_path, &filecount, regex);
+    size = check_dir_size(check_path, &filecount,  re);
     if (mailbox_size < 0) mailbox_size = size;
     if (mailbox_filecount < 0) mailbox_filecount = filecount;
     }
diff --git a/src/src/transports/appendfile.h b/src/src/transports/appendfile.h
index 4f0f126..0f5740e 100644
--- a/src/src/transports/appendfile.h
+++ b/src/src/transports/appendfile.h
@@ -94,6 +94,6 @@ extern void appendfile_transport_init(transport_instance *);


/* Function that is shared with tf_maildir.c */

-extern off_t check_dir_size(const uschar *, int *, const pcre *);
+extern off_t check_dir_size(const uschar *, int *, const pcre2_code *);

/* End of transports/appendfile.h */
diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c
index 5a1b7c9..e3b3639 100644
--- a/src/src/transports/lmtp.c
+++ b/src/src/transports/lmtp.c
@@ -558,23 +558,23 @@ allows for message+recipient checks after the message has been received. */
/* First thing is to wait for an initial greeting. */

Ustrcpy(big_buffer, US"initial connection");
-if (!lmtp_read_response(out, buffer, sizeof(buffer), '2',
- timeout)) goto RESPONSE_FAILED;
+if (!lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout))
+ goto RESPONSE_FAILED;

/* Next, we send a LHLO command, and expect a positive response */

-if (!lmtp_write_command(fd_in, "%s %s\r\n", "LHLO",
- primary_hostname)) goto WRITE_FAILED;
+if (!lmtp_write_command(fd_in, "%s %s\r\n", "LHLO", primary_hostname))
+ goto WRITE_FAILED;

-if (!lmtp_read_response(out, buffer, sizeof(buffer), '2',
-     timeout)) goto RESPONSE_FAILED;
+if (!lmtp_read_response(out, buffer, sizeof(buffer), '2', timeout))
+  goto RESPONSE_FAILED;


/* If the ignore_quota option is set, note whether the server supports the
IGNOREQUOTA option, and if so, set an appropriate addition for RCPT. */

 if (ob->ignore_quota)
-  igquotstr = (pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer,
-    Ustrlen(CS buffer), 0, PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+  igquotstr = regex_match(regex_IGNOREQUOTA, buffer, -1, NULL)
+    ? US" IGNOREQUOTA" : US"";


/* Now the envelope sender */

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 7bb1249..0db157a 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -237,48 +237,39 @@ static unsigned ehlo_response(uschar * buf, unsigned checks);
 void
 smtp_deliver_init(void)
 {
-if (!regex_PIPELINING) regex_PIPELINING =
-  regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_SIZE) regex_SIZE =
-  regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_AUTH) regex_AUTH =
-  regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+struct list
+  {
+  const pcre2_code **    re;
+  const uschar *    string;
+  } list[] =
+  {
+    { &regex_AUTH,        AUTHS_REGEX },
+    { &regex_CHUNKING,        US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)" },
+    { &regex_DSN,        US"\\n250[\\s\\-]DSN(\\s|\\n|$)" },
+    { &regex_IGNOREQUOTA,    US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)" },
+    { &regex_PIPELINING,    US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)" },
+    { &regex_SIZE,        US"\\n250[\\s\\-]SIZE(\\s|\\n|$)" },


 #ifndef DISABLE_TLS
-if (!regex_STARTTLS) regex_STARTTLS =
-  regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_STARTTLS,        US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)" },
 #endif
-
-if (!regex_CHUNKING) regex_CHUNKING =
-  regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE);
-
 #ifndef DISABLE_PRDR
-if (!regex_PRDR) regex_PRDR =
-  regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_PRDR,        US"\\n250[\\s\\-]PRDR(\\s|\\n|$)" },
 #endif
-
 #ifdef SUPPORT_I18N
-if (!regex_UTF8) regex_UTF8 =
-  regex_must_compile(US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_UTF8,        US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)" },
 #endif
-
-if (!regex_DSN) regex_DSN  =
-  regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
-  regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
-
 #ifndef DISABLE_PIPE_CONNECT
-if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
-  regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
+    { &regex_EARLY_PIPE,      US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)" },
 #endif
-
 #ifdef EXPERIMENTAL_ESMTP_LIMITS
-if (!regex_LIMITS) regex_LIMITS =
-  regex_must_compile(US"\\n250[\\s\\-]LIMITS\\s", FALSE, TRUE);
+    { &regex_LIMITS,        US"\\n250[\\s\\-]LIMITS\\s" },
 #endif
+  };
+
+for (struct list * l = list; l < list + nelem(list); l++)
+  if (!*l->re)
+    *l->re = regex_must_compile(l->string, FALSE, TRUE);
 }



@@ -777,13 +768,12 @@ This saves us dealing with a duplicate set of values. */
 static void
 ehlo_response_limits_read(smtp_context * sx)
 {
-int ovec[3];    /* results vector for a main-match only */
+uschar * match;


/* matches up to just after the first space after the keyword */

-if (pcre_exec(regex_LIMITS, NULL, CS sx->buffer, Ustrlen(sx->buffer),
-          0, PCRE_EOPT, ovec, nelem(ovec)) >= 0)
-  for (const uschar * s = sx->buffer + ovec[1]; *s; )
+if (regex_match(regex_LIMITS, sx->buffer, -1, &match))
+  for (const uschar * s = sx->buffer + Ustrlen(match); *s; )
     {
     while (isspace(*s)) s++;
     if (*s == '\n') break;
@@ -1809,57 +1799,65 @@ return Ustrcmp(current_local_identity, message_local_identity) == 0;
 static unsigned
 ehlo_response(uschar * buf, unsigned checks)
 {
-size_t bsize = Ustrlen(buf);
+PCRE2_SIZE bsize = Ustrlen(buf);
+pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx);


/* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */

 #ifndef DISABLE_TLS
 if (  checks & OPTION_TLS
-   && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_STARTTLS,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_TLS;


 if (  checks & OPTION_IGNQ
-   && pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
-        PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_IGNOREQUOTA,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_IGNQ;


 if (  checks & OPTION_CHUNKING
-   && pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_CHUNKING,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_CHUNKING;


 #ifndef DISABLE_PRDR
 if (  checks & OPTION_PRDR
-   && pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_PRDR,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_PRDR;


 #ifdef SUPPORT_I18N
 if (  checks & OPTION_UTF8
-   && pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_UTF8,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_UTF8;


 if (  checks & OPTION_DSN
-   && pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_DSN,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_DSN;


 if (  checks & OPTION_PIPE
-   && pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
-        PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_PIPELINING,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_PIPE;


 if (  checks & OPTION_SIZE
-   && pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_SIZE,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
   checks &= ~OPTION_SIZE;


 #ifndef DISABLE_PIPE_CONNECT
 if (  checks & OPTION_EARLY_PIPE
-   && pcre_exec(regex_EARLY_PIPE, NULL, CS buf, bsize, 0,
-        PCRE_EOPT, NULL, 0) < 0)
+   && pcre2_match(regex_EARLY_PIPE,
+          (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0)
 #endif
   checks &= ~OPTION_EARLY_PIPE;


+pcre2_match_data_free(md);
 /* debug_printf("%s: found     0x%04x\n", __FUNCTION__, checks); */
 return checks;
 }
diff --git a/src/src/transports/tf_maildir.c b/src/src/transports/tf_maildir.c
index cd1964a..2d7d01f 100644
--- a/src/src/transports/tf_maildir.c
+++ b/src/src/transports/tf_maildir.c
@@ -140,24 +140,26 @@ for (i = 0; i < 4; i++)
 /* If the basic path matches maildirfolder_create_regex, we are dealing with
 a subfolder, and should ensure that a maildirfolder file exists. */


-if (maildirfolder_create_regex != NULL)
+if (maildirfolder_create_regex)
{
- const uschar *error;
- int offset;
- const pcre *regex;
+ int err;
+ PCRE2_SIZE offset;
+ const pcre2_code * re;

DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n");

-  if (!(regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT,
-    CCSS &error, &offset, NULL)))
+  if (!(re = pcre2_compile((PCRE2_SPTR)maildirfolder_create_regex,
+          PCRE2_ZERO_TERMINATED, PCRE_COPT, &err, &offset, pcre_cmp_ctx)))
     {
+    uschar errbuf[128];
+    pcre2_get_error_message(err, errbuf, sizeof(errbuf));
     addr->message = string_sprintf("appendfile: regular expression "
-      "error: %s at offset %d while compiling %s", error, offset,
+      "error: %s at offset %l while compiling %s", errbuf, (long)offset,
       maildirfolder_create_regex);
     return FALSE;
     }


-  if (pcre_exec(regex, NULL, CS path, Ustrlen(path), 0, 0, NULL, 0) >= 0)
+  if (regex_match(re, path, -1, NULL))
     {
     uschar *fname = string_sprintf("%s/maildirfolder", path);
     if (Ustat(fname, &statbuf) == 0)
@@ -250,7 +252,7 @@ Returns:      the sum of the sizes of the messages


off_t
maildir_compute_size(uschar *path, int *filecount, time_t *latest,
- const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
+ const pcre2_code *regex, const pcre2_code *dir_regex, BOOL timestamp_only)
{
DIR *dir;
off_t sum = 0;
@@ -269,8 +271,7 @@ for (struct dirent *ent; ent = readdir(dir); )
scan. We do the regex match first, because that avoids a stat() for names
we aren't interested in. */

-  if (dir_regex != NULL &&
-      pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
+  if (dir_regex && !regex_match(dir_regex, name, -1, NULL))
     {
     DEBUG(D_transport)
       debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
@@ -358,7 +359,7 @@ Returns:           >=0  a file descriptor for an open maildirsize file


int
maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
- const pcre *regex, const pcre *dir_regex, off_t *returned_size,
+ const pcre2_code *regex, const pcre2_code *dir_regex, off_t *returned_size,
int *returned_filecount)
{
int count, fd;
diff --git a/src/src/transports/tf_maildir.h b/src/src/transports/tf_maildir.h
index 0be6bc4..8c26b32 100644
--- a/src/src/transports/tf_maildir.h
+++ b/src/src/transports/tf_maildir.h
@@ -8,13 +8,13 @@
/* Header file for the functions that are used to support the use of
maildirsize files for quota handling in maildir directories. */

-extern off_t  maildir_compute_size(uschar *, int *, time_t *, const pcre *,
-                const pcre *, BOOL);
+extern off_t  maildir_compute_size(uschar *, int *, time_t *, const pcre2_code *,
+                const pcre2_code *, BOOL);
 extern BOOL   maildir_ensure_directories(uschar *, address_item *, BOOL, int,
                 uschar *);
 extern int    maildir_ensure_sizefile(uschar *,
-                appendfile_transport_options_block *, const pcre *,
-                const pcre *, off_t *, int *);
+                appendfile_transport_options_block *, const pcre2_code *,
+                const pcre2_code *, off_t *, int *);
 extern void   maildir_record_length(int, int);


/* End of tf_maildir.h */