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