xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/match_ops.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: match_ops.c,v 1.2 2017/02/14 01:16:49 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	match_ops 3
6 /* SUMMARY
7 /*	simple string or host pattern matching
8 /* SYNOPSIS
9 /*	#include <match_list.h>
10 /*
11 /*	int	match_string(list, string, pattern)
12 /*	MATCH_LIST *list;
13 /*	const char *string;
14 /*	const char *pattern;
15 /*
16 /*	int	match_hostname(list, name, pattern)
17 /*	MATCH_LIST *list;
18 /*	const char *name;
19 /*	const char *pattern;
20 /*
21 /*	int	match_hostaddr(list, addr, pattern)
22 /*	MATCH_LIST *list;
23 /*	const char *addr;
24 /*	const char *pattern;
25 /* DESCRIPTION
26 /*	This module implements simple string and host name or address
27 /*	matching. The matching process is case insensitive. If a pattern
28 /*	has the form type:name, table lookup is used instead of string
29 /*	or address comparison.
30 /*
31 /*	match_string() matches the string against the pattern, requiring
32 /*	an exact (case-insensitive) match. The flags argument is not used.
33 /*
34 /*	match_hostname() matches the host name when the hostname matches
35 /*	the pattern exactly, or when the pattern matches a parent domain
36 /*	of the named host. The flags argument specifies the bit-wise OR
37 /*	of zero or more of the following:
38 /* .IP MATCH_FLAG_PARENT
39 /*	The hostname pattern foo.com matches itself and any name below
40 /*	the domain foo.com. If this flag is cleared, foo.com matches itself
41 /*	only, and .foo.com matches any name below the domain foo.com.
42 /* .IP MATCH_FLAG_RETURN
43 /*	Log a warning, return "not found", and set list->error to
44 /*	a non-zero dictionary error code, instead of raising a fatal
45 /*	run-time error.
46 /* .RE
47 /*	Specify MATCH_FLAG_NONE to request none of the above.
48 /*
49 /*	match_hostaddr() matches a host address when the pattern is
50 /*	identical to the host address, or when the pattern is a net/mask
51 /*	that contains the address. The mask specifies the number of
52 /*	bits in the network part of the pattern. The flags argument is
53 /*	not used.
54 /* LICENSE
55 /* .ad
56 /* .fi
57 /*	The Secure Mailer license must be distributed with this software.
58 /* AUTHOR(S)
59 /*	Wietse Venema
60 /*	IBM T.J. Watson Research
61 /*	P.O. Box 704
62 /*	Yorktown Heights, NY 10598, USA
63 /*--*/
64 
65 /* System library. */
66 
67 #include <sys_defs.h>
68 #include <netinet/in.h>
69 #include <arpa/inet.h>
70 #include <string.h>
71 #include <stdlib.h>
72 
73 /* Utility library. */
74 
75 #include <msg.h>
76 #include <mymalloc.h>
77 #include <split_at.h>
78 #include <dict.h>
79 #include <match_list.h>
80 #include <stringops.h>
81 #include <cidr_match.h>
82 
83 #define MATCH_DICTIONARY(pattern) \
84     ((pattern)[0] != '[' && strchr((pattern), ':') != 0)
85 
86 /* match_error - return or raise fatal error */
87 
88 static int match_error(MATCH_LIST *list, const char *fmt,...)
89 {
90     VSTRING *buf = vstring_alloc(100);
91     va_list ap;
92 
93     /*
94      * Report, and maybe return.
95      */
96     va_start(ap, fmt);
97     vstring_vsprintf(buf, fmt, ap);
98     va_end(ap);
99     if (list->flags & MATCH_FLAG_RETURN) {
100 	msg_warn("%s: %s", list->pname, vstring_str(buf));
101     } else {
102 	msg_fatal("%s: %s", list->pname, vstring_str(buf));
103     }
104     vstring_free(buf);
105     return (0);
106 }
107 
108 /* match_string - match a string literal */
109 
110 int     match_string(MATCH_LIST *list, const char *string, const char *pattern)
111 {
112     const char *myname = "match_string";
113     DICT   *dict;
114 
115     if (msg_verbose)
116 	msg_info("%s: %s: %s ~? %s", myname, list->pname, string, pattern);
117 
118     /*
119      * Try dictionary lookup: exact match.
120      */
121     if (MATCH_DICTIONARY(pattern)) {
122 	if ((dict = dict_handle(pattern)) == 0)
123 	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
124 	if (dict_get(dict, string) != 0)
125 	    return (1);
126 	if ((list->error = dict->error) != 0)
127 	    return (match_error(list, "%s:%s: table lookup problem",
128 				dict->type, dict->name));
129 	return (0);
130     }
131 
132     /*
133      * Try an exact string match. Note that the string and pattern are
134      * already casefolded.
135      */
136     if (strcmp(string, pattern) == 0) {
137 	return (1);
138     }
139 
140     /*
141      * No match found.
142      */
143     return (0);
144 }
145 
146 /* match_hostname - match a host by name */
147 
148 int     match_hostname(MATCH_LIST *list, const char *name, const char *pattern)
149 {
150     const char *myname = "match_hostname";
151     const char *pd;
152     const char *entry;
153     const char *next;
154     int     match;
155     DICT   *dict;
156 
157     if (msg_verbose)
158 	msg_info("%s: %s: %s ~? %s", myname, list->pname, name, pattern);
159 
160     /*
161      * Try dictionary lookup: exact match and parent domains.
162      *
163      * Don't look up parent domain substrings with regexp maps etc.
164      */
165     if (MATCH_DICTIONARY(pattern)) {
166 	if ((dict = dict_handle(pattern)) == 0)
167 	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
168 	match = 0;
169 	for (entry = name; *entry != 0; entry = next) {
170 	    if (entry == name || (dict->flags & DICT_FLAG_FIXED)) {
171 		match = (dict_get(dict, entry) != 0);
172 		if (msg_verbose > 1)
173 		    msg_info("%s: %s: lookup %s:%s %s: %s",
174 			     myname, list->pname, dict->type, dict->name,
175 			     entry, match ? "found" : "notfound");
176 		if (match != 0)
177 		    break;
178 		if ((list->error = dict->error) != 0)
179 		    return (match_error(list, "%s:%s: table lookup problem",
180 					dict->type, dict->name));
181 	    }
182 	    if ((next = strchr(entry + 1, '.')) == 0)
183 		break;
184 	    if (list->flags & MATCH_FLAG_PARENT)
185 		next += 1;
186 	}
187 	return (match);
188     }
189 
190     /*
191      * Try an exact match with the host name. Note that the name and the
192      * pattern are already casefolded.
193      */
194     if (strcmp(name, pattern) == 0) {
195 	return (1);
196     }
197 
198     /*
199      * See if the pattern is a parent domain of the hostname. Note that the
200      * name and the pattern are already casefolded.
201      */
202     else {
203 	if (list->flags & MATCH_FLAG_PARENT) {
204 	    pd = name + strlen(name) - strlen(pattern);
205 	    if (pd > name && pd[-1] == '.' && strcmp(pd, pattern) == 0)
206 		return (1);
207 	} else if (pattern[0] == '.') {
208 	    pd = name + strlen(name) - strlen(pattern);
209 	    if (pd > name && strcmp(pd, pattern) == 0)
210 		return (1);
211 	}
212     }
213     return (0);
214 }
215 
216 /* match_hostaddr - match host by address */
217 
218 int     match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern)
219 {
220     const char *myname = "match_hostaddr";
221     char   *saved_patt;
222     CIDR_MATCH match_info;
223     DICT   *dict;
224     VSTRING *err;
225     int     rc;
226 
227     if (msg_verbose)
228 	msg_info("%s: %s: %s ~? %s", myname, list->pname, addr, pattern);
229 
230 #define V4_ADDR_STRING_CHARS	"01234567890."
231 #define V6_ADDR_STRING_CHARS	V4_ADDR_STRING_CHARS "abcdefABCDEF:"
232 
233     if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0)
234 	return (0);
235 
236     /*
237      * Try dictionary lookup. This can be case insensitive.
238      */
239     if (MATCH_DICTIONARY(pattern)) {
240 	if ((dict = dict_handle(pattern)) == 0)
241 	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
242 	if (dict_get(dict, addr) != 0)
243 	    return (1);
244 	if ((list->error = dict->error) != 0)
245 	    return (match_error(list, "%s:%s: table lookup problem",
246 				dict->type, dict->name));
247 	return (0);
248     }
249 
250     /*
251      * Try an exact match with the host address. Note that the address and
252      * pattern are already casefolded.
253      */
254     if (pattern[0] != '[') {
255 	if (strcmp(addr, pattern) == 0)
256 	    return (1);
257     } else {
258 	size_t  addr_len = strlen(addr);
259 
260 	if (strncmp(addr, pattern + 1, addr_len) == 0
261 	    && strcmp(pattern + 1 + addr_len, "]") == 0)
262 	    return (1);
263     }
264 
265     /*
266      * Light-weight tests before we get into expensive operations.
267      *
268      * - Don't bother matching IPv4 against IPv6. Postfix transforms
269      * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in
270      * Postfix; if not, then Postfix has no business dealing with IPv4
271      * addresses anyway.
272      *
273      * - Don't bother unless the pattern is either an IPv6 address or net/mask.
274      *
275      * We can safely skip IPv4 address patterns because their form is
276      * unambiguous and they did not match in the strcmp() calls above.
277      *
278      * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST
279      * input, to avoid triggering false cidr_match_parse() errors.
280      *
281      * The last two conditions below are for backwards compatibility with
282      * earlier Postfix versions: don't abort with fatal errors on junk that
283      * was silently ignored (principle of least astonishment).
284      */
285     if (!strchr(addr, ':') != !strchr(pattern, ':')
286 	|| pattern[strcspn(pattern, ":/")] == 0
287 	|| pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0
288 	|| pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0)
289 	return (0);
290 
291     /*
292      * No escape from expensive operations: either we have a net/mask
293      * pattern, or we have an address that can have multiple valid
294      * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way
295      * to find out if the address matches the pattern is to transform
296      * everything into to binary form, and to do the comparison there.
297      */
298     saved_patt = mystrdup(pattern);
299     err = cidr_match_parse(&match_info, saved_patt, (VSTRING *) 0);
300     myfree(saved_patt);
301     if (err != 0) {
302 	list->error = DICT_ERR_RETRY;
303 	rc = match_error(list, "%s", vstring_str(err));
304 	vstring_free(err);
305 	return (rc);
306     }
307     return (cidr_match_execute(&match_info, addr) != 0);
308 }
309