Uesrland part of PF
authorJoerg Sonnenberger <joerg@dragonflybsd.org>
Tue, 21 Sep 2004 21:25:28 +0000 (21:25 +0000)
committerJoerg Sonnenberger <joerg@dragonflybsd.org>
Tue, 21 Sep 2004 21:25:28 +0000 (21:25 +0000)
Obtained-from: OpenBSD
Ported-by: Devon O'Dell and Simon 'corecode' Schubert
Additioncally, do a pass over the code to get it WARNS=6 clean.
This means mostly fixing const'ness of strings and cleanup sign/unsigned
comparisions. The warnings in authpf about unused arguments have been
removed by use of __unused.

47 files changed:
etc/MAKEDEV
etc/Makefile
etc/defaults/rc.conf
etc/ftpusers
etc/group
etc/inetd.conf
etc/mail/aliases
etc/master.passwd
etc/newsyslog.conf
etc/pf.conf [new file with mode: 0644]
etc/pf.os [new file with mode: 0644]
etc/rc.d/Makefile
etc/rc.d/pf [new file with mode: 0644]
etc/rc.d/pflog [new file with mode: 0644]
libexec/Makefile
libexec/ftp-proxy/Makefile [new file with mode: 0644]
libexec/ftp-proxy/ftp-proxy.8 [new file with mode: 0644]
libexec/ftp-proxy/ftp-proxy.c [new file with mode: 0644]
libexec/ftp-proxy/getline.c [new file with mode: 0644]
libexec/ftp-proxy/util.c [new file with mode: 0644]
libexec/ftp-proxy/util.h [new file with mode: 0644]
usr.sbin/Makefile
usr.sbin/authpf/Makefile [new file with mode: 0644]
usr.sbin/authpf/authpf.8 [new file with mode: 0644]
usr.sbin/authpf/authpf.c [new file with mode: 0644]
usr.sbin/authpf/pathnames.h [new file with mode: 0644]
usr.sbin/pfctl/Makefile [new file with mode: 0644]
usr.sbin/pfctl/parse.y [new file with mode: 0644]
usr.sbin/pfctl/pf.conf.5 [new file with mode: 0644]
usr.sbin/pfctl/pf.os.5 [new file with mode: 0644]
usr.sbin/pfctl/pf_print_state.c [new file with mode: 0644]
usr.sbin/pfctl/pfctl.8 [new file with mode: 0644]
usr.sbin/pfctl/pfctl.c [new file with mode: 0644]
usr.sbin/pfctl/pfctl.h [new file with mode: 0644]
usr.sbin/pfctl/pfctl_altq.c [new file with mode: 0644]
usr.sbin/pfctl/pfctl_osfp.c [new file with mode: 0644]
usr.sbin/pfctl/pfctl_parser.c [new file with mode: 0644]
usr.sbin/pfctl/pfctl_parser.h [new file with mode: 0644]
usr.sbin/pfctl/pfctl_qstats.c [new file with mode: 0644]
usr.sbin/pfctl/pfctl_radix.c [new file with mode: 0644]
usr.sbin/pfctl/pfctl_table.c [new file with mode: 0644]
usr.sbin/pflogd/Makefile [new file with mode: 0644]
usr.sbin/pflogd/pflogd.8 [new file with mode: 0644]
usr.sbin/pflogd/pflogd.c [new file with mode: 0644]
usr.sbin/pflogd/pflogd.h [new file with mode: 0644]
usr.sbin/pflogd/privsep.c [new file with mode: 0644]
usr.sbin/pflogd/privsep_fdpass.c [new file with mode: 0644]

index 86fa2ea..2ad2050 100644 (file)
@@ -21,7 +21,7 @@
 #
 #      @(#)MAKEDEV     5.2 (Berkeley) 6/22/90
 # $FreeBSD: src/etc/MAKEDEV,v 1.243.2.57 2003/02/10 11:35:53 simokawa Exp $
-# $DragonFly: src/etc/MAKEDEV,v 1.9 2004/07/05 00:22:38 dillon Exp $
+# $DragonFly: src/etc/MAKEDEV,v 1.10 2004/09/21 21:25:28 joerg Exp $
 #
 # Device "make" file.  Valid arguments:
 #      all     makes all known devices, standard number of units (or close)
 #      agpgart AGP interface
 #      cfs*    Coda Distributed Filesystem
 #      nsmb*   SMB/CIFS protocol interface
+#      pf      PF packet filter
 #
 
 if [ -n "$MAKEDEVPATH" ]; then
@@ -351,6 +352,7 @@ all)
        sh MAKEDEV twed0                                # 3ware
        sh MAKEDEV crypto                               # cdev, crypto
        sh MAKEDEV fw0 fw1 fw2 fw3 fwmem0               # cdev, firewire
+       sh MAKEDEV pf                                   # pf
        ;;
 
 # a much restricted set of the above, to save precious i-nodes on the
@@ -1167,7 +1169,10 @@ crypto)
        mknod crypto c 70 0 root:operator
        chmod 666 crypto
        ;;
-
+pf)
+       mknod of c 73 0 root:operator
+       chmod 600 pf
+       ;;
 fwmem*)
        unit=`expr $i : 'fwmem\(.*\)'`
        mknod fwmem$unit c 127 $((1<<24 | $unit)) root:operator
index d674823..8be48a2 100644 (file)
@@ -1,12 +1,13 @@
 #      from: @(#)Makefile      5.11 (Berkeley) 5/21/91
 # $FreeBSD: src/etc/Makefile,v 1.219.2.38 2003/03/04 09:49:00 ru Exp $
-# $DragonFly: src/etc/Makefile,v 1.28 2004/09/21 03:25:16 dillon Exp $
+# $DragonFly: src/etc/Makefile,v 1.29 2004/09/21 21:25:28 joerg Exp $
 
 .if !defined(NO_SENDMAIL)
 SUBDIR=        sendmail
 .endif
 
 BINUPDATE= apmd.conf fbtab gettytab network.subr \
+       pf.os \
        protocols \
        rc rc.firewall rc.firewall6 \
        rc.sendmail rc.shutdown \
@@ -22,7 +23,7 @@ BIN1= ${BINUPDATE} \
        hosts hosts.allow host.conf hosts.equiv hosts.lpd \
        inetd.conf login.access login.conf \
        motd modems networks newsyslog.conf \
-       pam.conf phones printcap profile \
+       pam.conf pf.conf phones printcap profile \
        remote \
        shells sysctl.conf syslog.conf usbd.conf \
        etc.${MACHINE_ARCH}/ttys \
index fcc19a1..1d9e618 100644 (file)
@@ -14,7 +14,7 @@
 # All arguments must be in double or single quotes.
 #
 # $FreeBSD: src/etc/defaults/rc.conf,v 1.180 2003/06/26 09:50:50 smkelly Exp $
-# $DragonFly: src/etc/defaults/rc.conf,v 1.13 2004/07/28 17:55:46 dillon Exp $
+# $DragonFly: src/etc/defaults/rc.conf,v 1.14 2004/09/21 21:25:28 joerg Exp $
 
 ##############################################################
 ###  Important initial Boot-time options  ####################
@@ -86,6 +86,14 @@ ipfs_enable="NO"             # Set to YES to enable saving and restoring
                                # of state tables at shutdown and boot
 ipfs_program="/sbin/ipfs"      # where the ipfs program lives
 ipfs_flags=""                  # additional flags for ipfs
+pf_enable="NO"                 # Set to YES to enable packet filter (pf)
+pf_rules="/etc/pf.conf"                # rules definition file for pf
+pf_program="/sbin/pfctl"       # where the pfctl program lives
+pf_flags=""                    # additional flags for pfctl
+pflog_enable="NO"              # Set to YES to enable packet filter logging
+pflog_logfile="/var/log/pflog" # where pflogd shoule store the logfile
+pflog_program="/sbin/pflogd"   # where the pflogd program lives
+pflog_flags=""                 # additional flags for pflogd
 tcp_extensions="YES"           # Set to NO to turn off RFC1323 extensions.
 log_in_vain="0"                        # >=1 to log connects to ports w/o listeners.
 tcp_keepalive="YES"            # Enable stale TCP connection timeout (or NO).
index 9355e84..e989cf7 100644 (file)
@@ -1,5 +1,5 @@
 # $FreeBSD: src/etc/ftpusers,v 1.6.2.3 2002/06/30 17:57:17 des Exp $
-# $DragonFly: src/etc/ftpusers,v 1.3 2004/07/10 22:15:32 dillon Exp $
+# $DragonFly: src/etc/ftpusers,v 1.4 2004/09/21 21:25:28 joerg Exp $
 #
 # list of users disallowed any ftp access.
 # read by ftpd(8).
@@ -16,6 +16,8 @@ news
 man
 sshd
 bind
+proxy
+_pflogd
 uucp
 xten
 pop
index 3355e2e..bf78ad6 100644 (file)
--- a/etc/group
+++ b/etc/group
@@ -1,5 +1,5 @@
 # $FreeBSD: src/etc/group,v 1.19.2.3 2002/06/30 17:57:17 des Exp $
-# $DragonFly: src/etc/group,v 1.2 2003/06/17 04:24:45 dillon Exp $
+# $DragonFly: src/etc/group,v 1.3 2004/09/21 21:25:28 joerg Exp $
 #
 wheel:*:0:root
 daemon:*:1:daemon
@@ -18,6 +18,9 @@ smmsp:*:25:
 mailnull:*:26:
 guest:*:31:root
 bind:*:53:
+proxy:*:62:
+authpf:*:63:
+_pflogd:*:64:
 uucp:*:66:
 xten:*:67:xten
 dialer:*:68:
index 71bef73..6e3436a 100644 (file)
@@ -1,5 +1,5 @@
 # $FreeBSD: src/etc/inetd.conf,v 1.44.2.17 2003/06/13 10:43:19 yar Exp $
-# $DragonFly: src/etc/inetd.conf,v 1.2 2003/06/17 04:24:45 dillon Exp $
+# $DragonFly: src/etc/inetd.conf,v 1.3 2004/09/21 21:25:28 joerg Exp $
 #
 # Internet server configuration database
 #
 #netbios-ssn stream tcp        nowait          root    /usr/local/sbin/smbd    smbd
 #netbios-ns dgram udp  wait            root    /usr/local/sbin/nmbd    nmbd
 #swat  stream  tcp     nowait/400      root    /usr/local/sbin/swat    swat
+#
+# Enable the following entry to enable ftp-proxy to NAT ftp sessions with pf
+# N.B.: inetd binds to * in the default installation so you should add
+#      an appropriate block rule to your pf.conf
+#
+#ftp-proxy     stream  tcp     nowait  root    /usr/libexec/ftp-proxy  ftp-proxy
index ed97366..2edafbc 100644 (file)
@@ -1,5 +1,5 @@
 # $FreeBSD: src/etc/mail/aliases,v 1.10.4.7 2003/04/04 06:15:55 gshapiro Exp $
-# $DragonFly: src/etc/mail/aliases,v 1.2 2003/06/17 04:24:47 dillon Exp $
+# $DragonFly: src/etc/mail/aliases,v 1.3 2004/09/21 21:25:28 joerg Exp $
 #      @(#)aliases     5.3 (Berkeley) 5/24/90
 #
 #  Aliases in this file will NOT be expanded in the header from
@@ -24,6 +24,7 @@ MAILER-DAEMON: postmaster
 postmaster: root
 
 # General redirections for pseudo accounts
+_pflogd: root
 bin:   root
 bind:  root
 daemon:        root
@@ -35,6 +36,7 @@ news: root
 nobody:        root
 operator: root
 pop:   root
+proxy: root
 smmsp: postmaster
 sshd:  root
 system:        root
index b8a9899..e2b63d6 100644 (file)
@@ -1,5 +1,5 @@
 # $FreeBSD: src/etc/master.passwd,v 1.25.2.6 2002/06/30 17:57:17 des Exp $
-# $DragonFly: src/etc/master.passwd,v 1.2 2003/06/17 04:24:45 dillon Exp $
+# $DragonFly: src/etc/master.passwd,v 1.3 2004/09/21 21:25:28 joerg Exp $
 #
 root::0:0::0:0:Charlie &:/root:/bin/csh
 toor:*:0:0::0:0:Bourne-again Superuser:/root:
@@ -15,6 +15,8 @@ sshd:*:22:22::0:0:Secure Shell Daemon:/var/empty:/sbin/nologin
 smmsp:*:25:25::0:0:Sendmail Submission User:/var/spool/clientmqueue:/sbin/nologin
 mailnull:*:26:26::0:0:Sendmail Default User:/var/spool/mqueue:/sbin/nologin
 bind:*:53:53::0:0:Bind Sandbox:/:/sbin/nologin
