xref: /freebsd/sys/netpfil/pf/pf_ruleset.c (revision f9fd7337f63698f33239c58c07bf430198235a22)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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  *	$OpenBSD: pf_ruleset.c,v 1.2 2008/12/18 15:31:37 dhill Exp $
37  */
38 
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD$");
41 
42 #include <sys/param.h>
43 #include <sys/socket.h>
44 #ifdef _KERNEL
45 # include <sys/systm.h>
46 # include <sys/refcount.h>
47 #endif /* _KERNEL */
48 #include <sys/mbuf.h>
49 
50 #include <netinet/in.h>
51 #include <netinet/in_systm.h>
52 #include <netinet/ip.h>
53 #include <netinet/tcp.h>
54 
55 #include <net/if.h>
56 #include <net/vnet.h>
57 #include <net/pfvar.h>
58 
59 #ifdef INET6
60 #include <netinet/ip6.h>
61 #endif /* INET6 */
62 
63 #ifdef _KERNEL
64 #define DPFPRINTF(format, x...)				\
65 	if (V_pf_status.debug >= PF_DEBUG_NOISY)	\
66 		printf(format , ##x)
67 #define rs_malloc(x)		malloc(x, M_TEMP, M_NOWAIT|M_ZERO)
68 #define rs_free(x)		free(x, M_TEMP)
69 
70 #else
71 /* Userland equivalents so we can lend code to pfctl et al. */
72 
73 #include <arpa/inet.h>
74 #include <errno.h>
75 #include <stdio.h>
76 #include <stdlib.h>
77 #include <string.h>
78 #define rs_malloc(x)		 calloc(1, x)
79 #define rs_free(x)		 free(x)
80 
81 #ifdef PFDEBUG
82 #include <sys/stdarg.h>
83 #define DPFPRINTF(format, x...)	fprintf(stderr, format , ##x)
84 #else
85 #define DPFPRINTF(format, x...)	((void)0)
86 #endif /* PFDEBUG */
87 #endif /* _KERNEL */
88 
89 #ifdef _KERNEL
90 VNET_DEFINE(struct pf_anchor_global,	pf_anchors);
91 VNET_DEFINE(struct pf_anchor,		pf_main_anchor);
92 #else /* ! _KERNEL */
93 struct pf_anchor_global	 pf_anchors;
94 struct pf_anchor	 pf_main_anchor;
95 #undef V_pf_anchors
96 #define V_pf_anchors		 pf_anchors
97 #undef pf_main_ruleset
98 #define pf_main_ruleset		 pf_main_anchor.ruleset
99 #endif /* _KERNEL */
100 
101 static __inline int pf_anchor_compare(struct pf_anchor *, struct pf_anchor *);
102 
103 static struct pf_anchor		*pf_find_anchor(const char *);
104 
105 RB_GENERATE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare);
106 RB_GENERATE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare);
107 
108 static __inline int
109 pf_anchor_compare(struct pf_anchor *a, struct pf_anchor *b)
110 {
111 	int c = strcmp(a->path, b->path);
112 
113 	return (c ? (c < 0 ? -1 : 1) : 0);
114 }
115 
116 int
117 pf_get_ruleset_number(u_int8_t action)
118 {
119 	switch (action) {
120 	case PF_SCRUB:
121 	case PF_NOSCRUB:
122 		return (PF_RULESET_SCRUB);
123 		break;
124 	case PF_PASS:
125 	case PF_DROP:
126 		return (PF_RULESET_FILTER);
127 		break;
128 	case PF_NAT:
129 	case PF_NONAT:
130 		return (PF_RULESET_NAT);
131 		break;
132 	case PF_BINAT:
133 	case PF_NOBINAT:
134 		return (PF_RULESET_BINAT);
135 		break;
136 	case PF_RDR:
137 	case PF_NORDR:
138 		return (PF_RULESET_RDR);
139 		break;
140 	default:
141 		return (PF_RULESET_MAX);
142 		break;
143 	}
144 }
145 
146 void
147 pf_init_ruleset(struct pf_ruleset *ruleset)
148 {
149 	int	i;
150 
151 	memset(ruleset, 0, sizeof(struct pf_ruleset));
152 	for (i = 0; i < PF_RULESET_MAX; i++) {
153 		TAILQ_INIT(&ruleset->rules[i].queues[0]);
154 		TAILQ_INIT(&ruleset->rules[i].queues[1]);
155 		ruleset->rules[i].active.ptr = &ruleset->rules[i].queues[0];
156 		ruleset->rules[i].inactive.ptr = &ruleset->rules[i].queues[1];
157 	}
158 }
159 
160 static struct pf_anchor *
161 pf_find_anchor(const char *path)
162 {
163 	struct pf_anchor	*key, *found;
164 
165 	key = (struct pf_anchor *)rs_malloc(sizeof(*key));
166 	if (key == NULL)
167 		return (NULL);
168 	strlcpy(key->path, path, sizeof(key->path));
169 	found = RB_FIND(pf_anchor_global, &V_pf_anchors, key);
170 	rs_free(key);
171 	return (found);
172 }
173 
174 struct pf_ruleset *
175 pf_find_ruleset(const char *path)
176 {
177 	struct pf_anchor	*anchor;
178 
179 	while (*path == '/')
180 		path++;
181 	if (!*path)
182 		return (&pf_main_ruleset);
183 	anchor = pf_find_anchor(path);
184 	if (anchor == NULL)
185 		return (NULL);
186 	else
187 		return (&anchor->ruleset);
188 }
189 
190 struct pf_ruleset *
191 pf_find_or_create_ruleset(const char *path)
192 {
193 	char			*p, *q, *r;
194 	struct pf_ruleset	*ruleset;
195 	struct pf_anchor	*anchor = NULL, *dup, *parent = NULL;
196 
197 	if (path[0] == 0)
198 		return (&pf_main_ruleset);
199 	while (*path == '/')
200 		path++;
201 	ruleset = pf_find_ruleset(path);
202 	if (ruleset != NULL)
203 		return (ruleset);
204 	p = (char *)rs_malloc(MAXPATHLEN);
205 	if (p == NULL)
206 		return (NULL);
207 	strlcpy(p, path, MAXPATHLEN);
208 	while (parent == NULL && (q = strrchr(p, '/')) != NULL) {
209 		*q = 0;
210 		if ((ruleset = pf_find_ruleset(p)) != NULL) {
211 			parent = ruleset->anchor;
212 			break;
213 		}
214 	}
215 	if (q == NULL)
216 		q = p;
217 	else
218 		q++;
219 	strlcpy(p, path, MAXPATHLEN);
220 	if (!*q) {
221 		rs_free(p);
222 		return (NULL);
223 	}
224 	while ((r = strchr(q, '/')) != NULL || *q) {
225 		if (r != NULL)
226 			*r = 0;
227 		if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE ||
228 		    (parent != NULL && strlen(parent->path) >=
229 		    MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) {
230 			rs_free(p);
231 			return (NULL);
232 		}
233 		anchor = (struct pf_anchor *)rs_malloc(sizeof(*anchor));
234 		if (anchor == NULL) {
235 			rs_free(p);
236 			return (NULL);
237 		}
238 		RB_INIT(&anchor->children);
239 		strlcpy(anchor->name, q, sizeof(anchor->name));
240 		if (parent != NULL) {
241 			strlcpy(anchor->path, parent->path,
242 			    sizeof(anchor->path));
243 			strlcat(anchor->path, "/", sizeof(anchor->path));
244 		}
245 		strlcat(anchor->path, anchor->name, sizeof(anchor->path));
246 		if ((dup = RB_INSERT(pf_anchor_global, &V_pf_anchors, anchor)) !=
247 		    NULL) {
248 			printf("pf_find_or_create_ruleset: RB_INSERT1 "
249 			    "'%s' '%s' collides with '%s' '%s'\n",
250 			    anchor->path, anchor->name, dup->path, dup->name);
251 			rs_free(anchor);
252 			rs_free(p);
253 			return (NULL);
254 		}
255 		if (parent != NULL) {
256 			anchor->parent = parent;
257 			if ((dup = RB_INSERT(pf_anchor_node, &parent->children,
258 			    anchor)) != NULL) {
259 				printf("pf_find_or_create_ruleset: "
260 				    "RB_INSERT2 '%s' '%s' collides with "
261 				    "'%s' '%s'\n", anchor->path, anchor->name,
262 				    dup->path, dup->name);
263 				RB_REMOVE(pf_anchor_global, &V_pf_anchors,
264 				    anchor);
265 				rs_free(anchor);
266 				rs_free(p);
267 				return (NULL);
268 			}
269 		}
270 		pf_init_ruleset(&anchor->ruleset);
271 		anchor->ruleset.anchor = anchor;
272 		parent = anchor;
273 		if (r != NULL)
274 			q = r + 1;
275 		else
276 			*q = 0;
277 	}
278 	rs_free(p);
279 	return (&anchor->ruleset);
280 }
281 
282 void
283 pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset)
284 {
285 	struct pf_anchor	*parent;
286 	int			 i;
287 
288 	while (ruleset != NULL) {
289 		if (ruleset == &pf_main_ruleset || ruleset->anchor == NULL ||
290 		    !RB_EMPTY(&ruleset->anchor->children) ||
291 		    ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
292 		    ruleset->topen)
293 			return;
294 		for (i = 0; i < PF_RULESET_MAX; ++i)
295 			if (!TAILQ_EMPTY(ruleset->rules[i].active.ptr) ||
296 			    !TAILQ_EMPTY(ruleset->rules[i].inactive.ptr) ||
297 			    ruleset->rules[i].inactive.open)
298 				return;
299 		RB_REMOVE(pf_anchor_global, &V_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);
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 = (char *)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 				printf("pf_anchor_setup: .. beyond root\n");
337 				rs_free(path);
338 				return (1);
339 			}
340 			if ((p = strrchr(path, '/')) != NULL)
341 				*p = 0;
342 			else
343 				path[0] = 0;
344 			r->anchor_relative++;
345 			name += 3;
346 		}
347 		if (path[0])
348 			strlcat(path, "/", MAXPATHLEN);
349 		strlcat(path, name, MAXPATHLEN);
350 	}
351 	if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
352 		r->anchor_wildcard = 1;
353 		*p = 0;
354 	}
355 	ruleset = pf_find_or_create_ruleset(path);
356 	rs_free(path);
357 	if (ruleset == NULL || ruleset->anchor == NULL) {
358 		printf("pf_anchor_setup: ruleset\n");
359 		return (1);
360 	}
361 	r->anchor = ruleset->anchor;
362 	r->anchor->refcnt++;
363 	return (0);
364 }
365 
366 int
367 pf_anchor_copyout(const struct pf_ruleset *rs, const struct pf_rule *r,
368     struct pfioc_rule *pr)
369 {
370 	pr->anchor_call[0] = 0;
371 	if (r->anchor == NULL)
372 		return (0);
373 	if (!r->anchor_relative) {
374 		strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call));
375 		strlcat(pr->anchor_call, r->anchor->path,
376 		    sizeof(pr->anchor_call));
377 	} else {
378 		char	*a, *p;
379 		int	 i;
380 
381 		a = (char *)rs_malloc(MAXPATHLEN);
382 		if (a == NULL)
383 			return (1);
384 		if (rs->anchor == NULL)
385 			a[0] = 0;
386 		else
387 			strlcpy(a, rs->anchor->path, MAXPATHLEN);
388 		for (i = 1; i < r->anchor_relative; ++i) {
389 			if ((p = strrchr(a, '/')) == NULL)
390 				p = a;
391 			*p = 0;
392 			strlcat(pr->anchor_call, "../",
393 			    sizeof(pr->anchor_call));
394 		}
395 		if (strncmp(a, r->anchor->path, strlen(a))) {
396 			printf("pf_anchor_copyout: '%s' '%s'\n", a,
397 			    r->anchor->path);
398 			rs_free(a);
399 			return (1);
400 		}
401 		if (strlen(r->anchor->path) > strlen(a))
402 			strlcat(pr->anchor_call, r->anchor->path + (a[0] ?
403 			    strlen(a) + 1 : 0), sizeof(pr->anchor_call));
404 		rs_free(a);
405 	}
406 	if (r->anchor_wildcard)
407 		strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*",
408 		    sizeof(pr->anchor_call));
409 	return (0);
410 }
411 
412 void
413 pf_anchor_remove(struct pf_rule *r)
414 {
415 	if (r->anchor == NULL)
416 		return;
417 	if (r->anchor->refcnt <= 0) {
418 		printf("pf_anchor_remove: broken refcount\n");
419 		r->anchor = NULL;
420 		return;
421 	}
422 	if (!--r->anchor->refcnt)
423 		pf_remove_if_empty_ruleset(&r->anchor->ruleset);
424 	r->anchor = NULL;
425 }
426