xref: /openbsd-src/sys/net/pf_ruleset.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
1 /*	$OpenBSD: pf_ruleset.c,v 1.18 2018/12/27 16:54:01 kn Exp $ */
2 
3 /*
4  * Copyright (c) 2001 Daniel Hartmeier
5  * Copyright (c) 2002,2003 Henning Brauer
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  *    - Redistributions of source code must retain the above copyright
13  *      notice, this list of conditions and the following disclaimer.
14  *    - Redistributions in binary form must reproduce the above
15  *      copyright notice, this list of conditions and the following
16  *      disclaimer in the documentation and/or other materials provided
17  *      with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  * Effort sponsored in part by the Defense Advanced Research Projects
33  * Agency (DARPA) and Air Force Research Laboratory, Air Force
34  * Materiel Command, USAF, under agreement number F30602-01-2-0537.
35  *
36  */
37 
38 #include <sys/param.h>
39 #include <sys/socket.h>
40 #ifdef _KERNEL
41 #include <sys/systm.h>
42 #include <sys/mbuf.h>
43 #endif /* _KERNEL */
44 #include <sys/syslog.h>
45 
46 #include <netinet/in.h>
47 #include <netinet/ip.h>
48 #include <netinet/tcp.h>
49 
50 #include <net/if.h>
51 #include <net/pfvar.h>
52 
53 #ifdef INET6
54 #include <netinet/ip6.h>
55 #endif /* INET6 */
56 
57 
58 #ifdef _KERNEL
59 #define rs_malloc(x)		malloc(x, M_TEMP, M_WAITOK|M_CANFAIL|M_ZERO)
60 #define rs_free(x, siz)		free(x, M_TEMP, siz)
61 
62 #else	/* !_KERNEL */
63 /* Userland equivalents so we can lend code to pfctl et al. */
64 
65 #include <arpa/inet.h>
66 #include <errno.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #define rs_malloc(x)		 calloc(1, x)
71 #define rs_free(x, siz)		 freezero(x, siz)
72 
73 #ifdef PFDEBUG
74 #include <sys/stdarg.h>	/* for DPFPRINTF() */
75 #endif	/* PFDEBUG */
76 #endif /* _KERNEL */
77 
78 
79 struct pf_anchor_global	 pf_anchors;
80 struct pf_anchor	 pf_main_anchor;
81 
82 static __inline int pf_anchor_compare(struct pf_anchor *, struct pf_anchor *);
83 
84 RB_GENERATE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare);
85 RB_GENERATE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare);
86 
87 static __inline int
88 pf_anchor_compare(struct pf_anchor *a, struct pf_anchor *b)
89 {
90 	int c = strcmp(a->path, b->path);
91 
92 	return (c ? (c < 0 ? -1 : 1) : 0);
93 }
94 
95 void
96 pf_init_ruleset(struct pf_ruleset *ruleset)
97 {
98 	memset(ruleset, 0, sizeof(struct pf_ruleset));
99 	TAILQ_INIT(&ruleset->rules.queues[0]);
100 	TAILQ_INIT(&ruleset->rules.queues[1]);
101 	ruleset->rules.active.ptr = &ruleset->rules.queues[0];
102 	ruleset->rules.inactive.ptr = &ruleset->rules.queues[1];
103 }
104 
105 struct pf_anchor *
106 pf_find_anchor(const char *path)
107 {
108 	struct pf_anchor	*key, *found;
109 
110 	key = rs_malloc(sizeof(*key));
111 	if (key == NULL)
112 		return (NULL);
113 	strlcpy(key->path, path, sizeof(key->path));
114 	found = RB_FIND(pf_anchor_global, &pf_anchors, key);
115 	rs_free(key, sizeof(*key));
116 	return (found);
117 }
118 
119 struct pf_ruleset *
120 pf_find_ruleset(const char *path)
121 {
122 	struct pf_anchor	*anchor;
123 
124 	while (*path == '/')
125 		path++;
126 	if (!*path)
127 		return (&pf_main_ruleset);
128 	anchor = pf_find_anchor(path);
129 	if (anchor == NULL)
130 		return (NULL);
131 	else
132 		return (&anchor->ruleset);
133 }
134 
135 struct pf_ruleset *
136 pf_get_leaf_ruleset(char *path, char **path_remainder)
137 {
138 	struct pf_ruleset	*ruleset;
139 	char			*leaf, *p;
140 	int			 i = 0;
141 
142 	p = path;
143 	while (*p == '/')
144 		p++;
145 
146 	ruleset = pf_find_ruleset(p);
147 	leaf = p;
148 	while (ruleset == NULL) {
149 		leaf = strrchr(p, '/');
150 		if (leaf != NULL) {
151 			*leaf = '\0';
152 			i++;
153 			ruleset = pf_find_ruleset(p);
154 		} else {
155 			leaf = path;
156 			/*
157 			 * if no path component exists, then main ruleset is
158 			 * our parent.
159 			 */
160 			ruleset = &pf_main_ruleset;
161 		}
162 	}
163 
164 	if (path_remainder != NULL)
165 		*path_remainder = leaf;
166 
167 	/* restore slashes in path.  */
168 	while (i != 0) {
169 		while (*leaf != '\0')
170 			leaf++;
171 		*leaf = '/';
172 		i--;
173 	}
174 
175 	return (ruleset);
176 }
177 
178 struct pf_anchor *
179 pf_create_anchor(struct pf_anchor *parent, const char *aname)
180 {
181 	struct pf_anchor	*anchor, *dup;
182 
183 	if (!*aname || (strlen(aname) >= PF_ANCHOR_NAME_SIZE) ||
184 	    ((parent != NULL) && (strlen(parent->path) >= PF_ANCHOR_MAXPATH)))
185 		return (NULL);
186 
187 	anchor = rs_malloc(sizeof(*anchor));
188 	if (anchor == NULL)
189 		return (NULL);
190 
191 	RB_INIT(&anchor->children);
192 	strlcpy(anchor->name, aname, sizeof(anchor->name));
193 	if (parent != NULL) {
194 		/*
195 		 * Make sure path for levels 2, 3, ... is terminated by '/':
196 		 *	1/2/3/...
197 		 */
198 		strlcpy(anchor->path, parent->path, sizeof(anchor->path));
199 		strlcat(anchor->path, "/", sizeof(anchor->path));
200 	}
201 	strlcat(anchor->path, anchor->name, sizeof(anchor->path));
202 
203 	if ((dup = RB_INSERT(pf_anchor_global, &pf_anchors, anchor)) != NULL) {
204 		DPFPRINTF(LOG_NOTICE,
205 		    "%s: RB_INSERT to global '%s' '%s' collides with '%s' '%s'",
206 		    __func__, anchor->path, anchor->name, dup->path, dup->name);
207 		rs_free(anchor, sizeof(*anchor));
208 		return (NULL);
209 	}
210 
211 	if (parent != NULL) {
212 		anchor->parent = parent;
213 		dup = RB_INSERT(pf_anchor_node, &parent->children, anchor);
214 		if (dup != NULL) {
215 			DPFPRINTF(LOG_NOTICE,
216 			    "%s: RB_INSERT to parent '%s' '%s' collides with "
217 			    "'%s' '%s'", __func__, anchor->path, anchor->name,
218 			    dup->path, dup->name);
219 			RB_REMOVE(pf_anchor_global, &pf_anchors,
220 			    anchor);
221 			rs_free(anchor, sizeof(*anchor));
222 			return (NULL);
223 		}
224 	}
225 
226 	pf_init_ruleset(&anchor->ruleset);
227 	anchor->ruleset.anchor = anchor;
228 
229 	return (anchor);
230 }
231 
232 struct pf_ruleset *
233 pf_find_or_create_ruleset(const char *path)
234 {
235 	char			*p, *aname, *r;
236 	struct pf_ruleset	*ruleset;
237 	struct pf_anchor	*anchor;
238 
239 	if (path[0] == 0)
240 		return (&pf_main_ruleset);
241 
242 	while (*path == '/')
243 		path++;
244 
245 	ruleset = pf_find_ruleset(path);
246 	if (ruleset != NULL)
247 		return (ruleset);
248 
249 	p = rs_malloc(MAXPATHLEN);
250 	if (p == NULL)
251 		return (NULL);
252 	strlcpy(p, path, MAXPATHLEN);
253 
254 	ruleset = pf_get_leaf_ruleset(p, &aname);
255 	anchor = ruleset->anchor;
256 
257 	while (*aname == '/')
258 		aname++;
259 	/*
260 	 * aname is a path remainder, which contains nodes we must create.  We
261 	 * process the aname path from left to right, effectively descending
262 	 * from parents to children.
263 	 */
264 	while ((r = strchr(aname, '/')) != NULL || *aname) {
265 		if (r != NULL)
266 			*r = 0;
267 
268 		anchor = pf_create_anchor(anchor, aname);
269 		if (anchor == NULL) {
270 			rs_free(p, MAXPATHLEN);
271 			return (NULL);
272 		}
273 
274 		if (r == NULL)
275 			break;
276 		else
277 			aname = r + 1;
278 	}
279 
280 	rs_free(p, MAXPATHLEN);
281 	return (&anchor->ruleset);
282 }
283 
284 void
285 pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset)
286 {
287 	struct pf_anchor	*parent;
288 
289 	while (ruleset != NULL) {
290 		if (ruleset == &pf_main_ruleset ||
291 		    !RB_EMPTY(&ruleset->anchor->children) ||
292 		    ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
293 		    ruleset->topen)
294 			return;
295 		if (!TAILQ_EMPTY(ruleset->rules.active.ptr) ||
296 		    !TAILQ_EMPTY(ruleset->rules.inactive.ptr) ||
297 		    ruleset->rules.inactive.open)
298 			return;
299 		RB_REMOVE(pf_anchor_global, &pf_anchors, ruleset->anchor);
300 		if ((parent = ruleset->anchor->parent) != NULL)
301 			RB_REMOVE(pf_anchor_node, &parent->children,
302 			    ruleset->anchor);
303 		rs_free(ruleset->anchor, sizeof(*(ruleset->anchor)));
304 		if (parent == NULL)
305 			return;
306 		ruleset = &parent->ruleset;
307 	}
308 }
309 
310 int
311 pf_anchor_setup(struct pf_rule *r, const struct pf_ruleset *s,
312     const char *name)
313 {
314 	char			*p, *path;
315 	struct pf_ruleset	*ruleset;
316 
317 	r->anchor = NULL;
318 	r->anchor_relative = 0;
319 	r->anchor_wildcard = 0;
320 	if (!name[0])
321 		return (0);
322 	path = rs_malloc(MAXPATHLEN);
323 	if (path == NULL)
324 		return (1);
325 	if (name[0] == '/')
326 		strlcpy(path, name + 1, MAXPATHLEN);
327 	else {
328 		/* relative path */
329 		r->anchor_relative = 1;
330 		if (s->anchor == NULL || !s->anchor->path[0])
331 			path[0] = 0;
332 		else
333 			strlcpy(path, s->anchor->path, MAXPATHLEN);
334 		while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
335 			if (!path[0]) {
336 				DPFPRINTF(LOG_NOTICE,
337 				    "pf_anchor_setup: .. beyond root");
338 				rs_free(path, MAXPATHLEN);
339 				return (1);
340 			}
341 			if ((p = strrchr(path, '/')) != NULL)
342 				*p = 0;
343 			else
344 				path[0] = 0;
345 			r->anchor_relative++;
346 			name += 3;
347 		}
348 		if (path[0])
349 			strlcat(path, "/", MAXPATHLEN);
350 		strlcat(path, name, MAXPATHLEN);
351 	}
352 	if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
353 		r->anchor_wildcard = 1;
354 		*p = 0;
355 	}
356 	ruleset = pf_find_or_create_ruleset(path);
357 	rs_free(path, MAXPATHLEN);
358 	if (ruleset == NULL || ruleset == &pf_main_ruleset) {
359 		DPFPRINTF(LOG_NOTICE,
360 		    "pf_anchor_setup: ruleset");
361 		return (1);
362 	}
363 	r->anchor = ruleset->anchor;
364 	r->anchor->refcnt++;
365 	return (0);
366 }
367 
368 int
369 pf_anchor_copyout(const struct pf_ruleset *rs, const struct pf_rule *r,
370     struct pfioc_rule *pr)
371 {
372 	pr->anchor_call[0] = 0;
373 	if (r->anchor == NULL)
374 		return (0);
375 	if (!r->anchor_relative) {
376 		strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call));
377 		strlcat(pr->anchor_call, r->anchor->path,
378 		    sizeof(pr->anchor_call));
379 	} else {
380 		char	*a, *p;
381 		int	 i;
382 
383 		a = rs_malloc(MAXPATHLEN);
384 		if (a == NULL)
385 			return (1);
386 		if (rs == &pf_main_ruleset)
387 			a[0] = 0;
388 		else
389 			strlcpy(a, rs->anchor->path, MAXPATHLEN);
390 		for (i = 1; i < r->anchor_relative; ++i) {
391 			if ((p = strrchr(a, '/')) == NULL)
392 				p = a;
393 			*p = 0;
394 			strlcat(pr->anchor_call, "../",
395 			    sizeof(pr->anchor_call));
396 		}
397 		if (strncmp(a, r->anchor->path, strlen(a))) {
398 			DPFPRINTF(LOG_NOTICE,
399 			    "pf_anchor_copyout: '%s' '%s'", a,
400 			    r->anchor->path);
401 			rs_free(a, MAXPATHLEN);
402 			return (1);
403 		}
404 		if (strlen(r->anchor->path) > strlen(a))
405 			strlcat(pr->anchor_call, r->anchor->path + (a[0] ?
406 			    strlen(a) + 1 : 0), sizeof(pr->anchor_call));
407 		rs_free(a, MAXPATHLEN);
408 	}
409 	if (r->anchor_wildcard)
410 		strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*",
411 		    sizeof(pr->anchor_call));
412 	return (0);
413 }
414 
415 void
416 pf_remove_anchor(struct pf_rule *r)
417 {
418 	if (r->anchor == NULL)
419 		return;
420 	if (r->anchor->refcnt <= 0)
421 		DPFPRINTF(LOG_NOTICE, "pf_remove_anchor: broken refcount");
422 	else if (!--r->anchor->refcnt)
423 		pf_remove_if_empty_ruleset(&r->anchor->ruleset);
424 	r->anchor = NULL;
425 }
426