+proxy:*:62:62::0:0:Packet Filter pseudo-user:/nonexistent:/usr/sbin/nologin
+_pflogd:*:64:64::0:0:pflogd privsep user:/var/empty:/usr/sbin/nologin
 uucp:*:66:66::0:0:UUCP pseudo-user:/var/spool/uucppublic:/usr/libexec/uucp/uucico
 xten:*:67:67::0:0:X-10 daemon:/usr/local/xten:/sbin/nologin
 pop:*:68:6::0:0:Post Office Owner:/nonexistent:/sbin/nologin
index cc59f54..518c296 100644 (file)
@@ -1,6 +1,6 @@
 # configuration file for newsyslog
 # $FreeBSD: src/etc/newsyslog.conf,v 1.25.2.12 2003/05/12 23:15:33 gad Exp $
-# $DragonFly: src/etc/newsyslog.conf,v 1.2 2003/06/17 04:24:45 dillon Exp $
+# $DragonFly: src/etc/newsyslog.conf,v 1.3 2004/09/21 21:25:28 joerg Exp $
 #
 # Entries which do not specify the '/pid_file' field will cause the
 # syslogd process to be signalled when that log file is rotated.  This
@@ -28,6 +28,7 @@
 /var/log/messages                      644  5     100  *     Z
 /var/log/all.log                       600  7     *    @T00  Z
 /var/log/slip.log      root:network    640  3     100  *     Z
+/var/log/pflog                         600  3     100  *     ZB    /var/run/pflogd.pid
 /var/log/ppp.log       root:network    640  3     100  *     Z
 /var/log/security                      600  10    100  *     Z
 /var/log/wtmp                          644  3     *    @01T05 B
