]> Pileus Git - ~andy/fetchmail/blob - x509_name_match.c
Fix typo repsonsible -> responsible.
[~andy/fetchmail] / x509_name_match.c
1 #include "fetchmail.h"
2
3 #include <string.h>
4 #include <strings.h>
5
6 /** A picky certificate name check:
7  * check if the pattern or string in s1 (from a certificate) matches the
8  * hostname (in s2), returns true if matched.
9  *
10  * The only place where a wildcard is allowed is in the leftmost
11  * position of p1. */
12 int name_match(const char *p1, const char *p2) {
13     const char *const dom = "0123456789.";
14     int wildcard_ok = 1;
15
16     /* blank patterns never match */
17     if (p1[0] == '\0')
18         return 0;
19
20     /* disallow wildcards in certificates for domain literals
21      * (10.9.8.7-like) */
22     if (strspn(p1+(*p1 == '*' ? 1 : 0), dom) == strlen(p1))
23         wildcard_ok = 0;
24
25     /* disallow wildcards for domain literals */
26     if (strspn(p2, dom) == strlen(p2))
27         wildcard_ok = 0;
28
29     /* If we decided above that wildcarding is still OK,
30      * try a wildcard match first - providing that
31      * the wildcard is for a full component,
32      * i. e. starts with "*." */
33     if (wildcard_ok && p1[0] == '*' && p1[1] == '.') {
34         size_t l1, l2;
35         int number_dots = 0;
36         const char *tmp;
37
38         /* skip over the asterisk */
39         ++p1;
40
41         /* make sure CAs don't wildcard top-level domains by requiring there
42          * are at least two dots in wildcarded X.509 CN/SANs */
43         for(tmp = p1; *tmp; tmp += strcspn(tmp, ".")) {
44             if (*tmp == '.') {
45                 ++number_dots;
46                 ++tmp;
47             }
48         }
49
50         /* If there are at least 2 dots, do the wildcard match.
51          * Match from the end by incrementing the p2 pointer by the
52          * length difference between remainder of pattern and string to
53          * be matched. */
54         if (number_dots >= 2) {
55             l1 = strlen(p1);
56             l2 = strlen(p2);
57             if (l2 > l1)
58                 p2 += l2 - l1;
59             /* FALLTHROUGH */
60         }
61     }
62
63     /* Now to the match. Either wildcards are forbidden or not found,
64      * then it's a case-insensitive full-string match, or wildcards are
65      * permitted and found and we've bumped the start-string pointers
66      * sufficiently. */
67     return (0 == strcasecmp(p1, p2));
68
69     /* XXX open issue: do we need to deal with trailing dots in patterns
70      * or domains? A trailing dot is an anchor that prevents resolver
71      * "search"es to DNS, so might cause false mismatches. */
72 }
73
74 #ifdef TEST
75 #include <stdlib.h>
76 #include <stdio.h>
77
78 static int verbose;
79
80 /* print test and return true on failure */
81 static int test(const char *p1, const char *p2, int expect) {
82     int match = name_match(p1, p2);
83     if (verbose)
84         printf("name_match(\"%s\", \"%s\") == %d (%d expected)\n", p1, p2, match, expect);
85     return expect != match;
86 }
87
88 int main(int argc, const char **argv) {
89     int rc = 0;
90
91     if (argc > 1 && 0 == strcmp(argv[1], "-v"))
92         verbose = 1;
93
94     rc |= test("example.org", "example.org", 1);
95     rc |= test("*example.org", "foo.example.org", 0);
96     rc |= test("*.example.org", "foo.example.org", 1);
97     rc |= test("*.168.23.23", "192.168.23.23", 0);
98     rc |= test("*.com", "example.com", 0);
99     if (verbose) {
100         printf("x509_name_match: ");
101         puts(rc ? "FAIL" : "PASS");
102     }
103     return rc;
104 }
105 #endif