diff options
Diffstat (limited to 'regress/sbin/pfctl/changerule.c')
-rw-r--r-- | regress/sbin/pfctl/changerule.c | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/regress/sbin/pfctl/changerule.c b/regress/sbin/pfctl/changerule.c new file mode 100644 index 00000000000..8efd47d9a36 --- /dev/null +++ b/regress/sbin/pfctl/changerule.c @@ -0,0 +1,221 @@ +/* $OpenBSD: changerule.c,v 1.1 2021/11/11 12:49:53 sashan Exp $ */ +/* + * Copyright (c) 2021 Alexandr Nedvedicky <sashan@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * changerule - simple tool to test DIOCCHANGERULE functionality (see pf(4)) + * Tool reads firewall rules from stdin only. If more rules are passed, then + * only the first one is being used. Examples: + * echo 'pass all' | changerule -a test -i 0 + * inserts a rule to the first position in ruleset test + * + * echo 'pass all' | changerule -a test -i -1 + * inserts a rule to the last position in ruleset test + * + * echo 'pass all' | changerule -a test -i 3 + * inserts a rule before existing No. 3 rule (rules numbering + * starts with 0) in ruleset test + * + * echo 'pass all' | changerule -a test -I 3 + * inserts a rule after existing No. 3 rule (rules numbering + * starts with 0) in ruleset test + * + * changerule -a test -r 3 + * removes existing No. 3 rule from ruleset test + * + */ +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> +#include <arpa/inet.h> +#include <sys/sysctl.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <syslog.h> +#include <stdarg.h> +#include <libgen.h> + +#include "pfctl_parser.h" +#include "pfctl.h" + +void changerule_usage(void); +int do_chng_cmd(char *, int, int); + +extern int dev; +extern char *anchoropt; +extern char *pf_device; + +__dead void +changerule_usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s", __progname); + fprintf(stderr, "[-a anchor] [ -i ruleNo ] [ -I ruleNo ]\n"); + exit(1); +} + +int +do_chng_cmd(char *anchorname, int cmd, int rule_no) +{ + struct pfctl pf; + struct pf_anchor rs_anchor; + struct pf_ruleset *rs = &rs_anchor.ruleset; + struct pfioc_rule pcr; + + memset(&pf, 0, sizeof(pf)); + memset(&rs_anchor, 0, sizeof(rs_anchor)); + pf.anchor = &rs_anchor; + pf_init_ruleset(rs); + + if (strlcpy(pf.anchor->path, anchorname, + sizeof(pf.anchor->path)) >= sizeof (pf.anchor->path)) + errx(1, "%s: strlcpy\n", __func__); + + pf.astack[0] = pf.anchor; + pf.asd = 0; + pf.dev = dev; + + memset(&pcr, 0, sizeof(pcr)); + strlcpy(pcr.anchor, anchorname, sizeof(pcr.anchor)); + pcr.action = PF_CHANGE_GET_TICKET; + if (ioctl(dev, DIOCCHANGERULE, &pcr) < 0) + errx(1, "ioctl(ticket) @ %s", __func__); + + pcr.action = cmd; + pcr.nr = rule_no; + if (cmd != PF_CHANGE_REMOVE) { + if (parse_config("-", &pf) < 0) { + errx(1, "Syntax error in rule"); + return (1); + } + + if (TAILQ_FIRST(rs->rules.active.ptr) != NULL) + memcpy(&pcr.rule, TAILQ_FIRST(rs->rules.active.ptr), + sizeof(pcr.rule)); + else + errx(1, "no rule"); + } + + if (ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { + errx(1, "ioctl(commit) @ %s", __func__); + } + + return (0); +} + +int +main(int argc, char *argv[]) +{ + char anchorname[PATH_MAX]; + const char *errstr; + int ch; + int rule_no; + int chng_cmd; + int after = 0; + + if (argc < 2) + changerule_usage(); + + while ((ch = getopt(argc, argv, "a:i:I:r:")) != -1) { + switch (ch) { + case 'a': + anchoropt = optarg; + break; + case 'I': + after = 1; + /* FALLTHROUGH */ + case 'i': + rule_no = strtonum(optarg, -1, 0x7fffffff, &errstr); + if (errstr != NULL) { + warnx("Rule number outside of range <%d, %d\n", + -1, 0x7fffffff); + exit(1); + } + switch (rule_no) { + case 0: + chng_cmd = PF_CHANGE_ADD_HEAD; + break; + case -1: + chng_cmd = PF_CHANGE_ADD_TAIL; + break; + default: + if (after) + chng_cmd = PF_CHANGE_ADD_AFTER; + else + chng_cmd = PF_CHANGE_ADD_BEFORE; + } + break; + case 'r': + rule_no = strtonum(optarg, -1, 0x7fffffff, &errstr); + if (errstr != NULL) { + warnx("Rule number outside of range <%d, %d\n", + -1, 0x7fffffff); + exit(1); + } + chng_cmd = PF_CHANGE_REMOVE; + break; + default: + changerule_usage(); + /* NOTREACHED */ + } + } + + if (argc != optind) { + warnx("unknown command line argument: %s ...", argv[optind]); + changerule_usage(); + /* NOTREACHED */ + } + + memset(anchorname, 0, sizeof(anchorname)); + if (anchoropt != NULL) { + if (anchoropt[0] == '\0') + errx(1, "anchor name must not be empty"); + + if (anchoropt[0] == '_' || strstr(anchoropt, "/_") != NULL) + errx(1, "anchor names beginning with '_' cannot " + "be modified from the command line"); + int len = strlen(anchoropt); + + if (anchoropt[len - 1] == '*') { + warnx("wildcard anchors not supported\n"); + changerule_usage(); + } + if (strlcpy(anchorname, anchoropt, + sizeof(anchorname)) >= sizeof(anchorname)) + errx(1, "anchor name '%s' too long", + anchoropt); + } + + dev = open(pf_device, O_RDWR); + if (dev == -1) + err(1, "/dev/pf"); + + return (do_chng_cmd(anchoropt, chng_cmd, rule_no)); +} |