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