| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include "dlfcn-util.h" |
| #include "log.h" |
| #include "pcre2-util.h" |
| |
| #if HAVE_PCRE2 |
| static void *pcre2_dl = NULL; |
| |
| pcre2_match_data* (*sym_pcre2_match_data_create)(uint32_t, pcre2_general_context *); |
| void (*sym_pcre2_match_data_free)(pcre2_match_data *); |
| void (*sym_pcre2_code_free)(pcre2_code *); |
| pcre2_code* (*sym_pcre2_compile)(PCRE2_SPTR, PCRE2_SIZE, uint32_t, int *, PCRE2_SIZE *, pcre2_compile_context *); |
| int (*sym_pcre2_get_error_message)(int, PCRE2_UCHAR *, PCRE2_SIZE); |
| int (*sym_pcre2_match)(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, pcre2_match_data *, pcre2_match_context *); |
| PCRE2_SIZE* (*sym_pcre2_get_ovector_pointer)(pcre2_match_data *); |
| |
| DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( |
| pcre2_code_hash_ops_free, |
| pcre2_code, |
| (void (*)(const pcre2_code *, struct siphash*))trivial_hash_func, |
| (int (*)(const pcre2_code *, const pcre2_code*))trivial_compare_func, |
| sym_pcre2_code_free); |
| #else |
| const struct hash_ops pcre2_code_hash_ops_free = {}; |
| #endif |
| |
| int dlopen_pcre2(void) { |
| #if HAVE_PCRE2 |
| /* So here's something weird: PCRE2 actually renames the symbols exported by the library via C |
| * macros, so that the exported symbols carry a suffix "_8" but when used from C the suffix is |
| * gone. In the argument list below we ignore this mangling. Surprisingly (at least to me), we |
| * actually get away with that. That's because DLSYM_ARG() useses STRINGIFY() to generate a string |
| * version of the symbol name, and that resolves the macro mapping implicitly already, so that the |
| * string actually contains the "_8" suffix already due to that and we don't have to append it |
| * manually anymore. C is weird. 🤯 */ |
| |
| return dlopen_many_sym_or_warn( |
| &pcre2_dl, "libpcre2-8.so.0", LOG_ERR, |
| DLSYM_ARG(pcre2_match_data_create), |
| DLSYM_ARG(pcre2_match_data_free), |
| DLSYM_ARG(pcre2_code_free), |
| DLSYM_ARG(pcre2_compile), |
| DLSYM_ARG(pcre2_get_error_message), |
| DLSYM_ARG(pcre2_match), |
| DLSYM_ARG(pcre2_get_ovector_pointer)); |
| #else |
| return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in."); |
| #endif |
| } |
| |
| int pattern_compile_and_log(const char *pattern, PatternCompileCase case_, pcre2_code **ret) { |
| #if HAVE_PCRE2 |
| PCRE2_SIZE erroroffset; |
| _cleanup_(sym_pcre2_code_freep) pcre2_code *p = NULL; |
| unsigned flags = 0; |
| int errorcode, r; |
| |
| assert(pattern); |
| |
| r = dlopen_pcre2(); |
| if (r < 0) |
| return r; |
| |
| if (case_ == PATTERN_COMPILE_CASE_INSENSITIVE) |
| flags = PCRE2_CASELESS; |
| else if (case_ == PATTERN_COMPILE_CASE_AUTO) { |
| _cleanup_(sym_pcre2_match_data_freep) pcre2_match_data *md = NULL; |
| bool has_case; |
| _cleanup_(sym_pcre2_code_freep) pcre2_code *cs = NULL; |
| |
| md = sym_pcre2_match_data_create(1, NULL); |
| if (!md) |
| return log_oom(); |
| |
| r = pattern_compile_and_log("[[:upper:]]", PATTERN_COMPILE_CASE_SENSITIVE, &cs); |
| if (r < 0) |
| return r; |
| |
| r = sym_pcre2_match(cs, (PCRE2_SPTR8) pattern, PCRE2_ZERO_TERMINATED, 0, 0, md, NULL); |
| has_case = r >= 0; |
| |
| flags = !has_case * PCRE2_CASELESS; |
| } |
| |
| log_debug("Doing case %s matching based on %s", |
| flags & PCRE2_CASELESS ? "insensitive" : "sensitive", |
| case_ != PATTERN_COMPILE_CASE_AUTO ? "request" : "pattern casing"); |
| |
| p = sym_pcre2_compile((PCRE2_SPTR8) pattern, |
| PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL); |
| if (!p) { |
| unsigned char buf[LINE_MAX]; |
| |
| r = sym_pcre2_get_error_message(errorcode, buf, sizeof buf); |
| |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
| "Bad pattern \"%s\": %s", pattern, |
| r < 0 ? "unknown error" : (char *)buf); |
| } |
| |
| if (ret) |
| *ret = TAKE_PTR(p); |
| |
| return 0; |
| #else |
| return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in."); |
| #endif |
| } |
| |
| int pattern_matches_and_log(pcre2_code *compiled_pattern, const char *message, size_t size, size_t *ret_ovec) { |
| #if HAVE_PCRE2 |
| _cleanup_(sym_pcre2_match_data_freep) pcre2_match_data *md = NULL; |
| int r; |
| |
| assert(compiled_pattern); |
| assert(message); |
| /* pattern_compile_and_log() must be called before this function is called and that function already |
| * dlopens pcre2 so we can assert on it being available here. */ |
| assert(pcre2_dl); |
| |
| md = sym_pcre2_match_data_create(1, NULL); |
| if (!md) |
| return log_oom(); |
| |
| r = sym_pcre2_match(compiled_pattern, |
| (const unsigned char *)message, |
| size, |
| 0, /* start at offset 0 in the subject */ |
| 0, /* default options */ |
| md, |
| NULL); |
| if (r == PCRE2_ERROR_NOMATCH) |
| return false; |
| if (r < 0) { |
| unsigned char buf[LINE_MAX]; |
| |
| r = sym_pcre2_get_error_message(r, buf, sizeof(buf)); |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern matching failed: %s", |
| r < 0 ? "unknown error" : (char*) buf); |
| } |
| |
| if (ret_ovec) { |
| ret_ovec[0] = sym_pcre2_get_ovector_pointer(md)[0]; |
| ret_ovec[1] = sym_pcre2_get_ovector_pointer(md)[1]; |
| } |
| |
| return true; |
| #else |
| return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in."); |
| #endif |
| } |
| |
| void *pattern_free(pcre2_code *p) { |
| #if HAVE_PCRE2 |
| if (!p) |
| return NULL; |
| |
| assert(pcre2_dl); |
| sym_pcre2_code_free(p); |
| return NULL; |
| #else |
| assert(p == NULL); |
| return NULL; |
| #endif |
| } |