From: Tobias Brunner Date: Tue, 15 Apr 2025 13:09:20 +0000 (+0200) Subject: identification: Add support for POSIX regular expressions X-Git-Tag: 6.0.2dr1~7^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7de05b918c37049349201f9801adc35467e757f3;p=thirdparty%2Fstrongswan.git identification: Add support for POSIX regular expressions When cross-compiling for Windows on Ubuntu, we don't have POSIX regular expressions available (there does not seem to be any alternative libraries either), but since the tests are not executed that's OK. On AppVeyor, MSYS2 has libgnurx installed, which works fine but requires explicit linking with `-lregex`. This is loosely based on a patch by Thomas Egerer. --- diff --git a/configure.ac b/configure.ac index db8c55a071..220c33ff55 100644 --- a/configure.ac +++ b/configure.ac @@ -604,6 +604,10 @@ AC_LINK_IFELSE( AC_SUBST(ATOMICLIB) LIBS=$saved_LIBS + +# Some platforms require explicit linking to use POSIX regular expressions +AC_SEARCH_LIBS([regcomp], [regex], [AC_DEFINE([HAVE_REGEX], [], [have regcomp() etc.])]) + # ------------------------------------------------------ AC_MSG_CHECKING(for dladdr) diff --git a/src/libstrongswan/tests/suites/test_identification.c b/src/libstrongswan/tests/suites/test_identification.c index db40437692..3ece5e1a72 100644 --- a/src/libstrongswan/tests/suites/test_identification.c +++ b/src/libstrongswan/tests/suites/test_identification.c @@ -311,6 +311,45 @@ START_TEST(test_from_string) } END_TEST +START_TEST(test_from_string_with_regex) +{ + identification_t *id; + + id = identification_create_from_string_with_regex("fqdn:^vpn.*\\.strongswan\\.org$"); + ck_assert(id); + ck_assert_int_eq(ID_FQDN, id->get_type(id)); + ck_assert_chunk_eq(chunk_from_str("^vpn.*\\.strongswan\\.org$"), id->get_encoding(id)); + id->destroy(id); + + id = identification_create_from_string_with_regex("email:^.+@strongswan\\.org$"); + ck_assert(id); + ck_assert_int_eq(ID_RFC822_ADDR, id->get_type(id)); + ck_assert_chunk_eq(chunk_from_str("^.+@strongswan\\.org$"), id->get_encoding(id)); + id->destroy(id); + + id = identification_create_from_string_with_regex("asn1dn:^.+CN=.+@strongswan\\.org$"); + ck_assert(id); + ck_assert_int_eq(ID_DER_ASN1_DN, id->get_type(id)); + ck_assert_chunk_eq(chunk_from_str("^.+CN=.+@strongswan\\.org$"), id->get_encoding(id)); + id->destroy(id); + + /* invalid regex */ + ck_assert(!identification_create_from_string_with_regex("fqdn:^[+\\.strongswan\\.org$")); + + /* ignored if pre-/suffix is missing or the type is unsupported */ + id = identification_create_from_string_with_regex("fqdn:[+\\.strongswan\\.org$"); + ck_assert(id); + id->destroy(id); + id = identification_create_from_string_with_regex("fqdn:^[+\\.strongswan\\.org"); + ck_assert(id); + id->destroy(id); + id = identification_create_from_string_with_regex("keyid:^[+\\.strongswan\\.org$"); + ck_assert(id); + ck_assert_int_eq(ID_KEY_ID, id->get_type(id)); + id->destroy(id); +} +END_TEST + /******************************************************************************* * printf_hook */ @@ -458,6 +497,22 @@ START_TEST(test_printf_hook_width) } END_TEST +START_TEST(test_printf_hook_regex) +{ + identification_t *a; + char buf[128]; + + a = identification_create_from_string_with_regex("fqdn:^vpn.+\\.strongswan\\.org$"); + snprintf(buf, sizeof(buf), "%Y", a); + ck_assert_str_eq("^vpn.+\\.strongswan\\.org$", buf); + snprintf(buf, sizeof(buf), "%30Y", a); + ck_assert_str_eq(" ^vpn.+\\.strongswan\\.org$", buf); + snprintf(buf, sizeof(buf), "%-*Y", 30, a); + ck_assert_str_eq("^vpn.+\\.strongswan\\.org$ ", buf); + a->destroy(a); +} +END_TEST + /******************************************************************************* * equals */ @@ -587,6 +642,30 @@ START_TEST(test_equals_fqdn) } END_TEST +START_TEST(test_equals_regex) +{ + identification_t *a, *b; + + a = identification_create_from_string_with_regex("fqdn:^vpn.*\\.strongswan\\.org$"); + b = identification_create_from_string_with_regex("fqdn:^vpn.*\\.strongswan\\.org$"); + ck_assert(a->equals(a, b)); + ck_assert(b->equals(b, a)); + b->destroy(b); + b = identification_create_from_string_with_regex("fqdn:^vpn.+\\.strongswan\\.org$"); + ck_assert(!a->equals(a, b)); + ck_assert(!b->equals(b, a)); + b->destroy(b); + + b = identification_create_from_string("fqdn:^vpn.*\\.strongswan\\.org$"); + ck_assert_int_eq(a->get_type(a), b->get_type(b)); + ck_assert_chunk_eq(a->get_encoding(a), b->get_encoding(b)); + ck_assert(!a->equals(a, b)); + ck_assert(!b->equals(b, a)); + b->destroy(b); + a->destroy(a); +} +END_TEST + START_TEST(test_equals_empty) { identification_t *a; @@ -1140,6 +1219,78 @@ START_TEST(test_matches_string) } END_TEST +static bool regex_matches(identification_t *a, char *b_regex, id_match_t expected) +{ + identification_t *b; + id_match_t match; + + b = identification_create_from_string_with_regex(b_regex); + ck_assert_msg(b, "'%s' is invalid", b_regex); + match = a->matches(a, b); + b->destroy(b); + return match == expected; +} + +START_TEST(test_matches_regex) +{ + identification_t *a; + + a = identification_create_from_string("moon@strongswan.org"); + + ck_assert(regex_matches(a, "email:^moon@strongswan.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "email:^moon@strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "email:^MOON@strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "email:^(moon|sun)@strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "email:^moon@strongswan\\.or$", ID_MATCH_NONE)); + ck_assert(regex_matches(a, "email:^.+@strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "email:^moon@.*.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "email:^.+$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "email:^$", ID_MATCH_NONE)); + a->destroy(a); + + a = identification_create_from_string("vpn1.strongswan.org"); + + ck_assert(regex_matches(a, "fqdn:^vpn1\\.strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "fqdn:^VPN1\\.strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "fqdn:^strongswan\\.org$", ID_MATCH_NONE)); + ck_assert(regex_matches(a, "fqdn:^.*\\.strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "fqdn:^vpn[0-9]\\.strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "fqdn:^vpn[[:digit:]]\\.strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "fqdn:^[^0-9]+[0-9]+\\.strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + a->destroy(a); + + a = identification_create_from_string("C=CH, O=strongSwan, CN=vpn1.strongswan.org"); + + ck_assert(regex_matches(a, "asn1dn:^.*CN=.*\\.strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + ck_assert(regex_matches(a, "asn1dn:^.*CN=vpn[0-9]\\.strongswan\\.org$", ID_MATCH_MAX_WILDCARDS)); + a->destroy(a); + + a = identification_create_from_string("fqdn:^vpn1\\.strongswan\\.org$"); + + ck_assert(regex_matches(a, "fqdn:^vpn1\\.strongswan\\.org$", ID_MATCH_NONE)); + ck_assert(regex_matches(a, "vpn1.strongswan.org", ID_MATCH_NONE)); + ck_assert(regex_matches(a, "%any", ID_MATCH_ANY)); + a->destroy(a); + + a = identification_create_from_string_with_regex("fqdn:^vpn1\\.strongswan\\.org$"); + + ck_assert(regex_matches(a, "fqdn:^vpn1\\.strongswan\\.org$", ID_MATCH_NONE)); + ck_assert(regex_matches(a, "vpn1.strongswan.org", ID_MATCH_NONE)); + ck_assert(regex_matches(a, "%any", ID_MATCH_ANY)); + a->destroy(a); + + /* internal buffer for matched identities is BUF_LEN-1, do a suffix match + * as it would fail if the buffer was too small anyway otherwise */ + a = identification_create_from_string("moon@strongswan.org "); + ck_assert(regex_matches(a, "email:^moon@strongswan.org.*$", ID_MATCH_MAX_WILDCARDS)); + a->destroy(a); + + a = identification_create_from_string("moon@strongswan.org "); + ck_assert(regex_matches(a, "email:^moon@strongswan.org.*$", ID_MATCH_NONE)); + a->destroy(a); +} +END_TEST + START_TEST(test_matches_empty) { identification_t *a; @@ -1305,6 +1456,23 @@ START_TEST(test_hash_dn) } END_TEST +START_TEST(test_hash_regex) +{ + identification_t *a, *b; + + a = identification_create_from_string_with_regex("fqdn:^vpn1\\.strongswan\\.org$"); + b = identification_create_from_string_with_regex("fqdn:^vpn1\\.strongswan\\.org$"); + ck_assert_int_eq(a->hash(a, 0), b->hash(b, 0)); + b->destroy(b); + b = identification_create_from_string("fqdn:^vpn1\\.strongswan\\.org$"); + ck_assert_int_eq(a->get_type(a), b->get_type(b)); + ck_assert_chunk_eq(a->get_encoding(a), b->get_encoding(b)); + ck_assert(a->hash(a, 0) != b->hash(b, 0)); + b->destroy(b); + a->destroy(a); +} +END_TEST + START_TEST(test_hash_inc) { identification_t *a; @@ -1364,25 +1532,38 @@ END_TEST * wildcards */ -static bool id_contains_wildcards(char *string) +static bool check_id_contains_wildcards(identification_t *id) { - identification_t *id; bool contains; - id = identification_create_from_string(string); contains = id->contains_wildcards(id); id->destroy(id); return contains; } +static bool id_contains_wildcards(char *str) +{ + return check_id_contains_wildcards(identification_create_from_string(str)); +} + START_TEST(test_contains_wildcards) { + identification_t *id; + ck_assert(id_contains_wildcards("%any")); ck_assert(id_contains_wildcards("C=*, O=strongSwan, CN=gw")); ck_assert(id_contains_wildcards("C=CH, O=strongSwan, CN=*")); ck_assert(id_contains_wildcards("*@strongswan.org")); ck_assert(id_contains_wildcards("*.strongswan.org")); ck_assert(!id_contains_wildcards("C=**, O=a*, CN=*a")); + /* not actual regexes as the wrong constructor is used */ + ck_assert(!id_contains_wildcards("fqdn:^vpn1\\.strongswan\\.org$")); + ck_assert(id_contains_wildcards("fqdn:^vpn.*\\.strongswan\\.org$")); + /* no regex due to missing type prefix */ + id = identification_create_from_string_with_regex("^vpn1\\.strongswan\\.org$"); + ck_assert(!check_id_contains_wildcards(id)); + id = identification_create_from_string_with_regex("fqdn:^vpn1\\.strongswan\\.org$"); + ck_assert(check_id_contains_wildcards(id)); } END_TEST @@ -1392,7 +1573,7 @@ END_TEST START_TEST(test_clone) { - identification_t *a, *b; + identification_t *a, *b, *c; chunk_t a_enc, b_enc; a = identification_create_from_string("moon@strongswan.org"); @@ -1400,11 +1581,30 @@ START_TEST(test_clone) b = a->clone(a); ck_assert(b != NULL); ck_assert(a != b); + ck_assert_int_eq(a->get_type(a), b->get_type(b)); + b_enc = b->get_encoding(b); + ck_assert(a_enc.ptr != b_enc.ptr); + ck_assert(chunk_equals(a_enc, b_enc)); + a->destroy(a); + b->destroy(b); + + a = identification_create_from_string_with_regex("fqdn:^vpn.+\\.strongswan\\.org$"); + a_enc = a->get_encoding(a); + b = a->clone(a); + ck_assert(b != NULL); + ck_assert(a != b); + ck_assert_int_eq(a->get_type(a), b->get_type(b)); b_enc = b->get_encoding(b); ck_assert(a_enc.ptr != b_enc.ptr); ck_assert(chunk_equals(a_enc, b_enc)); + ck_assert_int_eq(0, a_enc.ptr[a_enc.len]); + ck_assert_int_eq(0, b_enc.ptr[b_enc.len]); + c = identification_create_from_string("vpn1.strongswan.org"); + ck_assert_int_eq(ID_MATCH_MAX_WILDCARDS, c->matches(c, a)); + ck_assert_int_eq(ID_MATCH_MAX_WILDCARDS, c->matches(c, b)); a->destroy(a); b->destroy(b); + c->destroy(c); } END_TEST @@ -1420,11 +1620,13 @@ Suite *identification_suite_create() tcase_add_test(tc, test_from_data); tcase_add_test(tc, test_from_sockaddr); tcase_add_loop_test(tc, test_from_string, 0, countof(string_data)); + tcase_add_test(tc, test_from_string_with_regex); suite_add_tcase(s, tc); tc = tcase_create("printf_hook"); tcase_add_test(tc, test_printf_hook); tcase_add_test(tc, test_printf_hook_width); + tcase_add_test(tc, test_printf_hook_regex); suite_add_tcase(s, tc); tc = tcase_create("equals"); @@ -1432,6 +1634,7 @@ Suite *identification_suite_create() tcase_add_test(tc, test_equals_any); tcase_add_test(tc, test_equals_binary); tcase_add_test(tc, test_equals_fqdn); + tcase_add_test(tc, test_equals_regex); tcase_add_loop_test(tc, test_equals_empty, ID_ANY, ID_KEY_ID + 1); suite_add_tcase(s, tc); @@ -1444,6 +1647,7 @@ Suite *identification_suite_create() tcase_add_test(tc, test_matches_range_subnet); tcase_add_test(tc, test_matches_range_range); tcase_add_test(tc, test_matches_string); + tcase_add_test(tc, test_matches_regex); tcase_add_loop_test(tc, test_matches_empty, ID_ANY, ID_KEY_ID + 1); tcase_add_loop_test(tc, test_matches_empty_reverse, ID_ANY, ID_KEY_ID + 1); suite_add_tcase(s, tc); @@ -1452,6 +1656,7 @@ Suite *identification_suite_create() tcase_add_test(tc, test_hash); tcase_add_test(tc, test_hash_any); tcase_add_test(tc, test_hash_dn); + tcase_add_test(tc, test_hash_regex); tcase_add_test(tc, test_hash_inc); suite_add_tcase(s, tc); diff --git a/src/libstrongswan/utils/identification.c b/src/libstrongswan/utils/identification.c index 58a05052dc..08c2b7bb9a 100644 --- a/src/libstrongswan/utils/identification.c +++ b/src/libstrongswan/utils/identification.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2016 Andreas Steffen - * Copyright (C) 2009-2019 Tobias Brunner + * Copyright (C) 2009-2025 Tobias Brunner * Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2005 Jan Hutter * @@ -21,6 +21,24 @@ #include #include +#ifdef HAVE_REGEX +#include +#else +/* usually, POSIX regular expressions are supported. for the rare case they are + * not, e.g. when cross-compiling for Windows, we define some stubs to simplify + * the implementation below */ +typedef void regex_t; +static int regexec(const regex_t *restrict preg, const char *restrict string, + size_t nmatch, void *pmatch, int eflags) +{ + return -1; +} + +static void regfree(regex_t *preg) +{ +} +#endif /* HAVE_REGEX */ + #include "identification.h" #include @@ -104,7 +122,6 @@ static const x501rdn_t x501rdns[] = { */ #define RDN_MAX 20 - typedef struct private_identification_t private_identification_t; /** @@ -125,8 +142,23 @@ struct private_identification_t { * Type of this ID. */ id_type_t type; + + /** + * Pointer if we use regex comparison. + */ + regex_t *regex; }; +/** + * Check that neither object uses a regex. + */ +static inline bool neither_is_regex(private_identification_t *this, + identification_t *other) +{ + private_identification_t *other_priv = (private_identification_t*)other; + return !this->regex && !other_priv->regex; +} + /** * Enumerator over RDNs */ @@ -174,7 +206,7 @@ METHOD(enumerator_t, rdn_enumerate, bool, /** * Create an enumerator over all RDNs (oid, string type, data) of a DN */ -static enumerator_t* create_rdn_enumerator(chunk_t dn) +static enumerator_t *create_rdn_enumerator(chunk_t dn) { rdn_enumerator_t *e; @@ -613,19 +645,26 @@ METHOD(identification_t, hash_binary, u_int, { hash = chunk_hash_inc(this->encoded, hash); } + if (this->regex) + { /* ensure regexes have different hashes even if type/encoding is equal */ + hash = chunk_hash_inc(chunk_from_chars(0x01), hash); + } return hash; } METHOD(identification_t, equals_binary, bool, private_identification_t *this, identification_t *other) { - if (this->type == other->get_type(other)) + private_identification_t *other_priv = (private_identification_t*)other; + + if (this->type == other_priv->type && + (neither_is_regex(this, other) || (this->regex && other_priv->regex))) { if (this->type == ID_ANY) { return TRUE; } - return chunk_equals(this->encoded, other->get_encoding(other)); + return chunk_equals(this->encoded, other_priv->encoded); } return FALSE; } @@ -895,7 +934,8 @@ static bool is_valid_dn(chunk_t dn) METHOD(identification_t, equals_dn, bool, private_identification_t *this, identification_t *other) { - return compare_dn(this->encoded, other->get_encoding(other), NULL); + return neither_is_regex(this, other) && + compare_dn(this->encoded, other->get_encoding(other), NULL); } METHOD(identification_t, hash_dn, u_int, @@ -924,6 +964,7 @@ METHOD(identification_t, equals_strcasecmp, bool, /* we do some extra sanity checks to check for invalid IDs with a * terminating null in it. */ if (this->type == other->get_type(other) && + neither_is_regex(this, other) && this->encoded.len == encoded.len && memchr(this->encoded.ptr, 0, this->encoded.len) == NULL && memchr(encoded.ptr, 0, encoded.len) == NULL && @@ -949,9 +990,38 @@ METHOD(identification_t, matches_binary, id_match_t, return ID_MATCH_NONE; } +/** + * Matches the regex in other against our own encoding. + */ +static id_match_t matches_regex(private_identification_t *this, + private_identification_t *other) +{ + char buf[BUF_LEN-1]; + int rc; + + if (this->regex) + { /* don't match two regex values */ + return ID_MATCH_NONE; + } + if (!this->encoded.len) + { + return ID_MATCH_NONE; + } + /* match against the string representation of the identity */ + if (snprintf(buf, sizeof(buf), "%Y", this) >= sizeof(buf)) + { + /* fail if the buffer is too small. note that we use BUF_LEN-1 because + * the printf hook uses BUF_LEN to print the identity internally */ + return ID_MATCH_NONE; + } + rc = regexec(other->regex, buf, 0, NULL, 0); + return rc == 0 ? ID_MATCH_MAX_WILDCARDS : ID_MATCH_NONE; +} + METHOD(identification_t, matches_string, id_match_t, private_identification_t *this, identification_t *other) { + private_identification_t *other_priv = (private_identification_t*)other; chunk_t encoded = other->get_encoding(other); u_int len = encoded.len; @@ -963,6 +1033,10 @@ METHOD(identification_t, matches_string, id_match_t, { return ID_MATCH_NONE; } + if (other_priv->regex) + { + return matches_regex(this, other_priv); + } /* try a equals check first */ if (equals_strcasecmp(this, other)) { @@ -1007,6 +1081,7 @@ static id_match_t matches_dn_internal(private_identification_t *this, identification_t *other, bool (*match)(chunk_t,chunk_t,int*)) { + private_identification_t *other_priv = (private_identification_t*)other; int wc; if (other->get_type(other) == ID_ANY) @@ -1016,7 +1091,11 @@ static id_match_t matches_dn_internal(private_identification_t *this, if (this->type == other->get_type(other)) { - if (match(this->encoded, other->get_encoding(other), &wc)) + if (other_priv->regex) + { + return matches_regex(this, other_priv); + } + else if (match(this->encoded, other->get_encoding(other), &wc)) { wc = min(wc, ID_MATCH_ONE_WILDCARD - ID_MATCH_MAX_WILDCARDS); return ID_MATCH_PERFECT - wc; @@ -1442,21 +1521,20 @@ METHOD(identification_t, matches_range, id_match_t, } /** - * Described in header. + * Convert the given identity to a string depending on its type. */ -int identification_printf_hook(printf_hook_data_t *data, - printf_hook_spec_t *spec, const void *const *args) +static void identity_to_string(private_identification_t *this, char buf[BUF_LEN]) { - private_identification_t *this = *((private_identification_t**)(args[0])); chunk_t proper; - char buf[BUF_LEN], *pos; + char *pos; uint8_t address_size; size_t len; int written; - if (this == NULL) + if (this->regex) { - return print_in_hook(data, "%*s", spec->width, "(null)"); + snprintf(buf, BUF_LEN, "%s", this->encoded.ptr); + return; } switch (this->type) @@ -1573,6 +1651,24 @@ int identification_printf_hook(printf_hook_data_t *data, snprintf(buf, BUF_LEN, "(unknown ID type: %d)", this->type); break; } +} + +/** + * Described in header. + */ +int identification_printf_hook(printf_hook_data_t *data, + printf_hook_spec_t *spec, const void *const *args) +{ + private_identification_t *this = *((private_identification_t**)(args[0])); + char buf[BUF_LEN]; + + if (!this) + { + return print_in_hook(data, "%*s", spec->width, "(null)"); + } + + identity_to_string(this, buf); + if (spec->minus) { return print_in_hook(data, "%-*s", spec->width, buf); @@ -1580,13 +1676,52 @@ int identification_printf_hook(printf_hook_data_t *data, return print_in_hook(data, "%*s", spec->width, buf); } +#ifdef HAVE_REGEX + +/** + * Compile the encoded regular expression. + */ +static bool compile_regex(private_identification_t *this) +{ + char buf[BUF_LEN]; + int err = 0; + + this->regex = malloc(sizeof(*this->regex)); + err = regcomp(this->regex, this->encoded.ptr, + REG_EXTENDED | REG_ICASE | REG_NOSUB); + if (err != 0) + { + regerror(err, NULL, buf, sizeof(buf)); + DBG1(DBG_LIB, "invalid regular expression '%s': %s", + this->encoded.ptr, buf); + return FALSE; + } + return TRUE; +} + +#else /* HAVE_REGEX */ + +static bool compile_regex(private_identification_t *this) +{ + DBG1(DBG_LIB, "regular expressions are not supported"); + return FALSE; +} + +#endif /* HAVE_REGEX */ + METHOD(identification_t, clone_, identification_t*, private_identification_t *this) { private_identification_t *clone = malloc_thing(private_identification_t); memcpy(clone, this, sizeof(private_identification_t)); - if (this->encoded.len) + if (this->regex) + { + /* make sure we have the full encoding cloned */ + clone->encoded = chunk_from_str(strdup(this->encoded.ptr)); + compile_regex(clone); + } + else if (this->encoded.len) { clone->encoded = chunk_clone(this->encoded); } @@ -1597,6 +1732,11 @@ METHOD(identification_t, destroy, void, private_identification_t *this) { chunk_free(&this->encoded); + if (this->regex) + { + regfree(this->regex); + free(this->regex); + } free(this); } @@ -1672,32 +1812,37 @@ static private_identification_t *identification_create(id_type_t type) return this; } +/** + * Prefixes used when parsing identities. + */ +static const struct { + const char *str; + id_type_t type; + bool regex; +} prefixes[] = { + { "ipv4:", ID_IPV4_ADDR, FALSE}, + { "ipv6:", ID_IPV6_ADDR, FALSE}, + { "ipv4net:", ID_IPV4_ADDR_SUBNET, FALSE}, + { "ipv6net:", ID_IPV6_ADDR_SUBNET, FALSE}, + { "ipv4range:", ID_IPV4_ADDR_RANGE, FALSE}, + { "ipv6range:", ID_IPV6_ADDR_RANGE, FALSE}, + { "rfc822:", ID_RFC822_ADDR, TRUE}, + { "email:", ID_RFC822_ADDR, TRUE}, + { "userfqdn:", ID_USER_FQDN, FALSE}, + { "fqdn:", ID_FQDN, TRUE}, + { "dns:", ID_FQDN, TRUE}, + { "asn1dn:", ID_DER_ASN1_DN, TRUE}, + { "asn1gn:", ID_DER_ASN1_GN, FALSE}, + { "xmppaddr:", ID_DER_ASN1_GN, FALSE}, + { "keyid:", ID_KEY_ID, FALSE}, + { "uri:", ID_DER_ASN1_GN_URI, FALSE}, +}; + /** * Create an identity for a specific type, determined by prefix */ -static private_identification_t* create_from_string_with_prefix_type(char *str) -{ - struct { - const char *str; - id_type_t type; - } prefixes[] = { - { "ipv4:", ID_IPV4_ADDR }, - { "ipv6:", ID_IPV6_ADDR }, - { "ipv4net:", ID_IPV4_ADDR_SUBNET }, - { "ipv6net:", ID_IPV6_ADDR_SUBNET }, - { "ipv4range:", ID_IPV4_ADDR_RANGE }, - { "ipv6range:", ID_IPV6_ADDR_RANGE }, - { "rfc822:", ID_RFC822_ADDR }, - { "email:", ID_RFC822_ADDR }, - { "userfqdn:", ID_USER_FQDN }, - { "fqdn:", ID_FQDN }, - { "dns:", ID_FQDN }, - { "asn1dn:", ID_DER_ASN1_DN }, - { "asn1gn:", ID_DER_ASN1_GN }, - { "xmppaddr:", ID_DER_ASN1_GN }, - { "keyid:", ID_KEY_ID }, - { "uri:", ID_DER_ASN1_GN_URI }, - }; +static private_identification_t *create_from_string_with_prefix_type(char *str) +{ private_identification_t *this; int i; @@ -1726,7 +1871,6 @@ static private_identification_t* create_from_string_with_prefix_type(char *str) asn1_wrap(ASN1_UTF8STRING, "m", this->encoded))); } - return this; } } @@ -1739,7 +1883,7 @@ static private_identification_t* create_from_string_with_prefix_type(char *str) * The prefix is of the form "{x}:", where x denotes the numerical identity * type. */ -static private_identification_t* create_from_string_with_num_type(char *str) +static private_identification_t *create_from_string_with_num_type(char *str) { private_identification_t *this; u_long type; @@ -1769,7 +1913,7 @@ static private_identification_t* create_from_string_with_num_type(char *str) /** * Convert to an IPv4/IPv6 host address, subnet or address range */ -static private_identification_t* create_ip_address_from_string(char *string, +static private_identification_t *create_ip_address_from_string(char *string, bool is_ipv4) { private_identification_t *this; @@ -1910,7 +2054,7 @@ identification_t *identification_create_from_string(char *string) else { this = identification_create(ID_KEY_ID); - this->encoded = chunk_from_str(strdup(string)); + this->encoded = chunk_clone(chunk_from_str(string)); } return &this->public; } @@ -1937,7 +2081,7 @@ identification_t *identification_create_from_string(char *string) if (!this) { /* not IPv4, mostly FQDN */ this = identification_create(ID_FQDN); - this->encoded = chunk_from_str(strdup(string)); + this->encoded = chunk_clone(chunk_from_str(string)); } return &this->public; } @@ -1948,7 +2092,7 @@ identification_t *identification_create_from_string(char *string) if (!this) { /* not IPv4/6 fallback to KEY_ID */ this = identification_create(ID_KEY_ID); - this->encoded = chunk_from_str(strdup(string)); + this->encoded = chunk_clone(chunk_from_str(string)); } return &this->public; } @@ -1981,16 +2125,75 @@ identification_t *identification_create_from_string(char *string) else { this = identification_create(ID_RFC822_ADDR); - this->encoded = chunk_from_str(strdup(string)); + this->encoded = chunk_clone(chunk_from_str(string)); return &this->public; } } } +/** + * Check if the given string should be parsed as regular expression identity. + * If so, it modifies the string and returns the identity type, otherwise, + * ID_ANY is returned. + */ +static id_type_t is_regex_identity(char **string) +{ + char *regex; + int i; + + for (i = 0; i < countof(prefixes); i++) + { + if (strcasepfx(*string, prefixes[i].str)) + { + regex = *string + strlen(prefixes[i].str); + + if (prefixes[i].regex && + *regex == '^' && *(regex + strlen(regex) - 1) == '$') + { + *string = regex; + return prefixes[i].type; + } + break; + } + } + return ID_ANY; +} + +/* + * Described in header. + */ +identification_t *identification_create_from_string_with_regex(char *string) +{ + private_identification_t *this; + id_type_t type; + + type = is_regex_identity(&string); + if (type != ID_ANY) + { + this = identification_create(type); + + this->public.hash = _hash_binary; + this->public.equals = _equals_binary; + this->public.matches = _matches_any; + this->public.contains_wildcards = (void*)return_true; + + /* this encoding explicitly includes the null-terminator so we can + * directly use it to compile the regex and printing */ + this->encoded = chunk_from_str(strdup(string)); + if (!compile_regex(this)) + { + destroy(this); + return NULL; + } + return &this->public; + } + return identification_create_from_string(string); +} + /* * Described in header. */ -identification_t * identification_create_from_data(chunk_t data) +identification_t *identification_create_from_data(chunk_t data) { char buf[data.len + 1]; diff --git a/src/libstrongswan/utils/identification.h b/src/libstrongswan/utils/identification.h index 83f2560176..3be5a54db2 100644 --- a/src/libstrongswan/utils/identification.h +++ b/src/libstrongswan/utils/identification.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 Tobias Brunner + * Copyright (C) 2009-2025 Tobias Brunner * Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2005 Jan Hutter * @@ -325,7 +325,30 @@ struct identification_t { * @param string input string, which will be converted * @return identification_t */ -identification_t * identification_create_from_string(char *string); +identification_t *identification_create_from_string(char *string); + +/** + * Creates an identification_t object from a string that may contain a regular + * expression. + * + * This function behaves like identification_create_from_string() but also + * accepts regular expressions. So it must only be used to parse + * trusted/configured values, never untrusted values received over the network. + * + * A regular expression must be prefixed by an identity type (supported are + * rfc822:, email:, fqdn:, dns:, and asn1dn:), and it must start with a caret + * ('^') and end with a dollar sign ('$') to indicate an anchored pattern. + * If the regular expression is invalid, the function returns NULL. + * + * The regular expression is always matched against the string representation + * of other identities and matching is performed case-insensitive. + * + * @param string string to parse, regex must begin with '^' and end with '$' + * and must be prefixed with a valid identification type + * @return pointer to newly allocated identification_t object, or NULL + * if regular expression is invalid + */ +identification_t *identification_create_from_string_with_regex(char *string); /** * Creates an identification from a chunk of data, guessing its type. @@ -333,7 +356,7 @@ identification_t * identification_create_from_string(char *string); * @param data identification data * @return identification_t */ -identification_t * identification_create_from_data(chunk_t data); +identification_t *identification_create_from_data(chunk_t data); /** * Creates an identification_t object from an encoded chunk. @@ -342,7 +365,8 @@ identification_t * identification_create_from_data(chunk_t data); * @param encoded encoded bytes, such as from identification_t.get_encoding * @return identification_t */ -identification_t * identification_create_from_encoding(id_type_t type, chunk_t encoded); +identification_t *identification_create_from_encoding(id_type_t type, + chunk_t encoded); /** * Creates an identification_t object from a sockaddr struct @@ -350,7 +374,7 @@ identification_t * identification_create_from_encoding(id_type_t type, chunk_t e * @param sockaddr sockaddr struct which contains family and address * @return identification_t */ -identification_t * identification_create_from_sockaddr(sockaddr_t *sockaddr); +identification_t *identification_create_from_sockaddr(sockaddr_t *sockaddr); /** * printf hook function for identification_t. @@ -359,6 +383,7 @@ identification_t * identification_create_from_sockaddr(sockaddr_t *sockaddr); * identification_t *identification */ int identification_printf_hook(printf_hook_data_t *data, - printf_hook_spec_t *spec, const void *const *args); + printf_hook_spec_t *spec, + const void *const *args); #endif /** IDENTIFICATION_H_ @}*/