xref: /openbsd/regress/sbin/pfctl/changerule.c (revision d89ec533)
1 /* $OpenBSD: changerule.c,v 1.1 2021/11/11 12:49:53 sashan Exp $ */
2 /*
3  * Copyright (c) 2021 Alexandr Nedvedicky <sashan@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 /*
19  * changerule - simple tool to test DIOCCHANGERULE functionality (see pf(4))
20  * Tool reads firewall rules from stdin only. If more rules are passed, then
21  * only the first one is being used. Examples:
22  *	echo 'pass all' | changerule -a test -i 0
23  *		inserts a rule to the first position in ruleset test
24  *
25  *	echo 'pass all' | changerule -a test -i -1
26  *		inserts a rule to the last position in ruleset test
27  *
28  *	echo 'pass all' | changerule -a test -i 3
29  *		inserts a rule before existing No. 3 rule (rules numbering
30  *		starts with 0) in ruleset test
31  *
32  *	echo 'pass all' | changerule -a test -I 3
33  *		inserts a rule after existing No. 3 rule (rules numbering
34  *		starts with 0) in ruleset test
35  *
36  *	changerule -a test -r 3
37  *		removes existing No. 3 rule from ruleset test
38  *
39  */
40 #include <sys/types.h>
41 #include <sys/ioctl.h>
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 
45 #include <net/if.h>
46 #include <netinet/in.h>
47 #include <net/pfvar.h>
48 #include <arpa/inet.h>
49 #include <sys/sysctl.h>
50 
51 #include <err.h>
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <limits.h>
55 #include <netdb.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60 #include <syslog.h>
61 #include <stdarg.h>
62 #include <libgen.h>
63 
64 #include "pfctl_parser.h"
65 #include "pfctl.h"
66 
67 void	changerule_usage(void);
68 int	do_chng_cmd(char *, int, int);
69 
70 extern int dev;
71 extern char *anchoropt;
72 extern char *pf_device;
73 
74 __dead void
75 changerule_usage(void)
76 {
77 	extern char *__progname;
78 
79 	fprintf(stderr, "usage: %s", __progname);
80 	fprintf(stderr, "[-a anchor] [ -i ruleNo ] [ -I ruleNo ]\n");
81 	exit(1);
82 }
83 
84 int
85 do_chng_cmd(char *anchorname, int cmd, int rule_no)
86 {
87 	struct pfctl		pf;
88 	struct pf_anchor	rs_anchor;
89 	struct pf_ruleset	*rs = &rs_anchor.ruleset;
90 	struct pfioc_rule	pcr;
91 
92 	memset(&pf, 0, sizeof(pf));
93 	memset(&rs_anchor, 0, sizeof(rs_anchor));
94 	pf.anchor = &rs_anchor;
95 	pf_init_ruleset(rs);
96 
97 	if (strlcpy(pf.anchor->path, anchorname,
98 	    sizeof(pf.anchor->path)) >= sizeof (pf.anchor->path))
99 		errx(1, "%s: strlcpy\n", __func__);
100 
101 	pf.astack[0] = pf.anchor;
102 	pf.asd = 0;
103 	pf.dev = dev;
104 
105 	memset(&pcr, 0, sizeof(pcr));
106 	strlcpy(pcr.anchor, anchorname, sizeof(pcr.anchor));
107 	pcr.action = PF_CHANGE_GET_TICKET;
108 	if (ioctl(dev, DIOCCHANGERULE, &pcr) < 0)
109 		errx(1, "ioctl(ticket) @ %s", __func__);
110 
111 	pcr.action = cmd;
112 	pcr.nr = rule_no;
113 	if (cmd != PF_CHANGE_REMOVE) {
114 		if (parse_config("-", &pf) < 0) {
115 			errx(1, "Syntax error in rule");
116 			return (1);
117 		}
118 
119 		if (TAILQ_FIRST(rs->rules.active.ptr) != NULL)
120 			memcpy(&pcr.rule, TAILQ_FIRST(rs->rules.active.ptr),
121 			    sizeof(pcr.rule));
122 		else
123 			errx(1, "no rule");
124 	}
125 
126 	if (ioctl(dev, DIOCCHANGERULE, &pcr) < 0) {
127 		errx(1, "ioctl(commit) @ %s", __func__);
128 	}
129 
130 	return (0);
131 }
132 
133 int
134 main(int argc, char *argv[])
135 {
136 	char	 anchorname[PATH_MAX];
137 	const char *errstr;
138 	int ch;
139 	int rule_no;
140 	int chng_cmd;
141 	int after = 0;
142 
143 	if (argc < 2)
144 		changerule_usage();
145 
146 	while ((ch = getopt(argc, argv, "a:i:I:r:")) != -1) {
147 		switch (ch) {
148 		case 'a':
149 			anchoropt = optarg;
150 			break;
151 		case 'I':
152 			after = 1;
153 			/* FALLTHROUGH */
154 		case 'i':
155 			rule_no = strtonum(optarg, -1, 0x7fffffff, &errstr);
156 			if (errstr != NULL) {
157 				warnx("Rule number outside of range <%d, %d\n",
158 				    -1, 0x7fffffff);
159 				exit(1);
160 			}
161 			switch (rule_no) {
162 			case 0:
163 				chng_cmd = PF_CHANGE_ADD_HEAD;
164 				break;
165 			case -1:
166 				chng_cmd = PF_CHANGE_ADD_TAIL;
167 				break;
168 			default:
169 				if (after)
170 					chng_cmd = PF_CHANGE_ADD_AFTER;
171 				else
172 					chng_cmd = PF_CHANGE_ADD_BEFORE;
173 			}
174 			break;
175 		case 'r':
176 			rule_no = strtonum(optarg, -1, 0x7fffffff, &errstr);
177 			if (errstr != NULL) {
178 				warnx("Rule number outside of range <%d, %d\n",
179 				    -1, 0x7fffffff);
180 				exit(1);
181 			}
182 			chng_cmd = PF_CHANGE_REMOVE;
183 			break;
184 		default:
185 			changerule_usage();
186 			/* NOTREACHED */
187 		}
188 	}
189 
190 	if (argc != optind) {
191 		warnx("unknown command line argument: %s ...", argv[optind]);
192 		changerule_usage();
193 		/* NOTREACHED */
194 	}
195 
196 	memset(anchorname, 0, sizeof(anchorname));
197 	if (anchoropt != NULL) {
198 		if (anchoropt[0] == '\0')
199 			errx(1, "anchor name must not be empty");
200 
201 		if (anchoropt[0] == '_' || strstr(anchoropt, "/_") != NULL)
202 			errx(1, "anchor names beginning with '_' cannot "
203 			    "be modified from the command line");
204 		int len = strlen(anchoropt);
205 
206 		if (anchoropt[len - 1] == '*') {
207 			warnx("wildcard anchors not supported\n");
208 			changerule_usage();
209 		}
210 		if (strlcpy(anchorname, anchoropt,
211 		    sizeof(anchorname)) >= sizeof(anchorname))
212 			errx(1, "anchor name '%s' too long",
213 			    anchoropt);
214 	}
215 
216 	dev = open(pf_device, O_RDWR);
217 	if (dev == -1)
218 		err(1, "/dev/pf");
219 
220 	return (do_chng_cmd(anchoropt, chng_cmd, rule_no));
221 }
222