diff --git a/contrib/pf/authpf/authpf.c b/contrib/pf/authpf/authpf.c index ed3d1725ca20..c14bcf78f693 100644 --- a/contrib/pf/authpf/authpf.c +++ b/contrib/pf/authpf/authpf.c @@ -1,882 +1,884 @@ -/* $FreeBSD$ */ /* $OpenBSD: authpf.c,v 1.68 2003/08/21 19:13:23 frantzen Exp $ */ /* * Copyright (C) 1998 - 2002 Bob Beck (beck@openbsd.org). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pathnames.h" extern int symset(const char *, const char *, int); static int read_config(FILE *); static void print_message(char *); static int allowed_luser(char *); static int check_luser(char *, char *); static int remove_stale_rulesets(void); static int change_filter(int, const char *, const char *); static void authpf_kill_states(void); int dev; /* pf device */ char anchorname[PF_ANCHOR_NAME_SIZE] = "authpf"; char rulesetname[PF_RULESET_NAME_SIZE]; FILE *pidfp; char *infile; /* file name printed by yyerror() in parse.y */ char luser[MAXLOGNAME]; /* username */ char ipsrc[256]; /* ip as a string */ char pidfile[MAXPATHLEN]; /* we save pid in this file. */ struct timeval Tstart, Tend; /* start and end times of session */ volatile sig_atomic_t want_death; static void need_death(int signo); #ifdef __FreeBSD__ static __dead2 void do_death(int); #else static __dead void do_death(int); #endif /* * User shell for authenticating gateways. Sole purpose is to allow * a user to ssh to a gateway, and have the gateway modify packet * filters to allow access, then remove access when the user finishes * up. Meant to be used only from ssh(1) connections. */ int main(int argc, char *argv[]) { int lockcnt = 0, n, pidfd; FILE *config; struct in_addr ina; struct passwd *pw; char *cp; uid_t uid; if ((n = snprintf(rulesetname, sizeof(rulesetname), "%ld", (long)getpid())) < 0 || n >= sizeof(rulesetname)) { syslog(LOG_ERR, "pid too large for ruleset name"); exit(1); } config = fopen(PATH_CONFFILE, "r"); if ((cp = getenv("SSH_TTY")) == NULL) { syslog(LOG_ERR, "non-interactive session connection for authpf"); exit(1); } if ((cp = getenv("SSH_CLIENT")) == NULL) { syslog(LOG_ERR, "cannot determine connection source"); exit(1); } if (strlcpy(ipsrc, cp, sizeof(ipsrc)) >= sizeof(ipsrc)) { syslog(LOG_ERR, "SSH_CLIENT variable too long"); exit(1); } cp = strchr(ipsrc, ' '); if (!cp) { syslog(LOG_ERR, "corrupt SSH_CLIENT variable %s", ipsrc); exit(1); } *cp = '\0'; if (inet_pton(AF_INET, ipsrc, &ina) != 1) { syslog(LOG_ERR, "cannot determine IP from SSH_CLIENT %s", ipsrc); exit(1); } /* open the pf device */ dev = open(PATH_DEVFILE, O_RDWR); if (dev == -1) { syslog(LOG_ERR, "cannot open packet filter device (%m)"); goto die; } uid = getuid(); pw = getpwuid(uid); if (pw == NULL) { syslog(LOG_ERR, "cannot find user for uid %u", uid); goto die; } if (strcmp(pw->pw_shell, PATH_AUTHPF_SHELL)) { syslog(LOG_ERR, "wrong shell for user %s, uid %u", pw->pw_name, pw->pw_uid); goto die; } /* * Paranoia, but this data _does_ come from outside authpf, and * truncation would be bad. */ if (strlcpy(luser, pw->pw_name, sizeof(luser)) >= sizeof(luser)) { syslog(LOG_ERR, "username too long: %s", pw->pw_name); goto die; } /* Make our entry in /var/authpf as /var/authpf/ipaddr */ n = snprintf(pidfile, sizeof(pidfile), "%s/%s", PATH_PIDFILE, ipsrc); if (n < 0 || (u_int)n >= sizeof(pidfile)) { syslog(LOG_ERR, "path to pidfile too long"); goto die; } /* * If someone else is already using this ip, then this person * wants to switch users - so kill the old process and exit * as well. * * Note, we could print a message and tell them to log out, but the * usual case of this is that someone has left themselves logged in, * with the authenticated connection iconized and someone else walks * up to use and automatically logs in before using. If this just * gets rid of the old one silently, the new user never knows they * could have used someone else's old authentication. If we * tell them to log out before switching users it is an invitation * for abuse. */ do { int save_errno, otherpid = -1; char otherluser[MAXLOGNAME]; if ((pidfd = open(pidfile, O_RDWR|O_CREAT, 0644)) == -1 || (pidfp = fdopen(pidfd, "r+")) == NULL) { if (pidfd != -1) close(pidfd); syslog(LOG_ERR, "cannot open or create %s: %s", pidfile, strerror(errno)); goto die; } if (flock(fileno(pidfp), LOCK_EX|LOCK_NB) == 0) break; save_errno = errno; /* Mark our pid, and username to our file. */ rewind(pidfp); /* 31 == MAXLOGNAME - 1 */ if (fscanf(pidfp, "%d\n%31s\n", &otherpid, otherluser) != 2) otherpid = -1; syslog(LOG_DEBUG, "tried to lock %s, in use by pid %d: %s", pidfile, otherpid, strerror(save_errno)); if (otherpid > 0) { syslog(LOG_INFO, "killing prior auth (pid %d) of %s by user %s", otherpid, ipsrc, otherluser); if (kill((pid_t) otherpid, SIGTERM) == -1) { syslog(LOG_INFO, "could not kill process %d: (%m)", otherpid); } } /* * we try to kill the previous process and acquire the lock * for 10 seconds, trying once a second. if we can't after * 10 attempts we log an error and give up */ if (++lockcnt > 10) { syslog(LOG_ERR, "cannot kill previous authpf (pid %d)", otherpid); goto dogdeath; } sleep(1); /* re-open, and try again. The previous authpf process * we killed above should unlink the file and release * it's lock, giving us a chance to get it now */ fclose(pidfp); } while (1); /* revoke privs */ seteuid(getuid()); setuid(getuid()); if (!check_luser(PATH_BAN_DIR, luser) || !allowed_luser(luser)) do_death(0); openlog("authpf", LOG_PID | LOG_NDELAY, LOG_DAEMON); if (config == NULL || read_config(config)) do_death(0); if (remove_stale_rulesets()) do_death(0); /* We appear to be making headway, so actually mark our pid */ rewind(pidfp); fprintf(pidfp, "%ld\n%s\n", (long)getpid(), luser); fflush(pidfp); (void) ftruncate(fileno(pidfp), ftell(pidfp)); if (change_filter(1, luser, ipsrc) == -1) { printf("Unable to modify filters\r\n"); do_death(1); } signal(SIGTERM, need_death); signal(SIGINT, need_death); signal(SIGALRM, need_death); signal(SIGPIPE, need_death); signal(SIGHUP, need_death); signal(SIGSTOP, need_death); signal(SIGTSTP, need_death); while (1) { printf("\r\nHello %s, ", luser); printf("You are authenticated from host \"%s\"\r\n", ipsrc); setproctitle("%s@%s", luser, ipsrc); print_message(PATH_MESSAGE); while (1) { sleep(10); if (want_death) do_death(1); } } /* NOTREACHED */ dogdeath: printf("\r\n\r\nSorry, this service is currently unavailable due to "); printf("technical difficulties\r\n\r\n"); print_message(PATH_PROBLEM); printf("\r\nYour authentication process (pid %ld) was unable to run\n", (long)getpid()); sleep(180); /* them lusers read reaaaaal slow */ die: do_death(0); /* NOTREACHED */ } /* * reads config file in PATH_CONFFILE to set optional behaviours up */ static int read_config(FILE *f) { char buf[1024]; int i = 0; do { char **ap; char *pair[4], *cp, *tp; int len; if (fgets(buf, sizeof(buf), f) == NULL) { fclose(f); return (0); } i++; len = strlen(buf); if (buf[len - 1] != '\n' && !feof(f)) { syslog(LOG_ERR, "line %d too long in %s", i, PATH_CONFFILE); return (1); } buf[len - 1] = '\0'; for (cp = buf; *cp == ' ' || *cp == '\t'; cp++) ; /* nothing */ if (!*cp || *cp == '#' || *cp == '\n') continue; for (ap = pair; ap < &pair[3] && (*ap = strsep(&cp, "=")) != NULL; ) { if (**ap != '\0') ap++; } if (ap != &pair[2]) goto parse_error; tp = pair[1] + strlen(pair[1]); while ((*tp == ' ' || *tp == '\t') && tp >= pair[1]) *tp-- = '\0'; if (strcasecmp(pair[0], "anchor") == 0) { if (!pair[1][0] || strlcpy(anchorname, pair[1], sizeof(anchorname)) >= sizeof(anchorname)) goto parse_error; } } while (!feof(f) && !ferror(f)); fclose(f); return (0); parse_error: fclose(f); syslog(LOG_ERR, "parse error, line %d of %s", i, PATH_CONFFILE); return (1); } /* * splatter a file to stdout - max line length of 1024, * used for spitting message files at users to tell them * they've been bad or we're unavailable. */ static void print_message(char *filename) { char buf[1024]; FILE *f; if ((f = fopen(filename, "r")) == NULL) return; /* fail silently, we don't care if it isn't there */ do { if (fgets(buf, sizeof(buf), f) == NULL) { fflush(stdout); fclose(f); return; } } while (fputs(buf, stdout) != EOF && !feof(f)); fflush(stdout); fclose(f); } /* * allowed_luser checks to see if user "luser" is allowed to * use this gateway by virtue of being listed in an allowed * users file, namely /etc/authpf/authpf.allow . * * If /etc/authpf/authpf.allow does not exist, then we assume that * all users who are allowed in by sshd(8) are permitted to * use this gateway. If /etc/authpf/authpf.allow does exist, then a * user must be listed if the connection is to continue, else * the session terminates in the same manner as being banned. */ static int allowed_luser(char *luser) { char *buf, *lbuf; int matched; size_t len; FILE *f; if ((f = fopen(PATH_ALLOWFILE, "r")) == NULL) { if (errno == ENOENT) { /* * allowfile doesn't exist, thus this gateway * isn't restricted to certain users... */ return (1); } /* * luser may in fact be allowed, but we can't open * the file even though it's there. probably a config * problem. */ syslog(LOG_ERR, "cannot open allowed users file %s (%s)", PATH_ALLOWFILE, strerror(errno)); return (0); } else { /* * /etc/authpf/authpf.allow exists, thus we do a linear * search to see if they are allowed. * also, if username "*" exists, then this is a * "public" gateway, such as it is, so let * everyone use it. */ lbuf = NULL; while ((buf = fgetln(f, &len))) { if (buf[len - 1] == '\n') buf[len - 1] = '\0'; else { if ((lbuf = (char *)malloc(len + 1)) == NULL) err(1, NULL); memcpy(lbuf, buf, len); lbuf[len] = '\0'; buf = lbuf; } matched = strcmp(luser, buf) == 0 || strcmp("*", buf) == 0; if (lbuf != NULL) { free(lbuf); lbuf = NULL; } if (matched) return (1); /* matched an allowed username */ } syslog(LOG_INFO, "denied access to %s: not listed in %s", luser, PATH_ALLOWFILE); /* reuse buf */ buf = "\n\nSorry, you are not allowed to use this facility!\n"; fputs(buf, stdout); } fflush(stdout); return (0); } /* * check_luser checks to see if user "luser" has been banned * from using us by virtue of having an file of the same name * in the "luserdir" directory. * * If the user has been banned, we copy the contents of the file * to the user's screen. (useful for telling the user what to * do to get un-banned, or just to tell them they aren't * going to be un-banned.) */ static int check_luser(char *luserdir, char *luser) { FILE *f; int n; char tmp[MAXPATHLEN]; n = snprintf(tmp, sizeof(tmp), "%s/%s", luserdir, luser); if (n < 0 || (u_int)n >= sizeof(tmp)) { syslog(LOG_ERR, "provided banned directory line too long (%s)", luserdir); return (0); } if ((f = fopen(tmp, "r")) == NULL) { if (errno == ENOENT) { /* * file or dir doesn't exist, so therefore * this luser isn't banned.. all is well */ return (1); } else { /* * luser may in fact be banned, but we can't open the * file even though it's there. probably a config * problem. */ syslog(LOG_ERR, "cannot open banned file %s (%s)", tmp, strerror(errno)); return (0); } } else { /* * luser is banned - spit the file at them to * tell what they can do and where they can go. */ syslog(LOG_INFO, "denied access to %s: %s exists", luser, tmp); /* reuse tmp */ strlcpy(tmp, "\n\n-**- Sorry, you have been banned! -**-\n\n", sizeof(tmp)); while (fputs(tmp, stdout) != EOF && !feof(f)) { if (fgets(tmp, sizeof(tmp), f) == NULL) { fflush(stdout); return (0); } } } fflush(stdout); return (0); } /* * Search for rulesets left by other authpf processes (either because they * died ungracefully or were terminated) and remove them. */ static int remove_stale_rulesets(void) { struct pfioc_ruleset prs; const int action[PF_RULESET_MAX] = { PF_SCRUB, PF_PASS, PF_NAT, PF_BINAT, PF_RDR }; u_int32_t nr, mnr; memset(&prs, 0, sizeof(prs)); strlcpy(prs.anchor, anchorname, sizeof(prs.anchor)); if (ioctl(dev, DIOCGETRULESETS, &prs)) { if (errno == EINVAL) return (0); else return (1); } mnr = prs.nr; nr = 0; while (nr < mnr) { char *s; pid_t pid; prs.nr = nr; if (ioctl(dev, DIOCGETRULESET, &prs)) return (1); errno = 0; pid = strtoul(prs.name, &s, 10); if (!prs.name[0] || errno || *s) return (1); if (kill(pid, 0) && errno != EPERM) { int i; for (i = 0; i < PF_RULESET_MAX; ++i) { struct pfioc_rule pr; memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, prs.anchor, sizeof(pr.anchor)); memcpy(pr.ruleset, prs.name, sizeof(pr.ruleset)); pr.rule.action = action[i]; if ((ioctl(dev, DIOCBEGINRULES, &pr) || ioctl(dev, DIOCCOMMITRULES, &pr)) && errno != EINVAL) return (1); } mnr--; } else nr++; } return (0); } /* * Add/remove filter entries for user "luser" from ip "ipsrc" */ static int change_filter(int add, const char *luser, const char *ipsrc) { char fn[MAXPATHLEN]; FILE *f = NULL; const int action[PF_RULESET_MAX] = { PF_SCRUB, PF_PASS, PF_NAT, PF_BINAT, PF_RDR }; struct pfctl pf; struct pfioc_rule pr[PF_RULESET_MAX]; int i; if (luser == NULL || !luser[0] || strlen(luser) >= PF_RULESET_NAME_SIZE || ipsrc == NULL || !ipsrc[0]) { syslog(LOG_ERR, "invalid luser/ipsrc"); goto error; } if (add) { if ((i = snprintf(fn, sizeof(fn), "%s/%s/authpf.rules", PATH_USER_DIR, luser)) < 0 || i >= sizeof(fn)) { syslog(LOG_ERR, "user rule path too long"); goto error; } if ((f = fopen(fn, "r")) == NULL && errno != ENOENT) { syslog(LOG_ERR, "cannot open %s (%m)", fn); goto error; } if (f == NULL) { if (strlcpy(fn, PATH_PFRULES, sizeof(fn)) >= sizeof(fn)) { syslog(LOG_ERR, "rule path too long"); goto error; } if ((f = fopen(fn, "r")) == NULL) { syslog(LOG_ERR, "cannot open %s (%m)", fn); goto error; } } } if (pfctl_load_fingerprints(dev, 0)) { syslog(LOG_ERR, "unable to load kernel's OS fingerprints"); goto error; } memset(&pf, 0, sizeof(pf)); for (i = 0; i < PF_RULESET_MAX; ++i) { memset(&pr[i], 0, sizeof(pr[i])); pr[i].rule.action = action[i]; strlcpy(pr[i].anchor, anchorname, sizeof(pr[i].anchor)); strlcpy(pr[i].ruleset, rulesetname, sizeof(pr[i].ruleset)); if (ioctl(dev, DIOCBEGINRULES, &pr[i])) { syslog(LOG_ERR, "DIOCBEGINRULES %m"); goto error; } pf.prule[i] = &pr[i]; } if (add) { if (symset("user_ip", ipsrc, 0) || symset("user_id", luser, 0)) { syslog(LOG_ERR, "symset"); goto error; } pf.dev = dev; infile = fn; if (parse_rules(f, &pf) < 0) { syslog(LOG_ERR, "syntax error in rule file: " "authpf rules not loaded"); goto error; } infile = NULL; fclose(f); f = NULL; } for (i = 0; i < PF_RULESET_MAX; ++i) /* * ignore EINVAL on removal, it means the anchor was * already automatically removed by the kernel. */ if (ioctl(dev, DIOCCOMMITRULES, &pr[i]) && (add || errno != EINVAL)) { syslog(LOG_ERR, "DIOCCOMMITRULES %m"); goto error; } if (add) { gettimeofday(&Tstart, NULL); syslog(LOG_INFO, "allowing %s, user %s", ipsrc, luser); } else { gettimeofday(&Tend, NULL); syslog(LOG_INFO, "removed %s, user %s - duration %ld seconds", ipsrc, luser, Tend.tv_sec - Tstart.tv_sec); } return (0); error: if (f != NULL) fclose(f); infile = NULL; return (-1); } /* * This is to kill off states that would otherwise be left behind stateful * rules. This means we don't need to allow in more traffic than we really * want to, since we don't have to worry about any luser sessions lasting * longer than their ssh session. This function is based on * pfctl_kill_states from pfctl. */ static void authpf_kill_states(void) { struct pfioc_state_kill psk; struct in_addr target; memset(&psk, 0, sizeof(psk)); psk.psk_af = AF_INET; inet_pton(AF_INET, ipsrc, &target); /* Kill all states from ipsrc */ psk.psk_src.addr.v.a.addr.v4 = target; memset(&psk.psk_src.addr.v.a.mask, 0xff, sizeof(psk.psk_src.addr.v.a.mask)); if (ioctl(dev, DIOCKILLSTATES, &psk)) syslog(LOG_ERR, "DIOCKILLSTATES failed (%m)"); /* Kill all states to ipsrc */ psk.psk_af = AF_INET; memset(&psk.psk_src, 0, sizeof(psk.psk_src)); psk.psk_dst.addr.v.a.addr.v4 = target; memset(&psk.psk_dst.addr.v.a.mask, 0xff, sizeof(psk.psk_dst.addr.v.a.mask)); if (ioctl(dev, DIOCKILLSTATES, &psk)) syslog(LOG_ERR, "DIOCKILLSTATES failed (%m)"); } /* signal handler that makes us go away properly */ static void need_death(int signo) { want_death = 1; } /* * function that removes our stuff when we go away. */ #ifdef __FreeBSD__ static __dead2 void #else static __dead void #endif do_death(int active) { int ret = 0; if (active) { change_filter(0, luser, ipsrc); authpf_kill_states(); remove_stale_rulesets(); } if (pidfp) ftruncate(fileno(pidfp), 0); if (pidfile[0]) if (unlink(pidfile) == -1) syslog(LOG_ERR, "cannot unlink %s (%m)", pidfile); exit(ret); } /* * callbacks for parse_rules(void) */ int pfctl_add_rule(struct pfctl *pf, struct pf_rule *r) { struct pfioc_rule *pr; switch (r->action) { case PF_PASS: case PF_DROP: pr = pf->prule[PF_RULESET_FILTER]; break; case PF_SCRUB: pr = pf->prule[PF_RULESET_SCRUB]; break; case PF_NAT: case PF_NONAT: pr = pf->prule[PF_RULESET_NAT]; break; case PF_RDR: case PF_NORDR: pr = pf->prule[PF_RULESET_RDR]; break; case PF_BINAT: case PF_NOBINAT: pr = pf->prule[PF_RULESET_BINAT]; break; default: syslog(LOG_ERR, "invalid rule action %d", r->action); return (1); } if (pfctl_add_pool(pf, &r->rpool, r->af)) return (1); pr->pool_ticket = pf->paddr.ticket; memcpy(&pr->rule, r, sizeof(pr->rule)); if (ioctl(pf->dev, DIOCADDRULE, pr)) { syslog(LOG_ERR, "DIOCADDRULE %m"); return (1); } pfctl_clear_pool(&r->rpool); return (0); } int pfctl_add_pool(struct pfctl *pf, struct pf_pool *p, sa_family_t af) { struct pf_pooladdr *pa; if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr)) { syslog(LOG_ERR, "DIOCBEGINADDRS %m"); return (1); } pf->paddr.af = af; TAILQ_FOREACH(pa, &p->list, entries) { memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr)) { syslog(LOG_ERR, "DIOCADDADDR %m"); return (1); } } return (0); } void pfctl_clear_pool(struct pf_pool *pool) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { TAILQ_REMOVE(&pool->list, pa, entries); free(pa); } } int pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) { fprintf(stderr, "altq rules not supported in authpf\n"); return (1); } int pfctl_set_optimization(struct pfctl *pf, const char *opt) { fprintf(stderr, "set optimization not supported in authpf\n"); return (1); } int pfctl_set_logif(struct pfctl *pf, char *ifname) { fprintf(stderr, "set loginterface not supported in authpf\n"); return (1); } int pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) { fprintf(stderr, "set timeout not supported in authpf\n"); return (1); } int pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) { fprintf(stderr, "set limit not supported in authpf\n"); return (1); } int pfctl_define_table(char *name, int flags, int addrs, const char *anchor, const char *ruleset, struct pfr_buffer *ab, u_int32_t ticket) { fprintf(stderr, "table definitions not yet supported in authpf\n"); return (1); } int pfctl_rules(int dev, char *filename, int opts, char *anchorname, char *rulesetname) { /* never called, no anchors inside anchors, but we need the stub */ fprintf(stderr, "load anchor not supported from authpf\n"); return (1); } diff --git a/contrib/pf/ftp-proxy/ftp-proxy.c b/contrib/pf/ftp-proxy/ftp-proxy.c index 03c3777cd096..87e9a65c535b 100644 --- a/contrib/pf/ftp-proxy/ftp-proxy.c +++ b/contrib/pf/ftp-proxy/ftp-proxy.c @@ -1,1321 +1,1323 @@ -/* $FreeBSD$ */ /* $OpenBSD: ftp-proxy.c,v 1.33 2003/08/22 21:50:34 david Exp $ */ /* * Copyright (c) 1996-2001 * Obtuse Systems Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Obtuse Systems nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ +#include +__FBSDID("$FreeBSD$"); + /* * ftp proxy, Originally based on juniper_ftp_proxy from the Obtuse * Systems juniper firewall, written by Dan Boulet * and Bob Beck * * This version basically passes everything through unchanged except * for the PORT and the * "227 Entering Passive Mode" reply. * * A PORT command is handled by noting the IP address and port number * specified and then configuring a listen port on some very high port * number and telling the server about it using a PORT message. * We then watch for an in-bound connection on the port from the server * and connect to the client's port when it happens. * * A "227 Entering Passive Mode" reply is handled by noting the IP address * and port number specified and then configuring a listen port on some * very high port number and telling the client about it using a * "227 Entering Passive Mode" reply. * We then watch for an in-bound connection on the port from the client * and connect to the server's port when it happens. * * supports tcp wrapper lookups/access control with the -w flag using * the real destination address - the tcp wrapper stuff is done after * the real destination address is retrieved from pf * */ /* * TODO: * Plenty, this is very basic, with the idea to get it in clean first. * * - IPv6 and EPASV support * - Content filter support * - filename filter support * - per-user rules perhaps. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #ifdef LIBWRAP #include int allow_severity = LOG_INFO; int deny_severity = LOG_NOTICE; #endif /* LIBWRAP */ int min_port = IPPORT_HIFIRSTAUTO; int max_port = IPPORT_HILASTAUTO; #define STARTBUFSIZE 1024 /* Must be at least 3 */ /* * Variables used to support PORT mode connections. * * This gets a bit complicated. * * If PORT mode is on then client_listen_sa describes the socket that * the real client is listening on and server_listen_sa describes the * socket that we are listening on (waiting for the real server to connect * with us). * * If PASV mode is on then client_listen_sa describes the socket that * we are listening on (waiting for the real client to connect to us on) * and server_listen_sa describes the socket that the real server is * listening on. * * If the socket we are listening on gets a connection then we connect * to the other side's socket. Similarly, if a connected socket is * shutdown then we shutdown the other side's socket. */ double xfer_start_time; struct sockaddr_in real_server_sa; struct sockaddr_in client_listen_sa; struct sockaddr_in server_listen_sa; int client_listen_socket = -1; /* Only used in PASV mode */ int client_data_socket = -1; /* Connected socket to real client */ int server_listen_socket = -1; /* Only used in PORT mode */ int server_data_socket = -1; /* Connected socket to real server */ int client_data_bytes, server_data_bytes; int AnonFtpOnly; int Verbose; int NatMode; char ClientName[NI_MAXHOST]; char RealServerName[NI_MAXHOST]; char OurName[NI_MAXHOST]; char *User = "proxy"; char *Group; extern int Debug_Level; extern int Use_Rdns; extern char *__progname; typedef enum { UNKNOWN_MODE, PORT_MODE, PASV_MODE, EPRT_MODE, EPSV_MODE } connection_mode_t; connection_mode_t connection_mode; extern void debuglog(int debug_level, const char *fmt, ...); double wallclock_time(void); void show_xfer_stats(void); void log_control_command (char *cmd, int client); int new_dataconn(int server); void do_client_cmd(struct csiob *client, struct csiob *server); void do_server_reply(struct csiob *server, struct csiob *client); static void usage(void) { syslog(LOG_NOTICE, "usage: %s [-AnrVw] [-D debuglevel] [-g group] %s %s", __progname, "[-m minport] [-M maxport] [-t timeout]", "[-u user]"); exit(EX_USAGE); } static void close_client_data(void) { if (client_data_socket >= 0) { shutdown(client_data_socket, 2); close(client_data_socket); client_data_socket = -1; } } static void close_server_data(void) { if (server_data_socket >= 0) { shutdown(server_data_socket, 2); close(server_data_socket); server_data_socket = -1; } } static void drop_privs(void) { struct passwd *pw; struct group *gr; uid_t uid = 0; gid_t gid = 0; if (User != NULL) { pw = getpwnam(User); if (pw == NULL) { syslog(LOG_ERR, "cannot find user %s", User); exit(EX_USAGE); } uid = pw->pw_uid; gid = pw->pw_gid; } if (Group != NULL) { gr = getgrnam(Group); if (gr == NULL) { syslog(LOG_ERR, "cannot find group %s", Group); exit(EX_USAGE); } gid = gr->gr_gid; } if (gid != 0 && (setegid(gid) == -1 || setgid(gid) == -1)) { syslog(LOG_ERR, "cannot drop group privs (%m)"); exit(EX_CONFIG); } if (uid != 0 && (seteuid(uid) == -1 || setuid(uid) == -1)) { syslog(LOG_ERR, "cannot drop root privs (%m)"); exit(EX_CONFIG); } } #ifdef LIBWRAP /* * Check a connection against the tcpwrapper, log if we're going to * reject it, returns: 0 -> reject, 1 -> accept. We add in hostnames * if we are set to do reverse DNS, otherwise no. */ static int check_host(struct sockaddr_in *client_sin, struct sockaddr_in *server_sin) { char cname[NI_MAXHOST]; char sname[NI_MAXHOST]; struct request_info request; int i; request_init(&request, RQ_DAEMON, __progname, RQ_CLIENT_SIN, client_sin, RQ_SERVER_SIN, server_sin, RQ_CLIENT_ADDR, inet_ntoa(client_sin->sin_addr), 0); if (Use_Rdns) { /* * We already looked these up, but we have to do it again * for tcp wrapper, to ensure that we get the DNS name, since * the tcp wrapper cares about these things, and we don't * want to pass in a printed address as a name. */ i = getnameinfo((struct sockaddr *) &client_sin->sin_addr, sizeof(&client_sin->sin_addr), cname, sizeof(cname), NULL, 0, NI_NAMEREQD); if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) strlcpy(cname, STRING_UNKNOWN, sizeof(cname)); i = getnameinfo((struct sockaddr *)&server_sin->sin_addr, sizeof(&server_sin->sin_addr), sname, sizeof(sname), NULL, 0, NI_NAMEREQD); if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) strlcpy(sname, STRING_UNKNOWN, sizeof(sname)); } else { /* * ensure the TCP wrapper doesn't start doing * reverse DNS lookups if we aren't supposed to. */ strlcpy(cname, STRING_UNKNOWN, sizeof(cname)); strlcpy(sname, STRING_UNKNOWN, sizeof(sname)); } request_set(&request, RQ_SERVER_ADDR, inet_ntoa(server_sin->sin_addr), 0); request_set(&request, RQ_CLIENT_NAME, cname, RQ_SERVER_NAME, sname, 0); if (!hosts_access(&request)) { syslog(LOG_NOTICE, "tcpwrappers rejected: %s -> %s", ClientName, RealServerName); return(0); } return(1); } #endif /* LIBWRAP */ double wallclock_time(void) { struct timeval tv; gettimeofday(&tv, NULL); return(tv.tv_sec + tv.tv_usec / 1e6); } /* * Show the stats for this data transfer */ void show_xfer_stats(void) { char tbuf[1000]; double delta; size_t len; int i; if (!Verbose) return; delta = wallclock_time() - xfer_start_time; if (delta < 0.001) delta = 0.001; if (client_data_bytes == 0 && server_data_bytes == 0) { syslog(LOG_INFO, "data transfer complete (no bytes transferred)"); return; } len = sizeof(tbuf); if (delta >= 60) { int idelta; idelta = delta + 0.5; if (idelta >= 60*60) { i = snprintf(tbuf, len, "data transfer complete (%dh %dm %ds", idelta / (60*60), (idelta % (60*60)) / 60, idelta % 60); if (i >= len) goto logit; len -= i; } else { i = snprintf(tbuf, len, "data transfer complete (%dm %ds", idelta / 60, idelta % 60); if (i >= len) goto logit; len -= i; } } else { i = snprintf(tbuf, len, "data transfer complete (%.1fs", delta); if (i >= len) goto logit; len -= i; } if (client_data_bytes > 0) { i = snprintf(&tbuf[strlen(tbuf)], len, ", %d bytes to server) (%.1fKB/s", client_data_bytes, (client_data_bytes / delta) / (double)1024); if (i >= len) goto logit; len -= i; } if (server_data_bytes > 0) { i = snprintf(&tbuf[strlen(tbuf)], len, ", %d bytes to client) (%.1fKB/s", server_data_bytes, (server_data_bytes / delta) / (double)1024); if (i >= len) goto logit; len -= i; } strlcat(tbuf, ")", sizeof(tbuf)); logit: syslog(LOG_INFO, "%s", tbuf); } void log_control_command (char *cmd, int client) { /* log an ftp control command or reply */ char *logstring; int level = LOG_DEBUG; if (!Verbose) return; /* don't log passwords */ if (strncasecmp(cmd, "pass ", 5) == 0) logstring = "PASS XXXX"; else logstring = cmd; if (client) { /* log interesting stuff at LOG_INFO, rest at LOG_DEBUG */ if ((strncasecmp(cmd, "user ", 5) == 0) || (strncasecmp(cmd, "retr ", 5) == 0) || (strncasecmp(cmd, "cwd ", 4) == 0) || (strncasecmp(cmd, "stor " ,5) == 0)) level = LOG_INFO; } syslog(level, "%s %s", client ? "client:" : " server:", logstring); } /* * set ourselves up for a new data connection. Direction is toward client if * "server" is 0, towards server otherwise. */ int new_dataconn(int server) { /* * Close existing data conn. */ if (client_listen_socket != -1) { close(client_listen_socket); client_listen_socket = -1; } close_client_data(); if (server_listen_socket != -1) { close(server_listen_socket); server_listen_socket = -1; } close_server_data(); if (server) { bzero(&server_listen_sa, sizeof(server_listen_sa)); server_listen_socket = get_backchannel_socket(SOCK_STREAM, min_port, max_port, -1, 1, &server_listen_sa); if (server_listen_socket == -1) { syslog(LOG_INFO, "server socket bind() failed (%m)"); exit(EX_OSERR); } if (listen(server_listen_socket, 5) != 0) { syslog(LOG_INFO, "server socket listen() failed (%m)"); exit(EX_OSERR); } } else { bzero(&client_listen_sa, sizeof(client_listen_sa)); client_listen_socket = get_backchannel_socket(SOCK_STREAM, min_port, max_port, -1, 1, &client_listen_sa); if (client_listen_socket == -1) { syslog(LOG_NOTICE, "cannot get client listen socket (%m)"); exit(EX_OSERR); } if (listen(client_listen_socket, 5) != 0) { syslog(LOG_NOTICE, "cannot listen on client socket (%m)"); exit(EX_OSERR); } } return(0); } static void connect_pasv_backchannel(void) { struct sockaddr_in listen_sa; socklen_t salen; /* * We are about to accept a connection from the client. * This is a PASV data connection. */ debuglog(2, "client listen socket ready"); close_server_data(); close_client_data(); salen = sizeof(listen_sa); client_data_socket = accept(client_listen_socket, (struct sockaddr *)&listen_sa, &salen); if (client_data_socket < 0) { syslog(LOG_NOTICE, "accept() failed (%m)"); exit(EX_OSERR); } close(client_listen_socket); client_listen_socket = -1; memset(&listen_sa, 0, sizeof(listen_sa)); server_data_socket = get_backchannel_socket(SOCK_STREAM, min_port, max_port, -1, 1, &listen_sa); if (server_data_socket < 0) { syslog(LOG_NOTICE, "get_backchannel_socket() failed (%m)"); exit(EX_OSERR); } if (connect(server_data_socket, (struct sockaddr *) &server_listen_sa, sizeof(server_listen_sa)) != 0) { syslog(LOG_NOTICE, "connect() failed (%m)"); exit(EX_NOHOST); } client_data_bytes = 0; server_data_bytes = 0; xfer_start_time = wallclock_time(); } static void connect_port_backchannel(void) { struct sockaddr_in listen_sa; socklen_t salen; /* * We are about to accept a connection from the server. * This is a PORT or EPRT data connection. */ debuglog(2, "server listen socket ready"); close_server_data(); close_client_data(); salen = sizeof(listen_sa); server_data_socket = accept(server_listen_socket, (struct sockaddr *)&listen_sa, &salen); if (server_data_socket < 0) { syslog(LOG_NOTICE, "accept() failed (%m)"); exit(EX_OSERR); } close(server_listen_socket); server_listen_socket = -1; if (getuid() != 0) { /* * We're not running as root, so we get a backchannel * socket bound in our designated range, instead of * getting one bound to port 20 - This is deliberately * not RFC compliant. */ bzero(&listen_sa.sin_addr, sizeof(struct in_addr)); client_data_socket = get_backchannel_socket(SOCK_STREAM, min_port, max_port, -1, 1, &listen_sa); if (client_data_socket < 0) { syslog(LOG_NOTICE, "get_backchannel_socket() failed (%m)"); exit(EX_OSERR); } } else { /* * We're root, get our backchannel socket bound to port * 20 here, so we're fully RFC compliant. */ client_data_socket = socket(AF_INET, SOCK_STREAM, 0); salen = 1; listen_sa.sin_family = AF_INET; bzero(&listen_sa.sin_addr, sizeof(struct in_addr)); listen_sa.sin_port = htons(20); if (setsockopt(client_data_socket, SOL_SOCKET, SO_REUSEADDR, &salen, sizeof(salen)) == -1) { syslog(LOG_NOTICE, "setsockopt() failed (%m)"); exit(EX_OSERR); } if (bind(client_data_socket, (struct sockaddr *)&listen_sa, sizeof(listen_sa)) == - 1) { syslog(LOG_NOTICE, "data channel bind() failed (%m)"); exit(EX_OSERR); } } if (connect(client_data_socket, (struct sockaddr *) &client_listen_sa, sizeof(client_listen_sa)) != 0) { syslog(LOG_INFO, "cannot connect data channel (%m)"); exit(EX_NOHOST); } client_data_bytes = 0; server_data_bytes = 0; xfer_start_time = wallclock_time(); } void do_client_cmd(struct csiob *client, struct csiob *server) { int i, j, rv; char tbuf[100]; char *sendbuf = NULL; log_control_command((char *)client->line_buffer, 1); /* client->line_buffer is an ftp control command. * There is no reason for these to be very long. * In the interest of limiting buffer overrun attempts, * we catch them here. */ if (strlen((char *)client->line_buffer) > 512) { syslog(LOG_NOTICE, "excessively long control command"); exit(EX_DATAERR); } /* * Check the client user provided if needed */ if (AnonFtpOnly && strncasecmp((char *)client->line_buffer, "user ", strlen("user ")) == 0) { char *cp; cp = (char *) client->line_buffer + strlen("user "); if ((strcasecmp(cp, "ftp\r\n") != 0) && (strcasecmp(cp, "anonymous\r\n") != 0)) { /* * this isn't anonymous - give the client an * error before they send a password */ snprintf(tbuf, sizeof(tbuf), "500 Only anonymous FTP is allowed\r\n"); j = 0; i = strlen(tbuf); do { rv = send(client->fd, tbuf + j, i - j, 0); if (rv == -1 && errno != EAGAIN && errno != EINTR) break; else if (rv != -1) j += rv; } while (j >= 0 && j < i); sendbuf = NULL; } else sendbuf = (char *)client->line_buffer; } else if ((strncasecmp((char *)client->line_buffer, "eprt ", strlen("eprt ")) == 0)) { /* Watch out for EPRT commands */ char *line = NULL, *q, *p, *result[3], delim; struct addrinfo hints, *res = NULL; unsigned long proto; j = 0; line = strdup((char *)client->line_buffer+strlen("eprt ")); if (line == NULL) { syslog(LOG_ERR, "insufficient memory"); exit(EX_UNAVAILABLE); } p = line; delim = p[0]; p++; memset(result,0, sizeof(result)); for (i = 0; i < 3; i++) { q = strchr(p, delim); if (!q || *q != delim) goto parsefail; *q++ = '\0'; result[i] = p; p = q; } proto = strtoul(result[0], &p, 10); if (!*result[0] || *p) goto protounsupp; memset(&hints, 0, sizeof(hints)); if (proto != 1) /* 1 == AF_INET - all we support for now */ goto protounsupp; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST; /*no DNS*/ if (getaddrinfo(result[1], result[2], &hints, &res)) goto parsefail; if (res->ai_next) goto parsefail; if (sizeof(client_listen_sa) < res->ai_addrlen) goto parsefail; memcpy(&client_listen_sa, res->ai_addr, res->ai_addrlen); debuglog(1, "client wants us to use %s:%u", inet_ntoa(client_listen_sa.sin_addr), htons(client_listen_sa.sin_port)); /* * Configure our own listen socket and tell the server about it */ new_dataconn(1); connection_mode = EPRT_MODE; debuglog(1, "we want server to use %s:%u", inet_ntoa(server->sa.sin_addr), ntohs(server_listen_sa.sin_port)); snprintf(tbuf, sizeof(tbuf), "EPRT |%d|%s|%u|\r\n", 1, inet_ntoa(server->sa.sin_addr), ntohs(server_listen_sa.sin_port)); debuglog(1, "to server (modified): %s", tbuf); sendbuf = tbuf; goto out; parsefail: snprintf(tbuf, sizeof(tbuf), "500 Invalid argument; rejected\r\n"); sendbuf = NULL; goto out; protounsupp: /* we only support AF_INET for now */ if (proto == 2) snprintf(tbuf, sizeof(tbuf), "522 Protocol not supported, use (1)\r\n"); else snprintf(tbuf, sizeof(tbuf), "501 Protocol not supported\r\n"); sendbuf = NULL; out: if (line) free(line); if (res) freeaddrinfo(res); if (sendbuf == NULL) { debuglog(1, "to client (modified): %s", tbuf); i = strlen(tbuf); do { rv = send(client->fd, tbuf + j, i - j, 0); if (rv == -1 && errno != EAGAIN && errno != EINTR) break; else if (rv != -1) j += rv; } while (j >= 0 && j < i); } } else if (!NatMode && (strncasecmp((char *)client->line_buffer, "epsv", strlen("epsv")) == 0)) { /* * If we aren't in NAT mode, deal with EPSV. * EPSV is a problem - Unlike PASV, the reply from the * server contains *only* a port, we can't modify the reply * to the client and get the client to connect to us without * resorting to using a dynamic rdr rule we have to add in * for the reply to this connection, and take away afterwards. * so this will wait until we have the right solution for rule * additions/deletions in pf. * * in the meantime we just tell the client we don't do it, * and most clients should fall back to using PASV. */ snprintf(tbuf, sizeof(tbuf), "500 EPSV command not understood\r\n"); debuglog(1, "to client (modified): %s", tbuf); j = 0; i = strlen(tbuf); do { rv = send(client->fd, tbuf + j, i - j, 0); if (rv == -1 && errno != EAGAIN && errno != EINTR) break; else if (rv != -1) j += rv; } while (j >= 0 && j < i); sendbuf = NULL; } else if (strncasecmp((char *)client->line_buffer, "port ", strlen("port ")) == 0) { unsigned int values[6]; char *tailptr; debuglog(1, "Got a PORT command"); tailptr = (char *)&client->line_buffer[strlen("port ")]; values[0] = 0; i = sscanf(tailptr, "%u,%u,%u,%u,%u,%u", &values[0], &values[1], &values[2], &values[3], &values[4], &values[5]); if (i != 6) { syslog(LOG_INFO, "malformed PORT command (%s)", client->line_buffer); exit(EX_DATAERR); } for (i = 0; i<6; i++) { if (values[i] > 255) { syslog(LOG_INFO, "malformed PORT command (%s)", client->line_buffer); exit(EX_DATAERR); } } client_listen_sa.sin_family = AF_INET; client_listen_sa.sin_addr.s_addr = htonl((values[0] << 24) | (values[1] << 16) | (values[2] << 8) | (values[3] << 0)); client_listen_sa.sin_port = htons((values[4] << 8) | values[5]); debuglog(1, "client wants us to use %u.%u.%u.%u:%u", values[0], values[1], values[2], values[3], (values[4] << 8) | values[5]); /* * Configure our own listen socket and tell the server about it */ new_dataconn(1); connection_mode = PORT_MODE; debuglog(1, "we want server to use %s:%u", inet_ntoa(server->sa.sin_addr), ntohs(server_listen_sa.sin_port)); snprintf(tbuf, sizeof(tbuf), "PORT %u,%u,%u,%u,%u,%u\r\n", ((u_char *)&server->sa.sin_addr.s_addr)[0], ((u_char *)&server->sa.sin_addr.s_addr)[1], ((u_char *)&server->sa.sin_addr.s_addr)[2], ((u_char *)&server->sa.sin_addr.s_addr)[3], ((u_char *)&server_listen_sa.sin_port)[0], ((u_char *)&server_listen_sa.sin_port)[1]); debuglog(1, "to server (modified): %s", tbuf); sendbuf = tbuf; } else sendbuf = (char *)client->line_buffer; /* *send our (possibly modified) control command in sendbuf * on it's way to the server */ if (sendbuf != NULL) { j = 0; i = strlen(sendbuf); do { rv = send(server->fd, sendbuf + j, i - j, 0); if (rv == -1 && errno != EAGAIN && errno != EINTR) break; else if (rv != -1) j += rv; } while (j >= 0 && j < i); } } void do_server_reply(struct csiob *server, struct csiob *client) { int code, i, j, rv; struct in_addr *iap; static int continuing = 0; char tbuf[100], *sendbuf, *p; log_control_command((char *)server->line_buffer, 0); if (strlen((char *)server->line_buffer) > 512) { /* * someone's playing games. Have a cow in the syslogs and * exit - we don't pass this on for fear of hurting * our other end, which might be poorly implemented. */ syslog(LOG_NOTICE, "long FTP control reply"); exit(EX_DATAERR); } /* * Watch out for "227 Entering Passive Mode ..." replies */ code = strtol((char *)server->line_buffer, &p, 10); if (isspace(server->line_buffer[0])) code = 0; if (!*(server->line_buffer) || (*p != ' ' && *p != '-')) { if (continuing) goto sendit; syslog(LOG_INFO, "malformed control reply"); exit(EX_DATAERR); } if (code <= 0 || code > 999) { if (continuing) goto sendit; syslog(LOG_INFO, "invalid server reply code %d", code); exit(EX_DATAERR); } if (*p == '-') continuing = 1; else continuing = 0; if (code == 227 && !NatMode) { unsigned int values[6]; char *tailptr; debuglog(1, "Got a PASV reply"); debuglog(1, "{%s}", (char *)server->line_buffer); tailptr = (char *)strchr((char *)server->line_buffer, '('); if (tailptr == NULL) { tailptr = strrchr((char *)server->line_buffer, ' '); if (tailptr == NULL) { syslog(LOG_NOTICE, "malformed 227 reply"); exit(EX_DATAERR); } } tailptr++; /* skip past space or ( */ values[0] = 0; i = sscanf(tailptr, "%u,%u,%u,%u,%u,%u", &values[0], &values[1], &values[2], &values[3], &values[4], &values[5]); if (i != 6) { syslog(LOG_INFO, "malformed PASV reply (%s)", client->line_buffer); exit(EX_DATAERR); } for (i = 0; i<6; i++) if (values[i] > 255) { syslog(LOG_INFO, "malformed PASV reply(%s)", client->line_buffer); exit(EX_DATAERR); } server_listen_sa.sin_family = AF_INET; server_listen_sa.sin_addr.s_addr = htonl((values[0] << 24) | (values[1] << 16) | (values[2] << 8) | (values[3] << 0)); server_listen_sa.sin_port = htons((values[4] << 8) | values[5]); debuglog(1, "server wants us to use %s:%u", inet_ntoa(server_listen_sa.sin_addr), (values[4] << 8) | values[5]); new_dataconn(0); connection_mode = PASV_MODE; iap = &(server->sa.sin_addr); debuglog(1, "we want client to use %s:%u", inet_ntoa(*iap), htons(client_listen_sa.sin_port)); snprintf(tbuf, sizeof(tbuf), "227 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n", ((u_char *)iap)[0], ((u_char *)iap)[1], ((u_char *)iap)[2], ((u_char *)iap)[3], ((u_char *)&client_listen_sa.sin_port)[0], ((u_char *)&client_listen_sa.sin_port)[1]); debuglog(1, "to client (modified): %s", tbuf); sendbuf = tbuf; } else { sendit: sendbuf = (char *)server->line_buffer; } /* * send our (possibly modified) control command in sendbuf * on it's way to the client */ j = 0; i = strlen(sendbuf); do { rv = send(client->fd, sendbuf + j, i - j, 0); if (rv == -1 && errno != EAGAIN && errno != EINTR) break; else if (rv != -1) j += rv; } while (j >= 0 && j < i); } int main(int argc, char *argv[]) { struct csiob client_iob, server_iob; struct sigaction new_sa, old_sa; int sval, ch, flags, i; socklen_t salen; int one = 1; long timeout_seconds = 0; struct timeval tv; #ifdef LIBWRAP int use_tcpwrapper = 0; #endif /* LIBWRAP */ while ((ch = getopt(argc, argv, "D:g:m:M:t:u:AnVwr")) != -1) { char *p; switch (ch) { case 'A': AnonFtpOnly = 1; /* restrict to anon usernames only */ break; case 'D': Debug_Level = strtol(optarg, &p, 10); if (!*optarg || *p) usage(); break; case 'g': Group = optarg; break; case 'm': min_port = strtol(optarg, &p, 10); if (!*optarg || *p) usage(); if (min_port < 0 || min_port > USHRT_MAX) usage(); break; case 'M': max_port = strtol(optarg, &p, 10); if (!*optarg || *p) usage(); if (max_port < 0 || max_port > USHRT_MAX) usage(); break; case 'n': NatMode = 1; /* pass all passives, we're using NAT */ break; case 'r': Use_Rdns = 1; /* look up hostnames */ break; case 't': timeout_seconds = strtol(optarg, &p, 10); if (!*optarg || *p) usage(); break; case 'u': User = optarg; break; case 'V': Verbose = 1; break; #ifdef LIBWRAP case 'w': use_tcpwrapper = 1; /* do the libwrap thing */ break; #endif /* LIBWRAP */ default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (max_port < min_port) usage(); openlog(__progname, LOG_NDELAY|LOG_PID, LOG_DAEMON); setlinebuf(stdout); setlinebuf(stderr); memset(&client_iob, 0, sizeof(client_iob)); memset(&server_iob, 0, sizeof(server_iob)); if (get_proxy_env(0, &real_server_sa, &client_iob.sa) == -1) exit(EX_PROTOCOL); /* * We may now drop root privs, as we have done our ioctl for * pf. If we do drop root, we can't make backchannel connections * for PORT and EPRT come from port 20, which is not strictly * RFC compliant. This shouldn't cause problems for all but * the stupidest ftp clients and the stupidest packet filters. */ drop_privs(); /* * We check_host after get_proxy_env so that checks are done * against the original destination endpoint, not the endpoint * of our side of the rdr. This allows the use of tcpwrapper * rules to restrict destinations as well as sources of connections * for ftp. */ if (Use_Rdns) flags = 0; else flags = NI_NUMERICHOST | NI_NUMERICSERV; i = getnameinfo((struct sockaddr *)&client_iob.sa, sizeof(client_iob.sa), ClientName, sizeof(ClientName), NULL, 0, flags); if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) { debuglog(2, "name resolution failure (client)"); exit(EX_OSERR); } i = getnameinfo((struct sockaddr *)&real_server_sa, sizeof(real_server_sa), RealServerName, sizeof(RealServerName), NULL, 0, flags); if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) { debuglog(2, "name resolution failure (server)"); exit(EX_OSERR); } #ifdef LIBWRAP if (use_tcpwrapper && !check_host(&client_iob.sa, &real_server_sa)) exit(EX_NOPERM); #endif client_iob.fd = 0; syslog(LOG_INFO, "accepted connection from %s:%u to %s:%u", ClientName, ntohs(client_iob.sa.sin_port), RealServerName, ntohs(real_server_sa.sin_port)); server_iob.fd = get_backchannel_socket(SOCK_STREAM, min_port, max_port, -1, 1, &server_iob.sa); if (connect(server_iob.fd, (struct sockaddr *)&real_server_sa, sizeof(real_server_sa)) != 0) { syslog(LOG_INFO, "cannot connect to %s:%u (%m)", RealServerName, ntohs(real_server_sa.sin_port)); exit(EX_NOHOST); } /* * Now that we are connected to the real server, get the name * of our end of the server socket so we know our IP address * from the real server's perspective. */ salen = sizeof(server_iob.sa); getsockname(server_iob.fd, (struct sockaddr *)&server_iob.sa, &salen); i = getnameinfo((struct sockaddr *)&server_iob.sa, sizeof(server_iob.sa), OurName, sizeof(OurName), NULL, 0, flags); if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) { debuglog(2, "name resolution failure (local)"); exit(EX_OSERR); } debuglog(1, "local socket is %s:%u", OurName, ntohs(server_iob.sa.sin_port)); /* ignore SIGPIPE */ bzero(&new_sa, sizeof(new_sa)); new_sa.sa_handler = SIG_IGN; (void)sigemptyset(&new_sa.sa_mask); new_sa.sa_flags = SA_RESTART; if (sigaction(SIGPIPE, &new_sa, &old_sa) != 0) { syslog(LOG_ERR, "sigaction() failed (%m)"); exit(EX_OSERR); } if (setsockopt(client_iob.fd, SOL_SOCKET, SO_OOBINLINE, (char *)&one, sizeof(one)) == -1) { syslog(LOG_NOTICE, "cannot set SO_OOBINLINE (%m)"); exit(EX_OSERR); } client_iob.line_buffer_size = STARTBUFSIZE; client_iob.line_buffer = malloc(client_iob.line_buffer_size); client_iob.io_buffer_size = STARTBUFSIZE; client_iob.io_buffer = malloc(client_iob.io_buffer_size); client_iob.next_byte = 0; client_iob.io_buffer_len = 0; client_iob.alive = 1; client_iob.who = "client"; client_iob.send_oob_flags = 0; client_iob.real_sa = client_iob.sa; server_iob.line_buffer_size = STARTBUFSIZE; server_iob.line_buffer = malloc(server_iob.line_buffer_size); server_iob.io_buffer_size = STARTBUFSIZE; server_iob.io_buffer = malloc(server_iob.io_buffer_size); server_iob.next_byte = 0; server_iob.io_buffer_len = 0; server_iob.alive = 1; server_iob.who = "server"; server_iob.send_oob_flags = MSG_OOB; server_iob.real_sa = real_server_sa; if (client_iob.line_buffer == NULL || client_iob.io_buffer == NULL || server_iob.line_buffer == NULL || server_iob.io_buffer == NULL) { syslog (LOG_NOTICE, "insufficient memory"); exit(EX_UNAVAILABLE); } while (client_iob.alive || server_iob.alive) { int maxfd = 0; fd_set *fdsp; if (client_iob.fd > maxfd) maxfd = client_iob.fd; if (client_listen_socket > maxfd) maxfd = client_listen_socket; if (client_data_socket > maxfd) maxfd = client_data_socket; if (server_iob.fd > maxfd) maxfd = server_iob.fd; if (server_listen_socket > maxfd) maxfd = server_listen_socket; if (server_data_socket > maxfd) maxfd = server_data_socket; debuglog(3, "client is %s; server is %s", client_iob.alive ? "alive" : "dead", server_iob.alive ? "alive" : "dead"); fdsp = (fd_set *)calloc(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); if (fdsp == NULL) { syslog(LOG_NOTICE, "insufficient memory"); exit(EX_UNAVAILABLE); } if (client_iob.alive && telnet_getline(&client_iob, &server_iob)) { debuglog(3, "client line buffer is \"%s\"", (char *)client_iob.line_buffer); if (client_iob.line_buffer[0] != '\0') do_client_cmd(&client_iob, &server_iob); } else if (server_iob.alive && telnet_getline(&server_iob, &client_iob)) { debuglog(3, "server line buffer is \"%s\"", (char *)server_iob.line_buffer); if (server_iob.line_buffer[0] != '\0') do_server_reply(&server_iob, &client_iob); } else { if (client_iob.alive) { FD_SET(client_iob.fd, fdsp); if (client_listen_socket >= 0) FD_SET(client_listen_socket, fdsp); if (client_data_socket >= 0) FD_SET(client_data_socket, fdsp); } if (server_iob.alive) { FD_SET(server_iob.fd, fdsp); if (server_listen_socket >= 0) FD_SET(server_listen_socket, fdsp); if (server_data_socket >= 0) FD_SET(server_data_socket, fdsp); } tv.tv_sec = timeout_seconds; tv.tv_usec = 0; doselect: sval = select(maxfd + 1, fdsp, NULL, NULL, (tv.tv_sec == 0) ? NULL : &tv); if (sval == 0) { /* * This proxy has timed out. Expire it * quietly with an obituary in the syslogs * for any passing mourners. */ syslog(LOG_INFO, "timeout: no data for %ld seconds", timeout_seconds); exit(EX_OK); } if (sval == -1) { if (errno == EINTR || errno == EAGAIN) goto doselect; syslog(LOG_NOTICE, "select() failed (%m)"); exit(EX_OSERR); } if (client_data_socket >= 0 && FD_ISSET(client_data_socket, fdsp)) { int rval; debuglog(3, "transfer: client to server"); rval = xfer_data("client to server", client_data_socket, server_data_socket, client_iob.sa.sin_addr, real_server_sa.sin_addr); if (rval <= 0) { close_client_data(); close_server_data(); show_xfer_stats(); } else client_data_bytes += rval; } if (server_data_socket >= 0 && FD_ISSET(server_data_socket, fdsp)) { int rval; debuglog(3, "transfer: server to client"); rval = xfer_data("server to client", server_data_socket, client_data_socket, real_server_sa.sin_addr, client_iob.sa.sin_addr); if (rval <= 0) { close_client_data(); close_server_data(); show_xfer_stats(); } else server_data_bytes += rval; } if (server_listen_socket >= 0 && FD_ISSET(server_listen_socket, fdsp)) { connect_port_backchannel(); } if (client_listen_socket >= 0 && FD_ISSET(client_listen_socket, fdsp)) { connect_pasv_backchannel(); } if (client_iob.alive && FD_ISSET(client_iob.fd, fdsp)) { client_iob.data_available = 1; } if (server_iob.alive && FD_ISSET(server_iob.fd, fdsp)) { server_iob.data_available = 1; } } free(fdsp); if (client_iob.got_eof) { shutdown(server_iob.fd, 1); shutdown(client_iob.fd, 0); client_iob.got_eof = 0; client_iob.alive = 0; } if (server_iob.got_eof) { shutdown(client_iob.fd, 1); shutdown(server_iob.fd, 0); server_iob.got_eof = 0; server_iob.alive = 0; } } if (Verbose) syslog(LOG_INFO, "session ended"); exit(EX_OK); } diff --git a/contrib/pf/pfctl/parse.y b/contrib/pf/pfctl/parse.y index 5218cc12d09a..5a53bacc7f76 100644 --- a/contrib/pf/pfctl/parse.y +++ b/contrib/pf/pfctl/parse.y @@ -1,4495 +1,4497 @@ -/* $FreeBSD$ */ /* $OpenBSD: parse.y,v 1.415 2003/09/01 15:07:40 henning Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ %{ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" #ifdef __FreeBSD__ #define HTONL(x) (x) = htonl((__uint32_t)(x)) #endif static struct pfctl *pf = NULL; static FILE *fin = NULL; static int debug = 0; static int lineno = 1; static int errors = 0; static int rulestate = 0; static u_int16_t returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; static u_int16_t returnicmp6default = (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; static int blockpolicy = PFRULE_DROP; static int require_order = 1; enum { PFCTL_STATE_NONE, PFCTL_STATE_OPTION, PFCTL_STATE_SCRUB, PFCTL_STATE_QUEUE, PFCTL_STATE_NAT, PFCTL_STATE_FILTER }; struct node_proto { u_int8_t proto; struct node_proto *next; struct node_proto *tail; }; struct node_port { u_int16_t port[2]; u_int8_t op; struct node_port *next; struct node_port *tail; }; struct node_uid { uid_t uid[2]; u_int8_t op; struct node_uid *next; struct node_uid *tail; }; struct node_gid { gid_t gid[2]; u_int8_t op; struct node_gid *next; struct node_gid *tail; }; struct node_icmp { u_int8_t code; u_int8_t type; u_int8_t proto; struct node_icmp *next; struct node_icmp *tail; }; enum { PF_STATE_OPT_MAX=0, PF_STATE_OPT_TIMEOUT=1 }; struct node_state_opt { int type; union { u_int32_t max_states; struct { int number; u_int32_t seconds; } timeout; } data; struct node_state_opt *next; struct node_state_opt *tail; }; struct peer { struct node_host *host; struct node_port *port; }; struct node_queue { char queue[PF_QNAME_SIZE]; char parent[PF_QNAME_SIZE]; char ifname[IFNAMSIZ]; int scheduler; struct node_queue *next; struct node_queue *tail; } *queues = NULL; struct node_qassign { char *qname; char *pqname; }; struct filter_opts { int marker; #define FOM_FLAGS 0x01 #define FOM_ICMP 0x02 #define FOM_TOS 0x04 #define FOM_KEEP 0x08 struct node_uid *uid; struct node_gid *gid; struct { u_int8_t b1; u_int8_t b2; u_int16_t w; u_int16_t w2; } flags; struct node_icmp *icmpspec; u_int32_t tos; struct { int action; struct node_state_opt *options; } keep; int fragment; int allowopts; char *label; struct node_qassign queues; char *tag; char *match_tag; u_int8_t match_tag_not; } filter_opts; struct antispoof_opts { char *label; } antispoof_opts; struct scrub_opts { int marker; #define SOM_MINTTL 0x01 #define SOM_MAXMSS 0x02 #define SOM_FRAGCACHE 0x04 int nodf; int minttl; int maxmss; int fragcache; int randomid; int reassemble_tcp; } scrub_opts; struct queue_opts { int marker; #define QOM_BWSPEC 0x01 #define QOM_SCHEDULER 0x02 #define QOM_PRIORITY 0x04 #define QOM_TBRSIZE 0x08 #define QOM_QLIMIT 0x10 struct node_queue_bw queue_bwspec; struct node_queue_opt scheduler; int priority; int tbrsize; int qlimit; } queue_opts; struct table_opts { int flags; int init_addr; struct node_tinithead init_nodes; } table_opts; struct node_hfsc_opts hfsc_opts; int yyerror(const char *, ...); int disallow_table(struct node_host *, const char *); int rule_consistent(struct pf_rule *); int filter_consistent(struct pf_rule *); int nat_consistent(struct pf_rule *); int rdr_consistent(struct pf_rule *); int process_tabledef(char *, struct table_opts *); int yyparse(void); void expand_label_str(char *, const char *, const char *); void expand_label_if(const char *, char *, const char *); void expand_label_addr(const char *, char *, u_int8_t, struct node_host *); void expand_label_port(const char *, char *, struct node_port *); void expand_label_proto(const char *, char *, u_int8_t); void expand_label_nr(const char *, char *); void expand_label(char *, const char *, u_int8_t, struct node_host *, struct node_port *, struct node_host *, struct node_port *, u_int8_t); void expand_rule(struct pf_rule *, struct node_if *, struct node_host *, struct node_proto *, struct node_os*, struct node_host *, struct node_port *, struct node_host *, struct node_port *, struct node_uid *, struct node_gid *, struct node_icmp *); int expand_altq(struct pf_altq *, struct node_if *, struct node_queue *, struct node_queue_bw bwspec, struct node_queue_opt *); int expand_queue(struct pf_altq *, struct node_if *, struct node_queue *, struct node_queue_bw, struct node_queue_opt *); int check_rulestate(int); int kw_cmp(const void *, const void *); int lookup(char *); int lgetc(FILE *); int lungetc(int); int findeol(void); int yylex(void); int atoul(char *, u_long *); int getservice(char *); int rule_label(struct pf_rule *, char *); TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); struct sym { TAILQ_ENTRY(sym) entries; int used; int persist; char *nam; char *val; }; int symset(const char *, const char *, int); char *symget(const char *); void decide_address_family(struct node_host *, sa_family_t *); void remove_invalid_hosts(struct node_host **, sa_family_t *); int invalid_redirect(struct node_host *, sa_family_t); u_int16_t parseicmpspec(char *, sa_family_t); TAILQ_HEAD(loadanchorshead, loadanchors) loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead); struct loadanchors { TAILQ_ENTRY(loadanchors) entries; char *anchorname; char *rulesetname; char *filename; }; typedef struct { union { u_int32_t number; int i; char *string; struct { u_int8_t b1; u_int8_t b2; u_int16_t w; u_int16_t w2; } b; struct range { int a; int b; int t; } range; struct node_if *interface; struct node_proto *proto; struct node_icmp *icmp; struct node_host *host; struct node_os *os; struct node_port *port; struct node_uid *uid; struct node_gid *gid; struct node_state_opt *state_opt; struct peer peer; struct { struct peer src, dst; struct node_os *src_os; } fromto; struct pf_poolhashkey *hashkey; struct { struct node_host *host; u_int8_t rt; u_int8_t pool_opts; sa_family_t af; struct pf_poolhashkey *key; } route; struct redirection { struct node_host *host; struct range rport; } *redirection; struct { int type; struct pf_poolhashkey *key; } pooltype; struct { int action; struct node_state_opt *options; } keep_state; struct { u_int8_t log; u_int8_t quick; } logquick; struct node_queue *queue; struct node_queue_opt queue_options; struct node_queue_bw queue_bwspec; struct node_qassign qassign; struct filter_opts filter_opts; struct antispoof_opts antispoof_opts; struct queue_opts queue_opts; struct scrub_opts scrub_opts; struct table_opts table_opts; struct node_hfsc_opts hfsc_opts; } v; int lineno; } YYSTYPE; #define PREPARE_ANCHOR_RULE(r, a) \ do { \ memset(&(r), 0, sizeof(r)); \ if (strlcpy(r.anchorname, (a), \ sizeof(r.anchorname)) >= \ sizeof(r.anchorname)) { \ yyerror("anchor name '%s' too long", \ (a)); \ YYERROR; \ } \ } while (0) %} %token PASS BLOCK SCRUB RETURN IN OS OUT LOG LOGALL QUICK ON FROM TO FLAGS %token RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE %token ICMP6TYPE CODE KEEP MODULATE STATE PORT RDR NAT BINAT ARROW NODF %token MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL %token NOROUTE FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE %token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR %token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY RANDOMID %token REQUIREORDER SYNPROXY FINGERPRINTS %token ANTISPOOF FOR %token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT %token ALTQ CBQ PRIQ HFSC BANDWIDTH TBRSIZE LINKSHARE REALTIME UPPERLIMIT %token QUEUE PRIORITY QLIMIT %token LOAD %token TAGGED TAG %token STRING %token PORTBINARY %type interface if_list if_item_not if_item %type number icmptype icmp6type uid gid %type tos not yesno natpass %type no dir log af fragcache %type staticport unaryop %type action nataction flags flag blockspec %type port rport %type hashkey %type pooltype %type proto proto_list proto_item %type icmpspec %type icmp_list icmp_item %type icmp6_list icmp6_item %type fromto %type ipportspec from to %type ipspec xhost host dynaddr host_list %type redir_host_list redirspec %type route_host route_host_list routespec %type os xos os_list %type portspec port_list port_item %type uids uid_list uid_item %type gids gid_list gid_item %type route %type redirection redirpool %type label string tag %type keep %type state_opt_spec state_opt_list state_opt_item %type logquick %type antispoof_ifspc antispoof_iflst %type qname %type qassign qassign_list qassign_item %type scheduler %type cbqflags_list cbqflags_item %type priqflags_list priqflags_item %type hfscopts_list hfscopts_item hfsc_opts %type bandwidth %type filter_opts filter_opt filter_opts_l %type antispoof_opts antispoof_opt antispoof_opts_l %type queue_opts queue_opt queue_opts_l %type scrub_opts scrub_opt scrub_opts_l %type table_opts table_opt table_opts_l %% ruleset : /* empty */ | ruleset '\n' | ruleset option '\n' | ruleset scrubrule '\n' | ruleset natrule '\n' | ruleset binatrule '\n' | ruleset pfrule '\n' | ruleset anchorrule '\n' | ruleset loadrule '\n' | ruleset altqif '\n' | ruleset queuespec '\n' | ruleset varset '\n' | ruleset antispoof '\n' | ruleset tabledef '\n' | ruleset error '\n' { errors++; } ; option : SET OPTIMIZATION STRING { if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; if (pfctl_set_optimization(pf, $3) != 0) { yyerror("unknown optimization %s", $3); YYERROR; } } | SET TIMEOUT timeout_spec | SET TIMEOUT '{' timeout_list '}' | SET LIMIT limit_spec | SET LIMIT '{' limit_list '}' | SET LOGINTERFACE STRING { if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; if ((ifa_exists($3) == NULL) && strcmp($3, "none")) { yyerror("interface %s doesn't exist", $3); YYERROR; } if (pfctl_set_logif(pf, $3) != 0) { yyerror("error setting loginterface %s", $3); YYERROR; } } | SET BLOCKPOLICY DROP { if (pf->opts & PF_OPT_VERBOSE) printf("set block-policy drop\n"); if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; blockpolicy = PFRULE_DROP; } | SET BLOCKPOLICY RETURN { if (pf->opts & PF_OPT_VERBOSE) printf("set block-policy return\n"); if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; blockpolicy = PFRULE_RETURN; } | SET REQUIREORDER yesno { if (pf->opts & PF_OPT_VERBOSE) printf("set require-order %s\n", $3 == 1 ? "yes" : "no"); require_order = $3; } | SET FINGERPRINTS STRING { if (pf->opts & PF_OPT_VERBOSE) printf("fingerprints %s\n", $3); if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; if (pfctl_file_fingerprints(pf->dev, pf->opts, $3)) { yyerror("error loading fingerprints %s", $3); YYERROR; } } ; string : string STRING { if (asprintf(&$$, "%s %s", $1, $2) == -1) err(1, "string: asprintf"); free($1); free($2); } | STRING ; varset : STRING '=' string { if (pf->opts & PF_OPT_VERBOSE) printf("%s = \"%s\"\n", $1, $3); if (symset($1, $3, 0) == -1) err(1, "cannot store variable %s", $1); } ; anchorrule : ANCHOR string dir interface af proto fromto { struct pf_rule r; if (check_rulestate(PFCTL_STATE_FILTER)) YYERROR; PREPARE_ANCHOR_RULE(r, $2); r.direction = $3; r.af = $5; decide_address_family($7.src.host, &r.af); decide_address_family($7.dst.host, &r.af); expand_rule(&r, $4, NULL, $6, $7.src_os, $7.src.host, $7.src.port, $7.dst.host, $7.dst.port, 0, 0, 0); } | NATANCHOR string interface af proto fromto { struct pf_rule r; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; PREPARE_ANCHOR_RULE(r, $2); r.action = PF_NAT; r.af = $4; decide_address_family($6.src.host, &r.af); decide_address_family($6.dst.host, &r.af); expand_rule(&r, $3, NULL, $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host, $6.dst.port, 0, 0, 0); } | RDRANCHOR string interface af proto fromto { struct pf_rule r; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; PREPARE_ANCHOR_RULE(r, $2); r.action = PF_RDR; r.af = $4; decide_address_family($6.src.host, &r.af); decide_address_family($6.dst.host, &r.af); if ($6.src.port != NULL) { yyerror("source port parameter not supported" " in rdr-anchor"); YYERROR; } if ($6.dst.port != NULL) { if ($6.dst.port->next != NULL) { yyerror("destination port list " "expansion not supported in " "rdr-anchor"); YYERROR; } else if ($6.dst.port->op != PF_OP_EQ) { yyerror("destination port operators" " not supported in rdr-anchor"); YYERROR; } r.dst.port[0] = $6.dst.port->port[0]; r.dst.port[1] = $6.dst.port->port[1]; r.dst.port_op = $6.dst.port->op; } expand_rule(&r, $3, NULL, $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host, $6.dst.port, 0, 0, 0); } | BINATANCHOR string interface af proto fromto { struct pf_rule r; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; PREPARE_ANCHOR_RULE(r, $2); r.action = PF_BINAT; r.af = $4; if ($5 != NULL) { if ($5->next != NULL) { yyerror("proto list expansion" " not supported in binat-anchor"); YYERROR; } r.proto = $5->proto; free($5); } if ($6.src.host != NULL || $6.src.port != NULL || $6.dst.host != NULL || $6.dst.port != NULL) { yyerror("fromto parameter not supported" " in binat-anchor"); YYERROR; } decide_address_family($6.src.host, &r.af); decide_address_family($6.dst.host, &r.af); pfctl_add_rule(pf, &r); } ; loadrule : LOAD ANCHOR string FROM string { char *t; struct loadanchors *loadanchor; t = strsep(&$3, ":"); if (*t == '\0' || *$3 == '\0') { yyerror("anchor '%s' invalid\n", $3); YYERROR; } if (strlen(t) >= PF_ANCHOR_NAME_SIZE) { yyerror("anchorname %s too long, max %u\n", t, PF_ANCHOR_NAME_SIZE - 1); YYERROR; } if (strlen($3) >= PF_RULESET_NAME_SIZE) { yyerror("rulesetname %s too long, max %u\n", $3, PF_RULESET_NAME_SIZE - 1); YYERROR; } loadanchor = calloc(1, sizeof(struct loadanchors)); if (loadanchor == NULL) err(1, "loadrule: calloc"); if ((loadanchor->anchorname = strdup(t)) == NULL) err(1, "loadrule: strdup"); if ((loadanchor->rulesetname = strdup($3)) == NULL) err(1, "loadrule: strdup"); if ((loadanchor->filename = strdup($5)) == NULL) err(1, "loadrule: strdup"); TAILQ_INSERT_TAIL(&loadanchorshead, loadanchor, entries); free(t); /* not $3 */ free($5); }; scrubrule : SCRUB dir logquick interface af proto fromto scrub_opts { struct pf_rule r; if (check_rulestate(PFCTL_STATE_SCRUB)) YYERROR; memset(&r, 0, sizeof(r)); r.action = PF_SCRUB; r.direction = $2; r.log = $3.log; if ($3.quick) { yyerror("scrub rules do not support 'quick'"); YYERROR; } if ($4) { if ($4->not) { yyerror("scrub rules do not support " "'! '"); YYERROR; } } r.af = $5; if ($8.nodf) r.rule_flag |= PFRULE_NODF; if ($8.randomid) r.rule_flag |= PFRULE_RANDOMID; if ($8.reassemble_tcp) { if (r.direction != PF_INOUT) { yyerror("reassemble tcp rules can not " "specify direction"); YYERROR; } r.rule_flag |= PFRULE_REASSEMBLE_TCP; } if ($8.minttl) r.min_ttl = $8.minttl; if ($8.maxmss) r.max_mss = $8.maxmss; if ($8.fragcache) r.rule_flag |= $8.fragcache; expand_rule(&r, $4, NULL, $6, $7.src_os, $7.src.host, $7.src.port, $7.dst.host, $7.dst.port, NULL, NULL, NULL); } ; scrub_opts : { bzero(&scrub_opts, sizeof scrub_opts); } scrub_opts_l { $$ = scrub_opts; } | /* empty */ { bzero(&scrub_opts, sizeof scrub_opts); $$ = scrub_opts; } ; scrub_opts_l : scrub_opts_l scrub_opt | scrub_opt ; scrub_opt : NODF { if (scrub_opts.nodf) { yyerror("no-df cannot be respecified"); YYERROR; } scrub_opts.nodf = 1; } | MINTTL number { if (scrub_opts.marker & SOM_MINTTL) { yyerror("min-ttl cannot be respecified"); YYERROR; } if ($2 > 255) { yyerror("illegal min-ttl value %d", $2); YYERROR; } scrub_opts.marker |= SOM_MINTTL; scrub_opts.minttl = $2; } | MAXMSS number { if (scrub_opts.marker & SOM_MAXMSS) { yyerror("max-mss cannot be respecified"); YYERROR; } if ($2 > 65535) { yyerror("illegal max-mss value %d", $2); YYERROR; } scrub_opts.marker |= SOM_MAXMSS; scrub_opts.maxmss = $2; } | fragcache { if (scrub_opts.marker & SOM_FRAGCACHE) { yyerror("fragcache cannot be respecified"); YYERROR; } scrub_opts.marker |= SOM_FRAGCACHE; scrub_opts.fragcache = $1; } | REASSEMBLE STRING { if (strcasecmp($2, "tcp") != 0) YYERROR; if (scrub_opts.reassemble_tcp) { yyerror("reassemble tcp cannot be respecified"); YYERROR; } scrub_opts.reassemble_tcp = 1; } | RANDOMID { if (scrub_opts.randomid) { yyerror("random-id cannot be respecified"); YYERROR; } scrub_opts.randomid = 1; } ; fragcache : FRAGMENT REASSEMBLE { $$ = 0; /* default */ } | FRAGMENT FRAGCROP { $$ = PFRULE_FRAGCROP; } | FRAGMENT FRAGDROP { $$ = PFRULE_FRAGDROP; } ; antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts { struct pf_rule r; struct node_host *h = NULL; struct node_if *i, *j; if (check_rulestate(PFCTL_STATE_FILTER)) YYERROR; for (i = $3; i; i = i->next) { bzero(&r, sizeof(r)); r.action = PF_DROP; r.direction = PF_IN; r.log = $2.log; r.quick = $2.quick; r.af = $4; if (rule_label(&r, $5.label)) YYERROR; j = calloc(1, sizeof(struct node_if)); if (j == NULL) err(1, "antispoof: calloc"); if (strlcpy(j->ifname, i->ifname, sizeof(j->ifname)) >= sizeof(j->ifname)) { free(j); yyerror("interface name too long"); YYERROR; } j->not = 1; h = ifa_lookup(j->ifname, PFCTL_IFLOOKUP_NET); expand_rule(&r, j, NULL, NULL, NULL, h, NULL, NULL, NULL, NULL, NULL, NULL); if ((i->ifa_flags & IFF_LOOPBACK) == 0) { bzero(&r, sizeof(r)); r.action = PF_DROP; r.direction = PF_IN; r.log = $2.log; r.quick = $2.quick; r.af = $4; if (rule_label(&r, $5.label)) YYERROR; h = ifa_lookup(i->ifname, PFCTL_IFLOOKUP_HOST); expand_rule(&r, NULL, NULL, NULL, NULL, h, NULL, NULL, NULL, NULL, NULL, NULL); } } free($5.label); } ; antispoof_ifspc : FOR if_item { $$ = $2; } | FOR '{' antispoof_iflst '}' { $$ = $3; } ; antispoof_iflst : if_item { $$ = $1; } | antispoof_iflst comma if_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; antispoof_opts : { bzero(&antispoof_opts, sizeof antispoof_opts); } antispoof_opts_l { $$ = antispoof_opts; } | /* empty */ { bzero(&antispoof_opts, sizeof antispoof_opts); $$ = antispoof_opts; } ; antispoof_opts_l : antispoof_opts_l antispoof_opt | antispoof_opt ; antispoof_opt : label { if (antispoof_opts.label) { yyerror("label cannot be redefined"); YYERROR; } antispoof_opts.label = $1; } ; not : '!' { $$ = 1; } | /* empty */ { $$ = 0; } tabledef : TABLE '<' STRING '>' table_opts { struct node_host *h, *nh; struct node_tinit *ti, *nti; if (strlen($3) >= PF_TABLE_NAME_SIZE) { yyerror("table name too long, max %d chars", PF_TABLE_NAME_SIZE - 1); YYERROR; } if (pf->loadopt & PFCTL_FLAG_TABLE) if (process_tabledef($3, &$5)) YYERROR; for (ti = SIMPLEQ_FIRST(&$5.init_nodes); ti != SIMPLEQ_END(&$5.init_nodes); ti = nti) { if (ti->file) free(ti->file); for (h = ti->host; h != NULL; h = nh) { nh = h->next; free(h); } nti = SIMPLEQ_NEXT(ti, entries); free (ti); } } ; table_opts : { bzero(&table_opts, sizeof table_opts); SIMPLEQ_INIT(&table_opts.init_nodes); } table_opts_l { $$ = table_opts; } | /* empty */ { bzero(&table_opts, sizeof table_opts); SIMPLEQ_INIT(&table_opts.init_nodes); $$ = table_opts; } ; table_opts_l : table_opts_l table_opt | table_opt ; table_opt : STRING { if (!strcmp($1, "const")) table_opts.flags |= PFR_TFLAG_CONST; else if (!strcmp($1, "persist")) table_opts.flags |= PFR_TFLAG_PERSIST; else YYERROR; } | '{' '}' { table_opts.init_addr = 1; } | '{' host_list '}' { struct node_host *n; struct node_tinit *ti; for (n = $2; n != NULL; n = n->next) { switch(n->addr.type) { case PF_ADDR_ADDRMASK: continue; /* ok */ case PF_ADDR_DYNIFTL: yyerror("dynamic addresses are not " "permitted inside tables"); break; case PF_ADDR_TABLE: yyerror("tables cannot contain tables"); break; case PF_ADDR_NOROUTE: yyerror("\"no-route\" is not permitted " "inside tables"); break; default: yyerror("unknown address type %d", n->addr.type); } YYERROR; } if (!(ti = calloc(1, sizeof(*ti)))) err(1, "table_opt: calloc"); ti->host = $2; SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, entries); table_opts.init_addr = 1; } | FILENAME STRING { struct node_tinit *ti; if (!(ti = calloc(1, sizeof(*ti)))) err(1, "table_opt: calloc"); ti->file = $2; SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, entries); table_opts.init_addr = 1; } ; altqif : ALTQ interface queue_opts QUEUE qassign { struct pf_altq a; if (check_rulestate(PFCTL_STATE_QUEUE)) YYERROR; memset(&a, 0, sizeof(a)); if ($3.scheduler.qtype == ALTQT_NONE) { yyerror("no scheduler specified!"); YYERROR; } a.scheduler = $3.scheduler.qtype; a.qlimit = $3.qlimit; a.tbrsize = $3.tbrsize; if ($5 == NULL) { yyerror("no child queues specified"); YYERROR; } if (expand_altq(&a, $2, $5, $3.queue_bwspec, &$3.scheduler)) YYERROR; } ; queuespec : QUEUE STRING interface queue_opts qassign { struct pf_altq a; if (check_rulestate(PFCTL_STATE_QUEUE)) YYERROR; memset(&a, 0, sizeof(a)); if (strlcpy(a.qname, $2, sizeof(a.qname)) >= sizeof(a.qname)) { yyerror("queue name too long (max " "%d chars)", PF_QNAME_SIZE-1); YYERROR; } if ($4.tbrsize) { yyerror("cannot specify tbrsize for queue"); YYERROR; } if ($4.priority > 255) { yyerror("priority out of range: max 255"); YYERROR; } a.priority = $4.priority; a.qlimit = $4.qlimit; a.scheduler = $4.scheduler.qtype; if (expand_queue(&a, $3, $5, $4.queue_bwspec, &$4.scheduler)) { yyerror("errors in queue definition"); YYERROR; } } ; queue_opts : { bzero(&queue_opts, sizeof queue_opts); queue_opts.priority = DEFAULT_PRIORITY; queue_opts.qlimit = DEFAULT_QLIMIT; queue_opts.scheduler.qtype = ALTQT_NONE; queue_opts.queue_bwspec.bw_percent = 100; } queue_opts_l { $$ = queue_opts; } | /* empty */ { bzero(&queue_opts, sizeof queue_opts); queue_opts.priority = DEFAULT_PRIORITY; queue_opts.qlimit = DEFAULT_QLIMIT; queue_opts.scheduler.qtype = ALTQT_NONE; queue_opts.queue_bwspec.bw_percent = 100; $$ = queue_opts; } ; queue_opts_l : queue_opts_l queue_opt | queue_opt ; queue_opt : BANDWIDTH bandwidth { if (queue_opts.marker & QOM_BWSPEC) { yyerror("bandwidth cannot be respecified"); YYERROR; } queue_opts.marker |= QOM_BWSPEC; queue_opts.queue_bwspec = $2; } | PRIORITY number { if (queue_opts.marker & QOM_PRIORITY) { yyerror("priority cannot be respecified"); YYERROR; } if ($2 > 255) { yyerror("priority out of range: max 255"); YYERROR; } queue_opts.marker |= QOM_PRIORITY; queue_opts.priority = $2; } | QLIMIT number { if (queue_opts.marker & QOM_QLIMIT) { yyerror("qlimit cannot be respecified"); YYERROR; } if ($2 > 65535) { yyerror("qlimit out of range: max 65535"); YYERROR; } queue_opts.marker |= QOM_QLIMIT; queue_opts.qlimit = $2; } | scheduler { if (queue_opts.marker & QOM_SCHEDULER) { yyerror("scheduler cannot be respecified"); YYERROR; } queue_opts.marker |= QOM_SCHEDULER; queue_opts.scheduler = $1; } | TBRSIZE number { if (queue_opts.marker & QOM_TBRSIZE) { yyerror("tbrsize cannot be respecified"); YYERROR; } if ($2 > 65535) { yyerror("tbrsize too big: max 65535"); YYERROR; } queue_opts.marker |= QOM_TBRSIZE; queue_opts.tbrsize = $2; } ; bandwidth : STRING { double bps; char *cp; $$.bw_percent = 0; bps = strtod($1, &cp); if (cp != NULL) { if (!strcmp(cp, "b")) ; /* nothing */ else if (!strcmp(cp, "Kb")) bps *= 1000; else if (!strcmp(cp, "Mb")) bps *= 1000 * 1000; else if (!strcmp(cp, "Gb")) bps *= 1000 * 1000 * 1000; else if (!strcmp(cp, "%")) { if (bps < 0 || bps > 100) { yyerror("bandwidth spec " "out of range"); YYERROR; } $$.bw_percent = bps; bps = 0; } else { yyerror("unknown unit %s", cp); YYERROR; } } $$.bw_absolute = (u_int32_t)bps; } scheduler : CBQ { $$.qtype = ALTQT_CBQ; $$.data.cbq_opts.flags = 0; } | CBQ '(' cbqflags_list ')' { $$.qtype = ALTQT_CBQ; $$.data.cbq_opts.flags = $3; } | PRIQ { $$.qtype = ALTQT_PRIQ; $$.data.priq_opts.flags = 0; } | PRIQ '(' priqflags_list ')' { $$.qtype = ALTQT_PRIQ; $$.data.priq_opts.flags = $3; } | HFSC { $$.qtype = ALTQT_HFSC; bzero(&$$.data.hfsc_opts, sizeof(struct node_hfsc_opts)); } | HFSC '(' hfsc_opts ')' { $$.qtype = ALTQT_HFSC; $$.data.hfsc_opts = $3; } ; cbqflags_list : cbqflags_item { $$ |= $1; } | cbqflags_list comma cbqflags_item { $$ |= $3; } ; cbqflags_item : STRING { if (!strcmp($1, "default")) $$ = CBQCLF_DEFCLASS; else if (!strcmp($1, "borrow")) $$ = CBQCLF_BORROW; else if (!strcmp($1, "red")) $$ = CBQCLF_RED; else if (!strcmp($1, "ecn")) $$ = CBQCLF_RED|CBQCLF_ECN; else if (!strcmp($1, "rio")) $$ = CBQCLF_RIO; else { yyerror("unknown cbq flag \"%s\"", $1); YYERROR; } } ; priqflags_list : priqflags_item { $$ |= $1; } | priqflags_list comma priqflags_item { $$ |= $3; } ; priqflags_item : STRING { if (!strcmp($1, "default")) $$ = PRCF_DEFAULTCLASS; else if (!strcmp($1, "red")) $$ = PRCF_RED; else if (!strcmp($1, "ecn")) $$ = PRCF_RED|PRCF_ECN; else if (!strcmp($1, "rio")) $$ = PRCF_RIO; else { yyerror("unknown priq flag \"%s\"", $1); YYERROR; } } ; hfsc_opts : { bzero(&hfsc_opts, sizeof(struct node_hfsc_opts)); } hfscopts_list { $$ = hfsc_opts; } ; hfscopts_list : hfscopts_item | hfscopts_list comma hfscopts_item ; hfscopts_item : LINKSHARE bandwidth { if (hfsc_opts.linkshare.used) { yyerror("linkshare already specified"); YYERROR; } hfsc_opts.linkshare.m2 = $2; hfsc_opts.linkshare.used = 1; } | LINKSHARE '(' bandwidth number bandwidth ')' { if (hfsc_opts.linkshare.used) { yyerror("linkshare already specified"); YYERROR; } hfsc_opts.linkshare.m1 = $3; hfsc_opts.linkshare.d = $4; hfsc_opts.linkshare.m2 = $5; hfsc_opts.linkshare.used = 1; } | REALTIME bandwidth { if (hfsc_opts.realtime.used) { yyerror("realtime already specified"); YYERROR; } hfsc_opts.realtime.m2 = $2; hfsc_opts.realtime.used = 1; } | REALTIME '(' bandwidth number bandwidth ')' { if (hfsc_opts.realtime.used) { yyerror("realtime already specified"); YYERROR; } hfsc_opts.realtime.m1 = $3; hfsc_opts.realtime.d = $4; hfsc_opts.realtime.m2 = $5; hfsc_opts.realtime.used = 1; } | UPPERLIMIT bandwidth { if (hfsc_opts.upperlimit.used) { yyerror("upperlimit already specified"); YYERROR; } hfsc_opts.upperlimit.m2 = $2; hfsc_opts.upperlimit.used = 1; } | UPPERLIMIT '(' bandwidth number bandwidth ')' { if (hfsc_opts.upperlimit.used) { yyerror("upperlimit already specified"); YYERROR; } hfsc_opts.upperlimit.m1 = $3; hfsc_opts.upperlimit.d = $4; hfsc_opts.upperlimit.m2 = $5; hfsc_opts.upperlimit.used = 1; } | STRING { if (!strcmp($1, "default")) hfsc_opts.flags |= HFCF_DEFAULTCLASS; else if (!strcmp($1, "red")) hfsc_opts.flags |= HFCF_RED; else if (!strcmp($1, "ecn")) hfsc_opts.flags |= HFCF_RED|HFCF_ECN; else if (!strcmp($1, "rio")) hfsc_opts.flags |= HFCF_RIO; else { yyerror("unknown hfsc flag \"%s\"", $1); YYERROR; } } ; qassign : /* empty */ { $$ = NULL; } | qassign_item { $$ = $1; } | '{' qassign_list '}' { $$ = $2; } ; qassign_list : qassign_item { $$ = $1; } | qassign_list comma qassign_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; qassign_item : STRING { $$ = calloc(1, sizeof(struct node_queue)); if ($$ == NULL) err(1, "qassign_item: calloc"); if (strlcpy($$->queue, $1, sizeof($$->queue)) >= sizeof($$->queue)) { free($$); yyerror("queue name '%s' too long (max " "%d chars)", $1, sizeof($$->queue)-1); YYERROR; } $$->next = NULL; $$->tail = $$; } ; pfrule : action dir logquick interface route af proto fromto filter_opts { struct pf_rule r; struct node_state_opt *o; struct node_proto *proto; if (check_rulestate(PFCTL_STATE_FILTER)) YYERROR; memset(&r, 0, sizeof(r)); r.action = $1.b1; switch ($1.b2) { case PFRULE_RETURNRST: r.rule_flag |= PFRULE_RETURNRST; r.return_ttl = $1.w; break; case PFRULE_RETURNICMP: r.rule_flag |= PFRULE_RETURNICMP; r.return_icmp = $1.w; r.return_icmp6 = $1.w2; break; case PFRULE_RETURN: r.rule_flag |= PFRULE_RETURN; r.return_icmp = $1.w; r.return_icmp6 = $1.w2; break; } r.direction = $2; r.log = $3.log; r.quick = $3.quick; r.af = $6; if ($9.tag) if (strlcpy(r.tagname, $9.tag, PF_TAG_NAME_SIZE) > PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } if ($9.match_tag) if (strlcpy(r.match_tagname, $9.match_tag, PF_TAG_NAME_SIZE) > PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } r.match_tag_not = $9.match_tag_not; r.flags = $9.flags.b1; r.flagset = $9.flags.b2; if (rule_label(&r, $9.label)) YYERROR; free($9.label); if ($9.flags.b1 || $9.flags.b2 || $8.src_os) { for (proto = $7; proto != NULL && proto->proto != IPPROTO_TCP; proto = proto->next) ; /* nothing */ if (proto == NULL && $7 != NULL) { if ($9.flags.b1 || $9.flags.b2) yyerror( "flags only apply to tcp"); if ($8.src_os) yyerror( "OS fingerprinting only " "apply to tcp"); YYERROR; } #if 0 if (($9.flags.b1 & parse_flags("S")) == 0 && $8.src_os) { yyerror("OS fingerprinting requires " "the SYN TCP flag (flags S/SA)"); YYERROR; } #endif } r.tos = $9.tos; r.keep_state = $9.keep.action; o = $9.keep.options; while (o) { struct node_state_opt *p = o; switch (o->type) { case PF_STATE_OPT_MAX: if (r.max_states) { yyerror("state option 'max' " "multiple definitions"); YYERROR; } r.max_states = o->data.max_states; break; case PF_STATE_OPT_TIMEOUT: if (r.timeout[o->data.timeout.number]) { yyerror("state timeout %s " "multiple definitions", pf_timeouts[o->data. timeout.number].name); YYERROR; } r.timeout[o->data.timeout.number] = o->data.timeout.seconds; } o = o->next; free(p); } if ($9.fragment) r.rule_flag |= PFRULE_FRAGMENT; r.allow_opts = $9.allowopts; decide_address_family($8.src.host, &r.af); decide_address_family($8.dst.host, &r.af); if ($5.rt) { if (!r.direction) { yyerror("direction must be explicit " "with rules that specify routing"); YYERROR; } r.rt = $5.rt; r.rpool.opts = $5.pool_opts; if ($5.key != NULL) memcpy(&r.rpool.key, $5.key, sizeof(struct pf_poolhashkey)); } if (r.rt && r.rt != PF_FASTROUTE) { decide_address_family($5.host, &r.af); remove_invalid_hosts(&$5.host, &r.af); if ($5.host == NULL) { yyerror("no routing address with " "matching address family found."); YYERROR; } if (r.rpool.opts == PF_POOL_NONE && ( $5.host->next != NULL || $5.host->addr.type == PF_ADDR_TABLE)) r.rpool.opts = PF_POOL_ROUNDROBIN; if (r.rpool.opts != PF_POOL_ROUNDROBIN) if (disallow_table($5.host, "tables " "are only supported in round-robin " "routing pools")) YYERROR; if ($5.host->next != NULL) { if (r.rpool.opts != PF_POOL_ROUNDROBIN) { yyerror("r.rpool.opts must " "be PF_POOL_ROUNDROBIN"); YYERROR; } } } if ($9.queues.qname != NULL) { if (strlcpy(r.qname, $9.queues.qname, sizeof(r.qname)) >= sizeof(r.qname)) { yyerror("rule qname too long (max " "%d chars)", sizeof(r.qname)-1); YYERROR; } free($9.queues.qname); } if ($9.queues.pqname != NULL) { if (strlcpy(r.pqname, $9.queues.pqname, sizeof(r.pqname)) >= sizeof(r.pqname)) { yyerror("rule pqname too long (max " "%d chars)", sizeof(r.pqname)-1); YYERROR; } free($9.queues.pqname); } expand_rule(&r, $4, $5.host, $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host, $8.dst.port, $9.uid, $9.gid, $9.icmpspec); } ; filter_opts : { bzero(&filter_opts, sizeof filter_opts); } filter_opts_l { $$ = filter_opts; } | /* empty */ { bzero(&filter_opts, sizeof filter_opts); $$ = filter_opts; } ; filter_opts_l : filter_opts_l filter_opt | filter_opt ; filter_opt : USER uids { if (filter_opts.uid) $2->tail->next = filter_opts.uid; filter_opts.uid = $2; } | GROUP gids { if (filter_opts.gid) $2->tail->next = filter_opts.gid; filter_opts.gid = $2; } | flags { if (filter_opts.marker & FOM_FLAGS) { yyerror("flags cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_FLAGS; filter_opts.flags.b1 |= $1.b1; filter_opts.flags.b2 |= $1.b2; filter_opts.flags.w |= $1.w; filter_opts.flags.w2 |= $1.w2; } | icmpspec { if (filter_opts.marker & FOM_ICMP) { yyerror("icmp-type cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_ICMP; filter_opts.icmpspec = $1; } | tos { if (filter_opts.marker & FOM_TOS) { yyerror("tos cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_TOS; filter_opts.tos = $1; } | keep { if (filter_opts.marker & FOM_KEEP) { yyerror("modulate or keep cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_KEEP; filter_opts.keep.action = $1.action; filter_opts.keep.options = $1.options; } | FRAGMENT { filter_opts.fragment = 1; } | ALLOWOPTS { filter_opts.allowopts = 1; } | label { if (filter_opts.label) { yyerror("label cannot be redefined"); YYERROR; } filter_opts.label = $1; } | qname { if (filter_opts.queues.qname) { yyerror("queue cannot be redefined"); YYERROR; } filter_opts.queues = $1; } | TAG string { filter_opts.tag = $2; } | not TAGGED string { filter_opts.match_tag = $3; filter_opts.match_tag_not = $1; } ; action : PASS { $$.b1 = PF_PASS; $$.b2 = $$.w = 0; } | BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; } ; blockspec : /* empty */ { $$.b2 = blockpolicy; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } | DROP { $$.b2 = PFRULE_DROP; $$.w = 0; $$.w2 = 0; } | RETURNRST { $$.b2 = PFRULE_RETURNRST; $$.w = 0; $$.w2 = 0; } | RETURNRST '(' TTL number ')' { if ($4 > 255) { yyerror("illegal ttl value %d", $4); YYERROR; } $$.b2 = PFRULE_RETURNRST; $$.w = $4; $$.w2 = 0; } | RETURNICMP { $$.b2 = PFRULE_RETURNICMP; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } | RETURNICMP6 { $$.b2 = PFRULE_RETURNICMP; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } | RETURNICMP '(' STRING ')' { $$.b2 = PFRULE_RETURNICMP; if (!($$.w = parseicmpspec($3, AF_INET))) YYERROR; $$.w2 = returnicmp6default; } | RETURNICMP6 '(' STRING ')' { $$.b2 = PFRULE_RETURNICMP; $$.w = returnicmpdefault; if (!($$.w2 = parseicmpspec($3, AF_INET6))) YYERROR; } | RETURNICMP '(' STRING comma STRING ')' { $$.b2 = PFRULE_RETURNICMP; if (!($$.w = parseicmpspec($3, AF_INET))) YYERROR; if (!($$.w2 = parseicmpspec($5, AF_INET6))) YYERROR; } | RETURN { $$.b2 = PFRULE_RETURN; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } ; dir : /* empty */ { $$ = 0; } | IN { $$ = PF_IN; } | OUT { $$ = PF_OUT; } ; logquick : /* empty */ { $$.log = 0; $$.quick = 0; } | log { $$.log = $1; $$.quick = 0; } | QUICK { $$.log = 0; $$.quick = 1; } | log QUICK { $$.log = $1; $$.quick = 1; } | QUICK log { $$.log = $2; $$.quick = 1; } ; log : LOG { $$ = 1; } | LOGALL { $$ = 2; } ; interface : /* empty */ { $$ = NULL; } | ON if_item_not { $$ = $2; } | ON '{' if_list '}' { $$ = $3; } ; if_list : if_item_not { $$ = $1; } | if_list comma if_item_not { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; if_item_not : not if_item { $$ = $2; $$->not = $1; } ; if_item : STRING { struct node_host *n; if ((n = ifa_exists($1)) == NULL) { yyerror("unknown interface %s", $1); YYERROR; } $$ = calloc(1, sizeof(struct node_if)); if ($$ == NULL) err(1, "if_item: calloc"); if (strlcpy($$->ifname, $1, sizeof($$->ifname)) >= sizeof($$->ifname)) { free($$); yyerror("interface name too long"); YYERROR; } $$->ifa_flags = n->ifa_flags; $$->not = 0; $$->next = NULL; $$->tail = $$; } ; af : /* empty */ { $$ = 0; } | INET { $$ = AF_INET; } | INET6 { $$ = AF_INET6; } proto : /* empty */ { $$ = NULL; } | PROTO proto_item { $$ = $2; } | PROTO '{' proto_list '}' { $$ = $3; } ; proto_list : proto_item { $$ = $1; } | proto_list comma proto_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; proto_item : STRING { u_int8_t pr; u_long ulval; if (atoul($1, &ulval) == 0) { if (ulval > 255) { yyerror("protocol outside range"); YYERROR; } pr = (u_int8_t)ulval; } else { struct protoent *p; p = getprotobyname($1); if (p == NULL) { yyerror("unknown protocol %s", $1); YYERROR; } pr = p->p_proto; } if (pr == 0) { yyerror("proto 0 cannot be used"); YYERROR; } $$ = calloc(1, sizeof(struct node_proto)); if ($$ == NULL) err(1, "proto_item: calloc"); $$->proto = pr; $$->next = NULL; $$->tail = $$; } ; fromto : ALL { $$.src.host = NULL; $$.src.port = NULL; $$.dst.host = NULL; $$.dst.port = NULL; $$.src_os = NULL; } | from os to { $$.src = $1; $$.src_os = $2; $$.dst = $3; } ; os : /* empty */ { $$ = NULL; } | OS xos { $$ = $2; } | OS '{' os_list '}' { $$ = $3; } ; xos : STRING { $$ = calloc(1, sizeof(struct node_os)); if ($$ == NULL) err(1, "os: calloc"); $$->os = $1; $$->tail = $$; } ; os_list : xos { $$ = $1; } | os_list comma xos { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; from : /* empty */ { $$.host = NULL; $$.port = NULL; } | FROM ipportspec { $$ = $2; } ; to : /* empty */ { $$.host = NULL; $$.port = NULL; } | TO ipportspec { $$ = $2; } ; ipportspec : ipspec { $$.host = $1; $$.port = NULL; } | ipspec PORT portspec { $$.host = $1; $$.port = $3; } | PORT portspec { $$.host = NULL; $$.port = $2; } ; ipspec : ANY { $$ = NULL; } | xhost { $$ = $1; } | '{' host_list '}' { $$ = $2; } ; host_list : xhost { $$ = $1; } | host_list comma xhost { if ($3 == NULL) $$ = $1; else if ($1 == NULL) $$ = $3; else { $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; } } ; xhost : not host { struct node_host *n; for (n = $2; n != NULL; n = n->next) n->not = $1; $$ = $2; } | NOROUTE { $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "xhost: calloc"); $$->addr.type = PF_ADDR_NOROUTE; $$->next = NULL; $$->tail = $$; } ; host : STRING { if (($$ = host($1)) == NULL) { /* error. "any" is handled elsewhere */ yyerror("could not parse host specification"); YYERROR; } } | STRING '/' number { char *buf; if (asprintf(&buf, "%s/%u", $1, $3) == -1) err(1, "host: asprintf"); if (($$ = host(buf)) == NULL) { /* error. "any" is handled elsewhere */ free(buf); yyerror("could not parse host specification"); YYERROR; } free(buf); } | dynaddr | dynaddr '/' number { struct node_host *n; $$ = $1; for (n = $1; n != NULL; n = n->next) set_ipmask(n, $3); } | '<' STRING '>' { if (strlen($2) >= PF_TABLE_NAME_SIZE) { yyerror("table name '%s' too long"); YYERROR; } $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "host: calloc"); $$->addr.type = PF_ADDR_TABLE; if (strlcpy($$->addr.v.tblname, $2, sizeof($$->addr.v.tblname)) >= sizeof($$->addr.v.tblname)) errx(1, "host: strlcpy"); $$->next = NULL; $$->tail = $$; } ; number : STRING { u_long ulval; if (atoul($1, &ulval) == -1) { yyerror("%s is not a number", $1); YYERROR; } else $$ = ulval; } ; dynaddr : '(' STRING ')' { if (ifa_exists($2) == NULL) { yyerror("interface %s does not exist", $2); YYERROR; } $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "address: calloc"); $$->af = 0; set_ipmask($$, 128); $$->addr.type = PF_ADDR_DYNIFTL; if (strlcpy($$->addr.v.ifname, $2, sizeof($$->addr.v.ifname)) >= sizeof($$->addr.v.ifname)) { free($$); yyerror("interface name too long"); YYERROR; } $$->next = NULL; $$->tail = $$; } ; portspec : port_item { $$ = $1; } | '{' port_list '}' { $$ = $2; } ; port_list : port_item { $$ = $1; } | port_list comma port_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; port_item : port { $$ = calloc(1, sizeof(struct node_port)); if ($$ == NULL) err(1, "port_item: calloc"); $$->port[0] = $1.a; $$->port[1] = $1.b; if ($1.t) $$->op = PF_OP_RRG; else $$->op = PF_OP_EQ; $$->next = NULL; $$->tail = $$; } | unaryop port { if ($2.t) { yyerror("':' cannot be used with an other " "port operator"); YYERROR; } $$ = calloc(1, sizeof(struct node_port)); if ($$ == NULL) err(1, "port_item: calloc"); $$->port[0] = $2.a; $$->port[1] = $2.b; $$->op = $1; $$->next = NULL; $$->tail = $$; } | port PORTBINARY port { if ($1.t || $3.t) { yyerror("':' cannot be used with an other " "port operator"); YYERROR; } $$ = calloc(1, sizeof(struct node_port)); if ($$ == NULL) err(1, "port_item: calloc"); $$->port[0] = $1.a; $$->port[1] = $3.a; $$->op = $2; $$->next = NULL; $$->tail = $$; } ; port : STRING { char *p = strchr($1, ':'); struct servent *s = NULL; u_long ulval; if (p == NULL) { if (atoul($1, &ulval) == 0) { if (ulval > 65535) { yyerror("illegal port value %d", ulval); YYERROR; } $$.a = htons(ulval); } else { s = getservbyname($1, "tcp"); if (s == NULL) s = getservbyname($1, "udp"); if (s == NULL) { yyerror("unknown port %s", $1); YYERROR; } $$.a = s->s_port; } $$.b = 0; $$.t = 0; } else { int port[2]; *p++ = 0; if ((port[0] = getservice($1)) == -1 || (port[1] = getservice(p)) == -1) YYERROR; $$.a = port[0]; $$.b = port[1]; $$.t = PF_OP_RRG; } } ; uids : uid_item { $$ = $1; } | '{' uid_list '}' { $$ = $2; } ; uid_list : uid_item { $$ = $1; } | uid_list comma uid_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; uid_item : uid { $$ = calloc(1, sizeof(struct node_uid)); if ($$ == NULL) err(1, "uid_item: calloc"); $$->uid[0] = $1; $$->uid[1] = $1; $$->op = PF_OP_EQ; $$->next = NULL; $$->tail = $$; } | unaryop uid { if ($2 == UID_MAX && $1 != PF_OP_EQ && $1 != PF_OP_NE) { yyerror("user unknown requires operator = or " "!="); YYERROR; } $$ = calloc(1, sizeof(struct node_uid)); if ($$ == NULL) err(1, "uid_item: calloc"); $$->uid[0] = $2; $$->uid[1] = $2; $$->op = $1; $$->next = NULL; $$->tail = $$; } | uid PORTBINARY uid { if ($1 == UID_MAX || $3 == UID_MAX) { yyerror("user unknown requires operator = or " "!="); YYERROR; } $$ = calloc(1, sizeof(struct node_uid)); if ($$ == NULL) err(1, "uid_item: calloc"); $$->uid[0] = $1; $$->uid[1] = $3; $$->op = $2; $$->next = NULL; $$->tail = $$; } ; uid : STRING { u_long ulval; if (atoul($1, &ulval) == -1) { if (!strcmp($1, "unknown")) $$ = UID_MAX; else { struct passwd *pw; if ((pw = getpwnam($1)) == NULL) { yyerror("unknown user %s", $1); YYERROR; } $$ = pw->pw_uid; } } else { if (ulval >= UID_MAX) { yyerror("illegal uid value %lu", ulval); YYERROR; } $$ = ulval; } } ; gids : gid_item { $$ = $1; } | '{' gid_list '}' { $$ = $2; } ; gid_list : gid_item { $$ = $1; } | gid_list comma gid_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; gid_item : gid { $$ = calloc(1, sizeof(struct node_gid)); if ($$ == NULL) err(1, "gid_item: calloc"); $$->gid[0] = $1; $$->gid[1] = $1; $$->op = PF_OP_EQ; $$->next = NULL; $$->tail = $$; } | unaryop gid { if ($2 == GID_MAX && $1 != PF_OP_EQ && $1 != PF_OP_NE) { yyerror("group unknown requires operator = or " "!="); YYERROR; } $$ = calloc(1, sizeof(struct node_gid)); if ($$ == NULL) err(1, "gid_item: calloc"); $$->gid[0] = $2; $$->gid[1] = $2; $$->op = $1; $$->next = NULL; $$->tail = $$; } | gid PORTBINARY gid { if ($1 == GID_MAX || $3 == GID_MAX) { yyerror("group unknown requires operator = or " "!="); YYERROR; } $$ = calloc(1, sizeof(struct node_gid)); if ($$ == NULL) err(1, "gid_item: calloc"); $$->gid[0] = $1; $$->gid[1] = $3; $$->op = $2; $$->next = NULL; $$->tail = $$; } ; gid : STRING { u_long ulval; if (atoul($1, &ulval) == -1) { if (!strcmp($1, "unknown")) $$ = GID_MAX; else { struct group *grp; if ((grp = getgrnam($1)) == NULL) { yyerror("unknown group %s", $1); YYERROR; } $$ = grp->gr_gid; } } else { if (ulval >= GID_MAX) { yyerror("illegal gid value %lu", ulval); YYERROR; } $$ = ulval; } } ; flag : STRING { int f; if ((f = parse_flags($1)) < 0) { yyerror("bad flags %s", $1); YYERROR; } $$.b1 = f; } ; flags : FLAGS flag '/' flag { $$.b1 = $2.b1; $$.b2 = $4.b1; } | FLAGS '/' flag { $$.b1 = 0; $$.b2 = $3.b1; } ; icmpspec : ICMPTYPE icmp_item { $$ = $2; } | ICMPTYPE '{' icmp_list '}' { $$ = $3; } | ICMP6TYPE icmp6_item { $$ = $2; } | ICMP6TYPE '{' icmp6_list '}' { $$ = $3; } ; icmp_list : icmp_item { $$ = $1; } | icmp_list comma icmp_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; icmp6_list : icmp6_item { $$ = $1; } | icmp6_list comma icmp6_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; icmp_item : icmptype { $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = 0; $$->proto = IPPROTO_ICMP; $$->next = NULL; $$->tail = $$; } | icmptype CODE STRING { const struct icmpcodeent *p; u_long ulval; if (atoul($3, &ulval) == 0) { if (ulval > 255) { yyerror("illegal icmp-code %d", ulval); YYERROR; } } else { if ((p = geticmpcodebyname($1-1, $3, AF_INET)) == NULL) { yyerror("unknown icmp-code %s", $3); YYERROR; } ulval = p->code; } $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = ulval + 1; $$->proto = IPPROTO_ICMP; $$->next = NULL; $$->tail = $$; } ; icmp6_item : icmp6type { $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = 0; $$->proto = IPPROTO_ICMPV6; $$->next = NULL; $$->tail = $$; } | icmp6type CODE STRING { const struct icmpcodeent *p; u_long ulval; if (atoul($3, &ulval) == 0) { if (ulval > 255) { yyerror("illegal icmp6-code %ld", ulval); YYERROR; } } else { if ((p = geticmpcodebyname($1-1, $3, AF_INET6)) == NULL) { yyerror("unknown icmp6-code %s", $3); YYERROR; } ulval = p->code; } $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = ulval + 1; $$->proto = IPPROTO_ICMPV6; $$->next = NULL; $$->tail = $$; } ; icmptype : STRING { const struct icmptypeent *p; u_long ulval; if (atoul($1, &ulval) == 0) { if (ulval > 255) { yyerror("illegal icmp-type %d", ulval); YYERROR; } $$ = ulval + 1; } else { if ((p = geticmptypebyname($1, AF_INET)) == NULL) { yyerror("unknown icmp-type %s", $1); YYERROR; } $$ = p->type + 1; } } ; icmp6type : STRING { const struct icmptypeent *p; u_long ulval; if (atoul($1, &ulval) == 0) { if (ulval > 255) { yyerror("illegal icmp6-type %d", ulval); YYERROR; } $$ = ulval + 1; } else { if ((p = geticmptypebyname($1, AF_INET6)) == NULL) { yyerror("unknown icmp6-type %s", $1); YYERROR; } $$ = p->type + 1; } } ; tos : TOS STRING { if (!strcmp($2, "lowdelay")) $$ = IPTOS_LOWDELAY; else if (!strcmp($2, "throughput")) $$ = IPTOS_THROUGHPUT; else if (!strcmp($2, "reliability")) $$ = IPTOS_RELIABILITY; else if ($2[0] == '0' && $2[1] == 'x') $$ = strtoul($2, NULL, 16); else $$ = strtoul($2, NULL, 10); if (!$$ || $$ > 255) { yyerror("illegal tos value %s", $2); YYERROR; } } ; keep : KEEP STATE state_opt_spec { $$.action = PF_STATE_NORMAL; $$.options = $3; } | MODULATE STATE state_opt_spec { $$.action = PF_STATE_MODULATE; $$.options = $3; } | SYNPROXY STATE state_opt_spec { $$.action = PF_STATE_SYNPROXY; $$.options = $3; } ; state_opt_spec : '(' state_opt_list ')' { $$ = $2; } | /* empty */ { $$ = NULL; } ; state_opt_list : state_opt_item { $$ = $1; } | state_opt_list comma state_opt_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; state_opt_item : MAXIMUM number { $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_MAX; $$->data.max_states = $2; $$->next = NULL; $$->tail = $$; } | STRING number { int i; for (i = 0; pf_timeouts[i].name && strcmp(pf_timeouts[i].name, $1); ++i) ; /* nothing */ if (!pf_timeouts[i].name) { yyerror("illegal timeout name %s", $1); YYERROR; } if (strchr(pf_timeouts[i].name, '.') == NULL) { yyerror("illegal state timeout %s", $1); YYERROR; } $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_TIMEOUT; $$->data.timeout.number = pf_timeouts[i].timeout; $$->data.timeout.seconds = $2; $$->next = NULL; $$->tail = $$; } ; label : LABEL STRING { if (($$ = strdup($2)) == NULL) err(1, "rule label strdup() failed"); } ; qname : QUEUE STRING { if (($$.qname = strdup($2)) == NULL) err(1, "qname strdup() failed"); } | QUEUE '(' STRING ')' { if (($$.qname = strdup($3)) == NULL) err(1, "qname strdup() failed"); } | QUEUE '(' STRING comma STRING ')' { if (($$.qname = strdup($3)) == NULL || ($$.pqname = strdup($5)) == NULL) err(1, "qname strdup() failed"); } ; no : /* empty */ { $$ = 0; } | NO { $$ = 1; } ; rport : STRING { char *p = strchr($1, ':'); if (p == NULL) { if (($$.a = getservice($1)) == -1) YYERROR; $$.b = $$.t = 0; } else if (!strcmp(p+1, "*")) { *p = 0; if (($$.a = getservice($1)) == -1) YYERROR; $$.b = 0; $$.t = 1; } else { *p++ = 0; if (($$.a = getservice($1)) == -1 || ($$.b = getservice(p)) == -1) YYERROR; if ($$.a == $$.b) $$.b = 0; $$.t = 0; } } ; redirspec : host { $$ = $1; } | '{' redir_host_list '}' { $$ = $2; } ; redir_host_list : host { $$ = $1; } | redir_host_list comma host { $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; } ; redirpool : /* empty */ { $$ = NULL; } | ARROW redirspec { $$ = calloc(1, sizeof(struct redirection)); if ($$ == NULL) err(1, "redirection: calloc"); $$->host = $2; $$->rport.a = $$->rport.b = $$->rport.t = 0; } | ARROW redirspec PORT rport { $$ = calloc(1, sizeof(struct redirection)); if ($$ == NULL) err(1, "redirection: calloc"); $$->host = $2; $$->rport = $4; } ; hashkey : /* empty */ { $$ = calloc(1, sizeof(struct pf_poolhashkey)); if ($$ == NULL) err(1, "hashkey: calloc"); $$->key32[0] = arc4random(); $$->key32[1] = arc4random(); $$->key32[2] = arc4random(); $$->key32[3] = arc4random(); } | string { if (!strncmp($1, "0x", 2)) { if (strlen($1) != 34) { yyerror("hex key must be 128 bits " "(32 hex digits) long"); YYERROR; } $$ = calloc(1, sizeof(struct pf_poolhashkey)); if ($$ == NULL) err(1, "hashkey: calloc"); if (sscanf($1, "0x%8x%8x%8x%8x", &$$->key32[0], &$$->key32[1], &$$->key32[2], &$$->key32[3]) != 4) { free($$); yyerror("invalid hex key"); YYERROR; } } else { MD5_CTX context; $$ = calloc(1, sizeof(struct pf_poolhashkey)); if ($$ == NULL) err(1, "hashkey: calloc"); MD5Init(&context); MD5Update(&context, (unsigned char *)$1, strlen($1)); MD5Final((unsigned char *)$$, &context); HTONL($$->key32[0]); HTONL($$->key32[1]); HTONL($$->key32[2]); HTONL($$->key32[3]); } } ; pooltype : /* empty */ { $$.type = PF_POOL_NONE; $$.key = NULL; } | BITMASK { $$.type = PF_POOL_BITMASK; $$.key = NULL; } | RANDOM { $$.type = PF_POOL_RANDOM; $$.key = NULL; } | SOURCEHASH hashkey { $$.type = PF_POOL_SRCHASH; $$.key = $2; } | ROUNDROBIN { $$.type = PF_POOL_ROUNDROBIN; $$.key = NULL; } ; staticport : /* empty */ { $$ = 0; } | STATICPORT { $$ = 1; } ; redirection : /* empty */ { $$ = NULL; } | ARROW host { $$ = calloc(1, sizeof(struct redirection)); if ($$ == NULL) err(1, "redirection: calloc"); $$->host = $2; $$->rport.a = $$->rport.b = $$->rport.t = 0; } | ARROW host PORT rport { $$ = calloc(1, sizeof(struct redirection)); if ($$ == NULL) err(1, "redirection: calloc"); $$->host = $2; $$->rport = $4; } ; natpass : /* empty */ { $$ = 0; } | PASS { $$ = 1; } ; nataction : no NAT natpass { $$.b2 = $$.w = 0; if ($1) $$.b1 = PF_NONAT; else $$.b1 = PF_NAT; $$.b2 = $3; } | no RDR natpass { $$.b2 = $$.w = 0; if ($1) $$.b1 = PF_NORDR; else $$.b1 = PF_RDR; $$.b2 = $3; } ; natrule : nataction interface af proto fromto tag redirpool pooltype staticport { struct pf_rule r; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; memset(&r, 0, sizeof(r)); r.action = $1.b1; r.natpass = $1.b2; r.af = $3; if (!r.af) { if ($5.src.host && $5.src.host->af && !$5.src.host->ifindex) r.af = $5.src.host->af; else if ($5.dst.host && $5.dst.host->af && !$5.dst.host->ifindex) r.af = $5.dst.host->af; } if ($6 != NULL) if (strlcpy(r.tagname, $6, PF_TAG_NAME_SIZE) > PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } if (r.action == PF_NONAT || r.action == PF_NORDR) { if ($7 != NULL) { yyerror("translation rule with 'no' " "does not need '->'"); YYERROR; } } else { if ($7 == NULL || $7->host == NULL) { yyerror("translation rule requires '-> " "address'"); YYERROR; } if (!r.af && ! $7->host->ifindex) r.af = $7->host->af; remove_invalid_hosts(&$7->host, &r.af); if (invalid_redirect($7->host, r.af)) YYERROR; if (check_netmask($7->host, r.af)) YYERROR; r.rpool.proxy_port[0] = ntohs($7->rport.a); switch (r.action) { case PF_RDR: if (!$7->rport.b && $7->rport.t && $5.dst.port != NULL) { r.rpool.proxy_port[1] = ntohs($7->rport.a) + (ntohs($5.dst.port->port[1]) - ntohs($5.dst.port->port[0])); } else r.rpool.proxy_port[1] = ntohs($7->rport.b); break; case PF_NAT: r.rpool.proxy_port[1] = ntohs($7->rport.b); if (!r.rpool.proxy_port[0] && !r.rpool.proxy_port[1]) { r.rpool.proxy_port[0] = PF_NAT_PROXY_PORT_LOW; r.rpool.proxy_port[1] = PF_NAT_PROXY_PORT_HIGH; } else if (!r.rpool.proxy_port[1]) r.rpool.proxy_port[1] = r.rpool.proxy_port[0]; break; default: break; } r.rpool.opts = $8.type; if (r.rpool.opts == PF_POOL_NONE) r.rpool.opts = PF_POOL_ROUNDROBIN; if (r.rpool.opts != PF_POOL_ROUNDROBIN) if (disallow_table($7->host, "tables " "are only supported in round-robin " "redirection pools")) YYERROR; if ($7->host->next) { if (r.rpool.opts != PF_POOL_ROUNDROBIN) { yyerror("only round-robin " "valid for multiple " "redirection addresses"); YYERROR; } } else { if ((r.af == AF_INET && unmask(&$7->host->addr.v.a.mask, r.af) == 32) || (r.af == AF_INET6 && unmask(&$7->host->addr.v.a.mask, r.af) == 128)) { r.rpool.opts = PF_POOL_NONE; } } } if ($8.key != NULL) memcpy(&r.rpool.key, $8.key, sizeof(struct pf_poolhashkey)); if ($9 != 0) { if (r.action != PF_NAT) { yyerror("the 'static-port' option is " "only valid with nat rules"); YYERROR; } if (r.rpool.proxy_port[0] != PF_NAT_PROXY_PORT_LOW && r.rpool.proxy_port[1] != PF_NAT_PROXY_PORT_HIGH) { yyerror("the 'static-port' option can't" " be used when specifying a port" " range"); YYERROR; } r.rpool.proxy_port[0] = 0; r.rpool.proxy_port[1] = 0; } expand_rule(&r, $2, $7 == NULL ? NULL : $7->host, $4, $5.src_os, $5.src.host, $5.src.port, $5.dst.host, $5.dst.port, 0, 0, 0); free($7); } ; binatrule : no BINAT natpass interface af proto FROM host TO ipspec tag redirection { struct pf_rule binat; struct pf_pooladdr *pa; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; memset(&binat, 0, sizeof(binat)); if ($1) binat.action = PF_NOBINAT; else binat.action = PF_BINAT; binat.natpass = $3; binat.af = $5; if (!binat.af && $8 != NULL && $8->af) binat.af = $8->af; if (!binat.af && $10 != NULL && $10->af) binat.af = $10->af; if (!binat.af && $12 != NULL && $12->host) binat.af = $12->host->af; if (!binat.af) { yyerror("address family (inet/inet6) " "undefined"); YYERROR; } if ($4 != NULL) { memcpy(binat.ifname, $4->ifname, sizeof(binat.ifname)); free($4); } if ($11 != NULL) if (strlcpy(binat.tagname, $11, PF_TAG_NAME_SIZE) > PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } if ($6 != NULL) { binat.proto = $6->proto; free($6); } if ($8 != NULL && disallow_table($8, "invalid use of " "table <%s> as the source address of a binat rule")) YYERROR; if ($12 != NULL && $12->host != NULL && disallow_table( $12->host, "invalid use of table <%s> as the " "redirect address of a binat rule")) YYERROR; if ($8 != NULL) { if ($8->next) { yyerror("multiple binat ip addresses"); YYERROR; } if ($8->addr.type == PF_ADDR_DYNIFTL) $8->af = binat.af; if ($8->af != binat.af) { yyerror("binat ip versions must match"); YYERROR; } if (check_netmask($8, binat.af)) YYERROR; memcpy(&binat.src.addr, &$8->addr, sizeof(binat.src.addr)); free($8); } if ($10 != NULL) { if ($10->next) { yyerror("multiple binat ip addresses"); YYERROR; } if ($10->af != binat.af && $10->af) { yyerror("binat ip versions must match"); YYERROR; } if (check_netmask($10, binat.af)) YYERROR; memcpy(&binat.dst.addr, &$10->addr, sizeof(binat.dst.addr)); binat.dst.not = $10->not; free($10); } if (binat.action == PF_NOBINAT) { if ($12 != NULL) { yyerror("'no binat' rule does not need" " '->'"); YYERROR; } } else { if ($12 == NULL || $12->host == NULL) { yyerror("'binat' rule requires" " '-> address'"); YYERROR; } remove_invalid_hosts(&$12->host, &binat.af); if (invalid_redirect($12->host, binat.af)) YYERROR; if ($12->host->next != NULL) { yyerror("binat rule must redirect to " "a single address"); YYERROR; } if (check_netmask($12->host, binat.af)) YYERROR; if (!PF_AZERO(&binat.src.addr.v.a.mask, binat.af) && !PF_AEQ(&binat.src.addr.v.a.mask, &$12->host->addr.v.a.mask, binat.af)) { yyerror("'binat' source mask and " "redirect mask must be the same"); YYERROR; } TAILQ_INIT(&binat.rpool.list); pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "binat: calloc"); pa->addr = $12->host->addr; pa->ifname[0] = 0; TAILQ_INSERT_TAIL(&binat.rpool.list, pa, entries); free($12); } pfctl_add_rule(pf, &binat); } ; tag : /* empty */ { $$ = NULL; } | TAG STRING { $$ = $2; } route_host : STRING { struct node_host *n; $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "route_host: calloc"); if (($$->ifname = strdup($1)) == NULL) err(1, "routeto: strdup"); if ((n = ifa_exists($$->ifname)) == NULL) { yyerror("routeto: unknown interface %s", $$->ifname); YYERROR; } set_ipmask($$, 128); $$->next = NULL; $$->tail = $$; } | '(' STRING host ')' { struct node_host *n; $$ = $3; if (($$->ifname = strdup($2)) == NULL) err(1, "routeto: strdup"); if ((n = ifa_exists($$->ifname)) == NULL) { yyerror("routeto: unknown interface %s", $$->ifname); YYERROR; } } ; route_host_list : route_host { $$ = $1; } | route_host_list comma route_host { if ($1->af == 0) $1->af = $3->af; if ($1->af != $3->af) { yyerror("all pool addresses must be in the " "same address family"); YYERROR; } $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; } ; routespec : route_host { $$ = $1; } | '{' route_host_list '}' { $$ = $2; } ; route : /* empty */ { $$.host = NULL; $$.rt = 0; $$.pool_opts = 0; } | FASTROUTE { $$.host = NULL; $$.rt = PF_FASTROUTE; $$.pool_opts = 0; } | ROUTETO routespec pooltype { $$.host = $2; $$.rt = PF_ROUTETO; $$.pool_opts = $3.type; if ($3.key != NULL) $$.key = $3.key; } | REPLYTO routespec pooltype { $$.host = $2; $$.rt = PF_REPLYTO; $$.pool_opts = $3.type; if ($3.key != NULL) $$.key = $3.key; } | DUPTO routespec pooltype { $$.host = $2; $$.rt = PF_DUPTO; $$.pool_opts = $3.type; if ($3.key != NULL) $$.key = $3.key; } ; timeout_spec : STRING number { if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; if (pfctl_set_timeout(pf, $1, $2, 0) != 0) { yyerror("unknown timeout %s", $1); YYERROR; } } ; timeout_list : timeout_list comma timeout_spec | timeout_spec ; limit_spec : STRING number { if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; if (pfctl_set_limit(pf, $1, $2) != 0) { yyerror("unable to set limit %s %u", $1, $2); YYERROR; } } limit_list : limit_list comma limit_spec | limit_spec ; comma : ',' | /* empty */ ; yesno : NO { $$ = 0; } | STRING { if (!strcmp($1, "yes")) $$ = 1; else YYERROR; } unaryop : '=' { $$ = PF_OP_EQ; } | '!' '=' { $$ = PF_OP_NE; } | '<' '=' { $$ = PF_OP_LE; } | '<' { $$ = PF_OP_LT; } | '>' '=' { $$ = PF_OP_GE; } | '>' { $$ = PF_OP_GT; } ; %% int yyerror(const char *fmt, ...) { va_list ap; extern char *infile; errors = 1; va_start(ap, fmt); fprintf(stderr, "%s:%d: ", infile, yylval.lineno); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); return (0); } int disallow_table(struct node_host *h, const char *fmt) { for (; h != NULL; h = h->next) if (h->addr.type == PF_ADDR_TABLE) { yyerror(fmt, h->addr.v.tblname); return (1); } return (0); } int rule_consistent(struct pf_rule *r) { int problems = 0; switch (r->action) { case PF_PASS: case PF_DROP: case PF_SCRUB: problems = filter_consistent(r); break; case PF_NAT: case PF_NONAT: problems = nat_consistent(r); break; case PF_RDR: case PF_NORDR: problems = rdr_consistent(r); break; case PF_BINAT: case PF_NOBINAT: default: break; } return (problems); } int filter_consistent(struct pf_rule *r) { int problems = 0; if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && (r->src.port_op || r->dst.port_op)) { yyerror("port only applies to tcp/udp"); problems++; } if (r->src.port_op == PF_OP_RRG || r->dst.port_op == PF_OP_RRG) { yyerror("the ':' port operator only applies to rdr"); problems++; } if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 && (r->type || r->code)) { yyerror("icmp-type/code only applies to icmp"); problems++; } if (!r->af && (r->type || r->code)) { yyerror("must indicate address family with icmp-type/code"); problems++; } if ((r->proto == IPPROTO_ICMP && r->af == AF_INET6) || (r->proto == IPPROTO_ICMPV6 && r->af == AF_INET)) { yyerror("proto %s doesn't match address family %s", r->proto == IPPROTO_ICMP ? "icmp" : "icmp6", r->af == AF_INET ? "inet" : "inet6"); problems++; } if ((r->keep_state == PF_STATE_MODULATE || r->keep_state == PF_STATE_SYNPROXY) && r->proto && r->proto != IPPROTO_TCP) { yyerror("modulate/synproxy state can only be applied to " "TCP rules"); problems++; } if (r->allow_opts && r->action != PF_PASS) { yyerror("allow-opts can only be specified for pass rules"); problems++; } if (!r->af && (r->src.addr.type == PF_ADDR_DYNIFTL || r->dst.addr.type == PF_ADDR_DYNIFTL)) { yyerror("dynamic addresses require address family " "(inet/inet6)"); problems++; } if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op || r->dst.port_op || r->flagset || r->type || r->code)) { yyerror("fragments can be filtered only on IP header fields"); problems++; } if (r->rule_flag & PFRULE_RETURNRST && r->proto != IPPROTO_TCP) { yyerror("return-rst can only be applied to TCP rules"); problems++; } if (r->action == PF_DROP && r->keep_state) { yyerror("keep state on block rules doesn't make sense"); problems++; } if ((r->tagname[0] || r->match_tagname[0]) && !r->keep_state && r->action == PF_PASS) { yyerror("tags cannot be used without keep state"); problems++; } return (-problems); } int nat_consistent(struct pf_rule *r) { int problems = 0; struct pf_pooladdr *pa; if (r->src.port_op == PF_OP_RRG || r->dst.port_op == PF_OP_RRG) { yyerror("the ':' port operator only applies to rdr"); problems++; } if (!r->af) { TAILQ_FOREACH(pa, &r->rpool.list, entries) { if (pa->addr.type == PF_ADDR_DYNIFTL) { yyerror("dynamic addresses require " "address family (inet/inet6)"); problems++; break; } } } return (-problems); } int rdr_consistent(struct pf_rule *r) { int problems = 0; struct pf_pooladdr *pa; if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP) { if (r->src.port_op) { yyerror("src port only applies to tcp/udp"); problems++; } if (r->dst.port_op) { yyerror("dst port only applies to tcp/udp"); problems++; } if (r->rpool.proxy_port[0]) { yyerror("rpool port only applies to tcp/udp"); problems++; } } if (r->dst.port_op && r->dst.port_op != PF_OP_EQ && r->dst.port_op != PF_OP_RRG) { yyerror("invalid port operator for rdr destination port"); problems++; } if (r->src.port_op == PF_OP_RRG) { yyerror("the ':' port operator only applies to rdr " "destination port"); problems++; } if (!r->af) { if (r->src.addr.type == PF_ADDR_DYNIFTL || r->dst.addr.type == PF_ADDR_DYNIFTL) { yyerror("dynamic addresses require address family " "(inet/inet6)"); problems++; } else { TAILQ_FOREACH(pa, &r->rpool.list, entries) { if (pa->addr.type == PF_ADDR_DYNIFTL) { yyerror("dynamic addresses require " "address family (inet/inet6)"); problems++; break; } } } } return (-problems); } int process_tabledef(char *name, struct table_opts *opts) { struct pfr_buffer ab; struct node_tinit *ti; bzero(&ab, sizeof(ab)); ab.pfrb_type = PFRB_ADDRS; SIMPLEQ_FOREACH(ti, &opts->init_nodes, entries) { if (ti->file) if (pfr_buf_load(&ab, ti->file, 0, append_addr)) { if (errno) yyerror("cannot load \"%s\": %s", ti->file, strerror(errno)); else yyerror("file \"%s\" contains bad data", ti->file); goto _error; } if (ti->host) if (append_addr_host(&ab, ti->host, 0, 0)) { yyerror("cannot create address buffer: %s", strerror(errno)); goto _error; } } if (pf->opts & PF_OPT_VERBOSE) print_tabledef(name, opts->flags, opts->init_addr, &opts->init_nodes); if (!(pf->opts & PF_OPT_NOACTION) && pfctl_define_table(name, opts->flags, opts->init_addr, pf->anchor, pf->ruleset, &ab, pf->tticket)) { yyerror("cannot define table %s: %s", name, pfr_strerror(errno)); goto _error; } pf->tdirty = 1; pfr_buf_clear(&ab); return (0); _error: pfr_buf_clear(&ab); return (-1); } struct keywords { const char *k_name; int k_val; }; /* macro gore, but you should've seen the prior indentation nightmare... */ #define FREE_LIST(T,r) \ do { \ T *p, *node = r; \ while (node != NULL) { \ p = node; \ node = node->next; \ free(p); \ } \ } while (0) #define LOOP_THROUGH(T,n,r,C) \ do { \ T *n; \ if (r == NULL) { \ r = calloc(1, sizeof(T)); \ if (r == NULL) \ err(1, "LOOP: calloc"); \ r->next = NULL; \ } \ n = r; \ while (n != NULL) { \ do { \ C; \ } while (0); \ n = n->next; \ } \ } while (0) void expand_label_str(char *label, const char *srch, const char *repl) { char tmp[PF_RULE_LABEL_SIZE] = ""; char *p, *q; p = q = label; while ((q = strstr(p, srch)) != NULL) { *q = '\0'; if ((strlcat(tmp, p, sizeof(tmp)) >= sizeof(tmp)) || (strlcat(tmp, repl, sizeof(tmp)) >= sizeof(tmp))) err(1, "expand_label: label too long"); q += strlen(srch); p = q; } if (strlcat(tmp, p, sizeof(tmp)) >= sizeof(tmp)) err(1, "expand_label: label too long"); strlcpy(label, tmp, PF_RULE_LABEL_SIZE); /* always fits */ } void expand_label_if(const char *name, char *label, const char *ifname) { if (strstr(label, name) != NULL) { if (!*ifname) expand_label_str(label, name, "any"); else expand_label_str(label, name, ifname); } } void expand_label_addr(const char *name, char *label, sa_family_t af, struct node_host *h) { char tmp[64], tmp_not[66]; if (strstr(label, name) != NULL) { switch (h->addr.type) { case PF_ADDR_DYNIFTL: snprintf(tmp, sizeof(tmp), "(%s)", h->addr.v.ifname); break; case PF_ADDR_TABLE: snprintf(tmp, sizeof(tmp), "<%s>", h->addr.v.tblname); break; case PF_ADDR_NOROUTE: snprintf(tmp, sizeof(tmp), "no-route"); break; case PF_ADDR_ADDRMASK: if (!af || (PF_AZERO(&h->addr.v.a.addr, af) && PF_AZERO(&h->addr.v.a.mask, af))) snprintf(tmp, sizeof(tmp), "any"); else { char a[48]; int bits; if (inet_ntop(af, &h->addr.v.a.addr, a, sizeof(a)) == NULL) snprintf(tmp, sizeof(tmp), "?"); else { bits = unmask(&h->addr.v.a.mask, af); if ((af == AF_INET && bits < 32) || (af == AF_INET6 && bits < 128)) snprintf(tmp, sizeof(tmp), "%s/%d", a, bits); else snprintf(tmp, sizeof(tmp), "%s", a); } } break; default: snprintf(tmp, sizeof(tmp), "?"); break; } if (h->not) { snprintf(tmp_not, sizeof(tmp_not), "! %s", tmp); expand_label_str(label, name, tmp_not); } else expand_label_str(label, name, tmp); } } void expand_label_port(const char *name, char *label, struct node_port *port) { char a1[6], a2[6], op[13] = ""; if (strstr(label, name) != NULL) { snprintf(a1, sizeof(a1), "%u", ntohs(port->port[0])); snprintf(a2, sizeof(a2), "%u", ntohs(port->port[1])); if (!port->op) ; else if (port->op == PF_OP_IRG) snprintf(op, sizeof(op), "%s><%s", a1, a2); else if (port->op == PF_OP_XRG) snprintf(op, sizeof(op), "%s<>%s", a1, a2); else if (port->op == PF_OP_EQ) snprintf(op, sizeof(op), "%s", a1); else if (port->op == PF_OP_NE) snprintf(op, sizeof(op), "!=%s", a1); else if (port->op == PF_OP_LT) snprintf(op, sizeof(op), "<%s", a1); else if (port->op == PF_OP_LE) snprintf(op, sizeof(op), "<=%s", a1); else if (port->op == PF_OP_GT) snprintf(op, sizeof(op), ">%s", a1); else if (port->op == PF_OP_GE) snprintf(op, sizeof(op), ">=%s", a1); expand_label_str(label, name, op); } } void expand_label_proto(const char *name, char *label, u_int8_t proto) { struct protoent *pe; char n[4]; if (strstr(label, name) != NULL) { pe = getprotobynumber(proto); if (pe != NULL) expand_label_str(label, name, pe->p_name); else { snprintf(n, sizeof(n), "%u", proto); expand_label_str(label, name, n); } } } void expand_label_nr(const char *name, char *label) { char n[11]; if (strstr(label, name) != NULL) { snprintf(n, sizeof(n), "%u", pf->rule_nr); expand_label_str(label, name, n); } } void expand_label(char *label, const char *ifname, sa_family_t af, struct node_host *src_host, struct node_port *src_port, struct node_host *dst_host, struct node_port *dst_port, u_int8_t proto) { expand_label_if("$if", label, ifname); expand_label_addr("$srcaddr", label, af, src_host); expand_label_addr("$dstaddr", label, af, dst_host); expand_label_port("$srcport", label, src_port); expand_label_port("$dstport", label, dst_port); expand_label_proto("$proto", label, proto); expand_label_nr("$nr", label); } int expand_altq(struct pf_altq *a, struct node_if *interfaces, struct node_queue *nqueues, struct node_queue_bw bwspec, struct node_queue_opt *opts) { struct pf_altq pa, pb; char qname[PF_QNAME_SIZE]; struct node_queue *n; struct node_queue_bw bw; int errs = 0; if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { FREE_LIST(struct node_if, interfaces); FREE_LIST(struct node_queue, nqueues); return (0); } LOOP_THROUGH(struct node_if, interface, interfaces, memcpy(&pa, a, sizeof(struct pf_altq)); if (strlcpy(pa.ifname, interface->ifname, sizeof(pa.ifname)) >= sizeof(pa.ifname)) errx(1, "expand_altq: strlcpy"); if (interface->not) { yyerror("altq on ! is not supported"); errs++; } else { if (eval_pfaltq(pf, &pa, &bwspec, opts)) errs++; else if (pfctl_add_altq(pf, &pa)) errs++; if (pf->opts & PF_OPT_VERBOSE) { print_altq(&pf->paltq->altq, 0, &bwspec, opts); if (nqueues && nqueues->tail) { printf("queue { "); LOOP_THROUGH(struct node_queue, queue, nqueues, printf("%s ", queue->queue); ); printf("}"); } printf("\n"); } if (pa.scheduler == ALTQT_CBQ || pa.scheduler == ALTQT_HFSC) { /* now create a root queue */ memset(&pb, 0, sizeof(struct pf_altq)); if (strlcpy(qname, "root_", sizeof(qname)) >= sizeof(qname)) errx(1, "expand_altq: strlcpy"); if (strlcat(qname, interface->ifname, sizeof(qname)) >= sizeof(qname)) errx(1, "expand_altq: strlcat"); if (strlcpy(pb.qname, qname, sizeof(pb.qname)) >= sizeof(pb.qname)) errx(1, "expand_altq: strlcpy"); if (strlcpy(pb.ifname, interface->ifname, sizeof(pb.ifname)) >= sizeof(pb.ifname)) errx(1, "expand_altq: strlcpy"); pb.qlimit = pa.qlimit; pb.scheduler = pa.scheduler; bw.bw_absolute = pa.ifbandwidth; bw.bw_percent = 0; if (eval_pfqueue(pf, &pb, &bw, opts)) errs++; else if (pfctl_add_altq(pf, &pb)) errs++; } LOOP_THROUGH(struct node_queue, queue, nqueues, n = calloc(1, sizeof(struct node_queue)); if (n == NULL) err(1, "expand_altq: calloc"); if (pa.scheduler == ALTQT_CBQ || pa.scheduler == ALTQT_HFSC) if (strlcpy(n->parent, qname, sizeof(n->parent)) >= sizeof(n->parent)) errx(1, "expand_altq: strlcpy"); if (strlcpy(n->queue, queue->queue, sizeof(n->queue)) >= sizeof(n->queue)) errx(1, "expand_altq: strlcpy"); if (strlcpy(n->ifname, interface->ifname, sizeof(n->ifname)) >= sizeof(n->ifname)) errx(1, "expand_altq: strlcpy"); n->scheduler = pa.scheduler; n->next = NULL; n->tail = n; if (queues == NULL) queues = n; else { queues->tail->next = n; queues->tail = n; } ); } ); FREE_LIST(struct node_if, interfaces); FREE_LIST(struct node_queue, nqueues); return (errs); } int expand_queue(struct pf_altq *a, struct node_if *interfaces, struct node_queue *nqueues, struct node_queue_bw bwspec, struct node_queue_opt *opts) { struct node_queue *n, *nq; struct pf_altq pa; u_int8_t found = 0; u_int8_t errs = 0; if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { FREE_LIST(struct node_queue, nqueues); return (0); } if (queues == NULL) { yyerror("queue %s has no parent", a->qname); FREE_LIST(struct node_queue, nqueues); return (1); } LOOP_THROUGH(struct node_if, interface, interfaces, LOOP_THROUGH(struct node_queue, tqueue, queues, if (!strncmp(a->qname, tqueue->queue, PF_QNAME_SIZE) && (interface->ifname[0] == 0 || (!interface->not && !strncmp(interface->ifname, tqueue->ifname, IFNAMSIZ)) || (interface->not && strncmp(interface->ifname, tqueue->ifname, IFNAMSIZ)))) { /* found ourself in queues */ found++; memcpy(&pa, a, sizeof(struct pf_altq)); if (pa.scheduler != ALTQT_NONE && pa.scheduler != tqueue->scheduler) { yyerror("exactly one scheduler type " "per interface allowed"); return (1); } pa.scheduler = tqueue->scheduler; /* scheduler dependent error checking */ switch (pa.scheduler) { case ALTQT_PRIQ: if (nqueues != NULL) { yyerror("priq queues cannot " "have child queues"); return (1); } if (bwspec.bw_absolute > 0 || bwspec.bw_percent < 100) { yyerror("priq doesn't take " "bandwidth"); return (1); } break; default: break; } if (strlcpy(pa.ifname, tqueue->ifname, sizeof(pa.ifname)) >= sizeof(pa.ifname)) errx(1, "expand_queue: strlcpy"); if (strlcpy(pa.parent, tqueue->parent, sizeof(pa.parent)) >= sizeof(pa.parent)) errx(1, "expand_queue: strlcpy"); if (eval_pfqueue(pf, &pa, &bwspec, opts)) errs++; else if (pfctl_add_altq(pf, &pa)) errs++; for (nq = nqueues; nq != NULL; nq = nq->next) { if (!strcmp(a->qname, nq->queue)) { yyerror("queue cannot have " "itself as child"); errs++; continue; } n = calloc(1, sizeof(struct node_queue)); if (n == NULL) err(1, "expand_queue: calloc"); if (strlcpy(n->parent, a->qname, sizeof(n->parent)) >= sizeof(n->parent)) errx(1, "expand_queue strlcpy"); if (strlcpy(n->queue, nq->queue, sizeof(n->queue)) >= sizeof(n->queue)) errx(1, "expand_queue strlcpy"); if (strlcpy(n->ifname, tqueue->ifname, sizeof(n->ifname)) >= sizeof(n->ifname)) errx(1, "expand_queue strlcpy"); n->scheduler = tqueue->scheduler; n->next = NULL; n->tail = n; if (queues == NULL) queues = n; else { queues->tail->next = n; queues->tail = n; } } if ((pf->opts & PF_OPT_VERBOSE) && ( (found == 1 && interface->ifname[0] == 0) || (found > 0 && interface->ifname[0] != 0))) { print_queue(&pf->paltq->altq, 0, &bwspec, interface->ifname[0] != 0, opts); if (nqueues && nqueues->tail) { printf("{ "); LOOP_THROUGH(struct node_queue, queue, nqueues, printf("%s ", queue->queue); ); printf("}"); } printf("\n"); } } ); ); FREE_LIST(struct node_queue, nqueues); FREE_LIST(struct node_if, interfaces); if (!found) { yyerror("queue %s has no parent", a->qname); errs++; } if (errs) return (1); else return (0); } void expand_rule(struct pf_rule *r, struct node_if *interfaces, struct node_host *rpool_hosts, struct node_proto *protos, struct node_os *src_oses, struct node_host *src_hosts, struct node_port *src_ports, struct node_host *dst_hosts, struct node_port *dst_ports, struct node_uid *uids, struct node_gid *gids, struct node_icmp *icmp_types) { sa_family_t af = r->af; int added = 0, error = 0; char ifname[IF_NAMESIZE]; char label[PF_RULE_LABEL_SIZE]; struct pf_pooladdr *pa; struct node_host *h; u_int8_t flags, flagset; if (strlcpy(label, r->label, sizeof(label)) >= sizeof(label)) errx(1, "expand_rule: strlcpy"); flags = r->flags; flagset = r->flagset; LOOP_THROUGH(struct node_if, interface, interfaces, LOOP_THROUGH(struct node_proto, proto, protos, LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types, LOOP_THROUGH(struct node_host, src_host, src_hosts, LOOP_THROUGH(struct node_port, src_port, src_ports, LOOP_THROUGH(struct node_os, src_os, src_oses, LOOP_THROUGH(struct node_host, dst_host, dst_hosts, LOOP_THROUGH(struct node_port, dst_port, dst_ports, LOOP_THROUGH(struct node_uid, uid, uids, LOOP_THROUGH(struct node_gid, gid, gids, r->af = af; /* for link-local IPv6 address, interface must match up */ if ((r->af && src_host->af && r->af != src_host->af) || (r->af && dst_host->af && r->af != dst_host->af) || (src_host->af && dst_host->af && src_host->af != dst_host->af) || (src_host->ifindex && dst_host->ifindex && src_host->ifindex != dst_host->ifindex) || (src_host->ifindex && if_nametoindex(interface->ifname) && src_host->ifindex != if_nametoindex(interface->ifname)) || (dst_host->ifindex && if_nametoindex(interface->ifname) && dst_host->ifindex != if_nametoindex(interface->ifname))) continue; if (!r->af && src_host->af) r->af = src_host->af; else if (!r->af && dst_host->af) r->af = dst_host->af; if (if_indextoname(src_host->ifindex, ifname)) memcpy(r->ifname, ifname, sizeof(r->ifname)); else if (if_indextoname(dst_host->ifindex, ifname)) memcpy(r->ifname, ifname, sizeof(r->ifname)); else memcpy(r->ifname, interface->ifname, sizeof(r->ifname)); if (strlcpy(r->label, label, sizeof(r->label)) >= sizeof(r->label)) errx(1, "expand_rule: strlcpy"); expand_label(r->label, r->ifname, r->af, src_host, src_port, dst_host, dst_port, proto->proto); error += check_netmask(src_host, r->af); error += check_netmask(dst_host, r->af); r->ifnot = interface->not; r->proto = proto->proto; r->src.addr = src_host->addr; r->src.not = src_host->not; r->src.port[0] = src_port->port[0]; r->src.port[1] = src_port->port[1]; r->src.port_op = src_port->op; r->dst.addr = dst_host->addr; r->dst.not = dst_host->not; r->dst.port[0] = dst_port->port[0]; r->dst.port[1] = dst_port->port[1]; r->dst.port_op = dst_port->op; r->uid.op = uid->op; r->uid.uid[0] = uid->uid[0]; r->uid.uid[1] = uid->uid[1]; r->gid.op = gid->op; r->gid.gid[0] = gid->gid[0]; r->gid.gid[1] = gid->gid[1]; r->type = icmp_type->type; r->code = icmp_type->code; if (r->proto && r->proto != IPPROTO_TCP) { r->flags = 0; r->flagset = 0; } else { r->flags = flags; r->flagset = flagset; } if (icmp_type->proto && r->proto != icmp_type->proto) { yyerror("icmp-type mismatch"); error++; } if (src_os && src_os->os) { r->os_fingerprint = pfctl_get_fingerprint(src_os->os); if ((pf->opts & PF_OPT_VERBOSE2) && r->os_fingerprint == PF_OSFP_NOMATCH) fprintf(stderr, "warning: unknown '%s' OS fingerprint\n", src_os->os); } else { r->os_fingerprint = PF_OSFP_ANY; } TAILQ_INIT(&r->rpool.list); for (h = rpool_hosts; h != NULL; h = h->next) { pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "expand_rule: calloc"); pa->addr = h->addr; if (h->ifname != NULL) { if (strlcpy(pa->ifname, h->ifname, sizeof(pa->ifname)) >= sizeof(pa->ifname)) errx(1, "expand_rule: strlcpy"); } else pa->ifname[0] = 0; TAILQ_INSERT_TAIL(&r->rpool.list, pa, entries); } if (rule_consistent(r) < 0 || error) yyerror("skipping rule due to errors"); else { r->nr = pf->rule_nr++; pfctl_add_rule(pf, r); added++; } )))))))))); FREE_LIST(struct node_if, interfaces); FREE_LIST(struct node_proto, protos); FREE_LIST(struct node_host, src_hosts); FREE_LIST(struct node_port, src_ports); FREE_LIST(struct node_os, src_oses); FREE_LIST(struct node_host, dst_hosts); FREE_LIST(struct node_port, dst_ports); FREE_LIST(struct node_uid, uids); FREE_LIST(struct node_gid, gids); FREE_LIST(struct node_icmp, icmp_types); FREE_LIST(struct node_host, rpool_hosts); if (!added) yyerror("rule expands to no valid combination"); } #undef FREE_LIST #undef LOOP_THROUGH int check_rulestate(int desired_state) { if (require_order && (rulestate > desired_state)) { yyerror("Rules must be in order: options, normalization, " "queueing, translation, filtering"); return (1); } rulestate = desired_state; return (0); } int kw_cmp(const void *k, const void *e) { return (strcmp(k, ((const struct keywords *)e)->k_name)); } int lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { { "all", ALL}, { "allow-opts", ALLOWOPTS}, { "altq", ALTQ}, { "anchor", ANCHOR}, { "antispoof", ANTISPOOF}, { "any", ANY}, { "bandwidth", BANDWIDTH}, { "binat", BINAT}, { "binat-anchor", BINATANCHOR}, { "bitmask", BITMASK}, { "block", BLOCK}, { "block-policy", BLOCKPOLICY}, { "cbq", CBQ}, { "code", CODE}, { "crop", FRAGCROP}, { "drop", DROP}, { "drop-ovl", FRAGDROP}, { "dup-to", DUPTO}, { "fastroute", FASTROUTE}, { "file", FILENAME}, { "fingerprints", FINGERPRINTS}, { "flags", FLAGS}, { "for", FOR}, { "fragment", FRAGMENT}, { "from", FROM}, { "group", GROUP}, { "hfsc", HFSC}, { "icmp-type", ICMPTYPE}, { "icmp6-type", ICMP6TYPE}, { "in", IN}, { "inet", INET}, { "inet6", INET6}, { "keep", KEEP}, { "label", LABEL}, { "limit", LIMIT}, { "linkshare", LINKSHARE}, { "load", LOAD}, { "log", LOG}, { "log-all", LOGALL}, { "loginterface", LOGINTERFACE}, { "max", MAXIMUM}, { "max-mss", MAXMSS}, { "min-ttl", MINTTL}, { "modulate", MODULATE}, { "nat", NAT}, { "nat-anchor", NATANCHOR}, { "no", NO}, { "no-df", NODF}, { "no-route", NOROUTE}, { "on", ON}, { "optimization", OPTIMIZATION}, { "os", OS}, { "out", OUT}, { "pass", PASS}, { "port", PORT}, { "priority", PRIORITY}, { "priq", PRIQ}, { "proto", PROTO}, { "qlimit", QLIMIT}, { "queue", QUEUE}, { "quick", QUICK}, { "random", RANDOM}, { "random-id", RANDOMID}, { "rdr", RDR}, { "rdr-anchor", RDRANCHOR}, { "realtime", REALTIME}, { "reassemble", REASSEMBLE}, { "reply-to", REPLYTO}, { "require-order", REQUIREORDER}, { "return", RETURN}, { "return-icmp", RETURNICMP}, { "return-icmp6", RETURNICMP6}, { "return-rst", RETURNRST}, { "round-robin", ROUNDROBIN}, { "route-to", ROUTETO}, { "scrub", SCRUB}, { "set", SET}, { "source-hash", SOURCEHASH}, { "state", STATE}, { "static-port", STATICPORT}, { "synproxy", SYNPROXY}, { "table", TABLE}, { "tag", TAG}, { "tagged", TAGGED}, { "tbrsize", TBRSIZE}, { "timeout", TIMEOUT}, { "to", TO}, { "tos", TOS}, { "ttl", TTL}, { "upperlimit", UPPERLIMIT}, { "user", USER}, }; const struct keywords *p; p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), sizeof(keywords[0]), kw_cmp); if (p) { if (debug > 1) fprintf(stderr, "%s: %d\n", s, p->k_val); return (p->k_val); } else { if (debug > 1) fprintf(stderr, "string: %s\n", s); return (STRING); } } #define MAXPUSHBACK 128 char *parsebuf; int parseindex; char pushback_buffer[MAXPUSHBACK]; int pushback_index = 0; int lgetc(FILE *f) { int c, next; if (parsebuf) { /* Read character from the parsebuffer instead of input. */ if (parseindex >= 0) { c = parsebuf[parseindex++]; if (c != '\0') return (c); parsebuf = NULL; } else parseindex++; } if (pushback_index) return (pushback_buffer[--pushback_index]); while ((c = getc(f)) == '\\') { next = getc(f); if (next != '\n') { if (isspace(next)) yyerror("whitespace after \\"); ungetc(next, f); break; } yylval.lineno = lineno; lineno++; } if (c == '\t' || c == ' ') { /* Compress blanks to a single space. */ do { c = getc(f); } while (c == '\t' || c == ' '); ungetc(c, f); c = ' '; } return (c); } int lungetc(int c) { if (c == EOF) return (EOF); if (parsebuf) { parseindex--; if (parseindex >= 0) return (c); } if (pushback_index < MAXPUSHBACK-1) return (pushback_buffer[pushback_index++] = c); else return (EOF); } int findeol(void) { int c; parsebuf = NULL; pushback_index = 0; /* skip to either EOF or the first real EOL */ while (1) { c = lgetc(fin); if (c == '\n') { lineno++; break; } if (c == EOF) break; } return (ERROR); } int yylex(void) { char buf[8096]; char *p, *val; int endc, c, next; int token; top: p = buf; while ((c = lgetc(fin)) == ' ') ; /* nothing */ yylval.lineno = lineno; if (c == '#') while ((c = lgetc(fin)) != '\n' && c != EOF) ; /* nothing */ if (c == '$' && parsebuf == NULL) { while (1) { if ((c = lgetc(fin)) == EOF) return (0); if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } if (isalnum(c) || c == '_') { *p++ = (char)c; continue; } *p = '\0'; lungetc(c); break; } val = symget(buf); if (val == NULL) { yyerror("macro '%s' not defined", buf); return (findeol()); } parsebuf = val; parseindex = 0; goto top; } switch (c) { case '\'': case '"': endc = c; while (1) { if ((c = lgetc(fin)) == EOF) return (0); if (c == endc) { *p = '\0'; break; } if (c == '\n') { lineno++; continue; } if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } *p++ = (char)c; } yylval.v.string = strdup(buf); if (yylval.v.string == NULL) err(1, "yylex: strdup"); return (STRING); case '<': next = lgetc(fin); if (next == '>') { yylval.v.i = PF_OP_XRG; return (PORTBINARY); } lungetc(next); break; case '>': next = lgetc(fin); if (next == '<') { yylval.v.i = PF_OP_IRG; return (PORTBINARY); } lungetc(next); break; case '-': next = lgetc(fin); if (next == '>') return (ARROW); lungetc(next); break; } #define allowed_in_string(x) \ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ x != '{' && x != '}' && x != '<' && x != '>' && \ x != '!' && x != '=' && x != '/' && x != '#' && \ x != ',')) if (isalnum(c) || c == ':' || c == '_') { do { *p++ = c; if ((unsigned)(p-buf) >= sizeof(buf)) { yyerror("string too long"); return (findeol()); } } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c))); lungetc(c); *p = '\0'; token = lookup(buf); yylval.v.string = strdup(buf); if (yylval.v.string == NULL) err(1, "yylex: strdup"); return (token); } if (c == '\n') { yylval.lineno = lineno; lineno++; } if (c == EOF) return (0); return (c); } int parse_rules(FILE *input, struct pfctl *xpf) { struct sym *sym; fin = input; pf = xpf; lineno = 1; errors = 0; rulestate = PFCTL_STATE_NONE; returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; returnicmp6default = (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; blockpolicy = PFRULE_DROP; require_order = 1; yyparse(); /* Free macros and check which have not been used. */ TAILQ_FOREACH(sym, &symhead, entries) { if ((pf->opts & PF_OPT_VERBOSE2) && !sym->used) fprintf(stderr, "warning: macro '%s' not " "used\n", sym->nam); free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entries); } return (errors ? -1 : 0); } /* * Over-designed efficiency is a French and German concept, so how about * we wait until they discover this ugliness and make it all fancy. */ int symset(const char *nam, const char *val, int persist) { struct sym *sym; for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); sym = TAILQ_NEXT(sym, entries)) ; /* nothing */ if (sym != NULL) { if (sym->persist == 1) return (0); else { free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entries); free(sym); } } if ((sym = calloc(1, sizeof(*sym))) == NULL) return (-1); sym->nam = strdup(nam); if (sym->nam == NULL) { free(sym); return (-1); } sym->val = strdup(val); if (sym->val == NULL) { free(sym->nam); free(sym); return (-1); } sym->used = 0; sym->persist = persist; TAILQ_INSERT_TAIL(&symhead, sym, entries); return (0); } int pfctl_cmdline_symset(char *s) { char *sym, *val; int ret; if ((val = strrchr(s, '=')) == NULL) return (-1); if ((sym = malloc(strlen(s) - strlen(val) + 1)) == NULL) err(1, "pfctl_cmdline_symset: malloc"); strlcpy(sym, s, strlen(s) - strlen(val) + 1); ret = symset(sym, val + 1, 1); free(sym); return (ret); } char * symget(const char *nam) { struct sym *sym; TAILQ_FOREACH(sym, &symhead, entries) if (strcmp(nam, sym->nam) == 0) { sym->used = 1; return (sym->val); } return (NULL); } void decide_address_family(struct node_host *n, sa_family_t *af) { sa_family_t target_af = 0; while (!*af && n != NULL) { if (n->af) { if (target_af == 0) target_af = n->af; if (target_af != n->af) return; } n = n->next; } if (!*af && target_af) *af = target_af; } void remove_invalid_hosts(struct node_host **nh, sa_family_t *af) { struct node_host *n = *nh, *prev = NULL; while (n != NULL) { if (*af && n->af && n->af != *af) { /* unlink and free n */ struct node_host *next = n->next; /* adjust tail pointer */ if (n == (*nh)->tail) (*nh)->tail = prev; /* adjust previous node's next pointer */ if (prev == NULL) *nh = next; else prev->next = next; /* free node */ if (n->ifname != NULL) free(n->ifname); free(n); n = next; } else { if (n->af && !*af) *af = n->af; prev = n; n = n->next; } } } int invalid_redirect(struct node_host *nh, sa_family_t af) { if (!af) { struct node_host *n; /* only tables are ok without an address family */ for (n = nh; n != NULL; n = n->next) { if (n->addr.type != PF_ADDR_TABLE) { yyerror("address family not given and " "translation address expands to multiple " "address families"); return (1); } } } if (nh == NULL) { yyerror("no translation address with matching address family " "found."); return (1); } return (0); } int atoul(char *s, u_long *ulvalp) { u_long ulval; char *ep; errno = 0; ulval = strtoul(s, &ep, 0); if (s[0] == '\0' || *ep != '\0') return (-1); if (errno == ERANGE && ulval == ULONG_MAX) return (-1); *ulvalp = ulval; return (0); } int getservice(char *n) { struct servent *s; u_long ulval; if (atoul(n, &ulval) == 0) { if (ulval > 65535) { yyerror("illegal port value %d", ulval); return (-1); } return (htons(ulval)); } else { s = getservbyname(n, "tcp"); if (s == NULL) s = getservbyname(n, "udp"); if (s == NULL) { yyerror("unknown port %s", n); return (-1); } return (s->s_port); } } int rule_label(struct pf_rule *r, char *s) { if (s) { if (strlcpy(r->label, s, sizeof(r->label)) >= sizeof(r->label)) { yyerror("rule label too long (max %d chars)", sizeof(r->label)-1); return (-1); } } return (0); } u_int16_t parseicmpspec(char *w, sa_family_t af) { const struct icmpcodeent *p; u_long ulval; u_int8_t icmptype; if (af == AF_INET) icmptype = returnicmpdefault >> 8; else icmptype = returnicmp6default >> 8; if (atoul(w, &ulval) == -1) { if ((p = geticmpcodebyname(icmptype, w, af)) == NULL) { yyerror("unknown icmp code %s", w); return (0); } ulval = p->code; } if (ulval > 255) { yyerror("invalid icmp code %ld", ulval); return (0); } return (icmptype << 8 | ulval); } int pfctl_load_anchors(int dev, int opts) { struct loadanchors *la; TAILQ_FOREACH(la, &loadanchorshead, entries) { if (opts & PF_OPT_VERBOSE) fprintf(stderr, "\nLoading anchor %s:%s from %s\n", la->anchorname, la->rulesetname, la->filename); if (pfctl_rules(dev, la->filename, opts, la->anchorname, la->rulesetname) == -1) return (-1); } return (0); } diff --git a/contrib/pf/pfctl/pfctl.c b/contrib/pf/pfctl/pfctl.c index cd07433030d5..418b51ff76d6 100644 --- a/contrib/pf/pfctl/pfctl.c +++ b/contrib/pf/pfctl/pfctl.c @@ -1,1642 +1,1644 @@ -/* $FreeBSD$ */ /* $OpenBSD: pfctl.c,v 1.188 2003/08/29 21:47:36 cedric Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" void usage(void); int pfctl_enable(int, int); int pfctl_disable(int, int); int pfctl_clear_stats(int, int); int pfctl_clear_rules(int, int, char *, char *); int pfctl_clear_nat(int, int, char *, char *); int pfctl_clear_altq(int, int); int pfctl_clear_states(int, int); int pfctl_kill_states(int, int); int pfctl_get_pool(int, struct pf_pool *, u_int32_t, u_int32_t, int, char *, char *); void pfctl_print_rule_counters(struct pf_rule *, int); int pfctl_show_rules(int, int, int, char *, char *); int pfctl_show_nat(int, int, char *, char *); int pfctl_show_states(int, u_int8_t, int); int pfctl_show_status(int); int pfctl_show_timeouts(int); int pfctl_show_limits(int); int pfctl_debug(int, u_int32_t, int); int pfctl_clear_rule_counters(int, int); int pfctl_test_altqsupport(int, int); int pfctl_show_anchors(int, int, char *); const char *pfctl_lookup_option(char *, const char **); const char *clearopt; char *rulesopt; const char *showopt; const char *debugopt; char *anchoropt; char *tableopt; const char *tblcmdopt; int state_killers; char *state_kill[2]; int loadopt; int altqsupport; int dev = -1; const char *infile; static const struct { const char *name; int index; } pf_limits[] = { { "states", PF_LIMIT_STATES }, { "frags", PF_LIMIT_FRAGS }, { NULL, 0 } }; struct pf_hint { const char *name; int timeout; }; static const struct pf_hint pf_hint_normal[] = { { "tcp.first", 2 * 60 }, { "tcp.opening", 30 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 }, { "tcp.finwait", 45 }, { "tcp.closed", 90 }, { NULL, 0 } }; static const struct pf_hint pf_hint_satellite[] = { { "tcp.first", 3 * 60 }, { "tcp.opening", 30 + 5 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 + 5 }, { "tcp.finwait", 45 + 5 }, { "tcp.closed", 90 + 5 }, { NULL, 0 } }; static const struct pf_hint pf_hint_conservative[] = { { "tcp.first", 60 * 60 }, { "tcp.opening", 15 * 60 }, { "tcp.established", 5 * 24 * 60 * 60 }, { "tcp.closing", 60 * 60 }, { "tcp.finwait", 10 * 60 }, { "tcp.closed", 3 * 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_aggressive[] = { { "tcp.first", 30 }, { "tcp.opening", 5 }, { "tcp.established", 5 * 60 * 60 }, { "tcp.closing", 60 }, { "tcp.finwait", 30 }, { "tcp.closed", 30 }, { NULL, 0 } }; static const struct { const char *name; const struct pf_hint *hint; } pf_hints[] = { { "normal", pf_hint_normal }, { "satellite", pf_hint_satellite }, { "high-latency", pf_hint_satellite }, { "conservative", pf_hint_conservative }, { "aggressive", pf_hint_aggressive }, { NULL, NULL } }; static const char *clearopt_list[] = { "nat", "queue", "rules", "state", "info", "Tables", "osfp", "all", NULL }; static const char *showopt_list[] = { "nat", "queue", "rules", "Anchors", "state", "info", "labels", "timeouts", "memory", "Tables", "osfp", "all", NULL }; static const char *tblcmdopt_list[] = { "kill", "flush", "add", "delete", "load", "replace", "show", "test", "zero", NULL }; static const char *debugopt_list[] = { "none", "urgent", "misc", "loud", NULL }; void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-AdeghnNqrROvz] ", __progname); fprintf(stderr, "[-a anchor[:ruleset]] [-D macro=value]\n"); fprintf(stderr, " "); fprintf(stderr, "[-f file] [-F modifier] [-k host] [-s modifier]\n"); fprintf(stderr, " "); fprintf(stderr, "[-t table] [-T command [address ...]] [-x level]\n"); exit(1); } int pfctl_enable(int dev, int opts) { if (ioctl(dev, DIOCSTART)) { if (errno == EEXIST) errx(1, "pf already enabled"); #ifdef __FreeBSD__ else if (errno == ESRCH) errx(1, "pfil registeration failed"); #endif else err(1, "DIOCSTART"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf enabled\n"); if (altqsupport && ioctl(dev, DIOCSTARTALTQ)) if (errno != EEXIST) err(1, "DIOCSTARTALTQ"); return (0); } int pfctl_disable(int dev, int opts) { if (ioctl(dev, DIOCSTOP)) { if (errno == ENOENT) errx(1, "pf not enabled"); else err(1, "DIOCSTOP"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf disabled\n"); if (altqsupport && ioctl(dev, DIOCSTOPALTQ)) if (errno != ENOENT) err(1, "DIOCSTOPALTQ"); return (0); } int pfctl_clear_stats(int dev, int opts) { if (ioctl(dev, DIOCCLRSTATUS)) err(1, "DIOCCLRSTATUS"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: statistics cleared\n"); return (0); } int pfctl_clear_rules(int dev, int opts, char *anchorname, char *rulesetname) { struct pfioc_rule pr; if (*anchorname && !*rulesetname) { struct pfioc_ruleset pr; int mnr, nr, r; memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "No rulesets in anchor '%s'.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; for (nr = mnr - 1; nr >= 0; --nr) { pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "DIOCGETRULESET"); r = pfctl_clear_rules(dev, opts | PF_OPT_QUIET, anchorname, pr.name); if (r) return (r); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "rules cleared\n"); return (0); } memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); pr.rule.action = PF_SCRUB; if (ioctl(dev, DIOCBEGINRULES, &pr)) err(1, "DIOCBEGINRULES"); else if (ioctl(dev, DIOCCOMMITRULES, &pr)) err(1, "DIOCCOMMITRULES"); pr.rule.action = PF_PASS; if (ioctl(dev, DIOCBEGINRULES, &pr)) err(1, "DIOCBEGINRULES"); else if (ioctl(dev, DIOCCOMMITRULES, &pr)) err(1, "DIOCCOMMITRULES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "rules cleared\n"); return (0); } int pfctl_clear_nat(int dev, int opts, char *anchorname, char *rulesetname) { struct pfioc_rule pr; if (*anchorname && !*rulesetname) { struct pfioc_ruleset pr; int mnr, nr, r; memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "No rulesets in anchor '%s'.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; for (nr = mnr - 1; nr >= 0; --nr) { pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "DIOCGETRULESET"); r = pfctl_clear_nat(dev, opts | PF_OPT_QUIET, anchorname, pr.name); if (r) return (r); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "nat cleared\n"); return (0); } memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); pr.rule.action = PF_NAT; if (ioctl(dev, DIOCBEGINRULES, &pr)) err(1, "DIOCBEGINRULES"); else if (ioctl(dev, DIOCCOMMITRULES, &pr)) err(1, "DIOCCOMMITRULES"); pr.rule.action = PF_BINAT; if (ioctl(dev, DIOCBEGINRULES, &pr)) err(1, "DIOCBEGINRULES"); else if (ioctl(dev, DIOCCOMMITRULES, &pr)) err(1, "DIOCCOMMITRULES"); pr.rule.action = PF_RDR; if (ioctl(dev, DIOCBEGINRULES, &pr)) err(1, "DIOCBEGINRULES"); else if (ioctl(dev, DIOCCOMMITRULES, &pr)) err(1, "DIOCCOMMITRULES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "nat cleared\n"); return (0); } int pfctl_clear_altq(int dev, int opts) { struct pfioc_altq pa; if (!altqsupport) return (-1); memset(&pa, 0, sizeof(pa)); if (ioctl(dev, DIOCBEGINALTQS, &pa.ticket)) err(1, "DIOCBEGINALTQS"); else if (ioctl(dev, DIOCCOMMITALTQS, &pa.ticket)) err(1, "DIOCCOMMITALTQS"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "altq cleared\n"); return (0); } int pfctl_clear_states(int dev, int opts) { if (ioctl(dev, DIOCCLRSTATES)) err(1, "DIOCCLRSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "states cleared\n"); return (0); } int pfctl_kill_states(int dev, int opts) { struct pfioc_state_kill psk; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&psk, 0, sizeof(psk)); memset(&psk.psk_src.addr.v.a.mask, 0xff, sizeof(psk.psk_src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; psk.psk_af = resp[0]->ai_family; sources++; if (psk.psk_af == AF_INET) psk.psk_src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (psk.psk_af == AF_INET6) psk.psk_src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", psk.psk_af); if (state_killers > 1) { dests = 0; memset(&psk.psk_dst.addr.v.a.mask, 0xff, sizeof(psk.psk_dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (psk.psk_af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (psk.psk_af == AF_INET) psk.psk_dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (psk.psk_af == AF_INET6) psk.psk_dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", psk.psk_af); if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); killed += psk.psk_af; /* fixup psk.psk_af */ psk.psk_af = resp[1]->ai_family; } freeaddrinfo(res[1]); } else { if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); killed += psk.psk_af; /* fixup psk.psk_af */ psk.psk_af = res[0]->ai_family; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states from %d sources and %d " "destinations\n", killed, sources, dests); return (0); } int pfctl_get_pool(int dev, struct pf_pool *pool, u_int32_t nr, u_int32_t ticket, int r_action, char *anchorname, char *rulesetname) { struct pfioc_pooladdr pp; struct pf_pooladdr *pa; u_int32_t pnr, mpnr; memset(&pp, 0, sizeof(pp)); memcpy(pp.anchor, anchorname, sizeof(pp.anchor)); memcpy(pp.ruleset, rulesetname, sizeof(pp.ruleset)); pp.r_action = r_action; pp.r_num = nr; pp.ticket = ticket; if (ioctl(dev, DIOCGETADDRS, &pp)) { warn("DIOCGETADDRS"); return (-1); } mpnr = pp.nr; TAILQ_INIT(&pool->list); for (pnr = 0; pnr < mpnr; ++pnr) { pp.nr = pnr; if (ioctl(dev, DIOCGETADDR, &pp)) { warn("DIOCGETADDR"); return (-1); } pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "calloc"); bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr)); TAILQ_INSERT_TAIL(&pool->list, pa, entries); } return (0); } void pfctl_clear_pool(struct pf_pool *pool) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { TAILQ_REMOVE(&pool->list, pa, entries); free(pa); } } void pfctl_print_rule_counters(struct pf_rule *rule, int opts) { if (opts & PF_OPT_DEBUG) { const char *t[PF_SKIP_COUNT] = { "i", "d", "f", "p", "sa", "sp", "da", "dp" }; int i; printf(" [ Skip steps: "); for (i = 0; i < PF_SKIP_COUNT; ++i) { if (rule->skip[i].nr == rule->nr + 1) continue; printf("%s=", t[i]); if (rule->skip[i].nr == -1) printf("end "); else printf("%u ", rule->skip[i].nr); } printf("]\n"); printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", rule->qname, rule->qid, rule->pqname, rule->pqid); } if (opts & PF_OPT_VERBOSE) printf(" [ Evaluations: %-8llu Packets: %-8llu " "Bytes: %-10llu States: %-6u]\n", (unsigned long long)rule->evaluations, (unsigned long long)rule->packets, (unsigned long long)rule->bytes, rule->states); } int pfctl_show_rules(int dev, int opts, int format, char *anchorname, char *rulesetname) { struct pfioc_rule pr; u_int32_t nr, mnr; int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); if (*anchorname && !*rulesetname) { struct pfioc_ruleset pr; int r; memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "No rulesets in anchor '%s'.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "DIOCGETRULESET"); r = pfctl_show_rules(dev, opts, format, anchorname, pr.name); if (r) return (r); } return (0); } memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); pr.rule.action = PF_SCRUB; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (ioctl(dev, DIOCGETRULE, &pr)) { warn("DIOCGETRULE"); return (-1); } if (pfctl_get_pool(dev, &pr.rule.rpool, nr, pr.ticket, PF_SCRUB, anchorname, rulesetname) != 0) return (-1); switch (format) { case 1: if (pr.rule.label[0]) { printf("%s ", pr.rule.label); printf("%llu %llu %llu\n", (unsigned long long)pr.rule.evaluations, (unsigned long long)pr.rule.packets, (unsigned long long)pr.rule.bytes); } break; default: print_rule(&pr.rule, rule_numbers); pfctl_print_rule_counters(&pr.rule, opts); } pfctl_clear_pool(&pr.rule.rpool); } pr.rule.action = PF_PASS; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (ioctl(dev, DIOCGETRULE, &pr)) { warn("DIOCGETRULE"); return (-1); } if (pfctl_get_pool(dev, &pr.rule.rpool, nr, pr.ticket, PF_PASS, anchorname, rulesetname) != 0) return (-1); switch (format) { case 1: if (pr.rule.label[0]) { printf("%s ", pr.rule.label); printf("%llu %llu %llu\n", (unsigned long long)pr.rule.evaluations, (unsigned long long)pr.rule.packets, (unsigned long long)pr.rule.bytes); } break; default: print_rule(&pr.rule, rule_numbers); pfctl_print_rule_counters(&pr.rule, opts); } pfctl_clear_pool(&pr.rule.rpool); } return (0); } int pfctl_show_nat(int dev, int opts, char *anchorname, char *rulesetname) { struct pfioc_rule pr; u_int32_t mnr, nr; static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; int i; if (*anchorname && !*rulesetname) { struct pfioc_ruleset pr; int r; memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "No rulesets in anchor '%s'.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "DIOCGETRULESET"); r = pfctl_show_nat(dev, opts, anchorname, pr.name); if (r) return (r); } return (0); } memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); for (i = 0; i < 3; i++) { pr.rule.action = nattype[i]; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (ioctl(dev, DIOCGETRULE, &pr)) { warn("DIOCGETRULE"); return (-1); } if (pfctl_get_pool(dev, &pr.rule.rpool, nr, pr.ticket, nattype[i], anchorname, rulesetname) != 0) return (-1); print_rule(&pr.rule, opts & PF_OPT_VERBOSE2); pfctl_print_rule_counters(&pr.rule, opts); pfctl_clear_pool(&pr.rule.rpool); } } return (0); } int pfctl_show_states(int dev, u_int8_t proto, int opts) { struct pfioc_states ps; struct pf_state *p; char *inbuf = NULL; unsigned len = 0; int i; memset(&ps, 0, sizeof(ps)); for (;;) { ps.ps_len = len; if (len) { ps.ps_buf = inbuf = realloc(inbuf, len); if (inbuf == NULL) err(1, "realloc"); } if (ioctl(dev, DIOCGETSTATES, &ps) < 0) { warn("DIOCGETSTATES"); return (-1); } if (ps.ps_len + sizeof(struct pfioc_states) < len) break; if (len == 0 && ps.ps_len == 0) return (0); if (len == 0 && ps.ps_len != 0) len = ps.ps_len; if (ps.ps_len == 0) return (0); /* no states */ len *= 2; } p = ps.ps_states; for (i = 0; i < ps.ps_len; i += sizeof(*p)) { if (!proto || (p->proto == proto)) print_state(p, opts); p++; } return (0); } int pfctl_show_status(int dev) { struct pf_status status; if (ioctl(dev, DIOCGETSTATUS, &status)) { warn("DIOCGETSTATUS"); return (-1); } print_status(&status); return (0); } int pfctl_show_timeouts(int dev) { struct pfioc_tm pt; int i; memset(&pt, 0, sizeof(pt)); for (i = 0; pf_timeouts[i].name; i++) { pt.timeout = pf_timeouts[i].timeout; if (ioctl(dev, DIOCGETTIMEOUT, &pt)) err(1, "DIOCGETTIMEOUT"); printf("%-20s %10d", pf_timeouts[i].name, pt.seconds); if (i >= PFTM_ADAPTIVE_START && i <= PFTM_ADAPTIVE_END) printf(" states"); else printf("s"); printf("\n"); } return (0); } int pfctl_show_limits(int dev) { struct pfioc_limit pl; int i; memset(&pl, 0, sizeof(pl)); for (i = 0; pf_limits[i].name; i++) { pl.index = i; if (ioctl(dev, DIOCGETLIMIT, &pl)) err(1, "DIOCGETLIMIT"); printf("%-10s ", pf_limits[i].name); if (pl.limit == UINT_MAX) printf("unlimited\n"); else printf("hard limit %6u\n", pl.limit); } return (0); } /* callbacks for rule/nat/rdr/addr */ int pfctl_add_pool(struct pfctl *pf, struct pf_pool *p, sa_family_t af) { struct pf_pooladdr *pa; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr)) err(1, "DIOCBEGINADDRS"); } pf->paddr.af = af; TAILQ_FOREACH(pa, &p->list, entries) { memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr)) err(1, "DIOCADDADDR"); } } return (0); } int pfctl_add_rule(struct pfctl *pf, struct pf_rule *r) { u_int8_t rs_num; switch (r->action) { case PF_SCRUB: if ((loadopt & PFCTL_FLAG_FILTER) == 0) return (0); rs_num = PF_RULESET_SCRUB; break; case PF_DROP: case PF_PASS: if ((loadopt & PFCTL_FLAG_FILTER) == 0) return (0); rs_num = PF_RULESET_FILTER; break; case PF_NAT: case PF_NONAT: if ((loadopt & PFCTL_FLAG_NAT) == 0) return (0); rs_num = PF_RULESET_NAT; break; case PF_RDR: case PF_NORDR: if ((loadopt & PFCTL_FLAG_NAT) == 0) return (0); rs_num = PF_RULESET_RDR; break; case PF_BINAT: case PF_NOBINAT: if ((loadopt & PFCTL_FLAG_NAT) == 0) return (0); rs_num = PF_RULESET_BINAT; break; default: errx(1, "Invalid rule type"); break; } if ((pf->opts & PF_OPT_NOACTION) == 0) { if (pfctl_add_pool(pf, &r->rpool, r->af)) return (1); memcpy(&pf->prule[rs_num]->rule, r, sizeof(pf->prule[rs_num]->rule)); pf->prule[rs_num]->pool_ticket = pf->paddr.ticket; if (ioctl(pf->dev, DIOCADDRULE, pf->prule[rs_num])) err(1, "DIOCADDRULE"); } if (pf->opts & PF_OPT_VERBOSE) print_rule(r, pf->opts & PF_OPT_VERBOSE2); pfctl_clear_pool(&r->rpool); return (0); } int pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) { if (altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0) { memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) { if (errno == ENXIO) errx(1, "qtype not configured"); else if (errno == ENODEV) errx(1, "%s: driver does not support " "altq", a->ifname); else err(1, "DIOCADDALTQ"); } } pfaltq_store(&pf->paltq->altq); } return (0); } int pfctl_rules(int dev, char *filename, int opts, char *anchorname, char *rulesetname) { #define ERR(x) do { warn(x); goto _error; } while(0) #define ERRX(x) do { warnx(x); goto _error; } while(0) FILE *fin; struct pfioc_rule pr[PF_RULESET_MAX]; struct pfioc_altq pa; struct pfctl pf; struct pfr_table trs; int i; memset(&pa, 0, sizeof(pa)); memset(&pf, 0, sizeof(pf)); memset(&trs, 0, sizeof(trs)); for (i = 0; i < PF_RULESET_MAX; i++) { memset(&pr[i], 0, sizeof(pr[i])); memcpy(pr[i].anchor, anchorname, sizeof(pr[i].anchor)); memcpy(pr[i].ruleset, rulesetname, sizeof(pr[i].ruleset)); } if (strlcpy(trs.pfrt_anchor, anchorname, sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor) || strlcpy(trs.pfrt_ruleset, rulesetname, sizeof(trs.pfrt_ruleset)) >= sizeof(trs.pfrt_ruleset)) ERRX("pfctl_rules: strlcpy"); if (strcmp(filename, "-") == 0) { fin = stdin; infile = "stdin"; } else { if ((fin = fopen(filename, "r")) == NULL) { warn("%s", filename); return (1); } infile = filename; } if ((opts & PF_OPT_NOACTION) == 0) { if ((loadopt & PFCTL_FLAG_NAT) != 0) { pr[PF_RULESET_NAT].rule.action = PF_NAT; if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_NAT])) ERR("DIOCBEGINRULES"); pr[PF_RULESET_RDR].rule.action = PF_RDR; if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_RDR])) ERR("DIOCBEGINRULES"); pr[PF_RULESET_BINAT].rule.action = PF_BINAT; if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_BINAT])) ERR("DIOCBEGINRULES"); } if (((altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0)) && ioctl(dev, DIOCBEGINALTQS, &pa.ticket)) { ERR("DIOCBEGINALTQS"); } if ((loadopt & PFCTL_FLAG_FILTER) != 0) { pr[PF_RULESET_SCRUB].rule.action = PF_SCRUB; if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_SCRUB])) ERR("DIOCBEGINRULES"); pr[PF_RULESET_FILTER].rule.action = PF_PASS; if (ioctl(dev, DIOCBEGINRULES, &pr[PF_RULESET_FILTER])) ERR("DIOCBEGINRULES"); } if (loadopt & PFCTL_FLAG_TABLE) { if (pfr_ina_begin(&trs, &pf.tticket, NULL, 0) != 0) ERR("begin table"); } } /* fill in callback data */ pf.dev = dev; pf.opts = opts; pf.loadopt = loadopt; pf.paltq = &pa; for (i = 0; i < PF_RULESET_MAX; i++) { pf.prule[i] = &pr[i]; } pf.rule_nr = 0; pf.anchor = anchorname; pf.ruleset = rulesetname; if (parse_rules(fin, &pf) < 0) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Syntax error in config file: " "pf rules not loaded"); else goto _error; } if ((altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0)) if (check_commit_altq(dev, opts) != 0) ERRX("errors in altq config"); if ((opts & PF_OPT_NOACTION) == 0) { if ((loadopt & PFCTL_FLAG_NAT) != 0) { pr[PF_RULESET_NAT].rule.action = PF_NAT; if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_NAT]) && (errno != EINVAL || pf.rule_nr)) ERR("DIOCCOMMITRULES NAT"); pr[PF_RULESET_RDR].rule.action = PF_RDR; if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_RDR]) && (errno != EINVAL || pf.rule_nr)) ERR("DIOCCOMMITRULES RDR"); pr[PF_RULESET_BINAT].rule.action = PF_BINAT; if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_BINAT]) && (errno != EINVAL || pf.rule_nr)) ERR("DIOCCOMMITRULES BINAT"); } if (((altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0)) && ioctl(dev, DIOCCOMMITALTQS, &pa.ticket)) ERR("DIOCCOMMITALTQS"); if ((loadopt & PFCTL_FLAG_FILTER) != 0) { pr[PF_RULESET_SCRUB].rule.action = PF_SCRUB; if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_SCRUB]) && (errno != EINVAL || pf.rule_nr)) ERR("DIOCCOMMITRULES SCRUB"); pr[PF_RULESET_FILTER].rule.action = PF_PASS; if (ioctl(dev, DIOCCOMMITRULES, &pr[PF_RULESET_FILTER]) && (errno != EINVAL || pf.rule_nr)) ERR("DIOCCOMMITRULES FILTER"); } if (loadopt & PFCTL_FLAG_TABLE) { if (pfr_ina_commit(&trs, pf.tticket, NULL, NULL, 0)) ERR("commit table"); pf.tdirty = 0; } } if (fin != stdin) fclose(fin); /* process "load anchor" directives */ if (!anchorname[0] && !rulesetname[0]) if (pfctl_load_anchors(dev, opts) == -1) ERRX("load anchors"); return (0); _error: if (pf.tdirty) /* cleanup kernel leftover */ pfr_ina_begin(&trs, NULL, NULL, 0); exit(1); #undef ERR #undef ERRX } int pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) { struct pfioc_limit pl; int i; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); memset(&pl, 0, sizeof(pl)); for (i = 0; pf_limits[i].name; i++) { if (strcasecmp(opt, pf_limits[i].name) == 0) { pl.index = i; pl.limit = limit; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) { if (errno == EBUSY) { warnx("Current pool " "size exceeds requested " "hard limit"); return (1); } else err(1, "DIOCSETLIMIT"); } } break; } } if (pf_limits[i].name == NULL) { warnx("Bad pool name."); return (1); } if (pf->opts & PF_OPT_VERBOSE) printf("set limit %s %d\n", opt, limit); return (0); } int pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) { struct pfioc_tm pt; int i; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); memset(&pt, 0, sizeof(pt)); for (i = 0; pf_timeouts[i].name; i++) { if (strcasecmp(opt, pf_timeouts[i].name) == 0) { pt.timeout = pf_timeouts[i].timeout; break; } } if (pf_timeouts[i].name == NULL) { warnx("Bad timeout name."); return (1); } pt.seconds = seconds; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) err(1, "DIOCSETTIMEOUT"); } if (pf->opts & PF_OPT_VERBOSE && ! quiet) printf("set timeout %s %d\n", opt, seconds); return (0); } int pfctl_set_optimization(struct pfctl *pf, const char *opt) { const struct pf_hint *hint; int i, r; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_hints[i].name; i++) if (strcasecmp(opt, pf_hints[i].name) == 0) break; hint = pf_hints[i].hint; if (hint == NULL) { warnx("Bad hint name."); return (1); } for (i = 0; hint[i].name; i++) if ((r = pfctl_set_timeout(pf, hint[i].name, hint[i].timeout, 1))) return (r); if (pf->opts & PF_OPT_VERBOSE) printf("set optimization %s\n", opt); return (0); } int pfctl_set_logif(struct pfctl *pf, char *ifname) { struct pfioc_if pi; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); memset(&pi, 0, sizeof(pi)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (!strcmp(ifname, "none")) bzero(pi.ifname, sizeof(pi.ifname)); else { if (strlcpy(pi.ifname, ifname, sizeof(pi.ifname)) >= sizeof(pi.ifname)) errx(1, "pfctl_set_logif: strlcpy"); } if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) err(1, "DIOCSETSTATUSIF"); } if (pf->opts & PF_OPT_VERBOSE) printf("set loginterface %s\n", ifname); return (0); } int pfctl_debug(int dev, u_int32_t level, int opts) { if (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "DIOCSETDEBUG"); if ((opts & PF_OPT_QUIET) == 0) { fprintf(stderr, "debug level set to '"); switch (level) { case PF_DEBUG_NONE: fprintf(stderr, "none"); break; case PF_DEBUG_URGENT: fprintf(stderr, "urgent"); break; case PF_DEBUG_MISC: fprintf(stderr, "misc"); break; case PF_DEBUG_NOISY: fprintf(stderr, "loud"); break; default: fprintf(stderr, ""); break; } fprintf(stderr, "'\n"); } return (0); } int pfctl_clear_rule_counters(int dev, int opts) { if (ioctl(dev, DIOCCLRRULECTRS)) err(1, "DIOCCLRRULECTRS"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: rule counters cleared\n"); return (0); } int pfctl_test_altqsupport(int dev, int opts) { #if defined(__FreeBSD__) && !defined(ENABLE_ALTQ) return (0); #else struct pfioc_altq pa; if (ioctl(dev, DIOCGETALTQS, &pa)) { if (errno == ENODEV) { if (!(opts & PF_OPT_QUIET)) fprintf(stderr, "No ALTQ support in kernel\n" "ALTQ related functions disabled\n"); return (0); } else err(1, "DIOCGETALTQS"); } return (1); #endif } int pfctl_show_anchors(int dev, int opts, char *anchorname) { u_int32_t nr, mnr; if (!*anchorname) { struct pfioc_anchor pa; memset(&pa, 0, sizeof(pa)); if (ioctl(dev, DIOCGETANCHORS, &pa)) { warn("DIOCGETANCHORS"); return (-1); } mnr = pa.nr; if (!(opts & PF_OPT_QUIET)) printf("%u anchors:\n", mnr); for (nr = 0; nr < mnr; ++nr) { pa.nr = nr; if (ioctl(dev, DIOCGETANCHOR, &pa)) { warn("DIOCGETANCHOR"); return (-1); } printf(" %s\n", pa.name); } } else { struct pfioc_ruleset pr; memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "No rulesets in anchor '%s'.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; if (!(opts & PF_OPT_QUIET)) printf("%u rulesets in anchor %s:\n", mnr, anchorname); for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "DIOCGETRULESET"); printf(" %s:%s\n", pr.anchor, pr.name); } } return (0); } const char * pfctl_lookup_option(char *cmd, const char **list) { if (cmd != NULL && *cmd) for (; *list; list++) if (!strncmp(cmd, *list, strlen(cmd))) return (*list); return (NULL); } int main(int argc, char *argv[]) { int error = 0; int ch; int mode = O_RDONLY; int opts = 0; char anchorname[PF_ANCHOR_NAME_SIZE]; char rulesetname[PF_RULESET_NAME_SIZE]; if (argc < 2) usage(); while ((ch = getopt(argc, argv, "a:AdD:eqf:F:ghk:nNOrRs:t:T:vx:z")) != -1) { switch (ch) { case 'a': anchoropt = optarg; break; case 'd': opts |= PF_OPT_DISABLE; mode = O_RDWR; break; case 'D': if (pfctl_cmdline_symset(optarg) < 0) warnx("could not parse macro definition %s", optarg); break; case 'e': opts |= PF_OPT_ENABLE; mode = O_RDWR; break; case 'q': opts |= PF_OPT_QUIET; break; case 'F': clearopt = pfctl_lookup_option(optarg, clearopt_list); if (clearopt == NULL) { warnx("Unknown flush modifier '%s'", optarg); usage(); } mode = O_RDWR; break; case 'k': if (state_killers >= 2) { warnx("can only specify -k twice"); usage(); /* NOTREACHED */ } state_kill[state_killers++] = optarg; mode = O_RDWR; break; case 'n': opts |= PF_OPT_NOACTION; break; case 'N': loadopt |= PFCTL_FLAG_NAT; break; case 'r': opts |= PF_OPT_USEDNS; break; case 'f': rulesopt = optarg; mode = O_RDWR; break; case 'g': opts |= PF_OPT_DEBUG; break; case 'A': loadopt |= PFCTL_FLAG_ALTQ; break; case 'R': loadopt |= PFCTL_FLAG_FILTER; break; case 'O': loadopt |= PFCTL_FLAG_OPTION; break; case 's': showopt = pfctl_lookup_option(optarg, showopt_list); if (showopt == NULL) { warnx("Unknown show modifier '%s'", optarg); usage(); } break; case 't': tableopt = optarg; break; case 'T': tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list); if (tblcmdopt == NULL) { warnx("Unknown table command '%s'", optarg); usage(); } break; case 'v': if (opts & PF_OPT_VERBOSE) opts |= PF_OPT_VERBOSE2; opts |= PF_OPT_VERBOSE; break; case 'x': debugopt = pfctl_lookup_option(optarg, debugopt_list); if (debugopt == NULL) { warnx("Unknown debug level '%s'", optarg); usage(); } mode = O_RDWR; break; case 'z': opts |= PF_OPT_CLRRULECTRS; mode = O_RDWR; break; case 'h': /* FALLTHROUGH */ default: usage(); /* NOTREACHED */ } } if (tblcmdopt != NULL) { argc -= optind; argv += optind; ch = *tblcmdopt; if (ch == 'l') { loadopt |= PFCTL_FLAG_TABLE; tblcmdopt = NULL; } else { mode = strchr("acdfkrz", ch) ? O_RDWR : O_RDONLY; if (opts & PF_OPT_NOACTION) { dev = open("/dev/pf", mode); if (dev >= 0) opts |= PF_OPT_DUMMYACTION; } } } else if (argc != optind) { warnx("unknown command line argument: %s ...", argv[optind]); usage(); /* NOTREACHED */ } if (loadopt == 0) loadopt = ~0; memset(anchorname, 0, sizeof(anchorname)); memset(rulesetname, 0, sizeof(rulesetname)); if (anchoropt != NULL) { char *t; if ((t = strchr(anchoropt, ':')) == NULL) { if (strlcpy(anchorname, anchoropt, sizeof(anchorname)) >= sizeof(anchorname)) errx(1, "anchor name '%s' too long", anchoropt); } else { char *p; if ((p = strdup(anchoropt)) == NULL) err(1, "anchoropt: strdup"); t = strsep(&p, ":"); if (*t == '\0' || *p == '\0') errx(1, "anchor '%s' invalid", anchoropt); if (strlcpy(anchorname, t, sizeof(anchorname)) >= sizeof(anchorname)) errx(1, "anchor name '%s' too long", t); if (strlcpy(rulesetname, p, sizeof(rulesetname)) >= sizeof(rulesetname)) errx(1, "ruleset name '%s' too long", p); free(t); /* not p */ } loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE; } if ((opts & PF_OPT_NOACTION) == 0) { dev = open("/dev/pf", mode); if (dev == -1) err(1, "/dev/pf"); altqsupport = pfctl_test_altqsupport(dev, opts); } else { /* turn off options */ opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); clearopt = showopt = debugopt = NULL; #if defined(__FreeBSD__) && !defined(ENABLE_ALTQ) altqsupport = 0; #else altqsupport = 1; #endif } if (opts & PF_OPT_DISABLE) if (pfctl_disable(dev, opts)) error = 1; if (showopt != NULL) { switch (*showopt) { case 'A': pfctl_show_anchors(dev, opts, anchorname); break; case 'r': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, opts, 0, anchorname, rulesetname); break; case 'l': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, opts, 1, anchorname, rulesetname); break; case 'n': pfctl_load_fingerprints(dev, opts); pfctl_show_nat(dev, opts, anchorname, rulesetname); break; case 'q': pfctl_show_altq(dev, opts, opts & PF_OPT_VERBOSE2); break; case 's': pfctl_show_states(dev, 0, opts); break; case 'i': pfctl_show_status(dev); break; case 't': pfctl_show_timeouts(dev); break; case 'm': pfctl_show_limits(dev); break; case 'a': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, opts, 0, anchorname, rulesetname); pfctl_show_nat(dev, opts, anchorname, rulesetname); pfctl_show_altq(dev, opts, 0); pfctl_show_states(dev, 0, opts); pfctl_show_status(dev); pfctl_show_rules(dev, opts, 1, anchorname, rulesetname); pfctl_show_timeouts(dev); pfctl_show_limits(dev); pfctl_show_tables(anchorname, rulesetname, opts); pfctl_show_fingerprints(opts); break; case 'T': pfctl_show_tables(anchorname, rulesetname, opts); break; case 'o': pfctl_load_fingerprints(dev, opts); pfctl_show_fingerprints(opts); break; } } if (clearopt != NULL) { switch (*clearopt) { case 'r': pfctl_clear_rules(dev, opts, anchorname, rulesetname); break; case 'n': pfctl_clear_nat(dev, opts, anchorname, rulesetname); break; case 'q': pfctl_clear_altq(dev, opts); break; case 's': pfctl_clear_states(dev, opts); break; case 'i': pfctl_clear_stats(dev, opts); break; case 'a': pfctl_clear_rules(dev, opts, anchorname, rulesetname); pfctl_clear_nat(dev, opts, anchorname, rulesetname); pfctl_clear_altq(dev, opts); pfctl_clear_states(dev, opts); pfctl_clear_stats(dev, opts); pfctl_clear_tables(anchorname, rulesetname, opts); pfctl_clear_fingerprints(dev, opts); break; case 'o': pfctl_clear_fingerprints(dev, opts); break; case 'T': pfctl_clear_tables(anchorname, rulesetname, opts); break; } } if (state_killers) pfctl_kill_states(dev, opts); if (tblcmdopt != NULL) { error = pfctl_command_tables(argc, argv, tableopt, tblcmdopt, rulesopt, anchorname, rulesetname, opts); rulesopt = NULL; } if (rulesopt != NULL) if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE)) error = 1; if (rulesopt != NULL) { if (pfctl_rules(dev, rulesopt, opts, anchorname, rulesetname)) error = 1; else if (!(opts & PF_OPT_NOACTION) && (loadopt & PFCTL_FLAG_TABLE)) warn_namespace_collision(NULL); } if (opts & PF_OPT_ENABLE) if (pfctl_enable(dev, opts)) error = 1; if (debugopt != NULL) { switch (*debugopt) { case 'n': pfctl_debug(dev, PF_DEBUG_NONE, opts); break; case 'u': pfctl_debug(dev, PF_DEBUG_URGENT, opts); break; case 'm': pfctl_debug(dev, PF_DEBUG_MISC, opts); break; case 'l': pfctl_debug(dev, PF_DEBUG_NOISY, opts); break; } } if (opts & PF_OPT_CLRRULECTRS) { if (pfctl_clear_rule_counters(dev, opts)) error = 1; } exit(error); } diff --git a/contrib/pf/pfctl/pfctl.h b/contrib/pf/pfctl/pfctl.h index 8ee1ad46ce9e..7ec0c3b52c46 100644 --- a/contrib/pf/pfctl/pfctl.h +++ b/contrib/pf/pfctl/pfctl.h @@ -1,120 +1,120 @@ -/* $FreeBSD$ */ /* $OpenBSD: pfctl.h,v 1.25 2003/08/29 21:47:36 cedric Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * + * $FreeBSD$ */ #ifndef _PFCTL_H_ #define _PFCTL_H_ enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, PFRB_MAX }; struct pfr_buffer { int pfrb_type; /* type of content, see enum above */ int pfrb_size; /* number of objects in buffer */ int pfrb_msize; /* maximum number of objects in buffer */ void *pfrb_caddr; /* malloc'ated memory area */ }; #define PFRB_FOREACH(var, buf) \ for ((var) = pfr_buf_next((buf), NULL); \ (var) != NULL; \ (var) = pfr_buf_next((buf), (var))) void pfr_set_fd(int); int pfr_get_fd(void); int pfr_clr_tables(struct pfr_table *, int *, int); int pfr_add_tables(struct pfr_table *, int, int *, int); int pfr_del_tables(struct pfr_table *, int, int *, int); int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); int pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int); int pfr_clr_tstats(struct pfr_table *, int, int *, int); int pfr_clr_addrs(struct pfr_table *, int *, int); int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int *, int *, int *, int); int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_set_tflags(struct pfr_table *, int, int, int, int *, int *, int); int pfr_ina_begin(struct pfr_table *, int *, int *, int); int pfr_ina_commit(struct pfr_table *, int, int *, int *, int); int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, int *, int, int); void pfr_buf_clear(struct pfr_buffer *); int pfr_buf_add(struct pfr_buffer *, const void *); void *pfr_buf_next(struct pfr_buffer *, const void *); int pfr_buf_grow(struct pfr_buffer *, int); int pfr_buf_load(struct pfr_buffer *, char *, int, int (*)(struct pfr_buffer *, char *, int)); char *pfr_strerror(int); int pfctl_clear_tables(const char *, const char *, int); int pfctl_show_tables(const char *, const char *, int); int pfctl_command_tables(int, char *[], char *, const char *, char *, const char *, const char *, int); int pfctl_show_altq(int, int, int); void warn_namespace_collision(const char *); #ifdef __FreeBSD__ extern int altqsupport; #endif #ifndef DEFAULT_PRIORITY #define DEFAULT_PRIORITY 1 #endif #ifndef DEFAULT_QLIMIT #define DEFAULT_QLIMIT 50 #endif /* * generalized service curve used for admission control */ struct segment { LIST_ENTRY(segment) _next; double x, y, d, m; }; int check_commit_altq(int, int); void pfaltq_store(struct pf_altq *); void pfaltq_free(struct pf_altq *); struct pf_altq *pfaltq_lookup(const char *); char *rate2str(double); void print_addr(struct pf_addr_wrap *, sa_family_t, int); void print_host(struct pf_state_host *, sa_family_t, int); void print_seq(struct pf_state_peer *); void print_state(struct pf_state *, int); int unmask(struct pf_addr *, sa_family_t); int pfctl_cmdline_symset(char *); #endif /* _PFCTL_H_ */ diff --git a/contrib/pf/pfctl/pfctl_altq.c b/contrib/pf/pfctl/pfctl_altq.c index 8232710e73e4..534902b967e1 100644 --- a/contrib/pf/pfctl/pfctl_altq.c +++ b/contrib/pf/pfctl/pfctl_altq.c @@ -1,1242 +1,1244 @@ -/* $FreeBSD$ */ /* $OpenBSD: pfctl_altq.c,v 1.77 2003/08/22 21:50:34 david Exp $ */ /* * Copyright (c) 2002 * Sony Computer Science Laboratories Inc. * Copyright (c) 2002, 2003 Henning Brauer * * 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. */ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" #define is_sc_null(sc) (((sc) == NULL) || ((sc)->m1 == 0 && (sc)->m2 == 0)) TAILQ_HEAD(altqs, pf_altq) altqs = TAILQ_HEAD_INITIALIZER(altqs); LIST_HEAD(gen_sc, segment) rtsc, lssc; struct pf_altq *qname_to_pfaltq(const char *, const char *); u_int32_t qname_to_qid(const char *); static int eval_pfqueue_cbq(struct pfctl *, struct pf_altq *); static int cbq_compute_idletime(struct pfctl *, struct pf_altq *); static int check_commit_cbq(int, int, struct pf_altq *); static int print_cbq_opts(const struct pf_altq *); static int eval_pfqueue_priq(struct pfctl *, struct pf_altq *); static int check_commit_priq(int, int, struct pf_altq *); static int print_priq_opts(const struct pf_altq *); static int eval_pfqueue_hfsc(struct pfctl *, struct pf_altq *); static int check_commit_hfsc(int, int, struct pf_altq *); static int print_hfsc_opts(const struct pf_altq *, const struct node_queue_opt *); static void gsc_add_sc(struct gen_sc *, struct service_curve *); static int is_gsc_under_sc(struct gen_sc *, struct service_curve *); static void gsc_destroy(struct gen_sc *); static struct segment *gsc_getentry(struct gen_sc *, double); static int gsc_add_seg(struct gen_sc *, double, double, double, double); static double sc_x2y(struct service_curve *, double); #ifdef __FreeBSD__ u_int32_t getifspeed(int, char *); #else u_int32_t getifspeed(char *); #endif u_long getifmtu(char *); int eval_queue_opts(struct pf_altq *, struct node_queue_opt *, u_int32_t); u_int32_t eval_bwspec(struct node_queue_bw *, u_int32_t); void print_hfsc_sc(const char *, u_int, u_int, u_int, const struct node_hfsc_sc *); static u_int32_t max_qid = 1; void pfaltq_store(struct pf_altq *a) { struct pf_altq *altq; if ((altq = malloc(sizeof(*altq))) == NULL) err(1, "malloc"); memcpy(altq, a, sizeof(struct pf_altq)); TAILQ_INSERT_TAIL(&altqs, altq, entries); } void pfaltq_free(struct pf_altq *a) { struct pf_altq *altq; TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(a->ifname, altq->ifname, IFNAMSIZ) == 0 && strncmp(a->qname, altq->qname, PF_QNAME_SIZE) == 0) { TAILQ_REMOVE(&altqs, altq, entries); free(altq); return; } } } struct pf_altq * pfaltq_lookup(const char *ifname) { struct pf_altq *altq; TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(ifname, altq->ifname, IFNAMSIZ) == 0 && altq->qname[0] == 0) return (altq); } return (NULL); } struct pf_altq * qname_to_pfaltq(const char *qname, const char *ifname) { struct pf_altq *altq; TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(ifname, altq->ifname, IFNAMSIZ) == 0 && strncmp(qname, altq->qname, PF_QNAME_SIZE) == 0) return (altq); } return (NULL); } u_int32_t qname_to_qid(const char *qname) { struct pf_altq *altq; /* * We guarantee that same named queues on different interfaces * have the same qid, so we do NOT need to limit matching on * one interface! */ TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(qname, altq->qname, PF_QNAME_SIZE) == 0) return (altq->qid); } return (0); } void print_altq(const struct pf_altq *a, unsigned level, struct node_queue_bw *bw, struct node_queue_opt *qopts) { if (a->qname[0] != '\0') { print_queue(a, level, bw, 0, qopts); return; } printf("altq on %s ", a->ifname); switch(a->scheduler) { case ALTQT_CBQ: if (!print_cbq_opts(a)) printf("cbq "); break; case ALTQT_PRIQ: if (!print_priq_opts(a)) printf("priq "); break; case ALTQT_HFSC: if (!print_hfsc_opts(a, qopts)) printf("hfsc "); break; } if (bw != NULL && bw->bw_percent > 0) { if (bw->bw_percent < 100) printf("bandwidth %u%% ", bw->bw_percent); } else printf("bandwidth %s ", rate2str((double)a->ifbandwidth)); if (a->qlimit != DEFAULT_QLIMIT) printf("qlimit %u ", a->qlimit); printf("tbrsize %u ", a->tbrsize); } void print_queue(const struct pf_altq *a, unsigned level, struct node_queue_bw *bw, int print_interface, struct node_queue_opt *qopts) { unsigned i; printf("queue "); for (i = 0; i < level; ++i) printf(" "); printf("%s ", a->qname); if (print_interface) printf("on %s ", a->ifname); if (a->scheduler == ALTQT_CBQ || a->scheduler == ALTQT_HFSC) { if (bw != NULL && bw->bw_percent > 0) { if (bw->bw_percent < 100) printf("bandwidth %u%% ", bw->bw_percent); } else printf("bandwidth %s ", rate2str((double)a->bandwidth)); } if (a->priority != DEFAULT_PRIORITY) printf("priority %u ", a->priority); if (a->qlimit != DEFAULT_QLIMIT) printf("qlimit %u ", a->qlimit); switch (a->scheduler) { case ALTQT_CBQ: print_cbq_opts(a); break; case ALTQT_PRIQ: print_priq_opts(a); break; case ALTQT_HFSC: print_hfsc_opts(a, qopts); break; } } /* * eval_pfaltq computes the discipline parameters. */ int eval_pfaltq(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw, struct node_queue_opt *opts) { u_int rate, size, errors = 0; if (bw->bw_absolute > 0) pa->ifbandwidth = bw->bw_absolute; else #ifdef __FreeBSD__ if ((rate = getifspeed(pf->dev, pa->ifname)) == 0) { #else if ((rate = getifspeed(pa->ifname)) == 0) { #endif fprintf(stderr, "cannot determine interface bandwidth " "for %s, specify an absolute bandwidth\n", pa->ifname); errors++; } else if ((pa->ifbandwidth = eval_bwspec(bw, rate)) == 0) pa->ifbandwidth = rate; errors += eval_queue_opts(pa, opts, pa->ifbandwidth); /* if tbrsize is not specified, use heuristics */ if (pa->tbrsize == 0) { rate = pa->ifbandwidth; if (rate <= 1 * 1000 * 1000) size = 1; else if (rate <= 10 * 1000 * 1000) size = 4; else if (rate <= 200 * 1000 * 1000) size = 8; else size = 24; size = size * getifmtu(pa->ifname); pa->tbrsize = size; } return (errors); } /* * check_commit_altq does consistency check for each interface */ int check_commit_altq(int dev, int opts) { struct pf_altq *altq; int error = 0; /* call the discipline check for each interface. */ TAILQ_FOREACH(altq, &altqs, entries) { if (altq->qname[0] == 0) { switch (altq->scheduler) { case ALTQT_CBQ: error = check_commit_cbq(dev, opts, altq); break; case ALTQT_PRIQ: error = check_commit_priq(dev, opts, altq); break; case ALTQT_HFSC: error = check_commit_hfsc(dev, opts, altq); break; default: break; } } } return (error); } /* * eval_pfqueue computes the queue parameters. */ int eval_pfqueue(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw, struct node_queue_opt *opts) { /* should be merged with expand_queue */ struct pf_altq *if_pa, *parent; int error = 0; /* find the corresponding interface and copy fields used by queues */ if ((if_pa = pfaltq_lookup(pa->ifname)) == NULL) { fprintf(stderr, "altq not defined on %s\n", pa->ifname); return (1); } pa->scheduler = if_pa->scheduler; pa->ifbandwidth = if_pa->ifbandwidth; if (qname_to_pfaltq(pa->qname, pa->ifname) != NULL) { fprintf(stderr, "queue %s already exists on interface %s\n", pa->qname, pa->ifname); return (1); } pa->qid = qname_to_qid(pa->qname); parent = NULL; if (pa->parent[0] != 0) { parent = qname_to_pfaltq(pa->parent, pa->ifname); if (parent == NULL) { fprintf(stderr, "parent %s not found for %s\n", pa->parent, pa->qname); return (1); } pa->parent_qid = parent->qid; } if (pa->qlimit == 0) pa->qlimit = DEFAULT_QLIMIT; if (pa->scheduler == ALTQT_CBQ || pa->scheduler == ALTQT_HFSC) { if ((pa->bandwidth = eval_bwspec(bw, parent == NULL ? 0 : parent->bandwidth)) == 0) { fprintf(stderr, "bandwidth for %s invalid (%d / %d)\n", pa->qname, bw->bw_absolute, bw->bw_percent); return (1); } if (pa->bandwidth > pa->ifbandwidth) { fprintf(stderr, "bandwidth for %s higher than " "interface\n", pa->qname); return (1); } if (parent != NULL && pa->bandwidth > parent->bandwidth) { fprintf(stderr, "bandwidth for %s higher than parent\n", pa->qname); return (1); } } if (eval_queue_opts(pa, opts, parent == NULL? 0 : parent->bandwidth)) return (1); switch (pa->scheduler) { case ALTQT_CBQ: error = eval_pfqueue_cbq(pf, pa); break; case ALTQT_PRIQ: error = eval_pfqueue_priq(pf, pa); break; case ALTQT_HFSC: error = eval_pfqueue_hfsc(pf, pa); break; default: break; } return (error); } /* * CBQ support functions */ #define RM_FILTER_GAIN 5 /* log2 of gain, e.g., 5 => 31/32 */ #define RM_NS_PER_SEC (1000000000) static int eval_pfqueue_cbq(struct pfctl *pf, struct pf_altq *pa) { struct cbq_opts *opts; u_int ifmtu; if (pa->priority >= CBQ_MAXPRI) { warnx("priority out of range: max %d", CBQ_MAXPRI - 1); return (-1); } ifmtu = getifmtu(pa->ifname); opts = &pa->pq_u.cbq_opts; if (opts->pktsize == 0) { /* use default */ opts->pktsize = ifmtu; if (opts->pktsize > MCLBYTES) /* do what TCP does */ opts->pktsize &= ~MCLBYTES; } else if (opts->pktsize > ifmtu) opts->pktsize = ifmtu; if (opts->maxpktsize == 0) /* use default */ opts->maxpktsize = ifmtu; else if (opts->maxpktsize > ifmtu) opts->pktsize = ifmtu; if (opts->pktsize > opts->maxpktsize) opts->pktsize = opts->maxpktsize; if (pa->parent[0] == 0) opts->flags |= (CBQCLF_ROOTCLASS | CBQCLF_WRR); else if (pa->qid == 0 && (opts->flags & CBQCLF_DEFCLASS) == 0) pa->qid = ++max_qid; cbq_compute_idletime(pf, pa); return (0); } /* * compute ns_per_byte, maxidle, minidle, and offtime */ static int cbq_compute_idletime(struct pfctl *pf, struct pf_altq *pa) { struct cbq_opts *opts; double maxidle_s, maxidle, minidle; double offtime, nsPerByte, ifnsPerByte, ptime, cptime; double z, g, f, gton, gtom; u_int minburst, maxburst; opts = &pa->pq_u.cbq_opts; ifnsPerByte = (1.0 / (double)pa->ifbandwidth) * RM_NS_PER_SEC * 8; minburst = opts->minburst; maxburst = opts->maxburst; if (pa->bandwidth == 0) f = 0.0001; /* small enough? */ else f = ((double) pa->bandwidth / (double) pa->ifbandwidth); nsPerByte = ifnsPerByte / f; ptime = (double)opts->pktsize * ifnsPerByte; cptime = ptime * (1.0 - f) / f; if (nsPerByte * (double)opts->maxpktsize > (double)INT_MAX) { /* * this causes integer overflow in kernel! * (bandwidth < 6Kbps when max_pkt_size=1500) */ if (pa->bandwidth != 0 && (pf->opts & PF_OPT_QUIET) == 0) warnx("queue bandwidth must be larger than %s", rate2str(ifnsPerByte * (double)opts->maxpktsize / (double)INT_MAX * (double)pa->ifbandwidth)); fprintf(stderr, "cbq: queue %s is too slow!\n", pa->qname); nsPerByte = (double)(INT_MAX / opts->maxpktsize); } if (maxburst == 0) { /* use default */ if (cptime > 10.0 * 1000000) maxburst = 4; else maxburst = 16; } if (minburst == 0) /* use default */ minburst = 2; if (minburst > maxburst) minburst = maxburst; z = (double)(1 << RM_FILTER_GAIN); g = (1.0 - 1.0 / z); gton = pow(g, (double)maxburst); gtom = pow(g, (double)(minburst-1)); maxidle = ((1.0 / f - 1.0) * ((1.0 - gton) / gton)); maxidle_s = (1.0 - g); if (maxidle > maxidle_s) maxidle = ptime * maxidle; else maxidle = ptime * maxidle_s; if (minburst) offtime = cptime * (1.0 + 1.0/(1.0 - g) * (1.0 - gtom) / gtom); else offtime = cptime; minidle = -((double)opts->maxpktsize * (double)nsPerByte); /* scale parameters */ maxidle = ((maxidle * 8.0) / nsPerByte) * pow(2.0, (double)RM_FILTER_GAIN); offtime = (offtime * 8.0) / nsPerByte * pow(2.0, (double)RM_FILTER_GAIN); minidle = ((minidle * 8.0) / nsPerByte) * pow(2.0, (double)RM_FILTER_GAIN); maxidle = maxidle / 1000.0; offtime = offtime / 1000.0; minidle = minidle / 1000.0; opts->minburst = minburst; opts->maxburst = maxburst; opts->ns_per_byte = (u_int) nsPerByte; opts->maxidle = (u_int) fabs(maxidle); opts->minidle = (int)minidle; opts->offtime = (u_int) fabs(offtime); return (0); } static int check_commit_cbq(int dev, int opts, struct pf_altq *pa) { struct pf_altq *altq; int root_class, default_class; int error = 0; /* * check if cbq has one root queue and one default queue * for this interface */ root_class = default_class = 0; TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) continue; if (altq->qname[0] == 0) /* this is for interface */ continue; if (altq->pq_u.cbq_opts.flags & CBQCLF_ROOTCLASS) root_class++; if (altq->pq_u.cbq_opts.flags & CBQCLF_DEFCLASS) default_class++; } if (root_class != 1) { warnx("should have one root queue on %s", pa->ifname); error++; } if (default_class != 1) { warnx("should have one default queue on %s", pa->ifname); error++; } return (error); } static int print_cbq_opts(const struct pf_altq *a) { const struct cbq_opts *opts; opts = &a->pq_u.cbq_opts; if (opts->flags) { printf("cbq("); if (opts->flags & CBQCLF_RED) printf(" red"); if (opts->flags & CBQCLF_ECN) printf(" ecn"); if (opts->flags & CBQCLF_RIO) printf(" rio"); if (opts->flags & CBQCLF_CLEARDSCP) printf(" cleardscp"); if (opts->flags & CBQCLF_FLOWVALVE) printf(" flowvalve"); if (opts->flags & CBQCLF_BORROW) printf(" borrow"); if (opts->flags & CBQCLF_WRR) printf(" wrr"); if (opts->flags & CBQCLF_EFFICIENT) printf(" efficient"); if (opts->flags & CBQCLF_ROOTCLASS) printf(" root"); if (opts->flags & CBQCLF_DEFCLASS) printf(" default"); printf(" ) "); return (1); } else return (0); } /* * PRIQ support functions */ static int eval_pfqueue_priq(struct pfctl *pf, struct pf_altq *pa) { struct pf_altq *altq; if (pa->priority >= PRIQ_MAXPRI) { warnx("priority out of range: max %d", PRIQ_MAXPRI - 1); return (-1); } /* the priority should be unique for the interface */ TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) == 0 && altq->qname[0] != 0 && altq->priority == pa->priority) { warnx("%s and %s have the same priority", altq->qname, pa->qname); return (-1); } } if (pa->qid == 0) pa->qid = ++max_qid; return (0); } static int check_commit_priq(int dev, int opts, struct pf_altq *pa) { struct pf_altq *altq; int default_class; int error = 0; /* * check if priq has one default class for this interface */ default_class = 0; TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) continue; if (altq->qname[0] == 0) /* this is for interface */ continue; if (altq->pq_u.priq_opts.flags & PRCF_DEFAULTCLASS) default_class++; } if (default_class != 1) { warnx("should have one default queue on %s", pa->ifname); error++; } return (error); } static int print_priq_opts(const struct pf_altq *a) { const struct priq_opts *opts; opts = &a->pq_u.priq_opts; if (opts->flags) { printf("priq("); if (opts->flags & PRCF_RED) printf(" red"); if (opts->flags & PRCF_ECN) printf(" ecn"); if (opts->flags & PRCF_RIO) printf(" rio"); if (opts->flags & PRCF_CLEARDSCP) printf(" cleardscp"); if (opts->flags & PRCF_DEFAULTCLASS) printf(" default"); printf(" ) "); return (1); } else return (0); } /* * HFSC support functions */ static int eval_pfqueue_hfsc(struct pfctl *pf, struct pf_altq *pa) { struct pf_altq *altq, *parent; struct hfsc_opts *opts; struct service_curve sc; opts = &pa->pq_u.hfsc_opts; if (pa->parent[0] == 0) { /* root queue */ pa->qid = HFSC_ROOTCLASS_HANDLE; opts->lssc_m1 = pa->ifbandwidth; opts->lssc_m2 = pa->ifbandwidth; opts->lssc_d = 0; return (0); } else if (pa->qid == 0) pa->qid = ++max_qid; LIST_INIT(&rtsc); LIST_INIT(&lssc); /* if link_share is not specified, use bandwidth */ if (opts->lssc_m2 == 0) opts->lssc_m2 = pa->bandwidth; if ((opts->rtsc_m1 > 0 && opts->rtsc_m2 == 0) || (opts->lssc_m1 > 0 && opts->lssc_m2 == 0) || (opts->ulsc_m1 > 0 && opts->ulsc_m2 == 0)) { warnx("m2 is zero for %s", pa->qname); return (-1); } if ((opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) || (opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) || (opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0)) { warnx("m1 must be zero for convex curve: %s", pa->qname); return (-1); } /* * admission control: * for the real-time service curve, the sum of the service curves * should not exceed 80% of the interface bandwidth. 20% is reserved * not to over-commit the actual interface bandwidth. * for the link-sharing service curve, the sum of the child service * curve should not exceed the parent service curve. * for the upper-limit service curve, the assigned bandwidth should * be smaller than the interface bandwidth, and the upper-limit should * be larger than the real-time service curve when both are defined. */ parent = qname_to_pfaltq(pa->parent, pa->ifname); if (parent == NULL) errx(1, "parent %s not found for %s", pa->parent, pa->qname); TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) continue; if (altq->qname[0] == 0) /* this is for interface */ continue; /* if the class has a real-time service curve, add it. */ if (opts->rtsc_m2 != 0 && altq->pq_u.hfsc_opts.rtsc_m2 != 0) { sc.m1 = altq->pq_u.hfsc_opts.rtsc_m1; sc.d = altq->pq_u.hfsc_opts.rtsc_d; sc.m2 = altq->pq_u.hfsc_opts.rtsc_m2; gsc_add_sc(&rtsc, &sc); } if (strncmp(altq->parent, pa->parent, PF_QNAME_SIZE) != 0) continue; /* if the class has a link-sharing service curve, add it. */ if (opts->lssc_m2 != 0 && altq->pq_u.hfsc_opts.lssc_m2 != 0) { sc.m1 = altq->pq_u.hfsc_opts.lssc_m1; sc.d = altq->pq_u.hfsc_opts.lssc_d; sc.m2 = altq->pq_u.hfsc_opts.lssc_m2; gsc_add_sc(&lssc, &sc); } } /* check the real-time service curve. reserve 20% of interface bw */ if (opts->rtsc_m2 != 0) { sc.m1 = 0; sc.d = 0; sc.m2 = pa->ifbandwidth / 100 * 80; if (!is_gsc_under_sc(&rtsc, &sc)) { warnx("real-time sc exceeds the interface bandwidth"); goto err_ret; } } /* check the link-sharing service curve. */ if (opts->lssc_m2 != 0) { sc.m1 = parent->pq_u.hfsc_opts.lssc_m1; sc.d = parent->pq_u.hfsc_opts.lssc_d; sc.m2 = parent->pq_u.hfsc_opts.lssc_m2; if (!is_gsc_under_sc(&lssc, &sc)) { warnx("link-sharing sc exceeds parent's sc"); goto err_ret; } } /* check the upper-limit service curve. */ if (opts->ulsc_m2 != 0) { if (opts->ulsc_m1 > pa->ifbandwidth || opts->ulsc_m2 > pa->ifbandwidth) { warnx("upper-limit larger than interface bandwidth"); goto err_ret; } if (opts->rtsc_m2 != 0 && opts->rtsc_m2 > opts->ulsc_m2) { warnx("upper-limit sc smaller than real-time sc"); goto err_ret; } } gsc_destroy(&rtsc); gsc_destroy(&lssc); return (0); err_ret: gsc_destroy(&rtsc); gsc_destroy(&lssc); return (-1); } static int check_commit_hfsc(int dev, int opts, struct pf_altq *pa) { struct pf_altq *altq, *def = NULL; int default_class; int error = 0; /* check if hfsc has one default queue for this interface */ default_class = 0; TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) continue; if (altq->qname[0] == 0) /* this is for interface */ continue; if (altq->parent[0] == 0) /* dummy root */ continue; if (altq->pq_u.hfsc_opts.flags & HFCF_DEFAULTCLASS) { default_class++; def = altq; } } if (default_class != 1) { warnx("should have one default queue on %s", pa->ifname); return (1); } /* make sure the default queue is a leaf */ TAILQ_FOREACH(altq, &altqs, entries) { if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) continue; if (altq->qname[0] == 0) /* this is for interface */ continue; if (strncmp(altq->parent, def->qname, PF_QNAME_SIZE) == 0) { warnx("default queue is not a leaf"); error++; } } return (error); } static int print_hfsc_opts(const struct pf_altq *a, const struct node_queue_opt *qopts) { const struct hfsc_opts *opts; const struct node_hfsc_sc *rtsc, *lssc, *ulsc; opts = &a->pq_u.hfsc_opts; if (qopts == NULL) rtsc = lssc = ulsc = NULL; else { rtsc = &qopts->data.hfsc_opts.realtime; lssc = &qopts->data.hfsc_opts.linkshare; ulsc = &qopts->data.hfsc_opts.upperlimit; } if (opts->flags || opts->rtsc_m2 != 0 || opts->ulsc_m2 != 0 || (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || opts->lssc_d != 0))) { printf("hfsc("); if (opts->flags & HFCF_RED) printf(" red"); if (opts->flags & HFCF_ECN) printf(" ecn"); if (opts->flags & HFCF_RIO) printf(" rio"); if (opts->flags & HFCF_CLEARDSCP) printf(" cleardscp"); if (opts->flags & HFCF_DEFAULTCLASS) printf(" default"); if (opts->rtsc_m2 != 0) print_hfsc_sc("realtime", opts->rtsc_m1, opts->rtsc_d, opts->rtsc_m2, rtsc); if (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || opts->lssc_d != 0)) print_hfsc_sc("linkshare", opts->lssc_m1, opts->lssc_d, opts->lssc_m2, lssc); if (opts->ulsc_m2 != 0) print_hfsc_sc("upperlimit", opts->ulsc_m1, opts->ulsc_d, opts->ulsc_m2, ulsc); printf(" ) "); return (1); } else return (0); } /* * admission control using generalized service curve */ #ifndef INFINITY #define INFINITY HUGE_VAL /* positive infinity defined in */ #endif /* add a new service curve to a generalized service curve */ static void gsc_add_sc(struct gen_sc *gsc, struct service_curve *sc) { if (is_sc_null(sc)) return; if (sc->d != 0) gsc_add_seg(gsc, 0.0, 0.0, (double)sc->d, (double)sc->m1); gsc_add_seg(gsc, (double)sc->d, 0.0, INFINITY, (double)sc->m2); } /* * check whether all points of a generalized service curve have * their y-coordinates no larger than a given two-piece linear * service curve. */ static int is_gsc_under_sc(struct gen_sc *gsc, struct service_curve *sc) { struct segment *s, *last, *end; double y; if (is_sc_null(sc)) { if (LIST_EMPTY(gsc)) return (1); LIST_FOREACH(s, gsc, _next) { if (s->m != 0) return (0); } return (1); } /* * gsc has a dummy entry at the end with x = INFINITY. * loop through up to this dummy entry. */ end = gsc_getentry(gsc, INFINITY); if (end == NULL) return (1); last = NULL; for (s = LIST_FIRST(gsc); s != end; s = LIST_NEXT(s, _next)) { if (s->y > sc_x2y(sc, s->x)) return (0); last = s; } /* last now holds the real last segment */ if (last == NULL) return (1); if (last->m > sc->m2) return (0); if (last->x < sc->d && last->m > sc->m1) { y = last->y + (sc->d - last->x) * last->m; if (y > sc_x2y(sc, sc->d)) return (0); } return (1); } static void gsc_destroy(struct gen_sc *gsc) { struct segment *s; while ((s = LIST_FIRST(gsc)) != NULL) { LIST_REMOVE(s, _next); free(s); } } /* * return a segment entry starting at x. * if gsc has no entry starting at x, a new entry is created at x. */ static struct segment * gsc_getentry(struct gen_sc *gsc, double x) { struct segment *new, *prev, *s; prev = NULL; LIST_FOREACH(s, gsc, _next) { if (s->x == x) return (s); /* matching entry found */ else if (s->x < x) prev = s; else break; } /* we have to create a new entry */ if ((new = calloc(1, sizeof(struct segment))) == NULL) return (NULL); new->x = x; if (x == INFINITY || s == NULL) new->d = 0; else if (s->x == INFINITY) new->d = INFINITY; else new->d = s->x - x; if (prev == NULL) { /* insert the new entry at the head of the list */ new->y = 0; new->m = 0; LIST_INSERT_HEAD(gsc, new, _next); } else { /* * the start point intersects with the segment pointed by * prev. divide prev into 2 segments */ if (x == INFINITY) { prev->d = INFINITY; if (prev->m == 0) new->y = prev->y; else new->y = INFINITY; } else { prev->d = x - prev->x; new->y = prev->d * prev->m + prev->y; } new->m = prev->m; LIST_INSERT_AFTER(prev, new, _next); } return (new); } /* add a segment to a generalized service curve */ static int gsc_add_seg(struct gen_sc *gsc, double x, double y, double d, double m) { struct segment *start, *end, *s; double x2; if (d == INFINITY) x2 = INFINITY; else x2 = x + d; start = gsc_getentry(gsc, x); end = gsc_getentry(gsc, x2); if (start == NULL || end == NULL) return (-1); for (s = start; s != end; s = LIST_NEXT(s, _next)) { s->m += m; s->y += y + (s->x - x) * m; } end = gsc_getentry(gsc, INFINITY); for (; s != end; s = LIST_NEXT(s, _next)) { s->y += m * d; } return (0); } /* get y-projection of a service curve */ static double sc_x2y(struct service_curve *sc, double x) { double y; if (x <= (double)sc->d) /* y belongs to the 1st segment */ y = x * (double)sc->m1; else /* y belongs to the 2nd segment */ y = (double)sc->d * (double)sc->m1 + (x - (double)sc->d) * (double)sc->m2; return (y); } /* * misc utilities */ #define R2S_BUFS 8 #define RATESTR_MAX 16 char * rate2str(double rate) { char *buf; static char r2sbuf[R2S_BUFS][RATESTR_MAX]; /* ring bufer */ static int idx = 0; int i; static const char unit[] = " KMG"; buf = r2sbuf[idx++]; if (idx == R2S_BUFS) idx = 0; for (i = 0; rate >= 1000 && i <= 3; i++) rate /= 1000; if ((int)(rate * 100) % 100) snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]); else snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]); return (buf); } #ifdef __FreeBSD__ /* * XXX * FreeBSD does not have SIOCGIFDATA. * To emulate this, DIOCGIFSPEED ioctl added to pf. */ u_int32_t getifspeed(int pfdev, char *ifname) { struct pf_ifspeed io; bzero(&io, sizeof io); if (strlcpy(io.ifname, ifname, IFNAMSIZ) >= sizeof(io.ifname)) errx(1, "getifspeed: strlcpy"); if (ioctl(pfdev, DIOCGIFSPEED, &io) == -1) err(1, "DIOCGIFSPEED"); return ((u_int32_t)io.baudrate); } #else u_int32_t getifspeed(char *ifname) { int s; struct ifreq ifr; struct if_data ifrdat; if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) err(1, "socket"); if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= sizeof(ifr.ifr_name)) errx(1, "getifspeed: strlcpy"); ifr.ifr_data = (caddr_t)&ifrdat; if (ioctl(s, SIOCGIFDATA, (caddr_t)&ifr) == -1) err(1, "SIOCGIFDATA"); if (shutdown(s, SHUT_RDWR) == -1) err(1, "shutdown"); if (close(s)) err(1, "close"); return ((u_int32_t)ifrdat.ifi_baudrate); } #endif u_long getifmtu(char *ifname) { int s; struct ifreq ifr; if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) err(1, "socket"); if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= sizeof(ifr.ifr_name)) errx(1, "getifmtu: strlcpy"); if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == -1) err(1, "SIOCGIFMTU"); if (shutdown(s, SHUT_RDWR) == -1) err(1, "shutdown"); if (close(s)) err(1, "close"); if (ifr.ifr_mtu > 0) return (ifr.ifr_mtu); else { warnx("could not get mtu for %s, assuming 1500", ifname); return (1500); } } int eval_queue_opts(struct pf_altq *pa, struct node_queue_opt *opts, u_int32_t ref_bw) { int errors = 0; switch (pa->scheduler) { case ALTQT_CBQ: pa->pq_u.cbq_opts = opts->data.cbq_opts; break; case ALTQT_PRIQ: pa->pq_u.priq_opts = opts->data.priq_opts; break; case ALTQT_HFSC: pa->pq_u.hfsc_opts.flags = opts->data.hfsc_opts.flags; if (opts->data.hfsc_opts.linkshare.used) { pa->pq_u.hfsc_opts.lssc_m1 = eval_bwspec(&opts->data.hfsc_opts.linkshare.m1, ref_bw); pa->pq_u.hfsc_opts.lssc_m2 = eval_bwspec(&opts->data.hfsc_opts.linkshare.m2, ref_bw); pa->pq_u.hfsc_opts.lssc_d = opts->data.hfsc_opts.linkshare.d; } if (opts->data.hfsc_opts.realtime.used) { pa->pq_u.hfsc_opts.rtsc_m1 = eval_bwspec(&opts->data.hfsc_opts.realtime.m1, ref_bw); pa->pq_u.hfsc_opts.rtsc_m2 = eval_bwspec(&opts->data.hfsc_opts.realtime.m2, ref_bw); pa->pq_u.hfsc_opts.rtsc_d = opts->data.hfsc_opts.realtime.d; } if (opts->data.hfsc_opts.upperlimit.used) { pa->pq_u.hfsc_opts.ulsc_m1 = eval_bwspec(&opts->data.hfsc_opts.upperlimit.m1, ref_bw); pa->pq_u.hfsc_opts.ulsc_m2 = eval_bwspec(&opts->data.hfsc_opts.upperlimit.m2, ref_bw); pa->pq_u.hfsc_opts.ulsc_d = opts->data.hfsc_opts.upperlimit.d; } break; default: warnx("eval_queue_opts: unknown scheduler type %u", opts->qtype); errors++; break; } return (errors); } u_int32_t eval_bwspec(struct node_queue_bw *bw, u_int32_t ref_bw) { if (bw->bw_absolute > 0) return (bw->bw_absolute); if (bw->bw_percent > 0) return (ref_bw / 100 * bw->bw_percent); return (0); } void print_hfsc_sc(const char *scname, u_int m1, u_int d, u_int m2, const struct node_hfsc_sc *sc) { printf(" %s", scname); if (d != 0) { printf("("); if (sc != NULL && sc->m1.bw_percent > 0) printf("%u%%", sc->m1.bw_percent); else printf("%s", rate2str((double)m1)); printf(" %u", d); } if (sc != NULL && sc->m2.bw_percent > 0) printf(" %u%%", sc->m2.bw_percent); else printf(" %s", rate2str((double)m2)); if (d != 0) printf(")"); } diff --git a/contrib/pf/pfctl/pfctl_parser.c b/contrib/pf/pfctl/pfctl_parser.c index 52b00bbb9672..61b47f5fae7b 100644 --- a/contrib/pf/pfctl/pfctl_parser.c +++ b/contrib/pf/pfctl/pfctl_parser.c @@ -1,1296 +1,1298 @@ -/* $FreeBSD$ */ /* $OpenBSD: pfctl_parser.c,v 1.175 2003/09/18 20:27:58 cedric Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" void print_op (u_int8_t, const char *, const char *); void print_port (u_int8_t, u_int16_t, u_int16_t, const char *); void print_ugid (u_int8_t, unsigned, unsigned, const char *, unsigned); void print_flags (u_int8_t); void print_fromto(struct pf_rule_addr *, pf_osfp_t, struct pf_rule_addr *, u_int8_t, u_int8_t, int); struct node_host *host_if(const char *, int); struct node_host *host_v4(const char *, int); struct node_host *host_v6(const char *, int); struct node_host *host_dns(const char *, int, int); const char *tcpflags = "FSRPAUEW"; static const struct icmptypeent icmp_type[] = { { "echoreq", ICMP_ECHO }, { "echorep", ICMP_ECHOREPLY }, { "unreach", ICMP_UNREACH }, { "squench", ICMP_SOURCEQUENCH }, { "redir", ICMP_REDIRECT }, { "althost", ICMP_ALTHOSTADDR }, { "routeradv", ICMP_ROUTERADVERT }, { "routersol", ICMP_ROUTERSOLICIT }, { "timex", ICMP_TIMXCEED }, { "paramprob", ICMP_PARAMPROB }, { "timereq", ICMP_TSTAMP }, { "timerep", ICMP_TSTAMPREPLY }, { "inforeq", ICMP_IREQ }, { "inforep", ICMP_IREQREPLY }, { "maskreq", ICMP_MASKREQ }, { "maskrep", ICMP_MASKREPLY }, { "trace", ICMP_TRACEROUTE }, { "dataconv", ICMP_DATACONVERR }, { "mobredir", ICMP_MOBILE_REDIRECT }, { "ipv6-where", ICMP_IPV6_WHEREAREYOU }, { "ipv6-here", ICMP_IPV6_IAMHERE }, { "mobregreq", ICMP_MOBILE_REGREQUEST }, { "mobregrep", ICMP_MOBILE_REGREPLY }, { "skip", ICMP_SKIP }, { "photuris", ICMP_PHOTURIS } }; static const struct icmptypeent icmp6_type[] = { { "unreach", ICMP6_DST_UNREACH }, { "toobig", ICMP6_PACKET_TOO_BIG }, { "timex", ICMP6_TIME_EXCEEDED }, { "paramprob", ICMP6_PARAM_PROB }, { "echoreq", ICMP6_ECHO_REQUEST }, { "echorep", ICMP6_ECHO_REPLY }, { "groupqry", ICMP6_MEMBERSHIP_QUERY }, { "listqry", MLD_LISTENER_QUERY }, { "grouprep", ICMP6_MEMBERSHIP_REPORT }, { "listenrep", MLD_LISTENER_REPORT }, { "groupterm", ICMP6_MEMBERSHIP_REDUCTION }, { "listendone", MLD_LISTENER_DONE }, { "routersol", ND_ROUTER_SOLICIT }, { "routeradv", ND_ROUTER_ADVERT }, { "neighbrsol", ND_NEIGHBOR_SOLICIT }, { "neighbradv", ND_NEIGHBOR_ADVERT }, { "redir", ND_REDIRECT }, { "routrrenum", ICMP6_ROUTER_RENUMBERING }, { "wrureq", ICMP6_WRUREQUEST }, { "wrurep", ICMP6_WRUREPLY }, { "fqdnreq", ICMP6_FQDN_QUERY }, { "fqdnrep", ICMP6_FQDN_REPLY }, { "niqry", ICMP6_NI_QUERY }, { "nirep", ICMP6_NI_REPLY }, { "mtraceresp", MLD_MTRACE_RESP }, { "mtrace", MLD_MTRACE } }; static const struct icmpcodeent icmp_code[] = { { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET }, { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST }, { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL }, { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT }, { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG }, { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL }, { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN }, { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN }, { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED }, { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB }, { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB }, { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET }, { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST }, { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB }, { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE }, { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF }, { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET }, { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST }, { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET }, { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST }, { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL }, { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON }, { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS }, { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS }, { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR }, { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT }, { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH }, { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX }, { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED }, { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED } }; static const struct icmpcodeent icmp6_code[] = { { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN }, { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE }, { "notnbr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOTNEIGHBOR }, { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE }, { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR }, { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT }, { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT }, { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY }, { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER }, { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER }, { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK }, { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER } }; const struct pf_timeout pf_timeouts[] = { { "tcp.first", PFTM_TCP_FIRST_PACKET }, { "tcp.opening", PFTM_TCP_OPENING }, { "tcp.established", PFTM_TCP_ESTABLISHED }, { "tcp.closing", PFTM_TCP_CLOSING }, { "tcp.finwait", PFTM_TCP_FIN_WAIT }, { "tcp.closed", PFTM_TCP_CLOSED }, { "udp.first", PFTM_UDP_FIRST_PACKET }, { "udp.single", PFTM_UDP_SINGLE }, { "udp.multiple", PFTM_UDP_MULTIPLE }, { "icmp.first", PFTM_ICMP_FIRST_PACKET }, { "icmp.error", PFTM_ICMP_ERROR_REPLY }, { "other.first", PFTM_OTHER_FIRST_PACKET }, { "other.single", PFTM_OTHER_SINGLE }, { "other.multiple", PFTM_OTHER_MULTIPLE }, { "frag", PFTM_FRAG }, { "interval", PFTM_INTERVAL }, { "adaptive.start", PFTM_ADAPTIVE_START }, { "adaptive.end", PFTM_ADAPTIVE_END }, { NULL, 0 } }; const struct icmptypeent * geticmptypebynumber(u_int8_t type, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < (sizeof (icmp_type) / sizeof(icmp_type[0])); i++) { if (type == icmp_type[i].type) return (&icmp_type[i]); } } else { for (i=0; i < (sizeof (icmp6_type) / sizeof(icmp6_type[0])); i++) { if (type == icmp6_type[i].type) return (&icmp6_type[i]); } } return (NULL); } const struct icmptypeent * geticmptypebyname(char *w, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < (sizeof (icmp_type) / sizeof(icmp_type[0])); i++) { if (!strcmp(w, icmp_type[i].name)) return (&icmp_type[i]); } } else { for (i=0; i < (sizeof (icmp6_type) / sizeof(icmp6_type[0])); i++) { if (!strcmp(w, icmp6_type[i].name)) return (&icmp6_type[i]); } } return (NULL); } const struct icmpcodeent * geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < (sizeof (icmp_code) / sizeof(icmp_code[0])); i++) { if (type == icmp_code[i].type && code == icmp_code[i].code) return (&icmp_code[i]); } } else { for (i=0; i < (sizeof (icmp6_code) / sizeof(icmp6_code[0])); i++) { if (type == icmp6_code[i].type && code == icmp6_code[i].code) return (&icmp6_code[i]); } } return (NULL); } const struct icmpcodeent * geticmpcodebyname(u_long type, char *w, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < (sizeof (icmp_code) / sizeof(icmp_code[0])); i++) { if (type == icmp_code[i].type && !strcmp(w, icmp_code[i].name)) return (&icmp_code[i]); } } else { for (i=0; i < (sizeof (icmp6_code) / sizeof(icmp6_code[0])); i++) { if (type == icmp6_code[i].type && !strcmp(w, icmp6_code[i].name)) return (&icmp6_code[i]); } } return (NULL); } void print_op(u_int8_t op, const char *a1, const char *a2) { if (op == PF_OP_IRG) printf(" %s >< %s", a1, a2); else if (op == PF_OP_XRG) printf(" %s <> %s", a1, a2); else if (op == PF_OP_EQ) printf(" = %s", a1); else if (op == PF_OP_NE) printf(" != %s", a1); else if (op == PF_OP_LT) printf(" < %s", a1); else if (op == PF_OP_LE) printf(" <= %s", a1); else if (op == PF_OP_GT) printf(" > %s", a1); else if (op == PF_OP_GE) printf(" >= %s", a1); else if (op == PF_OP_RRG) printf(" %s:%s", a1, a2); } void print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto) { char a1[6], a2[6]; struct servent *s; s = getservbyport(p1, proto); p1 = ntohs(p1); p2 = ntohs(p2); snprintf(a1, sizeof(a1), "%u", p1); snprintf(a2, sizeof(a2), "%u", p2); printf(" port"); if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE)) print_op(op, s->s_name, a2); else print_op(op, a1, a2); } void print_ugid(u_int8_t op, unsigned u1, unsigned u2, const char *t, unsigned umax) { char a1[11], a2[11]; snprintf(a1, sizeof(a1), "%u", u1); snprintf(a2, sizeof(a2), "%u", u2); printf(" %s", t); if (u1 == umax && (op == PF_OP_EQ || op == PF_OP_NE)) print_op(op, "unknown", a2); else print_op(op, a1, a2); } void print_flags(u_int8_t f) { int i; for (i = 0; tcpflags[i]; ++i) if (f & (1 << i)) printf("%c", tcpflags[i]); } void print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst, sa_family_t af, u_int8_t proto, int verbose) { char buf[PF_OSFP_LEN*3]; if (src->addr.type == PF_ADDR_ADDRMASK && dst->addr.type == PF_ADDR_ADDRMASK && PF_AZERO(&src->addr.v.a.addr, AF_INET6) && PF_AZERO(&src->addr.v.a.mask, AF_INET6) && PF_AZERO(&dst->addr.v.a.addr, AF_INET6) && PF_AZERO(&dst->addr.v.a.mask, AF_INET6) && !src->not && !dst->not && !src->port_op && !dst->port_op && osfp == PF_OSFP_ANY) printf(" all"); else { printf(" from "); if (src->not) printf("! "); print_addr(&src->addr, af, verbose); if (src->port_op) print_port(src->port_op, src->port[0], src->port[1], proto == IPPROTO_TCP ? "tcp" : "udp"); if (osfp != PF_OSFP_ANY) printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf, sizeof(buf))); printf(" to "); if (dst->not) printf("! "); print_addr(&dst->addr, af, verbose); if (dst->port_op) print_port(dst->port_op, dst->port[0], dst->port[1], proto == IPPROTO_TCP ? "tcp" : "udp"); } } void print_pool(struct pf_pool *pool, u_int16_t p1, u_int16_t p2, sa_family_t af, int id) { struct pf_pooladdr *pooladdr; if ((TAILQ_FIRST(&pool->list) != NULL) && TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) printf("{ "); TAILQ_FOREACH(pooladdr, &pool->list, entries){ switch (id) { case PF_NAT: case PF_RDR: case PF_BINAT: print_addr(&pooladdr->addr, af, 0); break; case PF_PASS: if (PF_AZERO(&pooladdr->addr.v.a.addr, af)) printf("%s", pooladdr->ifname); else { printf("(%s ", pooladdr->ifname); print_addr(&pooladdr->addr, af, 0); printf(")"); } break; default: break; } if (TAILQ_NEXT(pooladdr, entries) != NULL) printf(", "); else if (TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) printf(" }"); } switch (id) { case PF_NAT: if ((p1 != PF_NAT_PROXY_PORT_LOW || p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) { if (p1 == p2) printf(" port %u", p1); else printf(" port %u:%u", p1, p2); } break; case PF_RDR: if (p1) { printf(" port %u", p1); if (p2 && (p2 != p1)) printf(":%u", p2); } break; default: break; } switch (pool->opts & PF_POOL_TYPEMASK) { case PF_POOL_NONE: break; case PF_POOL_BITMASK: printf(" bitmask"); break; case PF_POOL_RANDOM: printf(" random"); break; case PF_POOL_SRCHASH: printf(" source-hash 0x%08x%08x%08x%08x", pool->key.key32[0], pool->key.key32[1], pool->key.key32[2], pool->key.key32[3]); break; case PF_POOL_ROUNDROBIN: printf(" round-robin"); break; } if (id == PF_NAT && p1 == 0 && p2 == 0) printf(" static-port"); } const char *pf_reasons[PFRES_MAX+1] = PFRES_NAMES; const char *pf_fcounters[FCNT_MAX+1] = FCNT_NAMES; void print_status(struct pf_status *s) { char statline[80]; time_t runtime; int i; runtime = time(NULL) - s->since; if (s->running) { unsigned sec, min, hrs, day = runtime; sec = day % 60; day /= 60; min = day % 60; day /= 60; hrs = day % 24; day /= 24; snprintf(statline, sizeof(statline), "Status: Enabled for %u days %.2u:%.2u:%.2u", day, hrs, min, sec); } else snprintf(statline, sizeof(statline), "Status: Disabled"); printf("%-44s", statline); switch (s->debug) { case 0: printf("%15s\n\n", "Debug: None"); break; case 1: printf("%15s\n\n", "Debug: Urgent"); break; case 2: printf("%15s\n\n", "Debug: Misc"); break; } if (s->ifname[0] != 0) { printf("Interface Stats for %-16s %5s %16s\n", s->ifname, "IPv4", "IPv6"); printf(" %-25s %14llu %16llu\n", "Bytes In", (unsigned long long)s->bcounters[0][0], (unsigned long long)s->bcounters[1][0]); printf(" %-25s %14llu %16llu\n", "Bytes Out", (unsigned long long)s->bcounters[0][1], (unsigned long long)s->bcounters[1][1]); printf(" Packets In\n"); printf(" %-23s %14llu %16llu\n", "Passed", (unsigned long long)s->pcounters[0][0][PF_PASS], (unsigned long long)s->pcounters[1][0][PF_PASS]); printf(" %-23s %14llu %16llu\n", "Blocked", (unsigned long long)s->pcounters[0][0][PF_DROP], (unsigned long long)s->pcounters[1][0][PF_DROP]); printf(" Packets Out\n"); printf(" %-23s %14llu %16llu\n", "Passed", (unsigned long long)s->pcounters[0][1][PF_PASS], (unsigned long long)s->pcounters[1][1][PF_PASS]); printf(" %-23s %14llu %16llu\n\n", "Blocked", (unsigned long long)s->pcounters[0][1][PF_DROP], (unsigned long long)s->pcounters[1][1][PF_DROP]); } printf("%-27s %14s %16s\n", "State Table", "Total", "Rate"); printf(" %-25s %14u %14s\n", "current entries", s->states, ""); for (i = 0; i < FCNT_MAX; i++) { printf(" %-25s %14llu ", pf_fcounters[i], (unsigned long long)s->fcounters[i]); if (runtime > 0) printf("%14.1f/s\n", (double)s->fcounters[i] / (double)runtime); else printf("%14s\n", ""); } printf("Counters\n"); for (i = 0; i < PFRES_MAX; i++) { printf(" %-25s %14llu ", pf_reasons[i], (unsigned long long)s->counters[i]); if (runtime > 0) printf("%14.1f/s\n", (double)s->counters[i] / (double)runtime); else printf("%14s\n", ""); } } void print_rule(struct pf_rule *r, int verbose) { static const char *actiontypes[] = { "pass", "block", "scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr" }; static const char *anchortypes[] = { "anchor", "anchor", "anchor", "nat-anchor", "nat-anchor", "binat-anchor", "binat-anchor", "rdr-anchor", "rdr-anchor" }; int i, opts; if (verbose) printf("@%d ", r->nr); if (r->action > PF_NORDR) printf("action(%d)", r->action); else if (r->anchorname[0]) printf("%s %s", anchortypes[r->action], r->anchorname); else { printf("%s", actiontypes[r->action]); if (r->natpass) printf(" pass"); } if (r->action == PF_DROP) { if (r->rule_flag & PFRULE_RETURN) printf(" return"); else if (r->rule_flag & PFRULE_RETURNRST) { if (!r->return_ttl) printf(" return-rst"); else printf(" return-rst(ttl %d)", r->return_ttl); } else if (r->rule_flag & PFRULE_RETURNICMP) { const struct icmpcodeent *ic, *ic6; ic = geticmpcodebynumber(r->return_icmp >> 8, r->return_icmp & 255, AF_INET); ic6 = geticmpcodebynumber(r->return_icmp6 >> 8, r->return_icmp6 & 255, AF_INET6); switch(r->af) { case AF_INET: printf(" return-icmp"); if (ic == NULL) printf("(%u)", r->return_icmp & 255); else printf("(%s)", ic->name); break; case AF_INET6: printf(" return-icmp6"); if (ic6 == NULL) printf("(%u)", r->return_icmp6 & 255); else printf("(%s)", ic6->name); break; default: printf(" return-icmp"); if (ic == NULL) printf("(%u, ", r->return_icmp & 255); else printf("(%s, ", ic->name); if (ic6 == NULL) printf("%u)", r->return_icmp6 & 255); else printf("%s)", ic6->name); break; } } else printf(" drop"); } if (r->direction == PF_IN) printf(" in"); else if (r->direction == PF_OUT) printf(" out"); if (r->log == 1) printf(" log"); else if (r->log == 2) printf(" log-all"); if (r->quick) printf(" quick"); if (r->ifname[0]) { if (r->ifnot) printf(" on ! %s", r->ifname); else printf(" on %s", r->ifname); } if (r->rt) { if (r->rt == PF_ROUTETO) printf(" route-to"); else if (r->rt == PF_REPLYTO) printf(" reply-to"); else if (r->rt == PF_DUPTO) printf(" dup-to"); else if (r->rt == PF_FASTROUTE) printf(" fastroute"); if (r->rt != PF_FASTROUTE) { printf(" "); print_pool(&r->rpool, 0, 0, r->af, PF_PASS); } } if (r->af) { if (r->af == AF_INET) printf(" inet"); else printf(" inet6"); } if (r->proto) { struct protoent *p; if ((p = getprotobynumber(r->proto)) != NULL) printf(" proto %s", p->p_name); else printf(" proto %u", r->proto); } print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto, verbose); if (r->uid.op) print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user", UID_MAX); if (r->gid.op) print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group", GID_MAX); if (r->flags || r->flagset) { printf(" flags "); print_flags(r->flags); printf("/"); print_flags(r->flagset); } if (r->type) { const struct icmptypeent *it; it = geticmptypebynumber(r->type-1, r->af); if (r->af != AF_INET6) printf(" icmp-type"); else printf(" icmp6-type"); if (it != NULL) printf(" %s", it->name); else printf(" %u", r->type-1); if (r->code) { const struct icmpcodeent *ic; ic = geticmpcodebynumber(r->type-1, r->code-1, r->af); if (ic != NULL) printf(" code %s", ic->name); else printf(" code %u", r->code-1); } } if (r->tos) printf(" tos 0x%2.2x", r->tos); if (r->keep_state == PF_STATE_NORMAL) printf(" keep state"); else if (r->keep_state == PF_STATE_MODULATE) printf(" modulate state"); else if (r->keep_state == PF_STATE_SYNPROXY) printf(" synproxy state"); opts = 0; if (r->max_states) opts = 1; for (i = 0; !opts && i < PFTM_MAX; ++i) if (r->timeout[i]) opts = 1; if (opts) { printf(" ("); if (r->max_states) { printf("max %u", r->max_states); opts = 0; } for (i = 0; i < PFTM_MAX; ++i) if (r->timeout[i]) { if (!opts) printf(", "); opts = 0; printf("%s %u", pf_timeouts[i].name, r->timeout[i]); } printf(")"); } if (r->rule_flag & PFRULE_FRAGMENT) printf(" fragment"); if (r->rule_flag & PFRULE_NODF) printf(" no-df"); if (r->rule_flag & PFRULE_RANDOMID) printf(" random-id"); if (r->min_ttl) printf(" min-ttl %d", r->min_ttl); if (r->max_mss) printf(" max-mss %d", r->max_mss); if (r->allow_opts) printf(" allow-opts"); if (r->action == PF_SCRUB) { if (r->rule_flag & PFRULE_REASSEMBLE_TCP) printf(" reassemble tcp"); if (r->rule_flag & PFRULE_FRAGDROP) printf(" fragment drop-ovl"); else if (r->rule_flag & PFRULE_FRAGCROP) printf(" fragment crop"); else printf(" fragment reassemble"); } if (r->label[0]) printf(" label \"%s\"", r->label); if (r->qname[0] && r->pqname[0]) printf(" queue(%s, %s)", r->qname, r->pqname); else if (r->qname[0]) printf(" queue %s", r->qname); if (r->tagname[0]) printf(" tag %s", r->tagname); if (r->match_tagname[0]) { if (r->match_tag_not) printf(" !"); printf(" tagged %s", r->match_tagname); } if (!r->anchorname[0] && (r->action == PF_NAT || r->action == PF_BINAT || r->action == PF_RDR)) { printf(" -> "); print_pool(&r->rpool, r->rpool.proxy_port[0], r->rpool.proxy_port[1], r->af, r->action); } printf("\n"); } void print_tabledef(const char *name, int flags, int addrs, struct node_tinithead *nodes) { struct node_tinit *ti, *nti; struct node_host *h; printf("table <%s>", name); if (flags & PFR_TFLAG_CONST) printf(" const"); if (flags & PFR_TFLAG_PERSIST) printf(" persist"); SIMPLEQ_FOREACH(ti, nodes, entries) { if (ti->file) { printf(" file \"%s\"", ti->file); continue; } printf(" {"); for (;;) { for (h = ti->host; h != NULL; h = h->next) { printf(h->not ? " !" : " "); print_addr(&h->addr, h->af, 0); } nti = SIMPLEQ_NEXT(ti, entries); if (nti != NULL && nti->file == NULL) ti = nti; /* merge lists */ else break; } printf(" }"); } if (addrs && SIMPLEQ_EMPTY(nodes)) printf(" { }"); printf("\n"); } int parse_flags(char *s) { char *p, *q; u_int8_t f = 0; for (p = s; *p; p++) { if ((q = strchr(tcpflags, *p)) == NULL) return -1; else f |= 1 << (q - tcpflags); } return (f ? f : PF_TH_ALL); } void set_ipmask(struct node_host *h, u_int8_t b) { struct pf_addr *m, *n; int i, j = 0; m = &h->addr.v.a.mask; for (i = 0; i < 4; i++) m->addr32[i] = 0; while (b >= 32) { m->addr32[j++] = 0xffffffff; b -= 32; } for (i = 31; i > 31-b; --i) m->addr32[j] |= (1 << i); if (b) m->addr32[j] = htonl(m->addr32[j]); /* Mask off bits of the address that will never be used. */ n = &h->addr.v.a.addr; if (h->addr.type == PF_ADDR_ADDRMASK) for (i = 0; i < 4; i++) n->addr32[i] = n->addr32[i] & m->addr32[i]; } int check_netmask(struct node_host *h, sa_family_t af) { struct node_host *n = NULL; struct pf_addr *m; for (n = h; n != NULL; n = n->next) { if (h->addr.type == PF_ADDR_TABLE) continue; m = &h->addr.v.a.mask; /* fix up netmask for dynaddr */ if (af == AF_INET && h->addr.type == PF_ADDR_DYNIFTL && unmask(m, AF_INET6) > 32) set_ipmask(n, 32); /* netmasks > 32 bit are invalid on v4 */ if (af == AF_INET && (m->addr32[1] || m->addr32[2] || m->addr32[3])) { fprintf(stderr, "netmask %u invalid for IPv4 address\n", unmask(m, AF_INET6)); return (1); } } return (0); } /* interface lookup routines */ struct node_host *iftab; void ifa_load(void) { struct ifaddrs *ifap, *ifa; struct node_host *n = NULL, *h = NULL; if (getifaddrs(&ifap) < 0) err(1, "getifaddrs"); for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (!(ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6 || ifa->ifa_addr->sa_family == AF_LINK)) continue; n = calloc(1, sizeof(struct node_host)); if (n == NULL) err(1, "address: calloc"); n->af = ifa->ifa_addr->sa_family; n->ifa_flags = ifa->ifa_flags; #ifdef __KAME__ if (n->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr) && ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id == 0) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 | sin6->sin6_addr.s6_addr[3]; sin6->sin6_addr.s6_addr[2] = 0; sin6->sin6_addr.s6_addr[3] = 0; } #endif n->ifindex = 0; if (n->af == AF_INET) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr.s_addr, sizeof(struct in_addr)); memcpy(&n->addr.v.a.mask, &((struct sockaddr_in *) ifa->ifa_netmask)->sin_addr.s_addr, sizeof(struct in_addr)); if (ifa->ifa_broadaddr != NULL) memcpy(&n->bcast, &((struct sockaddr_in *) ifa->ifa_broadaddr)->sin_addr.s_addr, sizeof(struct in_addr)); } else if (n->af == AF_INET6) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); memcpy(&n->addr.v.a.mask, &((struct sockaddr_in6 *) ifa->ifa_netmask)->sin6_addr.s6_addr, sizeof(struct in6_addr)); if (ifa->ifa_broadaddr != NULL) memcpy(&n->bcast, &((struct sockaddr_in6 *) ifa->ifa_broadaddr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); n->ifindex = ((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_scope_id; } if ((n->ifname = strdup(ifa->ifa_name)) == NULL) err(1, "ifa_load: strdup"); n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } iftab = h; freeifaddrs(ifap); } struct node_host * ifa_exists(const char *ifa_name) { struct node_host *n; if (iftab == NULL) ifa_load(); for (n = iftab; n; n = n->next) { if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ)) return (n); } return (NULL); } struct node_host * ifa_lookup(const char *ifa_name, enum pfctl_iflookup_mode mode) { struct node_host *p = NULL, *h = NULL, *n = NULL; int return_all = 0; if (!strncmp(ifa_name, "self", IFNAMSIZ)) return_all = 1; if (iftab == NULL) ifa_load(); for (p = iftab; p; p = p->next) { if (!((p->af == AF_INET || p->af == AF_INET6) && (!strncmp(p->ifname, ifa_name, IFNAMSIZ) || return_all))) continue; if (mode == PFCTL_IFLOOKUP_BCAST && p->af != AF_INET) continue; if (mode == PFCTL_IFLOOKUP_NET && p->ifindex > 0) continue; n = calloc(1, sizeof(struct node_host)); if (n == NULL) err(1, "address: calloc"); n->af = p->af; if (mode == PFCTL_IFLOOKUP_BCAST) memcpy(&n->addr.v.a.addr, &p->bcast, sizeof(struct pf_addr)); else memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr, sizeof(struct pf_addr)); if (mode == PFCTL_IFLOOKUP_NET) set_ipmask(n, unmask(&p->addr.v.a.mask, n->af)); else { if (n->af == AF_INET) { if (p->ifa_flags & IFF_LOOPBACK && p->ifa_flags & IFF_LINK1) memcpy(&n->addr.v.a.mask, &p->addr.v.a.mask, sizeof(struct pf_addr)); else set_ipmask(n, 32); } else set_ipmask(n, 128); } n->ifindex = p->ifindex; n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } if (h == NULL && mode == PFCTL_IFLOOKUP_HOST) { fprintf(stderr, "no IP address found for %s\n", ifa_name); } return (h); } struct node_host * host(const char *s) { struct node_host *h = NULL; int mask, v4mask, v6mask, cont = 1; char *p, *q, *ps; if ((p = strrchr(s, '/')) != NULL) { mask = strtol(p+1, &q, 0); if (!q || *q || mask > 128 || q == (p+1)) { fprintf(stderr, "invalid netmask\n"); return (NULL); } if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) err(1, "host: malloc"); strlcpy(ps, s, strlen(s) - strlen(p) + 1); v4mask = v6mask = mask; } else { if ((ps = strdup(s)) == NULL) err(1, "host: strdup"); v4mask = 32; v6mask = 128; mask = -1; } /* interface with this name exists? */ if (cont && (h = host_if(ps, mask)) != NULL) cont = 0; /* IPv4 address? */ if (cont && (h = host_v4(s, mask)) != NULL) cont = 0; /* IPv6 address? */ if (cont && (h = host_v6(ps, v6mask)) != NULL) cont = 0; /* dns lookup */ if (cont && (h = host_dns(ps, v4mask, v6mask)) != NULL) cont = 0; free(ps); if (h == NULL || cont == 1) { fprintf(stderr, "no IP address found for %s\n", s); return (NULL); } return (h); } struct node_host * host_if(const char *s, int mask) { struct node_host *n, *h = NULL; char *p, *ps; int mode = PFCTL_IFLOOKUP_HOST; if ((p = strrchr(s, ':')) != NULL && (!strcmp(p+1, "network") || !strcmp(p+1, "broadcast"))) { if (!strcmp(p+1, "network")) mode = PFCTL_IFLOOKUP_NET; if (!strcmp(p+1, "broadcast")) mode = PFCTL_IFLOOKUP_BCAST; if (mask > -1) { fprintf(stderr, "network or broadcast lookup, but " "extra netmask given\n"); return (NULL); } if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) err(1, "host: malloc"); strlcpy(ps, s, strlen(s) - strlen(p) + 1); } else if ((ps = strdup(s)) == NULL) err(1, "host_if: strdup"); if (ifa_exists(ps) || !strncmp(ps, "self", IFNAMSIZ)) { /* interface with this name exists */ h = ifa_lookup(ps, mode); for (n = h; n != NULL && mask > -1; n = n->next) set_ipmask(n, mask); } free(ps); return (h); } struct node_host * host_v4(const char *s, int mask) { struct node_host *h = NULL; struct in_addr ina; int bits; memset(&ina, 0, sizeof(struct in_addr)); if ((bits = inet_net_pton(AF_INET, s, &ina, sizeof(ina))) > -1) { h = calloc(1, sizeof(struct node_host)); if (h == NULL) err(1, "address: calloc"); h->ifname = NULL; h->af = AF_INET; h->addr.v.a.addr.addr32[0] = ina.s_addr; #if defined(__FreeBSD__) && (__FreeBSD_version <= 501106) /* inet_net_pton acts strange w/ multicast addresses, RFC1112 */ if (mask == -1 && h->addr.v.a.addr.addr8[0] >= 224 && h->addr.v.a.addr.addr8[0] < 240) bits = 32; #endif set_ipmask(h, bits); h->next = NULL; h->tail = h; } return (h); } struct node_host * host_v6(const char *s, int mask) { struct addrinfo hints, *res; struct node_host *h = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_DGRAM; /*dummy*/ hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(s, "0", &hints, &res) == 0) { h = calloc(1, sizeof(struct node_host)); if (h == NULL) err(1, "address: calloc"); h->ifname = NULL; h->af = AF_INET6; memcpy(&h->addr.v.a.addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, sizeof(h->addr.v.a.addr)); h->ifindex = ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; set_ipmask(h, mask); freeaddrinfo(res); h->next = NULL; h->tail = h; } return (h); } struct node_host * host_dns(const char *s, int v4mask, int v6mask) { struct addrinfo hints, *res0, *res; struct node_host *n, *h = NULL; int error; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* DUMMY */ error = getaddrinfo(s, NULL, &hints, &res0); if (error) return (h); for (res = res0; res; res = res->ai_next) { if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; n = calloc(1, sizeof(struct node_host)); if (n == NULL) err(1, "host_dns: calloc"); n->ifname = NULL; n->af = res->ai_family; if (res->ai_family == AF_INET) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *) res->ai_addr)->sin_addr.s_addr, sizeof(struct in_addr)); set_ipmask(n, v4mask); } else { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); n->ifindex = ((struct sockaddr_in6 *) res->ai_addr)->sin6_scope_id; set_ipmask(n, v6mask); } n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } freeaddrinfo(res0); return (h); } /* * convert a hostname to a list of addresses and put them in the given buffer. * test: * if set to 1, only simple addresses are accepted (no netblock, no "!"). */ int append_addr(struct pfr_buffer *b, char *s, int test) { char *r; struct node_host *h, *n; int rv, not = 0; for (r = s; *r == '!'; r++) not = !not; if ((n = host(r)) == NULL) { errno = 0; return (-1); } rv = append_addr_host(b, n, test, not); do { h = n; n = n->next; free(h); } while (n != NULL); return (rv); } /* * same as previous function, but with a pre-parsed input and the ability * to "negate" the result. Does not free the node_host list. * not: * setting it to 1 is equivalent to adding "!" in front of parameter s. */ int append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not) { int bits; struct pfr_addr addr; do { bzero(&addr, sizeof(addr)); addr.pfra_not = n->not ^ not; addr.pfra_af = n->af; addr.pfra_net = unmask(&n->addr.v.a.mask, n->af); switch (n->af) { case AF_INET: addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0]; bits = 32; break; case AF_INET6: memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6, sizeof(struct in6_addr)); bits = 128; break; default: errno = EINVAL; return (-1); } if ((test && (not || addr.pfra_net != bits)) || addr.pfra_net > bits) { errno = EINVAL; return (-1); } if (pfr_buf_add(b, &addr)) return (-1); } while ((n = n->next) != NULL); return (0); } diff --git a/contrib/pf/pfctl/pfctl_parser.h b/contrib/pf/pfctl/pfctl_parser.h index 47e60ee66769..6911a47fde9c 100644 --- a/contrib/pf/pfctl/pfctl_parser.h +++ b/contrib/pf/pfctl/pfctl_parser.h @@ -1,253 +1,253 @@ -/* $FreeBSD$ */ /* $OpenBSD: pfctl_parser.h,v 1.67 2003/08/21 19:12:09 frantzen Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * + * $FreeBSD$ */ #ifndef _PFCTL_PARSER_H_ #define _PFCTL_PARSER_H_ #define PF_OSFP_FILE "/etc/pf.os" #define PF_OPT_DISABLE 0x0001 #define PF_OPT_ENABLE 0x0002 #define PF_OPT_VERBOSE 0x0004 #define PF_OPT_NOACTION 0x0008 #define PF_OPT_QUIET 0x0010 #define PF_OPT_CLRRULECTRS 0x0020 #define PF_OPT_USEDNS 0x0040 #define PF_OPT_VERBOSE2 0x0080 #define PF_OPT_DUMMYACTION 0x0100 #define PF_OPT_DEBUG 0x0200 #define PF_TH_ALL 0xFF #define PF_NAT_PROXY_PORT_LOW 50001 #define PF_NAT_PROXY_PORT_HIGH 65535 #define FCNT_NAMES { \ "searches", \ "inserts", \ "removals", \ NULL \ } struct pfctl { int dev; int opts; int loadopt; u_int32_t tticket; /* table ticket */ int tdirty; /* kernel dirty */ u_int32_t rule_nr; struct pfioc_pooladdr paddr; struct pfioc_rule *prule[PF_RULESET_MAX]; struct pfioc_altq *paltq; struct pfioc_queue *pqueue; const char *anchor; const char *ruleset; }; enum pfctl_iflookup_mode { PFCTL_IFLOOKUP_HOST, PFCTL_IFLOOKUP_NET, PFCTL_IFLOOKUP_BCAST }; struct node_if { char ifname[IFNAMSIZ]; u_int8_t not; u_int ifa_flags; struct node_if *next; struct node_if *tail; }; struct node_host { struct pf_addr_wrap addr; struct pf_addr bcast; sa_family_t af; u_int8_t not; u_int32_t ifindex; /* link-local IPv6 addrs */ char *ifname; u_int ifa_flags; struct node_host *next; struct node_host *tail; }; struct node_os { char *os; pf_osfp_t fingerprint; struct node_os *next; struct node_os *tail; }; struct node_queue_bw { u_int32_t bw_absolute; u_int16_t bw_percent; }; struct node_hfsc_sc { struct node_queue_bw m1; /* slope of 1st segment; bps */ u_int d; /* x-projection of m1; msec */ struct node_queue_bw m2; /* slope of 2nd segment; bps */ u_int8_t used; }; struct node_hfsc_opts { struct node_hfsc_sc realtime; struct node_hfsc_sc linkshare; struct node_hfsc_sc upperlimit; int flags; }; struct node_queue_opt { int qtype; union { struct cbq_opts cbq_opts; struct priq_opts priq_opts; struct node_hfsc_opts hfsc_opts; } data; }; #ifdef __FreeBSD__ /* * XXX * Absolutely this is not correct location to define this. * Should we use an another sperate header file? */ #define SIMPLEQ_HEAD STAILQ_HEAD #define SIMPLEQ_HEAD_INITIALIZER STAILQ_HEAD_INITIALIZER #define SIMPLEQ_ENTRY STAILQ_ENTRY #define SIMPLEQ_FIRST STAILQ_FIRST #define SIMPLEQ_END(head) NULL #define SIMPLEQ_EMPTY STAILQ_EMPTY #define SIMPLEQ_NEXT STAILQ_NEXT /*#define SIMPLEQ_FOREACH STAILQ_FOREACH*/ #define SIMPLEQ_FOREACH(var, head, field) \ for((var) = SIMPLEQ_FIRST(head); \ (var) != SIMPLEQ_END(head); \ (var) = SIMPLEQ_NEXT(var, field)) #define SIMPLEQ_INIT STAILQ_INIT #define SIMPLEQ_INSERT_HEAD STAILQ_INSERT_HEAD #define SIMPLEQ_INSERT_TAIL STAILQ_INSERT_TAIL #define SIMPLEQ_INSERT_AFTER STAILQ_INSERT_AFTER #define SIMPLEQ_REMOVE_HEAD STAILQ_REMOVE_HEAD #endif SIMPLEQ_HEAD(node_tinithead, node_tinit); struct node_tinit { /* table initializer */ SIMPLEQ_ENTRY(node_tinit) entries; struct node_host *host; char *file; }; struct pfr_buffer; /* forward definition */ int pfctl_rules(int, char *, int, char *, char *); int pfctl_add_rule(struct pfctl *, struct pf_rule *); int pfctl_add_altq(struct pfctl *, struct pf_altq *); int pfctl_add_pool(struct pfctl *, struct pf_pool *, sa_family_t); void pfctl_clear_pool(struct pf_pool *); int pfctl_set_timeout(struct pfctl *, const char *, int, int); int pfctl_set_optimization(struct pfctl *, const char *); int pfctl_set_limit(struct pfctl *, const char *, unsigned int); int pfctl_set_logif(struct pfctl *, char *); int parse_rules(FILE *, struct pfctl *); int parse_flags(char *); int pfctl_load_anchors(int, int); void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int); void print_rule(struct pf_rule *, int); void print_tabledef(const char *, int, int, struct node_tinithead *); void print_status(struct pf_status *); int eval_pfaltq(struct pfctl *, struct pf_altq *, struct node_queue_bw *, struct node_queue_opt *); int eval_pfqueue(struct pfctl *, struct pf_altq *, struct node_queue_bw *, struct node_queue_opt *); void print_altq(const struct pf_altq *, unsigned, struct node_queue_bw *, struct node_queue_opt *); void print_queue(const struct pf_altq *, unsigned, struct node_queue_bw *, int, struct node_queue_opt *); int pfctl_define_table(char *, int, int, const char *, const char *, struct pfr_buffer *, u_int32_t); void pfctl_clear_fingerprints(int, int); int pfctl_file_fingerprints(int, int, const char *); pf_osfp_t pfctl_get_fingerprint(const char *); int pfctl_load_fingerprints(int, int); char *pfctl_lookup_fingerprint(pf_osfp_t, char *, size_t); void pfctl_show_fingerprints(int); struct icmptypeent { const char *name; u_int8_t type; }; struct icmpcodeent { const char *name; u_int8_t type; u_int8_t code; }; const struct icmptypeent *geticmptypebynumber(u_int8_t, u_int8_t); const struct icmptypeent *geticmptypebyname(char *, u_int8_t); const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, u_int8_t); const struct icmpcodeent *geticmpcodebyname(u_long, char *, u_int8_t); struct pf_timeout { const char *name; int timeout; }; #define PFCTL_FLAG_FILTER 0x02 #define PFCTL_FLAG_NAT 0x04 #define PFCTL_FLAG_OPTION 0x08 #define PFCTL_FLAG_ALTQ 0x10 #define PFCTL_FLAG_TABLE 0x20 extern const struct pf_timeout pf_timeouts[]; void set_ipmask(struct node_host *, u_int8_t); int check_netmask(struct node_host *, sa_family_t); void ifa_load(void); struct node_host *ifa_exists(const char *); struct node_host *ifa_lookup(const char *, enum pfctl_iflookup_mode); struct node_host *host(const char *); int append_addr(struct pfr_buffer *, char *, int); int append_addr_host(struct pfr_buffer *, struct node_host *, int, int); #endif /* _PFCTL_PARSER_H_ */ diff --git a/contrib/pf/pfctl/pfctl_qstats.c b/contrib/pf/pfctl/pfctl_qstats.c index 5d65ebcfa8f0..0e8b61f89794 100644 --- a/contrib/pf/pfctl/pfctl_qstats.c +++ b/contrib/pf/pfctl/pfctl_qstats.c @@ -1,406 +1,408 @@ -/* $FreeBSD$ */ /* $OpenBSD: pfctl_qstats.c,v 1.24 2003/07/31 09:46:08 kjc Exp $ */ /* * Copyright (c) Henning Brauer * * 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. */ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl.h" #include "pfctl_parser.h" union class_stats { class_stats_t cbq_stats; struct priq_classstats priq_stats; struct hfsc_classstats hfsc_stats; }; #define AVGN_MAX 8 #define STAT_INTERVAL 5 struct queue_stats { union class_stats data; int avgn; double avg_bytes; double avg_packets; u_int64_t prev_bytes; u_int64_t prev_packets; }; struct pf_altq_node { struct pf_altq altq; struct pf_altq_node *next; struct pf_altq_node *children; struct queue_stats qstats; }; int pfctl_update_qstats(int, struct pf_altq_node **); void pfctl_insert_altq_node(struct pf_altq_node **, const struct pf_altq, const struct queue_stats); struct pf_altq_node *pfctl_find_altq_node(struct pf_altq_node *, const char *, const char *); void pfctl_print_altq_node(int, const struct pf_altq_node *, unsigned, int); void print_cbqstats(struct queue_stats); void print_priqstats(struct queue_stats); void print_hfscstats(struct queue_stats); void pfctl_free_altq_node(struct pf_altq_node *); void pfctl_print_altq_nodestat(int, const struct pf_altq_node *); void update_avg(struct pf_altq_node *); int pfctl_show_altq(int dev, int opts, int verbose2) { struct pf_altq_node *root = NULL, *node; #ifdef __FreeBSD__ if (!altqsupport) return (-1); #endif if (pfctl_update_qstats(dev, &root)) return (-1); for (node = root; node != NULL; node = node->next) pfctl_print_altq_node(dev, node, 0, opts); while (verbose2) { printf("\n"); fflush(stdout); sleep(STAT_INTERVAL); if (pfctl_update_qstats(dev, &root)) return (-1); for (node = root; node != NULL; node = node->next) pfctl_print_altq_node(dev, node, 0, opts); } pfctl_free_altq_node(root); return (0); } int pfctl_update_qstats(int dev, struct pf_altq_node **root) { struct pf_altq_node *node; struct pfioc_altq pa; struct pfioc_qstats pq; u_int32_t mnr, nr; struct queue_stats qstats; static u_int32_t last_ticket; memset(&pa, 0, sizeof(pa)); memset(&pq, 0, sizeof(pq)); memset(&qstats, 0, sizeof(qstats)); if (ioctl(dev, DIOCGETALTQS, &pa)) { warn("DIOCGETALTQS"); return (-1); } /* if a new set is found, start over */ if (pa.ticket != last_ticket && *root != NULL) { pfctl_free_altq_node(*root); *root = NULL; } last_ticket = pa.ticket; mnr = pa.nr; for (nr = 0; nr < mnr; ++nr) { pa.nr = nr; if (ioctl(dev, DIOCGETALTQ, &pa)) { warn("DIOCGETALTQ"); return (-1); } if (pa.altq.qid > 0) { pq.nr = nr; pq.ticket = pa.ticket; pq.buf = &qstats.data; pq.nbytes = sizeof(qstats.data); if (ioctl(dev, DIOCGETQSTATS, &pq)) { warn("DIOCGETQSTATS"); return (-1); } if ((node = pfctl_find_altq_node(*root, pa.altq.qname, pa.altq.ifname)) != NULL) { memcpy(&node->qstats.data, &qstats.data, sizeof(qstats.data)); update_avg(node); } else { pfctl_insert_altq_node(root, pa.altq, qstats); } } } return (0); } void pfctl_insert_altq_node(struct pf_altq_node **root, const struct pf_altq altq, const struct queue_stats qstats) { struct pf_altq_node *node; node = calloc(1, sizeof(struct pf_altq_node)); if (node == NULL) err(1, "pfctl_insert_altq_node: calloc"); memcpy(&node->altq, &altq, sizeof(struct pf_altq)); memcpy(&node->qstats, &qstats, sizeof(qstats)); node->next = node->children = NULL; if (*root == NULL) *root = node; else if (!altq.parent[0]) { struct pf_altq_node *prev = *root; while (prev->next != NULL) prev = prev->next; prev->next = node; } else { struct pf_altq_node *parent; parent = pfctl_find_altq_node(*root, altq.parent, altq.ifname); if (parent == NULL) errx(1, "parent %s not found", altq.parent); if (parent->children == NULL) parent->children = node; else { struct pf_altq_node *prev = parent->children; while (prev->next != NULL) prev = prev->next; prev->next = node; } } update_avg(node); } struct pf_altq_node * pfctl_find_altq_node(struct pf_altq_node *root, const char *qname, const char *ifname) { struct pf_altq_node *node, *child; for (node = root; node != NULL; node = node->next) { if (!strcmp(node->altq.qname, qname) && !(strcmp(node->altq.ifname, ifname))) return (node); if (node->children != NULL) { child = pfctl_find_altq_node(node->children, qname, ifname); if (child != NULL) return (child); } } return (NULL); } void pfctl_print_altq_node(int dev, const struct pf_altq_node *node, unsigned level, int opts) { const struct pf_altq_node *child; if (node == NULL) return; print_altq(&node->altq, level, NULL, NULL); if (node->children != NULL) { printf("{"); for (child = node->children; child != NULL; child = child->next) { printf("%s", child->altq.qname); if (child->next != NULL) printf(", "); } printf("}"); } printf("\n"); if (opts & PF_OPT_VERBOSE) pfctl_print_altq_nodestat(dev, node); if (opts & PF_OPT_DEBUG) printf(" [ qid=%u ifname=%s ifbandwidth=%s ]\n", node->altq.qid, node->altq.ifname, rate2str((double)(node->altq.ifbandwidth))); for (child = node->children; child != NULL; child = child->next) pfctl_print_altq_node(dev, child, level+1, opts); } void pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a) { if (a->altq.qid == 0) return; switch (a->altq.scheduler) { case ALTQT_CBQ: print_cbqstats(a->qstats); break; case ALTQT_PRIQ: print_priqstats(a->qstats); break; case ALTQT_HFSC: print_hfscstats(a->qstats); break; } } void print_cbqstats(struct queue_stats cur) { printf(" [ pkts: %10llu bytes: %10llu " "dropped pkts: %6llu bytes: %6llu ]\n", (unsigned long long)cur.data.cbq_stats.xmit_cnt.packets, (unsigned long long)cur.data.cbq_stats.xmit_cnt.bytes, (unsigned long long)cur.data.cbq_stats.drop_cnt.packets, (unsigned long long)cur.data.cbq_stats.drop_cnt.bytes); printf(" [ qlength: %3d/%3d borrows: %6u suspends: %6u ]\n", cur.data.cbq_stats.qcnt, cur.data.cbq_stats.qmax, cur.data.cbq_stats.borrows, cur.data.cbq_stats.delays); if (cur.avgn < 2) return; printf(" [ measured: %7.1f packets/s, %s/s ]\n", cur.avg_packets / STAT_INTERVAL, rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); } void print_priqstats(struct queue_stats cur) { printf(" [ pkts: %10llu bytes: %10llu " "dropped pkts: %6llu bytes: %6llu ]\n", (unsigned long long)cur.data.priq_stats.xmitcnt.packets, (unsigned long long)cur.data.priq_stats.xmitcnt.bytes, (unsigned long long)cur.data.priq_stats.dropcnt.packets, (unsigned long long)cur.data.priq_stats.dropcnt.bytes); printf(" [ qlength: %3d/%3d ]\n", cur.data.priq_stats.qlength, cur.data.priq_stats.qlimit); if (cur.avgn < 2) return; printf(" [ measured: %7.1f packets/s, %s/s ]\n", cur.avg_packets / STAT_INTERVAL, rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); } void print_hfscstats(struct queue_stats cur) { printf(" [ pkts: %10llu bytes: %10llu " "dropped pkts: %6llu bytes: %6llu ]\n", (unsigned long long)cur.data.hfsc_stats.xmit_cnt.packets, (unsigned long long)cur.data.hfsc_stats.xmit_cnt.bytes, (unsigned long long)cur.data.hfsc_stats.drop_cnt.packets, (unsigned long long)cur.data.hfsc_stats.drop_cnt.bytes); printf(" [ qlength: %3d/%3d ]\n", cur.data.hfsc_stats.qlength, cur.data.hfsc_stats.qlimit); if (cur.avgn < 2) return; printf(" [ measured: %7.1f packets/s, %s/s ]\n", cur.avg_packets / STAT_INTERVAL, rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); } void pfctl_free_altq_node(struct pf_altq_node *node) { while (node != NULL) { struct pf_altq_node *prev; if (node->children != NULL) pfctl_free_altq_node(node->children); prev = node; node = node->next; free(prev); } } void update_avg(struct pf_altq_node *a) { struct queue_stats *qs; u_int64_t b, p; int n; if (a->altq.qid == 0) return; qs = &a->qstats; n = qs->avgn; switch (a->altq.scheduler) { case ALTQT_CBQ: b = qs->data.cbq_stats.xmit_cnt.bytes; p = qs->data.cbq_stats.xmit_cnt.packets; break; case ALTQT_PRIQ: b = qs->data.priq_stats.xmitcnt.bytes; p = qs->data.priq_stats.xmitcnt.packets; break; case ALTQT_HFSC: b = qs->data.hfsc_stats.xmit_cnt.bytes; p = qs->data.hfsc_stats.xmit_cnt.packets; break; default: b = 0; p = 0; break; } if (n == 0) { qs->prev_bytes = b; qs->prev_packets = p; qs->avgn++; return; } if (b >= qs->prev_bytes) qs->avg_bytes = ((qs->avg_bytes * (n - 1)) + (b - qs->prev_bytes)) / n; if (p >= qs->prev_packets) qs->avg_packets = ((qs->avg_packets * (n - 1)) + (p - qs->prev_packets)) / n; qs->prev_bytes = b; qs->prev_packets = p; if (n < AVGN_MAX) qs->avgn++; } diff --git a/contrib/pf/pfctl/pfctl_table.c b/contrib/pf/pfctl/pfctl_table.c index 3796e1e55d56..ad4d53abb9c7 100644 --- a/contrib/pf/pfctl/pfctl_table.c +++ b/contrib/pf/pfctl/pfctl_table.c @@ -1,526 +1,528 @@ -/* $FreeBSD$ */ /* $OpenBSD: pfctl_table.c,v 1.50 2003/08/29 21:47:36 cedric Exp $ */ /* * Copyright (c) 2002 Cedric Berger * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" extern void usage(void); static int pfctl_table(int, char *[], char *, const char *, char *, const char *, const char *, int); static void print_table(struct pfr_table *, int, int); static void print_tstats(struct pfr_tstats *, int); static int load_addr(struct pfr_buffer *, int, char *[], char *, int); static void print_addrx(struct pfr_addr *, struct pfr_addr *, int); static void print_astats(struct pfr_astats *, int); static void radix_perror(void); static void xprintf(int, const char *, ...); static const char *stats_text[PFR_DIR_MAX][PFR_OP_TABLE_MAX] = { { "In/Block:", "In/Pass:", "In/XPass:" }, { "Out/Block:", "Out/Pass:", "Out/XPass:" } }; #define RVTEST(fct) do { \ if ((!(opts & PF_OPT_NOACTION) || \ (opts & PF_OPT_DUMMYACTION)) && \ (fct)) { \ radix_perror(); \ goto _error; \ } \ } while (0) #define CREATE_TABLE do { \ table.pfrt_flags |= PFR_TFLAG_PERSIST; \ RVTEST(pfr_add_tables(&table, 1, &nadd, flags)); \ if (nadd) { \ warn_namespace_collision(table.pfrt_name); \ xprintf(opts, "%d table created", nadd); \ if (opts & PF_OPT_NOACTION) \ return (0); \ } \ table.pfrt_flags &= ~PFR_TFLAG_PERSIST; \ } while(0) int pfctl_clear_tables(const char *anchor, const char *ruleset, int opts) { return pfctl_table(0, NULL, NULL, "-F", NULL, anchor, ruleset, opts); } int pfctl_show_tables(const char *anchor, const char *ruleset, int opts) { return pfctl_table(0, NULL, NULL, "-s", NULL, anchor, ruleset, opts); } int pfctl_command_tables(int argc, char *argv[], char *tname, const char *command, char *file, const char *anchor, const char *ruleset, int opts) { if (tname == NULL || command == NULL) usage(); return pfctl_table(argc, argv, tname, command, file, anchor, ruleset, opts); } int pfctl_table(int argc, char *argv[], char *tname, const char *command, char *file, const char *anchor, const char *ruleset, int opts) { struct pfr_table table; struct pfr_buffer b, b2; struct pfr_addr *a, *a2; int nadd = 0, ndel = 0, nchange = 0, nzero = 0; int rv = 0, flags = 0, nmatch = 0; void *p; if (command == NULL) usage(); if (opts & PF_OPT_NOACTION) flags |= PFR_FLAG_DUMMY; bzero(&b, sizeof(b)); bzero(&b2, sizeof(b2)); bzero(&table, sizeof(table)); if (tname != NULL) { if (strlen(tname) >= PF_TABLE_NAME_SIZE) usage(); if (strlcpy(table.pfrt_name, tname, sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) errx(1, "pfctl_table: strlcpy"); } if (strlcpy(table.pfrt_anchor, anchor, sizeof(table.pfrt_anchor)) >= sizeof(table.pfrt_anchor) || strlcpy(table.pfrt_ruleset, ruleset, sizeof(table.pfrt_ruleset)) >= sizeof(table.pfrt_ruleset)) errx(1, "pfctl_table: strlcpy"); if (!strcmp(command, "-F")) { if (argc || file != NULL) usage(); RVTEST(pfr_clr_tables(&table, &ndel, flags)); xprintf(opts, "%d tables deleted", ndel); } else if (!strcmp(command, "-s")) { b.pfrb_type = (opts & PF_OPT_VERBOSE2) ? PFRB_TSTATS : PFRB_TABLES; if (argc || file != NULL) usage(); for (;;) { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; if (opts & PF_OPT_VERBOSE2) RVTEST(pfr_get_tstats(&table, b.pfrb_caddr, &b.pfrb_size, flags)); else RVTEST(pfr_get_tables(&table, b.pfrb_caddr, &b.pfrb_size, flags)); if (b.pfrb_size <= b.pfrb_msize) break; } PFRB_FOREACH(p, &b) if (opts & PF_OPT_VERBOSE2) print_tstats(p, opts & PF_OPT_DEBUG); else print_table(p, opts & PF_OPT_VERBOSE, opts & PF_OPT_DEBUG); } else if (!strcmp(command, "kill")) { if (argc || file != NULL) usage(); RVTEST(pfr_del_tables(&table, 1, &ndel, flags)); xprintf(opts, "%d table deleted", ndel); } else if (!strcmp(command, "flush")) { if (argc || file != NULL) usage(); RVTEST(pfr_clr_addrs(&table, &ndel, flags)); xprintf(opts, "%d addresses deleted", ndel); } else if (!strcmp(command, "add")) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0)) goto _error; CREATE_TABLE; if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; RVTEST(pfr_add_addrs(&table, b.pfrb_caddr, b.pfrb_size, &nadd, flags)); xprintf(opts, "%d/%d addresses added", nadd, b.pfrb_size); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b) if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "delete")) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0)) goto _error; if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; RVTEST(pfr_del_addrs(&table, b.pfrb_caddr, b.pfrb_size, &ndel, flags)); xprintf(opts, "%d/%d addresses deleted", ndel, b.pfrb_size); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b) if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "replace")) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0)) goto _error; CREATE_TABLE; if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; for (;;) { int sz2 = b.pfrb_msize; RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size, &sz2, &nadd, &ndel, &nchange, flags)); if (sz2 <= b.pfrb_msize) { b.pfrb_size = sz2; break; } else pfr_buf_grow(&b, sz2); } if (nadd) xprintf(opts, "%d addresses added", nadd); if (ndel) xprintf(opts, "%d addresses deleted", ndel); if (nchange) xprintf(opts, "%d addresses changed", nchange); if (!nadd && !ndel && !nchange) xprintf(opts, "no changes"); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b) if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "show")) { b.pfrb_type = (opts & PF_OPT_VERBOSE) ? PFRB_ASTATS : PFRB_ADDRS; if (argc || file != NULL) usage(); for (;;) { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; if (opts & PF_OPT_VERBOSE) RVTEST(pfr_get_astats(&table, b.pfrb_caddr, &b.pfrb_size, flags)); else RVTEST(pfr_get_addrs(&table, b.pfrb_caddr, &b.pfrb_size, flags)); if (b.pfrb_size <= b.pfrb_msize) break; } PFRB_FOREACH(p, &b) if (opts & PF_OPT_VERBOSE) print_astats(p, opts & PF_OPT_USEDNS); else print_addrx(p, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "test")) { b.pfrb_type = PFRB_ADDRS; b2.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 1)) goto _error; if (opts & PF_OPT_VERBOSE2) { flags |= PFR_FLAG_REPLACE; PFRB_FOREACH(a, &b) if (pfr_buf_add(&b2, a)) err(1, "duplicate buffer"); } RVTEST(pfr_tst_addrs(&table, b.pfrb_caddr, b.pfrb_size, &nmatch, flags)); xprintf(opts, "%d/%d addresses match", nmatch, b.pfrb_size); if (opts & PF_OPT_VERBOSE && !(opts & PF_OPT_VERBOSE2)) PFRB_FOREACH(a, &b) if (a->pfra_fback == PFR_FB_MATCH) print_addrx(a, NULL, opts & PF_OPT_USEDNS); if (opts & PF_OPT_VERBOSE2) { a2 = NULL; PFRB_FOREACH(a, &b) { a2 = pfr_buf_next(&b2, a2); print_addrx(a2, a, opts & PF_OPT_USEDNS); } } if (nmatch < b.pfrb_size) rv = 2; } else if (!strcmp(command, "zero")) { if (argc || file != NULL) usage(); flags |= PFR_FLAG_ADDRSTOO; RVTEST(pfr_clr_tstats(&table, 1, &nzero, flags)); xprintf(opts, "%d table/stats cleared", nzero); } else warnx("pfctl_table: unknown command '%s'", command); goto _cleanup; _error: rv = -1; _cleanup: pfr_buf_clear(&b); pfr_buf_clear(&b2); return (rv); } void print_table(struct pfr_table *ta, int verbose, int debug) { if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE)) return; if (verbose) { printf("%c%c%c%c%c%c\t%s", (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-', (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-', (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-', (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-', (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-', (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-', ta->pfrt_name); if (ta->pfrt_anchor[0]) printf("\t%s", ta->pfrt_anchor); if (ta->pfrt_ruleset[0]) printf(":%s", ta->pfrt_ruleset); puts(""); } else puts(ta->pfrt_name); } void print_tstats(struct pfr_tstats *ts, int debug) { time_t time = ts->pfrts_tzero; int dir, op; if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE)) return; print_table(&ts->pfrts_t, 1, debug); printf("\tAddresses: %d\n", ts->pfrts_cnt); printf("\tCleared: %s", ctime(&time)); printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n", ts->pfrts_refcnt[PFR_REFCNT_ANCHOR], ts->pfrts_refcnt[PFR_REFCNT_RULE]); printf("\tEvaluations: [ NoMatch: %-18llu Match: %-18llu ]\n", (unsigned long long)ts->pfrts_nomatch, (unsigned long long)ts->pfrts_match); for (dir = 0; dir < PFR_DIR_MAX; dir++) for (op = 0; op < PFR_OP_TABLE_MAX; op++) printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", stats_text[dir][op], (unsigned long long)ts->pfrts_packets[dir][op], (unsigned long long)ts->pfrts_bytes[dir][op]); } int load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file, int nonetwork) { while (argc--) if (append_addr(b, *argv++, nonetwork)) { if (errno) warn("cannot decode %s", argv[-1]); return (-1); } if (pfr_buf_load(b, file, nonetwork, append_addr)) { warn("cannot load %s", file); return (-1); } return (0); } void print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns) { char ch, buf[256] = "{error}"; char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y' }; unsigned int fback, hostnet; fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback; ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?'; hostnet = (ad->pfra_af == AF_INET6) ? 128 : 32; inet_ntop(ad->pfra_af, &ad->pfra_u, buf, sizeof(buf)); printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf); if (ad->pfra_net < hostnet) printf("/%d", ad->pfra_net); if (rad != NULL && fback != PFR_FB_NONE) { if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf)) errx(1, "print_addrx: strlcpy"); inet_ntop(rad->pfra_af, &rad->pfra_u, buf, sizeof(buf)); printf("\t%c%s", (rad->pfra_not?'!':' '), buf); if (rad->pfra_net < hostnet) printf("/%d", rad->pfra_net); } if (rad != NULL && fback == PFR_FB_NONE) printf("\t nomatch"); if (dns && ad->pfra_net == hostnet) { char host[NI_MAXHOST]; union sockaddr_union sa; strlcpy(host, "?", sizeof(host)); bzero(&sa, sizeof(sa)); sa.sa.sa_family = ad->pfra_af; if (sa.sa.sa_family == AF_INET) { sa.sa.sa_len = sizeof(sa.sin); sa.sin.sin_addr = ad->pfra_ip4addr; } else { sa.sa.sa_len = sizeof(sa.sin6); sa.sin6.sin6_addr = ad->pfra_ip6addr; } if (getnameinfo(&sa.sa, sa.sa.sa_len, host, sizeof(host), NULL, 0, NI_NAMEREQD) == 0) printf("\t(%s)", host); } printf("\n"); } void print_astats(struct pfr_astats *as, int dns) { time_t time = as->pfras_tzero; int dir, op; print_addrx(&as->pfras_a, NULL, dns); printf("\tCleared: %s", ctime(&time)); for (dir = 0; dir < PFR_DIR_MAX; dir++) for (op = 0; op < PFR_OP_ADDR_MAX; op++) printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", stats_text[dir][op], (unsigned long long)as->pfras_packets[dir][op], (unsigned long long)as->pfras_bytes[dir][op]); } void radix_perror(void) { extern char *__progname; fprintf(stderr, "%s: %s.\n", __progname, pfr_strerror(errno)); } int pfctl_define_table(char *name, int flags, int addrs, const char *anchor, const char *ruleset, struct pfr_buffer *ab, u_int32_t ticket) { struct pfr_table tbl; bzero(&tbl, sizeof(tbl)); if (strlcpy(tbl.pfrt_name, name, sizeof(tbl.pfrt_name)) >= sizeof(tbl.pfrt_name) || strlcpy(tbl.pfrt_anchor, anchor, sizeof(tbl.pfrt_anchor)) >= sizeof(tbl.pfrt_anchor) || strlcpy(tbl.pfrt_ruleset, ruleset, sizeof(tbl.pfrt_ruleset)) >= sizeof(tbl.pfrt_ruleset)) errx(1, "pfctl_define_table: strlcpy"); tbl.pfrt_flags = flags; return pfr_ina_define(&tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, NULL, ticket, addrs ? PFR_FLAG_ADDRSTOO : 0); } void warn_namespace_collision(const char *filter) { struct pfr_buffer b; struct pfr_table *t; const char *name = NULL, *lastcoll; int coll = 0; bzero(&b, sizeof(b)); b.pfrb_type = PFRB_TABLES; for (;;) { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; if (pfr_get_tables(NULL, b.pfrb_caddr, &b.pfrb_size, PFR_FLAG_ALLRSETS)) err(1, "pfr_get_tables"); if (b.pfrb_size <= b.pfrb_msize) break; } PFRB_FOREACH(t, &b) { if (!(t->pfrt_flags & PFR_TFLAG_ACTIVE)) continue; if (filter != NULL && strcmp(filter, t->pfrt_name)) continue; if (!t->pfrt_anchor[0]) name = t->pfrt_name; else if (name != NULL && !strcmp(name, t->pfrt_name)) { coll++; lastcoll = name; name = NULL; } } if (coll == 1) warnx("warning: namespace collision with <%s> global table.", lastcoll); else if (coll > 1) warnx("warning: namespace collisions with %d global tables.", coll); pfr_buf_clear(&b); } void xprintf(int opts, const char *fmt, ...) { va_list args; if (opts & PF_OPT_QUIET) return; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); if (opts & PF_OPT_DUMMYACTION) fprintf(stderr, " (dummy).\n"); else if (opts & PF_OPT_NOACTION) fprintf(stderr, " (syntax only).\n"); else fprintf(stderr, ".\n"); } diff --git a/contrib/pf/pflogd/pflogd.c b/contrib/pf/pflogd/pflogd.c index 6ed5f9f1c8f2..e69c4ae2b22b 100644 --- a/contrib/pf/pflogd/pflogd.c +++ b/contrib/pf/pflogd/pflogd.c @@ -1,409 +1,411 @@ -/* $FreeBSD$ */ /* $OpenBSD: pflogd.c,v 1.21 2003/08/22 21:50:34 david Exp $ */ /* * Copyright (c) 2001 Theo de Raadt * Copyright (c) 2001 Can Erkin Acar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include "pidfile.h" #else #include #endif #define DEF_SNAPLEN 116 /* default plus allow for larger header of pflog */ #define PCAP_TO_MS 500 /* pcap read timeout (ms) */ #define PCAP_NUM_PKTS 1000 /* max number of packets to process at each loop */ #define PCAP_OPT_FIL 0 /* filter optimization */ #define FLUSH_DELAY 60 /* flush delay */ #define PFLOGD_LOG_FILE "/var/log/pflog" #define PFLOGD_DEFAULT_IF "pflog0" pcap_t *hpcap; pcap_dumper_t *dpcap; int Debug = 0; int snaplen = DEF_SNAPLEN; volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup; char *filename = PFLOGD_LOG_FILE; char *interface = PFLOGD_DEFAULT_IF; char *filter = NULL; char errbuf[PCAP_ERRBUF_SIZE]; int log_debug = 0; unsigned int delay = FLUSH_DELAY; char *copy_argv(char * const *argv); int init_pcap(void); void logmsg(int priority, const char *message, ...); int reset_dump(void); void sig_alrm(int); void sig_close(int); void sig_hup(int); void usage(void); char * copy_argv(char * const *argv) { size_t len = 0, n; char *buf; if (argv == NULL) return (NULL); for (n = 0; argv[n]; n++) len += strlen(argv[n])+1; if (len == 0) return (NULL); buf = malloc(len); if (buf == NULL) return (NULL); strlcpy(buf, argv[0], len); for (n = 1; argv[n]; n++) { strlcat(buf, " ", len); strlcat(buf, argv[n], len); } return (buf); } void logmsg(int pri, const char *message, ...) { va_list ap; va_start(ap, message); if (log_debug) { vfprintf(stderr, message, ap); fprintf(stderr, "\n"); } else vsyslog(pri, message, ap); va_end(ap); } #ifdef __FreeBSD__ __dead2 void #else __dead void #endif usage(void) { fprintf(stderr, "usage: pflogd [-D] [-d delay] [-f filename] "); fprintf(stderr, "[-s snaplen] [expression]\n"); exit(1); } void sig_close(int sig) { gotsig_close = 1; } void sig_hup(int sig) { gotsig_hup = 1; } void sig_alrm(int sig) { gotsig_alrm = 1; } int init_pcap(void) { struct bpf_program bprog; pcap_t *oldhpcap = hpcap; hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf); if (hpcap == NULL) { logmsg(LOG_ERR, "Failed to initialize: %s", errbuf); hpcap = oldhpcap; return (-1); } if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0) logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); else if (pcap_setfilter(hpcap, &bprog) < 0) logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); if (filter != NULL) free(filter); if (pcap_datalink(hpcap) != DLT_PFLOG) { logmsg(LOG_ERR, "Invalid datalink type"); pcap_close(hpcap); hpcap = oldhpcap; return (-1); } if (oldhpcap) pcap_close(oldhpcap); snaplen = pcap_snapshot(hpcap); return (0); } int reset_dump(void) { struct pcap_file_header hdr; struct stat st; int tmpsnap; FILE *fp; if (hpcap == NULL) return (1); if (dpcap) { pcap_dump_close(dpcap); dpcap = 0; } /* * Basically reimplement pcap_dump_open() because it truncates * files and duplicates headers and such. */ fp = fopen(filename, "a+"); if (fp == NULL) { snprintf(hpcap->errbuf, PCAP_ERRBUF_SIZE, "%s: %s", filename, pcap_strerror(errno)); logmsg(LOG_ERR, "Error: %s", pcap_geterr(hpcap)); return (1); } if (fstat(fileno(fp), &st) == -1) { snprintf(hpcap->errbuf, PCAP_ERRBUF_SIZE, "%s: %s", filename, pcap_strerror(errno)); logmsg(LOG_ERR, "Error: %s", pcap_geterr(hpcap)); return (1); } dpcap = (pcap_dumper_t *)fp; #define TCPDUMP_MAGIC 0xa1b2c3d4 if (st.st_size == 0) { if (snaplen != pcap_snapshot(hpcap)) { logmsg(LOG_NOTICE, "Using snaplen %d", snaplen); if (init_pcap()) { logmsg(LOG_ERR, "Failed to initialize"); if (hpcap == NULL) return (-1); logmsg(LOG_NOTICE, "Using old settings"); } } hdr.magic = TCPDUMP_MAGIC; hdr.version_major = PCAP_VERSION_MAJOR; hdr.version_minor = PCAP_VERSION_MINOR; hdr.thiszone = hpcap->tzoff; hdr.snaplen = hpcap->snapshot; hdr.sigfigs = 0; hdr.linktype = hpcap->linktype; if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) { dpcap = NULL; fclose(fp); return (-1); } return (0); } /* * XXX Must read the file, compare the header against our new * options (in particular, snaplen) and adjust our options so * that we generate a correct file. */ (void) fseek(fp, 0L, SEEK_SET); if (fread((char *)&hdr, sizeof(hdr), 1, fp) == 1) { if (hdr.magic != TCPDUMP_MAGIC || hdr.version_major != PCAP_VERSION_MAJOR || hdr.version_minor != PCAP_VERSION_MINOR || hdr.linktype != hpcap->linktype) { logmsg(LOG_ERR, "Invalid/incompatible log file, move it away"); fclose(fp); return (1); } if (hdr.snaplen != snaplen) { logmsg(LOG_WARNING, "Existing file specifies a snaplen of %u, using it", hdr.snaplen); tmpsnap = snaplen; snaplen = hdr.snaplen; if (init_pcap()) { logmsg(LOG_ERR, "Failed to re-initialize"); if (hpcap == 0) return (-1); logmsg(LOG_NOTICE, "Using old settings, offset: %llu", (unsigned long long)st.st_size); } snaplen = tmpsnap; } } (void) fseek(fp, 0L, SEEK_END); return (0); } int main(int argc, char **argv) { struct pcap_stat pstat; int ch, np; while ((ch = getopt(argc, argv, "Dd:s:f:")) != -1) { switch (ch) { case 'D': Debug = 1; break; case 'd': delay = atoi(optarg); if (delay < 5 || delay > 60*60) usage(); break; case 'f': filename = optarg; break; case 's': snaplen = atoi(optarg); if (snaplen <= 0) snaplen = DEF_SNAPLEN; break; default: usage(); } } log_debug = Debug; argc -= optind; argv += optind; if (!Debug) { openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON); if (daemon(0, 0)) { logmsg(LOG_WARNING, "Failed to become daemon: %s", strerror(errno)); } pidfile(NULL); } (void)umask(S_IRWXG | S_IRWXO); signal(SIGTERM, sig_close); signal(SIGINT, sig_close); signal(SIGQUIT, sig_close); signal(SIGALRM, sig_alrm); signal(SIGHUP, sig_hup); alarm(delay); if (argc) { filter = copy_argv(argv); if (filter == NULL) logmsg(LOG_NOTICE, "Failed to form filter expression"); } if (init_pcap()) { logmsg(LOG_ERR, "Exiting, init failure"); exit(1); } if (reset_dump()) { logmsg(LOG_ERR, "Failed to open log file %s", filename); pcap_close(hpcap); exit(1); } while (1) { np = pcap_dispatch(hpcap, PCAP_NUM_PKTS, pcap_dump, (u_char *)dpcap); if (np < 0) logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap)); if (gotsig_close) break; if (gotsig_hup) { if (reset_dump()) { logmsg(LOG_ERR, "Failed to open log file!"); break; } logmsg(LOG_NOTICE, "Reopened logfile"); gotsig_hup = 0; } if (gotsig_alrm) { /* XXX pcap_dumper is an incomplete type which libpcap * casts to a FILE* currently. For now it is safe to * make the same assumption, however this may change * in the future. */ if (dpcap) { if (fflush((FILE *)dpcap) == EOF) { break; } } gotsig_alrm = 0; alarm(delay); } } logmsg(LOG_NOTICE, "Exiting due to signal"); if (dpcap) pcap_dump_close(dpcap); if (pcap_stats(hpcap, &pstat) < 0) logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap)); else logmsg(LOG_NOTICE, "%u packets received, %u dropped", pstat.ps_recv, pstat.ps_drop); pcap_close(hpcap); if (!Debug) closelog(); return (0); }