]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add support for pcre2 in place of pcre
authorNick Porter <nick@portercomputing.co.uk>
Mon, 28 Jul 2025 09:53:43 +0000 (10:53 +0100)
committerNick Porter <nick@portercomputing.co.uk>
Mon, 28 Jul 2025 11:26:06 +0000 (12:26 +0100)
configure
configure.ac
src/include/autoconf.h.in
src/include/radiusd.h
src/include/regex.h
src/lib/regex.c
src/main/evaluate.c
src/main/mainconfig.c
src/main/regex.c

index 4264cd4432c8cd599336e7fe2b85d167e0a834c6..2b82073e9cbe4af060631722bfc8aa3ef7655842 100755 (executable)
--- a/configure
+++ b/configure
@@ -13568,6 +13568,317 @@ if test "x$REGEX" != "xno" && test "x$PCRE" != "xno"; then
   smart_try_dir=$pcre_include_dir
 
 
+ac_safe=`echo "pcre2.h" | sed 'y%./+-%__pm%'`
+old_CPPFLAGS="$CPPFLAGS"
+smart_include=
+smart_include_dir="/usr/local/include /opt/include"
+
+_smart_try_dir=
+_smart_include_dir=
+
+for _prefix in $smart_prefix ""; do
+  for _dir in $smart_try_dir; do
+    _smart_try_dir="${_smart_try_dir} ${_dir}/${_prefix}"
+  done
+
+  for _dir in $smart_include_dir; do
+    _smart_include_dir="${_smart_include_dir} ${_dir}/${_prefix}"
+  done
+done
+
+if test "x$_smart_try_dir" != "x"; then
+  for try in $_smart_try_dir; do
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pcre2.h in $try" >&5
+printf %s "checking for pcre2.h in $try... " >&6; }
+    CPPFLAGS="-isystem $try $old_CPPFLAGS"
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#define PCRE2_CODE_UNIT_WIDTH (8)
+                   #include <pcre2.h>
+int
+main (void)
+{
+int a = 1;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+                    smart_include="-isystem $try"
+                    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+                    break
+
+else $as_nop
+
+                    smart_include=
+                    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+  done
+  CPPFLAGS="$old_CPPFLAGS"
+fi
+
+if test "x$smart_include" = "x"; then
+  for _prefix in $smart_prefix; do
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${_prefix}/pcre2.h" >&5
+printf %s "checking for ${_prefix}/pcre2.h... " >&6; }
+
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#define PCRE2_CODE_UNIT_WIDTH (8)
+                   #include <pcre2.h>
+int
+main (void)
+{
+int a = 1;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+                    smart_include="-isystem ${_prefix}/"
+                    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+                    break
+
+else $as_nop
+
+                    smart_include=
+                    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+  done
+fi
+
+if test "x$smart_include" = "x"; then
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pcre2.h" >&5
+printf %s "checking for pcre2.h... " >&6; }
+
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#define PCRE2_CODE_UNIT_WIDTH (8)
+                   #include <pcre2.h>
+int
+main (void)
+{
+int a = 1;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+                    smart_include=" "
+                    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+                    break
+
+else $as_nop
+
+                    smart_include=
+                    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+if test "x$smart_include" = "x"; then
+
+  for try in $_smart_include_dir; do
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pcre2.h in $try" >&5
+printf %s "checking for pcre2.h in $try... " >&6; }
+    CPPFLAGS="-isystem $try $old_CPPFLAGS"
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#define PCRE2_CODE_UNIT_WIDTH (8)
+                   #include <pcre2.h>
+int
+main (void)
+{
+int a = 1;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+                    smart_include="-isystem $try"
+                    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+                    break
+
+else $as_nop
+
+                    smart_include=
+                    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+  done
+  CPPFLAGS="$old_CPPFLAGS"
+fi
+
+if test "x$smart_include" != "x"; then
+  eval "ac_cv_header_$ac_safe=yes"
+  CPPFLAGS="$smart_include $old_CPPFLAGS"
+  SMART_CPPFLAGS="$smart_include $SMART_CPPFLAGS"
+fi
+
+smart_prefix=
+
+  if test "x$ac_cv_header_pcre2_h" = "xyes"; then
+    smart_try_dir=$pcre_lib_dir
+
+
+sm_lib_safe=`echo "pcre2-8" | sed 'y%./+-%__p_%'`
+sm_func_safe=`echo "pcre2_compile_8" | sed 'y%./+-%__p_%'`
+
+old_LIBS="$LIBS"
+old_CPPFLAGS="$CPPFLAGS"
+smart_lib=
+smart_ldflags=
+smart_lib_dir=
+
+if test "x$smart_try_dir" != "x"; then
+  for try in $smart_try_dir; do
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pcre2_compile_8 in -lpcre2-8 in $try" >&5
+printf %s "checking for pcre2_compile_8 in -lpcre2-8 in $try... " >&6; }
+    LIBS="-lpcre2-8 $old_LIBS"
+    CPPFLAGS="-L$try -Wl,-rpath,$try $old_CPPFLAGS"
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+extern char pcre2_compile_8();
+int
+main (void)
+{
+pcre2_compile_8()
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+
+                smart_lib="-lpcre2-8"
+                smart_ldflags="-L$try -Wl,-rpath,$try"
+                { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+                break
+
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+  done
+  LIBS="$old_LIBS"
+  CPPFLAGS="$old_CPPFLAGS"
+fi
+
+if test "x$smart_lib" = "x"; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pcre2_compile_8 in -lpcre2-8" >&5
+printf %s "checking for pcre2_compile_8 in -lpcre2-8... " >&6; }
+  LIBS="-lpcre2-8 $old_LIBS"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+extern char pcre2_compile_8();
+int
+main (void)
+{
+pcre2_compile_8()
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+
+               smart_lib="-lpcre2-8"
+               { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+  LIBS="$old_LIBS"
+fi
+
+if test "x$smart_lib" = "x"; then
+  for try in /usr/local/lib /opt/lib; do
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pcre2_compile_8 in -lpcre2-8 in $try" >&5
+printf %s "checking for pcre2_compile_8 in -lpcre2-8 in $try... " >&6; }
+    LIBS="-lpcre2-8 $old_LIBS"
+    CPPFLAGS="-L$try -Wl,-rpath,$try $old_CPPFLAGS"
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+extern char pcre2_compile_8();
+int
+main (void)
+{
+pcre2_compile_8()
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+
+                 smart_lib="-lpcre2-8"
+                 smart_ldflags="-L$try -Wl,-rpath,$try"
+                 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+                 break
+
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+  done
+  LIBS="$old_LIBS"
+  CPPFLAGS="$old_CPPFLAGS"
+fi
+
+if test "x$smart_lib" != "x"; then
+  eval "ac_cv_lib_${sm_lib_safe}_${sm_func_safe}=yes"
+  LIBS="$smart_ldflags $smart_lib $old_LIBS"
+  SMART_LIBS="$smart_ldflags $smart_lib $SMART_LIBS"
+fi
+
+    if test "x$ac_cv_lib_pcre2_8_pcre2_compile_8" = "xyes"; then
+      REGEX=yes
+
+printf "%s\n" "#define HAVE_PCRE2 1" >>confdefs.h
+
+
+printf "%s\n" "#define HAVE_BINSAFE_REGEX 1" >>confdefs.h
+
+    fi
+  fi
+fi
+
+if test "x$REGEX" = "x" && test "x$PCRE" != "xno"; then
+  smart_try_dir=$pcre_include_dir
+
+
 ac_safe=`echo "pcre.h" | sed 'y%./+-%__pm%'`
 old_CPPFLAGS="$CPPFLAGS"
 smart_include=
index 033b248e36c579212f3cf9714db07fd690c9f968..73e40b84d5f698b459870271c11d9426dd1f1c42 100644 (file)
@@ -2277,9 +2277,26 @@ AC_ARG_WITH(regex,
 )
 
 dnl #
-dnl #  First look for PCRE
+dnl #  First look for PCRE2
 dnl #
 if test "x$REGEX" != "xno" && test "x$PCRE" != "xno"; then
+  smart_try_dir=$pcre_include_dir
+  FR_SMART_CHECK_INCLUDE(pcre2.h,[#define PCRE2_CODE_UNIT_WIDTH (8)])
+  if test "x$ac_cv_header_pcre2_h" = "xyes"; then
+    smart_try_dir=$pcre_lib_dir
+    FR_SMART_CHECK_LIB(pcre2-8, pcre2_compile_8)
+    if test "x$ac_cv_lib_pcre2_8_pcre2_compile_8" = "xyes"; then
+      REGEX=yes
+      AC_DEFINE(HAVE_PCRE2, [1], [define this if we have libpcre2])
+      AC_DEFINE(HAVE_BINSAFE_REGEX, 1, [Define if we have a binary safe regular expression library])
+    fi
+  fi
+fi
+
+dnl #
+dnl #  If no PCRE2, look for PCRE
+dnl #
+if test "x$REGEX" = "x" && test "x$PCRE" != "xno"; then
   smart_try_dir=$pcre_include_dir
   FR_SMART_CHECK_INCLUDE(pcre.h)
   if test "x$ac_cv_header_pcre_h" = "xyes"; then
@@ -2294,7 +2311,7 @@ if test "x$REGEX" != "xno" && test "x$PCRE" != "xno"; then
 fi
 
 dnl #
-dnl #  If no PCRE, fallback to POSIX regular expressions
+dnl #  If no PCRE2 or PCRE, fallback to POSIX regular expressions
 dnl #
 if test "x$REGEX" = "x"; then
   smart_try_dir=
index 4774482fb1d5c0d1170b81d37e0ef66dcbce27b1..726bfa9915ee5a65da985ab6983ab18005a18900 100644 (file)
 /* define this if we have libpcre */
 #undef HAVE_PCRE
 
+/* define this if we have libpcre2 */
+#undef HAVE_PCRE2
+
 /* Define to 1 if you have the <prot.h> header file. */
 #undef HAVE_PROT_H
 
index e442b99c19aa3989e117a8c07cc1719162d303a2..67ef17e17d6cfacc5dfdfe535078637951f0c372 100644 (file)
@@ -470,7 +470,7 @@ int regex_request_to_sub(TALLOC_CTX *ctx, char **out, REQUEST *request, uint32_t
 /*
  *     Named capture groups only supported by PCRE.
  */
-#  ifdef HAVE_PCRE
+#  if defined(HAVE_PCRE) || defined(HAVE_PCRE2)
 int    regex_request_to_sub_named(TALLOC_CTX *ctx, char **out, REQUEST *request, char const *name);
 #  endif
 #endif
index efb7b8615cbed32b893108defb0335ec3e5f8553..c56a41c96af2ef1c736a62823fc39d50c2c2aabb 100644 (file)
@@ -29,8 +29,31 @@ RCSIDH(regex_h, "$Id$")
 #  ifdef __cplusplus
 extern "C" {
 #  endif
-#  ifdef HAVE_PCRE
-#    include <pcre.h>
+#  ifdef HAVE_PCRE2
+#    define PCRE2_CODE_UNIT_WIDTH 8
+#    include <pcre2.h>
+int fr_pcre2_gcontext_setup(void);
+void fr_pcre2_gcontext_free(void);
+
+typedef struct regmatch {
+       pcre2_match_data        *match_data;    //!< Match data containing the subject
+                                               ///< and various match offsets.
+#ifndef NDEBUG
+       char const              *subject;       //!< Here for debugging purposes if we explicitly duped the string.
+#endif
+} regmatch_t;
+
+typedef struct regex {
+       pcre2_code_8    *compiled;      //!< Compiled regular expression.
+       uint32_t        subcaptures;    //!< Number of subcaptures contained within the expression.
+       bool            precompiled;    //!< Whether this regex was precompiled, or compiled for one of evaluation.
+       bool            jitd;           //!< Whether JIT data is available.
+} regex_t;
+
+regmatch_t *regex_match_data_alloc(TALLOC_CTX *ctx, uint32_t count);
+
+#  elif defined (HAVE_PCRE)
+#      include <pcre.h>
 /*
  *  Versions older then 8.20 didn't have the JIT functionality
  *  gracefully degrade.
index 64a6dbe31ec5edf809d74b34ac50656b75f3c6d9..67fd16b2b680a20620f7ba64a9bf0ea7f350c38f 100644 (file)
 #include <freeradius-devel/libradius.h>
 #include <freeradius-devel/regex.h>
 
+/*
+ *     Wrapper functions for libpcre2. Much more powerful, and guaranteed
+ *     to be binary safe for both patterns and subjects but require
+ *     libpcre2.
+ */
+#  ifdef HAVE_PCRE2
+/** Free regex_t structure
+ *
+ * Calls libpcre specific free functions for the expression and study.
+ *
+ * @param preg to free.
+ */
+static int _regex_free(regex_t *preg)
+{
+       if (preg->compiled) {
+               pcre2_code_free(preg->compiled);
+               preg->compiled = NULL;
+       }
+
+       return 0;
+}
+
+/** Talloc wrapper for pcre2 memory allocation
+ *
+ * @param[in] to_alloc         How many bytes to alloc.
+ * @param[in] uctx             UNUSED.
+ */
+static void *_pcre2_talloc(PCRE2_SIZE to_alloc, UNUSED void *uctx)
+{
+       return talloc_array(NULL, uint8_t, to_alloc);
+}
+
+/** Talloc wrapper for pcre2 memory freeing
+ *
+ * @param[in] to_free          Memory to free.
+ * @param[in] uctx             UNUSED.
+ */
+static void _pcre2_talloc_free(void *to_free, UNUSED void *uctx)
+{
+       talloc_free(to_free);
+}
+
+/*
+ *     A PCRE2 general context used to point to our custom alloc / free functions
+ */
+static pcre2_general_context   *_pcre2_gcontext = NULL;
+
+int fr_pcre2_gcontext_setup(void)
+{
+       _pcre2_gcontext = pcre2_general_context_create(_pcre2_talloc, _pcre2_talloc_free, NULL);
+       if (!_pcre2_gcontext) return -1;
+       return 1;
+}
+
+void fr_pcre2_gcontext_free(void)
+{
+       pcre2_general_context_free(_pcre2_gcontext);
+}
+
+/** Wrapper around pcre_compile
+ *
+ * Allows the rest of the code to do compilations using one function signature.
+ *
+ * @note Compiled expression must be freed with talloc_free.
+ *
+ * @param out Where to write out a pointer to the structure containing the compiled expression.
+ * @param pattern to compile.
+ * @param len of pattern.
+ * @param ignore_case whether to do case insensitive matching.
+ * @param multiline If true $ matches newlines.
+ * @param subcaptures Whether to compile the regular expression to store subcapture
+ *     data.
+ * @param runtime If false run the pattern through the PCRE JIT to convert it to machine code.
+ *     This trades startup time (longer) for runtime performance (better).
+ * @return >= 1 on success, <= 0 on error. Negative value is offset of parse error.
+ */
+ssize_t regex_compile(TALLOC_CTX *ctx, regex_t **out, char const *pattern, size_t len,
+                     bool ignore_case, bool multiline, bool subcaptures, bool runtime)
+{
+       int             ret;
+       PCRE2_SIZE      offset;
+       int             cflags = 0;
+       regex_t         *preg;
+#ifdef PCRE2_CONFIG_JIT
+       static bool     setup = false;
+       static bool     do_jit = false;
+
+       if (!setup) {
+               pcre2_config(PCRE2_CONFIG_JIT, &do_jit);
+               setup = true;
+       }
+#endif
+
+       *out = NULL;
+
+       if (len == 0) {
+               fr_strerror_printf("Empty expression");
+               return 0;
+       }
+
+       if (ignore_case) cflags |= PCRE2_CASELESS;
+       if (multiline) cflags |= PCRE2_MULTILINE;
+       if (!subcaptures) cflags |= PCRE2_NO_AUTO_CAPTURE;
+
+       preg = talloc_zero(ctx, regex_t);
+       talloc_set_destructor(preg, _regex_free);
+
+       preg->compiled = pcre2_compile((PCRE2_SPTR8)pattern, len, cflags, &ret, &offset, NULL);
+       if (!preg->compiled) {
+               PCRE2_UCHAR errbuff[128];
+
+               pcre2_get_error_message(ret, errbuff, sizeof(errbuff));
+               fr_strerror_printf("%s", (char *)errbuff);
+               talloc_free(preg);
+
+               return -(ssize_t)offset;
+       }
+       if (!runtime) {
+               preg->precompiled = true;
+
+#ifdef PCRE2_CONFIG_JIT
+               /*
+                *      This is expensive, so only do it for
+                *      expressions that are going to be
+                *      evaluated repeatedly.
+                */
+               if (do_jit) {
+                       ret = pcre2_jit_compile(preg->compiled, PCRE2_JIT_COMPLETE);
+                       if (ret < 0) {
+                               PCRE2_UCHAR errbuff[128];
+
+                               pcre2_get_error_message(ret, errbuff, sizeof(errbuff));
+                               fr_strerror_printf("Pattern JIT failed: %s", (char *)errbuff);
+                               talloc_free(preg);
+
+                               return 0;
+                       }
+                       preg->jitd = true;
+               }
+#endif
+       }
+
+       *out = preg;
+
+       return len;
+}
+
+/** Wrapper around pcre2_match
+ *
+ * @param preg The compiled expression.
+ * @param subject to match.
+ * @param len Length of subject.
+ * @param pmatch Array of match pointers.
+ * @param nmatch How big the match array is. Updated to number of matches.
+ * @return -1 on error, 0 on no match, 1 on match.
+ */
+int regex_exec(regex_t *preg, char const *subject, size_t len, regmatch_t *pmatch, size_t *nmatch)
+{
+       int                     ret;
+       uint32_t                options = 0;
+       bool                    dup_subject = true;
+       char                    *our_subject = NULL;
+       pcre2_match_data        *match_data;
+
+       /*
+        *      PCRE_NO_AUTO_CAPTURE is a compile time only flag,
+        *      and can't be passed here.
+        *      We rely on the fact that matches has been set to
+        *      0 as a hint that no subcapture data should be
+        *      generated.
+        */
+       if (!pmatch || !nmatch) {
+               pmatch = NULL;
+               if (nmatch) *nmatch = 0;
+       }
+
+       if (pmatch) {
+#ifdef PCRE2_COPY_MATCHED_SUBJECT
+               /*
+                *      This is apparently only supported for pcre2_match
+                *      NOT pcre2_jit_match.
+                */
+#  ifdef PCRE2_CONFIG_JIT
+               if (!preg->jitd) {
+#  endif
+                       dup_subject = false;
+
+                       /*
+                        *      If PCRE2_COPY_MATCHED_SUBJECT is available
+                        *      and set as an options flag, pcre2_match will
+                        *      strdup the subject string if pcre2_match is
+                        *      successful and store a pointer to it in the
+                        *      regmatch struct.
+                        *
+                        *      The lifetime of the string memory will be
+                        *      bound to the regmatch struct.  This is more
+                        *      efficient that doing it ourselves, as the
+                        *      strdup only occurs if the subject matches.
+                        */
+                       options |= PCRE2_COPY_MATCHED_SUBJECT;
+#  ifdef PCRE2_CONFIG_JIT
+               }
+#  endif
+#endif
+               if (dup_subject) {
+                       /*
+                        *      We have to dup and operate on the duplicate
+                        *      of the subject, because pcre2_jit_match and
+                        *      pcre2_match store a pointer to the subject
+                        *      in the regmatch structure.
+                        */
+                       subject = our_subject = talloc_bstrndup(pmatch, subject, len);
+                       if (!subject) {
+                               fr_strerror_printf("Out of memory");
+                               return -1;
+                       }
+#ifndef NDEBUG
+                       pmatch->subject = subject; /* Stored only for tracking memory issues */
+#endif
+               }
+       }
+
+       /*
+        *      If we weren't given match data we need to alloc it else pcre2_match
+        *      fails when passed NULL match data.
+        */
+       if (!pmatch) {
+               match_data = pcre2_match_data_create_from_pattern(preg->compiled, _pcre2_gcontext);
+               if (!match_data) {
+                       fr_strerror_printf("Failed allocating temporary match data");
+                       return -1;
+               }
+       } else {
+               match_data = pmatch->match_data;
+       }
+
+#ifdef PCRE2_CONFIG_JIT
+       if (preg->jitd) {
+               ret = pcre2_jit_match(preg->compiled, (PCRE2_SPTR8)subject, len, 0, options,
+                                     match_data, NULL);
+       } else
+#endif
+       {
+               ret = pcre2_match(preg->compiled, (PCRE2_SPTR8)subject, len, 0, options,
+                                 match_data, NULL);
+       }
+       if (!pmatch) pcre2_match_data_free(match_data);
+       if (ret < 0) {
+               PCRE2_UCHAR     errbuff[128];
+
+               if (dup_subject) talloc_free(our_subject);
+
+               if (ret == PCRE2_ERROR_NOMATCH) {
+                       if (nmatch) *nmatch = 0;
+                       return 0;
+               }
+
+               pcre2_get_error_message(ret, errbuff, sizeof(errbuff));
+               fr_strerror_printf("regex evaluation failed with code (%i): %s", ret, errbuff);
+
+               return -1;
+       }
+
+       if (nmatch && (ret > 0)) *nmatch = ret;
+
+       return 1;
+}
+
+/** Free libpcre2's matchdata
+ *
+ * @note Don't call directly, will be called if talloc_free is called on a #regmatch_t.
+ */
+static int _pcre2_match_data_free(regmatch_t *regmatch)
+{
+       pcre2_match_data_free(regmatch->match_data);
+       return 0;
+}
+
+/** Allocate vectors to fill with match data
+ *
+ * @param[in] ctx      to allocate match vectors in.
+ * @param[in] count    The number of vectors to allocate.
+ * @return
+ *     - NULL on error.
+ *     - Array of match vectors.
+ */
+regmatch_t *regex_match_data_alloc(TALLOC_CTX *ctx, uint32_t count)
+{
+       regmatch_t *regmatch;
+
+       regmatch = talloc(ctx, regmatch_t);
+       if (!regmatch) {
+       oom:
+               fr_strerror_printf("Out of memory");
+               return NULL;
+       }
+
+       regmatch->match_data = pcre2_match_data_create(count, _pcre2_gcontext);
+       if (!regmatch->match_data) {
+               talloc_free(regmatch);
+               goto oom;
+       }
+       talloc_set_type(regmatch->match_data, pcre2_match_data);
+
+       talloc_set_destructor(regmatch, _pcre2_match_data_free);
+
+       return regmatch;
+}
+
 /*
  *     Wrapper functions for libpcre. Much more powerful, and guaranteed
  *     to be binary safe but require libpcre.
  */
-#  ifdef HAVE_PCRE
+#  elif defined(HAVE_PCRE)
 /** Free regex_t structure
  *
  * Calls libpcre specific free functions for the expression and study.
index c8585b67e0e0940e4bc328d425d7b2ca4d5941ad..8066c56454e89a676831277b12dd78dc2d53fab7 100644 (file)
@@ -161,8 +161,13 @@ static int cond_do_regex(REQUEST *request, fr_cond_t const *c,
        int             ret;
 
        regex_t         *preg, *rreg = NULL;
+#ifdef HAVE_PCRE2
+       regmatch_t      *rxmatch;
+       size_t          nmatch = REQUEST_MAX_REGEX + 1;
+#else
        regmatch_t      rxmatch[REQUEST_MAX_REGEX + 1]; /* +1 for %{0} (whole match) capture group */
        size_t          nmatch = sizeof(rxmatch) / sizeof(regmatch_t);
+#endif
 
        if (!lhs || (lhs_type != PW_TYPE_STRING)) return -1;
 
@@ -173,7 +178,7 @@ static int cond_do_regex(REQUEST *request, fr_cond_t const *c,
        switch (map->rhs->type) {
        case TMPL_TYPE_REGEX_STRUCT: /* pre-compiled to a regex */
                preg = map->rhs->tmpl_preg;
-#ifdef HAVE_PCRE
+#if defined(HAVE_PCRE) || defined(HAVE_PCRE2)
                rad_assert(preg->precompiled);
 #endif
                break;
@@ -196,6 +201,9 @@ static int cond_do_regex(REQUEST *request, fr_cond_t const *c,
                break;
        }
 
+#ifdef HAVE_PCRE2
+       rxmatch = regex_match_data_alloc(request, REQUEST_MAX_REGEX + 1);
+#endif
        ret = regex_exec(preg, lhs->strvalue, lhs_len, rxmatch, &nmatch);
        switch (ret) {
        case 0:
index 4120392a91f072073ec9c98de1916c032281fbaf..3e073b08413439e481a386431e6d17a61591741e 100644 (file)
@@ -49,6 +49,10 @@ extern fr_cond_t     *debug_condition;
 fr_cond_t              *debug_condition = NULL;                        //!< Condition used to mark packets up for checking.
 bool                   event_loop_started = false;             //!< Whether the main event loop has been started yet.
 
+#ifdef HAVE_PCRE2
+#  include <freeradius-devel/regex.h>
+#endif
+
 typedef struct cached_config_t {
        struct cached_config_t *next;
        time_t          created;
@@ -1340,6 +1344,19 @@ do {\
        rad_assert(cs_cache == NULL);
        cs_cache = cc;
 
+#ifdef HAVE_PCRE2
+       /*
+        *      If pcre2 is being used for regex, we need to set up a global context
+        *      to use our alloc / free routines.
+        *      Since this is a library rather than module specific, it can't be done
+        *      with a module bootstrap.
+        */
+       if (fr_pcre2_gcontext_setup() < 0) {
+               ERROR("Failed creating pcre2 general context");
+               return -1;
+       }
+#endif
+
        /* Clear any unprocessed configuration errors */
        (void) fr_strerror();
 
@@ -1353,6 +1370,9 @@ int main_config_free(void)
 {
        virtual_servers_free(0);
 
+#ifdef HAVE_PCRE2
+       fr_pcre2_gcontext_free();
+#endif
        /*
         *      Clean up the configuration data
         *      structures.
index f66414c903bde09edcaf3a5d297690fd51aefd12..60d51a8c9e1a5b25b267b2f21080c1948b3b6176 100644 (file)
@@ -33,7 +33,7 @@ RCSID("$Id$")
 #define REQUEST_DATA_REGEX (0xadbeef00)
 
 typedef struct regcapture {
-#ifdef HAVE_PCRE
+#if defined(HAVE_PCRE) || defined(HAVE_PCRE2)
        regex_t         *preg;          //!< Compiled pattern.
 #endif
        char const      *value;         //!< Original string.
@@ -83,8 +83,12 @@ void regex_sub_to_request(REQUEST *request, regex_t **preg, char const *value, s
         */
        MEM(new_sc = talloc(request, regcapture_t));
 
+#ifdef HAVE_PCRE2
+       new_sc->rxmatch = talloc_steal(new_sc, rxmatch);
+#else
        MEM(new_sc->rxmatch = talloc_memdup(new_sc, rxmatch, sizeof(rxmatch[0]) * nmatch));
        talloc_set_type(new_sc->rxmatch, regmatch_t[]);
+#endif
 
        MEM(p = talloc_array(new_sc, char, len + 1));
        memcpy(p, value, len);
@@ -92,7 +96,7 @@ void regex_sub_to_request(REQUEST *request, regex_t **preg, char const *value, s
        new_sc->value = p;
        new_sc->nmatch = nmatch;
 
-#ifdef HAVE_PCRE
+#if defined(HAVE_PCRE) || defined(HAVE_PCRE2)
        if (!(*preg)->precompiled) {
                new_sc->preg = talloc_steal(new_sc, *preg);
                *preg = NULL;
@@ -104,7 +108,121 @@ void regex_sub_to_request(REQUEST *request, regex_t **preg, char const *value, s
        request_data_add(request, request, REQUEST_DATA_REGEX, new_sc, true);
 }
 
-#  ifdef HAVE_PCRE
+#  ifdef HAVE_PCRE2
+/** Extract a subcapture value from the request
+ *
+ * @note This is the PCRE2 variant of the function.
+ *
+ * @param ctx To allocate subcapture buffer in.
+ * @param out Where to write the subcapture string.
+ * @param request to extract.
+ * @param num Subcapture index (0 for entire match).
+ * @return 0 on success, -1 on notfound.
+ */
+int regex_request_to_sub(TALLOC_CTX *ctx, char **out, REQUEST *request, uint32_t num)
+{
+       regcapture_t            *cap;
+       char                    *buff;
+       int                     ret;
+       size_t                  len;
+       pcre2_match_data        *match_data;
+
+       cap = request_data_reference(request, request, REQUEST_DATA_REGEX);
+       if (!cap) {
+               RDEBUG4("No subcapture data found");
+               *out = NULL;
+               return -1;
+       }
+       match_data = talloc_get_type_abort(cap->rxmatch->match_data, pcre2_match_data);
+
+       ret = pcre2_substring_length_bynumber(match_data, num, &len);
+       switch (ret) {
+       case PCRE2_ERROR_NOMEMORY:
+               MEM(NULL);
+               /* FALL-THROUGH */
+
+       /*
+        *      Not finding a substring is fine
+        */
+       case PCRE2_ERROR_NOSUBSTRING:
+               RDEBUG4("%i/%zu Not found", num, cap->nmatch);
+               *out = NULL;
+               return -1;
+
+       default:
+               if (ret < 0) {
+                       *out = NULL;
+                       return -1;
+               }
+
+               MEM(buff = talloc_array(ctx, char, ++len));     /* +1 for \0, it'll get reset by pcre2_substring */
+               pcre2_substring_copy_bynumber(match_data, num, (PCRE2_UCHAR *)buff, &len); /* can't error */
+
+               memcpy(out, &buff, sizeof(*out));
+
+               RDEBUG4("%i/%zu Found: %s (%zu)", num, cap->nmatch, buff, talloc_array_length(buff));
+
+               return 0;
+       }
+}
+
+/** Extract a named subcapture value from the request
+ *
+ * @note This is the PCRE2 variant of the function.
+ *
+ * @param ctx To allocate subcapture buffer in.
+ * @param out Where to write the subcapture string.
+ * @param request to extract.
+ * @param name of subcapture.
+ * @return 0 on success, -1 on notfound.
+ */
+int regex_request_to_sub_named(TALLOC_CTX *ctx, char **out, REQUEST *request, char const *name)
+{
+       regcapture_t            *cap;
+       char                    *buff;
+       int                     ret;
+       size_t                  len;
+       pcre2_match_data        *match_data;
+
+       cap = request_data_reference(request, request, REQUEST_DATA_REGEX);
+       if (!cap) {
+               RDEBUG4("No subcapture data found");
+               *out = NULL;
+               return -1;
+       }
+       match_data = talloc_get_type_abort(cap->rxmatch->match_data, pcre2_match_data);
+
+       ret = pcre2_substring_length_byname(match_data, (PCRE2_UCHAR const *)name, &len);
+       switch (ret) {
+       case PCRE2_ERROR_NOMEMORY:
+               MEM(NULL);
+               /* FALL-THROUGH */
+
+       /*
+        *      Not finding a substring is fine
+        */
+       case PCRE2_ERROR_NOSUBSTRING:
+               RDEBUG4("No named capture group \"%s\"", name);
+               *out = NULL;
+               return -1;
+
+       default:
+               if (ret < 0) {
+                       *out = NULL;
+                       return -1;
+               }
+
+               MEM(buff = talloc_array(ctx, char, ++len));     /* +1 for \0, it'll get reset by pcre2_substring */
+               pcre2_substring_copy_byname(match_data, (PCRE2_UCHAR const *)name, (PCRE2_UCHAR *)buff, &len); /* can't error */
+
+               memcpy(out, &buff, sizeof(*out));
+
+               RDEBUG4("Found \"%s\": %s (%zu)", name, buff, talloc_array_length(buff));
+
+               return 0;
+       }
+}
+#  elif defined(HAVE_PCRE)
 /** Extract a subcapture value from the request
  *
  * @note This is the PCRE variant of the function.