1 /* $OpenBSD: pf_ruleset.c,v 1.21 2023/06/30 09:58:30 mvs 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 #include <sys/pool.h>
44 #endif /* _KERNEL */
45 #include <sys/syslog.h>
46
47 #include <netinet/in.h>
48 #include <netinet/ip.h>
49 #include <netinet/tcp.h>
50
51 #include <net/if.h>
52 #include <net/pfvar.h>
53
54 #ifdef INET6
55 #include <netinet/ip6.h>
56 #endif /* INET6 */
57
58
59 #ifdef _KERNEL
60 #define rs_malloc(x) malloc(x, M_PF, M_WAITOK|M_CANFAIL|M_ZERO)
61 #define rs_free(x, siz) free(x, M_PF, siz)
62 #define rs_pool_get_anchor() pool_get(&pf_anchor_pl, \
63 PR_WAITOK|PR_LIMITFAIL|PR_ZERO)
64 #define rs_pool_put_anchor(x) pool_put(&pf_anchor_pl, x)
65
66 struct pool pf_anchor_pl;
67
68 #else /* !_KERNEL */
69 /* Userland equivalents so we can lend code to pfctl et al. */
70
71 #include <arpa/inet.h>
72 #include <errno.h>
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #define rs_malloc(x) calloc(1, x)
77 #define rs_free(x, siz) freezero(x, siz)
78 #define rs_pool_get_anchor() calloc(1, sizeof(struct pf_anchor))
79 #define rs_pool_put_anchor(x) freezero(x, sizeof(struct pf_anchor))
80
81 #ifdef PFDEBUG
82 #include <sys/stdarg.h> /* for DPFPRINTF() */
83 #endif /* PFDEBUG */
84 #endif /* _KERNEL */
85
86
87 struct pf_anchor_global pf_anchors;
88 struct pf_anchor pf_main_anchor;
89
90 static __inline int pf_anchor_compare(struct pf_anchor *, struct pf_anchor *);
91
92 RB_GENERATE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare);
93 RB_GENERATE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare);
94
95 static __inline int
pf_anchor_compare(struct pf_anchor * a,struct pf_anchor * b)96 pf_anchor_compare(struct pf_anchor *a, struct pf_anchor *b)
97 {
98 int c = strcmp(a->path, b->path);
99
100 return (c ? (c < 0 ? -1 : 1) : 0);
101 }
102
103 void
pf_init_ruleset(struct pf_ruleset * ruleset)104 pf_init_ruleset(struct pf_ruleset *ruleset)
105 {
106 memset(ruleset, 0, sizeof(struct pf_ruleset));
107 TAILQ_INIT(&ruleset->rules.queues[0]);
108 TAILQ_INIT(&ruleset->rules.queues[1]);
109 ruleset->rules.active.ptr = &ruleset->rules.queues[0];
110 ruleset->rules.inactive.ptr = &ruleset->rules.queues[1];
111 }
112
113 struct pf_anchor *
pf_find_anchor(const char * path)114 pf_find_anchor(const char *path)
115 {
116 struct pf_anchor *key, *found;
117
118 key = rs_malloc(sizeof(*key));
119 if (key == NULL)
120 return (NULL);
121 strlcpy(key->path, path, sizeof(key->path));
122 found = RB_FIND(pf_anchor_global, &pf_anchors, key);
123 rs_free(key, sizeof(*key));
124 return (found);
125 }
126
127 struct pf_ruleset *
pf_find_ruleset(const char * path)128 pf_find_ruleset(const char *path)
129 {
130 struct pf_anchor *anchor;
131
132 while (*path == '/')
133 path++;
134 if (!*path)
135 return (&pf_main_ruleset);
136 anchor = pf_find_anchor(path);
137 if (anchor == NULL)
138 return (NULL);
139 else
140 return (&anchor->ruleset);
141 }
142
143 struct pf_ruleset *
pf_get_leaf_ruleset(char * path,char ** path_remainder)144 pf_get_leaf_ruleset(char *path, char **path_remainder)
145 {
146 struct pf_ruleset *ruleset;
147 char *leaf, *p;
148 int i = 0;
149
150 p = path;
151 while (*p == '/')
152 p++;
153
154 ruleset = pf_find_ruleset(p);
155 leaf = p;
156 while (ruleset == NULL) {
157 leaf = strrchr(p, '/');
158 if (leaf != NULL) {
159 *leaf = '\0';
160 i++;
161 ruleset = pf_find_ruleset(p);
162 } else {
163 leaf = path;
164 /*
165 * if no path component exists, then main ruleset is
166 * our parent.
167 */
168 ruleset = &pf_main_ruleset;
169 }
170 }
171
172 if (path_remainder != NULL)
173 *path_remainder = leaf;
174
175 /* restore slashes in path. */
176 while (i != 0) {
177 while (*leaf != '\0')
178 leaf++;
179 *leaf = '/';
180 i--;
181 }
182
183 return (ruleset);
184 }
185
186 struct pf_anchor *
pf_create_anchor(struct pf_anchor * parent,const char * aname)187 pf_create_anchor(struct pf_anchor *parent, const char *aname)
188 {
189 struct pf_anchor *anchor, *dup;
190
191 if (!*aname || (strlen(aname) >= PF_ANCHOR_NAME_SIZE) ||
192 ((parent != NULL) && (strlen(parent->path) >= PF_ANCHOR_MAXPATH)))
193 return (NULL);
194
195 anchor = rs_pool_get_anchor();
196 if (anchor == NULL)
197 return (NULL);
198
199 RB_INIT(&anchor->children);
200 strlcpy(anchor->name, aname, sizeof(anchor->name));
201 if (parent != NULL) {
202 /*
203 * Make sure path for levels 2, 3, ... is terminated by '/':
204 * 1/2/3/...
205 */
206 strlcpy(anchor->path, parent->path, sizeof(anchor->path));
207 strlcat(anchor->path, "/", sizeof(anchor->path));
208 }
209 strlcat(anchor->path, anchor->name, sizeof(anchor->path));
210
211 if ((dup = RB_INSERT(pf_anchor_global, &pf_anchors, anchor)) != NULL) {
212 DPFPRINTF(LOG_NOTICE,
213 "%s: RB_INSERT to global '%s' '%s' collides with '%s' '%s'",
214 __func__, anchor->path, anchor->name, dup->path, dup->name);
215 rs_pool_put_anchor(anchor);
216 return (NULL);
217 }
218
219 if (parent != NULL) {
220 anchor->parent = parent;
221 dup = RB_INSERT(pf_anchor_node, &parent->children, anchor);
222 if (dup != NULL) {
223 DPFPRINTF(LOG_NOTICE,
224 "%s: RB_INSERT to parent '%s' '%s' collides with "
225 "'%s' '%s'", __func__, anchor->path, anchor->name,
226 dup->path, dup->name);
227 RB_REMOVE(pf_anchor_global, &pf_anchors,
228 anchor);
229 rs_pool_put_anchor(anchor);
230 return (NULL);
231 }
232 }
233
234 pf_init_ruleset(&anchor->ruleset);
235 anchor->ruleset.anchor = anchor;
236 #ifdef _KERNEL
237 refcnt_init(&anchor->ref);
238 #endif
239
240 return (anchor);
241 }
242
243 struct pf_ruleset *
pf_find_or_create_ruleset(const char * path)244 pf_find_or_create_ruleset(const char *path)
245 {
246 char *p, *aname, *r;
247 struct pf_ruleset *ruleset;
248 struct pf_anchor *anchor;
249
250 if (path[0] == 0)
251 return (&pf_main_ruleset);
252
253 while (*path == '/')
254 path++;
255
256 ruleset = pf_find_ruleset(path);
257 if (ruleset != NULL)
258 return (ruleset);
259
260 p = rs_malloc(MAXPATHLEN);
261 if (p == NULL)
262 return (NULL);
263 strlcpy(p, path, MAXPATHLEN);
264
265 ruleset = pf_get_leaf_ruleset(p, &aname);
266 anchor = ruleset->anchor;
267
268 while (*aname == '/')
269 aname++;
270 /*
271 * aname is a path remainder, which contains nodes we must create. We
272 * process the aname path from left to right, effectively descending
273 * from parents to children.
274 */
275 while ((r = strchr(aname, '/')) != NULL || *aname) {
276 if (r != NULL)
277 *r = 0;
278
279 anchor = pf_create_anchor(anchor, aname);
280 if (anchor == NULL) {
281 rs_free(p, MAXPATHLEN);
282 return (NULL);
283 }
284
285 if (r == NULL)
286 break;
287 else
288 aname = r + 1;
289 }
290
291 rs_free(p, MAXPATHLEN);
292 return (&anchor->ruleset);
293 }
294
295 void
pf_remove_if_empty_ruleset(struct pf_ruleset * ruleset)296 pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset)
297 {
298 struct pf_anchor *parent;
299
300 while (ruleset != NULL) {
301 if (ruleset == &pf_main_ruleset ||
302 !RB_EMPTY(&ruleset->anchor->children) ||
303 ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
304 ruleset->topen)
305 return;
306 if (!TAILQ_EMPTY(ruleset->rules.active.ptr) ||
307 !TAILQ_EMPTY(ruleset->rules.inactive.ptr) ||
308 ruleset->rules.inactive.open)
309 return;
310 RB_REMOVE(pf_anchor_global, &pf_anchors, ruleset->anchor);
311 if ((parent = ruleset->anchor->parent) != NULL)
312 RB_REMOVE(pf_anchor_node, &parent->children,
313 ruleset->anchor);
314 pf_anchor_rele(ruleset->anchor);
315 if (parent == NULL)
316 return;
317 ruleset = &parent->ruleset;
318 }
319 }
320
321 int
pf_anchor_setup(struct pf_rule * r,const struct pf_ruleset * s,const char * name)322 pf_anchor_setup(struct pf_rule *r, const struct pf_ruleset *s,
323 const char *name)
324 {
325 char *p, *path;
326 struct pf_ruleset *ruleset;
327
328 r->anchor = NULL;
329 r->anchor_relative = 0;
330 r->anchor_wildcard = 0;
331 if (!name[0])
332 return (0);
333 path = rs_malloc(MAXPATHLEN);
334 if (path == NULL)
335 return (1);
336 if (name[0] == '/')
337 strlcpy(path, name + 1, MAXPATHLEN);
338 else {
339 /* relative path */
340 r->anchor_relative = 1;
341 if (s->anchor == NULL || !s->anchor->path[0])
342 path[0] = 0;
343 else
344 strlcpy(path, s->anchor->path, MAXPATHLEN);
345 while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
346 if (!path[0]) {
347 DPFPRINTF(LOG_NOTICE,
348 "pf_anchor_setup: .. beyond root");
349 rs_free(path, MAXPATHLEN);
350 return (1);
351 }
352 if ((p = strrchr(path, '/')) != NULL)
353 *p = 0;
354 else
355 path[0] = 0;
356 r->anchor_relative++;
357 name += 3;
358 }
359 if (path[0])
360 strlcat(path, "/", MAXPATHLEN);
361 strlcat(path, name, MAXPATHLEN);
362 }
363 if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
364 r->anchor_wildcard = 1;
365 *p = 0;
366 }
367 ruleset = pf_find_or_create_ruleset(path);
368 rs_free(path, MAXPATHLEN);
369 if (ruleset == NULL || ruleset == &pf_main_ruleset) {
370 DPFPRINTF(LOG_NOTICE,
371 "pf_anchor_setup: ruleset");
372 return (1);
373 }
374 r->anchor = ruleset->anchor;
375 r->anchor->refcnt++;
376 return (0);
377 }
378
379 int
pf_anchor_copyout(const struct pf_ruleset * rs,const struct pf_rule * r,struct pfioc_rule * pr)380 pf_anchor_copyout(const struct pf_ruleset *rs, const struct pf_rule *r,
381 struct pfioc_rule *pr)
382 {
383 pr->anchor_call[0] = 0;
384 if (r->anchor == NULL)
385 return (0);
386 if (!r->anchor_relative) {
387 strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call));
388 strlcat(pr->anchor_call, r->anchor->path,
389 sizeof(pr->anchor_call));
390 } else {
391 char *a, *p;
392 int i;
393
394 a = rs_malloc(MAXPATHLEN);
395 if (a == NULL)
396 return (1);
397 if (rs == &pf_main_ruleset)
398 a[0] = 0;
399 else
400 strlcpy(a, rs->anchor->path, MAXPATHLEN);
401 for (i = 1; i < r->anchor_relative; ++i) {
402 if ((p = strrchr(a, '/')) == NULL)
403 p = a;
404 *p = 0;
405 strlcat(pr->anchor_call, "../",
406 sizeof(pr->anchor_call));
407 }
408 if (strncmp(a, r->anchor->path, strlen(a))) {
409 DPFPRINTF(LOG_NOTICE,
410 "pf_anchor_copyout: '%s' '%s'", a,
411 r->anchor->path);
412 rs_free(a, MAXPATHLEN);
413 return (1);
414 }
415 if (strlen(r->anchor->path) > strlen(a))
416 strlcat(pr->anchor_call, r->anchor->path + (a[0] ?
417 strlen(a) + 1 : 0), sizeof(pr->anchor_call));
418 rs_free(a, MAXPATHLEN);
419 }
420 if (r->anchor_wildcard)
421 strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*",
422 sizeof(pr->anchor_call));
423 return (0);
424 }
425
426 void
pf_remove_anchor(struct pf_rule * r)427 pf_remove_anchor(struct pf_rule *r)
428 {
429 if (r->anchor == NULL)
430 return;
431 if (r->anchor->refcnt <= 0)
432 DPFPRINTF(LOG_NOTICE, "pf_remove_anchor: broken refcount");
433 else if (!--r->anchor->refcnt)
434 pf_remove_if_empty_ruleset(&r->anchor->ruleset);
435 r->anchor = NULL;
436 }
437
438 void
pf_anchor_rele(struct pf_anchor * anchor)439 pf_anchor_rele(struct pf_anchor *anchor)
440 {
441 if ((anchor == NULL) || (anchor == &pf_main_anchor))
442 return;
443
444 #ifdef _KERNEL
445 if (refcnt_rele(&anchor->ref))
446 rs_pool_put_anchor(anchor);
447 #else
448 rs_pool_put_anchor(anchor);
449 #endif
450 }
451
452 struct pf_anchor *
pf_anchor_take(struct pf_anchor * anchor)453 pf_anchor_take(struct pf_anchor *anchor)
454 {
455 #ifdef _KERNEL
456 if (anchor != NULL && anchor != &pf_main_anchor)
457 refcnt_take(&anchor->ref);
458 #endif
459 return (anchor);
460 }
461