cidr_match.c revision 1.3
1/*	$NetBSD: cidr_match.c,v 1.3 2020/03/18 19:05:21 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, match, 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/* AUXILIARY FUNCTIONS
20/*	VSTRING *cidr_match_parse_if(info, pattern, match, why)
21/*	CIDR_MATCH *info;
22/*	char	*pattern;
23/*	VSTRING	*why;
24/*
25/*	void	cidr_match_endif(info)
26/*	CIDR_MATCH *info;
27/* DESCRIPTION
28/*	This module parses address or address/length patterns and
29/*	provides simple address matching. The implementation is
30/*	such that parsing and execution can be done without dynamic
31/*	memory allocation. The purpose is to minimize overhead when
32/*	called by functions that parse and execute on the fly, such
33/*	as match_hostaddr().
34/*
35/*	cidr_match_parse() parses an address or address/mask
36/*	expression and stores the result into the info argument.
37/*	A non-zero (or zero) match argument requests a positive (or
38/*	negative) match. The symbolic constants CIDR_MATCH_TRUE and
39/*	CIDR_MATCH_FALSE may help to improve code readability.
40/*	The result is non-zero in case of problems: either the
41/*	value of the why argument, or a newly allocated VSTRING
42/*	(the caller should give the latter to vstring_free()).
43/*	The pattern argument is destroyed.
44/*
45/*	cidr_match_parse_if() parses the address that follows an IF
46/*	token, and stores the result into the info argument.
47/*	The arguments are the same as for cidr_match_parse().
48/*
49/*	cidr_match_endif() handles the occurrence of an ENDIF token,
50/*	and updates the info argument.
51/*
52/*	cidr_match_execute() matches the specified address against
53/*	a list of parsed expressions, and returns the matching
54/*	expression's data structure.
55/* SEE ALSO
56/*	dict_cidr(3) CIDR-style lookup table
57/* AUTHOR(S)
58/*	Wietse Venema
59/*	IBM T.J. Watson Research
60/*	P.O. Box 704
61/*	Yorktown Heights, NY 10598, USA
62/*
63/*	Wietse Venema
64/*	Google, Inc.
65/*	111 8th Avenue
66/*	New York, NY 10011, USA
67/*--*/
68
69/* System library. */
70
71#include <sys_defs.h>
72#include <stdlib.h>
73#include <unistd.h>
74#include <string.h>
75#include <ctype.h>
76#include <sys/socket.h>
77#include <netinet/in.h>
78#include <arpa/inet.h>
79
80/* Utility library. */
81
82#include <msg.h>
83#include <vstring.h>
84#include <stringops.h>
85#include <split_at.h>
86#include <myaddrinfo.h>
87#include <mask_addr.h>
88#include <cidr_match.h>
89
90/* Application-specific. */
91
92 /*
93  * This is how we figure out the address family, address bit count and
94  * address byte count for a CIDR_MATCH entry.
95  */
96#ifdef HAS_IPV6
97#define CIDR_MATCH_ADDR_FAMILY(a) (strchr((a), ':') ? AF_INET6 : AF_INET)
98#define CIDR_MATCH_ADDR_BIT_COUNT(f) \
99    ((f) == AF_INET6 ? MAI_V6ADDR_BITS : \
100     (f) == AF_INET ? MAI_V4ADDR_BITS : \
101     (msg_panic("%s: bad address family %d", myname, (f)), 0))
102#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \
103    ((f) == AF_INET6 ? MAI_V6ADDR_BYTES : \
104     (f) == AF_INET ? MAI_V4ADDR_BYTES : \
105     (msg_panic("%s: bad address family %d", myname, (f)), 0))
106#else
107#define CIDR_MATCH_ADDR_FAMILY(a) (AF_INET)
108#define CIDR_MATCH_ADDR_BIT_COUNT(f) \
109    ((f) == AF_INET ? MAI_V4ADDR_BITS : \
110     (msg_panic("%s: bad address family %d", myname, (f)), 0))
111#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \
112    ((f) == AF_INET ? MAI_V4ADDR_BYTES : \
113     (msg_panic("%s: bad address family %d", myname, (f)), 0))
114#endif
115
116/* cidr_match_entry - match one entry */
117
118static inline int cidr_match_entry(CIDR_MATCH *entry,
119				           unsigned char *addr_bytes)
120{
121    unsigned char *mp;
122    unsigned char *np;
123    unsigned char *ap;
124
125    /* Unoptimized case: netmask with some or all bits zero. */
126    if (entry->mask_shift < entry->addr_bit_count) {
127	for (np = entry->net_bytes, mp = entry->mask_bytes,
128	     ap = addr_bytes; /* void */ ; np++, mp++, ap++) {
129	    if (ap >= addr_bytes + entry->addr_byte_count)
130		return (entry->match);
131	    if ((*ap & *mp) != *np)
132		break;
133	}
134    }
135    /* Optimized case: all 1 netmask (i.e. no netmask specified). */
136    else {
137	for (np = entry->net_bytes,
138	     ap = addr_bytes; /* void */ ; np++, ap++) {
139	    if (ap >= addr_bytes + entry->addr_byte_count)
140		return (entry->match);
141	    if (*ap != *np)
142		break;
143	}
144    }
145    return (!entry->match);
146}
147
148/* cidr_match_execute - match address against compiled CIDR pattern list */
149
150CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr)
151{
152    unsigned char addr_bytes[CIDR_MATCH_ABYTES];
153    unsigned addr_family;
154    CIDR_MATCH *entry;
155
156    addr_family = CIDR_MATCH_ADDR_FAMILY(addr);
157    if (inet_pton(addr_family, addr, addr_bytes) != 1)
158	return (0);
159
160    for (entry = list; entry; entry = entry->next) {
161
162	switch (entry->op) {
163
164	case CIDR_MATCH_OP_MATCH:
165	    if (entry->addr_family == addr_family)
166		if (cidr_match_entry(entry, addr_bytes))
167		    return (entry);
168	    break;
169
170	case CIDR_MATCH_OP_IF:
171	    if (entry->addr_family == addr_family)
172		if (cidr_match_entry(entry, addr_bytes))
173		    continue;
174	    /* An IF without matching ENDIF has no end-of block entry. */
175	    if ((entry = entry->block_end) == 0)
176		return (0);
177	    /* FALLTHROUGH */
178
179	case CIDR_MATCH_OP_ENDIF:
180	    continue;
181	}
182    }
183    return (0);
184}
185
186/* cidr_match_parse - parse CIDR pattern */
187
188VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, int match,
189			          VSTRING *why)
190{
191    const char *myname = "cidr_match_parse";
192    char   *mask_search;
193    char   *mask;
194    MAI_HOSTADDR_STR hostaddr;
195    unsigned char *np;
196    unsigned char *mp;
197
198    /*
199     * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR
200     * maps don't need [] to eliminate syntax ambiguity, but matchlists need
201     * it. While stripping [], figure out where we should start looking for
202     * /mask information.
203     */
204    if (*pattern == '[') {
205	pattern++;
206	if ((mask_search = split_at(pattern, ']')) == 0) {
207	    vstring_sprintf(why ? why : (why = vstring_alloc(20)),
208			    "missing ']' character after \"[%s\"", pattern);
209	    return (why);
210	} else if (*mask_search != '/') {
211	    if (*mask_search != 0) {
212		vstring_sprintf(why ? why : (why = vstring_alloc(20)),
213				"garbage after \"[%s]\"", pattern);
214		return (why);
215	    }
216	    mask_search = pattern;
217	}
218    } else
219	mask_search = pattern;
220
221    /*
222     * Parse the pattern into network and mask, destroying the pattern.
223     */
224    if ((mask = split_at(mask_search, '/')) != 0) {
225	ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern);
226	ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family);
227	ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family);
228	if (!alldig(mask)
229	    || (ip->mask_shift = atoi(mask)) > ip->addr_bit_count
230	    || inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) {
231	    vstring_sprintf(why ? why : (why = vstring_alloc(20)),
232			  "bad net/mask pattern: \"%s/%s\"", pattern, mask);
233	    return (why);
234	}
235	if (ip->mask_shift > 0) {
236	    /* Allow for bytes > 8. */
237	    memset(ip->mask_bytes, ~0U, ip->addr_byte_count);
238	    mask_addr(ip->mask_bytes, ip->addr_byte_count, ip->mask_shift);
239	} else
240	    memset(ip->mask_bytes, 0, ip->addr_byte_count);
241
242	/*
243	 * Sanity check: all host address bits must be zero.
244	 */
245	for (np = ip->net_bytes, mp = ip->mask_bytes;
246	     np < ip->net_bytes + ip->addr_byte_count; np++, mp++) {
247	    if (*np & ~(*mp)) {
248		mask_addr(ip->net_bytes, ip->addr_byte_count, ip->mask_shift);
249		if (inet_ntop(ip->addr_family, ip->net_bytes, hostaddr.buf,
250			      sizeof(hostaddr.buf)) == 0)
251		    msg_fatal("inet_ntop: %m");
252		vstring_sprintf(why ? why : (why = vstring_alloc(20)),
253				"non-null host address bits in \"%s/%s\", "
254				"perhaps you should use \"%s/%d\" instead",
255				pattern, mask, hostaddr.buf, ip->mask_shift);
256		return (why);
257	    }
258	}
259    }
260
261    /*
262     * No /mask specified. Treat a bare network address as /allbits.
263     */
264    else {
265	ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern);
266	ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family);
267	ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family);
268	if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) {
269	    vstring_sprintf(why ? why : (why = vstring_alloc(20)),
270			    "bad address pattern: \"%s\"", pattern);
271	    return (why);
272	}
273	ip->mask_shift = ip->addr_bit_count;
274	/* Allow for bytes > 8. */
275	memset(ip->mask_bytes, ~0U, ip->addr_byte_count);
276    }
277
278    /*
279     * Wrap up the result.
280     */
281    ip->op = CIDR_MATCH_OP_MATCH;
282    ip->match = match;
283    ip->next = 0;
284    ip->block_end = 0;
285
286    return (0);
287}
288
289/* cidr_match_parse_if - parse CIDR pattern after IF */
290
291VSTRING *cidr_match_parse_if(CIDR_MATCH *ip, char *pattern, int match,
292			             VSTRING *why)
293{
294    VSTRING *ret;
295
296    if ((ret = cidr_match_parse(ip, pattern, match, why)) == 0)
297	ip->op = CIDR_MATCH_OP_IF;
298    return (ret);
299}
300
301/* cidr_match_endif - handle ENDIF pattern */
302
303void    cidr_match_endif(CIDR_MATCH *ip)
304{
305    memset(ip, 0, sizeof(*ip));
306    ip->op = CIDR_MATCH_OP_ENDIF;
307    ip->next = 0;				/* maybe not all bits 0 */
308    ip->block_end = 0;
309}
310