From: Nick Porter Date: Mon, 28 Jul 2025 09:53:43 +0000 (+0100) Subject: Add support for pcre2 in place of pcre X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=430ed115ca58b0b99f6ea372d4e32b573aa5d0fd;p=thirdparty%2Ffreeradius-server.git Add support for pcre2 in place of pcre --- diff --git a/configure b/configure index 4264cd4432..2b82073e9c 100755 --- 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 +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 +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 +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 +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= diff --git a/configure.ac b/configure.ac index 033b248e36..73e40b84d5 100644 --- a/configure.ac +++ b/configure.ac @@ -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= diff --git a/src/include/autoconf.h.in b/src/include/autoconf.h.in index 4774482fb1..726bfa9915 100644 --- a/src/include/autoconf.h.in +++ b/src/include/autoconf.h.in @@ -330,6 +330,9 @@ /* 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 header file. */ #undef HAVE_PROT_H diff --git a/src/include/radiusd.h b/src/include/radiusd.h index e442b99c19..67ef17e17d 100644 --- a/src/include/radiusd.h +++ b/src/include/radiusd.h @@ -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 diff --git a/src/include/regex.h b/src/include/regex.h index efb7b8615c..c56a41c96a 100644 --- a/src/include/regex.h +++ b/src/include/regex.h @@ -29,8 +29,31 @@ RCSIDH(regex_h, "$Id$") # ifdef __cplusplus extern "C" { # endif -# ifdef HAVE_PCRE -# include +# ifdef HAVE_PCRE2 +# define PCRE2_CODE_UNIT_WIDTH 8 +# include +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 /* * Versions older then 8.20 didn't have the JIT functionality * gracefully degrade. diff --git a/src/lib/regex.c b/src/lib/regex.c index 64a6dbe31e..67fd16b2b6 100644 --- a/src/lib/regex.c +++ b/src/lib/regex.c @@ -26,11 +26,320 @@ #include #include +/* + * 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. diff --git a/src/main/evaluate.c b/src/main/evaluate.c index c8585b67e0..8066c56454 100644 --- a/src/main/evaluate.c +++ b/src/main/evaluate.c @@ -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: diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c index 4120392a91..3e073b0841 100644 --- a/src/main/mainconfig.c +++ b/src/main/mainconfig.c @@ -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 +#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. diff --git a/src/main/regex.c b/src/main/regex.c index f66414c903..60d51a8c9e 100644 --- a/src/main/regex.c +++ b/src/main/regex.c @@ -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.