Merge branch 'regex-ids'

Adds support for POSIX regular expressions in configured identities,
which makes matching remote identities more flexible.
This commit is contained in:
Tobias Brunner 2025-05-28 09:58:46 +02:00
commit 301887b865
8 changed files with 526 additions and 69 deletions

View File

@ -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'(?<!\\)-', r'\\-', text)
text = re.sub(r'\\(?!-)', '\\[rs]', text)
text = re.sub(r'(?<!\\)-', '\\-', text)
# remove any leading whitespace
return re.sub(r'^\s+', '', text, flags = re.MULTILINE)

View File

@ -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)

View File

@ -1486,13 +1486,15 @@ CALLBACK(parse_auth, bool,
*/
static bool parse_id(auth_cfg_t *cfg, auth_rule_t rule, chunk_t v)
{
identification_t *id;
char buf[BUF_LEN];
if (!vici_stringify(v, buf, sizeof(buf)))
if (!vici_stringify(v, buf, sizeof(buf)) ||
!(id = identification_create_from_string_with_regex(buf)))
{
return FALSE;
}
cfg->add(cfg, rule, identification_create_from_string(buf));
cfg->add(cfg, rule, id);
return TRUE;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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 <stdio.h>
#include <errno.h>
#ifdef HAVE_REGEX
#include <regex.h>
#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 <utils/utils.h>
@ -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];

View File

@ -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_ @}*/

View File

@ -162,8 +162,8 @@ connections.<conn>.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.<conn>.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.<conn>.mediation_peer =
Identity under which the peer is registered at the mediation server.
@ -426,7 +426,7 @@ connections.<conn>.local<suffix>.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.<conn>.local<suffix>.auth = pubkey
Authentication to perform locally (_pubkey_, _psk_, _xauth[-backend]_ or
@ -534,13 +534,27 @@ connections.<conn>.remote<suffix>.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.<conn>.remote<suffix>.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.<conn>.remote<suffix>.groups =
Authorization group memberships to require.
@ -699,7 +713,7 @@ connections.<conn>.children.<child>.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.<conn>.children.<child>.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.<conn>.children.<child>.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.<conn>.children.<child>.mark_out = 0/0x00000000
@ -1269,7 +1283,7 @@ secrets.ppk<suffix> { # }
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<suffix>.secret =
Value of the PPK.