/* 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; DLSYM_PROTOTYPE(pcre2_match_data_create) = NULL; DLSYM_PROTOTYPE(pcre2_match_data_free) = NULL; DLSYM_PROTOTYPE(pcre2_code_free) = NULL; DLSYM_PROTOTYPE(pcre2_compile) = NULL; DLSYM_PROTOTYPE(pcre2_get_error_message) = NULL; DLSYM_PROTOTYPE(pcre2_match) = NULL; DLSYM_PROTOTYPE(pcre2_get_ovector_pointer) = NULL; 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 ELF_NOTE_DLOPEN("pcre2", "Support for regular expressions", ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libpcre2-8.so.0"); /* 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 }