From 2135f6a83c8e1b39db8af0b470ab6d0349144be9 Mon Sep 17 00:00:00 2001 From: mlaier Date: Sat, 28 Feb 2004 16:52:45 +0000 Subject: Vendor import of OpenBSD's pf userland as of OpenBSD 3.4 Approved by: bms(mentor), core(in general) --- contrib/pf/authpf/authpf.8 | 463 ++++ contrib/pf/authpf/authpf.c | 871 +++++++ contrib/pf/authpf/pathnames.h | 37 + contrib/pf/ftp-proxy/ftp-proxy.8 | 253 +++ contrib/pf/ftp-proxy/ftp-proxy.c | 1320 +++++++++++ contrib/pf/ftp-proxy/getline.c | 259 +++ contrib/pf/ftp-proxy/util.c | 296 +++ contrib/pf/ftp-proxy/util.h | 68 + contrib/pf/man/pf.4 | 703 ++++++ contrib/pf/man/pf.conf.5 | 2486 ++++++++++++++++++++ contrib/pf/man/pf.os.5 | 242 ++ contrib/pf/man/pflog.4 | 88 + contrib/pf/man/pfsync.4 | 80 + contrib/pf/pfctl/parse.y | 4489 +++++++++++++++++++++++++++++++++++++ contrib/pf/pfctl/pf_print_state.c | 279 +++ contrib/pf/pfctl/pfctl.8 | 506 +++++ contrib/pf/pfctl/pfctl.c | 1626 ++++++++++++++ contrib/pf/pfctl/pfctl.h | 115 + contrib/pf/pfctl/pfctl_altq.c | 1210 ++++++++++ contrib/pf/pfctl/pfctl_osfp.c | 1093 +++++++++ contrib/pf/pfctl/pfctl_parser.c | 1286 +++++++++++ contrib/pf/pfctl/pfctl_parser.h | 228 ++ contrib/pf/pfctl/pfctl_qstats.c | 401 ++++ contrib/pf/pfctl/pfctl_radix.c | 639 ++++++ contrib/pf/pfctl/pfctl_table.c | 524 +++++ contrib/pf/pflogd/pflogd.8 | 179 ++ contrib/pf/pflogd/pflogd.c | 400 ++++ 27 files changed, 20141 insertions(+) create mode 100644 contrib/pf/authpf/authpf.8 create mode 100644 contrib/pf/authpf/authpf.c create mode 100644 contrib/pf/authpf/pathnames.h create mode 100644 contrib/pf/ftp-proxy/ftp-proxy.8 create mode 100644 contrib/pf/ftp-proxy/ftp-proxy.c create mode 100644 contrib/pf/ftp-proxy/getline.c create mode 100644 contrib/pf/ftp-proxy/util.c create mode 100644 contrib/pf/ftp-proxy/util.h create mode 100644 contrib/pf/man/pf.4 create mode 100644 contrib/pf/man/pf.conf.5 create mode 100644 contrib/pf/man/pf.os.5 create mode 100644 contrib/pf/man/pflog.4 create mode 100644 contrib/pf/man/pfsync.4 create mode 100644 contrib/pf/pfctl/parse.y create mode 100644 contrib/pf/pfctl/pf_print_state.c create mode 100644 contrib/pf/pfctl/pfctl.8 create mode 100644 contrib/pf/pfctl/pfctl.c create mode 100644 contrib/pf/pfctl/pfctl.h create mode 100644 contrib/pf/pfctl/pfctl_altq.c create mode 100644 contrib/pf/pfctl/pfctl_osfp.c create mode 100644 contrib/pf/pfctl/pfctl_parser.c create mode 100644 contrib/pf/pfctl/pfctl_parser.h create mode 100644 contrib/pf/pfctl/pfctl_qstats.c create mode 100644 contrib/pf/pfctl/pfctl_radix.c create mode 100644 contrib/pf/pfctl/pfctl_table.c create mode 100644 contrib/pf/pflogd/pflogd.8 create mode 100644 contrib/pf/pflogd/pflogd.c (limited to 'contrib') diff --git a/contrib/pf/authpf/authpf.8 b/contrib/pf/authpf/authpf.8 new file mode 100644 index 0000000..141aecf --- /dev/null +++ b/contrib/pf/authpf/authpf.8 @@ -0,0 +1,463 @@ +.\" $OpenBSD: authpf.8,v 1.30 2003/08/17 23:24:47 henning Exp $ +.\" +.\" Copyright (c) 2002 Bob Beck (beck@openbsd.org>. 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. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" 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. +.\" +.Dd January 10, 2002 +.Dt AUTHPF 8 +.Os +.Sh NAME +.Nm authpf +.Nd authenticating gateway user shell +.Sh SYNOPSIS +.Nm authpf +.Sh DESCRIPTION +.Nm +is a user shell for authenticating gateways. +It is used to change +.Xr pf 4 +rules when a user authenticates and starts a session with +.Xr sshd 8 +and to undo these changes when the user's session exits. +It is designed for changing filter and translation rules for an individual +source IP address as long as a user maintains an active +.Xr ssh 1 +session. +Typical use would be for a gateway that authenticates users before +allowing them Internet use, or a gateway that allows different users into +different places. +.Nm +logs the successful start and end of a session to +.Xr syslogd 8 . +This, combined with properly set up filter rules and secure switches, +can be used to ensure users are held accountable for their network traffic. +.Pp +.Nm +can add filter and translation rules using the syntax described in +.Xr pf.conf 5 . +.Nm +requires that the +.Xr pf 4 +system be enabled before use. +.Pp +.Nm +is meant to be used with users who can connect via +.Xr ssh 1 +only. +On startup, +.Nm +retrieves the client's connecting IP address via the +.Ev SSH_CLIENT +environment variable and, after performing additional access checks, +reads a template file to determine what filter and translation rules +(if any) to add. +On session exit the same rules that were added at startup are removed. +.Pp +Each +.Nm +process stores its rules in a separate ruleset inside a +.Xr pf 4 +.Pa anchor +shared by all +.Nm +processes. +By default, the +.Pa anchor +name "authpf" is used, and the ruleset names equal the PIDs of the +.Nm +processes. +The following rules need to be added to the main ruleset +.Pa /etc/pf.conf +in order to cause evaluation of any +.Nm +rules: +.Bd -literal -offset indent +nat-anchor authpf +rdr-anchor authpf +binat-anchor authpf +anchor authpf +.Ed +.Sh FILTER AND TRANSLATION RULES +Filter and translation rules for +.Nm +use the same format described in +.Xr pf.conf 5 . +The only difference is that these rules may (and probably should) use +the macro +.Em user_ip , +which is assigned the connecting IP address whenever +.Nm +is run. +Additionally, the macro +.Em user_id +is assigned the user name. +.Pp +Filter and nat rules will first be searched for in +.Pa /etc/authpf/users/$USER/ +and then in +.Pa /etc/authpf/ . +Per-user rules from the +.Pa /etc/authpf/users/$USER/ +directory are intended to be used when non-default rules +are needed on an individual user basis. +It is important to ensure that a user can not write or change +these configuration files. +.Pp +Filter and translation rules are loaded from the file +.Pa /etc/authpf/users/$USER/authpf.rules . +If this file does not exist the file +.Pa /etc/authpf/authpf.rules +is used. +The +.Pa authpf.rules +file must exist in one of the above locations for +.Nm +to run. +.Pp +Translation rules are also loaded from this file. +The use of translation rules in an +.Pa authpf.rules +file is optional. +.Sh CONFIGURATION +Options are controlled by the +.Pa /etc/authpf/authpf.conf +file. +If the file is empty, defaults are used for all +configuration options. +The file consists of pairs of the form +.Li name=value , +one per line. +Currently, the allowed values are as follows: +.Bl -tag -width Ds +.It anchor=name +Use the specified +.Pa anchor +name instead of "authpf". +.El +.Sh USER MESSAGES +On successful invocation, +.Nm +displays a message telling the user he or she has been authenticated. +It will additionally display the contents of the file +.Pa /etc/authpf/authpf.message +if the file exists and is readable. +.Pp +There exist two methods for providing additional granularity to the control +offered by +.Nm +- it is possible to set the gateway to explicitly allow users who have +authenticated to +.Xr ssh 1 +and deny access to only a few troublesome individuals. +This is done by creating a file with the banned user's login name as the +filename in +.Pa /etc/authpf/banned/ . +The contents of this file will be displayed to a banned user, thus providing +a method for informing the user that they have been banned, and where they can +go and how to get there if they want to have their service restored. +This is the default behaviour. +.Pp +It is also possible to configure +.Nm +to only allow specific users access. +This is done by listing their login names, one per line, in +.Pa /etc/authpf/authpf.allow . +If "*" is found on a line, then all usernames match. +If +.Nm +is unable to verify the user's permission to use the gateway, it will +print a brief message and die. +It should be noted that a ban takes precedence over an allow. +.Pp +On failure, messages will be logged to +.Xr syslogd 8 +for the system administrator. +The user does not see these, but will be told the system is unavailable due to +technical difficulties. +The contents of the file +.Pa /etc/authpf/authpf.problem +will also be displayed if the file exists and is readable. +.Sh CONFIGURATION ISSUES +.Nm +maintains the changed filter rules as long as the user maintains an +active session. +It is important to remember however, that the existence +of this session means the user is authenticated. +Because of this, it is important to configure +.Xr sshd 8 +to ensure the security of the session, and to ensure that the network +through which users connect is secure. +.Xr sshd 8 +should be configured to use the +.Ar ClientAliveInterval +and +.Ar ClientAliveCountMax +parameters to ensure that a ssh session is terminated quickly if +it becomes unresponsive, or if arp or address spoofing is used to +hijack the session. +Note that TCP keepalives are not sufficient for +this, since they are not secure. +.Pp +.Nm +will remove statetable entries that were created during a user's +session. +This ensures that there will be no unauthenticated traffic +allowed to pass after the controlling +.Xr ssh 1 +session has been closed. +.Pp +.Nm +is designed for gateway machines which typically do not have regular +(non-administrative) users using the machine. +An administrator must remember that +.Nm +can be used to modify the filter rules through the environment in +which it is run, and as such could be used to modify the filter rules +(based on the contents of the configuration files) by regular +users. +In the case where a machine has regular users using it, as well +as users with +.Nm +as their shell, the regular users should be prevented from running +.Nm +by using the +.Pa /etc/authpf/authpf.allow +or +.Pa /etc/authpf/banned/ +facilities. +.Pp +.Nm +modifies the packet filter and address translation rules, and because +of this it needs to be configured carefully. +.Nm +will not run and will exit silently if the +.Pa /etc/authpf/authpf.conf +file does not exist. +After considering the effect +.Nm +may have on the main packet filter rules, the system administrator may +enable +.Nm +by creating an appropriate +.Pa /etc/authpf/authpf.conf +file. +.Sh EXAMPLES +\fBControl Files\fP - To illustrate the user-specific access control +mechanisms, let us consider a typical user named bob. +Normally, as long as bob can authenticate himself, the +.Nm +program will load the appropriate rules. +Enter the +.Pa /etc/authpf/banned/ +directory. +If bob has somehow fallen from grace in the eyes of the +powers-that-be, they can prohibit him from using the gateway by creating +the file +.Pa /etc/authpf/banned/bob +containing a message about why he has been banned from using the network. +Once bob has done suitable penance, his access may be restored by moving or +removing the file +.Pa /etc/authpf/banned/bob . +.Pp +Now consider a workgroup containing alice, bob, carol and dave. +They have a +wireless network which they would like to protect from unauthorized use. +To accomplish this, they create the file +.Pa /etc/authpf/authpf.allow +which lists their login ids, one per line. +At this point, even if eve could authenticate to +.Xr sshd 8 , +she would not be allowed to use the gateway. +Adding and removing users from +the work group is a simple matter of maintaining a list of allowed userids. +If bob once again manages to annoy the powers-that-be, they can ban him from +using the gateway by creating the familiar +.Pa /etc/authpf/banned/bob +file. +Though bob is listed in the allow file, he is prevented from using +this gateway due to the existence of a ban file. +.Pp +\fBDistributed Authentication\fP - It is often desirable to interface with a +distributed password system rather than forcing the sysadmins to keep a large +number of local password files in sync. +The +.Xr login.conf 5 +mechanism in +.Ox +can be used to fork the right shell. +To make that happen, +.Xr login.conf 5 +should have entries that look something like this: +.Bd -literal -offset indent +shell-default:shell=/bin/csh + +default:\e + ... + :shell=/usr/sbin/authpf + +daemon:\e + ... + :shell=/bin/csh:\e + :tc=default: + +staff:\e + ... + :shell=/bin/csh:\e + :tc=default: +.Ed +.Pp +Using a default password file, all users will get +.Nm +as their shell except for root who will get +.Pa /bin/csh . +.Pp +\fBSSH Configuration\fP - As stated earlier, +.Xr sshd 8 +must be properly configured to detect and defeat network attacks. +To that end, the following options should be added to +.Xr sshd_config 5 : +.Bd -literal -offset indent +Protocol 2 +ClientAliveInterval 15 +ClientAliveCountMax 3 +.Ed +.Pp +This ensures that unresponsive or spoofed sessions are terminated within a +minute, since a hijacker should not be able to spoof ssh keepalive messages. +.Pp +\fBBanners\fP - Once authenticated, the user is shown the contents of +.Pa /etc/authpf/authpf.message . +This message may be a screen-full of the appropriate use policy, the contents +of +.Pa /etc/motd +or something as simple as the following: +.Bd -literal -offset indent +This means you will be held accountable by the powers that be +for traffic originating from your machine, so please play nice. +.Ed +.Pp +To tell the user where to go when the system is broken, +.Pa /etc/authpf/authpf.problem +could contain something like this: +.Bd -literal -offset indent +Sorry, there appears to be some system problem. To report this +problem so we can fix it, please phone 1-900-314-1597 or send +an email to remove@bulkmailerz.net. +.Ed +.Pp +\fBPacket Filter Rules\fP - In areas where this gateway is used to protect a +wireless network (a hub with several hundred ports), the default rule set as +well as the per-user rules should probably allow very few things beyond +encrypted protocols like +.Xr ssh 1 , +.Xr ssl 8 , +or +.Xr ipsec 4 . +On a securely switched network, with plug-in jacks for visitors who are +given authentication accounts, you might want to allow out everything. +In this context, a secure switch is one that tries to prevent address table +overflow attacks. +The examples below assume a switched wired net. +.Pp +Example +.Pa /etc/pf.conf : +.Bd -literal +# by default we allow internal clients to talk to us using +# ssh and use us as a dns server. +internal_if=\&"fxp1\&" +gateway_addr=\&"10.0.1.1\&" +nat-anchor authpf +rdr-anchor authpf +binat-anchor authpf +block in on $internal_if from any to any +pass in quick on $internal_if proto tcp from any to $gateway_addr \e + port = ssh +pass in quick on $internal_if proto udp from any to $gateway_addr \e + port = domain +anchor authpf +.Ed +.Pp +Example +.Pa /etc/authpf/authpf.rules : +.Bd -literal +# no real restrictions here, basically turn the network jack off or on. + +external_if = \&"xl0\&" +internal_if = \&"fxp0\&" + +pass in log quick on $internal_if proto tcp from $user_ip to any \e + keep state +pass in quick on $internal_if from $user_ip to any +.Ed +.Pp +Another example +.Pa /etc/authpf/authpf.rules +for an insecure network (such as a public wireless network) where +we might need to be a bit more restrictive. +.Bd -literal +internal_if=\&"fxp1\&" +ipsec_gw=\&"10.2.3.4\&" + +# rdr ftp for proxying by ftp-proxy(8) +rdr on $internal_if proto tcp from $user_ip to any port 21 \e + -> 127.0.0.1 port 8081 + +# allow out ftp, ssh, www and https only, and allow user to negotiate +# ipsec with the ipsec server. +pass in log quick on $internal_if proto tcp from $user_ip to any \e + port { 21, 22, 80, 443 } flags S/SA +pass in quick on $internal_if proto tcp from $user_ip to any \e + port { 21, 22, 80, 443 } +pass in quick proto udp from $user_ip to $ipsec_gw port = isakmp \e + keep state +pass in quick proto esp from $user_ip to $ipsec_gw +.Ed +.Sh FILES +.Bl -tag -width "/etc/authpf/authpf.conf" -compact +.It Pa /etc/authpf/authpf.conf +.It Pa /etc/authpf/authpf.allow +.It Pa /etc/authpf/authpf.rules +.It Pa /etc/authpf/authpf.message +.It Pa /etc/authpf/authpf.problem +.El +.Sh SEE ALSO +.Xr pf 4 , +.Xr pf.conf 5 , +.Xr ftp-proxy 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 3.1 . +.Sh BUGS +Configuration issues are tricky. +The authenticating +.Xr ssh 1 +connection may be secured, but if the network is not secured the user may +expose insecure protocols to attackers on the same network, or enable other +attackers on the network to pretend to be the user by spoofing their IP +address. +.Pp +.Nm +is not designed to prevent users from denying service to other users. diff --git a/contrib/pf/authpf/authpf.c b/contrib/pf/authpf/authpf.c new file mode 100644 index 0000000..8828ffc --- /dev/null +++ b/contrib/pf/authpf/authpf.c @@ -0,0 +1,871 @@ +/* $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 +#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); +static __dead void do_death(int); + +/* + * 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); +} + +/* + * 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. + */ +static __dead void +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/authpf/pathnames.h b/contrib/pf/authpf/pathnames.h new file mode 100644 index 0000000..97d1d74 --- /dev/null +++ b/contrib/pf/authpf/pathnames.h @@ -0,0 +1,37 @@ +/* $OpenBSD: pathnames.h,v 1.6 2003/06/03 20:38:59 beck Exp $ */ + +/* + * Copyright (C) 2002 Chris Kuethe (ckuethe@ualberta.ca) + * + * 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. + */ + +#define PATH_CONFFILE "/etc/authpf/authpf.conf" +#define PATH_ALLOWFILE "/etc/authpf/authpf.allow" +#define PATH_PFRULES "/etc/authpf/authpf.rules" +#define PATH_PROBLEM "/etc/authpf/authpf.problem" +#define PATH_MESSAGE "/etc/authpf/authpf.message" +#define PATH_USER_DIR "/etc/authpf/users" +#define PATH_BAN_DIR "/etc/authpf/banned" +#define PATH_DEVFILE "/dev/pf" +#define PATH_PIDFILE "/var/authpf" +#define PATH_AUTHPF_SHELL "/usr/sbin/authpf" diff --git a/contrib/pf/ftp-proxy/ftp-proxy.8 b/contrib/pf/ftp-proxy/ftp-proxy.8 new file mode 100644 index 0000000..2832ddb --- /dev/null +++ b/contrib/pf/ftp-proxy/ftp-proxy.8 @@ -0,0 +1,253 @@ +.\" $OpenBSD: ftp-proxy.8,v 1.37 2003/09/05 12:27:47 jmc 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 University 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 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. +.\" +.Dd August 17, 2001 +.Dt FTP-PROXY 8 +.Os +.Sh NAME +.Nm ftp-proxy +.Nd Internet File Transfer Protocol proxy server +.Sh SYNOPSIS +.Nm ftp-proxy +.Op Fl AnrVw +.Op Fl D Ar debuglevel +.Op Fl g Ar group +.Op Fl m Ar minport +.Op Fl M Ar maxport +.Op Fl t Ar timeout +.Op Fl u Ar user +.Sh DESCRIPTION +.Nm +is a proxy for the Internet File Transfer Protocol. +The proxy uses +.Xr pf 4 +and expects to have the FTP control connection as described in +.Xr services 5 +redirected to it via a +.Xr pf 4 +.Em rdr +command. +An example of how to do that is further down in this document. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl A +Permit only anonymous FTP connections. +The proxy will allow connections to log in to other sites as the user +.Qq ftp +or +.Qq anonymous +only. +Any attempt to log in as another user will be blocked by the proxy. +.It Fl D Ar debuglevel +Specify a debug level, where the proxy emits verbose debug output +into +.Xr syslogd 8 +at level +.Dv LOG_DEBUG . +Meaningful values of debuglevel are 0-3, where 0 is no debug output and +3 is lots of debug output, the default being 0. +.It Fl g Ar group +Specify the named group to drop group privileges to, after doing +.Xr pf 4 +lookups which require root. +By default, +.Nm +uses the default group of the user it drops privilege to. +.It Fl m Ar minport +Specify the lower end of the port range the proxy will use for all +data connections it establishes. +The default is +.Dv IPPORT_HIFIRSTAUTO +defined in +.Aq Pa netinet/in.h +as 49152. +.It Fl M Ar maxport +Specify the upper end of the port range the proxy will use for the +data connections it establishes. +The default is +.Dv IPPORT_HILASTAUTO +defined in +.Aq Pa netinet/in.h +as 65535. +.It Fl n +Activate network address translation +.Pq NAT +mode. +In this mode, the proxy will not attempt to proxy passive mode +.Pq PASV or EPSV +data connections. +In order for this to work, the machine running the proxy will need to +be forwarding packets and doing network address translation to allow +the outbound passive connections from the client to reach the server. +See +.Xr pf.conf 5 +for more details on NAT. +The proxy only ignores passive mode data connections when using this flag; +it will still proxy PORT and EPRT mode data connections. +Without this flag, +.Nm +does not require any IP forwarding or NAT beyond the +.Em rdr +necessary to capture the FTP control connection. +.It Fl r +Use reverse host +.Pq reverse DNS +lookups for logging and libwrap use. +By default, +the proxy does not look up hostnames for libwrap or logging purposes. +.It Fl t Ar timeout +Specifies a timeout, in seconds. +The proxy will exit and close open connections if it sees no data +for the duration of the timeout. +The default is 0, which means the proxy will not time out. +.It Fl u Ar user +Specify the named user to drop privilege to, after doing +.Xr pf 4 +lookups which require root privilege. +By default, +.Nm +drops privilege to the user +.Em proxy . +.Pp +Running as root means that the source of data connections the proxy makes +for PORT and EPRT will be the RFC mandated port 20. +When running as a non-root user, the source of the data connections from +.Nm +will be chosen randomly from the range +.Ar minport +to +.Ar maxport +as described above. +.It Fl V +Be verbose. +With this option the proxy logs the control commands +sent by clients and the replies sent by the servers to +.Xr syslogd 8 . +.It Fl w +Use the tcp wrapper access control library +.Xr hosts_access 3 , +allowing connections to be allowed or denied based on the tcp wrapper's +.Xr hosts.allow 5 +and +.Xr hosts.deny 5 +files. +The proxy does libwrap operations after determining the destination +of the captured control connection, so that tcp wrapper rules may +be written based on the destination as well as the source of FTP connections. +.El +.Pp +.Nm ftp-proxy +is run from +.Xr inetd 8 +and requires that FTP connections are redirected to it using a +.Em rdr +rule. +A typical way to do this would be to use a +.Xr pf.conf 5 +rule such as +.Bd -literal -offset 2n +int_if = xl0 +rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 port 8021 +.Ed +.Pp +.Xr inetd 8 +must then be configured to run +.Nm +on the port from above using +.Bd -literal -offset 2n +127.0.0.1:8021 stream tcp nowait root /usr/libexec/ftp-proxy ftp-proxy +.Ed +.Pp +in +.Xr inetd.conf 5 . +.Pp +.Nm +accepts the redirected control connections and forwards them +to the server. +The proxy replaces the address and port number that the client +sends through the control connection to the server with its own +address and proxy port, where it listens for the data connection. +When the server opens the data connection back to this port, the +proxy forwards it to the client. +The +.Xr pf.conf 5 +rules need to let pass connections to these proxy ports +(see options +.Fl u , m , +and +.Fl M +above) in on the external interface. +The following example allows only ports 49152 to 65535 to pass in +statefully: +.Bd -literal -offset indent +block in on $ext_if proto tcp all +pass in on $ext_if inet proto tcp from any to $ext_if \e + port > 49151 keep state +.Ed +.Pp +Alternatively, rules can make use of the fact that by default, +.Nm +runs as user +.Qq proxy +to allow the backchannel connections, as in the following example: +.Bd -literal -offset indent +block in on $ext_if proto tcp all +pass in on $ext_if inet proto tcp from any to $ext_if \e + user proxy keep state +.Ed +.Pp +These examples do not cover the connections from the proxy to the +foreign FTP server. +If one does not pass outgoing connections by default additional rules +are needed. +.Sh SEE ALSO +.Xr ftp 1 , +.Xr pf 4 , +.Xr hosts.allow 5 , +.Xr hosts.deny 5 , +.Xr inetd.conf 5 , +.Xr pf.conf 5 , +.Xr inetd 8 , +.Xr pfctl 8 , +.Xr syslogd 8 +.Sh BUGS +Extended Passive mode +.Pq EPSV +is not supported by the proxy and will not work unless the proxy is run +in network address translation mode. +When not in network address translation mode, the proxy returns an error +to the client, hopefully forcing the client to revert to passive mode +.Pq PASV +which is supported. +EPSV will work in network address translation mode, assuming a +.Xr pf.conf 5 +setup which allows the EPSV connections through to their destinations. +.Pp +IPv6 is not yet supported. diff --git a/contrib/pf/ftp-proxy/ftp-proxy.c b/contrib/pf/ftp-proxy/ftp-proxy.c new file mode 100644 index 0000000..88b6fd1 --- /dev/null +++ b/contrib/pf/ftp-proxy/ftp-proxy.c @@ -0,0 +1,1320 @@ +/* $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. + * + */ + +/* + * 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/ftp-proxy/getline.c b/contrib/pf/ftp-proxy/getline.c new file mode 100644 index 0000000..2be3883 --- /dev/null +++ b/contrib/pf/ftp-proxy/getline.c @@ -0,0 +1,259 @@ +/* $OpenBSD: getline.c,v 1.15 2003/06/28 01:04:57 deraadt Exp $ */ + +/* + * Copyright (c) 1985, 1988 Regents of the University of California. + * 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 University 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 THE REGENTS 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 REGENTS 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. + * + * @(#)ftpcmd.y 5.24 (Berkeley) 2/25/91 + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +int refill_buffer(struct csiob *iobp); + +/* + * Refill the io buffer if we KNOW that data is available + * + * Returns 1 if any new data was obtained, 0 otherwise. + */ + +int +refill_buffer(struct csiob *iobp) +{ + int rqlen, rlen; + + if (!(iobp->data_available)) + return(0); + + if (iobp->got_eof) + return(0); + + /* + * The buffer has been entirely consumed if next_byte == io_buffer_len. + * Otherwise, there is some still-to-be-used data in io_buffer. + * Shuffle it to the start of the buffer. + * Note that next_byte will never exceed io_buffer_len. + * Also, note that we MUST use bcopy because the two regions could + * overlap (memcpy isn't defined to work properly with overlapping + * regions). + */ + if (iobp->next_byte < iobp->io_buffer_len) { + int dst_ix = 0; + int src_ix = iobp->next_byte; + int amount = iobp->io_buffer_len - iobp->next_byte; + + bcopy(&iobp->io_buffer[src_ix], &iobp->io_buffer[dst_ix], + amount); + iobp->io_buffer_len = amount; + } else if (iobp->next_byte == iobp->io_buffer_len) + iobp->io_buffer_len = 0; + else { + syslog(LOG_ERR, "next_byte(%d) > io_buffer_len(%d)", + iobp->next_byte, iobp->io_buffer_len); + exit(EX_OSERR); + } + + iobp->next_byte = 0; + + /* don't do tiny reads, grow first if we need to */ + rqlen = iobp->io_buffer_size - iobp->io_buffer_len; + if (rqlen <= 128) { + char *tmp; + + iobp->io_buffer_size += 128; + tmp = realloc(iobp->io_buffer, iobp->io_buffer_size); + if (tmp == NULL) { + syslog(LOG_INFO, "Insufficient memory"); + exit(EX_UNAVAILABLE); + } + iobp->io_buffer = tmp; + rqlen = iobp->io_buffer_size - iobp->io_buffer_len; + } + + /* + * Always leave an unused byte at the end of the buffer + * because the debug output uses that byte from time to time + * to ensure that something that is being printed is \0 terminated. + */ + rqlen -= 1; + + doread: + rlen = read(iobp->fd, &iobp->io_buffer[iobp->io_buffer_len], rqlen); + iobp->data_available = 0; + switch (rlen) { + case -1: + if (errno == EAGAIN || errno == EINTR) + goto doread; + if (errno != ECONNRESET) { + syslog(LOG_INFO, "read() failed on socket from %s (%m)", + iobp->who); + exit(EX_DATAERR); + } + /* fall through to EOF case */ + case 0: + iobp->got_eof = 1; + return(0); + break; + default: + iobp->io_buffer_len += rlen; + break; + } + return(1); +} + +/* + * telnet_getline - a hacked up version of fgets to ignore TELNET escape codes. + * + * This code is derived from the getline routine found in the UC Berkeley + * ftpd code. + * + */ + +int +telnet_getline(struct csiob *iobp, struct csiob *telnet_passthrough) +{ + unsigned char ch; + int ix; + char tbuf[100]; + + iobp->line_buffer[0] = '\0'; + + /* + * If the buffer is empty then refill it right away. + */ + if (iobp->next_byte == iobp->io_buffer_len) + if (!refill_buffer(iobp)) + return(0); + + /* + * Is there a telnet command in the buffer? + */ + ch = iobp->io_buffer[iobp->next_byte]; + if (ch == IAC) { + /* + * Yes - buffer must have at least three bytes in it + */ + if (iobp->io_buffer_len - iobp->next_byte < 3) { + if (!refill_buffer(iobp)) + return(0); + if (iobp->io_buffer_len - iobp->next_byte < 3) + return(0); + } + + iobp->next_byte++; + ch = iobp->io_buffer[iobp->next_byte++]; + + switch (ch) { + case WILL: + case WONT: + case DO: + case DONT: + tbuf[0] = IAC; + tbuf[1] = ch; + tbuf[2] = iobp->io_buffer[iobp->next_byte++]; + (void)send(telnet_passthrough->fd, tbuf, 3, + telnet_passthrough->send_oob_flags); + break; + case IAC: + break; + default: + break; + } + return(1); + } else { + int clen; + + /* + * Is there a newline in the buffer? + */ + for (ix = iobp->next_byte; ix < iobp->io_buffer_len; + ix += 1) { + if (iobp->io_buffer[ix] == '\n') + break; + if (iobp->io_buffer[ix] == '\0') { + syslog(LOG_INFO, + "got NUL byte from %s - bye!", + iobp->who); + exit(EX_DATAERR); + } + } + + if (ix == iobp->io_buffer_len) { + if (!refill_buffer(iobp)) + return(0); + /* + * Empty line returned + * will try again soon! + */ + return(1); + } + + /* + * Expand the line buffer if it isn't big enough. We + * use a fudge factor of 5 rather than trying to + * figure out exactly how to account for the '\0 \r\n' and + * such. The correct fudge factor is 0, 1 or 2 but + * anything higher also works. We also grow it by a + * bunch to avoid having to do this often. Yes this is + * nasty. + */ + if (ix - iobp->next_byte > iobp->line_buffer_size - 5) { + char *tmp; + + iobp->line_buffer_size = 256 + ix - iobp->next_byte; + tmp = realloc(iobp->line_buffer, + iobp->line_buffer_size); + if (tmp == NULL) { + syslog(LOG_INFO, "Insufficient memory"); + exit(EX_UNAVAILABLE); + } + iobp->line_buffer = tmp; + } + + /* +1 is for the newline */ + clen = (ix+1) - iobp->next_byte; + memcpy(iobp->line_buffer, &iobp->io_buffer[iobp->next_byte], + clen); + iobp->next_byte += clen; + iobp->line_buffer[clen] = '\0'; + return(1); + } +} diff --git a/contrib/pf/ftp-proxy/util.c b/contrib/pf/ftp-proxy/util.c new file mode 100644 index 0000000..3c8b20e --- /dev/null +++ b/contrib/pf/ftp-proxy/util.c @@ -0,0 +1,296 @@ +/* $OpenBSD: util.c,v 1.16 2003/06/28 01:04:57 deraadt 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 THE 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +int Debug_Level; +int Use_Rdns; + +void debuglog(int debug_level, const char *fmt, ...); + +void +debuglog(int debug_level, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + if (Debug_Level >= debug_level) + vsyslog(LOG_DEBUG, fmt, ap); + va_end(ap); +} + +int +get_proxy_env(int connected_fd, struct sockaddr_in *real_server_sa_ptr, + struct sockaddr_in *client_sa_ptr) +{ + struct pfioc_natlook natlook; + int slen, fd; + + slen = sizeof(*real_server_sa_ptr); + if (getsockname(connected_fd, (struct sockaddr *)real_server_sa_ptr, + &slen) != 0) { + syslog(LOG_ERR, "getsockname() failed (%m)"); + return(-1); + } + slen = sizeof(*client_sa_ptr); + if (getpeername(connected_fd, (struct sockaddr *)client_sa_ptr, + &slen) != 0) { + syslog(LOG_ERR, "getpeername() failed (%m)"); + return(-1); + } + + /* + * Build up the pf natlook structure. + * Just for IPv4 right now + */ + memset((void *)&natlook, 0, sizeof(natlook)); + natlook.af = AF_INET; + natlook.saddr.addr32[0] = client_sa_ptr->sin_addr.s_addr; + natlook.daddr.addr32[0] = real_server_sa_ptr->sin_addr.s_addr; + natlook.proto = IPPROTO_TCP; + natlook.sport = client_sa_ptr->sin_port; + natlook.dport = real_server_sa_ptr->sin_port; + natlook.direction = PF_OUT; + + /* + * Open the pf device and lookup the mapping pair to find + * the original address we were supposed to connect to. + */ + fd = open("/dev/pf", O_RDWR); + if (fd == -1) { + syslog(LOG_ERR, "cannot open /dev/pf (%m)"); + exit(EX_UNAVAILABLE); + } + + if (ioctl(fd, DIOCNATLOOK, &natlook) == -1) { + syslog(LOG_INFO, + "pf nat lookup failed %s:%hu (%m)", + inet_ntoa(client_sa_ptr->sin_addr), + ntohs(client_sa_ptr->sin_port)); + close(fd); + return(-1); + } + close(fd); + + /* + * Now jam the original address and port back into the into + * destination sockaddr_in for the proxy to deal with. + */ + memset((void *)real_server_sa_ptr, 0, sizeof(struct sockaddr_in)); + real_server_sa_ptr->sin_port = natlook.rdport; + real_server_sa_ptr->sin_addr.s_addr = natlook.rdaddr.addr32[0]; + real_server_sa_ptr->sin_len = sizeof(struct sockaddr_in); + real_server_sa_ptr->sin_family = AF_INET; + return(0); +} + + +/* + * Transfer one unit of data across a pair of sockets + * + * A unit of data is as much as we get with a single read(2) call. + */ +int +xfer_data(const char *what_read,int from_fd, int to_fd, struct in_addr from, + struct in_addr to) +{ + int rlen, offset, xerrno, mark, flags = 0; + char tbuf[4096]; + + /* + * Are we at the OOB mark? + */ + if (ioctl(from_fd, SIOCATMARK, &mark) < 0) { + xerrno = errno; + syslog(LOG_ERR, "cannot ioctl(SIOCATMARK) socket from %s (%m)", + what_read); + errno = xerrno; + return(-1); + } + if (mark) + flags = MSG_OOB; /* Yes - at the OOB mark */ + +snarf: + rlen = recv(from_fd, tbuf, sizeof(tbuf), flags); + if (rlen == -1 && flags == MSG_OOB && errno == EINVAL) { + /* OOB didn't work */ + flags = 0; + rlen = recv(from_fd, tbuf, sizeof(tbuf), flags); + } + if (rlen == 0) { + debuglog(3, "EOF on read socket"); + return(0); + } else if (rlen == -1) { + if (errno == EAGAIN || errno == EINTR) + goto snarf; + xerrno = errno; + syslog(LOG_ERR, "xfer_data (%s): failed (%m) with flags 0%o", + what_read, flags); + errno = xerrno; + return(-1); + } else { + offset = 0; + debuglog(3, "got %d bytes from socket", rlen); + + while (offset < rlen) { + int wlen; + fling: + wlen = send(to_fd, &tbuf[offset], rlen - offset, + flags); + if (wlen == 0) { + debuglog(3, "zero-length write"); + goto fling; + } else if (wlen == -1) { + if (errno == EAGAIN || errno == EINTR) + goto fling; + xerrno = errno; + syslog(LOG_INFO, "write failed (%m)"); + errno = xerrno; + return(-1); + } else { + debuglog(3, "wrote %d bytes to socket",wlen); + offset += wlen; + } + } + return(offset); + } +} + +/* + * get_backchannel_socket gets us a socket bound somewhere in a + * particular range of ports + */ +int +get_backchannel_socket(int type, int min_port, int max_port, int start_port, + int direction, struct sockaddr_in *sap) +{ + int count; + + /* + * Make sure that direction is 'defined' and that min_port is not + * greater than max_port. + */ + if (direction != -1) + direction = 1; + + /* by default we go up by one port until we find one */ + if (min_port > max_port) { + errno = EINVAL; + return(-1); + } + + count = 1 + max_port - min_port; + + /* + * Pick a port we can bind to from within the range we want. + * If the caller specifies -1 as the starting port number then + * we pick one somewhere in the range to try. + * This is an optimization intended to speedup port selection and + * has NOTHING to do with security. + */ + if (start_port == -1) + start_port = (arc4random() % count) + min_port; + + if (start_port < min_port || start_port > max_port) { + errno = EINVAL; + return(-1); + } + + while (count-- > 0) { + struct sockaddr_in sa; + int one, fd; + + fd = socket(AF_INET, type, 0); + + bzero(&sa, sizeof sa); + sa.sin_family = AF_INET; + if (sap == NULL) + sa.sin_addr.s_addr = INADDR_ANY; + else + sa.sin_addr.s_addr = sap->sin_addr.s_addr; + + /* + * Indicate that we want to reuse a port if it happens that the + * port in question was a listen port recently. + */ + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one)) == -1) + return(-1); + + sa.sin_port = htons(start_port); + + if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == 0) { + if (sap != NULL) + *sap = sa; + return(fd); + } + + if (errno != EADDRINUSE) + return(-1); + + /* if it's in use, try the next port */ + close(fd); + + start_port += direction; + if (start_port < min_port) + start_port = max_port; + else if (start_port > max_port) + start_port = min_port; + } + errno = EAGAIN; + return(-1); +} diff --git a/contrib/pf/ftp-proxy/util.h b/contrib/pf/ftp-proxy/util.h new file mode 100644 index 0000000..597cd18 --- /dev/null +++ b/contrib/pf/ftp-proxy/util.h @@ -0,0 +1,68 @@ +/* $OpenBSD: util.h,v 1.3 2002/05/23 10:22:14 deraadt 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. + * 4. 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 THE REGENTS 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. + */ + +struct proxy_channel { + int pc_to_fd, pc_from_fd; + int pc_alive; + int pc_nextbyte; + int pc_flags; + int pc_length; + int pc_size; + struct sockaddr_in pc_from_sa, pc_to_sa; + int (*pc_filter)( void ** databuf, int datalen); + char *pc_buffer; +}; + +struct csiob { + int fd; + int line_buffer_size, io_buffer_size, io_buffer_len, next_byte; + unsigned char *io_buffer, *line_buffer; + struct sockaddr_in sa, real_sa; + char *who; + char alive, got_eof, data_available; + int send_oob_flags; +}; + +extern int telnet_getline(struct csiob *iobp, + struct csiob *telnet_passthrough); + +extern int get_proxy_env(int fd, struct sockaddr_in *server_sa_ptr, + struct sockaddr_in *client_sa_ptr); + +extern int get_backchannel_socket(int type, int min_port, int max_port, + int start_port, int direction, struct sockaddr_in *sap); + +extern int xfer_data(const char *what_read, int from_fd, int to_fd, + struct in_addr from, struct in_addr to); + +extern char *ProgName; + + diff --git a/contrib/pf/man/pf.4 b/contrib/pf/man/pf.4 new file mode 100644 index 0000000..f01dcb3 --- /dev/null +++ b/contrib/pf/man/pf.4 @@ -0,0 +1,703 @@ +.\" $OpenBSD: pf.4,v 1.37 2003/08/28 09:41:22 jmc Exp $ +.\" +.\" Copyright (C) 2001, Kjell Wooding. 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 project 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 THE PROJECT 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 PROJECT 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. +.\" +.Dd June 24, 2001 +.Dt PF 4 +.Os +.Sh NAME +.Nm pf +.Nd packet filter +.Sh SYNOPSIS +.Cd "pseudo-device pf 1" +.Sh DESCRIPTION +Packet filtering takes place in the kernel. +A pseudo-device, +.Pa /dev/pf , +allows userland processes to control the +behavior of the packet filter through an +.Xr ioctl 2 +interface. +There are commands to enable and disable the filter, load rulesets, +add and remove individual rules or state table entries, +and retrieve statistics. +The most commonly used functions are covered by +.Xr pfctl 8 . +.Pp +Manipulations like loading a ruleset that involve more than a single +ioctl call require a so-called ticket, which prevents the occurrence of +multiple concurrent manipulations. +.Pp +Fields of ioctl parameter structures that refer to packet data (like +addresses and ports) are generally expected in network byte-order. +.Sh FILES +.Bl -tag -width /dev/pf -compact +.It Pa /dev/pf +packet filtering device. +.El +.Sh IOCTL INTERFACE +pf supports the following +.Xr ioctl 2 +commands: +.Bl -tag -width xxxxxx +.It Dv DIOCSTART +Starts the packet filter. +.It Dv DIOCSTOP +Stops the packet filter. +.It Dv DIOCSTARTALTQ +Starts the ALTQ bandwidth control system. +.It Dv DIOCSTOPALTQ +Stops the ALTQ bandwidth control system. +.It Dv DIOCBEGINADDRS Fa "u_int32_t" +Clears the buffer address pool +and returns a ticket for subsequent DIOCADDADDR, DIOCADDRULE and +DIOCCHANGERULE calls. +.It Dv DIOCADDADDR Fa "struct pfioc_pooladdr" +.Bd -literal +struct pfioc_pooladdr { + u_int32_t action; + u_int32_t ticket; + u_int32_t nr; + u_int32_t r_num; + u_int8_t r_action; + u_int8_t r_last; + u_int8_t af; + char anchor[PF_ANCHOR_NAME_SIZE]; + char ruleset[PF_RULESET_NAME_SIZE]; + struct pf_pooladdr addr; +}; +.Ed +.Pp +Adds pool address +.Va addr +to the buffer address pool to be used in the following +DIOCADDRULE or DIOCCHANGERULE call. +All other members of the structure are ignored. +.It Dv DIOCBEGINRULES Fa "u_int32_t" +Clears the inactive ruleset for the type of rule indicated by +.Va rule.action +and returns a ticket for subsequent +DIOCADDRULE and DIOCCOMMITRULES calls. +.It Dv DIOCADDRULE Fa "struct pfioc_rule" +.Bd -literal +struct pfioc_rule { + u_int32_t action; + u_int32_t ticket; + u_int32_t pool_ticket; + u_int32_t nr; + char anchor[PF_ANCHOR_NAME_SIZE]; + char ruleset[PF_RULESET_NAME_SIZE]; + struct pf_rule rule; +}; +.Ed +.Pp +Adds +.Va rule +at the end of the inactive ruleset. +Requires +.Va ticket +obtained through preceding DIOCBEGINRULES call, and +.Va pool_ticket +obtained through DIOCBEGINADDRS call. +DIOCADDADDR must also be called if any pool addresses are required. +The optional +.Va anchor +and +.Va ruleset +names indicate the anchor and ruleset in which to append the rule. +.Va nr +and +.Va action +are ignored. +.It Dv DIOCCOMMITRULES Fa "u_int32_t" +Switch inactive to active filter ruleset. +Requires +.Va ticket . +.It Dv DIOCBEGINALTQS Fa "u_int32_t" +Clears the inactive list of queues and returns a ticket for subsequent +DIOCADDALTQ and DIOCCOMMITALTQS calls. +.It Dv DIOCADDALTQ Fa "struct pfioc_altq" +Adds +.Bd -literal +struct pfioc_altq { + u_int32_t ticket; + u_int32_t nr; + struct pf_altq altq; +}; +.Ed +.It Dv DIOCCOMMITALTQS Fa "u_int32_t" +Switch inactive to active list of queues. +Requires +.Va ticket . +.It Dv DIOCGETRULES Fa "struct pfioc_rule" +Returns +.Va ticket +for subsequent DIOCGETRULE calls and +.Va nr +of rules in the active ruleset. +.It Dv DIOCGETRULE Fa "struct pfioc_rule" +Returns +.Va rule +number +.Va nr +using +.Va ticket +obtained through a preceding DIOCGETRULES call. +.It Dv DIOCGETADDRS Fa "struct pfioc_pooladdr" +Returns +.Va ticket +for subsequent DIOCGETADDR calls and +.Va nr +of pool addresses in the rule specified with +.Va r_action , +.Va r_num , +.Va anchor +and +.Va ruleset . +.It Dv DIOCGETADDR Fa "struct pfioc_pooladdr" +Returns pool address +.Va addr +number +.Va nr +from the rule specified with +.Va r_action , +.Va r_num , +.Va anchor +and +.Va ruleset +using +.Va ticket +obtained through a preceding DIOCGETADDRS call. +.It Dv DIOCGETALTQS Fa "struct pfioc_altq" +Returns +.Va ticket +for subsequent DIOCGETALTQ calls and +.Va nr +of queues in the active list. +.It Dv DIOCGETALTQ Fa "struct pfioc_altq" +Returns +.Va altq +number +.Va nr +using +.Va ticket +obtained through a preceding DIOCGETALTQS call. +.It Dv DIOCGETQSTATS Fa "struct pfioc_qstats" +Returns statistics on a queue. +.Bd -literal +struct pfioc_qstats { + u_int32_t ticket; + u_int32_t nr; + void *buf; + int nbytes; + u_int8_t scheduler; +}; +.Ed +.Pp +A pointer to a buffer of statistics +.Va buf +of length +.Va nbytes +for the queue specified by +.Va nr . +.It Dv DIOCCLRSTATES +Clears the state table. +.It Dv DIOCADDSTATE Fa "struct pfioc_state" +Adds a state entry. +.It Dv DIOCGETSTATE Fa "struct pfioc_state" +.Bd -literal +struct pfioc_state { + u_int32_t nr; + struct pf_state state; +}; +.Ed +.Pp +Extracts the entry with the specified number from the state table. +.It Dv DIOCKILLSTATES Fa "struct pfioc_state_kill" +Removes matching entries from the state table. +Returns the number of killed states in psk_af. +.Bd -literal +struct pfioc_state_kill { + int psk_af; + int psk_proto; + struct pf_rule_addr psk_src; + struct pf_rule_addr psk_dst; +}; +.Ed +.It Dv DIOCSETSTATUSIF Fa "struct pfioc_if" +.Bd -literal +struct pfioc_if { + char ifname[IFNAMSIZ]; +}; +.Ed +.Pp +Specifies the interface for which statistics are accumulated. +.It Dv DIOCGETSTATUS Fa "struct pf_status" +.Bd -literal +struct pf_status { + u_int64_t counters[PFRES_MAX]; + u_int64_t fcounters[FCNT_MAX]; + u_int64_t pcounters[2][2][3]; + u_int64_t bcounters[2][2]; + u_int32_t running; + u_int32_t states; + u_int32_t since; + u_int32_t debug; +}; +.Ed +.Pp +Gets the internal packet filter statistics. +.It Dv DIOCCLRSTATUS +Clears the internal packet filter statistics. +.It Dv DIOCNATLOOK Fa "struct pfioc_natlook" +Looks up a state table entry by source and destination addresses and ports. +.Bd -literal +struct pfioc_natlook { + struct pf_addr saddr; + struct pf_addr daddr; + struct pf_addr rsaddr; + struct pf_addr rdaddr; + u_int16_t sport; + u_int16_t dport; + u_int16_t rsport; + u_int16_t rdport; + u_int8_t af; + u_int8_t proto; + u_int8_t direction; +}; +.Ed +.It Dv DIOCSETDEBUG Fa "u_int32_t" +Sets the debug level. +.Bd -literal +enum { PF_DEBUG_NONE=0, PF_DEBUG_URGENT=1, PF_DEBUG_MISC=2 }; +.Ed +.It Dv DIOCGETSTATES Fa "struct pfioc_states" +.Bd -literal +struct pfioc_states { + int ps_len; + union { + caddr_t psu_buf; + struct pf_state *psu_states; + } ps_u; +#define ps_buf ps_u.psu_buf +#define ps_states ps_u.psu_states +}; +.Ed +.It Dv DIOCCHANGERULE Fa "struct pfioc_rule" +Adds or removes the +.Va rule +in the ruleset specified by +.Va rule.action . +.Bd -literal +enum { PF_CHANGE_ADD_HEAD=1, PF_CHANGE_ADD_TAIL=2, + PF_CHANGE_ADD_BEFORE=3, PF_CHANGE_ADD_AFTER=4, + PF_CHANGE_REMOVE=5, PF_CHANGE_GET_TICKET=6 }; +.Ed +.Pp +The type of operation to be performed is indicated by +.Va action . +.Pp +.Va ticket +must be set to the value obtained with PF_CHANGE_GET_TICKET +for all actions except PF_CHANGE_GET_TICKET. +.Va pool_ticket +must be set to the value obtained with the DIOCBEGINADDRS call +for all actions except PF_CHANGE_REMOVE and PF_CHANGE_GET_TICKET. +.Pp +.Va anchor +and +.Va ruleset +indicate which anchor and ruleset the operation applies to. +.Va nr +indicates the rule number against which PF_CHANGE_ADD_BEFORE, +PF_CHANGE_ADD_AFTER or PF_CHANGE_REMOVE actions are applied. +.It Dv DIOCCHANGEADDR Fa "struct pfioc_pooladdr" +Adds or removes a pool address +.Va addr +from a rule specified with +.Va r_action , +.Va r_num , +.Va anchor +and +.Va ruleset . +.It Dv DIOCSETTIMEOUT Fa "struct pfioc_tm" +.Bd -literal +struct pfioc_tm { + int timeout; + int seconds; +}; +.Ed +.It Dv DIOCGETTIMEOUT Fa "struct pfioc_tm" +.It Dv DIOCCLRRULECTRS +Clear per-rule statistics. +.It Dv DIOCSETLIMIT Fa "struct pfioc_limit" +Sets hard limits on the memory pools used by the packet filter. +.Bd -literal +struct pfioc_limit { + int index; + unsigned limit; +}; +.Ed +.It Dv DIOCGETLIMIT Fa "struct pfioc_limit" +.It Dv DIOCRCLRTABLES Fa "struct pfioc_table" +Clear all tables. +All the IOCTLs that manipulate radix tables +use the same structure described below. +For +.Dv DIOCRCLRTABLES, pfrio_ndel contains on exit the number +of tables deleted. +.Bd -literal +struct pfioc_table { + struct pfr_table pfrio_table; + void *pfrio_buffer; + int pfrio_esize; + int pfrio_size; + int pfrio_size2; + int pfrio_nadd; + int pfrio_ndel; + int pfrio_nchange; + int pfrio_flags; + int pfrio_ticket; +}; +#define pfrio_exists pfrio_nadd +#define pfrio_nzero pfrio_nadd +#define pfrio_nmatch pfrio_nadd +#define pfrio_naddr pfrio_size2 +#define pfrio_setflag pfrio_size2 +#define pfrio_clrflag pfrio_nadd +.Ed +.It Dv DIOCRADDTABLES Fa "struct pfioc_table" +Creates one or more tables. +On entry, pfrio_buffer[pfrio_size] contains a table of pfr_table structures. +On exit, pfrio_nadd contains the number of tables effectively created. +.Bd -literal +struct pfr_table { + char pfrt_anchor[PF_ANCHOR_NAME_SIZE]; + char pfrt_ruleset[PF_RULESET_NAME_SIZE]; + char pfrt_name[PF_TABLE_NAME_SIZE]; + u_int32_t pfrt_flags; + u_int8_t pfrt_fback; +}; +.Ed +.It Dv DIOCRDELTABLES Fa "struct pfioc_table" +Deletes one or more tables. +On entry, pfrio_buffer[pfrio_size] contains a table of pfr_table structures. +On exit, pfrio_nadd contains the number of tables effectively deleted. +.It Dv DIOCRGETTABLES Fa "struct pfioc_table" +Get the list of all tables. +On entry, pfrio_buffer[pfrio_size] contains a valid writeable buffer for +pfr_table structures. +On exit, pfrio_size contains the number of tables written into the buffer. +If the buffer is too small, the kernel does not store anything but just +returns the required buffer size, without error. +.It Dv DIOCRGETTSTATS Fa "struct pfioc_table" +Like +.Dv DIOCRGETTABLES , +but returns an array of pfr_tstats structures. +.Bd -literal +struct pfr_tstats { + struct pfr_table pfrts_t; + u_int64_t pfrts_packets + [PFR_DIR_MAX][PFR_OP_TABLE_MAX]; + u_int64_t pfrts_bytes + [PFR_DIR_MAX][PFR_OP_TABLE_MAX]; + u_int64_t pfrts_match; + u_int64_t pfrts_nomatch; + long pfrts_tzero; + int pfrts_cnt; + int pfrts_refcnt[PFR_REFCNT_MAX]; +}; +#define pfrts_name pfrts_t.pfrt_name +#define pfrts_flags pfrts_t.pfrt_flags +.Ed +.It Dv DIOCRCLRTSTATS Fa "struct pfioc_table" +Clears the statistics of one or more tables. +On entry, pfrio_buffer[pfrio_size] contains a table of pfr_table structures. +On exit, pfrio_nzero contains the number of tables effectively cleared. +.It Dv DIOCRCLRADDRS Fa "struct pfioc_table" +Clear all addresses in a table. +On entry, pfrio_table contains the table to clear. +On exit, pfrio_ndel contains the number of addresses removed. +.It Dv DIOCRADDADDRS Fa "struct pfioc_table" +Add one or more addresses to a table. +On entry, pfrio_table contains the table id and pfrio_buffer[pfrio_size] +contains the list of pfr_addr structures to add. +On exit, pfrio_nadd contains the number of addresses effectively added. +.Bd -literal +struct pfr_addr { + union { + struct in_addr _pfra_ip4addr; + struct in6_addr _pfra_ip6addr; + } pfra_u; + u_int8_t pfra_af; + u_int8_t pfra_net; + u_int8_t pfra_not; + u_int8_t pfra_fback; +}; +#define pfra_ip4addr pfra_u._pfra_ip4addr +#define pfra_ip6addr pfra_u._pfra_ip6addr +.Ed +.It Dv DIOCRDELADDRS Fa "struct pfioc_table" +Delete one or more addresses from a table. +On entry, pfrio_table contains the table id and pfrio_buffer[pfrio_size] +contains the list of pfr_addr structures to delete. +On exit, pfrio_ndel contains the number of addresses effectively deleted. +.It Dv DIOCRSETADDRS Fa "struct pfioc_table" +Replace the content of a table by a new address list. +This is the most complicated command, which uses all the structure members. +On entry, pfrio_table contains the table id and pfrio_buffer[pfrio_size] +contains the new list of pfr_addr structures. +In addition to that, if size2 is nonzero, pfrio_buffer[pfrio_size..pfrio_size2] +must be a writeable buffer, into which the kernel can copy the addresses that +have been deleted during the replace operation. +On exit, pfrio_ndel, pfrio_nadd and pfrio_nchange contain the number of +addresses deleted, added and changed by the kernel. +If pfrio_size2 was set on +entry, pfrio_size2 will point to the size of the buffer used, exactly like +.Dv DIOCRGETADDRS . +.It Dv DIOCRGETADDRS Fa "struct pfioc_table" +Get all the addresses of a table. +On entry, pfrio_table contains the table id and pfrio_buffer[pfrio_size] +contains a valid writeable buffer for pfr_addr structures. +On exit, pfrio_size contains the number of addresses written into the buffer. +If the buffer was too small, the kernel does not store anything but just +return the required buffer size, without returning an error. +.It Dv DIOCRGETASTATS Fa "struct pfioc_table" +Like +.Dv DIOCRGETADDRS , +but returns an array of pfr_astats structures. +.Bd -literal +struct pfr_astats { + struct pfr_addr pfras_a; + u_int64_t pfras_packets + [PFR_DIR_MAX][PFR_OP_ADDR_MAX]; + u_int64_t pfras_bytes + [PFR_DIR_MAX][PFR_OP_ADDR_MAX]; + long pfras_tzero; +}; +.Ed +.It Dv DIOCRCLRASTATS Fa "struct pfioc_table" +Clears the statistics of one or more addresses. +On entry, pfrio_table contains the table id and pfrio_buffer[pfrio_size] +contains a table of pfr_addr structures to clear. +On exit, pfrio_nzero contains the number of addresses effectively cleared. +.It Dv DIOCRTSTADDRS Fa "struct pfioc_table" +Test if the given addresses match a table. +On entry, pfrio_table contains the table id and pfrio_buffer[pfrio_size] +contains a table of pfr_addr structures to test. +On exit, the kernel updates the pfr_addr table by setting the pfra_fback +member appropriately. +.It Dv DIOCRSETTFLAGS Fa "struct pfioc_table" +Change the +.Va const +or +.Va persist +flag of a table. +On entry, pfrio_buffer[pfrio_size] contains a table of pfr_table structures, +and pfrio_setflag contains the flags to add, while pfrio_clrflag contains the +flags to remove. +On exit, pfrio_nchange and pfrio_ndel contain the number of tables altered +or deleted by the kernel. +Yes, tables can be deleted if one removes the +.Va persist +flag of an unreferenced table. +.It Dv DIOCRINABEGIN Fa "struct pfioc_table" +Starts a transaction with the inactive set of tables. +Cleans up any leftover from a previously aborted transaction, and returns +a new ticket. +On exit, pfrio_ndel contains the number of leftover table deleted, and +pfrio_ticket contains a valid ticket to use for the following two IOCTLs. +.It Dv DIOCRINACOMMIT Fa "struct pfioc_table" +Commit the inactive set of tables into the active set. +While copying the addresses, do a best effort to keep statistics for +addresses present before and after the commit. +On entry, io->pfrio_ticket takes a valid ticket. +On exit, io->pfrio_nadd and io->pfrio_nchange contain the number of tables +added and altered by the commit operation. +.It Dv DIOCRINADEFINE Fa "struct pfioc_table" +Defines a table in the inactive set. +On entry, pfrio_table contains the table id and pfrio_buffer[pfrio_size] +contains the list of pfr_addr structures to put in the table. +A valid ticket must also be supplied to pfrio_ticket. +On exit, pfrio_nadd contains 0 if the table was already defined in the +inactive list, or 1 if a new table has been created. +pfrio_naddr contains the number of addresses effectively put in the table. +.It Dv DIOCFPFLUSH +Flush the passive OS fingerprint table. +.It Dv DIOCFPADD Fa "struct pf_osfp_ioctl" +.Bd -literal +struct pf_osfp_ioctl { + struct pf_osfp_entry { + SLIST_ENTRY(pf_osfp_entry) fp_entry; + pf_osfp_t fp_os; + char fp_class_nm[PF_OSFP_LEN]; + char fp_version_nm[PF_OSFP_LEN]; + char fp_subtype_nm[PF_OSFP_LEN]; + } fp_os; + u_int16_t fp_mss; + u_int16_t fp_wsize; + u_int16_t fp_psize; + u_int8_t fp_ttl; + u_int8_t fp_wscale; + u_int8_t fp_flags; + int fp_getnum; +}; +.Ed +.Pp +Add a passive OS fingerprint to the table. +Set +.Va fp_os.fp_os +to the packed fingerprint, +.Va fp_os.fp_class_nm +to the name of the class (Linux, Windows, etc), +.Va fp_os.fp_version_nm +to the name of the version (NT, 95, 98), and +.Va fp_os.fp_subtype_nm +to the name of the subtype or patchlevel. +The members +.Va fp_mss , +.Va fp_wsize , +.Va fp_psize , +.Va fp_ttl , +and +.Va fp_wscale +are set to the TCP MSS, the TCP window size, the IP length and the IP TTL of +the TCP SYN packet respectively. +The +.Va fp_flags +member is filled according to the net/pfvar.h include file PF_OSFP_* defines. +The +.Va fp_getnum +is not used with this ioctl. +.Pp +The structure's slack space must be zeroed for correct operation; memset +the whole structure to zero before filling and sending to the kernel. +.It Dv DIOCFPGET Fa "struct pf_osfp_ioctl" +.Bd -literal +struct pf_osfp_ioctl { + struct pf_osfp_entry { + SLIST_ENTRY(pf_osfp_entry) fp_entry; + pf_osfp_t fp_os; + char fp_class_nm[PF_OSFP_LEN]; + char fp_version_nm[PF_OSFP_LEN]; + char fp_subtype_nm[PF_OSFP_LEN]; + } fp_os; + u_int16_t fp_mss; + u_int16_t fp_wsize; + u_int16_t fp_psize; + u_int8_t fp_ttl; + u_int8_t fp_wscale; + u_int8_t fp_flags; + int fp_getnum; +}; +.Ed +.Pp +Get the passive OS fingerprint number +.Va fp_getnum +from the kernel's fingerprint list. +The rest of the structure members will come back filled. +Get the whole list by repeatedly incrementing the +.Va fp_getnum +number until the ioctl returns EBUSY. +.El +.Sh EXAMPLES +The following example demonstrates how to use the DIOCNATLOOK command +to find the internal host/port of a NATed connection. +.Bd -literal +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +u_int32_t +read_address(const char *s) +{ + int a, b, c, d; + + sscanf(s, "%i.%i.%i.%i", &a, &b, &c, &d); + return htonl(a << 24 | b << 16 | c << 8 | d); +} + +void +print_address(u_int32_t a) +{ + a = ntohl(a); + printf("%d.%d.%d.%d", a >> 24 & 255, a >> 16 & 255, + a >> 8 & 255, a & 255); +} + +int +main(int argc, char *argv[]) +{ + struct pfioc_natlook nl; + int dev; + + if (argc != 5) { + printf("%s \\n", + argv[0]); + return 1; + } + + dev = open("/dev/pf", O_RDWR); + if (dev == -1) + err(1, "open(\\"/dev/pf\\") failed"); + + memset(&nl, 0, sizeof(struct pfioc_natlook)); + nl.saddr.v4.s_addr = read_address(argv[1]); + nl.sport = htons(atoi(argv[2])); + nl.daddr.v4.s_addr = read_address(argv[3]); + nl.dport = htons(atoi(argv[4])); + nl.af = AF_INET; + nl.proto = IPPROTO_TCP; + nl.direction = PF_IN; + + if (ioctl(dev, DIOCNATLOOK, &nl)) + err(1, "DIOCNATLOOK"); + + printf("internal host "); + print_address(nl.rsaddr.v4.s_addr); + printf(":%u\\n", ntohs(nl.rsport)); + return 0; +} +.Ed +.Sh SEE ALSO +.Xr ioctl 2 , +.Xr bridge 4 , +.Xr pflog 4 , +.Xr pfsync 4 , +.Xr pfctl 8 +.Sh HISTORY +The +.Nm +packet filtering mechanism first appeared in +.Ox 3.0 . diff --git a/contrib/pf/man/pf.conf.5 b/contrib/pf/man/pf.conf.5 new file mode 100644 index 0000000..9881318 --- /dev/null +++ b/contrib/pf/man/pf.conf.5 @@ -0,0 +1,2486 @@ +.\" $OpenBSD: pf.conf.5,v 1.271 2003/09/02 18:37:08 jmc Exp $ +.\" +.\" Copyright (c) 2002, 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. +.\" +.Dd November 19, 2002 +.Dt PF.CONF 5 +.Os +.Sh NAME +.Nm pf.conf +.Nd packet filter configuration file +.Sh DESCRIPTION +The +.Xr pf 4 +packet filter modifies, drops or passes packets according to rules or +definitions specified in +.Nm pf.conf . +.Sh STATEMENT ORDER +There are seven types of statements in +.Nm pf.conf : +.Bl -tag -width xxxx +.It Cm Macros +User-defined variables may be defined and used later, simplifying +the configuration file. +Macros must be defined before they are referenced in +.Nm pf.conf . +.It Cm Tables +Tables provide a mechanism for increasing the performance and flexibility of +rules with large numbers of source or destination addresses. +.It Cm Options +Options tune the behaviour of the packet filtering engine. +.It Cm Traffic Normalization Li (e.g. Em scrub ) +Traffic normalization protects internal machines against inconsistencies +in Internet protocols and implementations. +.It Cm Queueing +Queueing provides rule-based bandwidth control. +.It Cm Translation Li (Various forms of NAT) +Translation rules specify how addresses are to be mapped or redirected to +other addresses. +.It Cm Packet Filtering +Stateful and stateless packet filtering provides rule-based blocking or +passing of packets. +.El +.Pp +With the exception of +.Cm macros +and +.Cm tables , +the types of statements should be grouped and appear in +.Nm pf.conf +in the order shown above, as this matches the operation of the underlying +packet filtering engine. +By default +.Xr pfctl 8 +enforces this order (see +.Ar set require-order +below). +.Sh MACROS +Much like +.Xr cpp 1 +or +.Xr m4 1 , +macros can be defined that will later be expanded in context. +Macro names must start with a letter, and may contain letters, digits +and underscores. +Macro names may not be reserved words (for example +.Ar pass , +.Ar in , +.Ar out ) . +Macros are not expanded inside quotes. +.Pp +For example, +.Bd -literal -offset indent +ext_if = \&"kue0\&" +all_ifs = \&"{\&" $ext_if lo0 \&"}\&" +pass out on $ext_if from any to any keep state +pass in on $ext_if proto tcp from any to any port 25 keep state +.Ed +.Sh TABLES +Tables are named structures which can hold a collection of addresses and +networks. +Lookups against tables in +.Xr pf 4 +are relatively fast, making a single rule with tables much more efficient, +in terms of +processor usage and memory consumption, than a large number of rules which +differ only in IP address (either created explicitly or automatically by rule +expansion). +.Pp +Tables can be used as the source or destination of filter rules, +.Ar scrub +rules +or +translation rules such as +.Ar nat +or +.Ar rdr +(see below for details on the various rule types). +Tables can also be used for the redirect address of +.Ar nat +and +.Ar rdr +rules and in the routing options of filter rules, but only for +.Ar round-robin +pools. +.Pp +Tables can be defined with any of the following +.Xr pfctl 8 +mechanisms. +As with macros, reserved words may not be used as table names. +.Bl -tag -width "manually" +.It Ar manually +Persistent tables can be manually created with the +.Ar add +or +.Ar replace +option of +.Xr pfctl 8 , +before or after the ruleset has been loaded. +.It Pa pf.conf +Table definitions can be placed directly in this file, and loaded at the +same time as other rules are loaded, atomically. +Table definitions inside +.Nm pf.conf +use the +.Ar table +statement, and are especially useful to define non-persistent tables. +The contents of a pre-existing table defined without a list of addresses +to initialize it is not altered when +.Nm pf.conf +is loaded. +A table initialized with the empty list, +.Li { } , +will be cleared on load. +.El +.Pp +Tables may be defined with the following two attributes: +.Bl -tag -width persist +.It Ar persist +The +.Ar persist +flag forces the kernel to keep the table even when no rules refer to it. +If the flag is not set, the kernel will automatically remove the table +when the last rule referring to it is flushed. +.It Ar const +The +.Ar const +flag prevents the user from altering the contents of the table once it +has been created. +Without that flag, +.Xr pfctl 8 +can be used to add or remove addresses from the table at any time, even +when running with +.Xr securelevel 7 += 2. +.El +.Pp +For example, +.Bd -literal -offset indent +table const { 10/8, 172.16/12, 192.168/16 } +table persist +block on fxp0 from { , } to any +.Ed +.Pp +creates a table called private, to hold RFC 1918 private network +blocks, and a table called badhosts, which is initially empty. +A filter rule is set up to block all traffic coming from addresses listed in +either table. +The private table cannot have its contents changed and the badhosts table +will exist even when no active filter rules reference it. +Addresses may later be added to the badhosts table, so that traffic from +these hosts can be blocked by using +.Bd -literal -offset indent +# pfctl -t badhosts -Tadd 204.92.77.111 +.Ed +.Pp +A table can also be initialized with an address list specified in one or more +external files, using the following syntax: +.Bd -literal -offset indent +table persist file \&"/etc/spammers\&" file \&"/etc/openrelays\&" +block on fxp0 from to any +.Ed +.Pp +The files +.Pa /etc/spammers +and +.Pa /etc/openrelays +list IP addresses, one per line. +Any lines beginning with a # are treated as comments and ignored. +In addition to being specified by IP address, hosts may also be +specified by their hostname. +When the resolver is called to add a hostname to a table, +.Em all +resulting IPv4 and IPv6 addresses are placed into the table. +IP addresses can also be entered in a table by specifying a valid interface +name or the +.Em self +keyword, in which case all addresses assigned to the interface(s) will be +added to the table. +.Sh OPTIONS +.Xr pf 4 +may be tuned for various situations using the +.Ar set +command. +.Bl -tag -width xxxx +.It Ar set timeout +.Pp +.Bl -tag -width interval -compact +.It Ar interval +Interval between purging expired states and fragments. +.It Ar frag +Seconds before an unassembled fragment is expired. +.El +.Pp +When a packet matches a stateful connection, the seconds to live for the +connection will be updated to that of the +.Ar proto.modifier +which corresponds to the connection state. +Each packet which matches this state will reset the TTL. +Tuning these values may improve the performance of the +firewall at the risk of dropping valid idle connections. +.Pp +.Bl -tag -width xxxx -compact +.It Ar tcp.first +The state after the first packet. +.It Ar tcp.opening +The state before the destination host ever sends a packet. +.It Ar tcp.established +The fully established state. +.It Ar tcp.closing +The state after the first FIN has been sent. +.It Ar tcp.finwait +The state after both FINs have been exchanged and the connection is closed. +Some hosts (notably web servers on Solaris) send TCP packets even after closing +the connection. +Increasing +.Ar tcp.finwait +(and possibly +.Ar tcp.closing ) +can prevent blocking of such packets. +.It Ar tcp.closed +The state after one endpoint sends an RST. +.El +.Pp +ICMP and UDP are handled in a fashion similar to TCP, but with a much more +limited set of states: +.Pp +.Bl -tag -width xxxx -compact +.It Ar udp.first +The state after the first packet. +.It Ar udp.single +The state if the source host sends more than one packet but the destination +host has never sent one back. +.It Ar udp.multiple +The state if both hosts have sent packets. +.It Ar icmp.first +The state after the first packet. +.It Ar icmp.error +The state after an ICMP error came back in response to an ICMP packet. +.El +.Pp +Other protocols are handled similarly to UDP: +.Pp +.Bl -tag -width xxxx -compact +.It Ar other.first +.It Ar other.single +.It Ar other.multiple +.El +.Pp +Timeout values can be reduced adaptively as the number of state table +entries grows. +.Pp +.Bl -tag -width xxxx -compact +.It Ar adaptive.start +When the number of state entries exceeds this value, adaptive scaling +begins. +All timeout values are scaled linearly with factor +(adaptive.end - number of states) / (adaptive.end - adaptive.start). +.It Ar adaptive.end +When reaching this number of state entries, all timeout values become +zero, effectively purging all state entries immediately. +This value is used to define the scale factor, it should not actually +be reached (set a lower state limit, see below). +.El +.Pp +These values can be defined both globally and for each rule. +When used on a per-rule basis, the values relate to the number of +states created by the rule, otherwise to the total number of +states. +.Pp +For example: +.Bd -literal -offset indent +set timeout tcp.first 120 +set timeout tcp.established 86400 +set timeout { adaptive.start 6000, adaptive.end 12000 } +set limit states 10000 +.Ed +.Pp +With 9000 state table entries, the timeout values are scaled to 50% +(tcp.first 60, tcp.established 43200). +.Pp +.It Ar set loginterface +Enable collection of packet and byte count statistics for the given interface. +These statistics can be viewed using +.Bd -literal -offset indent +# pfctl -s info +.Ed +.Pp +In this example +.Xr pf 4 +collects statistics on the interface named dc0: +.Bd -literal -offset indent +set loginterface dc0 +.Ed +.Pp +One can disable the loginterface using: +.Bd -literal -offset indent +set loginterface none +.Ed +.Pp +.It Ar set limit +Sets hard limits on the memory pools used by the packet filter. +See +.Xr pool 9 +for an explanation of memory pools. +.Pp +For example, +.Bd -literal -offset indent +set limit states 20000 +.Ed +.Pp +sets the maximum number of entries in the memory pool used by state table +entries (generated by +.Ar keep state +rules) to 20000. +Using +.Bd -literal -offset indent +set limit frags 20000 +.Ed +.Pp +sets the maximum number of entries in the memory pool used for fragment +reassembly (generated by +.Ar scrub +rules) to 20000. +.Pp +These can be combined: +.Bd -literal -offset indent +set limit { states 20000, frags 20000 } +.Ed +.Pp +.It Ar set optimization +Optimize the engine for one of the following network environments: +.Pp +.Bl -tag -width xxxx -compact +.It Ar normal +A normal network environment. +Suitable for almost all networks. +.It Ar high-latency +A high-latency environment (such as a satellite connection). +.It Ar satellite +Alias for +.Ar high-latency . +.It Ar aggressive +Aggressively expire connections. +This can greatly reduce the memory usage of the firewall at the cost of +dropping idle connections early. +.It Ar conservative +Extremely conservative settings. +Avoid dropping legitimate connections at the +expense of greater memory utilization (possibly much greater on a busy +network) and slightly increased processor utilization. +.El +.Pp +For example: +.Bd -literal -offset indent +set optimization aggressive +.Ed +.Pp +.It Ar set block-policy +The +.Ar block-policy +option sets the default behaviour for the packet +.Ar block +action: +.Pp +.Bl -tag -width xxxxxxxx -compact +.It Ar drop +Packet is silently dropped. +.It Ar return +A TCP RST is returned for blocked TCP packets, +an ICMP UNREACHABLE is returned for blocked UDP packets, +and all other packets are silently dropped. +.El +.Pp +For example: +.Bd -literal -offset indent +set block-policy return +.Ed +.It Ar set require-order +By default +.Xr pfctl 8 +enforces an ordering of the statement types in the ruleset to: +.Em options , +.Em normalization , +.Em queueing , +.Em translation , +.Em filtering . +Setting this option to +.Ar no +disables this enforcement. +There may be non-trivial and non-obvious implications to an out of +order ruleset. +Consider carefully before disabling the order enforcement. +.It Ar set fingerprints +Load fingerprints of known operating systems from the given filename. +By default fingerprints of known operating systems are automatically +loaded from +.Xr pf.os 5 +in +.Pa /etc +but can be overridden via this option. +Setting this option may leave a small period of time where the fingerprints +referenced by the currently active ruleset are inconsistent until the new +ruleset finishes loading. +.Pp +For example: +.Pp +.Dl set fingerprints \&"/etc/pf.os.devel\&" +.El +.Sh TRAFFIC NORMALIZATION +Traffic normalization is used to sanitize packet content in such +a way that there are no ambiguities in packet interpretation on +the receiving side. +The normalizer does IP fragment reassembly to prevent attacks +that confuse intrusion detection systems by sending overlapping +IP fragments. +Packet normalization is invoked with the +.Ar scrub +directive. +.Pp +.Ar scrub +has the following options: +.Bl -tag -width xxxx +.It Ar no-df +Clears the +.Ar dont-fragment +bit from a matching IP packet. +Some operating systems are known to generate fragmented packets with the +.Ar dont-fragment +bit set. +This is particularly true with NFS. +.Ar Scrub +will drop such fragmented +.Ar dont-fragment +packets unless +.Ar no-df +is specified. +.Pp +Unfortunately some operating systems also generate their +.Ar dont-fragment +packets with a zero IP identification field. +Clearing the +.Ar dont-fragment +bit on packets with a zero IP ID may cause deleterious results if an +upstream router later fragments the packet. +Using the +.Ar random-id +modifier (see below) is recommended in combination with the +.Ar no-df +modifier to ensure unique IP identifiers. +.It Ar min-ttl +Enforces a minimum TTL for matching IP packets. +.It Ar max-mss +Enforces a maximum MSS for matching TCP packets. +.It Ar random-id +Replaces the IP identification field with random values to compensate +for predictable values generated by many hosts. +This option only applies to outgoing packets that are not fragmented +after the optional fragment reassembly. +.It Ar fragment reassemble +Using +.Ar scrub +rules, fragments can be reassembled by normalization. +In this case, fragments are buffered until they form a complete +packet, and only the completed packet is passed on to the filter. +The advantage is that filter rules have to deal only with complete +packets, and can ignore fragments. +The drawback of caching fragments is the additional memory cost. +But the full reassembly method is the only method that currently works +with NAT. +This is the default behavior of a +.Ar scrub +rule if no fragmentation modifier is supplied. +.It Ar fragment crop +The default fragment reassembly method is expensive, hence the option +to crop is provided. +In this case, +.Xr pf 4 +will track the fragments and cache a small range descriptor. +Duplicate fragments are dropped and overlaps are cropped. +Thus data will only occur once on the wire with ambiguities resolving to +the first occurrence. +Unlike the +.Ar fragment reassemble +modifier, fragments are not buffered, they are passed as soon as they +are received. +The +.Ar fragment crop +reassembly mechanism does not yet work with NAT. +.Pp +.It Ar fragment drop-ovl +This option is similar to the +.Ar fragment crop +modifier except that all overlapping or duplicate fragments will be +dropped, and all further corresponding fragments will be +dropped as well. +.It Ar reassemble tcp +Statefully normalizes TCP connections. +.Ar scrub reassemble tcp +rules may not have the direction (in/out) specified. +.Ar reassemble tcp +performs the following normalizations: +.Pp +.Bl -tag -width timeout -compact +.It ttl +Neither side of the connection is allowed to reduce their IP TTL. +An attacker may send a packet such that it reaches the firewall, affects +the firewall state, and expires before reaching the destination host. +.Ar reassemble tcp +will raise the TTL of all packets back up to the highest value seen on +the connection. +.It timeout modulation +Modern TCP stacks will send a timestamp on every TCP packet and echo +the other endpoint's timestamp back to them. +Many operating systems will merely start the timestamp at zero when +first booted, and increment it several times a second. +The uptime of the host can be deduced by reading the timestamp and multiplying +by a constant. +Also observing several different timestamps can be used to count hosts +behind a NAT device. +And spoofing TCP packets into a connection requires knowing or guessing +valid timestamps. +Timestamps merely need to be monotonically increasing and not derived off a +guessable base time. +.Ar reassemble tcp +will cause +.Ar scrub +to modulate the TCP timestamps with a random number. +.El +.El +.Pp +For example, +.Bd -literal -offset indent +scrub in on $ext_if all fragment reassemble +.Ed +.Sh QUEUEING +Packets can be assigned to queues for the purpose of bandwidth +control. +At least two declarations are required to configure queues, and later +any packet filtering rule can reference the defined queues by name. +During the filtering component of +.Nm pf.conf , +the last referenced +.Ar queue +name is where any packets from +.Ar pass +rules will be queued, while for +.Ar block +rules it specifies where any resulting ICMP or TCP RST +packets should be queued. +The +.Ar scheduler +defines the algorithm used to decide which packets get delayed, dropped, or +sent out immediately. +There are three +.Ar schedulers +currently supported. +.Bl -tag -width xxxx +.It Ar cbq +Class Based Queueing. +.Ar Queues +attached to an interface build a tree, thus each +.Ar queue +can have further child +.Ar queues . +Each queue can have a +.Ar priority +and a +.Ar bandwidth +assigned. +.Ar Priority +mainly controls the time packets take to get sent out, while +.Ar bandwidth +has primarily effects on throughput. +.It Ar priq +Priority Queueing. +.Ar Queues +are flat attached to the interface, thus, +.Ar queues +cannot have further child +.Ar queues . +Each +.Ar queue +has a unique +.Ar priority +assigned, ranging from 0 to 15. +Packets in the +.Ar queue +with the highest +.Ar priority +are processed first. +.It Ar hfsc +Hierarchical Fair Service Curve. +.Ar Queues +attached to an interface build a tree, thus each +.Ar queue +can have further child +.Ar queues . +Each queue can have a +.Ar priority +and a +.Ar bandwidth +assigned. +.Ar Priority +mainly controls the time packets take to get sent out, while +.Ar bandwidth +has primarily effects on throughput. +.El +.Pp +The interfaces on which queueing should be activated are declared using +the +.Ar altq on +declaration. +.Ar altq on +has the following keywords: +.Bl -tag -width xxxx +.It Ar +Queueing is enabled on the named interface. +.It Ar +Specifies which queueing scheduler to use. +Currently supported values +are +.Ar cbq +for Class Based Queueing, +.Ar priq +for Priority Queueing and +.Ar hfsc +for the Hierarchical Fair Service Curve scheduler. +.It Ar bandwidth +The maximum bitrate for all queues on an +interface may be specified using the +.Ar bandwidth +keyword. +The value can be specified as an absolute value or as a +percentage of the interface bandwidth. +When using an absolute value, the suffixes +.Ar b , +.Ar Kb , +.Ar Mb , +and +.Ar Gb +are used to represent bits, kilobits, megabits, and +gigabits per second, respectively. +The value must not exceed the interface bandwidth. +If +.Ar bandwidth +is not specified, the interface bandwidth is used. +.It Ar qlimit +The maximum number of packets held in the queue. +The default is 50. +.It Ar tbrsize +Adjusts the size, in bytes, of the token bucket regulator. +If not specified, heuristics based on the +interface bandwidth are used to determine the size. +.It Ar queue +Defines a list of subqueues to create on an interface. +.El +.Pp +In the following example, the interface dc0 +should queue up to 5 Mbit/s in four second-level queues using +Class Based Queueing. +Those four queues will be shown in a later example. +.Bd -literal -offset indent +altq on dc0 cbq bandwidth 5Mb queue { std, http, mail, ssh } +.Ed +.Pp +Once interfaces are activated for queueing using the +.Ar altq +directive, a sequence of +.Ar queue +directives may be defined. +The name associated with a +.Ar queue +must match a queue defined in the +.Ar altq +directive (e.g. mail), or, except for the +.Ar priq +.Ar scheduler , +in a parent +.Ar queue +declaration. +The following keywords can be used: +.Bl -tag -width xxxx +.It Ar on +Specifies the interface the queue operates on. +If not given, it operates on all matching interfaces. +.It Ar bandwidth +Specifies the maximum bitrate to be processed by the queue. +This value must not exceed the value of the parent +.Ar queue +and can be specified as an absolute value or a percentage of the parent +queue's bandwidth. +The +.Ar priq +scheduler does not support bandwidth specification. +.It Ar priority +Between queues a priority level can be set. +For +.Ar cbq +and +.Ar hfsc , +the range is 0 to 7 and for +.Ar priq , +the range is 0 to 15. +The default for all is 1. +.Ar Priq +queues with a higher priority are always served first. +.Ar Cbq +and +.Ar Hfsc +queues with a higher priority are preferred in the case of overload. +.It Ar qlimit +The maximum number of packets held in the queue. +The default is 50. +.El +.Pp +The +.Ar scheduler +can get additional parameters with +.Ar Ns Li (\& Ar No ) . +Parameters are as follows: +.Bl -tag -width Fl +.It Ar default +Packets not matched by another queue are assigned to this one. +Exactly one default queue is required. +.It Ar red +Enable RED (Random Early Detection) on this queue. +RED drops packets with a probability proportional to the average +queue length. +.It Ar rio +Enables RIO on this queue. +RIO is RED with IN/OUT, thus running +RED two times more than RIO would achieve the same effect. +RIO is currently not supported in the GENERIC kernel. +.It Ar ecn +Enables ECN (Explicit Congestion Notification) on this queue. +ECN implies RED. +.El +.Pp +The +.Ar cbq +.Ar scheduler +supports an additional option: +.Bl -tag -width Fl +.It Ar borrow +The queue can borrow bandwidth from the parent. +.El +.Pp +The +.Ar hfsc +.Ar scheduler +supports some additional options: +.Bl -tag -width Fl +.It Ar realtime +The minimum required bandwidth for the queue. +.It Ar upperlimit +The maximum allowed bandwidth for the queue. +.It Ar linkshare +The bandwidth share of a backlogged queue. +.El +.Pp + is an acronym for +.Ar service curve . +.Pp +The format for service curve specifications is +.Ar ( m1 , d , m2 ) . +.Ar m2 +controls the bandwidth assigned to the queue. +.Ar m1 +and +.Ar d +are optional and can be used to control the initial bandwidth assignment. +For the first +.Ar d +milliseconds the queue gets the bandwidth given as +.Ar m1 , +afterwards the value given in +.Ar m2 . +.Pp +Furthermore, with +.Ar cbq +and +.Ar hfsc , +child queues can be specified as in an +.Ar altq +declaration, thus building a tree of queues using a part of +their parent's bandwidth. +.Pp +Packets can be assigned to queues based on filter rules by using the +.Ar queue +keyword. +Normally only one +.Ar queue +is specified; when a second one is specified it will instead be used for +packets which have a +.Em TOS +of +.Em lowdelay +and for TCP ACKs with no data payload. +.Pp +To continue the previous example, the examples below would specify the +four referenced +queues, plus a few child queues. +Interactive +.Xr ssh 1 +sessions get priority over bulk transfers like +.Xr scp 1 +and +.Xr sftp 1 . +The queues may then be referenced by filtering rules (see +.Sx PACKET FILTERING +below). +.Bd -literal +queue std bandwidth 10% cbq(default) +queue http bandwidth 60% priority 2 cbq(borrow red) \e + { employees, developers } +queue developers bandwidth 75% cbq(borrow) +queue employees bandwidth 15% +queue mail bandwidth 10% priority 0 cbq(borrow ecn) +queue ssh bandwidth 20% cbq(borrow) { ssh_interactive, ssh_bulk } +queue ssh_interactive priority 7 +queue ssh_bulk priority 0 + +block return out on dc0 inet all queue std +pass out on dc0 inet proto tcp from $developerhosts to any port 80 \e + keep state queue developers +pass out on dc0 inet proto tcp from $employeehosts to any port 80 \e + keep state queue employees +pass out on dc0 inet proto tcp from any to any port 22 \e + keep state queue(ssh_bulk, ssh_interactive) +pass out on dc0 inet proto tcp from any to any port 25 \e + keep state queue mail +.Ed +.Sh TRANSLATION +Translation rules modify either the source or destination address of the +packets associated with a stateful connection. +A stateful connection is automatically created to track packets matching +such a rule as long as they are not blocked by the filtering section of +.Nm pf.conf . +The translation engine modifies the specified address and/or port in the +packet, recalculates IP, TCP and UDP checksums as necessary, and passes it to +the packet filter for evaluation. +.Pp +Since translation occurs before filtering the filter +engine will see packets as they look after any +addresses and ports have been translated. Filter rules +will therefore have to filter based on the translated +address and port number. +Packets that match a translation rule are only automatically passed if +the +.Ar pass +modifier is given, otherwise they are +still subject to +.Ar block +and +.Ar pass +rules. +.Pp +The state entry created permits +.Xr pf 4 +to keep track of the original address for traffic associated with that state +and correctly direct return traffic for that connection. +.Pp +Various types of translation are possible with pf: +.Bl -tag -width xxxx +.It Ar binat +A +.Ar binat +rule specifies a bidirectional mapping between an external IP netblock +and an internal IP netblock. +.It Ar nat +A +.Ar nat +rule specifies that IP addresses are to be changed as the packet +traverses the given interface. +This technique allows one or more IP addresses +on the translating host to support network traffic for a larger range of +machines on an "inside" network. +Although in theory any IP address can be used on the inside, it is strongly +recommended that one of the address ranges defined by RFC 1918 be used. +These netblocks are: +.Bd -literal +10.0.0.0 - 10.255.255.255 (all of net 10, i.e., 10/8) +172.16.0.0 - 172.31.255.255 (i.e., 172.16/12) +192.168.0.0 - 192.168.255.255 (i.e., 192.168/16) +.Ed +.It Pa rdr +The packet is redirected to another destination and possibly a +different port. +.Ar rdr +rules can optionally specify port ranges instead of single ports. +rdr ... port 2000:2999 -> ... port 4000 +redirects ports 2000 to 2999 (inclusive) to port 4000. +rdr ... port 2000:2999 -> ... port 4000:* +redirects port 2000 to 4000, 2001 to 4001, ..., 2999 to 4999. +.El +.Pp +In addition to modifying the address, some translation rules may modify +source or destination ports for +.Xr tcp 4 +or +.Xr udp 4 +connections; implicitly in the case of +.Ar nat +rules and explicitly in the case of +.Ar rdr +rules. +Port numbers are never translated with a +.Ar binat +rule. +.Pp +For each packet processed by the translator, the translation rules are +evaluated in sequential order, from first to last. +The first matching rule decides what action is taken. +.Pp +The +.Ar no +option prefixed to a translation rule causes packets to remain untranslated, +much in the same way as +.Ar drop quick +works in the packet filter (see below). +If no rule matches the packet it is passed to the filter engine unmodified. +.Pp +Translation rules apply only to packets that pass through +the specified interface, and if no interface is specified, +translation is applied to packets on all interfaces. +For instance, redirecting port 80 on an external interface to an internal +web server will only work for connections originating from the outside. +Connections to the address of the external interface from local hosts will +not be redirected, since such packets do not actually pass through the +external interface. +Redirections cannot reflect packets back through the interface they arrive +on, they can only be redirected to hosts connected to different interfaces +or to the firewall itself. +.Pp +Note that redirecting external incoming connections to the loopback +address, as in +.Bd -literal -offset indent +rdr on ne3 inet proto tcp to port 8025 -> 127.0.0.1 port 25 +.Ed +.Pp +will effectively allow an external host to connect to daemons +bound solely to the loopback address, circumventing the traditional +blocking of such connections on a real interface. +Unless this effect is desired, any of the local non-loopback addresses +should be used as redirection target instead, which allows external +connections only to daemons bound to this address or not bound to +any address. +.Pp +See +.Sx TRANSLATION EXAMPLES +below. +.Sh PACKET FILTERING +.Xr pf 4 +has the ability to +.Ar block +and +.Ar pass +packets based on attributes of their layer 3 (see +.Xr ip 4 +and +.Xr ip6 4 ) +and layer 4 (see +.Xr icmp 4 , +.Xr icmp6 4 , +.Xr tcp 4 , +.Xr udp 4 ) +headers. +In addition, packets may also be +assigned to queues for the purpose of bandwidth control. +.Pp +For each packet processed by the packet filter, the filter rules are +evaluated in sequential order, from first to last. +The last matching rule decides what action is taken. +.Pp +The following actions can be used in the filter: +.Bl -tag -width xxxx +.It Ar block +The packet is blocked. +There are a number of ways in which a +.Ar block +rule can behave when blocking a packet. +The default behaviour is to +.Ar drop +packets silently, however this can be overridden or made +explicit either globally, by setting the +.Ar block-policy +option, or on a per-rule basis with one of the following options: +.Pp +.Bl -tag -width xxxx -compact +.It Ar drop +The packet is silently dropped. +.It Ar return-rst +This applies only to +.Xr tcp 4 +packets, and issues a TCP RST which closes the +connection. +.It Ar return-icmp +.It Ar return-icmp6 +This causes ICMP messages to be returned for packets which match the rule. +By default this is an ICMP UNREACHABLE message, however this +can be overridden by specifying a message as a code or number. +.It Ar return +This causes a TCP RST to be returned for +.Xr tcp 4 +packets and an ICMP UNREACHABLE for UDP and other packets. +.El +.Pp +Options returning packets have no effect if +.Xr pf 4 +operates on a +.Xr bridge 4 . +.It Ar pass +The packet is passed. +.El +.Pp +If no rule matches the packet, the default action is +.Ar pass . +.Pp +To block everything by default and only pass packets +that match explicit rules, one uses +.Bd -literal -offset indent +block all +.Ed +.Pp +as the first filter rule. +.Pp +See +.Sx FILTER EXAMPLES +below. +.Sh PARAMETERS +The rule parameters specify the packets to which a rule applies. +A packet always comes in on, or goes out through, one interface. +Most parameters are optional. +If a parameter is specified, the rule only applies to packets with +matching attributes. +Certain parameters can be expressed as lists, in which case +.Xr pfctl 8 +generates all needed rule combinations. +.Bl -tag -width xxxx +.It Ar in No or Ar out +This rule applies to incoming or outgoing packets. +If neither +.Ar in +nor +.Ar out +are specified, the rule will match packets in both directions. +.It Ar log +In addition to the action specified, a log message is generated. +All packets for that connection are logged, unless the +.Ar keep state +or +.Ar modulate state +options are specified, in which case only the +packet that establishes the state is logged. +(See +.Ar keep state +and +.Ar modulate state +below). +The logged packets are sent to the +.Xr pflog 4 +interface. +This interface is monitored by the +.Xr pflogd 8 +logging daemon, which dumps the logged packets to the file +.Pa /var/log/pflog +in +.Xr pcap 3 +binary format. +.It Ar log-all +Used with +.Ar keep state +or +.Ar modulate state +rules to force logging of all packets for a connection. +As with +.Ar log , +packets are logged to +.Xr pflog 4 . +.It Ar quick +If a packet matches a rule which has the +.Ar quick +option set, this rule +is considered the last matching rule, and evaluation of subsequent rules +is skipped. +.It Ar on +This rule applies only to packets coming in on, or going out through, this +particular interface. +.It Ar +This rule applies only to packets of this address family. +Supported values are +.Ar inet +and +.Ar inet6 . +.It Ar proto +This rule applies only to packets of this protocol. +Common protocols are +.Xr icmp 4 , +.Xr icmp6 4 , +.Xr tcp 4 , +and +.Xr udp 4 . +For a list of all the protocol name to number mappings used by +.Xr pfctl 8 , +see the file +.Em /etc/protocols . +.It Xo +.Ar from port os +.Ar to port +.Xc +This rule applies only to packets with the specified source and destination +addresses and ports. +.Pp +Addresses can be specified in CIDR notation (matching netblocks), as +symbolic host names or interface names, or as any of the following keywords: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Ar any +Any address. +.It Ar no-route +Any address which is not currently routable. +.It Ar +Any address that matches the given table. +.El +.Pp +Interface names can have modifiers appended: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Ar :network +Translates to the network(s) attached to the interface. +.It Ar :broadcast +Translates to the interface's broadcast address(es). +.El +.Pp +Host name resolution and interface to address translation are done at +ruleset load-time. +When the address of an interface (or host name) changes (under DHCP or PPP, +for instance), the ruleset must be reloaded for the change to be reflected +in the kernel. +Surrounding the interface name in parentheses changes this behaviour. +When the interface name is surrounded by parentheses, the rule is +automatically updated whenever the interface changes its address. +The ruleset does not need to be reloaded. +This is especially useful with +.Ar nat . +.Pp +Ports can be specified either by number or by name. +For example, port 80 can be specified as +.Em www . +For a list of all port name to number mappings used by +.Xr pfctl 8 , +see the file +.Pa /etc/services . +.Pp +Ports and ranges of ports are specified by using these operators: +.Bd -literal -offset indent += (equal) +!= (unequal) +< (less than) +<= (less than or equal) +> (greater than) +>= (greater than or equal) +>< (range) +<> (except range) +.Ed +.Pp +>< and <> +are binary operators (they take two arguments), and the range +does not include the limits. +For instance: +.Bl -tag -width Fl +.It Ar port 2000 >< 2004 +means +.Sq all ports > 2000 and < 2004 , +hence ports 2001, 2002 and 2003. +.It Ar port 2000 <> 2004 +means +.Sq all ports < 2000 or > 2004 , +hence ports 1-1999 and 2005-65535. +.El +.Pp +The operating system of the source host can be specified in the case of TCP +rules with the +.Ar OS +modifier. +See the +.Sx OPERATING SYSTEM FINGERPRINTING +section for more information. +.Pp +The host, port and OS specifications are optional, as in the following examples: +.Bd -literal -offset indent +pass in all +pass in from any to any +pass in proto tcp from any port <= 1024 to any +pass in proto tcp from any to any port 25 +pass in proto tcp from 10.0.0.0/8 port > 1024 \e + to ! 10.1.2.3 port != ssh +pass in proto tcp from any os "OpenBSD" flags S/SA +.Ed +.It Ar all +This is equivalent to "from any to any". +.It Ar group +Similar to +.Ar user , +this rule only applies to packets of sockets owned by the specified group. +.It Ar user +This rule only applies to packets of sockets owned by the specified user. +For outgoing connections initiated from the firewall, this is the user +that opened the connection. +For incoming connections to the firewall itself, this is the user that +listens on the destination port. +For forwarded connections, where the firewall is not a connection endpoint, +the user and group are +.Em unknown . +.Pp +All packets, both outgoing and incoming, of one connection are associated +with the same user and group. +Only TCP and UDP packets can be associated with users; for other protocols +these parameters are ignored. +.Pp +User and group refer to the effective (as opposed to the real) IDs, in +case the socket is created by a setuid/setgid process. +User and group IDs are stored when a socket is created; +when a process creates a listening socket as root (for instance, by +binding to a privileged port) and subsequently changes to another +user ID (to drop privileges), the credentials will remain root. +.Pp +User and group IDs can be specified as either numbers or names. +The syntax is similar to the one for ports. +The value +.Em unknown +matches packets of forwarded connections. +.Em unknown +can only be used with the operators +.Cm = +and +.Cm != . +Other constructs like +.Cm user >= unknown +are invalid. +Forwarded packets with unknown user and group ID match only rules +that explicitly compare against +.Em unknown +with the operators +.Cm = +or +.Cm != . +For instance +.Cm user >= 0 +does not match forwarded packets. +The following example allows only selected users to open outgoing +connections: +.Bd -literal -offset indent +block out proto { tcp, udp } all +pass out proto { tcp, udp } all \e + user { < 1000, dhartmei } keep state +.Ed +.It Ar flags / | / +This rule only applies to TCP packets that have the flags +.Ar +set out of set +.Ar . +Flags not specified in +.Ar +are ignored. +The flags are: (F)IN, (S)YN, (R)ST, (P)USH, (A)CK, (U)RG, (E)CE, and C(W)R. +.Bl -tag -width Fl +.It Ar flags S/S +Flag SYN is set. +The other flags are ignored. +.It Ar flags S/SA +Out of SYN and ACK, exactly SYN may be set. +SYN, SYN+PSH and SYN+RST match, but SYN+ACK, ACK and ACK+RST do not. +This is more restrictive than the previous example. +.It Ar flags /SFRA +If the first set is not specified, it defaults to none. +All of SYN, FIN, RST and ACK must be unset. +.El +.It Ar icmp-type code +.It Ar icmp6-type code +This rule only applies to ICMP or ICMPv6 packets with the specified type +and code. +This parameter is only valid for rules that cover protocols ICMP or +ICMP6. +The protocol and the ICMP type indicator (icmp-type or icmp6-type) +must match. +.It Ar allow-opts +By default, packets which contain IP options are blocked. +When +.Ar allow-opts +is specified for a +.Ar pass +rule, packets that pass the filter based on that rule (last matching) +do so even if they contain IP options. +For packets that match state, the rule that initially created the +state is used. +The implicit +.Ar pass +rule that is used when a packet does not match any rules does not +allow IP options. +.It Ar label +Adds a label (name) to the rule, which can be used to identify the rule. +For instance, +pfctl -s labels +shows per-rule statistics for rules that have labels. +.Pp +The following macros can be used in labels: +.Pp +.Bl -tag -width $srcaddr -compact -offset indent +.It Ar $if +The interface. +.It Ar $srcaddr +The source IP address. +.It Ar $dstaddr +The destination IP address. +.It Ar $srcport +The source port specification. +.It Ar $dstport +The destination port specification. +.It Ar $proto +The protocol name. +.It Ar $nr +The rule number. +.El +.Pp +For example: +.Bd -literal -offset indent +ips = \&"{ 1.2.3.4, 1.2.3.5 }\&" +pass in proto tcp from any to $ips \e + port > 1023 label \&"$dstaddr:$dstport\&" +.Ed +.Pp +expands to +.Bd -literal -offset indent +pass in inet proto tcp from any to 1.2.3.4 \e + port > 1023 label \&"1.2.3.4:>1023\&" +pass in inet proto tcp from any to 1.2.3.5 \e + port > 1023 label \&"1.2.3.5:>1023\&" +.Ed +.Pp +The macro expansion for the +.Ar label +directive occurs only at configuration file parse time, not during runtime. +.It Ar queue | ( , ) +Packets matching this rule will be assigned to the specified queue. +If two queues are given, packets which have a +.Em tos +of +.Em lowdelay +and TCP ACKs with no data payload will be assigned to the second one. +See +.Sx QUEUEING +for setup details. +.Pp +For example: +.Bd -literal -offset indent +pass in proto tcp to port 25 queue mail +pass in proto tcp to port 22 queue(ssh_bulk, ssh_prio) +.Ed +.It Ar tag +Packets matching this rule will be tagged with the +specified string. +The tag acts as an internal marker that can be used to +identify these packets later on. +This can be used, for example, to provide trust between +interfaces and to determine if packets have been +processed by translation rules. +Tags are +.Qq sticky , +meaning that the packet will be tagged even if the rule +is not the last matching rule. +Further matching rules can replace the tag with a +new one but will not remove a previously applied tag. +A packet is only ever assigned one tag at a time. +.Ar pass +rules that use the +.Ar tag +keyword must also use +.Ar keep state . +Packet tagging can be done during +.Ar nat , +.Ar rdr , +or +.Ar binat +rules in addition to filter rules. +.It Ar tagged +Used with filter rules to specify that packets must already +be tagged with the given tag in order to match the rule. +Inverse tag matching can also be done +by specifying the +.Cm !\& +operator before the +.Ar tagged +keyword. +.El +.Sh ROUTING +If a packet matches a rule with a route option set, the packet filter will +route the packet according to the type of route option. +When such a rule creates state, the route option is also applied to all +packets matching the same connection. +.Bl -tag -width xxxx +.It Ar fastroute +The +.Ar fastroute +option does a normal route lookup to find the next hop for the packet. +.It Ar route-to +The +.Ar route-to +option routes the packet to the specified interface with an optional address +for the next hop. +When a +.Ar route-to +rule creates state, only packets that pass in the same direction as the +filter rule specifies will be routed in this way. +Packets passing in the opposite direction (replies) are not affected +and are routed normally. +.It Ar reply-to +The +.Ar reply-to +option is similar to +.Ar route-to , +but routes packets that pass in the opposite direction (replies) to the +specified interface. +Opposite direction is only defined in the context of a state entry, and +.Ar route-to +is useful only in rules that create state. +It can be used on systems with multiple external connections to +route all outgoing packets of a connection through the interface +the incoming connection arrived through (symmetric routing enforcement). +.It Ar dup-to +The +.Ar dup-to +option creates a duplicate of the packet and routes it like +.Ar route-to . +The original packet gets routed as it normally would. +.El +.Sh POOL OPTIONS +For +.Ar nat +and +.Ar rdr +rules, (as well as for the +.Ar route-to , +.Ar reply-to +and +.Ar dup-to +rule options) for which there is a single redirection address which has a +subnet mask smaller than 32 for IPv4 or 128 for IPv6 (more than one IP +address), a variety of different methods for assigning this address can be +used: +.Bl -tag -width xxxx +.It Ar bitmask +The +.Ar bitmask +option applies the network portion of the redirection address to the address +to be modified (source with +.Ar nat , +destination with +.Ar rdr ) . +.It Ar random +The +.Ar random +option selects an address at random within the defined block of addresses. +.It Ar source-hash +The +.Ar source-hash +option uses a hash of the source address to determine the redirection address, +ensuring that the redirection address is always the same for a given source. +An optional key can be specified after this keyword either in hex or as a +string; by default +.Xr pfctl 8 +randomly generates a key for source-hash every time the +ruleset is reloaded. +.It Ar round-robin +The +.Ar round-robin +option loops through the redirection address(es). +.Pp +When more than one redirection address is specified, +.Ar round-robin +is the only permitted pool type. +.It Ar static-port +With +.Ar nat +rules, the +.Ar static-port +option prevents +.Xr pf 4 +from modifying the source port on TCP and UDP packets. +.El +.Sh STATEFUL INSPECTION +.Xr pf 4 +is a stateful packet filter, which means it can track the state of +a connection. +Instead of passing all traffic to port 25, for instance, it is possible +to pass only the initial packet, and then begin to keep state. +Subsequent traffic will flow because the filter is aware of the connection. +.Pp +If a packet matches a +.Ar pass ... keep state +rule, the filter creates a state for this connection and automatically +lets pass all subsequent packets of that connection. +.Pp +Before any rules are evaluated, the filter checks whether the packet +matches any state. +If it does, the packet is passed without evaluation of any rules. +.Pp +States are removed after the connection is closed or has timed out. +.Pp +This has several advantages. +Comparing a packet to a state involves checking its sequence numbers. +If the sequence numbers are outside the narrow windows of expected +values, the packet is dropped. +This prevents spoofing attacks, such as when an attacker sends packets with +a fake source address/port but does not know the connection's sequence +numbers. +.Pp +Also, looking up states is usually faster than evaluating rules. +If there are 50 rules, all of them are evaluated sequentially in O(n). +Even with 50000 states, only 16 comparisons are needed to match a +state, since states are stored in a binary search tree that allows +searches in O(log2 n). +.Pp +For instance: +.Bd -literal -offset indent +block all +pass out proto tcp from any to any flags S/SA keep state +pass in proto tcp from any to any port 25 flags S/SA keep state +.Ed +.Pp +This ruleset blocks everything by default. +Only outgoing connections and incoming connections to port 25 are allowed. +The initial packet of each connection has the SYN +flag set, will be passed and creates state. +All further packets of these connections are passed if they match a state. +.Pp +Specifying +.Ar flags S/SA +restricts state creation to the initial SYN +packet of the TCP handshake. +One can also be less restrictive, and allow state creation from +intermediate +.Pq non-SYN +packets. +This will cause +.Xr pf 4 +to synchronize to existing connections, for instance +if one flushes the state table. +.Pp +For UDP, which is stateless by nature, +.Ar keep state +will create state as well. +UDP packets are matched to states using only host addresses and ports. +.Pp +ICMP messages fall into two categories: ICMP error messages, which always +refer to a TCP or UDP packet, are matched against the referred to connection. +If one keeps state on a TCP connection, and an ICMP source quench message +referring to this TCP connection arrives, it will be matched to the right +state and get passed. +.Pp +For ICMP queries, +.Ar keep state +creates an ICMP state, and +.Xr pf 4 +knows how to match ICMP replies to states. +For example, +.Bd -literal -offset indent +pass out inet proto icmp all icmp-type echoreq keep state +.Ed +.Pp +allows echo requests (such as those created by +.Xr ping 8 ) +out, creates state, and matches incoming echo replies correctly to states. +.Pp +Note: +.Ar nat , binat No and Ar rdr +rules implicitly create state for connections. +.Sh STATE MODULATION +Much of the security derived from TCP is attributable to how well the +initial sequence numbers (ISNs) are chosen. +Some popular stack implementations choose +.Em very +poor ISNs and thus are normally susceptible to ISN prediction exploits. +By applying a +.Ar modulate state +rule to a TCP connection, +.Xr pf 4 +will create a high quality random sequence number for each connection +endpoint. +.Pp +The +.Ar modulate state +directive implicitly keeps state on the rule and is +only applicable to TCP connections. +.Pp +For instance: +.Bd -literal -offset indent +block all +pass out proto tcp from any to any modulate state +pass in proto tcp from any to any port 25 flags S/SA modulate state +.Ed +.Pp +There are two caveats associated with state modulation: +A +.Ar modulate state +rule can not be applied to a pre-existing but unmodulated connection. +Such an application would desynchronize TCP's strict +sequencing between the two endpoints. +Instead, +.Xr pf 4 +will treat the +.Ar modulate state +modifier as a +.Ar keep state +modifier and the pre-existing connection will be inferred without +the protection conferred by modulation. +.Pp +The other caveat affects currently modulated states when the state table +is lost (firewall reboot, flushing the state table, etc...). +.Xr pf 4 +will not be able to infer a connection again after the state table flushes +the connection's modulator. +When the state is lost, the connection may be left dangling until the +respective endpoints time out the connection. +It is possible on a fast local network for the endpoints to start an ACK +storm while trying to resynchronize after the loss of the modulator. +Using a +.Ar flags S/SA +modifier on +.Ar modulate state +rules between fast networks is suggested to prevent ACK storms. +.Sh SYN PROXY +By default, +.Xr pf 4 +passes packets that are part of a +.Xr tcp 4 +handshake between the endpoints. +The +.Ar synproxy state +option can be used to cause +.Xr pf 4 +itself to complete the handshake with the active endpoint, perform a handshake +with the passive endpoint, and then forward packets between the endpoints. +.Pp +No packets are sent to the passive endpoint before the active endpoint has +completed the handshake, hence so-called SYN floods with spoofed source +addresses will not reach the passive endpoint, as the sender can't complete the +handshake. +.Pp +The proxy is transparent to both endpoints, they each see a single +connection from/to the other endpoint. +.Xr pf 4 +choses random initial sequence numbers for both handshakes. +Once the handshakes are completed, the sequence number modulators +(see previous section) are used to translate further packets of the +connection. +Hence, +.Ar synproxy state +includes +.Ar modulate state +and +.Ar keep state . +.Pp +Rules with +.Ar synproxy +will not work if +.Xr pf 4 +operates on a +.Xr bridge 4 . +.Pp +Example: +.Bd -literal -offset indent +pass in proto tcp from any to any port www flags S/SA synproxy state +.Ed +.Sh STATEFUL TRACKING OPTIONS +All three of +.Ar keep state , +.Ar modulate state +and +.Ar synproxy state +support the following options: +.Pp +.Bl -tag -width xxxx -compact +.It Ar max +Limits the number of concurrent states the rule may create. +When this limit is reached, further packets matching the rule that would +create state are dropped, until existing states time out. +.It Ar +Changes the timeout values used for states created by this rule. +For a list of all valid timeout names, see +.Sx OPTIONS +above. +.Pp +Multiple options can be specified, separated by commas: +.Bd -literal +pass in proto tcp from any to any \e + port www flags S/SA keep state \e + (max 100, tcp.established 60, tcp.closing 5) +.Ed +.El +.Sh OPERATING SYSTEM FINGERPRINTING +Passive OS Fingerprinting is a mechanism to inspect nuances of a TCP +connection's initial SYN packet and guess at the host's operating system. +Unfortunately these nuances are easily spoofed by an attacker so the +fingerprint is not useful in making security decisions. +But the fingerprint is typically accurate enough to make policy decisions +upon. +.Pp +The fingerprints may be specified by operating system class, by +version, or by subtype/patchlevel. +The class of an operating system is typically the vender or genre +and would be OpenBSD for the +.Xr pf 4 +firewall itself. +The version of the oldest available OpenBSD release on the main ftp site +would be 2.6 and the fingerprint would be written +.Pp +.Dl \&"OpenBSD 2.6\&" +.Pp +The subtype of an operating system is typically used to describe the +patchlevel if that patch led to changes in the TCP stack behavior. +In the case of OpenBSD, the only subtype is for a fingerprint that was +normalized by the +.Ar no-df +scrub option and would be specified as +.Pp +.Dl \&"OpenBSD 3.3 no-df\&" +.Pp +Fingerprints for most popular operating systems are provided by +.Xr pf.os 5 . +Once +.Xr pf 4 +is running, a complete list of known operating system fingerprints may +be listed by running: +.Pp +.Dl # pfctl -so +.Pp +Filter rules can enforce policy at any level of operating system specification +assuming a fingerprint is present. +Policy could limit traffic to approved operating systems or even ban traffic +from hosts that aren't at the latest service pack. +.Pp +The +.Ar unknown +class can also be used as the fingerprint which will match packets for +which no operating system fingerprint is known. +.Pp +Examples: +.Bd -literal -offset indent +pass out proto tcp from any os OpenBSD keep state +block out proto tcp from any os Doors +block out proto tcp from any os "Doors PT" +block out proto tcp from any os "Doors PT SP3" +block out from any os "unknown" +pass on lo0 proto tcp from any os "OpenBSD 3.3 lo0" keep state +.Ed +.Pp +Operating system fingerprinting is limited only to the TCP SYN packet. +This means that it will not work on other protocols and will not match +a currently established connection. +.Pp +Caveat: operating system fingerprints are occasionally wrong. +There are three problems: an attacker can trivially craft his packets to +appear as any operating system he chooses; +an operating system patch could change the stack behavior and no fingerprints +will match it until the database is updated; +and multiple operating systems may have the same fingerprint. +.Sh BLOCKING SPOOFED TRAFFIC +"Spoofing" is the faking of IP addresses, typically for malicious +purposes. +The +.Ar antispoof +directive expands to a set of filter rules which will block all +traffic with a source IP from the network(s) directly connected +to the specified interface(s) from entering the system through +any other interface. +.Pp +For example, the line +.Bd -literal -offset indent +antispoof for lo0 +.Ed +.Pp +expands to +.Bd -literal -offset indent +block drop in on ! lo0 inet from 127.0.0.1/8 to any +block drop in on ! lo0 inet6 from ::1 to any +.Ed +.Pp +For non-loopback interfaces, there are additional rules to block incoming +packets with a source IP address identical to the interface's IP(s). +For example, assuming the interface wi0 had an IP address of 10.0.0.1 and a +netmask of 255.255.255.0, +the line +.Bd -literal -offset indent +antispoof for wi0 inet +.Ed +.Pp +expands to +.Bd -literal -offset indent +block drop in on ! wi0 inet from 10.0.0.0/24 to any +block drop in inet from 10.0.0.1 to any +.Ed +.Pp +Caveat: Rules created by the +.Ar antispoof +directive interfere with packets sent over loopback interfaces +to local addresses. +One should pass these explicitly. +.Sh FRAGMENT HANDLING +The size of IP datagrams (packets) can be significantly larger than the +the maximum transmission unit (MTU) of the network. +In cases when it is necessary or more efficient to send such large packets, +the large packet will be fragmented into many smaller packets that will each +fit onto the wire. +Unfortunately for a firewalling device, only the first logical fragment will +contain the necessary header information for the subprotocol that allows +.Xr pf 4 +to filter on things such as TCP ports or to perform NAT. +.Pp +Besides the use of +.Ar scrub +rules as described in +.Sx TRAFFIC NORMALIZATION +above, there are three options for handling fragments in the packet filter. +.Pp +One alternative is to filter individual fragments with filter rules. +If no +.Ar scrub +rule applies to a fragment, it is passed to the filter. +Filter rules with matching IP header parameters decide whether the +fragment is passed or blocked, in the same way as complete packets +are filtered. +Without reassembly, fragments can only be filtered based on IP header +fields (source/destination address, protocol), since subprotocol header +fields are not available (TCP/UDP port numbers, ICMP code/type). +The +.Ar fragment +option can be used to restrict filter rules to apply only to +fragments, but not complete packets. +Filter rules without the +.Ar fragment +option still apply to fragments, if they only specify IP header fields. +For instance, the rule +.Bd -literal -offset indent +pass in proto tcp from any to any port 80 +.Ed +.Pp +never applies to a fragment, even if the fragment is part of a TCP +packet with destination port 80, because without reassembly this information +is not available for each fragment. +This also means that fragments cannot create new or match existing +state table entries, which makes stateful filtering and address +translation (NAT, redirection) for fragments impossible. +.Pp +It's also possible to reassemble only certain fragments by specifying +source or destination addresses or protocols as parameters in +.Ar scrub +rules. +.Pp +In most cases, the benefits of reassembly outweigh the additional +memory cost, and it's recommended to use +.Ar scrub +rules to reassemble +all fragments via the +.Ar fragment reassemble +modifier. +.Pp +The memory allocated for fragment caching can be limited using +.Xr pfctl 8 . +Once this limit is reached, fragments that would have to be cached +are dropped until other entries time out. +The timeout value can also be adjusted. +.Pp +Currently, only IPv4 fragments are supported and IPv6 fragments +are blocked unconditionally. +.Sh ANCHORS AND NAMED RULESETS +Besides the main ruleset, +.Xr pfctl 8 +can load named rulesets into +.Ar anchor +attachment points. +An +.Ar anchor +contains a list of named rulesets. +An +.Ar anchor +has a name which specifies where +.Xr pfctl 8 +can be used to attach sub-rulesets. +A named ruleset contains filter and translation rules, like the +main ruleset. +The main ruleset can reference +.Ar anchor +attachment points +using the following kinds +of rules: +.Bl -tag -width xxxx +.It Ar nat-anchor +Evaluates the +.Ar nat +rules of all named rulesets in the specified +.Ar anchor . +.It Ar rdr-anchor +Evaluates the +.Ar rdr +rules of all named rulesets in the specified +.Ar anchor . +.It Ar binat-anchor +Evaluates the +.Ar binat +rules of all named rulesets in the specified +.Ar anchor . +.It Ar anchor +Evaluates the filter rules of all named rulesets in the specified +.Ar anchor . +.It Ar load anchor : from +Loads the rules from the specified file into the named +ruleset +.Ar +attached to the anchor +.Ar . +.El +.Pp +When evaluation of the main ruleset reaches an +.Ar anchor +rule, +.Xr pf 4 +will proceed to evaluate all rules specified in the +named rulesets attached to that +.Ar anchor . +.Pp +Matching filter rules in named rulesets with the +.Ar quick +option and matching translation rules are final and abort the +evaluation of both the rules in the +.Ar anchor +and the main ruleset. +.Pp +Only the main ruleset can contain +.Ar anchor +rules. +.Pp +When an +.Ar anchor +contains more than one named ruleset, they are evaluated +in the alphabetical order of their names. +.Pp +Rules may contain +.Ar anchor +attachment points which do not contain any rules when the main ruleset +is loaded, and later such named rulesets can be manipulated through +.Xr pfctl 8 +without reloading the main ruleset. +For example, +.Bd -literal -offset indent +ext_if = \&"kue0\&" +block on $ext_if all +anchor spam +pass out on $ext_if all keep state +pass in on $ext_if proto tcp from any \e + to $ext_if port smtp keep state +.Ed +.Pp +blocks all packets on the external interface by default, then evaluates +all rulesets in the +.Ar anchor +named "spam", and finally passes all outgoing connections and +incoming connections to port 25. +.Bd -literal -offset indent +# echo \&"block in quick from 1.2.3.4 to any\&" \&| \e + pfctl -a spam:manual -f - +.Ed +.Pp +loads a single ruleset containing a single rule into the +.Ar anchor , +which blocks all packets from a specific address. +.Pp +The named ruleset can also be populated by adding a +.Ar load anchor +rule after the +.Ar anchor +rule: +.Bd -literal -offset indent +anchor spam +load anchor spam:manual from /etc/pf-spam.conf +.Ed +.Pp +When +.Xr pfctl 8 +loads +.Nm pf.conf , +it will also load all the rules from the file +.Pa /etc/pf-spam.conf +into the named ruleset. +.Pp +Optionally, +.Ar anchor +rules can specify the parameter's +direction, interface, address family, protocol and source/destination +address/port +using the same syntax as filter rules. +When parameters are used, the +.Ar anchor +rule is only evaluated for matching packets. +This allows conditional evaluation of named rulesets, like: +.Bd -literal -offset indent +block on $ext_if all +anchor spam proto tcp from any to any port smtp +pass out on $ext_if all keep state +pass in on $ext_if proto tcp from any to $ext_if port smtp keep state +.Ed +.Pp +The rules inside +.Ar anchor +spam are only evaluated for +.Ar tcp +packets with destination port 25. +Hence, +.Bd -literal -offset indent +# echo \&"block in quick from 1.2.3.4 to any" \&| \e + pfctl -a spam:manual -f - +.Ed +.Pp +will only block connections from 1.2.3.4 to port 25. +.Sh TRANSLATION EXAMPLES +This example maps incoming requests on port 80 to port 8080, on +which a daemon is running (because, for example, it is not run as root, +and therefore lacks permission to bind to port 80). +.Bd -literal +# map daemon on 8080 to appear to be on 80 +rdr on ne3 proto tcp from any to any port 80 -> 127.0.0.1 port 8080 +.Ed +.Pp +If the +.Ar pass +modifier is given, packets matching the translation rule are passed without +inspecting the filter rules: +.Bd -literal +rdr pass on ne3 proto tcp from any to any port 80 -> 127.0.0.1 port 8080 +.Ed +.Pp +In the example below, vlan12 is configured as 192.168.168.1; +the machine translates all packets coming from 192.168.168.0/24 to 204.92.77.111 +when they are going out any interface except vlan12. +This has the net effect of making traffic from the 192.168.168.0/24 +network appear as though it is the Internet routable address +204.92.77.111 to nodes behind any interface on the router except +for the nodes on vlan12. +(Thus, 192.168.168.1 can talk to the 192.168.168.0/24 nodes.) +.Bd -literal +nat on ! vlan12 from 192.168.168.0/24 to any -> 204.92.77.111 +.Ed +.Pp +In the example below, fxp1 is the outside interface; the machine sits between a +fake internal 144.19.74.* network, and a routable external IP of 204.92.77.100. +The +.Ar no nat +rule excludes protocol AH from being translated. +.Bd -literal +# NO NAT +no nat on fxp1 proto ah from 144.19.74.0/24 to any +nat on fxp1 from 144.19.74.0/24 to any -> 204.92.77.100 +.Ed +.Pp +In the example below, fxp0 is the internal interface. +Packets bound +for one specific server, as well as those generated by the sysadmins +are not proxied; all other connections are. +.Bd -literal +# NO RDR +no rdr on fxp0 proto { tcp, udp } from any to $server port 80 +no rdr on fxp0 proto { tcp, udp } from $sysadmins to any port 80 +rdr on fxp0 proto { tcp, udp } from any to any port 80 -> 127.0.0.1 port 80 +.Ed +.Pp +This longer example uses both a NAT and a redirection. +Interface kue0 is the outside interface, and its external address is +157.161.48.183. +Interface fxp0 is the inside interface, and we are running +.Xr ftp-proxy 8 , +listening for outbound ftp sessions captured to port 8021. +.Bd -literal +# NAT +# Translate outgoing packets' source addresses (any protocol). +# In this case, any address but the gateway's external address is mapped. +nat on kue0 inet from ! (kue0) to any -> (kue0) + +# NAT PROXYING +# Map outgoing packets' source port to an assigned proxy port instead of +# an arbitrary port. +# In this case, proxy outgoing isakmp with port 500 on the gateway. +nat on kue0 inet proto udp from any port = isakmp to any -> (kue0) \e + port 500 + +# BINAT +# Translate outgoing packets' source address (any protocol). +# Translate incoming packets' destination address to an internal machine +# (bidirectional). +binat on kue0 from 10.1.2.150 to any -> (kue0) + +# RDR +# Translate incoming packets' destination addresses. +# As an example, redirect a TCP and UDP port to an internal machine. +rdr on kue0 inet proto tcp from any to (kue0) port 8080 -> 10.1.2.151 \e + port 22 +rdr on kue0 inet proto udp from any to (kue0) port 8080 -> 10.1.2.151 \e + port 53 + +# RDR +# Translate outgoing ftp control connections to send them to localhost +# for proxying with ftp-proxy(8) running on port 8021. +rdr on fxp0 proto tcp from any to any port 21 -> 127.0.0.1 port 8021 +.Ed +.Pp +In this example, a NAT gateway is set up to translate internal addresses +using a pool of public addresses (192.0.2.16/28) and to redirect +incoming web server connections to a group of web servers on the internal +network. +Interface fxp0 is the external interface. +.Bd -literal +# NAT LOAD BALANCE +# Translate outgoing packets' source addresses using an address pool. +# A given source address is always translated to the same pool address by +# using the source-hash keyword. +nat on fxp0 inet from any to any -> 192.0.2.16/28 source-hash + +# RDR ROUND ROBIN +# Translate incoming web server connections to a group of web servers on +# the internal network. +rdr on fxp0 proto tcp from any to any port 80 \e + -> { 10.1.2.155, 10.1.2.160, 10.1.2.161 } round-robin +.Ed +.Sh FILTER EXAMPLES +.Bd -literal +# The external interface is kue0 +# (157.161.48.183, the only routable address) +# and the private network is 10.0.0.0/8, for which we are doing NAT. + +# use a macro for the interface name, so it can be changed easily +ext_if = \&"kue0\&" + +# normalize all incoming traffic +scrub in on $ext_if all fragment reassemble + +# block and log everything by default +block return log on $ext_if all + +# block anything coming from source we have no back routes for +block in from no-route to any + +# block and log outgoing packets that do not have our address as source, +# they are either spoofed or something is misconfigured (NAT disabled, +# for instance), we want to be nice and do not send out garbage. +block out log quick on $ext_if from ! 157.161.48.183 to any + +# silently drop broadcasts (cable modem noise) +block in quick on $ext_if from any to 255.255.255.255 + +# block and log incoming packets from reserved address space and invalid +# addresses, they are either spoofed or misconfigured, we cannot reply to +# them anyway (hence, no return-rst). +block in log quick on $ext_if from { 10.0.0.0/8, 172.16.0.0/12, \e + 192.168.0.0/16, 255.255.255.255/32 } to any + +# ICMP + +# pass out/in certain ICMP queries and keep state (ping) +# state matching is done on host addresses and ICMP id (not type/code), +# so replies (like 0/0 for 8/0) will match queries +# ICMP error messages (which always refer to a TCP/UDP packet) are +# handled by the TCP/UDP states +pass on $ext_if inet proto icmp all icmp-type 8 code 0 keep state + +# UDP + +# pass out all UDP connections and keep state +pass out on $ext_if proto udp all keep state + +# pass in certain UDP connections and keep state (DNS) +pass in on $ext_if proto udp from any to any port domain keep state + +# TCP + +# pass out all TCP connections and modulate state +pass out on $ext_if proto tcp all modulate state + +# pass in certain TCP connections and keep state (SSH, SMTP, DNS, IDENT) +pass in on $ext_if proto tcp from any to any port { ssh, smtp, domain, \e + auth } flags S/SA keep state + +# pass in data mode connections for ftp-proxy running on this host. +# (see ftp-proxy(8) for details) +pass in on $ext_if proto tcp from any to 157.161.48.183 port >= 49152 \e + flags S/SA keep state + +# Do not allow Windows 9x SMTP connections since they are typically +# a viral worm. Alternately we could limit these OSes to 1 connection each. +block in on $ext_if proto tcp from any os {"Windows 95", "Windows 98"} \e + to any port smtp + +# Packet Tagging + +# three interfaces: $int_if, $ext_if, and $wifi_if (wireless). NAT is +# being done on $ext_if for all outgoing packets. tag packets in on +# $int_if and pass those tagged packets out on $ext_if. all other +# outgoing packets (i.e., packets from the wireless network) are only +# permitted to access port 80. + +pass in on $int_if from any to any tag INTNET keep state +pass in on $wifi_if from any to any keep state + +block out on $ext_if from any to any +pass out quick on $ext_if tagged INTNET keep state +pass out on $ext_if from any to any port 80 keep state + +# tag incoming packets as they are redirected to spamd(8). use the tag +# to pass those packets through the packet filter. + +rdr on $ext_if inet proto tcp from to port smtp \e + tag SPAMD -> 127.0.0.1 port spamd + +block in on $ext_if +pass in on $ext_if inet proto tcp tagged SPAMD keep state +.Ed +.Sh GRAMMAR +Syntax for +.Nm +in BNF: +.Bd -literal +line = ( option | pf-rule | nat-rule | binat-rule | rdr-rule | + antispoof-rule | altq-rule | queue-rule | anchor-rule | + trans-anchors | load-anchors | table-rule ) + +option = "set" ( [ "timeout" ( timeout | "{" timeout-list "}" ) ] | + [ "optimization" [ "default" | "normal" | + "high-latency" | "satellite" | + "aggressive" | "conservative" ] ] + [ "limit" ( limit-item | "{" limit-list "}" ) ] | + [ "loginterface" ( interface-name | "none" ) ] | + [ "block-policy" ( "drop" | "return" ) ] | + [ "require-order" ( "yes" | "no" ) ] + [ "fingerprints" filename ] ) + +pf-rule = action [ ( "in" | "out" ) ] + [ "log" | "log-all" ] [ "quick" ] + [ "on" ifspec ] [ route ] [ af ] [ protospec ] + hosts [ filteropt-list ] + +filteropt-list = filteropt-list filteropt | filteropt +filteropt = user | group | flags | icmp-type | icmp6-type | tos | + ( "keep" | "modulate" | "synproxy" ) "state" + [ "(" state-opts ")" ] | + "fragment" | "no-df" | "min-ttl" number | + "max-mss" number | "random-id" | "reassemble tcp" | + fragmentation | "allow-opts" | + "label" string | "tag" string | [ ! ] "tagged" string + "queue" "(" string | ( string [ [ "," ] string ] ) ")" + +nat-rule = [ "no" ] "nat" [ "pass" ] [ "on" ifspec ] [ af ] + [ protospec ] hosts [ "tag" string ] + [ "->" ( redirhost | "{" redirhost-list "}" ) + [ portspec ] [ pooltype ] [ "static-port" ] ] + +binat-rule = [ "no" ] "binat" [ "pass" ] [ "on" interface-name ] + [ af ] [ "proto" ( proto-name | proto-number ) ] + "from" address [ "/" mask-bits ] "to" ipspec + [ "tag" string ] + [ "->" address [ "/" mask-bits ] ] + +rdr-rule = [ "no" ] "rdr" [ "pass" ] [ "on" ifspec ] [ af ] + [ protospec ] hosts [ "tag" string ] + [ "->" ( redirhost | "{" redirhost-list "}" ) + [ portspec ] [ pooltype ] ] + +antispoof-rule = "antispoof" [ "log" ] [ "quick" ] + "for" ( interface-name | "{" interface-list "}" ) + [ af ] [ "label" string ] + +table-rule = "table" "<" string ">" [ tableopts-list ] +tableopts-list = tableopts-list tableopts | tableopts +tableopts = "persist" | "const" | "file" string | + "{" [ tableaddr-list ] "}" +tableaddr-list = tableaddr-list [ "," ] tableaddr-spec | tableaddr-spec +tableaddr-spec = [ "!" ] tableaddr [ "/" mask-bits ] +tableaddr = hostname | ipv4-dotted-quad | ipv6-coloned-hex | + interface-name | "self" + +altq-rule = "altq on" interface-name queueopts-list + "queue" subqueue +queue-rule = "queue" string [ "on" interface-name ] queueopts-list + subqueue + +anchor-rule = "anchor" string [ ( "in" | "out" ) ] [ "on" ifspec ] + [ af ] [ "proto" ] [ protospec ] [ hosts ] + +trans-anchors = ( "nat-anchor" | "rdr-anchor" | "binat-anchor" ) string + [ "on" ifspec ] [ af ] [ "proto" ] [ protospec ] [ hosts ] + +load-anchor = "load" anchorname:rulesetname "from" filename + +queueopts-list = queueopts-list queueopts | queueopts +queueopts = [ "bandwidth" bandwidth-spec ] | + [ "qlimit" number ] | [ "tbrsize" number ] | + [ "priority" number ] | [ schedulers ] +schedulers = ( cbq-def | priq-def | hfsc-def ) +bandwidth-spec = "number" ( "b" | "Kb" | "Mb" | "Gb" | "%" ) + +action = "pass" | "block" [ "return" ] | "scrub" +return = "drop" | "return" | "return-rst" [ "( ttl" number ")" ] | + "return-icmp" [ "(" icmpcode ["," icmp6code ] ")" ] | + "return-icmp6" [ "(" icmp6code ")" ] +icmpcode = ( icmp-code-name | icmp-code-number ) +icmp6code = ( icmp6-code-name | icmp6-code-number ) + +ifspec = ( [ "!" ] interface-name ) | "{" interface-list "}" +interface-list = [ "!" ] interface-name [ [ "," ] interface-list ] +route = "fastroute" | + ( "route-to" | "reply-to" | "dup-to" ) + ( routehost | "{" routehost-list "}" ) + [ pooltype ] +af = "inet" | "inet6" + +protospec = "proto" ( proto-name | proto-number | + "{" proto-list "}" ) +proto-list = ( proto-name | proto-number ) [ [ "," ] proto-list ] + +hosts = "all" | + "from" ( "any" | "no-route" | "self" | host | + "{" host-list "}" ) [ port ] [ os ] + "to" ( "any" | "no-route" | "self" | host | + "{" host-list "}" ) [ port ] + +ipspec = "any" | host | "{" host-list "}" +host = [ "!" ] ( address [ "/" mask-bits ] | "<" string ">" ) +redirhost = address [ "/" mask-bits ] +routehost = ( interface-name [ address [ "/" mask-bits ] ] ) +address = ( interface-name | "(" interface-name ")" | hostname | + ipv4-dotted-quad | ipv6-coloned-hex ) +host-list = host [ [ "," ] host-list ] +redirhost-list = redirhost [ [ "," ] redirhost-list ] +routehost-list = routehost [ [ "," ] routehost-list ] + +port = "port" ( unary-op | binary-op | "{" op-list "}" ) +portspec = "port" ( number | name ) [ ":" ( "*" | number | name ) ] +os = "os" ( os-name | "{" os-list "}" ) +user = "user" ( unary-op | binary-op | "{" op-list "}" ) +group = "group" ( unary-op | binary-op | "{" op-list "}" ) + +unary-op = [ "=" | "!=" | "<" | "<=" | ">" | ">=" ] + ( name | number ) +binary-op = number ( "<>" | "><" | ":" ) number +op-list = ( unary-op | binary-op ) [ [ "," ] op-list ] + +os-name = operating-system-name +os-list = os-name [ [ "," ] os-list ] + +flags = "flags" [ flag-set ] "/" flag-set +flag-set = [ "F" ] [ "S" ] [ "R" ] [ "P" ] [ "A" ] [ "U" ] [ "E" ] + [ "W" ] + +icmp-type = "icmp-type" ( icmp-type-code | "{" icmp-list "}" ) +icmp6-type = "icmp6-type" ( icmp-type-code | "{" icmp-list "}" ) +icmp-type-code = ( icmp-type-name | icmp-type-number ) + [ "code" ( icmp-code-name | icmp-code-number ) ] +icmp-list = icmp-type-code [ [ "," ] icmp-list ] + +tos = "tos" ( "lowdelay" | "throughput" | "reliability" | + [ "0x" ] number ) + +state-opts = state-opt [ [ "," ] state-opts ] +state-opt = ( "max" number ) | ( timeout ) + +fragmentation = [ "fragment reassemble" | "fragment crop" | + "fragment drop-ovl" ] + +timeout-list = timeout [ [ "," ] timeout-list ] +timeout = ( "tcp.first" | "tcp.opening" | "tcp.established" | + "tcp.closing" | "tcp.finwait" | "tcp.closed" | + "udp.first" | "udp.single" | "udp.multiple" | + "icmp.first" | "icmp.error" | + "other.first" | "other.single" | "other.multiple" | + "frag" | "interval" | + "adaptive.start" | "adaptive.end" ) number + +limit-list = limit-item [ [ "," ] limit-list ] +limit-item = ( "states" | "frags" ) number + +pooltype = ( "bitmask" | "random" | + "source-hash" [ ( hex-key | string-key ) ] | + "round-robin" ) + +subqueue = string | "{" queue-list "}" +queue-list = string [ [ "," ] string ] +cbq-def = "cbq" [ "(" cbq-opt [ [ "," ] cbq-opt ] ")" ] +priq-def = "priq" [ "(" priq-opt [ [ "," ] priq-opt ] ")" ] +hfsc-def = "hfsc" [ "(" hfsc-opt [ [ "," ] hfsc-opt ] ")" ] +cbq-opt = ( "default" | "borrow" | "red" | "ecn" | "rio" ) +priq-opt = ( "default" | "red" | "ecn" | "rio" ) +hfsc-opt = ( "default" | "red" | "ecn" | "rio" | + linkshare-sc | realtime-sc | upperlimit-sc ) +linkshare-sc = "linkshare" sc-spec +realtime-sc = "realtime" sc-spec +upperlimit-sc = "upperlimit" sc-spec +sc-spec = ( bandwidth-spec | + "(" bandwidth-spec number bandwidth-spec ")" ) +.Ed +.Sh FILES +.Bl -tag -width "/etc/protocols" -compact +.It Pa /etc/hosts +Host name database. +.It Pa /etc/pf.conf +Default location of the ruleset file. +.It Pa /etc/pf.os +Default location of OS fingerprints. +.It Pa /etc/protocols +Protocol name database. +.It Pa /etc/services +Service name database. +.It Pa /usr/share/pf +Example rulesets. +.El +.Sh SEE ALSO +.Xr icmp 4 , +.Xr icmp6 4 , +.Xr ip 4 , +.Xr ip6 4 , +.Xr pf 4 , +.Xr tcp 4 , +.Xr udp 4 , +.Xr hosts 5 , +.Xr pf.os 5 , +.Xr protocols 5 , +.Xr services 5 , +.Xr ftp-proxy 8 , +.Xr pfctl 8 , +.Xr pflogd 8 +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 3.0 . diff --git a/contrib/pf/man/pf.os.5 b/contrib/pf/man/pf.os.5 new file mode 100644 index 0000000..485f69a --- /dev/null +++ b/contrib/pf/man/pf.os.5 @@ -0,0 +1,242 @@ +.\" $OpenBSD: pf.os.5,v 1.4 2003/08/28 09:41:23 jmc Exp $ +.\" +.\" Copyright (c) 2003 Mike Frantzen +.\" +.\" 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. +.Dd August 18, 2003 +.Dt PF.OS 5 +.Os +.Sh NAME +.Nm pf.os +.Nd format of the operating system fingerprints file +.Sh DESCRIPTION +The +.Xr pf 4 +firewall and the +.Xr tcpdump 8 +program can both fingerprint the operating system of hosts that +originate an IPv4 TCP connection. +The file consists of newline-separated records, one per fingerprint, +containing nine colon +.Pq Ql \&: +separated fields. +These fields are as follows: +.Pp +.Bl -tag -width Description -offset indent -compact +.It window +The TCP window size. +.It TTL +The IP time to live. +.It df +The presence of the IPv4 don't fragment bit. +.It packet size +The size of the initial TCP packet. +.It TCP options +An ordered list of the TCP options. +.It class +The class of operating system. +.It version +The version of the operating system. +.It subtype +The subtype of patchlevel of the operating system. +.It description +The overall textual description of the operating system, version and subtype. +.El +.Pp +The +.Ar window +field corresponds to the th->th_win field in the TCP header and is the +source host's advertised TCP window size. +It may be between zero and 65,535 inclusive. +The window size may be given as a multiple of a constant by prepending +the size with a percent sign +.Sq % +and the value will be used as a modulus. +Three special values may be used for the window size: +.Pp +.Bl -tag -width xxx -offset indent -compact +.It * +An asterisk will wildcard the value so any window size will match. +.It S +Allow any window size which is a multiple of the maximum segment size (MSS). +.It T +Allow any window size which is a multiple of the maximum transmission unit +(MTU). +.El +.Pp +The +.Ar ttl +value is the initial time to live in the IP header. +The fingerprint code will account for the volatility of the packets's TTL +as it traverses a network. +.Pp +The +.Ar df +bit corresponds to the Don't Fragment bit in an IPv4 header. +It tells intermediate routers not to fragment the packet and is used for +path MTU discovery. +It may be either a zero or a one. +.Pp +The +.Ar packet size +is the literal size of the full IP packet and is a function of all of +the IP and TCP options. +.Pp +The +.Ar TCP options +field is an ordered list of the individual TCP options that appear in the +SYN packet. +Each option is described by a single character separated by a comma and +certain ones may include a value. +The options are: +.Pp +.Bl -tag -width Description -offset indent -compact +.It Mnnn +maximum segment size (MSS) option. +The value is the maximum packet size of the network link which may +include the +.Sq % +modulus or match all MSSes with the +.Sq * +value. +.It N +the NOP option (NO Operation). +.It T[0] +the timestamp option. +Certain operating systems always start with a zero timestamp in which +case a zero value is added to the option; otherwise no value is appended. +.It S +the Selective ACKnowledgement OK (SACKOK) option. +.It Wnnn +window scaling option. +The value is the size of the window scaling which may include the +.Sq % +modulus or match all window scalings with the +.Sq * +value. +.El +.Pp +No TCP options in the fingerprint may be given with a single dot +.Sq \&. . +.Pp +An example of OpenBSD's TCP options are: +.Pp +.Dl M*,N,N,S,N,W0,N,N,T +.Pp +The first option +.Ar M* +is the MSS option and will match all values. +The second and third options +.Ar N +will match two NOPs. +The fourth option +.Ar S +will match the SACKOK option. +The fifth +.Ar N +will match another NOP. +The sixth +.Ar W0 +will match a window scaling option with a zero scaling size. +The seventh and eighth +.Ar N +options will match two NOPs. +And the ninth and final option +.Ar T +will match the timestamp option with any time value. +.Pp +The TCP options in a fingerprint will only match packets with the +exact same TCP options in the same order. +.Pp +The +.Ar class +field is the class, genre or vender of the operating system. +.Pp +The +.Ar version +is the version of the operating system. +It is used to distinguish between different fingerprints of operating +systems of the same class but different versions. +.Pp +The +.Ar subtype +is the subtype or patch level of the operating system version. +It is used to distinguish between different fingerprints of operating +systems of the same class and same version but slightly different +patches or tweaking. +.Pp +The +.Ar description +is a general description of the operating system, its version, +patchlevel and any further useful details. +.Sh EXAMPLES +The fingerprint of a plain +.Ox 3.3 +host is: +.Bd -literal + 16384:64:1:64:M*,N,N,S,N,W0,N,N,T:OpenBSD:3.3::OpenBSD 3.3 +.Ed +.Pp +The fingerprint of an +.Ox 3.3 +host behind a PF scrubbing firewall with a no-df rule would be: +.Bd -literal + 16384:64:0:64:M*,N,N,S,N,W0,N,N,T:OpenBSD:3.3:!df:OpenBSD 3.3 scrub no-df +.Ed +.Pp +An absolutely braindead embedded operating system fingerprint could be: +.Bd -literal + 65535:255:0:40:.:DUMMY:1.1:p3:Dummy embedded OS v1.1p3 +.Ed +.Pp +The +.Xr tcpdump 8 +output of +.Bd -literal + # tcpdump -s128 -c1 -nv 'tcp[13] == 2' + 03:13:48.118526 10.0.0.1.3377 > 10.0.0.0.2: S [tcp sum ok] \e + 534596083:534596083(0) win 57344 (DF) [tos 0x10] \e + (ttl 64, id 11315) +.Ed +.Pp +almost translates into the following fingerprint +.Bd -literal + 57344:64:1:44:M1460: exampleOS:1.0::exampleOS 1.0 +.Ed +.Pp +.Xr tcpdump 8 +does not explicitly give the packet length. +But it can usually be derived by adding the size of the IPv4 header to +the size of the TCP header to the size of the TCP options. +The size of both headers is typically twenty each and the usual +sizes of the TCP options are: +.Pp +.Bl -tag -width timestamp -offset indent -compact +.It mss +four bytes. +.It nop +1 byte. +.It sackOK +two bytes. +.It timestamp +ten bytes. +.It wscale +three bytes. +.El +.Pp +In the above example, the packet size comes out to 44 bytes. +.Sh SEE ALSO +.Xr pf 4 , +.Xr pf.conf 5 , +.Xr pfctl 8 , +.Xr tcpdump 8 diff --git a/contrib/pf/man/pflog.4 b/contrib/pf/man/pflog.4 new file mode 100644 index 0000000..eb7a72e --- /dev/null +++ b/contrib/pf/man/pflog.4 @@ -0,0 +1,88 @@ +.\" $OpenBSD: pflog.4,v 1.4 2003/09/22 04:53:15 jmc Exp $ +.\" +.\" Copyright (c) 2001 Tobias Weingartner +.\" 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. +.\" +.Dd December 10, 2001 +.Dt PFLOG 4 +.Os +.Sh NAME +.Nm pflog +.Nd packet filter logging interface +.Sh SYNOPSIS +.Sy pseudo-device Nm pflog Em +.Sh DESCRIPTION +The +.Nm pflog +interface is the interface the packet filter, +.Xr pf 4 , +copies all the packets to which it has been configured to log. +In this way, all logged packets can easily be monitored in real +time by invoking +.Xr tcpdump 8 +on the +.Nm +interface. +.Pp +Each packet retrieved on this interface has a header associated +with it of length +.Dv PFLOG_HDRLEN . +This header documents the address family, interface name, rule +number, reason, action, and direction of the packet that was logged. +This structure, defined in +.Aq Pa net/if_pflog.h +looks like +.Bd -literal -offset indent +struct pfloghdr { + u_int8_t length; + sa_family_t af; + u_int8_t action; + u_int8_t reason; + char ifname[IFNAMSIZ]; + char ruleset[PF_RULESET_NAME_SIZE]; + u_int32_t rulenr; + u_int32_t subrulenr; + u_int8_t dir; + u_int8_t pad[3]; +}; +.Ed +.Sh EXAMPLES +.Bd -literal -offset indent +# ifconfig pflog0 up +# tcpdump -n -e -ttt -i pflog0 +.Ed +.Sh SEE ALSO +.Xr inet 4 , +.Xr inet6 4 , +.Xr netintro 4 , +.Xr pf 4 , +.Xr ifconfig 8 , +.Xr pflogd 8 , +.Xr tcpdump 8 +.Sh HISTORY +The +.Nm +device first appeared in +.Ox 3.0 . +.\" .Sh BUGS +.\" Anything here? diff --git a/contrib/pf/man/pfsync.4 b/contrib/pf/man/pfsync.4 new file mode 100644 index 0000000..21dd7d5 --- /dev/null +++ b/contrib/pf/man/pfsync.4 @@ -0,0 +1,80 @@ +.\" $OpenBSD: pfsync.4,v 1.6 2003/06/06 10:29:41 jmc Exp $ +.\" +.\" Copyright (c) 2002 Michael Shalayeff +.\" 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 MIND, +.\" 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. +.\" +.Dd November 29, 2002 +.Dt PFSYNC 4 +.Os +.Sh NAME +.Nm pfsync +.Nd packet filter states table logging interface +.Sh SYNOPSIS +.Sy pseudo-device Nm pfsync +.Sh DESCRIPTION +The +.Nm pfsync +interface is the interface to the packet filter, +.Xr pf 4 , +exposing all the changes to the state table. +This allows for both debugging of rulesets and monitoring +for changes in the table by invoking +.Xr tcpdump 8 +on the +.Nm +interface. +.Pp +Each packet retrieved on this interface has a header associated +with it of length +.Dv PFSYNC_HDRLEN . +The header indicates the version of the protocol, address family, +action taken on the following states and the number of state +table entries attached in this packet. +This structure, defined in +.Aq Pa net/if_pfsync.h +looks like: +.Bd -literal -offset indent +struct pfsync_header { + u_int8_t version; + u_int8_t af; + u_int8_t action; + u_int8_t count; +}; +.Ed +.Sh EXAMPLES +.Bd -literal -offset indent +# ifconfig pfsync0 up +# tcpdump -s1500 -evtni pfsync0 +.Ed +.Sh SEE ALSO +.Xr inet 4 , +.Xr inet6 4 , +.Xr netintro 4 , +.Xr pf 4 , +.Xr ifconfig 8 , +.Xr tcpdump 8 +.Sh HISTORY +The +.Nm +device first appeared in +.Ox 3.3 . diff --git a/contrib/pf/pfctl/parse.y b/contrib/pf/pfctl/parse.y new file mode 100644 index 0000000..e633fcb --- /dev/null +++ b/contrib/pf/pfctl/parse.y @@ -0,0 +1,4489 @@ +/* $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 +#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" + +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 != NULL) { + 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/pf_print_state.c b/contrib/pf/pfctl/pf_print_state.c new file mode 100644 index 0000000..58ce23a --- /dev/null +++ b/contrib/pf/pfctl/pf_print_state.c @@ -0,0 +1,279 @@ +/* $OpenBSD: pf_print_state.c,v 1.33 2003/07/06 22:01:28 deraadt 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 +#include +#include +#define TCPSTATES +#include +#include +#include +#include + +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +void print_name(struct pf_addr *, sa_family_t); + +void +print_addr(struct pf_addr_wrap *addr, sa_family_t af, int verbose) +{ + switch(addr->type) { + case PF_ADDR_DYNIFTL: + printf("(%s)", addr->v.ifname); + break; + case PF_ADDR_TABLE: + if (verbose) + if (addr->p.tblcnt == -1) + printf("<%s:*>", addr->v.tblname); + else + printf("<%s:%d>", addr->v.tblname, + addr->p.tblcnt); + else + printf("<%s>", addr->v.tblname); + return; + case PF_ADDR_ADDRMASK: + if (PF_AZERO(&addr->v.a.addr, AF_INET6) && + PF_AZERO(&addr->v.a.mask, AF_INET6)) + printf("any"); + else { + char buf[48]; + + if (inet_ntop(af, &addr->v.a.addr, buf, + sizeof(buf)) == NULL) + printf("?"); + else + printf("%s", buf); + } + break; + case PF_ADDR_NOROUTE: + printf("no-route"); + return; + default: + printf("?"); + return; + } + if (! PF_AZERO(&addr->v.a.mask, af)) { + int bits = unmask(&addr->v.a.mask, af); + + if (bits != (af == AF_INET ? 32 : 128)) + printf("/%d", bits); + } +} + +void +print_name(struct pf_addr *addr, sa_family_t af) +{ + char host[NI_MAXHOST]; + + strlcpy(host, "?", sizeof(host)); + switch (af) { + case AF_INET: { + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr = addr->v4; + getnameinfo((struct sockaddr *)&sin, sin.sin_len, + host, sizeof(host), NULL, 0, NI_NOFQDN); + break; + } + case AF_INET6: { + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_len = sizeof(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = addr->v6; + getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len, + host, sizeof(host), NULL, 0, NI_NOFQDN); + break; + } + } + printf("%s", host); +} + +void +print_host(struct pf_state_host *h, sa_family_t af, int opts) +{ + u_int16_t p = ntohs(h->port); + + if (opts & PF_OPT_USEDNS) + print_name(&h->addr, af); + else { + struct pf_addr_wrap aw; + + memset(&aw, 0, sizeof(aw)); + aw.v.a.addr = h->addr; + if (af == AF_INET) + aw.v.a.mask.addr32[0] = 0xffffffff; + else + memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); + print_addr(&aw, af, opts & PF_OPT_VERBOSE2); + } + + if (p) { + if (af == AF_INET) + printf(":%u", p); + else + printf("[%u]", p); + } +} + +void +print_seq(struct pf_state_peer *p) +{ + if (p->seqdiff) + printf("[%u + %u](+%u)", p->seqlo, p->seqhi - p->seqlo, + p->seqdiff); + else + printf("[%u + %u]", p->seqlo, p->seqhi - p->seqlo); +} + +void +print_state(struct pf_state *s, int opts) +{ + struct pf_state_peer *src, *dst; + struct protoent *p; + int min, sec; + + if (s->direction == PF_OUT) { + src = &s->src; + dst = &s->dst; + } else { + src = &s->dst; + dst = &s->src; + } + if ((p = getprotobynumber(s->proto)) != NULL) + printf("%s ", p->p_name); + else + printf("%u ", s->proto); + if (PF_ANEQ(&s->lan.addr, &s->gwy.addr, s->af) || + (s->lan.port != s->gwy.port)) { + print_host(&s->lan, s->af, opts); + if (s->direction == PF_OUT) + printf(" -> "); + else + printf(" <- "); + } + print_host(&s->gwy, s->af, opts); + if (s->direction == PF_OUT) + printf(" -> "); + else + printf(" <- "); + print_host(&s->ext, s->af, opts); + + printf(" "); + if (s->proto == IPPROTO_TCP) { + if (src->state <= TCPS_TIME_WAIT && + dst->state <= TCPS_TIME_WAIT) + printf(" %s:%s\n", tcpstates[src->state], + tcpstates[dst->state]); + else if (src->state == PF_TCPS_PROXY_SRC || + dst->state == PF_TCPS_PROXY_SRC) + printf(" PROXY:SRC\n"); + else if (src->state == PF_TCPS_PROXY_DST || + dst->state == PF_TCPS_PROXY_DST) + printf(" PROXY:DST\n"); + else + printf(" \n", + src->state, dst->state); + if (opts & PF_OPT_VERBOSE) { + printf(" "); + print_seq(src); + if (src->wscale && dst->wscale) + printf(" wscale %u", + src->wscale & PF_WSCALE_MASK); + printf(" "); + print_seq(dst); + if (src->wscale && dst->wscale) + printf(" wscale %u", + dst->wscale & PF_WSCALE_MASK); + printf("\n"); + } + } else if (s->proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES && + dst->state < PFUDPS_NSTATES) { + const char *states[] = PFUDPS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else if (s->proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES && + dst->state < PFOTHERS_NSTATES) { + /* XXX ICMP doesn't really have state levels */ + const char *states[] = PFOTHERS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else { + printf(" %u:%u\n", src->state, dst->state); + } + + if (opts & PF_OPT_VERBOSE) { + sec = s->creation % 60; + s->creation /= 60; + min = s->creation % 60; + s->creation /= 60; + printf(" age %.2u:%.2u:%.2u", s->creation, min, sec); + sec = s->expire % 60; + s->expire /= 60; + min = s->expire % 60; + s->expire /= 60; + printf(", expires in %.2u:%.2u:%.2u", s->expire, min, sec); + printf(", %u:%u pkts, %u:%u bytes", + s->packets[0], s->packets[1], s->bytes[0], s->bytes[1]); + if (s->anchor.nr != -1) + printf(", anchor %u", s->anchor.nr); + if (s->rule.nr != -1) + printf(", rule %u", s->rule.nr); + printf("\n"); + } +} + +int +unmask(struct pf_addr *m, sa_family_t af) +{ + int i = 31, j = 0, b = 0; + u_int32_t tmp; + + while (j < 4 && m->addr32[j] == 0xffffffff) { + b += 32; + j++; + } + if (j < 4) { + tmp = ntohl(m->addr32[j]); + for (i = 31; tmp & (1 << i); --i) + b++; + } + return (b); +} diff --git a/contrib/pf/pfctl/pfctl.8 b/contrib/pf/pfctl/pfctl.8 new file mode 100644 index 0000000..fb73ce2 --- /dev/null +++ b/contrib/pf/pfctl/pfctl.8 @@ -0,0 +1,506 @@ +.\" $OpenBSD: pfctl.8,v 1.102 2003/09/18 09:18:51 jmc Exp $ +.\" +.\" Copyright (c) 2001 Kjell Wooding. 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. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" 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. +.\" +.Dd November 20, 2002 +.Dt PFCTL 8 +.Os +.Sh NAME +.Nm pfctl +.Nd "control the packet filter (PF) and network address translation (NAT) device" +.Sh SYNOPSIS +.Nm pfctl +.Bk -words +.Op Fl AdeghnNqrROvz +.Op Fl a Ar anchor Ns Op Ar :ruleset +.Op Fl D Ar macro=value +.Op Fl f Ar file +.Op Fl F Ar modifier +.Op Fl k Ar host +.Op Fl s Ar modifier +.Op Fl t Ar table +.Op Fl T Ar command Op Ar address ... +.Op Fl x Ar level +.Ek +.Sh DESCRIPTION +The +.Nm +utility communicates with the packet filter device using the +ioctl interface described in +.Xr pf 4 . +It allows ruleset and parameter configuration and retrieval of status +information from the packet filter. +.Pp +Packet filtering restricts the types of packets that pass through +network interfaces entering or leaving the host based on filter +rules as described in +.Xr pf.conf 5 . +The packet filter can also replace addresses and ports of packets. +Replacing source addresses and ports of outgoing packets is called +NAT (Network Address Translation) and is used to connect an internal +network (usually reserved address space) to an external one (the +Internet) by making all connections to external hosts appear to +come from the gateway. +Replacing destination addresses and ports of incoming packets +is used to redirect connections to different hosts and/or ports. +A combination of both translations, bidirectional NAT, is also +supported. +Translation rules are described in +.Xr pf.conf 5 . +.Pp +When the variable pf is set to YES in +.Xr rc.conf 8 , +the rule file specified with the variable pf_rules +is loaded automatically by the +.Xr rc 8 +scripts and the packet filter is enabled. +.Pp +The packet filter does not itself forward packets between interfaces. +Forwarding can be enabled by setting the +.Xr sysctl 8 +variables +.Em net.inet.ip.forwarding +and/or +.Em net.inet6.ip6.forwarding , +to 1. +Set them permanently in +.Xr sysctl.conf 5 . +.Pp +The +.Nm +utility provides several commands. +The options are as follows: +.Bl -tag -width Ds +.It Fl a Ar anchor Ns Op Ar :ruleset +Apply flags +.Fl f , +.Fl F +and +.Fl s +only to the rules in the specified +.Ar anchor +and optional named ruleset +.Ar ruleset . +In addition to the main ruleset, +.Nm +can load and manipulate additional rulesets by name. +Named rulesets are attached at +.Ar anchor +points, which are also referenced by name. +Evaluation of +.Ar anchor +rules from the main ruleset is described in +.Xr pf.conf 5 . +For example, to show all filter rules inside anchor +.Li foo : +.Bd -literal -offset indent +# pfctl -a foo -s rules +.Ed +.Pp +Private tables can also be put inside subrulesets, either by having table +statements in the +.Xr pf.conf 5 +file that is loaded in the anchor, or by using regular table commands as in: +.Bd -literal -offset indent +# pfctl -a foo:bar -t mytable -T add 1.2.3.4 5.6.7.8 +.Ed +.Pp +When a rule referring to a table is loaded in an anchor, the rule will use the +private table if one is defined, and then fallback to the table defined in the +main ruleset, if there is one. +This is similar to C rules for variables. +It is possible to create distinct tables with the same name in the global +ruleset and in an anchor, but this is often bad design and a warning will be +issued in that case. +.It Fl A +Load only the queue rules present in the rule file. +Other rules and options are ignored. +.It Fl d +Disable the packet filter. +.It Fl D Ar macro=value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the ruleset. +.It Fl e +Enable the packet filter. +.It Fl f Ar file +Load the rules contained in +.Ar file . +This +.Ar file +may contain macros, tables, options, and normalization, queueing, +translation, and filtering rules. +With the exception of macros and tables, the statements must appear in that +order. +.It Fl F Ar modifier +Flush the filter parameters specified by +.Ar modifier +(may be abbreviated): +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl F Ar nat +Flush the NAT rules. +.It Fl F Ar queue +Flush the queue rules. +.It Fl F Ar rules +Flush the filter rules. +.It Fl F Ar state +Flush the state table (NAT and filter). +.It Fl F Ar info +Flush the filter information (statistics that are not bound to rules). +.It Fl F Ar Tables +Flush the tables. +.It Fl F Ar osfp +Flush the passive operating system fingerprints. +.It Fl F Ar all +Flush all of the above. +.El +.It Fl g +Include output helpful for debugging. +.It Fl k Ar host +Kill all of the state entries originating from the specified +.Ar host . +A second +.Fl k Ar host +option may be specified, which will kill all the state entries +from the first +.Ar host +to the second +.Ar host . +For example, to kill all of the state entries originating from +.Li host : +.Bd -literal -offset indent +# pfctl -k host +.Ed +.Pp +To kill all of the state entries from +.Li host1 +to +.Li host2 : +.Bd -literal -offset indent +# pfctl -k host1 -k host2 +.Ed +.It Fl h +Help. +.It Fl n +Do not actually load rules, just parse them. +.It Fl N +Load only the NAT rules present in the rule file. +Other rules and options are ignored. +.It Fl q +Only print errors and warnings. +.It Fl r +Perform reverse DNS lookups on states when displaying them. +.It Fl R +Load only the filter rules present in the rule file. +Other rules and options are ignored. +.It Fl O +Load only the options present in the rule file. +Other rules and options are ignored. +.It Fl s Ar modifier +Show the filter parameters specified by +.Ar modifier +(may be abbreviated): +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl s Ar nat +Show the currently loaded NAT rules. +.It Fl s Ar queue +Show the currently loaded queue rules. +When used together with +.Fl v , +per-queue statistics are also shown. +When used together with +.Fl v v , +.Nm +will loop and show updated queue statistics every five seconds, including +measured bandwidth and packets per second. +.It Fl s Ar rules +Show the currently loaded filter rules. +When used together with +.Fl v , +the per-rule statistics (number of evaluations, +packets and bytes) are also shown. +Note that the 'skip step' optimization done automatically by the kernel +will skip evaluation of rules where possible. +Packets passed statefully are counted in the rule that created the state +(even though the rule isn't evaluated more than once for the entire +connection). +.It Fl s Ar Anchors +Show the currently loaded anchors. +If +.Fl a Ar anchor +is specified as well, the named rulesets currently loaded in the specified +anchor are shown instead. +.It Fl s Ar state +Show the contents of the state table. +.It Fl s Ar info +Show filter information (statistics and counters). +.It Fl s Ar labels +Show per-rule statistics (label, evaluations, packets, bytes) of +filter rules with labels, useful for accounting. +.It Fl s Ar timeouts +Show the current global timeouts. +.It Fl s Ar memory +Show the current pool memory hard limits. +.It Fl s Ar Tables +Show the list of tables. +.It Fl s Ar osfp +Show the list of operating system fingerprints. +Can be used in combination with +.Fl o Ar file +to list the fingerprints in a +.Xr pf.os 5 +file. +.It Fl s Ar all +Show all of the above. +.El +.It Fl t Ar table +Specify the name of the table. +.It Fl T Ar command Op Ar address ... +Specify the +.Ar command +(may be abbreviated) to apply to the table. +Commands include: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl T Ar kill +Kill a table. +.It Fl T Ar flush +Flush all addresses of a table. +.It Fl T Ar add +Add one or more addresses in a table. +Automatically create a nonexisting table. +.It Fl T Ar delete +Delete one or more addresses from a table. +.It Fl T Ar replace +Replace the addresses of the table. +Automatically create a nonexisting table. +.It Fl T Ar show +Show the content (addresses) of a table. +.It Fl T Ar test +Test if the given addresses match a table. +.It Fl T Ar zero +Clear all the statistics of a table. +.It Fl T Ar load +Load only the table definitions from +.Xr pf.conf 5 . +This is used in conjunction with the +.Fl f +flag, as in: +.Bd -literal -offset indent +# pfctl -Tl -f pf.conf +.Ed +.El +.Pp +For the +.Ar add , +.Ar delete , +.Ar replace +and +.Ar test +commands, the list of addresses can be specified either directly on the command +line and/or in an unformatted text file, using the +.Fl f +flag. +Comments starting with a "#" are allowed in the text file. +With these commands, the +.Fl v +flag can also be used once or twice, in which case +.Nm pfctl +will print the +detailed result of the operation for each individual address, prefixed by +one of the following letters: +.Pp +.Bl -tag -width XXX -compact +.It A +The address/network has been added. +.It C +The address/network has been changed (negated). +.It D +The address/network has been deleted. +.It M +The address matches (test operation only). +.It X +The address/network is duplicated and therefore ignored. +.It Y +The address/network cannot be added/deleted due to conflicting "!" attribute. +.It Z +The address/network has been cleared (statistics). +.El +.Pp +Each table maintains a set of counters that can be retrieved using the +.Fl v +flag of +.Nm pfctl . +For example, the following commands define a wide open firewall which will keep +track of packets going to or coming from the +.Ox +ftp server. +The following commands configure the firewall and send 10 pings to the ftp +server: +.Bd -literal -offset indent +# printf \&"table { ftp.openbsd.org }\en \e +\ \ pass out to keep state\en" \&| pfctl -f- +# ping -qc10 ftp.openbsd.org +.Ed +.Pp +We can now use the table +.Ar show +command to output, for each address and packet direction, the number of packets +and bytes that are being passed or blocked by rules referencing the table. +The time at which the current accounting started is also shown with the +.Ar Cleared +line. +.Bd -literal -offset indent +# pfctl -t test -vTshow +\ \ \ 129.128.5.191 +\ \ \ \ Cleared: \ \ \ \ Thu Feb 13 18:55:18 2003 +\ \ \ \ In/Block: \ \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ In/Pass: \ \ \ \ [ Packets: 10 \ \ \ \ \ \ Bytes: 840 \ \ \ \ \ ] +\ \ \ \ Out/Block: \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ Out/Pass: \ \ \ [ Packets: 10 \ \ \ \ \ \ Bytes: 840 \ \ \ \ \ ] +.Ed +.Pp +Similarly, it is possible to view global information about the tables +by using the +.Fl v +modifier twice and the +.Ar show Tables +command. +This will display the number of addresses on each table, +the number of rules which reference the table, and the global +packet statistics for the whole table: +.Bd -literal -offset indent +# pfctl -vvsTables +--a-r- test +\ \ \ \ Addresses: \ \ 1 +\ \ \ \ Cleared: \ \ \ \ Thu Feb 13 18:55:18 2003 +\ \ \ \ References: \ [ Anchors: 0 \ \ \ \ \ \ \ Rules: 1 \ \ \ \ \ \ \ ] +\ \ \ \ Evaluations: [ NoMatch: 3496 \ \ \ \ Match: 1 \ \ \ \ \ \ \ ] +\ \ \ \ In/Block: \ \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ In/Pass: \ \ \ \ [ Packets: 10 \ \ \ \ \ \ Bytes: 840 \ \ \ \ \ ] +\ \ \ \ In/XPass: \ \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ Out/Block: \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +\ \ \ \ Out/Pass: \ \ \ [ Packets: 10 \ \ \ \ \ \ Bytes: 840 \ \ \ \ \ ] +\ \ \ \ Out/XPass: \ \ [ Packets: 0 \ \ \ \ \ \ \ Bytes: 0 \ \ \ \ \ \ \ ] +.Ed +.Pp +As we can see here, only one packet - the initial ping request - matched the +table; but all packets passing as the result of the state are correctly +accounted for. +Reloading the table(s) or ruleset will not affect packet accounting in any way. +The two +.Ar XPass +counters are incremented instead of the +.Ar Pass +counters when a \&"stateful\&" packet is passed but doesn't match the table +anymore. +This will happen in our example if someone flushes the table while the ping +command is running. +.Pp +When used with a single +.Fl v , +.Nm pfctl +will only display the first line containing the table flags and name. +The flags are defined as follows: +.Pp +.Bl -tag -width XXX -compact +.It c +For constant tables, which cannot be altered outside +.Xr pf.conf 5 . +.It p +For persistent tables, which don't get automatically flushed when no rules +refer to them. +.It a +For tables which are part of the +.Ar active +tableset. +Tables without this flag do not really exist, cannot contain addresses, and are +only listed if the +.Fl g +flag is given. +.It i +For tables which are part of the +.Ar inactive +tableset. +This flag can only be witnessed briefly during the loading of +.Xr pf.conf 5 . +.It r +For tables which are referenced (used) by rules. +.It h +This flag is set when a table in the main ruleset is hidden by one or more +tables of the same name in sub-rulesets (anchors). +.El +.It Fl v +Produce more verbose output. +A second use of +.Fl v +will produce even more verbose output including ruleset warnings. +See previous section for its effect on table commands. +.It Fl x Ar level +Set the debug +.Ar level +(may be abbreviated) to one of the following: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl x Ar none +Don't generate debug messages. +.It Fl x Ar urgent +Generate debug messages only for serious errors. +.It Fl x Ar misc +Generate debug messages for various errors. +.It Fl x Ar loud +Generate debug messages for common conditions. +.El +.It Fl z +Clear per-rule statistics. +.El +.Sh FILES +.Bl -tag -width "/etc/pf.conf" -compact +.It Pa /etc/pf.conf +Packet filter rules file. +.El +.Sh SEE ALSO +.Xr pf 4 , +.Xr pf.conf 5 , +.Xr pf.os 5 , +.Xr sysctl.conf 5 , +.Xr ftp-proxy 8 , +.Xr rc 8 , +.Xr rc.conf 8 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +program and the +.Xr pf 4 +filter mechanism first appeared in +.Ox 3.0 . diff --git a/contrib/pf/pfctl/pfctl.c b/contrib/pf/pfctl/pfctl.c new file mode 100644 index 0000000..0e52476 --- /dev/null +++ b/contrib/pf/pfctl/pfctl.c @@ -0,0 +1,1626 @@ +/* $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 +#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"); + 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", + rule->evaluations, rule->packets, + 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", + pr.rule.evaluations, pr.rule.packets, + 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", + pr.rule.evaluations, pr.rule.packets, + 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) +{ + 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); +} + +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; + altqsupport = 1; + } + + 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 new file mode 100644 index 0000000..2149ac1 --- /dev/null +++ b/contrib/pf/pfctl/pfctl.h @@ -0,0 +1,115 @@ +/* $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. + * + */ + +#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 *); + +#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 new file mode 100644 index 0000000..efe92ab --- /dev/null +++ b/contrib/pf/pfctl/pfctl_altq.c @@ -0,0 +1,1210 @@ +/* $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 +#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); + +u_int32_t getifspeed(char *); +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] != NULL) { + 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 + if ((rate = getifspeed(pa->ifname)) == 0) { + 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 + */ +#define INFINITY HUGE_VAL /* positive infinity defined in */ + +/* 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); +} + +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); +} + +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_osfp.c b/contrib/pf/pfctl/pfctl_osfp.c new file mode 100644 index 0000000..9585314 --- /dev/null +++ b/contrib/pf/pfctl/pfctl_osfp.c @@ -0,0 +1,1093 @@ +/* $OpenBSD: pfctl_osfp.c,v 1.4 2003/08/27 17:42:00 frantzen Exp $ */ + +/* + * Copyright (c) 2003 Mike Frantzen + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" + +#ifndef MIN +# define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ +#ifndef MAX +# define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + + +#if 0 +# define DEBUG(fp, str, v...) \ + fprintf(stderr, "%s:%s:%s " str "\n", (fp)->fp_os.fp_class_nm, \ + (fp)->fp_os.fp_version_nm, (fp)->fp_os.fp_subtype_nm , ## v); +#else +# define DEBUG(fp, str, v...) ((void)0) +#endif + + +struct name_entry; +LIST_HEAD(name_list, name_entry); +struct name_entry { + LIST_ENTRY(name_entry) nm_entry; + int nm_num; + char nm_name[PF_OSFP_LEN]; + + struct name_list nm_sublist; + int nm_sublist_num; +}; +struct name_list classes = LIST_HEAD_INITIALIZER(&classes); +int class_count; +int fingerprint_count; + +void add_fingerprint(int, int, struct pf_osfp_ioctl *); +struct name_entry *fingerprint_name_entry(struct name_list *, char *); +void pfctl_flush_my_fingerprints(struct name_list *); +char *get_field(char **, size_t *, int *); +int get_int(char **, size_t *, int *, int *, const char *, + int, int, const char *, int); +int get_str(char **, size_t *, char **, const char *, int, + const char *, int); +int get_tcpopts(const char *, int, const char *, + pf_tcpopts_t *, int *, int *, int *, int *, int *, + int *); +void import_fingerprint(struct pf_osfp_ioctl *); +const char *print_ioctl(struct pf_osfp_ioctl *); +void print_name_list(int, struct name_list *, const char *); +void sort_name_list(int, struct name_list *); +struct name_entry *lookup_name_list(struct name_list *, const char *); + +/* Load fingerprints from a file */ +int +pfctl_file_fingerprints(int dev, int opts, const char *fp_filename) +{ + FILE *in; + char *line; + size_t len; + int i, lineno = 0; + int window, w_mod, ttl, df, psize, p_mod, mss, mss_mod, wscale, + wscale_mod, optcnt, ts0; + pf_tcpopts_t packed_tcpopts; + char *class, *version, *subtype, *desc, *tcpopts; + struct pf_osfp_ioctl fp; + + pfctl_flush_my_fingerprints(&classes); + + if ((in = fopen(fp_filename, "r")) == NULL) { + warn("fopen(%s)", fp_filename); + return (1); + } + class = version = subtype = desc = tcpopts = NULL; + + if ((opts & PF_OPT_NOACTION) == 0) + pfctl_clear_fingerprints(dev, opts); + + while ((line = fgetln(in, &len)) != NULL) { + lineno++; + if (class) + free(class); + if (version) + free(version); + if (subtype) + free(subtype); + if (desc) + free(desc); + if (tcpopts) + free(tcpopts); + class = version = subtype = desc = tcpopts = NULL; + memset(&fp, 0, sizeof(fp)); + + /* Chop off comment */ + for (i = 0; i < len; i++) + if (line[i] == '#') { + len = i; + break; + } + /* Chop off whitespace */ + while (len > 0 && isspace(line[len - 1])) + len--; + while (len > 0 && isspace(line[0])) { + len--; + line++; + } + if (len == 0) + continue; + +#define T_DC 0x01 /* Allow don't care */ +#define T_MSS 0x02 /* Allow MSS multiple */ +#define T_MTU 0x04 /* Allow MTU multiple */ +#define T_MOD 0x08 /* Allow modulus */ + +#define GET_INT(v, mod, n, ty, mx) \ + get_int(&line, &len, &v, mod, n, ty, mx, fp_filename, lineno) +#define GET_STR(v, n, mn) \ + get_str(&line, &len, &v, n, mn, fp_filename, lineno) + + if (GET_INT(window, &w_mod, "window size", T_DC|T_MSS|T_MTU| + T_MOD, 0xffff) || + GET_INT(ttl, NULL, "ttl", 0, 0xff) || + GET_INT(df, NULL, "don't fragment frag", 0, 1) || + GET_INT(psize, &p_mod, "overall packet size", T_MOD|T_DC, + 8192) || + GET_STR(tcpopts, "TCP Options", 1) || + GET_STR(class, "OS class", 1) || + GET_STR(version, "OS version", 0) || + GET_STR(subtype, "OS subtype", 0) || + GET_STR(desc, "OS description", 2)) + continue; + if (get_tcpopts(fp_filename, lineno, tcpopts, &packed_tcpopts, + &optcnt, &mss, &mss_mod, &wscale, &wscale_mod, &ts0)) + continue; + if (len != 0) { + fprintf(stderr, "%s:%d excess field\n", fp_filename, + lineno); + continue; + } + + fp.fp_ttl = ttl; + if (df) + fp.fp_flags |= PF_OSFP_DF; + switch (w_mod) { + case 0: + break; + case T_DC: + fp.fp_flags |= PF_OSFP_WSIZE_DC; + break; + case T_MSS: + fp.fp_flags |= PF_OSFP_WSIZE_MSS; + break; + case T_MTU: + fp.fp_flags |= PF_OSFP_WSIZE_MTU; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSIZE_MOD; + break; + } + fp.fp_wsize = window; + + switch (p_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_PSIZE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_PSIZE_MOD; + } + fp.fp_psize = psize; + + + switch (wscale_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_WSCALE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSCALE_MOD; + } + fp.fp_wscale = wscale; + + switch (mss_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_MSS_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_MSS_MOD; + break; + } + fp.fp_mss = mss; + + fp.fp_tcpopts = packed_tcpopts; + fp.fp_optcnt = optcnt; + if (ts0) + fp.fp_flags |= PF_OSFP_TS0; + + if (class[0] == '@') + fp.fp_os.fp_enflags |= PF_OSFP_GENERIC; + if (class[0] == '*') + fp.fp_os.fp_enflags |= PF_OSFP_NODETAIL; + + if (class[0] == '@' || class[0] == '*') + strlcpy(fp.fp_os.fp_class_nm, class + 1, + sizeof(fp.fp_os.fp_class_nm)); + else + strlcpy(fp.fp_os.fp_class_nm, class, + sizeof(fp.fp_os.fp_class_nm)); + strlcpy(fp.fp_os.fp_version_nm, version, + sizeof(fp.fp_os.fp_version_nm)); + strlcpy(fp.fp_os.fp_subtype_nm, subtype, + sizeof(fp.fp_os.fp_subtype_nm)); + + add_fingerprint(dev, opts, &fp); + } + + if (class) + free(class); + if (version) + free(version); + if (subtype) + free(subtype); + if (desc) + free(desc); + + fclose(in); + + if (opts & PF_OPT_VERBOSE2) + printf("Loaded %d passive OS fingerprints\n", + fingerprint_count); + return (0); +} + +/* flush the kernel's fingerprints */ +void +pfctl_clear_fingerprints(int dev, int opts) +{ + if (ioctl(dev, DIOCOSFPFLUSH)) + err(1, "DIOCOSFPFLUSH"); +} + +/* flush pfctl's view of the fingerprints */ +void +pfctl_flush_my_fingerprints(struct name_list *list) +{ + struct name_entry *nm; + + while ((nm = LIST_FIRST(list)) != NULL) { + LIST_REMOVE(nm, nm_entry); + pfctl_flush_my_fingerprints(&nm->nm_sublist); + fingerprint_count--; + free(nm); + } + class_count = 0; +} + +/* Fetch the active fingerprints from the kernel */ +int +pfctl_load_fingerprints(int dev, int opts) +{ + struct pf_osfp_ioctl io; + int i; + + pfctl_flush_my_fingerprints(&classes); + + for (i = 0; i >= 0; i++) { + memset(&io, 0, sizeof(io)); + io.fp_getnum = i; + if (ioctl(dev, DIOCOSFPGET, &io)) { + if (errno == EBUSY) + break; + warn("DIOCOSFPGET"); + return (1); + } + import_fingerprint(&io); + } + return (0); +} + +/* List the fingerprints */ +void +pfctl_show_fingerprints(int opts) +{ + printf("Passive OS Fingerprints:\n"); + printf("\tClass\tVersion\tSubtype(subversion)\n"); + printf("\t-----\t-------\t-------------------\n"); + sort_name_list(opts, &classes); + print_name_list(opts, &classes, "\t"); +} + +/* Lookup a fingerprint */ +pf_osfp_t +pfctl_get_fingerprint(const char *name) +{ + struct name_entry *nm, *class_nm, *version_nm, *subtype_nm; + pf_osfp_t ret = PF_OSFP_NOMATCH; + int class, version, subtype; + int unp_class, unp_version, unp_subtype; + int wr_len, version_len, subtype_len; + char *ptr, *wr_name; + + if (strcasecmp(name, "unknown") == 0) + return (PF_OSFP_UNKNOWN); + + /* Try most likely no version and no subtype */ + if ((nm = lookup_name_list(&classes, name))) { + class = nm->nm_num; + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + goto found; + } else { + + /* Chop it up into class/version/subtype */ + + if ((wr_name = strdup(name)) == NULL) + err(1, "malloc"); + if ((ptr = index(wr_name, ' ')) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + *ptr++ = '\0'; + + /* The class is easy to find since it is delimited by a space */ + if ((class_nm = lookup_name_list(&classes, wr_name)) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + class = class_nm->nm_num; + + /* Try no subtype */ + if ((version_nm = lookup_name_list(&class_nm->nm_sublist, ptr))) + { + version = version_nm->nm_num; + subtype = PF_OSFP_ANY; + free(wr_name); + goto found; + } + + + /* + * There must be a version and a subtype. + * We'll do some fuzzy matching to pick up things like: + * Linux 2.2.14 (version=2.2 subtype=14) + * FreeBSD 4.0-STABLE (version=4.0 subtype=STABLE) + * Windows 2000 SP2 (version=2000 subtype=SP2) + */ +#define CONNECTOR(x) ((x) == '.' || (x) == ' ' || (x) == '\t' || (x) == '-') + wr_len = strlen(ptr); + LIST_FOREACH(version_nm, &class_nm->nm_sublist, nm_entry) { + version_len = strlen(version_nm->nm_name); + if (wr_len < version_len + 2 || + !CONNECTOR(ptr[version_len])) + continue; + /* first part of the string must be version */ + if (strncasecmp(ptr, version_nm->nm_name, + version_len)) + continue; + + LIST_FOREACH(subtype_nm, &version_nm->nm_sublist, + nm_entry) { + subtype_len = strlen(subtype_nm->nm_name); + if (wr_len != version_len + subtype_len + 1) + continue; + + /* last part of the string must be subtype */ + if (strcasecmp(&ptr[version_len+1], + subtype_nm->nm_name) != 0) + continue; + + /* Found it!! */ + version = version_nm->nm_num; + subtype = subtype_nm->nm_num; + free(wr_name); + goto found; + } + } + + free(wr_name); + return (PF_OSFP_NOMATCH); + } + +found: + PF_OSFP_PACK(ret, class, version, subtype); + if (ret != PF_OSFP_NOMATCH) { + PF_OSFP_UNPACK(ret, unp_class, unp_version, unp_subtype); + if (class != unp_class) { + fprintf(stderr, "warning: fingerprint table overflowed " + "classes\n"); + return (PF_OSFP_NOMATCH); + } + if (version != unp_version) { + fprintf(stderr, "warning: fingerprint table overflowed " + "versions\n"); + return (PF_OSFP_NOMATCH); + } + if (subtype != unp_subtype) { + fprintf(stderr, "warning: fingerprint table overflowed " + "subtypes\n"); + return (PF_OSFP_NOMATCH); + } + } + if (ret == PF_OSFP_ANY) { + /* should never happen */ + fprintf(stderr, "warning: fingerprint packed to 'any'\n"); + return (PF_OSFP_NOMATCH); + } + + return (ret); +} + +/* Lookup a fingerprint name by ID */ +char * +pfctl_lookup_fingerprint(pf_osfp_t fp, char *buf, size_t len) +{ + int class, version, subtype; + struct name_list *list; + struct name_entry *nm; + + char *class_name, *version_name, *subtype_name; + class_name = version_name = subtype_name = NULL; + + if (fp == PF_OSFP_UNKNOWN) { + strlcpy(buf, "unknown", len); + return (buf); + } + if (fp == PF_OSFP_ANY) { + strlcpy(buf, "any", len); + return (buf); + } + + PF_OSFP_UNPACK(fp, class, version, subtype); + if (class >= (1 << _FP_CLASS_BITS) || + version >= (1 << _FP_VERSION_BITS) || + subtype >= (1 << _FP_SUBTYPE_BITS)) { + warnx("PF_OSFP_UNPACK(0x%x) failed!!", fp); + strlcpy(buf, "nomatch", len); + return (buf); + } + + LIST_FOREACH(nm, &classes, nm_entry) { + if (nm->nm_num == class) { + class_name = nm->nm_name; + if (version == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == version) { + version_name = nm->nm_name; + if (subtype == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == subtype) { + subtype_name = + nm->nm_name; + goto found; + } + } /* foreach subtype */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach version */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach class */ + + strlcpy(buf, "nomatch", len); + return (buf); + +found: + snprintf(buf, len, "%s", class_name); + if (version_name) { + strlcat(buf, " ", len); + strlcat(buf, version_name, len); + if (subtype_name) { + if (index(version_name, ' ')) + strlcat(buf, " ", len); + else if (index(version_name, '.') && + isdigit(*subtype_name)) + strlcat(buf, ".", len); + else + strlcat(buf, " ", len); + strlcat(buf, subtype_name, len); + } + } + return (buf); +} + +/* lookup a name in a list */ +struct name_entry * +lookup_name_list(struct name_list *list, const char *name) +{ + struct name_entry *nm; + LIST_FOREACH(nm, list, nm_entry) + if (strcasecmp(name, nm->nm_name) == 0) + return (nm); + + return (NULL); +} + + +void +add_fingerprint(int dev, int opts, struct pf_osfp_ioctl *fp) +{ + struct pf_osfp_ioctl fptmp; + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + +/* We expand #-# or #.#-#.# version/subtypes into multiple fingerprints */ +#define EXPAND(field) do { \ + int _dot = -1, _start = -1, _end = -1, _i = 0; \ + /* pick major version out of #.# */ \ + if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.') { \ + _dot = fp->field[_i] - '0'; \ + _i += 2; \ + } \ + if (isdigit(fp->field[_i])) \ + _start = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit(fp->field[_i])) \ + _start = (_start * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i++] != '-') \ + break; \ + if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.' && \ + fp->field[_i] - '0' == _dot) \ + _i += 2; \ + else if (_dot != -1) \ + break; \ + if (isdigit(fp->field[_i])) \ + _end = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit(fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (isdigit(fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i] != '\0') \ + break; \ + memcpy(&fptmp, fp, sizeof(fptmp)); \ + for (;_start <= _end; _start++) { \ + memset(fptmp.field, 0, sizeof(fptmp.field)); \ + fptmp.fp_os.fp_enflags |= PF_OSFP_EXPANDED; \ + if (_dot == -1) \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d", _start); \ + else \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d.%d", _dot, _start); \ + add_fingerprint(dev, opts, &fptmp); \ + } \ +} while(0) + + /* We allow "#-#" as a version or subtype and we'll expand it */ + EXPAND(fp_os.fp_version_nm); + EXPAND(fp_os.fp_subtype_nm); + + if (strcasecmp(fp->fp_os.fp_class_nm, "nomatch") == 0) + errx(1, "fingerprint class \"nomatch\" is reserved"); + + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) + nm_class->nm_num = ++class_count; + class = nm_class->nm_num; + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) + nm_version->nm_num = ++nm_class->nm_sublist_num; + version = nm_version->nm_num; + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) + nm_subtype->nm_num = + ++nm_version->nm_sublist_num; + subtype = nm_subtype->nm_num; + } + } + + + DEBUG(fp, "\tsignature %d:%d:%d %s", class, version, subtype, + print_ioctl(fp)); + + PF_OSFP_PACK(fp->fp_os.fp_os, class, version, subtype); + fingerprint_count++; + +#ifdef FAKE_PF_KERNEL + /* Linked to the sys/net/pf_osfp.c. Call pf_osfp_add() */ + if ((errno = pf_osfp_add(fp))) +#else + if ((opts & PF_OPT_NOACTION) == 0 && ioctl(dev, DIOCOSFPADD, fp)) +#endif /* FAKE_PF_KERNEL */ + { + if (errno == EEXIST) { + warn("Duplicate signature for %s %s %s", + fp->fp_os.fp_class_nm, + fp->fp_os.fp_version_nm, + fp->fp_os.fp_subtype_nm); + + } else { + err(1, "DIOCOSFPADD"); + } + } +} + +/* import a fingerprint from the kernel */ +void +import_fingerprint(struct pf_osfp_ioctl *fp) +{ + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + + PF_OSFP_UNPACK(fp->fp_os.fp_os, class, version, subtype); + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) { + nm_class->nm_num = class; + class_count = MAX(class_count, class); + } + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) { + nm_version->nm_num = version; + nm_class->nm_sublist_num = MAX(nm_class->nm_sublist_num, + version); + } + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) { + nm_subtype->nm_num = subtype; + nm_version->nm_sublist_num = + MAX(nm_version->nm_sublist_num, subtype); + } + } + } + + + fingerprint_count++; + DEBUG(fp, "import signature %d:%d:%d", class, version, subtype); +} + +/* Find an entry for a fingerprints class/version/subtype */ +struct name_entry * +fingerprint_name_entry(struct name_list *list, char *name) +{ + struct name_entry *nm_entry; + + if (name == NULL || strlen(name) == 0) + return (NULL); + + LIST_FOREACH(nm_entry, list, nm_entry) { + if (strcasecmp(nm_entry->nm_name, name) == 0) { + /* We'll move this to the front of the list later */ + LIST_REMOVE(nm_entry, nm_entry); + break; + } + } + if (nm_entry == NULL) { + nm_entry = calloc(1, sizeof(*nm_entry)); + if (nm_entry == NULL) + err(1, "calloc"); + LIST_INIT(&nm_entry->nm_sublist); + strlcpy(nm_entry->nm_name, name, + sizeof(nm_entry->nm_name)); + } + LIST_INSERT_HEAD(list, nm_entry, nm_entry); + return (nm_entry); +} + + +void +print_name_list(int opts, struct name_list *nml, const char *prefix) +{ + char newprefix[32]; + struct name_entry *nm; + + LIST_FOREACH(nm, nml, nm_entry) { + snprintf(newprefix, sizeof(newprefix), "%s%s\t", prefix, + nm->nm_name); + printf("%s\n", newprefix); + print_name_list(opts, &nm->nm_sublist, newprefix); + } +} + +void +sort_name_list(int opts, struct name_list *nml) +{ + struct name_list new; + struct name_entry *nm, *nmsearch, *nmlast; + + /* yes yes, it's a very slow sort. so sue me */ + + LIST_INIT(&new); + + while ((nm = LIST_FIRST(nml)) != NULL) { + LIST_REMOVE(nm, nm_entry); + nmlast = NULL; + LIST_FOREACH(nmsearch, &new, nm_entry) { + if (strcasecmp(nmsearch->nm_name, nm->nm_name) > 0) { + LIST_INSERT_BEFORE(nmsearch, nm, nm_entry); + break; + } + nmlast = nmsearch; + } + if (nmsearch == NULL) { + if (nmlast) + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + else + LIST_INSERT_HEAD(&new, nm, nm_entry); + } + + sort_name_list(opts, &nm->nm_sublist); + } + nmlast = NULL; + while ((nm = LIST_FIRST(&new)) != NULL) { + LIST_REMOVE(nm, nm_entry); + if (nmlast == NULL) + LIST_INSERT_HEAD(nml, nm, nm_entry); + else + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + nmlast = nm; + } + return; +} + +/* parse the next integer in a formatted config file line */ +int +get_int(char **line, size_t *len, int *var, int *mod, + const char *name, int flags, int max, const char *filename, int lineno) +{ + int fieldlen, i; + char *field; + long val = 0; + + if (mod) + *mod = 0; + *var = 0; + + field = get_field(line, len, &fieldlen); + if (field == NULL) + return (1); + if (fieldlen == 0) { + fprintf(stderr, "%s:%d empty %s\n", filename, lineno, name); + return (1); + } + + i = 0; + if ((*field == '%' || *field == 'S' || *field == 'T' || *field == '*') + && fieldlen >= 1) { + switch (*field) { + case 'S': + if (mod && (flags & T_MSS)) + *mod = T_MSS; + if (fieldlen == 1) + return (0); + break; + case 'T': + if (mod && (flags & T_MTU)) + *mod = T_MTU; + if (fieldlen == 1) + return (0); + break; + case '*': + if (fieldlen != 1) { + fprintf(stderr, "%s:%d long '%c' %s\n", + filename, lineno, *field, name); + return (1); + } + if (mod && (flags & T_DC)) { + *mod = T_DC; + return (0); + } + case '%': + if (mod && (flags & T_MOD)) + *mod = T_MOD; + if (fieldlen == 1) { + fprintf(stderr, "%s:%d modulus %s must have a " + "value\n", filename, lineno, name); + return (1); + } + break; + } + if (mod == NULL || *mod == 0) { + fprintf(stderr, "%s:%d does not allow %c' %s\n", + filename, lineno, *field, name); + return (1); + } + i++; + } + + for (; i < fieldlen; i++) { + if (field[i] < '0' || field[i] > '9') { + fprintf(stderr, "%s:%d non-digit character in %s\n", + filename, lineno, name); + return (1); + } + val = val * 10 + field[i] - '0'; + if (val < 0) { + fprintf(stderr, "%s:%d %s overflowed\n", filename, + lineno, name); + return (1); + } + } + + if (val > max) { + fprintf(stderr, "%s:%d %s value %ld > %d\n", filename, lineno, + name, val, max); + return (1); + } + *var = (int)val; + + return (0); +} + +/* parse the next string in a formatted config file line */ +int +get_str(char **line, size_t *len, char **v, const char *name, int minlen, + const char *filename, int lineno) +{ + int fieldlen; + char *ptr; + + ptr = get_field(line, len, &fieldlen); + if (ptr == NULL) + return (1); + if (fieldlen < minlen) { + fprintf(stderr, "%s:%d too short %s\n", filename, lineno, name); + return (1); + } + if ((*v = malloc(fieldlen + 1)) == NULL) { + perror("malloc()"); + return (1); + } + memcpy(*v, ptr, fieldlen); + (*v)[fieldlen] = '\0'; + + return (0); +} + +/* Parse out the TCP opts */ +int +get_tcpopts(const char *filename, int lineno, const char *tcpopts, + pf_tcpopts_t *packed, int *optcnt, int *mss, int *mss_mod, int *wscale, + int *wscale_mod, int *ts0) +{ + int i, opt; + + *packed = 0; + *optcnt = 0; + *wscale = 0; + *wscale_mod = T_DC; + *mss = 0; + *mss_mod = T_DC; + *ts0 = 0; + if (strcmp(tcpopts, ".") == 0) + return (0); + + for (i = 0; tcpopts[i] && *optcnt < PF_OSFP_MAX_OPTS;) { + switch ((opt = toupper(tcpopts[i++]))) { + case 'N': /* FALLTHROUGH */ + case 'S': + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'N' ? PF_OSFP_TCPOPT_NOP : + PF_OSFP_TCPOPT_SACK); + break; + case 'W': /* FALLTHROUGH */ + case 'M': { + int *this_mod, *this; + + if (opt == 'W') { + this = wscale; + this_mod = wscale_mod; + } else { + this = mss; + this_mod = mss_mod; + } + *this = 0; + *this_mod = 0; + + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'W' ? PF_OSFP_TCPOPT_WSCALE : + PF_OSFP_TCPOPT_MSS); + if (tcpopts[i] == '*' && (tcpopts[i + 1] == '\0' || + tcpopts[i + 1] == ',')) { + *this_mod = T_DC; + i++; + break; + } + + if (tcpopts[i] == '%') { + *this_mod = T_MOD; + i++; + } + do { + if (!isdigit(tcpopts[i])) { + fprintf(stderr, "%s:%d unknown " + "character '%c' in %c TCP opt\n", + filename, lineno, tcpopts[i], opt); + return (1); + } + *this = (*this * 10) + tcpopts[i++] - '0'; + } while(tcpopts[i] != ',' && tcpopts[i] != '\0'); + break; + } + case 'T': + if (tcpopts[i] == '0') { + *ts0 = 1; + i++; + } + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + PF_OSFP_TCPOPT_TS; + break; + } + (*optcnt) ++; + if (tcpopts[i] == '\0') + break; + if (tcpopts[i] != ',') { + fprintf(stderr, "%s:%d unknown option to %c TCP opt\n", + filename, lineno, opt); + return (1); + } + i++; + } + + return (0); +} + +/* rip the next field ouf of a formatted config file line */ +char * +get_field(char **line, size_t *len, int *fieldlen) +{ + char *ret, *ptr = *line; + size_t plen = *len; + + + while (plen && isspace(*ptr)) { + plen--; + ptr++; + } + ret = ptr; + *fieldlen = 0; + + for (; plen > 0 && *ptr != ':'; plen--, ptr++) + (*fieldlen)++; + if (plen) { + *line = ptr + 1; + *len = plen - 1; + } else { + *len = 0; + } + while (*fieldlen && isspace(ret[*fieldlen - 1])) + (*fieldlen)--; + return (ret); +} + + +const char * +print_ioctl(struct pf_osfp_ioctl *fp) +{ + static char buf[1024]; + char tmp[32]; + int i, opt; + + *buf = '\0'; + if (fp->fp_flags & PF_OSFP_WSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MSS) + strlcat(buf, "S", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MTU) + strlcat(buf, "T", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wsize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "%d", fp->fp_ttl); + strlcat(buf, tmp, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_DF) + strlcat(buf, "1", sizeof(buf)); + else + strlcat(buf, "0", sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_PSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_PSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_psize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_optcnt == 0) + strlcat(buf, ".", sizeof(buf)); + for (i = fp->fp_optcnt - 1; i >= 0; i--) { + opt = fp->fp_tcpopts >> (i * PF_OSFP_TCPOPT_BITS); + opt &= (1 << PF_OSFP_TCPOPT_BITS) - 1; + switch (opt) { + case PF_OSFP_TCPOPT_NOP: + strlcat(buf, "N", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_SACK: + strlcat(buf, "S", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_TS: + strlcat(buf, "T", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_TS0) + strlcat(buf, "0", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_MSS: + strlcat(buf, "M", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_MSS_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_MSS_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_mss); + strlcat(buf, tmp, sizeof(buf)); + } + break; + case PF_OSFP_TCPOPT_WSCALE: + strlcat(buf, "W", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_WSCALE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSCALE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wscale); + strlcat(buf, tmp, sizeof(buf)); + } + break; + } + + if (i != 0) + strlcat(buf, ",", sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + strlcat(buf, fp->fp_os.fp_class_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_version_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_subtype_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "TcpOpts %d 0x%llx", fp->fp_optcnt, + (long long int)fp->fp_tcpopts); + strlcat(buf, tmp, sizeof(buf)); + + return (buf); +} diff --git a/contrib/pf/pfctl/pfctl_parser.c b/contrib/pf/pfctl/pfctl_parser.c new file mode 100644 index 0000000..7c051ac --- /dev/null +++ b/contrib/pf/pfctl/pfctl_parser.c @@ -0,0 +1,1286 @@ +/* $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 +#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", + s->bcounters[0][0], s->bcounters[1][0]); + printf(" %-25s %14llu %16llu\n", "Bytes Out", + s->bcounters[0][1], s->bcounters[1][1]); + printf(" Packets In\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + s->pcounters[0][0][PF_PASS], + s->pcounters[1][0][PF_PASS]); + printf(" %-23s %14llu %16llu\n", "Blocked", + s->pcounters[0][0][PF_DROP], + s->pcounters[1][0][PF_DROP]); + printf(" Packets Out\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + s->pcounters[0][1][PF_PASS], + s->pcounters[1][1][PF_PASS]); + printf(" %-23s %14llu %16llu\n\n", "Blocked", + s->pcounters[0][1][PF_DROP], + 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; + 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 new file mode 100644 index 0000000..88047e5 --- /dev/null +++ b/contrib/pf/pfctl/pfctl_parser.h @@ -0,0 +1,228 @@ +/* $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. + * + */ + +#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; +}; + +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 new file mode 100644 index 0000000..23c431e --- /dev/null +++ b/contrib/pf/pfctl/pfctl_qstats.c @@ -0,0 +1,401 @@ +/* $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 +#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; + + 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", + cur.data.cbq_stats.xmit_cnt.packets, + cur.data.cbq_stats.xmit_cnt.bytes, + cur.data.cbq_stats.drop_cnt.packets, + 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", + cur.data.priq_stats.xmitcnt.packets, + cur.data.priq_stats.xmitcnt.bytes, + cur.data.priq_stats.dropcnt.packets, + 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", + cur.data.hfsc_stats.xmit_cnt.packets, + cur.data.hfsc_stats.xmit_cnt.bytes, + cur.data.hfsc_stats.drop_cnt.packets, + 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_radix.c b/contrib/pf/pfctl/pfctl_radix.c new file mode 100644 index 0000000..788522c --- /dev/null +++ b/contrib/pf/pfctl/pfctl_radix.c @@ -0,0 +1,639 @@ +/* $OpenBSD: pfctl_radix.c,v 1.21 2003/09/24 09:12:35 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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl.h" + +#define BUF_SIZE 256 + +extern int dev; + +static int pfr_next_token(char buf[], FILE *); + + +int +pfr_clr_tables(struct pfr_table *filter, int *ndel, int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + if (ioctl(dev, DIOCRCLRTABLES, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRADDTABLES, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +int +pfr_del_tables(struct pfr_table *tbl, int size, int *ndel, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRDELTABLES, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETTABLES, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_get_tstats(struct pfr_table *filter, struct pfr_tstats *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETTSTATS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + if (ioctl(dev, DIOCRCLRADDRS, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRADDADDRS, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +int +pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *ndel, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRDELADDRS, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *size2, int *nadd, int *ndel, int *nchange, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_size2 = (size2 != NULL) ? *size2 : 0; + if (ioctl(dev, DIOCRSETADDRS, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (ndel != NULL) + *ndel = io.pfrio_ndel; + if (nchange != NULL) + *nchange = io.pfrio_nchange; + if (size2 != NULL) + *size2 = io.pfrio_size2; + return (0); +} + +int +pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETADDRS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETASTATS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nzero, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRCLRASTATS, &io)) + return (-1); + if (nzero != NULL) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_clr_tstats(struct pfr_table *tbl, int size, int *nzero, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && !tbl)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRCLRTSTATS, &io)) + return (-1); + if (nzero) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_set_tflags(struct pfr_table *tbl, int size, int setflag, int clrflag, + int *nchange, int *ndel, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && !tbl)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + io.pfrio_setflag = setflag; + io.pfrio_clrflag = clrflag; + if (ioctl(dev, DIOCRSETTFLAGS, &io)) + return (-1); + if (nchange) + *nchange = io.pfrio_nchange; + if (ndel) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nmatch, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRTSTADDRS, &io)) + return (-1); + if (nmatch) + *nmatch = io.pfrio_nmatch; + return (0); +} + +int +pfr_ina_begin(struct pfr_table *trs, int *ticket, int *ndel, int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + if (trs != NULL) + io.pfrio_table = *trs; + io.pfrio_flags = flags; + if (ioctl(dev, DIOCRINABEGIN, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + if (ticket != NULL) + *ticket = io.pfrio_ticket; + return (0); +} + +int +pfr_ina_commit(struct pfr_table *trs, int ticket, int *nadd, int *nchange, + int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + if (trs != NULL) + io.pfrio_table = *trs; + io.pfrio_flags = flags; + io.pfrio_ticket = ticket; + if (ioctl(dev, DIOCRINACOMMIT, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (nchange != NULL) + *nchange = io.pfrio_nchange; + return (0); +} + +int +pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int *naddr, int ticket, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_ticket = ticket; + if (ioctl(dev, DIOCRINADEFINE, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (naddr != NULL) + *naddr = io.pfrio_naddr; + return (0); +} + +/* buffer management code */ + +size_t buf_esize[PFRB_MAX] = { 0, + sizeof(struct pfr_table), sizeof(struct pfr_tstats), + sizeof(struct pfr_addr), sizeof(struct pfr_astats), +}; + +/* + * add one element to the buffer + */ +int +pfr_buf_add(struct pfr_buffer *b, const void *e) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX || + e == NULL) { + errno = EINVAL; + return (-1); + } + bs = buf_esize[b->pfrb_type]; + if (b->pfrb_size == b->pfrb_msize) + if (pfr_buf_grow(b, 0)) + return (-1); + memcpy(((caddr_t)b->pfrb_caddr) + bs * b->pfrb_size, e, bs); + b->pfrb_size++; + return (0); +} + +/* + * return next element of the buffer (or first one if prev is NULL) + * see PFRB_FOREACH macro + */ +void * +pfr_buf_next(struct pfr_buffer *b, const void *prev) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) + return (NULL); + if (b->pfrb_size == 0) + return (NULL); + if (prev == NULL) + return (b->pfrb_caddr); + bs = buf_esize[b->pfrb_type]; + if ((((caddr_t)prev)-((caddr_t)b->pfrb_caddr)) / bs >= b->pfrb_size-1) + return (NULL); + return (((caddr_t)prev) + bs); +} + +/* + * minsize: + * 0: make the buffer somewhat bigger + * n: make room for "n" entries in the buffer + */ +int +pfr_buf_grow(struct pfr_buffer *b, int minsize) +{ + caddr_t p; + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) { + errno = EINVAL; + return (-1); + } + if (minsize != 0 && minsize <= b->pfrb_msize) + return (0); + bs = buf_esize[b->pfrb_type]; + if (!b->pfrb_msize) { + if (minsize < 64) + minsize = 64; + b->pfrb_caddr = calloc(bs, minsize); + if (b->pfrb_caddr == NULL) + return (-1); + b->pfrb_msize = minsize; + } else { + if (minsize == 0) + minsize = b->pfrb_msize * 2; + if (minsize < 0 || minsize >= SIZE_T_MAX / bs) { + /* msize overflow */ + errno = ENOMEM; + return (-1); + } + p = realloc(b->pfrb_caddr, minsize * bs); + if (p == NULL) + return (-1); + bzero(p + b->pfrb_msize * bs, (minsize - b->pfrb_msize) * bs); + b->pfrb_caddr = p; + b->pfrb_msize = minsize; + } + return (0); +} + +/* + * reset buffer and free memory. + */ +void +pfr_buf_clear(struct pfr_buffer *b) +{ + if (b == NULL) + return; + if (b->pfrb_caddr != NULL) + free(b->pfrb_caddr); + b->pfrb_caddr = NULL; + b->pfrb_size = b->pfrb_msize = 0; +} + +int +pfr_buf_load(struct pfr_buffer *b, char *file, int nonetwork, + int (*append_addr)(struct pfr_buffer *, char *, int)) +{ + FILE *fp; + char buf[BUF_SIZE]; + int rv; + + if (file == NULL) + return (0); + if (!strcmp(file, "-")) + fp = stdin; + else { + fp = fopen(file, "r"); + if (fp == NULL) + return (-1); + } + while ((rv = pfr_next_token(buf, fp)) == 1) + if (append_addr(b, buf, nonetwork)) { + rv = -1; + break; + } + if (fp != stdin) + fclose(fp); + return (rv); +} + +int +pfr_next_token(char buf[BUF_SIZE], FILE *fp) +{ + static char next_ch = ' '; + int i = 0; + + for (;;) { + /* skip spaces */ + while (isspace(next_ch) && !feof(fp)) + next_ch = fgetc(fp); + /* remove from '#' until end of line */ + if (next_ch == '#') + while (!feof(fp)) { + next_ch = fgetc(fp); + if (next_ch == '\n') + break; + } + else + break; + } + if (feof(fp)) { + next_ch = ' '; + return (0); + } + do { + if (i < BUF_SIZE) + buf[i++] = next_ch; + next_ch = fgetc(fp); + } while (!feof(fp) && !isspace(next_ch)); + if (i >= BUF_SIZE) { + errno = EINVAL; + return (-1); + } + buf[i] = '\0'; + return (1); +} + +char * +pfr_strerror(int errnum) +{ + switch (errnum) { + case ESRCH: + return "Table does not exist"; + case ENOENT: + return "Anchor or Ruleset does not exist"; + default: + return strerror(errnum); + } +} diff --git a/contrib/pf/pfctl/pfctl_table.c b/contrib/pf/pfctl/pfctl_table.c new file mode 100644 index 0000000..57bdf19 --- /dev/null +++ b/contrib/pf/pfctl/pfctl_table.c @@ -0,0 +1,524 @@ +/* $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 +#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", + ts->pfrts_nomatch, 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], + ts->pfrts_packets[dir][op], + 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], + as->pfras_packets[dir][op], + 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.8 b/contrib/pf/pflogd/pflogd.8 new file mode 100644 index 0000000..ab63259 --- /dev/null +++ b/contrib/pf/pflogd/pflogd.8 @@ -0,0 +1,179 @@ +.\" $OpenBSD: pflogd.8,v 1.22 2003/06/03 13:16:08 jmc Exp $ +.\" +.\" 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: +.\" 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. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" 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. +.\" +.Dd July 9, 2001 +.Dt PFLOGD 8 +.Os +.Sh NAME +.Nm pflogd +.Nd packet filter logging daemon +.Sh SYNOPSIS +.Nm pflogd +.Op Fl D +.Op Fl d Ar delay +.Op Fl f Ar filename +.Op Fl s Ar snaplen +.Op Ar expression +.Sh DESCRIPTION +.Nm +is a background daemon which reads packets logged by +.Xr pf 4 +to the packet logging interface +.Pa pflog0 +and writes the packets to a logfile (normally +.Pa /var/log/pflog ) +in +.Xr tcpdump 8 +binary format. +These logs can be reviewed later using the +.Fl r +option of +.Xr tcpdump 8 , +hopefully offline in case there are bugs in the packet parsing code of +.Xr tcpdump 8 . +.Pp +.Nm +closes and then re-opens the log file when it receives +.Va SIGHUP , +permitting +.Xr newsyslog 8 +to rotate logfiles automatically. +.Va SIGALRM +causes +.Nm +to flush the current logfile buffers to the disk, thus making the most +recent logs available. +The buffers are also flushed every +.Ar delay +seconds. +.Pp +If the log file contains data after a restart or a +.Va SIGHUP , +new logs are appended to the existing file. +If the existing log file was created with a different snaplen, +.Nm +temporarily uses the old snaplen to keep the log file consistent. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d Ar delay +Time in seconds to delay between automatic flushes of the file. +This may be specified with a value between 5 and 3600 seconds. +If not specified, the default is 60 seconds. +.It Fl D +Debugging mode. +.Nm +does not disassociate from the controlling terminal. +.It Fl f Ar filename +Log output filename. +Default is +.Pa /var/log/pflog . +.It Fl s Ar snaplen +Analyze at most the first +.Ar snaplen +bytes of data from each packet rather than the default of 96. +The default of 96 is adequate for IP, ICMP, TCP, and UDP headers but may +truncate protocol information for other protocols. +Other file parsers may desire a higher snaplen. +.It Ar expression +Selects which packets will be dumped, using the regular language of +.Xr tcpdump 8 . +.El +.Sh FILES +.Bl -tag -width /var/run/pflogd.pid -compact +.It Pa /var/run/pflogd.pid +Process ID of the currently running +.Nm pflogd . +.It Pa /var/log/pflog +Default log file. +.El +.Sh EXAMPLES +Log specific tcp packets to a different log file with a large snaplen +(useful with a log-all rule to dump complete sessions) +.Bd -literal -offset indent +# pflogd -s 1600 -f suspicious.log port 80 and host evilhost +.Ed +.Pp +Display binary logs: +.Bd -literal -offset indent +# tcpdump -n -e -ttt -r /var/log/pflog +.Ed +.Pp +Display the logs in real time (this does not interfere with the +operation of pflogd): +.Bd -literal -offset indent +# tcpdump -n -e -ttt -i pflog0 +.Ed +.Pp +Tcpdump has been extended to be able to filter on the pfloghdr +structure defined in +.Aq Ar net/if_pflog.h . +Tcpdump can restrict the output +to packets logged on a specified interface, a rule number, a reason, +a direction, an ip family or an action. +.Pp +.Bl -tag -width "reason match " -compact +.It ip +Address family equals IPv4. +.It ip6 +Address family equals IPv6. +.It ifname kue0 +Interface name equals "kue0" +.It on kue0 +Interface name equals "kue0" +.It rulenum 10 +Rule number equals 10. +.It reason match +Reason equals match. +Also accepts "bad-offset", "fragment", "short", "normalize" and "memory". +.It action pass +Action equals pass. +Also accepts "block". +.It inbound +The direction was inbound. +.It outbound +The direction was outbound. +.El +.Pp +Display the logs in real time of inbound packets that were blocked on +the wi0 interface: +.Bd -literal -offset indent +# tcpdump -n -e -ttt -i pflog0 inbound and action block and on wi0 +.Ed +.Sh SEE ALSO +.Xr pcap 3 , +.Xr pf 4 , +.Xr pflog 4 , +.Xr pf.conf 5 , +.Xr newsyslog 8 , +.Xr tcpdump 8 +.Sh HISTORY +The +.Nm +command appeared in +.Ox 3.0 . +.Sh AUTHORS +Can Erkin Acar diff --git a/contrib/pf/pflogd/pflogd.c b/contrib/pf/pflogd/pflogd.c new file mode 100644 index 0000000..9777f15 --- /dev/null +++ b/contrib/pf/pflogd/pflogd.c @@ -0,0 +1,400 @@ +/* $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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} + +__dead void +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); +} -- cgit v1.1