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