diff --git a/conf/format-options.py b/conf/format-options.py index e0fa18055..176a3837c 100755 --- a/conf/format-options.py +++ b/conf/format-options.py @@ -241,6 +241,7 @@ class GroffTagReplacer(TagReplacer): if not punct: punct = '' text = re.sub(r'[\r\n\t]', ' ', m.group('text')) + text = re.sub(r'"', '""', text) return '{0}.R{1} "{2}" "{3}" "{4}"\n'.format(nl, format, brack, text, punct) return replacer @@ -305,7 +306,8 @@ class ManFormatter: def __groffize(self, text): """Encode text as groff text""" text = self.__tags.replace(text) - text = re.sub(r'(?add(cfg, rule, identification_create_from_string(buf)); + cfg->add(cfg, rule, id); return TRUE; } diff --git a/src/libcharon/plugins/vici/vici_cred.c b/src/libcharon/plugins/vici/vici_cred.c index e5d013091..ff450fe1f 100644 --- a/src/libcharon/plugins/vici/vici_cred.c +++ b/src/libcharon/plugins/vici/vici_cred.c @@ -395,13 +395,15 @@ CALLBACK(shared_owners, bool, { if (streq(name, "owners")) { + identification_t *id; char buf[256]; - if (!vici_stringify(value, buf, sizeof(buf))) + if (!vici_stringify(value, buf, sizeof(buf)) || + !(id = identification_create_from_string_with_regex(buf))) { return FALSE; } - owners->insert_last(owners, identification_create_from_string(buf)); + owners->insert_last(owners, id); } return TRUE; } diff --git a/src/libstrongswan/tests/suites/test_identification.c b/src/libstrongswan/tests/suites/test_identification.c index db4043769..3ece5e1a7 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 58a05052d..08c2b7bb9 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) +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 }, - }; 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_data(chunk_t data) +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) { char buf[data.len + 1]; diff --git a/src/libstrongswan/utils/identification.h b/src/libstrongswan/utils/identification.h index 83f256017..3be5a54db 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_ @}*/ diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt index 6ed5de41d..a3c1c8b80 100644 --- a/src/swanctl/swanctl.opt +++ b/src/swanctl/swanctl.opt @@ -162,8 +162,8 @@ connections..fragmentation = yes Use IKE UDP datagram fragmentation (_yes_, _accept_, _no_ or _force_). Use IKE fragmentation (proprietary IKEv1 extension or RFC 7383 IKEv2 - fragmentation). Acceptable values are _yes_ (the default), _accept_, - _force_ and _no_. If set to _yes_, and the peer supports it, oversized IKE + fragmentation). Acceptable values are _yes_ (the default), _accept_, + _force_ and _no_. If set to _yes_, and the peer supports it, oversized IKE messages will be sent in fragments. If set to _accept_, support for fragmentation is announced to the peer but the daemon does not send its own messages in fragments. If set to _force_ (only supported for IKEv1) the @@ -350,7 +350,7 @@ connections..mediated_by = The name of the connection to mediate this connection through. If given, the connection will be mediated through the named mediation connection. - The mediation connection must have **mediation** enabled. + The mediation connection must have **mediation** enabled. connections..mediation_peer = Identity under which the peer is registered at the mediation server. @@ -426,7 +426,7 @@ connections..local.pubkeys = directory or an absolute path. Even though multiple local public keys could be defined in principle, only - the first public key in the list is used for authentication. + the first public key in the list is used for authentication. connections..local.auth = pubkey Authentication to perform locally (_pubkey_, _psk_, _xauth[-backend]_ or @@ -534,13 +534,27 @@ connections..remote.id = %any with wildcards, the _charon.rdn_matching_ option in **strongswan.conf**(5) specifies how RDNs are matched. + Extended POSIX regular expressions are also supported for remote identity + matching. They must start with an explicit type prefix, followed by a caret + character ('^'), and end with a dollar sign ('$') to indicate an anchored + pattern. Supported types are _rfc822_, _email_, _fqdn_, _dns_, and _asn1dn_. + While regular expressions are always matched against the string + representation of other identities, the type must match as well. The + matching is performed case insensitive. Make sure to escape backslash + characters when configuring identities in double quotes. Examples: + _email:^(moon|sun)@strongswan\.org$_, _fqdn:^vpn[0-9]+\.strongswan\.org$_, + _"asn1dn:^.*CN=.+\\.strongswan\\.org$"_. + connections..remote.eap_id = id Use EAP-Identity method to request an identity from the client to match against and use during EAP authentication. Use EAP-Identity method to request an identity from the client to match against and use during EAP authentication. There is currently no "best" - match, configs are matched in the order they are loaded. + match, configs are matched in the order they are loaded. + + Wildcards and regular expressions are supported, refer to the **id** keyword + for details. connections..remote.groups = Authorization group memberships to require. @@ -699,7 +713,7 @@ connections..children..ah_proposals = AH proposals to offer for the CHILD_SA. A proposal is a set of algorithms. For AH, this includes an integrity algorithm and an optional key exchange method. If a KE method is specified, CHILD_SA/Quick Mode rekeying and - initial negotiation uses a separate key exchange using the negotiated method + initial negotiation uses a separate key exchange using the negotiated method (refer to _esp_proposals_ for details). With peers that support multiple IKEv2 key exchanges (RFC 9370), up to seven @@ -737,7 +751,7 @@ connections..children..esp_proposals = default mismatch might, therefore, not immediately be noticed when the SA is established, but may later cause rekeying to fail. If one or more key exchange methods are configured in a proposal, the key exchange can be made - optional by also adding **none**. + optional by also adding **none**. With peers that support multiple IKEv2 key exchanges (RFC 9370), up to seven additional key exchanges may be negotiated. They can be configured by @@ -999,7 +1013,7 @@ connections..children..mark_in_sa = no only set on the inbound policy. The tuple destination address, protocol and SPI is unique and the mark is not required to find the correct SA, allowing to mark traffic after decryption instead (where more specific selectors may - be used) to match different policies. Marking packets before decryption is + be used) to match different policies. Marking packets before decryption is still possible, even if no mark is set on the SA. connections..children..mark_out = 0/0x00000000 @@ -1269,7 +1283,7 @@ secrets.ppk { # } Postquantum Preshared Key (PPK) section for a specific secret. Postquantum Preshared Key (PPK) section for a specific secret. Each PPK is - defined in a unique section having the _ppk_ prefix. + defined in a unique section having the _ppk_ prefix. secrets.ppk.secret = Value of the PPK.