diff --git a/etc/pf.conf b/etc/pf.conf
new file mode 100644 (file)
index 0000000..5564cbe
--- /dev/null
@@ -0,0 +1,30 @@
+#      $OpenBSD: pf.conf,v 1.25 2004/01/29 18:54:29 todd Exp $
+#      $DragonFly: src/etc/pf.conf,v 1.1 2004/09/21 21:25:28 joerg Exp $
+#
+# See pf.conf(5) and /usr/share/pf for syntax and examples.
+
+#ext_if="ext0"
+#int_if="int0"
+
+#table <spamd> persist
+#table <spamd-white> persist
+
+#scrub in
+
+#nat on $ext_if from !($ext_if) -> ($ext_if:0)
+#rdr pass on $int_if proto tcp to port ftp -> 127.0.0.1 port 8021
+#rdr pass on $ext_if proto tcp from <spamd> to port smtp \
+#      -> 127.0.0.1 port spamd
+#rdr pass on $ext_if proto tcp from !<spamd-white> to port smtp \
+#      -> 127.0.0.1 port spamd
+
+#block in
+#pass out keep state
+
+#pass quick on { lo $int_if }
+#antispoof quick for { lo $int_if }
+
+#pass in on $ext_if proto tcp to ($ext_if) port ssh keep state
+#pass in on $ext_if proto tcp to ($ext_if) port > 49151 user proxy keep state
+#pass in log on $ext_if proto tcp to ($ext_if) port smtp keep state
+#pass out log on $ext_if proto tcp from ($ext_if) to port smtp keep state
diff --git a/etc/pf.os b/etc/pf.os
new file mode 100644 (file)
index 0000000..cbd1d7d
--- /dev/null
+++ b/etc/pf.os
@@ -0,0 +1,644 @@
+# $OpenBSD: pf.os,v 1.15 2004/03/10 00:39:25 frantzen Exp $
+# $DragonFly: src/etc/pf.os,v 1.1 2004/09/21 21:25:28 joerg Exp $
+#
+# passive OS fingerprinting
+# -------------------------
+#
+# SYN signatures. Those signatures work for SYN packets only (duh!).
+#
+# (C) Copyright 2000-2003 by Michal Zalewski <lcamtuf@coredump.cx>
+# (C) Copyright 2003 by 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.
+#
+#
+# This fingerprint database is adapted from Michal Zalewski's p0f passive
+# operating system package.  The last database sync was from a Nov 3 2003
+# p0f.fp.
+#
+#
+# Each line in this file specifies a single fingerprint. Please read the
+# information below carefully before attempting to append any signatures
+# reported as UNKNOWN to this file to avoid mistakes.
+#
+# We use the following set metrics for fingerprinting:
+#
+# - Window size (WSS) - a highly OS dependent setting used for TCP/IP
+#   performance control (max. amount of data to be sent without ACK).
+#   Some systems use a fixed value for initial packets. On other
+#   systems, it is a multiple of MSS or MTU (MSS+40). In some rare
+#   cases, the value is just arbitrary.
+#
+#   NEW SIGNATURE: if p0f reported a special value of 'Snn', the number
+#   appears to be a multiple of MSS (MSS*nn); a special value of 'Tnn'
+#   means it is a multiple of MTU ((MSS+40)*nn). Unless you notice the
+#   value of nn is not fixed (unlikely), just copy the Snn or Tnn token
+#   literally. If you know this device has a simple stack and a fixed
+#   MTU, you can however multiply S value by MSS, or T value by MSS+40,
+#   and put it instead of Snn or Tnn.
+#
+#   If WSS otherwise looks like a fixed value (for example a multiple
+#   of two), or if you can confirm the value is fixed, please quote
+#   it literally. If there's no apparent pattern in WSS chosen, you
+#   should consider wildcarding this value.
+#
+# - Overall packet size - a function of all IP and TCP options and bugs.
+#
+#   NEW SIGNATURE: Copy this value literally.
+#
+# - Initial TTL - We check the actual TTL of a received packet. It can't
+#   be higher than the initial TTL, and also shouldn't be dramatically
+#   lower (maximum distance is defined as 40 hops).
+#
+#   NEW SIGNATURE: *Never* copy TTL from a p0f-reported signature literally.
+#   You need to determine the initial TTL. The best way to do it is to
+#   check the documentation for a remote system, or check its settings.
+#   A fairly good method is to simply round the observed TTL up to
+#   32, 64, 128, or 255, but it should be noted that some obscure devices
+#   might not use round TTLs (in particular, some shoddy appliances use
+#   "original" initial TTL settings). If not sure, you can see how many
+#   hops you're away from the remote party with traceroute or mtr.
+#
+# - Don't fragment flag (DF) - some modern OSes set this to implement PMTU
+#   discovery. Others do not bother.
+#
+#   NEW SIGNATURE: Copy this value literally.
+#
+# - Maximum segment size (MSS) - this setting is usually link-dependent. P0f
+#   uses it to determine link type of the remote host.
+#
+#   NEW SIGNATURE: Always wildcard this value, except for rare cases when
+#   you have an appliance with a fixed value, know the system supports only
+#   a very limited number of network interface types, or know the system
+#   is using a value it pulled out of nowhere.  Specific unique MSS
+#   can be used to tell Google crawlbots from the rest of the population.
+#
+# - Window scaling (WSCALE) - this feature is used to scale WSS.
+#   It extends the size of a TCP/IP window to 32 bits. Some modern
+#   systems implement this feature.
+#
+#   NEW SIGNATURE: Observe several signatures. Initial WSCALE is often set
+#   to zero or other low value. There's usually no need to wildcard this
+#   parameter.
+#
+# - Timestamp - some systems that implement timestamps set them to
+#   zero in the initial SYN. This case is detected and handled appropriately.
+#
+# - Selective ACK permitted - a flag set by systems that implement
+#   selective ACK functionality.
+#
+# - The sequence of TCP all options (MSS, window scaling, selective ACK
+#   permitted, timestamp, NOP). Other than the options previously
+#   discussed, p0f also checks for timestamp option (a silly
+#   extension to broadcast your uptime ;-), NOP options (used for
+#   header padding) and sackOK option (selective ACK feature).
+#
+#   NEW SIGNATURE: Copy the sequence literally.
+#
+# To wildcard any value (except for initial TTL or TCP options), replace
+# it with '*'. You can also use a modulo operator to match any values
+# that divide by nnn - '%nnn'.
+#
+# Fingerprint entry format:
+#
+# wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details
+#
+# wwww     - window size (can be *, %nnn, Snn or Tnn).  The special values
+#            "S" and "T" which are a multiple of MSS or a multiple of MTU
+#            respectively.
+# ttt      - initial TTL
+# D        - don't fragment bit (0 - not set, 1 - set)
+# ss       - overall SYN packet size
+# OOO      - option value and order specification (see below)
+# OS       - OS genre (Linux, Solaris, Windows)
+# Version  - OS Version (2.0.27 on x86, etc)
+# Subtype  - OS subtype or patchlevel (SP3, lo0)
+# details  - Generic OS details
+#
+# If OS genre starts with '*', p0f will not show distance, link type
+# and timestamp data. It is useful for userland TCP/IP stacks of
+# network scanners and so on, where many settings are randomized or
+# bogus.
+#
+# If OS genre starts with @, it denotes an approximate hit for a group
+# of operating systems (signature reporting still enabled in this case).
+# Use this feature at the end of this file to catch cases for which
+# you don't have a precise match, but can tell it's Windows or FreeBSD
+# or whatnot by looking at, say, flag layout alone.
+#
+# Option block description is a list of comma or space separated
+# options in the order they appear in the packet:
+#
+# N       - NOP option
+# Wnnn    - window scaling option, value nnn (or * or %nnn)
+# Mnnn    - maximum segment size option, value nnn (or * or %nnn)
+# S       - selective ACK OK
+# T       - timestamp
+# T0      - timestamp with a zero value
+#
+# To denote no TCP options, use a single '.'.
+#
+# Please report any additions to this file, or any inaccuracies or
+# problems spotted, to the maintainers: lcamtuf@coredump.cx,
+# frantzen@openbsd.org and bugs@openbsd.org with a tcpdump packet
+# capture of the relevant SYN packet(s)
+#
+# A test and submission page is available at 
+# http://lcamtuf.coredump.cx/p0f-help/
+#
+#
+# WARNING WARNING WARNING
+# -----------------------
+#
+# Do not add a system X as OS Y just because NMAP says so. It is often
+# the case that X is a NAT firewall. While nmap is talking to the
+# device itself, p0f is fingerprinting the guy behind the firewall
+# instead.
+#
+# When in doubt, use common sense, don't add something that looks like
+# a completely different system as Linux or FreeBSD or LinkSys router.
+# Check DNS name, establish a connection to the remote host and look
+# at SYN+ACK - does it look similar?
+#
+# Some users tweak their TCP/IP settings - enable or disable RFC1323
+# functionality, enable or disable timestamps or selective ACK,
+# disable PMTU discovery, change MTU and so on. Always compare a new rule
+# to other fingerprints for this system, and verify the system isn't
+# "customized" before adding it. It is OK to add signature variants
+# caused by a commonly used software (personal firewalls, security
+# packages, etc), but it makes no sense to try to add every single
+# possible /proc/sys/net/ipv4 tweak on Linux or so.
+#
+# KEEP IN MIND: Some packet firewalls configured to normalize outgoing
+# traffic (OpenBSD pf with "scrub" enabled, for example) will, well,
+# normalize packets. Signatures will not correspond to the originating
+# system (and probably not quite to the firewall either).
+#
+# NOTE: Try to keep this file in some reasonable order, from most to
+# least likely systems. This will speed up operation. Also keep most
+# generic and broad rules near the end.
+#
+
+##########################
+# Standard OS signatures #
+##########################
+
+# ----------------- AIX ---------------------
+
+# AIX is first because its signatures are close to NetBSD, MacOS X and
+# Linux 2.0, but it uses a fairly rare MSSes, at least sometimes...
+# This is a shoddy hack, though.
+
+45046:64:0:44:M*:              AIX:4.3::AIX 4.3
+16384:64:0:44:M512:            AIX:4.3:2-3:AIX 4.3.2 and earlier
+
+16384:64:0:60:M512,N,W%2,N,N,T:                AIX:4.3:3:AIX 4.3.3-5.2
+16384:64:0:60:M512,N,W%2,N,N,T:                AIX:5.1-5.2::AIX 4.3.3-5.2
+32768:64:0:60:M512,N,W%2,N,N,T:                AIX:4.3:3:AIX 4.3.3-5.2
+32768:64:0:60:M512,N,W%2,N,N,T:                AIX:5.1-5.2::AIX 4.3.3-5.2
+65535:64:0:60:M512,N,W%2,N,N,T:                AIX:4.3:3:AIX 4.3.3-5.2
+65535:64:0:60:M512,N,W%2,N,N,T:                AIX:5.1-5.2::AIX 4.3.3-5.2
+65535:64:0:64:M*,N,W1,N,N,T,N,N,S:     AIX:5.3:ML1:AIX 5.3 ML1
+
+# ----------------- Linux -------------------
+
+# S1:64:0:44:M*:A:             Linux:1.2::Linux 1.2.x (XXX quirks support)
+512:64:0:44:M*:                        Linux:2.0:3x:Linux 2.0.3x
+16384:64:0:44:M*:              Linux:2.0:3x:Linux 2.0.3x
+
+# Endian snafu! Nelson says "ha-ha":
+2:64:0:44:M*:                  Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac
+64:64:0:44:M*:                 Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac
+
+
+S4:64:1:60:M1360,S,T,N,W0:     Linux:google::Linux (Google crawlbot)
+
+S2:64:1:60:M*,S,T,N,W0:                Linux:2.4::Linux 2.4 (big boy)
+S3:64:1:60:M*,S,T,N,W0:                Linux:2.4:18-21:Linux 2.4.18 and newer
+S4:64:1:60:M*,S,T,N,W0:                Linux:2.4::Linux 2.4/2.6
+S4:64:1:60:M*,S,T,N,W0:                Linux:2.6::Linux 2.4/2.6
+
+S3:64:1:60:M*,S,T,N,W1:                Linux:2.5::Linux 2.5 (sometimes 2.4)
+S4:64:1:60:M*,S,T,N,W1:                Linux:2.5-2.6::Linux 2.5/2.6
+S3:64:1:60:M*,S,T,N,W2:                Linux:2.5::Linux 2.5 (sometimes 2.4)
+S4:64:1:60:M*,S,T,N,W2:                Linux:2.5::Linux 2.5 (sometimes 2.4)
+
+S20:64:1:60:M*,S,T,N,W0:       Linux:2.2:20-25:Linux 2.2.20 and newer
+S22:64:1:60:M*,S,T,N,W0:       Linux:2.2::Linux 2.2
+S11:64:1:60:M*,S,T,N,W0:       Linux:2.2::Linux 2.2
+
+# Popular cluster config scripts disable timestamps and
+# selective ACK:
+S4:64:1:48:M1460,N,W0:         Linux:2.4:cluster:Linux 2.4 in cluster
+
+# This needs to be investigated. On some systems, WSS
+# is selected as a multiple of MTU instead of MSS. I got
+# many submissions for this for many late versions of 2.4:
+T4:64:1:60:M1412,S,T,N,W0:     Linux:2.4::Linux 2.4 (late, uncommon)
+
+# This happens only over loopback, but let's make folks happy:
+32767:64:1:60:M16396,S,T,N,W0: Linux:2.4:lo0:Linux 2.4 (local)
+S8:64:1:60:M3884,S,T,N,W0:     Linux:2.2:lo0:Linux 2.2 (local)
+
+# Opera visitors:
+16384:64:1:60:M*,S,T,N,W0:     Linux:2.2:Opera:Linux 2.2 (Opera?)
+32767:64:1:60:M*,S,T,N,W0:     Linux:2.4:Opera:Linux 2.4 (Opera?)
+
+# Some fairly common mods:
+S4:64:1:52:M*,N,N,S,N,W0:      Linux:2.4:ts:Linux 2.4 w/o timestamps
+S22:64:1:52:M*,N,N,S,N,W0:     Linux:2.2:ts:Linux 2.2 w/o timestamps
+
+
+# ----------------- FreeBSD -----------------
+
+16384:64:1:44:M*:              FreeBSD:2.0-2.2::FreeBSD 2.0-4.1
+16384:64:1:44:M*:              FreeBSD:3.0-3.5::FreeBSD 2.0-4.1
+16384:64:1:44:M*:              FreeBSD:4.0-4.1::FreeBSD 2.0-4.1
+16384:64:1:60:M*,N,W0,N,N,T:   FreeBSD:4.4::FreeBSD 4.4
+
+1024:64:1:60:M*,N,W0,N,N,T:    FreeBSD:4.4::FreeBSD 4.4
+
+57344:64:1:44:M*:              FreeBSD:4.6-4.8:noRFC1323:FreeBSD 4.6-4.8 (no RFC1323)
+57344:64:1:60:M*,N,W0,N,N,T:   FreeBSD:4.6-4.8::FreeBSD 4.6-4.8
+
+32768:64:1:60:M*,N,W0,N,N,T:   FreeBSD:4.8-4.9::FreeBSD 4.8-5.1 (or MacOS X)
+32768:64:1:60:M*,N,W0,N,N,T:   FreeBSD:5.0-5.1::FreeBSD 4.8-5.1 (or MacOS X)
+65535:64:1:60:M*,N,W0,N,N,T:   FreeBSD:4.8-4.9::FreeBSD 4.8-5.1 (or MacOS X)
+65535:64:1:60:M*,N,W0,N,N,T:   FreeBSD:5.0-5.1::FreeBSD 4.8-5.1 (or MacOS X)
+65535:64:1:60:M*,N,W1,N,N,T:   FreeBSD:4.7-4.9::FreeBSD 4.7-5.1
+65535:64:1:60:M*,N,W1,N,N,T:   FreeBSD:5.0-5.1::FreeBSD 4.7-5.1
+
+# XXX need quirks support
+# 65535:64:1:60:M*,N,W0,N,N,T:Z:FreeBSD:5.1-current (1)
+# 65535:64:1:60:M*,N,W1,N,N,T:Z:FreeBSD:5.1-current (2)
+# 65535:64:1:60:M*,N,W2,N,N,T:Z:FreeBSD:5.1-current (3)
+
+# 16384:64:1:60:M*,N,N,N,N,N,N,T:FreeBSD:4.4:noTS:FreeBSD 4.4 (w/o timestamps)
+
+# ----------------- NetBSD ------------------
+
+16384:64:0:60:M*,N,W0,N,N,T:   NetBSD:1.3::NetBSD 1.3
+65535:64:0:60:M*,N,W0,N,N,T0:  NetBSD:1.6:opera:NetBSD 1.6 (Opera)
+16384:64:0:60:M*,N,W0,N,N,T0:  NetBSD:1.6::NetBSD 1.6
+16384:64:1:60:M*,N,W0,N,N,T0:  NetBSD:1.6:df:NetBSD 1.6 (DF)
+65535:64:1:60:M*,N,W1,N,N,T0:  NetBSD:1.6::NetBSD 1.6W-current (DF)
+65535:64:1:60:M*,N,W0,N,N,T0:  NetBSD:1.6::NetBSD 1.6X (DF)
+32768:64:1:60:M*,N,W0,N,N,T0:  NetBSD:1.6:randomization:NetBSD 1.6ZH-current (w/ ip_id randomization)
+
+# ----------------- OpenBSD -----------------
+
+16384:64:0:60:M*,N,W0,N,N,T:           OpenBSD:2.6::NetBSD 1.3 (or OpenBSD 2.6)
+16384:64:1:64:M*,N,N,S,N,W0,N,N,T:     OpenBSD:3.0-3.5::OpenBSD 3.0-3.5
+16384:64:0:64:M*,N,N,S,N,W0,N,N,T:     OpenBSD:3.0-3.5:no-df:OpenBSD 3.0-3.5 (scrub no-df)
+57344:64:1:64:M*,N,N,S,N,W0,N,N,T:     OpenBSD:3.3-3.5::OpenBSD 3.3-3.5
+57344:64:0:64:M*,N,N,S,N,W0,N,N,T:     OpenBSD:3.3-3.5:no-df:OpenBSD 3.3-3.5 (scrub no-df)
+
+65535:64:1:64:M*,N,N,S,N,W0,N,N,T:     OpenBSD:3.0-3.5:opera:OpenBSD 3.0-3.5 (Opera)
+
+# ----------------- Solaris -----------------
+
+S17:64:1:64:N,W3,N,N,T0,N,N,S,M*:      Solaris:8:RFC1323:Solaris 8 RFC1323
+S17:64:1:48:N,N,S,M*:                  Solaris:8::Solaris 8
+S17:255:1:44:M*:                       Solaris:2.5-2.7::Solaris 2.5 to 7
+
+S6:255:1:44:M*:                                Solaris:2.6-2.7::Solaris 2.6 to 7
+S23:255:1:44:M*:                       Solaris:2.5:1:Solaris 2.5.1
+S34:64:1:48:M*,N,N,S:                  Solaris:2.9::Solaris 9
+S44:255:1:44:M*:                       Solaris:2.7::Solaris 7
+
+4096:64:0:44:M1460:                    SunOS:4.1::SunOS 4.1.x
+
+S34:64:1:52:M*,N,W0,N,N,S:             Solaris:10::Solaris 10 (beta)
+
+# ----------------- IRIX --------------------
+
+49152:64:0:44:M*:                      IRIX:6.4::IRIX 6.4
+61440:64:0:44:M*:                      IRIX:6.2-6.5::IRIX 6.2-6.5
+49152:64:0:52:M*,N,W2,N,N,S:           IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323)
+49152:64:0:52:M*,N,W3,N,N,S:           IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323)
+
+61440:64:0:48:M*,N,N,S:                        IRIX:6.5:12-21:IRIX 6.5.12 - 6.5.21
+49152:64:0:48:M*,N,N,S:                        IRIX:6.5:15-21:IRIX 6.5.15 - 6.5.21
+
+# ----------------- Tru64 -------------------
+
+32768:64:1:48:M*,N,W0:                 Tru64:4.0::Tru64 4.0 (or OS/2 Warp 4)
+32768:64:0:48:M*,N,W0:                 Tru64:5.0::Tru64 5.0
+8192:64:0:44:M1460:                    Tru64:5.1:noRFC1323:Tru64 6.1 (no RFC1323) (or QNX 6)
+61440:64:0:48:M*,N,W0:                 Tru64:5.1a:JP4:Tru64 v5.1a JP4 (or OpenVMS 7.x on Compaq 5.x stack)
+
+# ----------------- OpenVMS -----------------
+
+6144:64:1:60:M*,N,W0,N,N,T:            OpenVMS:7.2::OpenVMS 7.2 (Multinet 4.4 stack)
+
+# ----------------- MacOS -------------------
+
+# XXX Need EOL tcp opt support
+# S2:255:1:48:M*,W0,E:.:MacOS:8.6 classic
+
+# XXX some of these use EOL too
+16616:255:1:48:M*,W0:                  MacOS:7.3-7.6:OTTCP:MacOS 7.3-8.6 (OTTCP)
+16616:255:1:48:M*,W0:                  MacOS:8.0-8.6:OTTCP:MacOS 7.3-8.6 (OTTCP)
+16616:255:1:48:M*,N,N,N:               MacOS:8.1-8.6:OTTCP:MacOS 8.1-8.6 (OTTCP)
+32768:255:1:48:M*,W0,N:                        MacOS:9.0-9.2::MacOS 9.0-9.2
+65535:255:1:48:M*,N,N,N,N:             MacOS:9.1::MacOS 9.1 (OT 2.7.4)
+
+
+# ----------------- Windows -----------------
+
+# Windows TCP/IP stack is a mess. For most recent XP, 2000 and
+# even 98, the pathlevel, not the actual OS version, is more
+# relevant to the signature. They share the same code, so it would
+# seem. Luckily for us, almost all Windows 9x boxes have an
+# awkward MSS of 536, which I use to tell one from another
+# in most difficult cases.
+
+8192:32:1:44:M*:                       Windows:3.11::Windows 3.11 (Tucows)
+S44:64:1:64:M*,N,W0,N,N,T0,N,N,S:      Windows:95::Windows 95
+8192:128:1:64:M*,N,W0,N,N,T0,N,N,S:    Windows:95:b:Windows 95b
+
+# There were so many tweaking tools and so many stack versions for
+# Windows 98 it is no longer possible to tell them from each other
+# without some very serious research. Until then, there's an insane
+# number of signatures, for your amusement:
+
+S44:32:1:48:M*,N,N,S:                  Windows:98:lowTTL:Windows 98 (low TTL)
+8192:32:1:48:M*,N,N,S:                 Windows:98:lowTTL:Windows 98 (low TTL)
+%8192:64:1:48:M536,N,N,S:              Windows:98::Windows 98
+%8192:128:1:48:M536,N,N,S:             Windows:98::Windows 98
+S4:64:1:48:M*,N,N,S:                   Windows:98::Windows 98
+S6:64:1:48:M*,N,N,S:                   Windows:98::Windows 98
+S12:64:1:48:M*,N,N,S:                  Windows:98::Windows 98
+T30:64:1:64:M1460,N,W0,N,N,T0,N,N,S:   Windows:98::Windows 98
+32767:64:1:48:M*,N,N,S:                        Windows:98::Windows 98
+37300:64:1:48:M*,N,N,S:                        Windows:98::Windows 98
+46080:64:1:52:M*,N,W3,N,N,S:           Windows:98:RFC1323:Windows 98 (RFC1323)
+65535:64:1:44:M*:                      Windows:98:noSack:Windows 98 (no sack)
+S16:128:1:48:M*,N,N,S:                 Windows:98::Windows 98
+S16:128:1:64:M*,N,W0,N,N,T0,N,N,S:     Windows:98::Windows 98
+S26:128:1:48:M*,N,N,S:                 Windows:98::Windows 98
+T30:128:1:48:M*,N,N,S:                 Windows:98::Windows 98
+32767:128:1:52:M*,N,W0,N,N,S:          Windows:98::Windows 98
+60352:128:1:48:M*,N,N,S:               Windows:98::Windows 98
+60352:128:1:64:M*,N,W2,N,N,T0,N,N,S:   Windows:98::Windows 98
+
+# What's with 1414 on NT?
+T31:128:1:44:M1414:                    Windows:NT:4.0:Windows NT 4.0 SP6a
+64512:128:1:44:M1414:                  Windows:NT:4.0:Windows NT 4.0 SP6a
+8192:128:1:44:M*:                      Windows:NT:4.0:Windows NT 4.0 (older)
+
+# Windows XP and 2000. Most of the signatures that were
+# either dubious or non-specific (no service pack data)
+# were deleted and replaced with generics at the end.
+
+65535:128:1:48:M*,N,N,S:               Windows:2000:SP4:Windows 2000 SP4, XP SP1
+65535:128:1:48:M*,N,N,S:               Windows:XP:SP1:Windows 2000 SP4, XP SP1
+%8192:128:1:48:M*,N,N,S:               Windows:2000:SP2+:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222)
+%8192:128:1:48:M*,N,N,S:               Windows:XP:SP1:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222)
+S20:128:1:48:M*,N,N,S:                 Windows:2000::Windows 2000/XP SP3
+S20:128:1:48:M*,N,N,S:                 Windows:XP:SP3:Windows 2000/XP SP3
+S45:128:1:48:M*,N,N,S:                 Windows:2000:SP4:Windows 2000 SP4, XP SP 1
+S45:128:1:48:M*,N,N,S:                 Windows:XP:SP1:Windows 2000 SP4, XP SP 1
+40320:128:1:48:M*,N,N,S:               Windows:2000:SP4:Windows 2000 SP4
+
+S6:128:1:48:M*,N,N,S:                  Windows:2000:SP2:Windows XP, 2000 SP2+
+S6:128:1:48:M*,N,N,S:                  Windows:XP::Windows XP, 2000 SP2+
+S12:128:1:48:M*,N,N,S:                 Windows:XP:SP1:Windows XP SP1
+S44:128:1:48:M*,N,N,S:                 Windows:2000:SP3:Windows Pro SP1, 2000 SP3
+S44:128:1:48:M*,N,N,S:                 Windows:XP:SP1:Windows Pro SP1, 2000 SP3
+64512:128:1:48:M*,N,N,S:               Windows:2000:SP3:Windows SP1, 2000 SP3
+64512:128:1:48:M*,N,N,S:               Windows:XP:SP1:Windows SP1, 2000 SP3
+32767:128:1:48:M*,N,N,S:               Windows:2000:SP4:Windows SP1, 2000 SP4
+32767:128:1:48:M*,N,N,S:               Windows:XP:SP1:Windows SP1, 2000 SP4
+
+# Odds, ends, mods:
+
+S52:128:1:48:M1260,N,N,S:              Windows:2000:cisco:Windows XP/2000 via Cisco
+S52:128:1:48:M1260,N,N,S:              Windows:XP:cisco:Windows XP/2000 via Cisco
+65520:128:1:48:M*,N,N,S:               Windows:XP::Windows XP bare-bone
+16384:128:1:52:M536,N,W0,N,N,S:                Windows:2000:ZoneAlarm:Windows 2000 w/ZoneAlarm?
+2048:255:0:40:.:                       Windows:.NET::Windows .NET Enterprise Server
+
+# No need to be more specific, it passes:
+# *:128:1:48:M*,N,N,S:U:-Windows:XP/2000 while downloading (leak!) XXX quirk
+# there is an equiv similar generic sig w/o the quirk
+
+# ----------------- HP/UX -------------------
+
+32768:64:1:44:M*:                      HP-UX:B.10.20::HP-UX B.10.20
+32768:64:0:48:M*,W0,N:                 HP-UX:11.0::HP-UX 11.0
+32768:64:1:48:M*,W0,N:                 HP-UX:11.10::HP-UX 11.0 or 11.11
+32768:64:1:48:M*,W0,N:                 HP-UX:11.11::HP-UX 11.0 or 11.11
+
+# Whoa. Hardcore WSS.
+0:64:0:48:M*,W0,N:                     HP-UX:B.11.00:A:HP-UX B.11.00 A (RFC1323)
+
+
+# ----------------- RiscOS ------------------
+
+# We don't yet support the ?12 TCP option
+#16384:64:1:68:M1460,N,W0,N,N,T,N,N,?12:       RISCOS:3.70-4.36::RISC OS 3.70-4.36
+12288:32:0:44:M536:                            RISC OS:3.70:4.10:RISC OS 3.70 inet 4.10
+
+# XXX quirk
+# 4096:64:1:56:M1460,N,N,T:T:                  RISC OS:3.70:freenet:RISC OS 3.70 freenet 2.00
+
+
+# ----------------- BSD/OS ------------------
+
+# Once again, power of two WSS is also shared by MacOS X with DF set
+8192:64:1:60:M1460,N,W0,N,N,T:         BSD/OS:3.1::BSD/OS 3.1-4.3 (or MacOS X 10.2 w/DF)
+8192:64:1:60:M1460,N,W0,N,N,T:         BSD/OS:4.0-4.3::BSD/OS 3.1-4.3 (or MacOS X 10.2)
+
+
+# ---------------- NewtonOS -----------------
+
+4096:64:0:44:M1420:            NewtonOS:2.1::NewtonOS 2.1
+
+# ---------------- NeXTSTEP -----------------
+
+S8:64:0:44:M512:               NeXTSTEP:3.3::NeXTSTEP 3.3
+
+# ------------------ BeOS -------------------
+
+1024:255:0:48:M*,N,W0:         BeOS:5.0-5.1::BeOS 5.0-5.1
+12288:255:0:44:M1402:          BeOS:5.0::BeOS 5.0.x
+
+# ------------------ OS/400 -----------------
+
+8192:64:1:60:M1440,N,W0,N,N,T: OS/400:VR4::OS/400 VR4/R5
+8192:64:1:60:M1440,N,W0,N,N,T: OS/400:VR5::OS/400 VR4/R5
+4096:64:1:60:M1440,N,W0,N,N,T: OS/400:V4R5:CF67032:OS/400 V4R5 + CF67032
+
+# XXX quirk
+# 28672:64:0:44:M1460:A:OS/390:?
+
+# ------------------ ULTRIX -----------------
+
+16384:64:0:40:.:               ULTRIX:4.5::ULTRIX 4.5
+
+# ------------------- QNX -------------------
+
+S16:64:0:44:M512:              QNX:::QNX demodisk
+
+# ------------------ Novell -----------------
+
+16384:128:1:44:M1460:          Novell:NetWare:5.0:Novel Netware 5.0
+6144:128:1:44:M1460:           Novell:IntranetWare:4.11:Novell IntranetWare 4.11
+6144:128:1:44:M1368:           Novell:BorderManager::Novell BorderManager ?
+
+6144:128:1:52:M*,W0,N,S,N,N:   Novell:Netware:6:Novell Netware 6 SP3
+
+
+# ----------------- SCO ------------------
+S3:64:1:60:M1460,N,W0,N,N,T:   SCO:UnixWare:7.1:SCO UnixWare 7.1
+S23:64:1:44:M1380:             SCO:OpenServer:5.0:SCO OpenServer 5.0
+
+# ------------------- DOS -------------------
+
+2048:255:0:44:M536:            DOS:WATTCP:1.05:DOS Arachne via WATTCP/1.05
+
+# ------------------ OS/2 -------------------
+
+S56:64:0:44:M512:              OS/2:4::OS/2 4
+
+# ----------------- TOPS-20 -----------------
+
+# Another hardcore MSS, one of the ACK leakers hunted down.
+# XXX QUIRK 0:64:0:44:M1460:A:TOPS-20:version 7
+0:64:0:44:M1460:               TOPS-20:7::TOPS-20 version 7
+
+# ------------------ AMIGA ------------------
+
+# XXX TCP option 12
+# S32:64:1:56:M*,N,N,S,N,N,?12:.:AMIGA:3.9 BB2 with Miami stack
+
+# ------------------ Plan9 ------------------
+
+65535:255:0:48:M1460,W0,N:     Plan9:4::Plan9 edition 4
+
+# ----------------- AMIGAOS -----------------
+
+16384:64:1:48:M1560,N,N,S:     AMIGAOS:3.9::AMIGAOS 3.9 BB2 MiamiDX
+
+###########################################
+# Appliance / embedded / other signatures #
+###########################################
+
+# ---------- Firewalls / routers ------------
+
+S12:64:1:44:M1460:                     @Checkpoint:::Checkpoint (unknown 1)
+S12:64:1:48:N,N,S,M1460:               @Checkpoint:::Checkpoint (unknown 2)
+4096:32:0:44:M1460:                    ExtremeWare:4.x::ExtremeWare 4.x
+60352:64:0:52:M1460,N,W2,N,N,S:                Clavister:7::Clavister firewall 7.x
+
+# XXX TCP option 12
+# S32:64:0:68:M512,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO w/Checkpoint NG FP3
+# S16:64:0:68:M1024,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO 3.7 build 026
+
+S4:64:1:60:W0,N,S,T,M1460:             FortiNet:FortiGate:50:FortiNet FortiGate 50
+
+8192:64:1:44:M1460:                    Eagle:::Eagle Secure Gateway
+
+
+# ------- Switches and other stuff ----------
+
+4128:255:0:44:M*:                      Cisco:::Cisco Catalyst 3500, 7500 etc
+S8:255:0:44:M*:                                Cisco:12008::Cisco 12008
+60352:128:1:64:M1460,N,W2,N,N,T,N,N,S: Alteon:ACEswitch::Alteon ACEswitch
+64512:128:1:44:M1370:                  Nortel:Contivity Client::Nortel Conectivity Client
+
+
+# ---------- Caches and whatnots ------------
+
+S4:64:1:52:M1460,N,N,S,N,W0:           AOL:web cache::AOL web cache
+
+32850:64:1:64:N,W1,N,N,T,N,N,S,M*:     NetApp:5.x::NetApp Data OnTap 5.x
+16384:64:1:64:M1460,N,N,S,N,W0,N:      NetApp:5.3:1:NetApp 5.3.1
+65535:64:0:64:M1460,N,N,S,N,W*,N,N,T:  NetApp:5.3-5.5::NetApp 5.3-5.5
+65535:64:0:60:M1460,N,W0,N,N,T:                NetApp:CacheFlow::NetApp CacheFlow
+8192:64:1:64:M1460,N,N,S,N,W0,N,N,T:   NetApp:5.2:1:NetApp NetCache 5.2.1
+20480:64:1:64:M1460,N,N,S,N,W0,N,N,T:  NetApp:4.1::NetApp NetCache4.1
+
+65535:64:0:60:M1460,N,W0,N,N,T:                CacheFlow:4.1::CacheFlow CacheOS 4.1
+8192:64:0:60:M1380,N,N,N,N,N,N,T:      CacheFlow:1.1::CacheFlow CacheOS 1.1
+
+S4:64:0:48:M1460,N,N,S:                        Cisco:Content Engine::Cisco Content Engine
+
+27085:128:0:40:.:                      Dell:PowerApp cache::Dell PowerApp (Linux-based)
+
+65535:255:1:48:N,W1,M1460:             Inktomi:crawler::Inktomi crawler
+S1:255:1:60:M1460,S,T,N,W0:            LookSmart:ZyBorg::LookSmart ZyBorg
+
+16384:255:0:40:.:                      Proxyblocker:::Proxyblocker (what's this?)
+
+# ----------- Embedded systems --------------
+
+S9:255:0:44:M536:                      PalmOS:Tungsten:C:PalmOS Tungsten C
+S5:255:0:44:M536:                      PalmOS:3::PalmOS 3/4
+S5:255:0:44:M536:                      PalmOS:4::PalmOS 3/4
+S4:255:0:44:M536:                      PalmOS:3:5:PalmOS 3.5
+2948:255:0:44:M536:                    PalmOS:3:5:PalmOS 3.5.3 (Handera)
+S29:255:0:44:M536:                     PalmOS:5::PalmOS 5.0
+
+S23:64:1:64:N,W1,N,N,T,N,N,S,M1460:    SymbianOS:7::SymbianOS 7
+8192:255:0:44:M1460:                   SymbianOS:6048::SymbianOS 6048 (on Nokia 7650?)
+8192:255:0:44:M536:                    SymbianOS:::SymbianOS (on Nokia 9210?)
+
+
+# Perhaps S4?
+5840:64:1:60:M1452,S,T,N,W1:           Zaurus:3.10::Zaurus 3.10
+
+32768:128:1:64:M1460,N,W0,N,N,T0,N,N,S:        PocketPC:2002::PocketPC 2002
+
+S1:255:0:44:M346:                      Contiki:1.1:rc0:Contiki 1.1-rc0
+
+4096:128:0:44:M1460:                   Sega:Dreamcast:3.0:Sega Dreamcast Dreamkey 3.0
+T5:64:0:44:M536:                       Sega:Dreamcast:HKT-3020:Sega Dreamcast HKT-3020 (browser disc 51027)
+S22:64:1:44:M1460:                     Sony:PS2::Sony Playstation 2 (SOCOM?)
+
+S12:64:0:44:M1452:                     AXIS:5600:v5.64:AXIS Printer Server 5600 v5.64
+
+
+
+####################
+# Fancy signatures #
+####################
+
+1024:64:0:40:.:                                *NMAP:syn scan:1:NMAP syn scan (1)
+2048:64:0:40:.:                                *NMAP:syn scan:2:NMAP syn scan (2)
+3072:64:0:40:.:                                *NMAP:syn scan:3:NMAP syn scan (3)
+4096:64:0:40:.:                                *NMAP:syn scan:4:NMAP syn scan (4)
+
+1024:64:0:60:W10,N,M265,T:             *NMAP:OS:1:NMAP OS detection probe (1)
+2048:64:0:60:W10,N,M265,T:             *NMAP:OS:2:NMAP OS detection probe (2)
+3072:64:0:60:W10,N,M265,T:             *NMAP:OS:3:NMAP OS detection probe (3)
+4096:64:0:60:W10,N,M265,T:             *NMAP:OS:4:NMAP OS detection probe (4)
+
+#####################################
+# Generic signatures - just in case #
+#####################################
+
+#*:64:1:60:M*,N,W*,N,N,T:              @FreeBSD:4.0-4.9::FreeBSD 4.x/5.x
+#*:64:1:60:M*,N,W*,N,N,T:              @FreeBSD:5.0-5.1::FreeBSD 4.x/5.x
+
+*:128:1:52:M*,N,W0,N,N,S:              @Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:52:M*,N,W0,N,N,S:              @Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:64:M*,N,W0,N,N,T0,N,N,S:       @Windows:XP:RFC1323:Windows XP/2000 (RFC1323)
+*:128:1:64:M*,N,W0,N,N,T0,N,N,S:       @Windows:2000:RFC1323:Windows XP/2000 (RFC1323)
+*:128:1:64:M*,N,W*,N,N,T0,N,N,S:       @Windows:XP:RFC1323:Windows XP (RFC1323, w+)
+*:128:1:48:M536,N,N,S:                 @Windows:98::Windows 98
+*:128:1:48:M*,N,N,S:                   @Windows:XP::Windows XP/2000
+*:128:1:48:M*,N,N,S:                   @Windows:2000::Windows XP/2000
+
+
index 7316bf4..5fec765 100644 (file)
@@ -1,6 +1,6 @@
 # $NetBSD: Makefile,v 1.16 2001/01/14 15:37:22 minoura Exp $
 # $FreeBSD: src/etc/rc.d/Makefile,v 1.20 2003/06/29 05:15:57 mtm Exp $
-# $DragonFly: src/etc/rc.d/Makefile,v 1.6 2004/07/19 10:24:03 asmodai Exp $
+# $DragonFly: src/etc/rc.d/Makefile,v 1.7 2004/09/21 21:25:28 joerg Exp $
 
 .include <bsd.own.mk>
 
@@ -19,13 +19,12 @@ FILES=      DAEMON LOGIN NETWORKING SERVERS abi accounting addswap adjkerntz \
        mountd moused mroute6d mrouted msgs \
        named netif netoptions network network1 network2 network3 \
        network_ipv6 nfsclient nfsd nfslocking nfsserver nisdomain ntpd \
-       ntpdate othermta pccard pcvt ppp ppp-user pppoed pwcheck quota random \
-       rarpd rcconf.sh resident rndcontrol root route6d routed routing \
-       rpcbind rtadvd \
-       rwho sysdb savecore securelevel sendmail serial sppp sshd swap1 syscons\
-       sysctl syslogd timed ttys usbd vinum virecover watchdogd ypbind \
-       yppasswdd ypserv ypset ypupdated ypxfrd \
-       varsym wscons
+       ntpdate othermta pccard pcvt pf pflog ppp ppp-user pppoed pwcheck \
+       quota random rarpd rcconf.sh resident rndcontrol root route6d routed \
+       routing rpcbind rtadvd rwho sysdb savecore securelevel sendmail \
+       serial sppp sshd swap1 syscons sysctl syslogd timed ttys usbd \
+       vinum virecover watchdogd ypbind yppasswdd ypserv ypset ypupdated \
+       ypxfrd varsym wscons
 FILESDIR=      /etc/rc.d
 FILESMODE=     ${BINMODE}
 
diff --git a/etc/rc.d/pf b/etc/rc.d/pf
new file mode 100644 (file)
index 0000000..612797e
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+#
+# $FreeBSD: src/etc/rc.d/pf,v 1.3 2004/06/23 01:42:06 mlaier Exp $
+# $DragonFly: src/etc/rc.d/pf,v 1.1 2004/09/21 21:25:28 joerg Exp $
+#
+
+# PROVIDE: pf
+# REQUIRE: root beforenetlkm mountcritlocal netif pflog
+# BEFORE:  DAEMON LOGIN
+# KEYWORD: DragonFly nojail
+
+. /etc/rc.subr
+
+name="pf"
+rcvar=`set_rcvar`
+load_rc_config $name
+stop_precmd="test -f ${pf_rules}"
+start_precmd="pf_prestart"
+start_cmd="pf_start"
+stop_cmd="pf_stop"
+reload_precmd="$stop_precmd"
+reload_cmd="pf_reload"
+resync_precmd="$stop_precmd"
+resync_cmd="pf_resync"
+status_precmd="$stop_precmd"
+status_cmd="pf_status"
+extra_commands="reload resync status"
+
+pf_prestart()
+{
+       # load pf kernel module if needed
+       if ! kldstat -v | grep -q pf\$; then
+               if kldload pf; then
+                       info 'pf module loaded.'
+               else
+                       err 1 'pf module failed to load.'
+               fi
+       fi
+
+       # check for pf rules
+       if [ ! -r "${pf_rules}" ]
+       then
+               warn 'pf: NO PF RULESET FOUND'
+               return 1
+       fi
+}
+
+pf_start()
+{
+       echo "Enabling pf."
+       ${pf_program:-/sbin/pfctl} -Fa > /dev/null 2>&1
+       if [ -r "${pf_rules}" ]; then
+               ${pf_program:-/sbin/pfctl} \
+                   -f "${pf_rules}" ${pf_flags}
+       fi
+       if ! ${pf_program:-/sbin/pfctl} -si | grep -q "Enabled" ; then
+               ${pf_program:-/sbin/pfctl} -e
+       fi
+}
+
+pf_stop()
+{
+       if ${pf_program:-/sbin/pfctl} -si | grep -q "Enabled" ; then
+               echo "Disabling pf."
+               ${pf_program:-/sbin/pfctl} -d
+       fi
+}
+
+pf_reload()
+{
+       echo "Reloading pf rules."
+
+       ${pf_program:-/sbin/pfctl} -Fa > /dev/null 2>&1
+       if [ -r "${pf_rules}" ]; then
+               ${pf_program:-/sbin/pfctl} \
+                   -f "${pf_rules}" ${pf_flags}
+       fi
+}
+
+pf_resync()
+{
+       # Don't resync if pf is not loaded
+       if ! kldstat -v | grep -q pf\$ ; then
+                return
+       fi
+       ${pf_program:-/sbin/pfctl} -f "${pf_rules}" ${pf_flags}
+}
+
+pf_status()
+{
+       ${pf_program:-/sbin/pfctl} -si
+}
+
+run_rc_command "$1"
diff --git a/etc/rc.d/pflog b/etc/rc.d/pflog
new file mode 100644 (file)
index 0000000..44c96b2
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# $FreeBSD: src/etc/rc.d/pflog,v 1.1 2004/04/02 19:25:27 mlaier Exp $
+# $DragonFly: src/etc/rc.d/pflog,v 1.1 2004/09/21 21:25:28 joerg Exp $
+#
+
+# PROVIDE: pflog
+# REQUIRE: root beforenetlkm mountcritlocal netif
+# BEFORE:  DAEMON LOGIN
+# KEYWORD: DragonFly nojail
+
+. /etc/rc.subr
+
+name="pflog"
+rcvar=`set_rcvar`
+load_rc_config $name
+stop_precmd="test -x ${pflog_program}"
+start_precmd="pflog_prestart"
+start_cmd="pflog_start"
+stop_cmd="pflog_stop"
+resync_precmd="$stop_precmd"
+resync_cmd="pflog_resync"
+status_precmd="$stop_precmd"
+status_cmd="pflog_status"
+extra_commands="resync status"
+
+pflog_prestart()
+{
+       # load pflog kernel module if needed
+       if ! kldstat -v | grep -q pflog\$; then
+               if kldload pflog; then
+                       info 'pflog module loaded.'
+               else
+                       err 1 'pflog module failed to load.'
+               fi
+       fi
+
+       # set pflog0 interface to up state
+       if ! ifconfig pflog0 up; then
+               warn 'pflog: COULD NOT SET UP pflog0'
+       fi
+
+       # check for pf rules
+       if [ ! -x "${pflog_program:-/sbin/pflogd}" ]
+       then
+               warn 'pflog: NO PFLOGD BINARY FOUND'
+               return 1
+       fi
+}
+
+pflog_start()
+{
+       echo -n "Enabling pflogd"
+       if ! ${pflog_program:-/sbin/pflogd} ${pflog_flags} \
+           -f ${pflog_logfile:-/var/log/pflog}; then
+               echo " failed!"
+       else
+               echo "."
+       fi
+}
+
+pflog_stop()
+{
+       if [ -r /var/run/pflogd.pid ]; then
+               echo "Stopping pflogd."
+               kill `cat /var/run/pflogd.pid`
+       fi
+}
+
+pflog_resync()
+{
+       if [ -r /var/run/pflogd.pid ]; then
+               kill -SIGHUP `cat /var/run/pflogd.pid`
+       fi
+}
+
+pflog_status()
+{
+       if [ -r /var/run/pflogd.pid ]; then
+               ps -p `cat /var/run/pflogd.pid` | tail -n 1
+       else
+               echo 'pflogd not running.'
+       fi
+}
+
+run_rc_command "$1"
index 61b7294..67d96fd 100644 (file)
@@ -1,6 +1,6 @@
 #      @(#)Makefile    8.1 (Berkeley) 6/4/93
 # $FreeBSD: src/libexec/Makefile,v 1.42.2.5 2002/11/12 17:32:48 obrien Exp $
-# $DragonFly: src/libexec/Makefile,v 1.8 2004/05/27 18:15:41 dillon Exp $
+# $DragonFly: src/libexec/Makefile,v 1.9 2004/09/21 21:25:28 joerg Exp $
 
 # Present but disabled: kpasswdd
 SUBDIR=        atrun \
@@ -8,6 +8,7 @@ SUBDIR= atrun \
        comsat \
        fingerd \
        ftpd \
+       ftp-proxy \
        getNAME \
        getty \
        makekey \
diff --git a/libexec/ftp-proxy/Makefile b/libexec/ftp-proxy/Makefile
new file mode 100644 (file)
index 0000000..efcaa67
--- /dev/null
@@ -0,0 +1,14 @@
+#      $OpenBSD: Makefile,v 1.4 2003/11/20 23:23:09 avsm Exp $
+#      @(#)Makefile    8.2 (Berkeley) 4/4/94
+#      $DragonFly: src/libexec/ftp-proxy/Makefile,v 1.1 2004/09/21 21:25:28 joerg Exp $
+
+PROG=  ftp-proxy
+SRCS=  ftp-proxy.c getline.c util.c
+MAN=   ftp-proxy.8
+WARNS?=        6
+
+CFLAGS+= -DLIBWRAP
+LDADD+=        -lwrap
+DPADD+=        ${LIBWRAP}
+
+.include <bsd.prog.mk>
diff --git a/libexec/ftp-proxy/ftp-proxy.8 b/libexec/ftp-proxy/ftp-proxy.8
new file mode 100644 (file)
index 0000000..8bf2a2e
--- /dev/null
@@ -0,0 +1,275 @@
+.\"    $OpenBSD: ftp-proxy.8,v 1.40 2004/03/16 08:50:07 jmc Exp $
+.\"    $DragonFly: src/libexec/ftp-proxy/ftp-proxy.8,v 1.1 2004/09/21 21:25:28 joerg 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 a Ar address
+.Op Fl D Ar debuglevel
+.Op Fl g Ar group
+.Op Fl M Ar maxport
+.Op Fl m Ar minport
+.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 a Ar address
+Specify the local IP address to use in
+.Xr bind 2
+as the source for connections made by
+.Nm ftp-proxy
+when connecting to destination FTP servers.
+This may be necessary if the interface address of
+your default route is not reachable from the destinations
+.Nm
+is attempting connections to, or this address is different from the one
+connections are being NATed to.
+In the usual case this means that
+.Ar address
+should be a publicly visible IP address assigned to one of
+the interfaces on the machine running
+.Nm
+and should be the same address to which you are translating traffic
+if you are using the
+.Fl n
+option.
+.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 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 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 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 pass 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/libexec/ftp-proxy/ftp-proxy.c b/libexec/ftp-proxy/ftp-proxy.c
new file mode 100644 (file)
index 0000000..dff353f
--- /dev/null
@@ -0,0 +1,1327 @@
+/*     $OpenBSD: ftp-proxy.c,v 1.35 2004/03/14 21:51:44 dhartmei Exp $ */
+/*     $DragonFly: src/libexec/ftp-proxy/ftp-proxy.c,v 1.1 2004/09/21 21:25:28 joerg 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/param.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];
+
+const char *User = "proxy";
+const char *Group;
+
+extern int Debug_Level;
+extern int Use_Rdns;
+extern in_addr_t Bind_Addr;
+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(const 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] [-a address] [-D debuglevel [-g group]"
+           " [-M maxport] [-m minport] [-t timeout] [-u user]", __progname);
+       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 >= (int)len)
+                               goto logit;
+                       len -= i;
+               } else {
+                       i = snprintf(tbuf, len,
+                           "data transfer complete (%dm %ds", idelta / 60,
+                           idelta % 60);
+                       if (i >= (int)len)
+                               goto logit;
+                       len -= i;
+               }
+       } else {
+               i = snprintf(tbuf, len, "data transfer complete (%.1fs",
+                   delta);
+               if (i >= (int)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 >= (int)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 >= (int)len)
+                       goto logit;
+               len -= i;
+       }
+       strlcat(tbuf, ")", sizeof(tbuf));
+ logit:
+       syslog(LOG_INFO, "%s", tbuf);
+}
+
+void
+log_control_command (const char *cmd, int client)
+{
+       /* log an ftp control command or reply */
+       const 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, "a:D:g:m:M:t:u:AnVwr")) != -1) {
+               char *p;
+               switch (ch) {
+               case 'a':
+                       if (!*optarg)
+                               usage();
+                       if ((Bind_Addr = inet_addr(optarg)) == INADDR_NONE) {
+                               syslog(LOG_NOTICE,
+                                       "%s: invalid address", optarg);
+                               usage();
+                       }
+                       break;
+               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);
+                               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);
+                               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/libexec/ftp-proxy/getline.c b/libexec/ftp-proxy/getline.c
new file mode 100644 (file)
index 0000000..4b72bf2
--- /dev/null
@@ -0,0 +1,260 @@
+/*     $OpenBSD: getline.c,v 1.15 2003/06/28 01:04:57 deraadt Exp $ */
+/*     $DragonFly: src/libexec/ftp-proxy/getline.c,v 1.1 2004/09/21 21:25:28 joerg 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/libexec/ftp-proxy/util.c b/libexec/ftp-proxy/util.c
new file mode 100644 (file)
index 0000000..3884670
--- /dev/null
@@ -0,0 +1,301 @@
+/*     $OpenBSD: util.c,v 1.18 2004/01/22 16:10:30 beck Exp $ */
+/*     $DragonFly: src/libexec/ftp-proxy/util.c,v 1.1 2004/09/21 21:25:28 joerg 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/param.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/pf/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;
+in_addr_t Bind_Addr = INADDR_NONE;
+
+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;
+       socklen_t slen;
+       int 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)
+{
+       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 (Bind_Addr == INADDR_NONE)
+                       if (sap == NULL)
+                               sa.sin_addr.s_addr = INADDR_ANY;
+                       else
+                               sa.sin_addr.s_addr = sap->sin_addr.s_addr;
+               else
+                       sa.sin_addr.s_addr = Bind_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/libexec/ftp-proxy/util.h b/libexec/ftp-proxy/util.h
new file mode 100644 (file)
index 0000000..2d87294
--- /dev/null
@@ -0,0 +1,64 @@
+/*     $OpenBSD: util.h,v 1.3 2002/05/23 10:22:14 deraadt Exp $ */
+/*     $DragonFly: src/libexec/ftp-proxy/util.h,v 1.1 2004/09/21 21:25:28 joerg 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;
+       const char *who;
+       char alive, got_eof, data_available;
+       int send_oob_flags;
+};
+
+int    telnet_getline(struct csiob *iobp, struct csiob *telnet_passthrough);
+
+int    get_proxy_env(int fd, struct sockaddr_in *server_sa_ptr,
+                    struct sockaddr_in *client_sa_ptr);
+
+int    get_backchannel_socket(int type, int min_port, int max_port,
+                              int start_port, int direction,
+                              struct sockaddr_in *sap);
+
+int    xfer_data(const char *what_read, int from_fd, int to_fd);
index 095fbda..fd8db45 100644 (file)
@@ -1,6 +1,6 @@
 #      From: @(#)Makefile      5.20 (Berkeley) 6/12/93
 # $FreeBSD: src/usr.sbin/Makefile,v 1.183.2.14 2003/04/16 11:01:51 ru Exp $
-# $DragonFly: src/usr.sbin/Makefile,v 1.12 2004/07/30 00:24:22 dillon Exp $
+# $DragonFly: src/usr.sbin/Makefile,v 1.13 2004/09/21 21:25:28 joerg Exp $
 
 # XXX MISSING:         mkproto
 SUBDIR=        IPXrouted \
@@ -13,6 +13,7 @@ SUBDIR=       IPXrouted \
        arp \
        asf \
        atm \
+       authpf \
        bootparamd \
        burncd \
        cdcontrol \
@@ -63,6 +64,8 @@ SUBDIR=       IPXrouted \
        pccard \
        pciconf \
        periodic \
+       pfctl \
+       pflogd \
        pkg_install \
        pnpinfo \
        portmap \
diff --git a/usr.sbin/authpf/Makefile b/usr.sbin/authpf/Makefile
new file mode 100644 (file)
index 0000000..4204e3b
--- /dev/null
@@ -0,0 +1,22 @@
+#      $OpenBSD: Makefile,v 1.10 2003/11/20 23:23:09 avsm Exp $
+# $DragonFly: src/usr.sbin/authpf/Makefile,v 1.1 2004/09/21 21:25:28 joerg Exp $
+
+PROG=  authpf
+MAN=   authpf.8
+BINOWN=        root
+BINGRP=        authpf
+BINMODE=       6555
+SRCS=  authpf.c parse.y pfctl_parser.c pf_print_state.c
+SRCS+= pfctl_radix.c pfctl_osfp.c
+#SRCS+=        pfctl_altq.c
+WARNS?=        6
+
+.PATH: ${.CURDIR}/../pfctl
+CFLAGS+=       -I${.CURDIR}/../pfctl
+
+LDADD+= -lm -lmd
+DPADD+= ${LIBM}
+
+CLEANFILES+=   y.tab.h
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/authpf/authpf.8 b/usr.sbin/authpf/authpf.8
new file mode 100644 (file)
index 0000000..66d51d3
--- /dev/null
@@ -0,0 +1,496 @@
+.\" $OpenBSD: authpf.8,v 1.31 2003/12/10 04:10:37 beck Exp $
+.\" $DragonFly: src/usr.sbin/authpf/authpf.8,v 1.1 2004/09/21 21:25:28 joerg 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 username and PID of the
+.Nm
+processes as "username(pid)".
+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
+.Sy Control Files
+\- 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
+.Sy Distributed Authentication
+\- 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
+.Sy SSH Configuration
+\- 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
+.Sy Banners
+\- 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
+.Sy Packet Filter Rules
+\- 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.
+.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
+.Sy For a switched, wired net
+\- This example
+.Pa /etc/authpf/authpf.rules
+makes no real restrictions; it turns the IP address on and off, logging
+TCP connections.
+.Bd -literal
+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
+.Sy For a wireless or shared net
+\- This example
+.Pa /etc/authpf/authpf.rules
+could be used 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
+.Pp
+.Sy Dealing with NAT
+\- The following
+.Pa /etc/authpf/authpf.rules
+shows how to deal with NAT, using tags:
+.Bd -literal
+ext_if = "fxp1"
+ext_addr = 129.128.11.10
+int_if = "fxp0"
+# nat and tag connections...
+nat on $ext_if from $user_ip to any tag $user_ip -> $ext_addr
+pass in quick on $int_if from $user_ip to any
+pass out log quick on $ext_if tagged $user_ip keep state
+.Ed
+.Pp
+With the above rules added by
+.Nm ,
+outbound connections corresponding to each users NAT'ed connections
+will be logged as in the example below, where the user may be identified
+from the ruleset name.
+.Bd -literal
+# tcpdump -n -e -ttt -i pflog0
+Oct 31 19:42:30.296553 rule 0.bbeck(20267).1/0(match): pass out on fxp1: \e
+129.128.11.10.60539 > 198.137.240.92.22: S 2131494121:2131494121(0) win \e
+16384 <mss 1460,nop,nop,sackOK> (DF)
+.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/usr.sbin/authpf/authpf.c b/usr.sbin/authpf/authpf.c
new file mode 100644 (file)
index 0000000..2d66564
--- /dev/null
@@ -0,0 +1,918 @@
+/*     $OpenBSD: authpf.c,v 1.75 2004/01/29 01:55:10 deraadt Exp $     */
+/*     $DragonFly: src/usr.sbin/authpf/authpf.c,v 1.1 2004/09/21 21:25:28 joerg 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/param.h>
+#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/pf/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 "pfctl.h"
+
+#include "pathnames.h"
+
+#define __dead __dead2
+
+extern int     symset(const char *, const char *, int);
+
+static int     read_config(FILE *);
+static void    print_message(const char *);
+static int     allowed_luser(const char *);
+static int     check_luser(const char *, const char *);
+static int     remove_stale_rulesets(void);
+static int     change_filter(int, const char *, const char *);
+static void    authpf_kill_states(void);
+
+int    dev_fd;                 /* 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 __unused, char **argv __unused)
+{
+       int              lockcnt = 0, n, pidfd;
+       FILE            *config;
+       struct in_addr   ina;
+       struct passwd   *pw;
+       char            *cp;
+       uid_t            uid;
+
+       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_fd = open(PATH_DEVFILE, O_RDWR);
+       if (dev_fd == -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;
+       }
+
+       if ((n = snprintf(rulesetname, sizeof(rulesetname), "%s(%ld)",
+           luser, (long)getpid())) < 0 || n >= (int)sizeof(rulesetname)) {
+               syslog(LOG_INFO, "%s(%ld) too large, ruleset name will be %ld",
+                   luser, (long)getpid(), (long)getpid());
+               if ((n = snprintf(rulesetname, sizeof(rulesetname), "%ld",
+                   (long)getpid())) < 0 || n >= (int)sizeof(rulesetname)) {
+                       syslog(LOG_ERR, "pid too large for ruleset 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());
+
+       openlog("authpf", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+       if (!check_luser(PATH_BAN_DIR, luser) || !allowed_luser(luser)) {
+               syslog(LOG_INFO, "user %s prohibited", luser);
+               do_death(0);
+       }
+
+       if (config == NULL || read_config(config)) {
+               syslog(LOG_INFO, "bad or nonexistent %s", PATH_CONFFILE);
+               do_death(0);
+       }
+
+       if (remove_stale_rulesets()) {
+               syslog(LOG_INFO, "error removing 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(0);
+       }
+
+       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(const 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(const char *user)
+{
+       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);
+               }
+
+               /*
+                * user 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(user, 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",
+                   user, PATH_ALLOWFILE);
+
+               fputs("\n\nSorry, you are not allowed to use this facility!\n",
+                     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(const char *userdir, const char *user)
+{
+       FILE    *f;
+       int      n;
+       char     tmp[MAXPATHLEN];
+
+       n = snprintf(tmp, sizeof(tmp), "%s/%s", userdir, user);
+       if (n < 0 || (u_int)n >= sizeof(tmp)) {
+               syslog(LOG_ERR, "provided banned directory line too long (%s)",
+                   userdir);
+               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 {
+                       /*
+                        * user 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 {
+               /*
+                * user 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_fd, DIOCGETRULESETS, &prs)) {
+               if (errno == EINVAL)
+                       return (0);
+               else
+                       return (1);
+       }
+
+       mnr = prs.nr;
+       nr = 0;
+       while (nr < mnr) {
+               char    *s, *t;
+               pid_t    pid;
+
+               prs.nr = nr;
+               if (ioctl(dev_fd, DIOCGETRULESET, &prs))
+                       return (1);
+               errno = 0;
+               if ((t = strchr(prs.name, '(')) == NULL)
+                       t = prs.name;
+               else
+                       t++;
+               pid = strtoul(t, &s, 10);
+               if (!prs.name[0] || errno ||
+                   (*s && (t == prs.name || *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_fd, DIOCBEGINRULES, &pr) ||
+                                   ioctl(dev_fd, 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 *user, const char *his_ipsrc)
+{
+       char                     fn[MAXPATHLEN];
+       FILE                    *f = NULL;
+       struct pfctl             pf;
+       struct pfr_buffer        t;
+       int i;
+
+       if (user == NULL || !user[0] || his_ipsrc == NULL || !his_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, user)) < 0 || i >= (int)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_fd, 0)) {
+               syslog(LOG_ERR, "unable to load kernel's OS fingerprints");
+               goto error;
+       }
+       bzero(&t, sizeof(t));
+       t.pfrb_type = PFRB_TRANS;
+       memset(&pf, 0, sizeof(pf));
+       for (i = 0; i < PF_RULESET_MAX; ++i) {
+               if (pfctl_add_trans(&t, i, anchorname, rulesetname)) {
+                       syslog(LOG_ERR, "pfctl_add_trans %m");
+                       goto error;
+               }
+       }
+       if (pfctl_trans(dev_fd, &t, DIOCXBEGIN, 0)) {
+               syslog(LOG_ERR, "DIOCXBEGIN (%s) %m", add?"add":"remove");
+               goto error;
+       }
+
+       if (add) {
+               if (symset("user_ip", his_ipsrc, 0) ||
+                   symset("user_id", user, 0)) {
+                       syslog(LOG_ERR, "symset");
+                       goto error;
+               }
+
+               pf.dev = dev_fd;
+               pf.trans = &t;
+               pf.anchor = anchorname;
+               pf.ruleset = rulesetname;
+
+               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;
+       }
+
+       if (pfctl_trans(dev_fd, &t, DIOCXCOMMIT, 0)) {
+               syslog(LOG_ERR, "DIOCXCOMMIT (%s) %m", add?"add":"remove");
+               goto error;
+       }
+
+       if (add) {
+               gettimeofday(&Tstart, NULL);
+               syslog(LOG_INFO, "allowing %s, user %s", his_ipsrc, user);
+       } else {
+               gettimeofday(&Tend, NULL);
+               syslog(LOG_INFO, "removed %s, user %s - duration %ld seconds",
+                   his_ipsrc, user, Tend.tv_sec - Tstart.tv_sec);
+       }
+       return (0);
+
+error:
+       if (f != NULL)
+               fclose(f);
+       if (pfctl_trans(dev_fd, &t, DIOCXROLLBACK, 0))
+               syslog(LOG_ERR, "DIOCXROLLBACK (%s) %m", add?"add":"remove");
+
+       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_fd, 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_fd, DIOCKILLSTATES, &psk))
+               syslog(LOG_ERR, "DIOCKILLSTATES failed (%m)");
+}
+
+/* signal handler that makes us go away properly */
+static void
+need_death(int signo __unused)
+{
+       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)
+{
+       u_int8_t                rs_num;
+       struct pfioc_rule       pr;
+
+       switch (r->action) {
+       case PF_PASS:
+       case PF_DROP:
+               rs_num = PF_RULESET_FILTER;
+               break;
+       case PF_SCRUB:
+               rs_num = PF_RULESET_SCRUB;
+               break;
+       case PF_NAT:
+       case PF_NONAT:
+               rs_num = PF_RULESET_NAT;
+               break;
+       case PF_RDR:
+       case PF_NORDR:
+               rs_num = PF_RULESET_RDR;
+               break;
+       case PF_BINAT:
+       case PF_NOBINAT:
+               rs_num = PF_RULESET_BINAT;
+               break;
+       default:
+               syslog(LOG_ERR, "invalid rule action %d", r->action);
+               return (1);
+       }
+
+       bzero(&pr, sizeof(pr));
+       strlcpy(pr.anchor, pf->anchor, sizeof(pr.anchor));
+       strlcpy(pr.ruleset, pf->ruleset, sizeof(pr.ruleset));
+       if (pfctl_add_pool(pf, &r->rpool, r->af))
+               return (1);
+       pr.ticket = pfctl_get_ticket(pf->trans, rs_num, pf->anchor,
+           pf->ruleset);
+       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 __unused, struct pf_altq *a __unused)
+{
+       fprintf(stderr, "altq rules not supported in authpf\n");
+       return (1);
+}
+
+int
+pfctl_set_optimization(struct pfctl *pf __unused, const char *opt __unused)
+{
+       fprintf(stderr, "set optimization not supported in authpf\n");
+       return (1);
+}
+
+int
+pfctl_set_logif(struct pfctl *pf __unused, char *ifname __unused)
+{
+       fprintf(stderr, "set loginterface not supported in authpf\n");
+       return (1);
+}
+
+int
+pfctl_set_hostid(struct pfctl *pf __unused, u_int32_t hostid __unused)
+{
+       fprintf(stderr, "set hostid not supported in authpf\n");
+       return (1);
+}
+
+int
+pfctl_set_timeout(struct pfctl *pf __unused, const char *opt __unused,
+                 int seconds __unused, int quiet __unused)
+{
+       fprintf(stderr, "set timeout not supported in authpf\n");
+       return (1);
+}
+
+int
+pfctl_set_limit(struct pfctl *pf __unused, const char *opt __unused,
+               unsigned int limit __unused)
+{
+       fprintf(stderr, "set limit not supported in authpf\n");
+       return (1);
+}
+
+int
+pfctl_set_debug(struct pfctl *pf __unused, char *d __unused)
+{
+       fprintf(stderr, "set debug not supported in authpf\n");
+       return (1);
+}
+
+int
+pfctl_define_table(char *name __unused, int flags __unused, int addrs __unused,
+                  const char *anchor __unused, const char *ruleset __unused,
+                  struct pfr_buffer *ab __unused, u_int32_t ticket __unused)
+{
+       fprintf(stderr, "table definitions not yet supported in authpf\n");
+       return (1);
+}
+
+int
+pfctl_rules(int dev __unused, char *filename __unused, int opts __unused,
+           char *my_anchorname __unused, char *my_rulesetname __unused,
+           struct pfr_buffer *t __unused)
+{
+       /* never called, no anchors inside anchors, but we need the stub */
+       fprintf(stderr, "load anchor not supported from authpf\n");
+       return (1);
+}
+
+void
+pfctl_print_title(const char *title __unused)
+{
+}
diff --git a/usr.sbin/authpf/pathnames.h b/usr.sbin/authpf/pathnames.h
new file mode 100644 (file)
index 0000000..7f543f6
--- /dev/null
@@ -0,0 +1,38 @@
+/*     $OpenBSD: pathnames.h,v 1.6 2003/06/03 20:38:59 beck Exp $      */
+/*     $DragonFly: src/usr.sbin/authpf/pathnames.h,v 1.1 2004/09/21 21:25:28 joerg 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/usr.sbin/pfctl/Makefile b/usr.sbin/pfctl/Makefile
new file mode 100644 (file)
index 0000000..7df9bc7
--- /dev/null
@@ -0,0 +1,14 @@
+#      $OpenBSD: Makefile,v 1.15 2004/03/10 18:49:49 mcbride Exp $
+#      $DragonFly: src/usr.sbin/pfctl/Makefile,v 1.1 2004/09/21 21:25:28 joerg Exp $
+
+PROG=  pfctl
+SRCS=  pfctl.c parse.y pfctl_parser.c pf_print_state.c # pfctl_altq.c
+SRCS+= pfctl_osfp.c pfctl_radix.c pfctl_table.c # pfctl_qstats.c
+CFLAGS+= -I${.CURDIR}
+MAN=   pfctl.8 pf.conf.5 pf.os.5
+WARNS?=        6
+
+LDADD+=        -lmd -lm
+DPADD+=        ${LIBM}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pfctl/parse.y b/usr.sbin/pfctl/parse.y
new file mode 100644 (file)
index 0000000..9538d7a
--- /dev/null
@@ -0,0 +1,4524 @@
+/*     $OpenBSD: parse.y,v 1.449 2004/03/20 23:20:20 david Exp $       */
+/*     $DragonFly: src/usr.sbin/pfctl/parse.y,v 1.1 2004/09/21 21:25:28 joerg Exp $ */
+
+/*
+ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
+ * Copyright (c) 2002,2003 Henning Brauer. 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/param.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/pf/pfvar.h>
+#include <arpa/inet.h>
+#ifndef __DragonFly__
+#include <altq/altq.h>
+#include <altq/altq_cbq.h>
+#include <altq/altq_priq.h>
+#include <altq/altq_hfsc.h>
+#endif
+#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 <limits.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;
+static int              default_statelock;
+
+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, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK,
+           PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_NODES,
+           PF_STATE_OPT_STATELOCK, PF_STATE_OPT_TIMEOUT };
+
+enum   { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE };
+
+struct node_state_opt {
+       int                      type;
+       union {
+               u_int32_t        max_states;
+               u_int32_t        max_src_states;
+               u_int32_t        max_src_nodes;
+               u_int8_t         src_track;
+               u_int32_t        statelock;
+               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
+#define FOM_SRCTRACK   0x10
+       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 pool_opts {
+       int                      marker;
+#define POM_TYPE               0x01
+#define POM_STICKYADDRESS      0x02
+       u_int8_t                 opts;
+       int                      type;
+       int                      staticport;
+       struct pf_poolhashkey   *key;
+
+} pool_opts;
+
+
+struct node_hfsc_opts  hfsc_opts;
+
+int    yyerror(const char *, ...);
+int    disallow_table(struct node_host *, const char *);
+int    disallow_alias(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 *, size_t, const char *, const char *);
+void   expand_label_if(const char *, char *, size_t, const char *);
+void   expand_label_addr(const char *, char *, size_t, u_int8_t,
+           struct node_host *);
+void   expand_label_port(const char *, char *, size_t, struct node_port *);
+void   expand_label_proto(const char *, char *, size_t, u_int8_t);
+void   expand_label_nr(const char *, char *, size_t);
+void   expand_label(char *, size_t, 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 {
+                       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                      action;
+                       struct node_state_opt   *options;
+               }                        keep_state;
+               struct {
+                       u_int8_t         log;
+                       u_int8_t         quick;
+               }                        logquick;
+               struct pf_poolhashkey   *hashkey;
+               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 pool_opts         pool_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)
+
+#define DYNIF_MULTIADDR(addr) ((addr).type == PF_ADDR_DYNIFTL && \
+       (!((addr).iflags & PFI_AFLAG_NOALIAS) ||                 \
+       !isdigit((addr).v.ifname[strlen((addr).v.ifname)-1])))
+
+extern const char      *infile;
+
+%}
+
+%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 NOSYNC DEBUG HOSTID
+%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 STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
+%token TAGGED TAG IFBOUND GRBOUND FLOATING STATEPOLICY
+%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 sourcetrack
+%type  <v.i>                   unaryop statelock
+%type  <v.b>                   action nataction flags flag blockspec
+%type  <v.range>               port rport
+%type  <v.hashkey>             hashkey
+%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.filter_opts>         filter_opts filter_opt filter_opts_l
+%type  <v.antispoof_opts>      antispoof_opts antispoof_opt antispoof_opts_l
+%type  <v.scrub_opts>          scrub_opts scrub_opt scrub_opts_l
+%type  <v.table_opts>          table_opts table_opt table_opts_l
+%type  <v.pool_opts>           pool_opts pool_opt pool_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)) {
+                               free($3);
+                               YYERROR;
+                       }
+                       if (pfctl_set_optimization(pf, $3) != 0) {
+                               yyerror("unknown optimization %s", $3);
+                               free($3);
+                               YYERROR;
+                       }
+                       free ($3);
+               }
+               | 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)) {
+                               free($3);
+                               YYERROR;
+                       }
+                       if ((ifa_exists($3, 0) == NULL) && strcmp($3, "none")) {
+                               yyerror("interface %s doesn't exist", $3);
+                               free($3);
+                               YYERROR;
+                       }
+                       if (pfctl_set_logif(pf, $3) != 0) {
+                               yyerror("error setting loginterface %s", $3);
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+               }
+               | SET HOSTID number {
+                       if ($3 == 0) {
+                               yyerror("hostid must be non-zero");
+                               YYERROR;
+                       }
+                       if (pfctl_set_hostid(pf, $3) != 0) {
+                               yyerror("error setting loginterface %08x", $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)) {
+                               free($3);
+                               YYERROR;
+                       }
+                       if (pfctl_file_fingerprints(pf->dev, pf->opts, $3)) {
+                               yyerror("error loading fingerprints %s", $3);
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+               }
+               | SET STATEPOLICY statelock {
+                       if (pf->opts & PF_OPT_VERBOSE)
+                               switch ($3) {
+                               case 0:
+                                       printf("set state-policy floating\n");
+                                       break;
+                               case PFRULE_IFBOUND:
+                                       printf("set state-policy if-bound\n");
+                                       break;
+                               case PFRULE_GRBOUND:
+                                       printf("set state-policy "
+                                           "group-bound\n");
+                                       break;
+                               }
+                       default_statelock = $3;
+               }
+               | SET DEBUG STRING {
+                       if (check_rulestate(PFCTL_STATE_OPTION)) {
+                               free($3);
+                               YYERROR;
+                       }
+                       if (pfctl_set_debug(pf, $3) != 0) {
+                               yyerror("error setting debuglevel %s", $3);
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+               }
+               ;
+
+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);
+                       free($1);
+                       free($3);
+               }
+               ;
+
+anchorrule     : ANCHOR string dir interface af proto fromto filter_opts {
+                       struct pf_rule  r;
+
+                       if (check_rulestate(PFCTL_STATE_FILTER)) {
+                               free($2);
+                               YYERROR;
+                       }
+
+                       PREPARE_ANCHOR_RULE(r, $2);
+                       r.direction = $3;
+                       r.af = $5;
+
+                       if ($8.match_tag)
+                               if (strlcpy(r.match_tagname, $8.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 = $8.match_tag_not;
+
+                       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)) {
+                               free($2);
+                               YYERROR;
+                       }
+
+                       PREPARE_ANCHOR_RULE(r, $2);
+                       free($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)) {
+                               free($2);
+                               YYERROR;
+                       }
+
+                       PREPARE_ANCHOR_RULE(r, $2);
+                       free($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)) {
+                               free($2);
+                               YYERROR;
+                       }
+
+                       PREPARE_ANCHOR_RULE(r, $2);
+                       free($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 == NULL || *$3 == '\0') {
+                               yyerror("anchor '%s' invalid\n", $3);
+                               free(t);
+                               YYERROR;
+                       }
+                       if (strlen(t) >= PF_ANCHOR_NAME_SIZE) {
+                               yyerror("anchorname %s too long, max %u\n",
+                                   t, PF_ANCHOR_NAME_SIZE - 1);
+                               free(t);
+                               YYERROR;
+                       }
+                       if (strlen($3) >= PF_RULESET_NAME_SIZE) {
+                               yyerror("rulesetname %s too long, max %u\n",
+                                   $3, PF_RULESET_NAME_SIZE - 1);
+                               free(t);
+                               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;
+                       }
+
+                       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) {
+                               free($2);
+                               YYERROR;
+                       }
+                       free($2);
+                       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, PFI_AFLAG_NETWORK);
+
+                               if (h != NULL)
+                                       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, 0);
+                                       if (h != NULL)
+                                               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);
+                               free($3);
+                               YYERROR;
+                       }
+                       if (pf->loadopt & PFCTL_FLAG_TABLE)
+                               if (process_tabledef($3, &$5)) {
+                                       free($3);
+                                       YYERROR;
+                               }
+                       free($3);
+                       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 {
+                               free($1);
+                               YYERROR;
+                       }
+                       free($1);
+               }
+               | '{' '}'               { 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;
+               }
+               ;
+
+pfrule         : action dir logquick interface route af proto fromto
+                   filter_opts
+               {
+                       struct pf_rule           r;
+                       struct node_state_opt   *o;
+                       struct node_proto       *proto;
+                       int                      srctrack = 0;
+                       int                      statelock = 0;
+
+                       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_NOSYNC:
+                                       if (r.rule_flag & PFRULE_NOSYNC) {
+                                               yyerror("state option 'sync' "
+                                                   "multiple definitions");
+                                               YYERROR;
+                                       }
+                                       r.rule_flag |= PFRULE_NOSYNC;
+                                       break;
+                               case PF_STATE_OPT_SRCTRACK:
+                                       if (srctrack) {
+                                               yyerror("state option "
+                                                   "'source-track' "
+                                                   "multiple definitions");
+                                               YYERROR;
+                                       }
+                                       srctrack =  o->data.src_track;
+                                       break;
+                               case PF_STATE_OPT_MAX_SRC_STATES:
+                                       if (r.max_src_states) {
+                                               yyerror("state option "
+                                                   "'max-src-states' "
+                                                   "multiple definitions");
+                                               YYERROR;
+                                       }
+                                       if (o->data.max_src_nodes == 0) {
+                                               yyerror("'max-src-states' must "
+                                                   "be > 0");
+                                               YYERROR;
+                                       }
+                                       r.max_src_states =
+                                           o->data.max_src_states;
+                                       r.rule_flag |= PFRULE_SRCTRACK;
+                                       break;
+                               case PF_STATE_OPT_MAX_SRC_NODES:
+                                       if (r.max_src_nodes) {
+                                               yyerror("state option "
+                                                   "'max-src-nodes' "
+                                                   "multiple definitions");
+                                               YYERROR;
+                                       }
+                                       if (o->data.max_src_nodes == 0) {
+                                               yyerror("'max-src-nodes' must "
+                                                   "be > 0");
+                                               YYERROR;
+                                       }
+                                       r.max_src_nodes =
+                                           o->data.max_src_nodes;
+                                       r.rule_flag |= PFRULE_SRCTRACK |
+                                           PFRULE_RULESRCTRACK;
+                                       break;
+                               case PF_STATE_OPT_STATELOCK:
+                                       if (statelock) {
+                                               yyerror("state locking option: "
+                                                   "multiple definitions");
+                                               YYERROR;
+                                       }
+                                       statelock = 1;
+                                       r.rule_flag |= o->data.statelock;
+                                       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 (srctrack) {
+                               if (srctrack == PF_SRCTRACK_GLOBAL &&
+                                   r.max_src_nodes) {
+                                       yyerror("'max-src-nodes' is "
+                                           "incompatible with "
+                                           "'source-track global'");
+                                       YYERROR;
+                               }
+                               r.rule_flag |= PFRULE_SRCTRACK;
+                               if (srctrack == PF_SRCTRACK_RULE)
+                                       r.rule_flag |= PFRULE_RULESRCTRACK;
+                       }
+                       if (r.keep_state && !statelock)
+                               r.rule_flag |= default_statelock;
+
+                       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_TYPEMASK) ==
+                                   PF_POOL_NONE && ($5.host->next != NULL ||
+                                   $5.host->addr.type == PF_ADDR_TABLE ||
+                                   DYNIF_MULTIADDR($5.host->addr)))
+                                       r.rpool.opts |= PF_POOL_ROUNDROBIN;
+                               if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+                                   PF_POOL_ROUNDROBIN &&
+                                   disallow_table($5.host, "tables are only "
+                                   "supported in round-robin routing pools"))
+                                       YYERROR;
+                               if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+                                   PF_POOL_ROUNDROBIN &&
+                                   disallow_alias($5.host, "interface (%s) "
+                                   "is only supported in round-robin "
+                                   "routing pools"))
+                                       YYERROR;
+                               if ($5.host->next != NULL) {
+                                       if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
+                                           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;
+               }
+               | 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))) {
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+                       $$.w2 = returnicmp6default;
+               }
+               | RETURNICMP6 '(' STRING ')'    {
+                       $$.b2 = PFRULE_RETURNICMP;
+                       $$.w = returnicmpdefault;
+                       if (!($$.w2 = parseicmpspec($3, AF_INET6))) {
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+               }
+               | RETURNICMP '(' STRING comma STRING ')' {
+                       $$.b2 = PFRULE_RETURNICMP;
+                       if (!($$.w = parseicmpspec($3, AF_INET)) ||
+                           !($$.w2 = parseicmpspec($5, AF_INET6))) {
+                               free($3);
+                               free($5);
+                               YYERROR;
+                       }
+                       free($3);
+                       free($5);
+               }
+               | 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, 1)) == NULL) {
+                               yyerror("unknown interface %s", $1);
+                               free($1);
+                               YYERROR;
+                       }
+                       $$ = calloc(1, sizeof(struct node_if));
+                       if ($$ == NULL)
+                               err(1, "if_item: calloc");
+                       if (strlcpy($$->ifname, $1, sizeof($$->ifname)) >=
+                           sizeof($$->ifname)) {
+                               free($1);
+                               free($$);
+                               yyerror("interface name too long");
+                               YYERROR;
+                       }
+                       free($1);
+                       $$->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");
+                                       free($1);
+                                       YYERROR;
+                               }
+                               pr = (u_int8_t)ulval;
+                       } else {
+                               struct protoent *p;
+
+                               p = getprotobyname($1);
+                               if (p == NULL) {
+                                       yyerror("unknown protocol %s", $1);
+                                       free($1);
+                                       YYERROR;
+                               }
+                               pr = p->p_proto;
+                       }
+                       free($1);
+                       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 */
+                               free($1);
+                               yyerror("could not parse host specification");
+                               YYERROR;
+                       }
+                       free($1);
+
+               }
+               | STRING '/' number             {
+                       char    *buf;
+
+                       if (asprintf(&buf, "%s/%u", $1, $3) == -1)
+                               err(1, "host: asprintf");
+                       free($1);
+                       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", $2);
+                               free($2);
+                               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");
+                       free($2);
+                       $$->next = NULL;
+                       $$->tail = $$;
+               }
+               ;
+
+number         : STRING                        {
+                       u_long  ulval;
+
+                       if (atoul($1, &ulval) == -1) {
+                               yyerror("%s is not a number", $1);
+                               free($1);
+                               YYERROR;
+                       } else
+                               $$ = ulval;
+                       free($1);
+               }
+               ;
+
+dynaddr                : '(' STRING ')'                {
+                       int      flags = 0;
+                       char    *p, *op;
+
+                       op = $2;
+                       while ((p = strrchr($2, ':')) != NULL) {
+                               if (!strcmp(p+1, "network"))
+                                       flags |= PFI_AFLAG_NETWORK;
+                               else if (!strcmp(p+1, "broadcast"))
+                                       flags |= PFI_AFLAG_BROADCAST;
+                               else if (!strcmp(p+1, "peer"))
+                                       flags |= PFI_AFLAG_PEER;
+                               else if (!strcmp(p+1, "0"))
+                                       flags |= PFI_AFLAG_NOALIAS;
+                               else {
+                                       yyerror("interface %s has bad modifier",
+                                           $2);
+                                       free(op);
+                                       YYERROR;
+                               }
+                               *p = '\0';
+                       }
+                       if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) {
+                               free(op);
+                               yyerror("illegal combination of "
+                                   "interface modifiers");
+                               YYERROR;
+                       }
+                       if (ifa_exists($2, 1) == NULL && strcmp($2, "self")) {
+                               yyerror("interface %s does not exist", $2);
+                               free(op);
+                               YYERROR;
+                       }
+                       $$ = calloc(1, sizeof(struct node_host));
+                       if ($$ == NULL)
+                               err(1, "address: calloc");
+                       $$->af = 0;
+                       set_ipmask($$, 128);
+                       $$->addr.type = PF_ADDR_DYNIFTL;
+                       $$->addr.iflags = flags;
+                       if (strlcpy($$->addr.v.ifname, $2,
+                           sizeof($$->addr.v.ifname)) >=
+                           sizeof($$->addr.v.ifname)) {
+                               free(op);
+                               free($$);
+                               yyerror("interface name too long");
+                               YYERROR;
+                       }
+                       free(op);
+                       $$->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) {
+                                               free($1);
+                                               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);
+                                               free($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) {
+                                       free($1);
+                                       YYERROR;
+                               }
+                               $$.a = port[0];
+                               $$.b = port[1];
+                               $$.t = PF_OP_RRG;
+                       }
+                       free($1);
+               }
+               ;
+
+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);
+                                               free($1);
+                                               YYERROR;
+                                       }
+                                       $$ = pw->pw_uid;
+                               }
+                       } else {
+                               if (ulval >= UID_MAX) {
+                                       free($1);
+                                       yyerror("illegal uid value %lu", ulval);
+                                       YYERROR;
+                               }
+                               $$ = ulval;
+                       }
+                       free($1);
+               }
+               ;
+
+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);
+                                               free($1);
+                                               YYERROR;
+                                       }
+                                       $$ = grp->gr_gid;
+                               }
+                       } else {
+                               if (ulval >= GID_MAX) {
+                                       yyerror("illegal gid value %lu", ulval);
+                                       free($1);
+                                       YYERROR;
+                               }
+                               $$ = ulval;
+                       }
+                       free($1);
+               }
+               ;
+
+flag           : STRING                        {
+                       int     f;
+
+                       if ((f = parse_flags($1)) < 0) {
+                               yyerror("bad flags %s", $1);
+                               free($1);
+                               YYERROR;
+&nb