cidr_match.c revision 1.2
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
95CIDR_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
137VSTRING *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