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#include <sys/systm.h>
45#include <sys/refcount.h>
46#include <sys/mbuf.h>
47
48#include <netinet/in.h>
49#include <netinet/in_systm.h>
50#include <netinet/ip.h>
51#include <netinet/tcp.h>
52
53#include <net/if.h>
54#include <net/vnet.h>
55#include <net/pfvar.h>
56
57#ifdef INET6
58#include <netinet/ip6.h>
59#endif /* INET6 */
60
61#ifndef _KERNEL
62#error "Kernel only file. Please use sbin/pfctl/pf_ruleset.c instead."
63#endif
64
65#define DPFPRINTF(format, x...)				\
66	if (V_pf_status.debug >= PF_DEBUG_NOISY)	\
67		printf(format , ##x)
68#define rs_malloc(x)		malloc(x, M_TEMP, M_NOWAIT|M_ZERO)
69#define rs_free(x)		free(x, M_TEMP)
70
71VNET_DEFINE(struct pf_kanchor_global,	pf_anchors);
72VNET_DEFINE(struct pf_kanchor,		pf_main_anchor);
73
74static __inline int		pf_kanchor_compare(struct pf_kanchor *,
75				    struct pf_kanchor *);
76static struct pf_kanchor	*pf_find_kanchor(const char *);
77
78RB_GENERATE(pf_kanchor_global, pf_kanchor, entry_global, pf_kanchor_compare);
79RB_GENERATE(pf_kanchor_node, pf_kanchor, entry_node, pf_kanchor_compare);
80
81static __inline int
82pf_kanchor_compare(struct pf_kanchor *a, struct pf_kanchor *b)
83{
84	int c = strcmp(a->path, b->path);
85
86	return (c ? (c < 0 ? -1 : 1) : 0);
87}
88
89int
90pf_get_ruleset_number(u_int8_t action)
91{
92	switch (action) {
93	case PF_SCRUB:
94	case PF_NOSCRUB:
95		return (PF_RULESET_SCRUB);
96		break;
97	case PF_PASS:
98	case PF_DROP:
99		return (PF_RULESET_FILTER);
100		break;
101	case PF_NAT:
102	case PF_NONAT:
103		return (PF_RULESET_NAT);
104		break;
105	case PF_BINAT:
106	case PF_NOBINAT:
107		return (PF_RULESET_BINAT);
108		break;
109	case PF_RDR:
110	case PF_NORDR:
111		return (PF_RULESET_RDR);
112		break;
113	default:
114		return (PF_RULESET_MAX);
115		break;
116	}
117}
118
119static struct pf_kanchor *
120pf_find_kanchor(const char *path)
121{
122	struct pf_kanchor	*key, *found;
123
124	key = (struct pf_kanchor *)rs_malloc(sizeof(*key));
125	if (key == NULL)
126		return (NULL);
127	strlcpy(key->path, path, sizeof(key->path));
128	found = RB_FIND(pf_kanchor_global, &V_pf_anchors, key);
129	rs_free(key);
130	return (found);
131}
132
133void
134pf_init_kruleset(struct pf_kruleset *ruleset)
135{
136	int	i;
137
138	memset(ruleset, 0, sizeof(struct pf_kruleset));
139	for (i = 0; i < PF_RULESET_MAX; i++) {
140		TAILQ_INIT(&ruleset->rules[i].queues[0]);
141		TAILQ_INIT(&ruleset->rules[i].queues[1]);
142		ruleset->rules[i].active.ptr = &ruleset->rules[i].queues[0];
143		ruleset->rules[i].inactive.ptr = &ruleset->rules[i].queues[1];
144	}
145}
146
147struct pf_kruleset *
148pf_find_kruleset(const char *path)
149{
150	struct pf_kanchor	*anchor;
151
152	while (*path == '/')
153		path++;
154	if (!*path)
155		return (&pf_main_ruleset);
156	anchor = pf_find_kanchor(path);
157	if (anchor == NULL)
158		return (NULL);
159	else
160		return (&anchor->ruleset);
161}
162
163struct pf_kruleset *
164pf_find_or_create_kruleset(const char *path)
165{
166	char			*p, *q, *r;
167	struct pf_kruleset	*ruleset;
168	struct pf_kanchor	*anchor = NULL, *dup, *parent = NULL;
169
170	if (path[0] == 0)
171		return (&pf_main_ruleset);
172	while (*path == '/')
173		path++;
174	ruleset = pf_find_kruleset(path);
175	if (ruleset != NULL)
176		return (ruleset);
177	p = (char *)rs_malloc(MAXPATHLEN);
178	if (p == NULL)
179		return (NULL);
180	strlcpy(p, path, MAXPATHLEN);
181	while (parent == NULL && (q = strrchr(p, '/')) != NULL) {
182		*q = 0;
183		if ((ruleset = pf_find_kruleset(p)) != NULL) {
184			parent = ruleset->anchor;
185			break;
186		}
187	}
188	if (q == NULL)
189		q = p;
190	else
191		q++;
192	strlcpy(p, path, MAXPATHLEN);
193	if (!*q) {
194		rs_free(p);
195		return (NULL);
196	}
197	while ((r = strchr(q, '/')) != NULL || *q) {
198		if (r != NULL)
199			*r = 0;
200		if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE ||
201		    (parent != NULL && strlen(parent->path) >=
202		    MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) {
203			rs_free(p);
204			return (NULL);
205		}
206		anchor = (struct pf_kanchor *)rs_malloc(sizeof(*anchor));
207		if (anchor == NULL) {
208			rs_free(p);
209			return (NULL);
210		}
211		RB_INIT(&anchor->children);
212		strlcpy(anchor->name, q, sizeof(anchor->name));
213		if (parent != NULL) {
214			strlcpy(anchor->path, parent->path,
215			    sizeof(anchor->path));
216			strlcat(anchor->path, "/", sizeof(anchor->path));
217		}
218		strlcat(anchor->path, anchor->name, sizeof(anchor->path));
219		if ((dup = RB_INSERT(pf_kanchor_global, &V_pf_anchors, anchor)) !=
220		    NULL) {
221			printf("pf_find_or_create_ruleset: RB_INSERT1 "
222			    "'%s' '%s' collides with '%s' '%s'\n",
223			    anchor->path, anchor->name, dup->path, dup->name);
224			rs_free(anchor);
225			rs_free(p);
226			return (NULL);
227		}
228		if (parent != NULL) {
229			anchor->parent = parent;
230			if ((dup = RB_INSERT(pf_kanchor_node, &parent->children,
231			    anchor)) != NULL) {
232				printf("pf_find_or_create_ruleset: "
233				    "RB_INSERT2 '%s' '%s' collides with "
234				    "'%s' '%s'\n", anchor->path, anchor->name,
235				    dup->path, dup->name);
236				RB_REMOVE(pf_kanchor_global, &V_pf_anchors,
237				    anchor);
238				rs_free(anchor);
239				rs_free(p);
240				return (NULL);
241			}
242		}
243		pf_init_kruleset(&anchor->ruleset);
244		anchor->ruleset.anchor = anchor;
245		parent = anchor;
246		if (r != NULL)
247			q = r + 1;
248		else
249			*q = 0;
250	}
251	rs_free(p);
252	return (&anchor->ruleset);
253}
254
255void
256pf_remove_if_empty_kruleset(struct pf_kruleset *ruleset)
257{
258	struct pf_kanchor	*parent;
259	int			 i;
260
261	while (ruleset != NULL) {
262		if (ruleset == &pf_main_ruleset || ruleset->anchor == NULL ||
263		    !RB_EMPTY(&ruleset->anchor->children) ||
264		    ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
265		    ruleset->topen)
266			return;
267		for (i = 0; i < PF_RULESET_MAX; ++i)
268			if (!TAILQ_EMPTY(ruleset->rules[i].active.ptr) ||
269			    !TAILQ_EMPTY(ruleset->rules[i].inactive.ptr) ||
270			    ruleset->rules[i].inactive.open)
271				return;
272		RB_REMOVE(pf_kanchor_global, &V_pf_anchors, ruleset->anchor);
273		if ((parent = ruleset->anchor->parent) != NULL)
274			RB_REMOVE(pf_kanchor_node, &parent->children,
275			    ruleset->anchor);
276		rs_free(ruleset->anchor);
277		if (parent == NULL)
278			return;
279		ruleset = &parent->ruleset;
280	}
281}
282
283int
284pf_kanchor_setup(struct pf_krule *r, const struct pf_kruleset *s,
285    const char *name)
286{
287	char			*p, *path;
288	struct pf_kruleset	*ruleset;
289
290	r->anchor = NULL;
291	r->anchor_relative = 0;
292	r->anchor_wildcard = 0;
293	if (!name[0])
294		return (0);
295	path = (char *)rs_malloc(MAXPATHLEN);
296	if (path == NULL)
297		return (1);
298	if (name[0] == '/')
299		strlcpy(path, name + 1, MAXPATHLEN);
300	else {
301		/* relative path */
302		r->anchor_relative = 1;
303		if (s->anchor == NULL || !s->anchor->path[0])
304			path[0] = 0;
305		else
306			strlcpy(path, s->anchor->path, MAXPATHLEN);
307		while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
308			if (!path[0]) {
309				DPFPRINTF("pf_anchor_setup: .. beyond root\n");
310				rs_free(path);
311				return (1);
312			}
313			if ((p = strrchr(path, '/')) != NULL)
314				*p = 0;
315			else
316				path[0] = 0;
317			r->anchor_relative++;
318			name += 3;
319		}
320		if (path[0])
321			strlcat(path, "/", MAXPATHLEN);
322		strlcat(path, name, MAXPATHLEN);
323	}
324	if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
325		r->anchor_wildcard = 1;
326		*p = 0;
327	}
328	ruleset = pf_find_or_create_kruleset(path);
329	rs_free(path);
330	if (ruleset == NULL || ruleset->anchor == NULL) {
331		DPFPRINTF("pf_anchor_setup: ruleset\n");
332		return (1);
333	}
334	r->anchor = ruleset->anchor;
335	r->anchor->refcnt++;
336	return (0);
337}
338
339int
340pf_kanchor_nvcopyout(const struct pf_kruleset *rs, const struct pf_krule *r,
341    nvlist_t *nvl)
342{
343	char anchor_call[MAXPATHLEN] = { 0 };
344
345	if (r->anchor == NULL)
346		goto done;
347	if (!r->anchor_relative) {
348		strlcpy(anchor_call, "/", sizeof(anchor_call));
349		strlcat(anchor_call, r->anchor->path,
350		    sizeof(anchor_call));
351	} else {
352		char	 a[MAXPATHLEN];
353		char	*p;
354		int	 i;
355		if (rs->anchor == NULL)
356			a[0] = 0;
357		else
358			strlcpy(a, rs->anchor->path, MAXPATHLEN);
359		for (i = 1; i < r->anchor_relative; ++i) {
360			if ((p = strrchr(a, '/')) == NULL)
361				p = a;
362			*p = 0;
363			strlcat(anchor_call, "../",
364			    sizeof(anchor_call));
365		}
366		if (strncmp(a, r->anchor->path, strlen(a))) {
367			printf("pf_anchor_copyout: '%s' '%s'\n", a,
368			    r->anchor->path);
369			return (1);
370		}
371		if (strlen(r->anchor->path) > strlen(a))
372			strlcat(anchor_call, r->anchor->path + (a[0] ?
373			    strlen(a) + 1 : 0), sizeof(anchor_call));
374
375	}
376	if (r->anchor_wildcard)
377		strlcat(anchor_call, anchor_call[0] ? "/*" : "*",
378		    sizeof(anchor_call));
379
380done:
381	nvlist_add_string(nvl, "anchor_call", anchor_call);
382
383	return (0);
384}
385
386int
387pf_kanchor_copyout(const struct pf_kruleset *rs, const struct pf_krule *r,
388    struct pfioc_rule *pr)
389{
390	pr->anchor_call[0] = 0;
391	if (r->anchor == NULL)
392		return (0);
393	if (!r->anchor_relative) {
394		strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call));
395		strlcat(pr->anchor_call, r->anchor->path,
396		    sizeof(pr->anchor_call));
397	} else {
398		char	*a, *p;
399		int	 i;
400
401		a = (char *)rs_malloc(MAXPATHLEN);
402		if (a == NULL)
403			return (1);
404		if (rs->anchor == NULL)
405			a[0] = 0;
406		else
407			strlcpy(a, rs->anchor->path, MAXPATHLEN);
408		for (i = 1; i < r->anchor_relative; ++i) {
409			if ((p = strrchr(a, '/')) == NULL)
410				p = a;
411			*p = 0;
412			strlcat(pr->anchor_call, "../",
413			    sizeof(pr->anchor_call));
414		}
415		if (strncmp(a, r->anchor->path, strlen(a))) {
416			printf("pf_anchor_copyout: '%s' '%s'\n", a,
417			    r->anchor->path);
418			rs_free(a);
419			return (1);
420		}
421		if (strlen(r->anchor->path) > strlen(a))
422			strlcat(pr->anchor_call, r->anchor->path + (a[0] ?
423			    strlen(a) + 1 : 0), sizeof(pr->anchor_call));
424		rs_free(a);
425	}
426	if (r->anchor_wildcard)
427		strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*",
428		    sizeof(pr->anchor_call));
429	return (0);
430}
431
432void
433pf_kanchor_remove(struct pf_krule *r)
434{
435	if (r->anchor == NULL)
436		return;
437	if (r->anchor->refcnt <= 0) {
438		printf("pf_anchor_remove: broken refcount\n");
439		r->anchor = NULL;
440		return;
441	}
442	if (!--r->anchor->refcnt)
443		pf_remove_if_empty_kruleset(&r->anchor->ruleset);
444	r->anchor = NULL;
445}
446