summaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
authormlaier <mlaier@FreeBSD.org>2004-02-28 16:52:45 +0000
committermlaier <mlaier@FreeBSD.org>2004-02-28 16:52:45 +0000
commit2135f6a83c8e1b39db8af0b470ab6d0349144be9 (patch)
tree73e1e06a1e5e925889f30ef537d21f7d3edd736d /contrib
downloadFreeBSD-src-2135f6a83c8e1b39db8af0b470ab6d0349144be9.zip
FreeBSD-src-2135f6a83c8e1b39db8af0b470ab6d0349144be9.tar.gz
Vendor import of OpenBSD's pf userland as of OpenBSD 3.4
Approved by: bms(mentor), core(in general)
Diffstat (limited to 'contrib')
-rw-r--r--contrib/pf/authpf/authpf.8463
-rw-r--r--contrib/pf/authpf/authpf.c871
-rw-r--r--contrib/pf/authpf/pathnames.h37
-rw-r--r--contrib/pf/ftp-proxy/ftp-proxy.8253
-rw-r--r--contrib/pf/ftp-proxy/ftp-proxy.c1320
-rw-r--r--contrib/pf/ftp-proxy/getline.c259
-rw-r--r--contrib/pf/ftp-proxy/util.c296
-rw-r--r--contrib/pf/ftp-proxy/util.h68
-rw-r--r--contrib/pf/man/pf.4703
-rw-r--r--contrib/pf/man/pf.conf.52486
-rw-r--r--contrib/pf/man/pf.os.5242
-rw-r--r--contrib/pf/man/pflog.488
-rw-r--r--contrib/pf/man/pfsync.480
-rw-r--r--contrib/pf/pfctl/parse.y4489
-rw-r--r--contrib/pf/pfctl/pf_print_state.c279
-rw-r--r--contrib/pf/pfctl/pfctl.8506
-rw-r--r--contrib/pf/pfctl/pfctl.c1626
-rw-r--r--contrib/pf/pfctl/pfctl.h115
-rw-r--r--contrib/pf/pfctl/pfctl_altq.c1210
-rw-r--r--contrib/pf/pfctl/pfctl_osfp.c1093
-rw-r--r--contrib/pf/pfctl/pfctl_parser.c1286
-rw-r--r--contrib/pf/pfctl/pfctl_parser.h228
-rw-r--r--contrib/pf/pfctl/pfctl_qstats.c401
-rw-r--r--contrib/pf/pfctl/pfctl_radix.c639
-rw-r--r--contrib/pf/pfctl/pfctl_table.c524
-rw-r--r--contrib/pf/pflogd/pflogd.8179
-rw-r--r--contrib/pf/pflogd/pflogd.c400
27 files changed, 20141 insertions, 0 deletions
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 <sys/types.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <pfctl_parser.h>
+
+#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 <danny@obtuse.com>
+ * and Bob Beck <beck@obtuse.com>
+ *
+ * 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 <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#ifdef LIBWRAP
+#include <tcpd.h>
+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 <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/telnet.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/file.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+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 <gwy addr> <gwy port> <ext addr> <ext port>\\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 <private> const { 10/8, 172.16/12, 192.168/16 }
+table <badhosts> persist
+block on fxp0 from { <private>, <badhosts> } 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 <spam> persist file \&"/etc/spammers\&" file \&"/etc/openrelays\&"
+block on fxp0 from <spam> 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 <number>
+Enforces a minimum TTL for matching IP packets.
+.It Ar max-mss <number>
+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 <interface>
+Queueing is enabled on the named interface.
+.It Ar <scheduler>
+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 <bw>
+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 <limit>
+The maximum number of packets held in the queue.
+The default is 50.
+.It Ar tbrsize <size>
+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 <list>
+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 <interface>
+Specifies the interface the queue operates on.
+If not given, it operates on all matching interfaces.
+.It Ar bandwidth <bw>
+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 <level>
+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 <limit>
+The maximum number of packets held in the queue.
+The default is 50.
+.El
+.Pp
+The
+.Ar scheduler
+can get additional parameters with
+.Ar <scheduler> Ns Li (\& Ar <parameters> 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 <sc>
+The minimum required bandwidth for the queue.
+.It Ar upperlimit <sc>
+The maximum allowed bandwidth for the queue.
+.It Ar linkshare <sc>
+The bandwidth share of a backlogged queue.
+.El
+.Pp
+<sc> 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 <interface>
+This rule applies only to packets coming in on, or going out through, this
+particular interface.
+.It Ar <af>
+This rule applies only to packets of this address family.
+Supported values are
+.Ar inet
+and
+.Ar inet6 .
+.It Ar proto <protocol>
+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 <source> port <source> os <source>
+.Ar to <dest> port <dest>
+.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 <table>
+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 <group>
+Similar to
+.Ar user ,
+this rule only applies to packets of sockets owned by the specified group.
+.It Ar user <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 <a>/<b> | /<b>
+This rule only applies to TCP packets that have the flags
+.Ar <a>
+set out of set
+.Ar <b> .
+Flags not specified in
+.Ar <b>
+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 <type> code <code>
+.It Ar icmp6-type <type> code <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 <string>
+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 <queue> | ( <queue> , <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 <string>
+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 <string>
+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 <number>
+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 <timeout> <seconds>
+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 <name>
+Evaluates the
+.Ar nat
+rules of all named rulesets in the specified
+.Ar anchor .
+.It Ar rdr-anchor <name>
+Evaluates the
+.Ar rdr
+rules of all named rulesets in the specified
+.Ar anchor .
+.It Ar binat-anchor <name>
+Evaluates the
+.Ar binat
+rules of all named rulesets in the specified
+.Ar anchor .
+.It Ar anchor <name>
+Evaluates the filter rules of all named rulesets in the specified
+.Ar anchor .
+.It Ar load anchor <name>:<ruleset> from <file>
+Loads the rules from the specified file into the named
+ruleset
+.Ar <ruleset>
+attached to the anchor
+.Ar <name> .
+.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 <spammers> 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 <frantzen@w4g.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.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 <mss 1460> (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 <number>
+.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 <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <altq/altq.h>
+#include <altq/altq_cbq.h>
+#include <altq/altq_priq.h>
+#include <altq/altq_hfsc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <err.h>
+#include <pwd.h>
+#include <grp.h>
+#include <md5.h>
+
+#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 <v.string> STRING
+%token <v.i> PORTBINARY
+%type <v.interface> interface if_list if_item_not if_item
+%type <v.number> number icmptype icmp6type uid gid
+%type <v.number> tos not yesno natpass
+%type <v.i> no dir log af fragcache
+%type <v.i> staticport unaryop
+%type <v.b> action nataction flags flag blockspec
+%type <v.range> port rport
+%type <v.hashkey> hashkey
+%type <v.pooltype> pooltype
+%type <v.proto> proto proto_list proto_item
+%type <v.icmp> icmpspec
+%type <v.icmp> icmp_list icmp_item
+%type <v.icmp> icmp6_list icmp6_item
+%type <v.fromto> fromto
+%type <v.peer> ipportspec from to
+%type <v.host> ipspec xhost host dynaddr host_list
+%type <v.host> redir_host_list redirspec
+%type <v.host> route_host route_host_list routespec
+%type <v.os> os xos os_list
+%type <v.port> portspec port_list port_item
+%type <v.uid> uids uid_list uid_item
+%type <v.gid> gids gid_list gid_item
+%type <v.route> route
+%type <v.redirection> redirection redirpool
+%type <v.string> label string tag
+%type <v.keep_state> keep
+%type <v.state_opt> state_opt_spec state_opt_list state_opt_item
+%type <v.logquick> logquick
+%type <v.interface> antispoof_ifspc antispoof_iflst
+%type <v.qassign> qname
+%type <v.queue> qassign qassign_list qassign_item
+%type <v.queue_options> scheduler
+%type <v.number> cbqflags_list cbqflags_item
+%type <v.number> priqflags_list priqflags_item
+%type <v.hfsc_opts> hfscopts_list hfscopts_item hfsc_opts
+%type <v.queue_bwspec> bandwidth
+%type <v.filter_opts> filter_opts filter_opt filter_opts_l
+%type <v.antispoof_opts> antispoof_opts antispoof_opt antispoof_opts_l
+%type <v.queue_opts> queue_opts queue_opt queue_opts_l
+%type <v.scrub_opts> scrub_opts scrub_opt scrub_opts_l
+%type <v.table_opts> 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 "
+ "'! <if>'");
+ 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 ! <interface> 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 <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#define TCPSTATES
+#include <netinet/tcp_fsm.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#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(" <BAD STATE LEVELS %u:%u>\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 <test> { ftp.openbsd.org }\en \e
+\ \ pass out to <test> 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 <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <altq/altq.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "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, "<invalid>");
+ 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 <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/limits.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+
+#include <err.h>
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <altq/altq.h>
+#include <altq/altq_cbq.h>
+#include <altq/altq_priq.h>
+#include <altq/altq_hfsc.h>
+
+#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 <math.h> */
+
+/* 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 <frantzen@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <err.h>
+#include <ifaddrs.h>
+
+#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 <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <altq/altq.h>
+#include <altq/altq_cbq.h>
+#include <altq/altq_priq.h>
+#include <altq/altq_hfsc.h>
+
+#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 <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <err.h>
+
+#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 <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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 <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pcap-int.h>
+#include <pcap.h>
+#include <syslog.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <util.h>
+
+#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);
+}
OpenPOWER on IntegriCloud