]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
identification: Add support for POSIX regular expressions
authorTobias Brunner <tobias@strongswan.org>
Tue, 15 Apr 2025 13:09:20 +0000 (15:09 +0200)
committerTobias Brunner <tobias@strongswan.org>
Wed, 28 May 2025 07:58:17 +0000 (09:58 +0200)
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.

configure.ac
src/libstrongswan/tests/suites/test_identification.c
src/libstrongswan/utils/identification.c
src/libstrongswan/utils/identification.h

index db8c55a071fa464289f7efeb8ff89f9f4f48acf5..220c33ff550061a9aa8425b6a65debeca7087b2a 100644 (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)
index db40437692396ede1ff7b324c29b4510dbea2b97..3ece5e1a72266f41c15d66d5cfc81b502feea720 100644 (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);
 
index 58a05052dc1307ed6a844f10d271cb7e96f7a01b..08c2b7bb9a87f8f7cb53c96c948c5678dc235110 100644 (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
  *
 #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_tcreate_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_tcreate_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_tcreate_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];
 
index 83f2560176c59e8b0571f570282d53992c5f2d71..3be5a54db23a2a04c0d0ece4c28806441e9802eb 100644 (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_ @}*/