xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/match_ops.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*	$NetBSD: match_ops.c,v 1.1.1.1 2009/06/23 10:09:00 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	match_ops 3
6 /* SUMMARY
7 /*	simple string or host pattern matching
8 /* SYNOPSIS
9 /*	#include <match_ops.h>
10 /*
11 /*	int	match_string(flags, string, pattern)
12 /*	int	flags;
13 /*	const char *string;
14 /*	const char *pattern;
15 /*
16 /*	int	match_hostname(flags, name, pattern)
17 /*	int	flags;
18 /*	const char *name;
19 /*	const char *pattern;
20 /*
21 /*	int	match_hostaddr(flags, addr, pattern)
22 /*	int	flags;
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 /* .RE
43 /*	Specify MATCH_FLAG_NONE to request none of the above.
44 /*
45 /*	match_hostaddr() matches a host address when the pattern is
46 /*	identical to the host address, or when the pattern is a net/mask
47 /*	that contains the address. The mask specifies the number of
48 /*	bits in the network part of the pattern. The flags argument is
49 /*	not used.
50 /* LICENSE
51 /* .ad
52 /* .fi
53 /*	The Secure Mailer license must be distributed with this software.
54 /* AUTHOR(S)
55 /*	Wietse Venema
56 /*	IBM T.J. Watson Research
57 /*	P.O. Box 704
58 /*	Yorktown Heights, NY 10598, USA
59 /*--*/
60 
61 /* System library. */
62 
63 #include <sys_defs.h>
64 #include <netinet/in.h>
65 #include <arpa/inet.h>
66 #include <string.h>
67 #include <stdlib.h>
68 
69 #ifdef STRCASECMP_IN_STRINGS_H
70 #include <strings.h>
71 #endif
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_ops.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_string - match a string literal */
87 
88 int     match_string(int unused_flags, const char *string, const char *pattern)
89 {
90     const char *myname = "match_string";
91     int     match;
92 
93     if (msg_verbose)
94 	msg_info("%s: %s ~? %s", myname, string, pattern);
95 
96     /*
97      * Try dictionary lookup: exact match.
98      */
99     if (MATCH_DICTIONARY(pattern)) {
100 	match = (dict_lookup(pattern, string) != 0);
101 	if (match != 0)
102 	    return (1);
103 	if (dict_errno != 0)
104 	    msg_fatal("%s: table lookup problem", pattern);
105 	return (0);
106     }
107 
108     /*
109      * Try an exact string match.
110      */
111     if (strcasecmp(string, pattern) == 0) {
112 	return (1);
113     }
114 
115     /*
116      * No match found.
117      */
118     return (0);
119 }
120 
121 /* match_hostname - match a host by name */
122 
123 int     match_hostname(int flags, const char *name, const char *pattern)
124 {
125     const char *myname = "match_hostname";
126     const char *pd;
127     const char *entry;
128     const char *next;
129     int     match;
130     DICT   *dict;
131 
132     if (msg_verbose)
133 	msg_info("%s: %s ~? %s", myname, name, pattern);
134 
135     /*
136      * Try dictionary lookup: exact match and parent domains.
137      *
138      * Don't look up parent domain substrings with regexp maps etc.
139      */
140     if (MATCH_DICTIONARY(pattern)) {
141 	if ((dict = dict_handle(pattern)) == 0)
142 	    msg_panic("%s: unknown dictionary: %s", myname, pattern);
143 	match = 0;
144 	for (entry = name; *entry != 0; entry = next) {
145 	    if (entry == name || (dict->flags & DICT_FLAG_FIXED)) {
146 		match = (dict_get(dict, entry) != 0);
147 		if (msg_verbose > 1)
148 		    msg_info("%s: lookup %s:%s %s: %s",
149 			     myname, dict->type, dict->name, entry,
150 			     match ? "found" : "notfound");
151 		if (match != 0)
152 		    break;
153 		if (dict_errno != 0)
154 		    msg_fatal("%s: table lookup problem", pattern);
155 	    }
156 	    if ((next = strchr(entry + 1, '.')) == 0)
157 		break;
158 	    if (flags & MATCH_FLAG_PARENT)
159 		next += 1;
160 	}
161 	return (match);
162     }
163 
164     /*
165      * Try an exact match with the host name.
166      */
167     if (strcasecmp(name, pattern) == 0) {
168 	return (1);
169     }
170 
171     /*
172      * See if the pattern is a parent domain of the hostname.
173      */
174     else {
175 	if (flags & MATCH_FLAG_PARENT) {
176 	    pd = name + strlen(name) - strlen(pattern);
177 	    if (pd > name && pd[-1] == '.' && strcasecmp(pd, pattern) == 0)
178 		return (1);
179 	} else if (pattern[0] == '.') {
180 	    pd = name + strlen(name) - strlen(pattern);
181 	    if (pd > name && strcasecmp(pd, pattern) == 0)
182 		return (1);
183 	}
184     }
185     return (0);
186 }
187 
188 /* match_hostaddr - match host by address */
189 
190 int     match_hostaddr(int unused_flags, const char *addr, const char *pattern)
191 {
192     const char *myname = "match_hostaddr";
193     char   *saved_patt;
194     CIDR_MATCH match_info;
195     VSTRING *err;
196 
197     if (msg_verbose)
198 	msg_info("%s: %s ~? %s", myname, addr, pattern);
199 
200 #define V4_ADDR_STRING_CHARS	"01234567890."
201 #define V6_ADDR_STRING_CHARS	V4_ADDR_STRING_CHARS "abcdefABCDEF:"
202 
203     if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0)
204 	return (0);
205 
206     /*
207      * Try dictionary lookup. This can be case insensitive.
208      */
209     if (MATCH_DICTIONARY(pattern)) {
210 	if (dict_lookup(pattern, addr) != 0)
211 	    return (1);
212 	if (dict_errno != 0)
213 	    msg_fatal("%s: table lookup problem", pattern);
214 	return (0);
215     }
216 
217     /*
218      * Try an exact match with the host address.
219      */
220     if (pattern[0] != '[') {
221 	if (strcasecmp(addr, pattern) == 0)
222 	    return (1);
223     } else {
224 	size_t  addr_len = strlen(addr);
225 
226 	if (strncasecmp(addr, pattern + 1, addr_len) == 0
227 	    && strcmp(pattern + 1 + addr_len, "]") == 0)
228 	    return (1);
229     }
230 
231     /*
232      * Light-weight tests before we get into expensive operations.
233      *
234      * - Don't bother matching IPv4 against IPv6. Postfix transforms
235      * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in
236      * Postfix; if not, then Postfix has no business dealing with IPv4
237      * addresses anyway.
238      *
239      * - Don't bother unless the pattern is either an IPv6 address or net/mask.
240      *
241      * We can safely skip IPv4 address patterns because their form is
242      * unambiguous and they did not match in the strcasecmp() calls above.
243      *
244      * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST
245      * input, to avoid triggering false cidr_match_parse() errors.
246      *
247      * The last two conditions below are for backwards compatibility with
248      * earlier Postfix versions: don't abort with fatal errors on junk that
249      * was silently ignored (principle of least astonishment).
250      */
251     if (!strchr(addr, ':') != !strchr(pattern, ':')
252 	|| pattern[strcspn(pattern, ":/")] == 0
253 	|| pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0
254 	|| pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0)
255 	return (0);
256 
257     /*
258      * No escape from expensive operations: either we have a net/mask
259      * pattern, or we have an address that can have multiple valid
260      * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way
261      * to find out if the address matches the pattern is to transform
262      * everything into to binary form, and to do the comparison there.
263      */
264     saved_patt = mystrdup(pattern);
265     if ((err = cidr_match_parse(&match_info, saved_patt, (VSTRING *) 0)) != 0)
266 	msg_fatal("%s", vstring_str(err));
267     myfree(saved_patt);
268     return (cidr_match_execute(&match_info, addr) != 0);
269 }
270