xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/cidr_match.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: cidr_match.c,v 1.2 2017/02/14 01:16:49 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	cidr_match 3
6 /* SUMMARY
7 /*	CIDR-style pattern matching
8 /* SYNOPSIS
9 /*	#include <cidr_match.h>
10 /*
11 /*	VSTRING *cidr_match_parse(info, pattern, why)
12 /*	CIDR_MATCH *info;
13 /*	char	*pattern;
14 /*	VSTRING	*why;
15 /*
16 /*	int	cidr_match_execute(info, address)
17 /*	CIDR_MATCH *info;
18 /*	const char *address;
19 /* DESCRIPTION
20 /*	This module parses address or address/length patterns and
21 /*	provides simple address matching. The implementation is
22 /*	such that parsing and execution can be done without dynamic
23 /*	memory allocation. The purpose is to minimize overhead when
24 /*	called by functions that parse and execute on the fly, such
25 /*	as match_hostaddr().
26 /*
27 /*	cidr_match_parse() parses an address or address/mask
28 /*	expression and stores the result into the info argument.
29 /*	The result is non-zero in case of problems: either the
30 /*	value of the why argument, or a newly allocated VSTRING
31 /*	(the caller should give the latter to vstring_free()).
32 /*	The pattern argument is destroyed.
33 /*
34 /*	cidr_match_execute() matches the specified address against
35 /*	a list of parsed expressions, and returns the matching
36 /*	expression's data structure.
37 /* SEE ALSO
38 /*	dict_cidr(3) CIDR-style lookup table
39 /* AUTHOR(S)
40 /*	Wietse Venema
41 /*	IBM T.J. Watson Research
42 /*	P.O. Box 704
43 /*	Yorktown Heights, NY 10598, USA
44 /*--*/
45 
46 /* System library. */
47 
48 #include <sys_defs.h>
49 #include <stdlib.h>
50 #include <unistd.h>
51 #include <string.h>
52 #include <ctype.h>
53 #include <sys/socket.h>
54 #include <netinet/in.h>
55 #include <arpa/inet.h>
56 
57 /* Utility library. */
58 
59 #include <msg.h>
60 #include <vstring.h>
61 #include <stringops.h>
62 #include <split_at.h>
63 #include <myaddrinfo.h>
64 #include <mask_addr.h>
65 #include <cidr_match.h>
66 
67 /* Application-specific. */
68 
69  /*
70   * This is how we figure out the address family, address bit count and
71   * address byte count for a CIDR_MATCH entry.
72   */
73 #ifdef HAS_IPV6
74 #define CIDR_MATCH_ADDR_FAMILY(a) (strchr((a), ':') ? AF_INET6 : AF_INET)
75 #define CIDR_MATCH_ADDR_BIT_COUNT(f) \
76     ((f) == AF_INET6 ? MAI_V6ADDR_BITS : \
77      (f) == AF_INET ? MAI_V4ADDR_BITS : \
78      (msg_panic("%s: bad address family %d", myname, (f)), 0))
79 #define CIDR_MATCH_ADDR_BYTE_COUNT(f) \
80     ((f) == AF_INET6 ? MAI_V6ADDR_BYTES : \
81      (f) == AF_INET ? MAI_V4ADDR_BYTES : \
82      (msg_panic("%s: bad address family %d", myname, (f)), 0))
83 #else
84 #define CIDR_MATCH_ADDR_FAMILY(a) (AF_INET)
85 #define CIDR_MATCH_ADDR_BIT_COUNT(f) \
86     ((f) == AF_INET ? MAI_V4ADDR_BITS : \
87      (msg_panic("%s: bad address family %d", myname, (f)), 0))
88 #define CIDR_MATCH_ADDR_BYTE_COUNT(f) \
89     ((f) == AF_INET ? MAI_V4ADDR_BYTES : \
90      (msg_panic("%s: bad address family %d", myname, (f)), 0))
91 #endif
92 
93 /* cidr_match_execute - match address against compiled CIDR pattern list */
94 
95 CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr)
96 {
97     unsigned char addr_bytes[CIDR_MATCH_ABYTES];
98     unsigned addr_family;
99     unsigned char *mp;
100     unsigned char *np;
101     unsigned char *ap;
102     CIDR_MATCH *entry;
103 
104     addr_family = CIDR_MATCH_ADDR_FAMILY(addr);
105     if (inet_pton(addr_family, addr, addr_bytes) != 1)
106 	return (0);
107 
108     for (entry = list; entry; entry = entry->next) {
109 	if (entry->addr_family == addr_family) {
110 	    /* Unoptimized case: netmask with some or all bits zero. */
111 	    if (entry->mask_shift < entry->addr_bit_count) {
112 		for (np = entry->net_bytes, mp = entry->mask_bytes,
113 		     ap = addr_bytes; /* void */ ; np++, mp++, ap++) {
114 		    if (ap >= addr_bytes + entry->addr_byte_count)
115 			return (entry);
116 		    if ((*ap & *mp) != *np)
117 			break;
118 		}
119 	    }
120 	    /* Optimized case: all 1 netmask (i.e. no netmask specified). */
121 	    else {
122 		for (np = entry->net_bytes,
123 		     ap = addr_bytes; /* void */ ; np++, ap++) {
124 		    if (ap >= addr_bytes + entry->addr_byte_count)
125 			return (entry);
126 		    if (*ap != *np)
127 			break;
128 		}
129 	    }
130 	}
131     }
132     return (0);
133 }
134 
135 /* cidr_match_parse - parse CIDR pattern */
136 
137 VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, VSTRING *why)
138 {
139     const char *myname = "cidr_match_parse";
140     char   *mask_search;
141     char   *mask;
142     MAI_HOSTADDR_STR hostaddr;
143     unsigned char *np;
144     unsigned char *mp;
145 
146     /*
147      * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR
148      * maps don't need [] to eliminate syntax ambiguity, but matchlists need
149      * it. While stripping [], figure out where we should start looking for
150      * /mask information.
151      */
152     if (*pattern == '[') {
153 	pattern++;
154 	if ((mask_search = split_at(pattern, ']')) == 0) {
155 	    vstring_sprintf(why ? why : (why = vstring_alloc(20)),
156 			    "missing ']' character after \"[%s\"", pattern);
157 	    return (why);
158 	} else if (*mask_search != '/') {
159 	    if (*mask_search != 0) {
160 		vstring_sprintf(why ? why : (why = vstring_alloc(20)),
161 				"garbage after \"[%s]\"", pattern);
162 		return (why);
163 	    }
164 	    mask_search = pattern;
165 	}
166     } else
167 	mask_search = pattern;
168 
169     /*
170      * Parse the pattern into network and mask, destroying the pattern.
171      */
172     if ((mask = split_at(mask_search, '/')) != 0) {
173 	ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern);
174 	ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family);
175 	ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family);
176 	if (!alldig(mask)
177 	    || (ip->mask_shift = atoi(mask)) > ip->addr_bit_count
178 	    || inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) {
179 	    vstring_sprintf(why ? why : (why = vstring_alloc(20)),
180 			  "bad net/mask pattern: \"%s/%s\"", pattern, mask);
181 	    return (why);
182 	}
183 	if (ip->mask_shift > 0) {
184 	    /* Allow for bytes > 8. */
185 	    memset(ip->mask_bytes, ~0U, ip->addr_byte_count);
186 	    mask_addr(ip->mask_bytes, ip->addr_byte_count, ip->mask_shift);
187 	} else
188 	    memset(ip->mask_bytes, 0, ip->addr_byte_count);
189 
190 	/*
191 	 * Sanity check: all host address bits must be zero.
192 	 */
193 	for (np = ip->net_bytes, mp = ip->mask_bytes;
194 	     np < ip->net_bytes + ip->addr_byte_count; np++, mp++) {
195 	    if (*np & ~(*mp)) {
196 		mask_addr(ip->net_bytes, ip->addr_byte_count, ip->mask_shift);
197 		if (inet_ntop(ip->addr_family, ip->net_bytes, hostaddr.buf,
198 			      sizeof(hostaddr.buf)) == 0)
199 		    msg_fatal("inet_ntop: %m");
200 		vstring_sprintf(why ? why : (why = vstring_alloc(20)),
201 				"non-null host address bits in \"%s/%s\", "
202 				"perhaps you should use \"%s/%d\" instead",
203 				pattern, mask, hostaddr.buf, ip->mask_shift);
204 		return (why);
205 	    }
206 	}
207     }
208 
209     /*
210      * No /mask specified. Treat a bare network address as /allbits.
211      */
212     else {
213 	ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern);
214 	ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family);
215 	ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family);
216 	if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) {
217 	    vstring_sprintf(why ? why : (why = vstring_alloc(20)),
218 			    "bad address pattern: \"%s\"", pattern);
219 	    return (why);
220 	}
221 	ip->mask_shift = ip->addr_bit_count;
222 	/* Allow for bytes > 8. */
223 	memset(ip->mask_bytes, ~0U, ip->addr_byte_count);
224     }
225 
226     /*
227      * Wrap up the result.
228      */
229     ip->next = 0;
230 
231     return (0);
232 }
233