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/param.h>
40#include <sys/socket.h>
41#include <sys/mbuf.h>
42
43#include <netinet/in.h>
44#include <netinet/in_systm.h>
45#include <netinet/ip.h>
46#include <netinet/tcp.h>
47
48#include <net/if.h>
49#include <net/vnet.h>
50#include <net/pfvar.h>
51
52#ifdef INET6
53#include <netinet/ip6.h>
54#endif /* INET6 */
55
56#include <arpa/inet.h>
57#include <errno.h>
58#include <stdio.h>
59#include <stdlib.h>
60#include <string.h>
61#define rs_malloc(x)		 calloc(1, x)
62#define rs_free(x)		 free(x)
63
64#include "pfctl.h"
65#include "pfctl_parser.h"
66
67#ifdef PFDEBUG
68#include <sys/stdarg.h>
69#define DPFPRINTF(format, x...)	fprintf(stderr, format , ##x)
70#else
71#define DPFPRINTF(format, x...)	((void)0)
72#endif /* PFDEBUG */
73
74struct pfctl_anchor_global	 pf_anchors;
75extern struct pfctl_anchor	 pf_main_anchor;
76extern struct pfctl_eth_anchor	 pf_eth_main_anchor;
77#undef V_pf_anchors
78#define V_pf_anchors		 pf_anchors
79#undef pf_main_ruleset
80#define pf_main_ruleset		 pf_main_anchor.ruleset
81
82static __inline int		pf_anchor_compare(struct pfctl_anchor *,
83				    struct pfctl_anchor *);
84static struct pfctl_anchor	*pf_find_anchor(const char *);
85
86RB_GENERATE(pfctl_anchor_global, pfctl_anchor, entry_global,
87    pf_anchor_compare);
88RB_GENERATE(pfctl_anchor_node, pfctl_anchor, entry_node, pf_anchor_compare);
89
90static __inline int
91pf_anchor_compare(struct pfctl_anchor *a, struct pfctl_anchor *b)
92{
93	int c = strcmp(a->path, b->path);
94
95	return (c ? (c < 0 ? -1 : 1) : 0);
96}
97
98int
99pf_get_ruleset_number(u_int8_t action)
100{
101	switch (action) {
102	case PF_SCRUB:
103	case PF_NOSCRUB:
104		return (PF_RULESET_SCRUB);
105		break;
106	case PF_PASS:
107	case PF_DROP:
108	case PF_MATCH:
109		return (PF_RULESET_FILTER);
110		break;
111	case PF_NAT:
112	case PF_NONAT:
113		return (PF_RULESET_NAT);
114		break;
115	case PF_BINAT:
116	case PF_NOBINAT:
117		return (PF_RULESET_BINAT);
118		break;
119	case PF_RDR:
120	case PF_NORDR:
121		return (PF_RULESET_RDR);
122		break;
123	default:
124		return (PF_RULESET_MAX);
125		break;
126	}
127}
128
129void
130pf_init_ruleset(struct pfctl_ruleset *ruleset)
131{
132	int	i;
133
134	memset(ruleset, 0, sizeof(struct pfctl_ruleset));
135	for (i = 0; i < PF_RULESET_MAX; i++) {
136		TAILQ_INIT(&ruleset->rules[i].queues[0]);
137		TAILQ_INIT(&ruleset->rules[i].queues[1]);
138		ruleset->rules[i].active.ptr = &ruleset->rules[i].queues[0];
139		ruleset->rules[i].inactive.ptr = &ruleset->rules[i].queues[1];
140	}
141}
142
143static struct pfctl_anchor *
144pf_find_anchor(const char *path)
145{
146	struct pfctl_anchor	*key, *found;
147
148	key = (struct pfctl_anchor *)rs_malloc(sizeof(*key));
149	if (key == NULL)
150		return (NULL);
151	strlcpy(key->path, path, sizeof(key->path));
152	found = RB_FIND(pfctl_anchor_global, &V_pf_anchors, key);
153	rs_free(key);
154	return (found);
155}
156
157struct pfctl_ruleset *
158pf_find_ruleset(const char *path)
159{
160	struct pfctl_anchor	*anchor;
161
162	while (*path == '/')
163		path++;
164	if (!*path)
165		return (&pf_main_ruleset);
166	anchor = pf_find_anchor(path);
167	if (anchor == NULL)
168		return (NULL);
169	else
170		return (&anchor->ruleset);
171}
172
173struct pfctl_ruleset *
174pf_find_or_create_ruleset(const char *path)
175{
176	char			*p, *q, *r;
177	struct pfctl_ruleset	*ruleset;
178	struct pfctl_anchor	*anchor = NULL, *dup, *parent = NULL;
179
180	if (path[0] == 0)
181		return (&pf_main_ruleset);
182	while (*path == '/')
183		path++;
184	ruleset = pf_find_ruleset(path);
185	if (ruleset != NULL)
186		return (ruleset);
187	p = (char *)rs_malloc(MAXPATHLEN);
188	if (p == NULL)
189		return (NULL);
190	strlcpy(p, path, MAXPATHLEN);
191	while (parent == NULL && (q = strrchr(p, '/')) != NULL) {
192		*q = 0;
193		if ((ruleset = pf_find_ruleset(p)) != NULL) {
194			parent = ruleset->anchor;
195			break;
196		}
197	}
198	if (q == NULL)
199		q = p;
200	else
201		q++;
202	strlcpy(p, path, MAXPATHLEN);
203	if (!*q) {
204		rs_free(p);
205		return (NULL);
206	}
207	while ((r = strchr(q, '/')) != NULL || *q) {
208		if (r != NULL)
209			*r = 0;
210		if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE ||
211		    (parent != NULL && strlen(parent->path) >=
212		    MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) {
213			rs_free(p);
214			return (NULL);
215		}
216		anchor = (struct pfctl_anchor *)rs_malloc(sizeof(*anchor));
217		if (anchor == NULL) {
218			rs_free(p);
219			return (NULL);
220		}
221		RB_INIT(&anchor->children);
222		strlcpy(anchor->name, q, sizeof(anchor->name));
223		if (parent != NULL) {
224			strlcpy(anchor->path, parent->path,
225			    sizeof(anchor->path));
226			strlcat(anchor->path, "/", sizeof(anchor->path));
227		}
228		strlcat(anchor->path, anchor->name, sizeof(anchor->path));
229		if ((dup = RB_INSERT(pfctl_anchor_global, &V_pf_anchors, anchor)) !=
230		    NULL) {
231			printf("pf_find_or_create_ruleset: RB_INSERT1 "
232			    "'%s' '%s' collides with '%s' '%s'\n",
233			    anchor->path, anchor->name, dup->path, dup->name);
234			rs_free(anchor);
235			rs_free(p);
236			return (NULL);
237		}
238		if (parent != NULL) {
239			anchor->parent = parent;
240			if ((dup = RB_INSERT(pfctl_anchor_node, &parent->children,
241			    anchor)) != NULL) {
242				printf("pf_find_or_create_ruleset: "
243				    "RB_INSERT2 '%s' '%s' collides with "
244				    "'%s' '%s'\n", anchor->path, anchor->name,
245				    dup->path, dup->name);
246				RB_REMOVE(pfctl_anchor_global, &V_pf_anchors,
247				    anchor);
248				rs_free(anchor);
249				rs_free(p);
250				return (NULL);
251			}
252		}
253		pf_init_ruleset(&anchor->ruleset);
254		anchor->ruleset.anchor = anchor;
255		parent = anchor;
256		if (r != NULL)
257			q = r + 1;
258		else
259			*q = 0;
260	}
261	rs_free(p);
262	return (&anchor->ruleset);
263}
264
265void
266pf_remove_if_empty_ruleset(struct pfctl_ruleset *ruleset)
267{
268	struct pfctl_anchor	*parent;
269	int			 i;
270
271	while (ruleset != NULL) {
272		if (ruleset == &pf_main_ruleset || ruleset->anchor == NULL ||
273		    !RB_EMPTY(&ruleset->anchor->children) ||
274		    ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
275		    ruleset->topen)
276			return;
277		for (i = 0; i < PF_RULESET_MAX; ++i)
278			if (!TAILQ_EMPTY(ruleset->rules[i].active.ptr) ||
279			    !TAILQ_EMPTY(ruleset->rules[i].inactive.ptr) ||
280			    ruleset->rules[i].inactive.open)
281				return;
282		RB_REMOVE(pfctl_anchor_global, &V_pf_anchors, ruleset->anchor);
283		if ((parent = ruleset->anchor->parent) != NULL)
284			RB_REMOVE(pfctl_anchor_node, &parent->children,
285			    ruleset->anchor);
286		rs_free(ruleset->anchor);
287		if (parent == NULL)
288			return;
289		ruleset = &parent->ruleset;
290	}
291}
292
293void
294pf_remove_if_empty_eth_ruleset(struct pfctl_eth_ruleset *ruleset)
295{
296	struct pfctl_eth_anchor	*parent;
297
298	return;
299	while (ruleset != NULL) {
300		if (ruleset == &pf_eth_main_anchor.ruleset ||
301		    ruleset->anchor == NULL || ruleset->anchor->refcnt > 0)
302			return;
303		if (!TAILQ_EMPTY(&ruleset->rules))
304			return;
305		rs_free(ruleset->anchor);
306		if (parent == NULL)
307			return;
308		ruleset = &parent->ruleset;
309	}
310}
311
312void
313pf_init_eth_ruleset(struct pfctl_eth_ruleset *ruleset)
314{
315
316	memset(ruleset, 0, sizeof(*ruleset));
317	TAILQ_INIT(&ruleset->rules);
318}
319
320
321static struct pfctl_eth_anchor*
322_pf_find_eth_anchor(struct pfctl_eth_anchor *anchor, const char *path)
323{
324	struct pfctl_eth_rule	*r;
325	struct pfctl_eth_anchor	*a;
326
327	if (strcmp(path, anchor->path) == 0)
328		return (anchor);
329
330	TAILQ_FOREACH(r, &anchor->ruleset.rules, entries) {
331		if (! r->anchor)
332			continue;
333
334		/* Step into anchor */
335		a = _pf_find_eth_anchor(r->anchor, path);
336		if (a)
337			return (a);
338	}
339
340	return (NULL);
341}
342
343static struct pfctl_eth_anchor*
344pf_find_eth_anchor(const char *path)
345{
346	return (_pf_find_eth_anchor(&pf_eth_main_anchor, path));
347}
348
349static struct pfctl_eth_ruleset*
350pf_find_eth_ruleset(const char *path)
351{
352	struct pfctl_eth_anchor	*anchor;
353
354	while (*path == '/')
355		path++;
356	if (!*path)
357		return (&pf_eth_main_anchor.ruleset);
358	anchor = pf_find_eth_anchor(path);
359	if (anchor == NULL)
360		return (NULL);
361	else
362		return (&anchor->ruleset);
363}
364
365struct pfctl_eth_ruleset *
366pf_find_or_create_eth_ruleset(const char *path)
367{
368	char				*p, *q, *r;
369	struct pfctl_eth_ruleset	*ruleset;
370	struct pfctl_eth_anchor		*anchor = NULL, *parent = NULL;
371
372	if (path[0] == 0)
373		return (&pf_eth_main_anchor.ruleset);
374	while (*path == '/')
375		path++;
376	ruleset = pf_find_eth_ruleset(path);
377	if (ruleset != NULL)
378		return (ruleset);
379	p = (char *)rs_malloc(MAXPATHLEN);
380	if (p == NULL)
381		return (NULL);
382	strlcpy(p, path, MAXPATHLEN);
383	while (parent == NULL && (q = strrchr(p, '/')) != NULL) {
384		*q = 0;
385		if ((ruleset = pf_find_eth_ruleset(p)) != NULL) {
386			parent = ruleset->anchor;
387			break;
388		}
389	}
390	if (q == NULL)
391		q = p;
392	else
393		q++;
394	strlcpy(p, path, MAXPATHLEN);
395	if (!*q) {
396		rs_free(p);
397		return (NULL);
398	}
399	while ((r = strchr(q, '/')) != NULL || *q) {
400		if (r != NULL)
401			*r = 0;
402		if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE ||
403		    (parent != NULL && strlen(parent->path) >=
404		    MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) {
405			rs_free(p);
406			return (NULL);
407		}
408		anchor = (struct pfctl_eth_anchor *)rs_malloc(sizeof(*anchor));
409		if (anchor == NULL) {
410			rs_free(p);
411			return (NULL);
412		}
413		strlcpy(anchor->name, q, sizeof(anchor->name));
414		if (parent != NULL) {
415			strlcpy(anchor->path, parent->path,
416			    sizeof(anchor->path));
417			strlcat(anchor->path, "/", sizeof(anchor->path));
418		}
419		strlcat(anchor->path, anchor->name, sizeof(anchor->path));
420		if (parent != NULL)
421			anchor->parent = parent;
422		pf_init_eth_ruleset(&anchor->ruleset);
423		anchor->ruleset.anchor = anchor;
424		parent = anchor;
425		if (r != NULL)
426			q = r + 1;
427		else
428			*q = 0;
429	}
430	rs_free(p);
431	return (&anchor->ruleset);
432}
433
434int
435pfctl_anchor_setup(struct pfctl_rule *r, const struct pfctl_ruleset *s,
436    const char *name)
437{
438	char			*p, *path;
439	struct pfctl_ruleset	*ruleset;
440
441	r->anchor = NULL;
442	r->anchor_relative = 0;
443	r->anchor_wildcard = 0;
444	if (!name[0])
445		return (0);
446	path = (char *)rs_malloc(MAXPATHLEN);
447	if (path == NULL)
448		return (1);
449	if (name[0] == '/')
450		strlcpy(path, name + 1, MAXPATHLEN);
451	else {
452		/* relative path */
453		r->anchor_relative = 1;
454		if (s->anchor == NULL || !s->anchor->path[0])
455			path[0] = 0;
456		else
457			strlcpy(path, s->anchor->path, MAXPATHLEN);
458		while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
459			if (!path[0]) {
460				printf("pfctl_anchor_setup: .. beyond root\n");
461				rs_free(path);
462				return (1);
463			}
464			if ((p = strrchr(path, '/')) != NULL)
465				*p = 0;
466			else
467				path[0] = 0;
468			r->anchor_relative++;
469			name += 3;
470		}
471		if (path[0])
472			strlcat(path, "/", MAXPATHLEN);
473		strlcat(path, name, MAXPATHLEN);
474	}
475	if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
476		r->anchor_wildcard = 1;
477		*p = 0;
478	}
479	ruleset = pf_find_or_create_ruleset(path);
480	rs_free(path);
481	if (ruleset == NULL || ruleset->anchor == NULL) {
482		printf("pfctl_anchor_setup: ruleset\n");
483		return (1);
484	}
485	r->anchor = ruleset->anchor;
486	r->anchor->refcnt++;
487	return (0);
488}
489
490int
491pfctl_eth_anchor_setup(struct pfctl *pf, struct pfctl_eth_rule *r,
492    const struct pfctl_eth_ruleset *s, const char *name)
493{
494	char				*p, *path;
495	struct pfctl_eth_ruleset	*ruleset;
496
497	r->anchor = NULL;
498	if (!name[0])
499		return (0);
500	path = (char *)rs_malloc(MAXPATHLEN);
501	if (path == NULL)
502		return (1);
503	if (name[0] == '/')
504		strlcpy(path, name + 1, MAXPATHLEN);
505	else {
506		/* relative path */
507		if (s->anchor == NULL || !s->anchor->path[0])
508			path[0] = 0;
509		else
510			strlcpy(path, s->anchor->path, MAXPATHLEN);
511		while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
512			if (!path[0]) {
513				printf("%s: .. beyond root\n", __func__);
514				rs_free(path);
515				return (1);
516			}
517			if ((p = strrchr(path, '/')) != NULL)
518				*p = 0;
519			else
520				path[0] = 0;
521			name += 3;
522		}
523		if (path[0])
524			strlcat(path, "/", MAXPATHLEN);
525		strlcat(path, name, MAXPATHLEN);
526	}
527	if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
528		*p = 0;
529	}
530	ruleset = pf_find_or_create_eth_ruleset(path);
531	rs_free(path);
532	if (ruleset == NULL || ruleset->anchor == NULL) {
533		printf("%s: ruleset\n", __func__);
534		return (1);
535	}
536	r->anchor = ruleset->anchor;
537	r->anchor->refcnt++;
538	return (0);
539}
540