From: Joerg Sonnenberger Date: Tue, 21 Sep 2004 21:25:28 +0000 (+0000) Subject: Uesrland part of PF X-Git-Tag: v2.0.1~10115 X-Git-Url: https://gitweb.dragonflybsd.org/~mneumann/dragonfly.git/commitdiff_plain/95cc27f04a317b5e45430e95b4ee9727418459bf Uesrland part of PF 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. --- diff --git a/etc/MAKEDEV b/etc/MAKEDEV index 86fa2ea3ee..2ad205056b 100644 --- a/etc/MAKEDEV +++ b/etc/MAKEDEV @@ -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) @@ -160,6 +160,7 @@ # 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 diff --git a/etc/Makefile b/etc/Makefile index d674823bd0..8be48a257c 100644 --- a/etc/Makefile +++ b/etc/Makefile @@ -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 \ diff --git a/etc/defaults/rc.conf b/etc/defaults/rc.conf index fcc19a165b..1d9e61877c 100644 --- a/etc/defaults/rc.conf +++ b/etc/defaults/rc.conf @@ -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). diff --git a/etc/ftpusers b/etc/ftpusers index 9355e84264..e989cf7eca 100644 --- a/etc/ftpusers +++ b/etc/ftpusers @@ -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 diff --git a/etc/group b/etc/group index 3355e2e101..bf78ad6ff1 100644 --- 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: diff --git a/etc/inetd.conf b/etc/inetd.conf index 71bef739b6..6e3436a5cd 100644 --- a/etc/inetd.conf +++ b/etc/inetd.conf @@ -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 # @@ -120,3 +120,9 @@ #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 diff --git a/etc/mail/aliases b/etc/mail/aliases index ed97366d8a..2edafbc546 100644 --- a/etc/mail/aliases +++ b/etc/mail/aliases @@ -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 diff --git a/etc/master.passwd b/etc/master.passwd index b8a9899844..e2b63d6c45 100644 --- a/etc/master.passwd +++ b/etc/master.passwd @@ -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 diff --git a/etc/newsyslog.conf b/etc/newsyslog.conf index cc59f542fa..518c296a3b 100644 --- a/etc/newsyslog.conf +++ b/etc/newsyslog.conf @@ -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 index 0000000000..5564cbe1d7 --- /dev/null +++ b/etc/pf.conf @@ -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 persist +#table 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 to port smtp \ +# -> 127.0.0.1 port spamd +#rdr pass on $ext_if proto tcp from ! 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 index 0000000000..cbd1d7db3f --- /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 +# (C) Copyright 2003 by Mike Frantzen +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# +# 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 + + diff --git a/etc/rc.d/Makefile b/etc/rc.d/Makefile index 7316bf4a2a..5fec76569d 100644 --- a/etc/rc.d/Makefile +++ b/etc/rc.d/Makefile @@ -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 @@ -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 index 0000000000..612797e98b --- /dev/null +++ b/etc/rc.d/pf @@ -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 index 0000000000..44c96b2947 --- /dev/null +++ b/etc/rc.d/pflog @@ -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" diff --git a/libexec/Makefile b/libexec/Makefile index 61b7294acf..67d96fd6e8 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -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 index 0000000000..efcaa67423 --- /dev/null +++ b/libexec/ftp-proxy/Makefile @@ -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 diff --git a/libexec/ftp-proxy/ftp-proxy.8 b/libexec/ftp-proxy/ftp-proxy.8 new file mode 100644 index 0000000000..8bf2a2e224 --- /dev/null +++ b/libexec/ftp-proxy/ftp-proxy.8 @@ -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 index 0000000000..dff353f896 --- /dev/null +++ b/libexec/ftp-proxy/ftp-proxy.c @@ -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 + * and Bob Beck + * + * This version basically passes everything through unchanged except + * for the PORT and the * "227 Entering Passive Mode" reply. + * + * A PORT command is handled by noting the IP address and port number + * specified and then configuring a listen port on some very high port + * number and telling the server about it using a PORT message. + * We then watch for an in-bound connection on the port from the server + * and connect to the client's port when it happens. + * + * A "227 Entering Passive Mode" reply is handled by noting the IP address + * and port number specified and then configuring a listen port on some + * very high port number and telling the client about it using a + * "227 Entering Passive Mode" reply. + * We then watch for an in-bound connection on the port from the client + * and connect to the server's port when it happens. + * + * supports tcp wrapper lookups/access control with the -w flag using + * the real destination address - the tcp wrapper stuff is done after + * the real destination address is retrieved from pf + * + */ + +/* + * TODO: + * Plenty, this is very basic, with the idea to get it in clean first. + * + * - IPv6 and EPASV support + * - Content filter support + * - filename filter support + * - per-user rules perhaps. + */ + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +#ifdef LIBWRAP +#include +int allow_severity = LOG_INFO; +int deny_severity = LOG_NOTICE; +#endif /* LIBWRAP */ + +int min_port = IPPORT_HIFIRSTAUTO; +int max_port = IPPORT_HILASTAUTO; + +#define STARTBUFSIZE 1024 /* Must be at least 3 */ + +/* + * Variables used to support PORT mode connections. + * + * This gets a bit complicated. + * + * If PORT mode is on then client_listen_sa describes the socket that + * the real client is listening on and server_listen_sa describes the + * socket that we are listening on (waiting for the real server to connect + * with us). + * + * If PASV mode is on then client_listen_sa describes the socket that + * we are listening on (waiting for the real client to connect to us on) + * and server_listen_sa describes the socket that the real server is + * listening on. + * + * If the socket we are listening on gets a connection then we connect + * to the other side's socket. Similarly, if a connected socket is + * shutdown then we shutdown the other side's socket. + */ + +double xfer_start_time; + +struct sockaddr_in real_server_sa; +struct sockaddr_in client_listen_sa; +struct sockaddr_in server_listen_sa; + +int client_listen_socket = -1; /* Only used in PASV mode */ +int client_data_socket = -1; /* Connected socket to real client */ +int server_listen_socket = -1; /* Only used in PORT mode */ +int server_data_socket = -1; /* Connected socket to real server */ +int client_data_bytes, server_data_bytes; + +int AnonFtpOnly; +int Verbose; +int NatMode; + +char ClientName[NI_MAXHOST]; +char RealServerName[NI_MAXHOST]; +char OurName[NI_MAXHOST]; + +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 index 0000000000..4b72bf2dc1 --- /dev/null +++ b/libexec/ftp-proxy/getline.c @@ -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 +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +int refill_buffer(struct csiob *iobp); + +/* + * Refill the io buffer if we KNOW that data is available + * + * Returns 1 if any new data was obtained, 0 otherwise. + */ + +int +refill_buffer(struct csiob *iobp) +{ + int rqlen, rlen; + + if (!(iobp->data_available)) + return(0); + + if (iobp->got_eof) + return(0); + + /* + * The buffer has been entirely consumed if next_byte == io_buffer_len. + * Otherwise, there is some still-to-be-used data in io_buffer. + * Shuffle it to the start of the buffer. + * Note that next_byte will never exceed io_buffer_len. + * Also, note that we MUST use bcopy because the two regions could + * overlap (memcpy isn't defined to work properly with overlapping + * regions). + */ + if (iobp->next_byte < iobp->io_buffer_len) { + int dst_ix = 0; + int src_ix = iobp->next_byte; + int amount = iobp->io_buffer_len - iobp->next_byte; + + bcopy(&iobp->io_buffer[src_ix], &iobp->io_buffer[dst_ix], + amount); + iobp->io_buffer_len = amount; + } else if (iobp->next_byte == iobp->io_buffer_len) + iobp->io_buffer_len = 0; + else { + syslog(LOG_ERR, "next_byte(%d) > io_buffer_len(%d)", + iobp->next_byte, iobp->io_buffer_len); + exit(EX_OSERR); + } + + iobp->next_byte = 0; + + /* don't do tiny reads, grow first if we need to */ + rqlen = iobp->io_buffer_size - iobp->io_buffer_len; + if (rqlen <= 128) { + char *tmp; + + iobp->io_buffer_size += 128; + tmp = realloc(iobp->io_buffer, iobp->io_buffer_size); + if (tmp == NULL) { + syslog(LOG_INFO, "Insufficient memory"); + exit(EX_UNAVAILABLE); + } + iobp->io_buffer = tmp; + rqlen = iobp->io_buffer_size - iobp->io_buffer_len; + } + + /* + * Always leave an unused byte at the end of the buffer + * because the debug output uses that byte from time to time + * to ensure that something that is being printed is \0 terminated. + */ + rqlen -= 1; + + doread: + rlen = read(iobp->fd, &iobp->io_buffer[iobp->io_buffer_len], rqlen); + iobp->data_available = 0; + switch (rlen) { + case -1: + if (errno == EAGAIN || errno == EINTR) + goto doread; + if (errno != ECONNRESET) { + syslog(LOG_INFO, "read() failed on socket from %s (%m)", + iobp->who); + exit(EX_DATAERR); + } + /* fall through to EOF case */ + case 0: + iobp->got_eof = 1; + return(0); + break; + default: + iobp->io_buffer_len += rlen; + break; + } + return(1); +} + +/* + * telnet_getline - a hacked up version of fgets to ignore TELNET escape codes. + * + * This code is derived from the getline routine found in the UC Berkeley + * ftpd code. + * + */ + +int +telnet_getline(struct csiob *iobp, struct csiob *telnet_passthrough) +{ + unsigned char ch; + int ix; + char tbuf[100]; + + iobp->line_buffer[0] = '\0'; + + /* + * If the buffer is empty then refill it right away. + */ + if (iobp->next_byte == iobp->io_buffer_len) + if (!refill_buffer(iobp)) + return(0); + + /* + * Is there a telnet command in the buffer? + */ + ch = iobp->io_buffer[iobp->next_byte]; + if (ch == IAC) { + /* + * Yes - buffer must have at least three bytes in it + */ + if (iobp->io_buffer_len - iobp->next_byte < 3) { + if (!refill_buffer(iobp)) + return(0); + if (iobp->io_buffer_len - iobp->next_byte < 3) + return(0); + } + + iobp->next_byte++; + ch = iobp->io_buffer[iobp->next_byte++]; + + switch (ch) { + case WILL: + case WONT: + case DO: + case DONT: + tbuf[0] = IAC; + tbuf[1] = ch; + tbuf[2] = iobp->io_buffer[iobp->next_byte++]; + (void)send(telnet_passthrough->fd, tbuf, 3, + telnet_passthrough->send_oob_flags); + break; + case IAC: + break; + default: + break; + } + return(1); + } else { + int clen; + + /* + * Is there a newline in the buffer? + */ + for (ix = iobp->next_byte; ix < iobp->io_buffer_len; + ix += 1) { + if (iobp->io_buffer[ix] == '\n') + break; + if (iobp->io_buffer[ix] == '\0') { + syslog(LOG_INFO, + "got NUL byte from %s - bye!", + iobp->who); + exit(EX_DATAERR); + } + } + + if (ix == iobp->io_buffer_len) { + if (!refill_buffer(iobp)) + return(0); + /* + * Empty line returned + * will try again soon! + */ + return(1); + } + + /* + * Expand the line buffer if it isn't big enough. We + * use a fudge factor of 5 rather than trying to + * figure out exactly how to account for the '\0 \r\n' and + * such. The correct fudge factor is 0, 1 or 2 but + * anything higher also works. We also grow it by a + * bunch to avoid having to do this often. Yes this is + * nasty. + */ + if (ix - iobp->next_byte > iobp->line_buffer_size - 5) { + char *tmp; + + iobp->line_buffer_size = 256 + ix - iobp->next_byte; + tmp = realloc(iobp->line_buffer, + iobp->line_buffer_size); + if (tmp == NULL) { + syslog(LOG_INFO, "Insufficient memory"); + exit(EX_UNAVAILABLE); + } + iobp->line_buffer = tmp; + } + + /* +1 is for the newline */ + clen = (ix+1) - iobp->next_byte; + memcpy(iobp->line_buffer, &iobp->io_buffer[iobp->next_byte], + clen); + iobp->next_byte += clen; + iobp->line_buffer[clen] = '\0'; + return(1); + } +} diff --git a/libexec/ftp-proxy/util.c b/libexec/ftp-proxy/util.c new file mode 100644 index 0000000000..388467063a --- /dev/null +++ b/libexec/ftp-proxy/util.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +int Debug_Level; +int Use_Rdns; +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 index 0000000000..2d872941c7 --- /dev/null +++ b/libexec/ftp-proxy/util.h @@ -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); diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index 095fbda1e3..fd8db45ad9 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -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 index 0000000000..4204e3b8b5 --- /dev/null +++ b/usr.sbin/authpf/Makefile @@ -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 diff --git a/usr.sbin/authpf/authpf.8 b/usr.sbin/authpf/authpf.8 new file mode 100644 index 0000000000..66d51d3688 --- /dev/null +++ b/usr.sbin/authpf/authpf.8 @@ -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 (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 index 0000000000..2d66564bb3 --- /dev/null +++ b/usr.sbin/authpf/authpf.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000000..7f543f648a --- /dev/null +++ b/usr.sbin/authpf/pathnames.h @@ -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 index 0000000000..7df9bc7337 --- /dev/null +++ b/usr.sbin/pfctl/Makefile @@ -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 diff --git a/usr.sbin/pfctl/parse.y b/usr.sbin/pfctl/parse.y new file mode 100644 index 0000000000..9538d7a446 --- /dev/null +++ b/usr.sbin/pfctl/parse.y @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __DragonFly__ +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +static struct pfctl *pf = NULL; +static FILE *fin = NULL; +static int debug = 0; +static int lineno = 1; +static int errors = 0; +static int rulestate = 0; +static u_int16_t returnicmpdefault = + (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; +static u_int16_t returnicmp6default = + (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; +static int blockpolicy = PFRULE_DROP; +static int require_order = 1; +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 STRING +%token PORTBINARY +%type interface if_list if_item_not if_item +%type number icmptype icmp6type uid gid +%type tos not yesno natpass +%type no dir log af fragcache sourcetrack +%type unaryop statelock +%type action nataction flags flag blockspec +%type port rport +%type hashkey +%type proto proto_list proto_item +%type icmpspec +%type icmp_list icmp_item +%type icmp6_list icmp6_item +%type fromto +%type ipportspec from to +%type ipspec xhost host dynaddr host_list +%type redir_host_list redirspec +%type route_host route_host_list routespec +%type os xos os_list +%type portspec port_list port_item +%type uids uid_list uid_item +%type gids gid_list gid_item +%type route +%type redirection redirpool +%type label string tag +%type keep +%type state_opt_spec state_opt_list state_opt_item +%type logquick +%type antispoof_ifspc antispoof_iflst +%type filter_opts filter_opt filter_opts_l +%type antispoof_opts antispoof_opt antispoof_opts_l +%type scrub_opts scrub_opt scrub_opts_l +%type table_opts table_opt table_opts_l +%type 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; + } + free($1); + $$.b1 = f; + } + ; + +flags : FLAGS flag '/' flag { $$.b1 = $2.b1; $$.b2 = $4.b1; } + | FLAGS '/' flag { $$.b1 = 0; $$.b2 = $3.b1; } + ; + +icmpspec : ICMPTYPE icmp_item { $$ = $2; } + | ICMPTYPE '{' icmp_list '}' { $$ = $3; } + | ICMP6TYPE icmp6_item { $$ = $2; } + | ICMP6TYPE '{' icmp6_list '}' { $$ = $3; } + ; + +icmp_list : icmp_item { $$ = $1; } + | icmp_list comma icmp_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +icmp6_list : icmp6_item { $$ = $1; } + | icmp6_list comma icmp6_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +icmp_item : icmptype { + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = 0; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + | icmptype CODE STRING { + const struct icmpcodeent *p; + u_long ulval; + + if (atoul($3, &ulval) == 0) { + if (ulval > 255) { + free($3); + yyerror("illegal icmp-code %d", ulval); + YYERROR; + } + } else { + if ((p = geticmpcodebyname($1-1, $3, + AF_INET)) == NULL) { + yyerror("unknown icmp-code %s", $3); + free($3); + YYERROR; + } + ulval = p->code; + } + free($3); + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = ulval + 1; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + ; + +icmp6_item : icmp6type { + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = 0; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + | icmp6type CODE STRING { + const struct icmpcodeent *p; + u_long ulval; + + if (atoul($3, &ulval) == 0) { + if (ulval > 255) { + yyerror("illegal icmp6-code %ld", + ulval); + free($3); + YYERROR; + } + } else { + if ((p = geticmpcodebyname($1-1, $3, + AF_INET6)) == NULL) { + yyerror("unknown icmp6-code %s", $3); + free($3); + YYERROR; + } + ulval = p->code; + } + free($3); + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = ulval + 1; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + ; + +icmptype : STRING { + const struct icmptypeent *p; + u_long ulval; + + if (atoul($1, &ulval) == 0) { + if (ulval > 255) { + yyerror("illegal icmp-type %d", ulval); + free($1); + YYERROR; + } + $$ = ulval + 1; + } else { + if ((p = geticmptypebyname($1, AF_INET)) == + NULL) { + yyerror("unknown icmp-type %s", $1); + free($1); + YYERROR; + } + $$ = p->type + 1; + } + free($1); + } + ; + +icmp6type : STRING { + const struct icmptypeent *p; + u_long ulval; + + if (atoul($1, &ulval) == 0) { + if (ulval > 255) { + yyerror("illegal icmp6-type %d", ulval); + free($1); + YYERROR; + } + $$ = ulval + 1; + } else { + if ((p = geticmptypebyname($1, AF_INET6)) == + NULL) { + yyerror("unknown icmp6-type %s", $1); + free($1); + YYERROR; + } + $$ = p->type + 1; + } + free($1); + } + ; + +tos : TOS STRING { + if (!strcmp($2, "lowdelay")) + $$ = IPTOS_LOWDELAY; + else if (!strcmp($2, "throughput")) + $$ = IPTOS_THROUGHPUT; + else if (!strcmp($2, "reliability")) + $$ = IPTOS_RELIABILITY; + else if ($2[0] == '0' && $2[1] == 'x') + $$ = strtoul($2, NULL, 16); + else + $$ = strtoul($2, NULL, 10); + if (!$$ || $$ > 255) { + yyerror("illegal tos value %s", $2); + free($2); + YYERROR; + } + free($2); + } + ; + +sourcetrack : SOURCETRACK { $$ = PF_SRCTRACK; } + | SOURCETRACK GLOBAL { $$ = PF_SRCTRACK_GLOBAL; } + | SOURCETRACK RULE { $$ = PF_SRCTRACK_RULE; } + ; + +statelock : IFBOUND { + $$ = PFRULE_IFBOUND; + } + | GRBOUND { + $$ = PFRULE_GRBOUND; + } + | FLOATING { + $$ = 0; + } + ; + +keep : KEEP STATE state_opt_spec { + $$.action = PF_STATE_NORMAL; + $$.options = $3; + } + | MODULATE STATE state_opt_spec { + $$.action = PF_STATE_MODULATE; + $$.options = $3; + } + | SYNPROXY STATE state_opt_spec { + $$.action = PF_STATE_SYNPROXY; + $$.options = $3; + } + ; + +state_opt_spec : '(' state_opt_list ')' { $$ = $2; } + | /* empty */ { $$ = NULL; } + ; + +state_opt_list : state_opt_item { $$ = $1; } + | state_opt_list comma state_opt_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +state_opt_item : MAXIMUM number { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX; + $$->data.max_states = $2; + $$->next = NULL; + $$->tail = $$; + } + | NOSYNC { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_NOSYNC; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCSTATES number { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_STATES; + $$->data.max_src_states = $2; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCNODES number { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_NODES; + $$->data.max_src_nodes = $2; + $$->next = NULL; + $$->tail = $$; + } + | sourcetrack { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_SRCTRACK; + $$->data.src_track = $1; + $$->next = NULL; + $$->tail = $$; + } + | statelock { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_STATELOCK; + $$->data.statelock = $1; + $$->next = NULL; + $$->tail = $$; + } + | STRING number { + int i; + + for (i = 0; pf_timeouts[i].name && + strcmp(pf_timeouts[i].name, $1); ++i) + ; /* nothing */ + if (!pf_timeouts[i].name) { + yyerror("illegal timeout name %s", $1); + free($1); + YYERROR; + } + if (strchr(pf_timeouts[i].name, '.') == NULL) { + yyerror("illegal state timeout %s", $1); + free($1); + YYERROR; + } + free($1); + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_TIMEOUT; + $$->data.timeout.number = pf_timeouts[i].timeout; + $$->data.timeout.seconds = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +label : LABEL STRING { + $$ = $2; + } + ; + +no : /* empty */ { $$ = 0; } + | NO { $$ = 1; } + ; + +rport : STRING { + char *p = strchr($1, ':'); + + if (p == NULL) { + if (($$.a = getservice($1)) == -1) { + free($1); + YYERROR; + } + $$.b = $$.t = 0; + } else if (!strcmp(p+1, "*")) { + *p = 0; + if (($$.a = getservice($1)) == -1) { + free($1); + YYERROR; + } + $$.b = 0; + $$.t = 1; + } else { + *p++ = 0; + if (($$.a = getservice($1)) == -1 || + ($$.b = getservice(p)) == -1) { + free($1); + YYERROR; + } + if ($$.a == $$.b) + $$.b = 0; + $$.t = 0; + } + free($1); + } + ; + +redirspec : host { $$ = $1; } + | '{' redir_host_list '}' { $$ = $2; } + ; + +redir_host_list : host { $$ = $1; } + | redir_host_list comma host { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + ; + +redirpool : /* empty */ { $$ = NULL; } + | ARROW redirspec { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $2; + $$->rport.a = $$->rport.b = $$->rport.t = 0; + } + | ARROW redirspec PORT rport { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $2; + $$->rport = $4; + } + ; + +hashkey : /* empty */ + { + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + $$->key32[0] = arc4random(); + $$->key32[1] = arc4random(); + $$->key32[2] = arc4random(); + $$->key32[3] = arc4random(); + } + | string + { + if (!strncmp($1, "0x", 2)) { + if (strlen($1) != 34) { + free($1); + yyerror("hex key must be 128 bits " + "(32 hex digits) long"); + YYERROR; + } + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + + if (sscanf($1, "0x%8x%8x%8x%8x", + &$$->key32[0], &$$->key32[1], + &$$->key32[2], &$$->key32[3]) != 4) { + free($$); + free($1); + yyerror("invalid hex key"); + YYERROR; + } + } else { + MD5_CTX context; + + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + MD5Init(&context); + MD5Update(&context, (unsigned char *)$1, + strlen($1)); + MD5Final((unsigned char *)$$, &context); + $$->key32[0] = htonl($$->key32[0]); + $$->key32[1] = htonl($$->key32[1]); + $$->key32[2] = htonl($$->key32[2]); + $$->key32[3] = htonl($$->key32[3]); + } + free($1); + } + ; + +pool_opts : { bzero(&pool_opts, sizeof pool_opts); } + pool_opts_l + { $$ = pool_opts; } + | /* empty */ { + bzero(&pool_opts, sizeof pool_opts); + $$ = pool_opts; + } + ; + +pool_opts_l : pool_opts_l pool_opt + | pool_opt + ; + +pool_opt : BITMASK { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_BITMASK; + } + | RANDOM { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_RANDOM; + } + | SOURCEHASH hashkey { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_SRCHASH; + pool_opts.key = $2; + } + | ROUNDROBIN { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_ROUNDROBIN; + } + | STATICPORT { + if (pool_opts.staticport) { + yyerror("static-port cannot be redefined"); + YYERROR; + } + pool_opts.staticport = 1; + } + | STICKYADDRESS { + if (filter_opts.marker & POM_STICKYADDRESS) { + yyerror("sticky-address cannot be redefined"); + YYERROR; + } + pool_opts.marker |= POM_STICKYADDRESS; + pool_opts.opts |= PF_POOL_STICKYADDR; + } + ; + +redirection : /* empty */ { $$ = NULL; } + | ARROW host { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $2; + $$->rport.a = $$->rport.b = $$->rport.t = 0; + } + | ARROW host PORT rport { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $2; + $$->rport = $4; + } + ; + +natpass : /* empty */ { $$ = 0; } + | PASS { $$ = 1; } + ; + +nataction : no NAT natpass { + $$.b2 = $$.w = 0; + if ($1) + $$.b1 = PF_NONAT; + else + $$.b1 = PF_NAT; + $$.b2 = $3; + } + | no RDR natpass { + $$.b2 = $$.w = 0; + if ($1) + $$.b1 = PF_NORDR; + else + $$.b1 = PF_RDR; + $$.b2 = $3; + } + ; + +natrule : nataction interface af proto fromto tag redirpool pool_opts + { + struct pf_rule r; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + + memset(&r, 0, sizeof(r)); + + r.action = $1.b1; + r.natpass = $1.b2; + r.af = $3; + + if (!r.af) { + if ($5.src.host && $5.src.host->af && + !$5.src.host->ifindex) + r.af = $5.src.host->af; + else if ($5.dst.host && $5.dst.host->af && + !$5.dst.host->ifindex) + r.af = $5.dst.host->af; + } + + if ($6 != NULL) + if (strlcpy(r.tagname, $6, PF_TAG_NAME_SIZE) >= + PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + + if (r.action == PF_NONAT || r.action == PF_NORDR) { + if ($7 != NULL) { + yyerror("translation rule with 'no' " + "does not need '->'"); + YYERROR; + } + } else { + if ($7 == NULL || $7->host == NULL) { + yyerror("translation rule requires '-> " + "address'"); + YYERROR; + } + if (!r.af && ! $7->host->ifindex) + r.af = $7->host->af; + + remove_invalid_hosts(&$7->host, &r.af); + if (invalid_redirect($7->host, r.af)) + YYERROR; + if (check_netmask($7->host, r.af)) + YYERROR; + + r.rpool.proxy_port[0] = ntohs($7->rport.a); + + switch (r.action) { + case PF_RDR: + if (!$7->rport.b && $7->rport.t && + $5.dst.port != NULL) { + r.rpool.proxy_port[1] = + ntohs($7->rport.a) + + (ntohs( + $5.dst.port->port[1]) - + ntohs( + $5.dst.port->port[0])); + } else + r.rpool.proxy_port[1] = + ntohs($7->rport.b); + break; + case PF_NAT: + r.rpool.proxy_port[1] = + ntohs($7->rport.b); + if (!r.rpool.proxy_port[0] && + !r.rpool.proxy_port[1]) { + r.rpool.proxy_port[0] = + PF_NAT_PROXY_PORT_LOW; + r.rpool.proxy_port[1] = + PF_NAT_PROXY_PORT_HIGH; + } else if (!r.rpool.proxy_port[1]) + r.rpool.proxy_port[1] = + r.rpool.proxy_port[0]; + break; + default: + break; + } + + r.rpool.opts = $8.type; + if ((r.rpool.opts & PF_POOL_TYPEMASK) == + PF_POOL_NONE && ($7->host->next != NULL || + $7->host->addr.type == PF_ADDR_TABLE || + DYNIF_MULTIADDR($7->host->addr))) + r.rpool.opts = PF_POOL_ROUNDROBIN; + if ((r.rpool.opts & PF_POOL_TYPEMASK) != + PF_POOL_ROUNDROBIN && + disallow_table($7->host, "tables are only " + "supported in round-robin redirection " + "pools")) + YYERROR; + if ((r.rpool.opts & PF_POOL_TYPEMASK) != + PF_POOL_ROUNDROBIN && + disallow_alias($7->host, "interface (%s) " + "is only supported in round-robin " + "redirection pools")) + YYERROR; + if ($7->host->next != NULL) { + if ((r.rpool.opts & PF_POOL_TYPEMASK) != + PF_POOL_ROUNDROBIN) { + yyerror("only round-robin " + "valid for multiple " + "redirection addresses"); + YYERROR; + } + } + } + + if ($8.key != NULL) + memcpy(&r.rpool.key, $8.key, + sizeof(struct pf_poolhashkey)); + + if ($8.opts) + r.rpool.opts |= $8.opts; + + if ($8.staticport) { + if (r.action != PF_NAT) { + yyerror("the 'static-port' option is " + "only valid with nat rules"); + YYERROR; + } + if (r.rpool.proxy_port[0] != + PF_NAT_PROXY_PORT_LOW && + r.rpool.proxy_port[1] != + PF_NAT_PROXY_PORT_HIGH) { + yyerror("the 'static-port' option can't" + " be used when specifying a port" + " range"); + YYERROR; + } + r.rpool.proxy_port[0] = 0; + r.rpool.proxy_port[1] = 0; + } + + expand_rule(&r, $2, $7 == NULL ? NULL : $7->host, $4, + $5.src_os, $5.src.host, $5.src.port, $5.dst.host, + $5.dst.port, 0, 0, 0); + free($7); + } + ; + +binatrule : no BINAT natpass interface af proto FROM host TO ipspec tag + redirection + { + struct pf_rule binat; + struct pf_pooladdr *pa; + + if (check_rulestate(PFCTL_STATE_NAT)) + YYERROR; + + memset(&binat, 0, sizeof(binat)); + + if ($1) + binat.action = PF_NOBINAT; + else + binat.action = PF_BINAT; + binat.natpass = $3; + binat.af = $5; + if (!binat.af && $8 != NULL && $8->af) + binat.af = $8->af; + if (!binat.af && $10 != NULL && $10->af) + binat.af = $10->af; + if (!binat.af && $12 != NULL && $12->host) + binat.af = $12->host->af; + if (!binat.af) { + yyerror("address family (inet/inet6) " + "undefined"); + YYERROR; + } + + if ($4 != NULL) { + memcpy(binat.ifname, $4->ifname, + sizeof(binat.ifname)); + binat.ifnot = $4->not; + free($4); + } + if ($11 != NULL) + if (strlcpy(binat.tagname, $11, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + YYERROR; + } + + if ($6 != NULL) { + binat.proto = $6->proto; + free($6); + } + + if ($8 != NULL && disallow_table($8, "invalid use of " + "table <%s> as the source address of a binat rule")) + YYERROR; + if ($8 != NULL && disallow_alias($8, "invalid use of " + "interface (%s) as the source address of a binat " + "rule")) + YYERROR; + if ($12 != NULL && $12->host != NULL && disallow_table( + $12->host, "invalid use of table <%s> as the " + "redirect address of a binat rule")) + YYERROR; + if ($12 != NULL && $12->host != NULL && disallow_alias( + $12->host, "invalid use of interface (%s) as the " + "redirect address of a binat rule")) + YYERROR; + + if ($8 != NULL) { + if ($8->next) { + yyerror("multiple binat ip addresses"); + YYERROR; + } + if ($8->addr.type == PF_ADDR_DYNIFTL) + $8->af = binat.af; + if ($8->af != binat.af) { + yyerror("binat ip versions must match"); + YYERROR; + } + if (check_netmask($8, binat.af)) + YYERROR; + memcpy(&binat.src.addr, &$8->addr, + sizeof(binat.src.addr)); + free($8); + } + if ($10 != NULL) { + if ($10->next) { + yyerror("multiple binat ip addresses"); + YYERROR; + } + if ($10->af != binat.af && $10->af) { + yyerror("binat ip versions must match"); + YYERROR; + } + if (check_netmask($10, binat.af)) + YYERROR; + memcpy(&binat.dst.addr, &$10->addr, + sizeof(binat.dst.addr)); + binat.dst.not = $10->not; + free($10); + } + + if (binat.action == PF_NOBINAT) { + if ($12 != NULL) { + yyerror("'no binat' rule does not need" + " '->'"); + YYERROR; + } + } else { + if ($12 == NULL || $12->host == NULL) { + yyerror("'binat' rule requires" + " '-> address'"); + YYERROR; + } + + remove_invalid_hosts(&$12->host, &binat.af); + if (invalid_redirect($12->host, binat.af)) + YYERROR; + if ($12->host->next != NULL) { + yyerror("binat rule must redirect to " + "a single address"); + YYERROR; + } + if (check_netmask($12->host, binat.af)) + YYERROR; + + if (!PF_AZERO(&binat.src.addr.v.a.mask, + binat.af) && + !PF_AEQ(&binat.src.addr.v.a.mask, + &$12->host->addr.v.a.mask, binat.af)) { + yyerror("'binat' source mask and " + "redirect mask must be the same"); + YYERROR; + } + + TAILQ_INIT(&binat.rpool.list); + pa = calloc(1, sizeof(struct pf_pooladdr)); + if (pa == NULL) + err(1, "binat: calloc"); + pa->addr = $12->host->addr; + pa->ifname[0] = 0; + TAILQ_INSERT_TAIL(&binat.rpool.list, + pa, entries); + + free($12); + } + + pfctl_add_rule(pf, &binat); + } + ; + +tag : /* empty */ { $$ = NULL; } + | TAG STRING { $$ = $2; } + ; + +route_host : STRING { + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "route_host: calloc"); + $$->ifname = $1; + if (ifa_exists($$->ifname, 0) == NULL) { + yyerror("routeto: unknown interface %s", + $$->ifname); + free($1); + free($$); + YYERROR; + } + set_ipmask($$, 128); + $$->next = NULL; + $$->tail = $$; + } + | '(' STRING host ')' { + $$ = $3; + $$->ifname = $2; + if (ifa_exists($$->ifname, 0) == NULL) { + yyerror("routeto: unknown interface %s", + $$->ifname); + YYERROR; + } + } + ; + +route_host_list : route_host { $$ = $1; } + | route_host_list comma route_host { + if ($1->af == 0) + $1->af = $3->af; + if ($1->af != $3->af) { + yyerror("all pool addresses must be in the " + "same address family"); + YYERROR; + } + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + ; + +routespec : route_host { $$ = $1; } + | '{' route_host_list '}' { $$ = $2; } + ; + +route : /* empty */ { + $$.host = NULL; + $$.rt = 0; + $$.pool_opts = 0; + } + | FASTROUTE { + $$.host = NULL; + $$.rt = PF_FASTROUTE; + $$.pool_opts = 0; + } + | ROUTETO routespec pool_opts { + $$.host = $2; + $$.rt = PF_ROUTETO; + $$.pool_opts = $3.type | $3.opts; + if ($3.key != NULL) + $$.key = $3.key; + } + | REPLYTO routespec pool_opts { + $$.host = $2; + $$.rt = PF_REPLYTO; + $$.pool_opts = $3.type | $3.opts; + if ($3.key != NULL) + $$.key = $3.key; + } + | DUPTO routespec pool_opts { + $$.host = $2; + $$.rt = PF_DUPTO; + $$.pool_opts = $3.type | $3.opts; + if ($3.key != NULL) + $$.key = $3.key; + } + ; + +timeout_spec : STRING number + { + if (check_rulestate(PFCTL_STATE_OPTION)) { + free($1); + YYERROR; + } + if (pfctl_set_timeout(pf, $1, $2, 0) != 0) { + yyerror("unknown timeout %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +timeout_list : timeout_list comma timeout_spec + | timeout_spec + ; + +limit_spec : STRING number + { + if (check_rulestate(PFCTL_STATE_OPTION)) { + free($1); + YYERROR; + } + if (pfctl_set_limit(pf, $1, $2) != 0) { + yyerror("unable to set limit %s %u", $1, $2); + free($1); + YYERROR; + } + free($1); + } + ; + +limit_list : limit_list comma limit_spec + | limit_spec + ; + +comma : ',' + | /* empty */ + ; + +yesno : NO { $$ = 0; } + | STRING { + if (!strcmp($1, "yes")) + $$ = 1; + else { + free($1); + YYERROR; + } + free($1); + } + ; + +unaryop : '=' { $$ = PF_OP_EQ; } + | '!' '=' { $$ = PF_OP_NE; } + | '<' '=' { $$ = PF_OP_LE; } + | '<' { $$ = PF_OP_LT; } + | '>' '=' { $$ = PF_OP_GE; } + | '>' { $$ = PF_OP_GT; } + ; + +%% + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + + errors = 1; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", infile, yylval.lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return (0); +} + +int +disallow_table(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (h->addr.type == PF_ADDR_TABLE) { + yyerror(fmt, h->addr.v.tblname); + return (1); + } + return (0); +} + +int +disallow_alias(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (DYNIF_MULTIADDR(h->addr)) { + yyerror(fmt, h->addr.v.tblname); + return (1); + } + return (0); +} + +int +rule_consistent(struct pf_rule *r) +{ + int problems = 0; + + switch (r->action) { + case PF_PASS: + case PF_DROP: + case PF_SCRUB: + problems = filter_consistent(r); + break; + case PF_NAT: + case PF_NONAT: + problems = nat_consistent(r); + break; + case PF_RDR: + case PF_NORDR: + problems = rdr_consistent(r); + break; + case PF_BINAT: + case PF_NOBINAT: + default: + break; + } + return (problems); +} + +int +filter_consistent(struct pf_rule *r) +{ + int problems = 0; + + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + (r->src.port_op || r->dst.port_op)) { + yyerror("port only applies to tcp/udp"); + problems++; + } + if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 && + (r->type || r->code)) { + yyerror("icmp-type/code only applies to icmp"); + problems++; + } + if (!r->af && (r->type || r->code)) { + yyerror("must indicate address family with icmp-type/code"); + problems++; + } + if ((r->proto == IPPROTO_ICMP && r->af == AF_INET6) || + (r->proto == IPPROTO_ICMPV6 && r->af == AF_INET)) { + yyerror("proto %s doesn't match address family %s", + r->proto == IPPROTO_ICMP ? "icmp" : "icmp6", + r->af == AF_INET ? "inet" : "inet6"); + problems++; + } + if (r->allow_opts && r->action != PF_PASS) { + yyerror("allow-opts can only be specified for pass rules"); + problems++; + } + if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op || + r->dst.port_op || r->flagset || r->type || r->code)) { + yyerror("fragments can be filtered only on IP header fields"); + problems++; + } + if (r->rule_flag & PFRULE_RETURNRST && r->proto != IPPROTO_TCP) { + yyerror("return-rst can only be applied to TCP rules"); + problems++; + } + if (r->max_src_nodes && !(r->rule_flag & PFRULE_RULESRCTRACK)) { + yyerror("max-src-nodes requires 'source-track rule'"); + problems++; + } + if (r->action == PF_DROP && r->keep_state) { + yyerror("keep state on block rules doesn't make sense"); + problems++; + } + if ((r->tagname[0] || r->match_tagname[0]) && !r->keep_state && + r->action == PF_PASS && !r->anchorname[0]) { + yyerror("tags cannot be used without keep state"); + problems++; + } + return (-problems); +} + +int +nat_consistent(struct pf_rule *r __unused) +{ + return (0); /* yeah! */ +} + +int +rdr_consistent(struct pf_rule *r) +{ + int problems = 0; + + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP) { + if (r->src.port_op) { + yyerror("src port only applies to tcp/udp"); + problems++; + } + if (r->dst.port_op) { + yyerror("dst port only applies to tcp/udp"); + problems++; + } + if (r->rpool.proxy_port[0]) { + yyerror("rpool port only applies to tcp/udp"); + problems++; + } + } + if (r->dst.port_op && + r->dst.port_op != PF_OP_EQ && r->dst.port_op != PF_OP_RRG) { + yyerror("invalid port operator for rdr destination port"); + problems++; + } + return (-problems); +} + +int +process_tabledef(char *name, struct table_opts *opts) +{ + struct pfr_buffer ab; + struct node_tinit *ti; + + bzero(&ab, sizeof(ab)); + ab.pfrb_type = PFRB_ADDRS; + SIMPLEQ_FOREACH(ti, &opts->init_nodes, entries) { + if (ti->file) + if (pfr_buf_load(&ab, ti->file, 0, append_addr)) { + if (errno) + yyerror("cannot load \"%s\": %s", + ti->file, strerror(errno)); + else + yyerror("file \"%s\" contains bad data", + ti->file); + goto _error; + } + if (ti->host) + if (append_addr_host(&ab, ti->host, 0, 0)) { + yyerror("cannot create address buffer: %s", + strerror(errno)); + goto _error; + } + } + if (pf->opts & PF_OPT_VERBOSE) + print_tabledef(name, opts->flags, opts->init_addr, + &opts->init_nodes); + if (!(pf->opts & PF_OPT_NOACTION) && + pfctl_define_table(name, opts->flags, opts->init_addr, + pf->anchor, pf->ruleset, &ab, pf->tticket)) { + yyerror("cannot define table %s: %s", name, + pfr_strerror(errno)); + goto _error; + } + pf->tdirty = 1; + pfr_buf_clear(&ab); + return (0); +_error: + pfr_buf_clear(&ab); + return (-1); +} + +struct keywords { + const char *k_name; + int k_val; +}; + +/* macro gore, but you should've seen the prior indentation nightmare... */ + +#define FREE_LIST(T,r) \ + do { \ + T *p, *node = r; \ + while (node != NULL) { \ + p = node; \ + node = node->next; \ + free(p); \ + } \ + } while (0) + +#define LOOP_THROUGH(T,n,r,C) \ + do { \ + T *n; \ + if (r == NULL) { \ + r = calloc(1, sizeof(T)); \ + if (r == NULL) \ + err(1, "LOOP: calloc"); \ + r->next = NULL; \ + } \ + n = r; \ + while (n != NULL) { \ + do { \ + C; \ + } while (0); \ + n = n->next; \ + } \ + } while (0) + +void +expand_label_str(char *label, size_t len, const char *srch, const char *repl) +{ + char *tmp; + char *p, *q; + + if ((tmp = calloc(1, len)) == NULL) + err(1, "expand_label_str: calloc"); + p = q = label; + while ((q = strstr(p, srch)) != NULL) { + *q = '\0'; + if ((strlcat(tmp, p, len) >= len) || + (strlcat(tmp, repl, len) >= len)) + errx(1, "expand_label: label too long"); + q += strlen(srch); + p = q; + } + if (strlcat(tmp, p, len) >= len) + errx(1, "expand_label: label too long"); + strlcpy(label, tmp, len); /* always fits */ + free(tmp); +} + +void +expand_label_if(const char *name, char *label, size_t len, const char *ifname) +{ + if (strstr(label, name) != NULL) { + if (!*ifname) + expand_label_str(label, len, name, "any"); + else + expand_label_str(label, len, name, ifname); + } +} + +void +expand_label_addr(const char *name, char *label, size_t len, sa_family_t af, + struct node_host *h) +{ + char tmp[64], tmp_not[66]; + + if (strstr(label, name) != NULL) { + switch (h->addr.type) { + case PF_ADDR_DYNIFTL: + snprintf(tmp, sizeof(tmp), "(%s)", h->addr.v.ifname); + break; + case PF_ADDR_TABLE: + snprintf(tmp, sizeof(tmp), "<%s>", h->addr.v.tblname); + break; + case PF_ADDR_NOROUTE: + snprintf(tmp, sizeof(tmp), "no-route"); + break; + case PF_ADDR_ADDRMASK: + if (!af || (PF_AZERO(&h->addr.v.a.addr, af) && + PF_AZERO(&h->addr.v.a.mask, af))) + snprintf(tmp, sizeof(tmp), "any"); + else { + char a[48]; + int bits; + + if (inet_ntop(af, &h->addr.v.a.addr, a, + sizeof(a)) == NULL) + snprintf(tmp, sizeof(tmp), "?"); + else { + bits = unmask(&h->addr.v.a.mask, af); + if ((af == AF_INET && bits < 32) || + (af == AF_INET6 && bits < 128)) + snprintf(tmp, sizeof(tmp), + "%s/%d", a, bits); + else + snprintf(tmp, sizeof(tmp), + "%s", a); + } + } + break; + default: + snprintf(tmp, sizeof(tmp), "?"); + break; + } + + if (h->not) { + snprintf(tmp_not, sizeof(tmp_not), "! %s", tmp); + expand_label_str(label, len, name, tmp_not); + } else + expand_label_str(label, len, name, tmp); + } +} + +void +expand_label_port(const char *name, char *label, size_t len, + struct node_port *port) +{ + char a1[6], a2[6], op[13] = ""; + + if (strstr(label, name) != NULL) { + snprintf(a1, sizeof(a1), "%u", ntohs(port->port[0])); + snprintf(a2, sizeof(a2), "%u", ntohs(port->port[1])); + if (!port->op) + ; + else if (port->op == PF_OP_IRG) + snprintf(op, sizeof(op), "%s><%s", a1, a2); + else if (port->op == PF_OP_XRG) + snprintf(op, sizeof(op), "%s<>%s", a1, a2); + else if (port->op == PF_OP_EQ) + snprintf(op, sizeof(op), "%s", a1); + else if (port->op == PF_OP_NE) + snprintf(op, sizeof(op), "!=%s", a1); + else if (port->op == PF_OP_LT) + snprintf(op, sizeof(op), "<%s", a1); + else if (port->op == PF_OP_LE) + snprintf(op, sizeof(op), "<=%s", a1); + else if (port->op == PF_OP_GT) + snprintf(op, sizeof(op), ">%s", a1); + else if (port->op == PF_OP_GE) + snprintf(op, sizeof(op), ">=%s", a1); + expand_label_str(label, len, name, op); + } +} + +void +expand_label_proto(const char *name, char *label, size_t len, u_int8_t proto) +{ + struct protoent *pe; + char n[4]; + + if (strstr(label, name) != NULL) { + pe = getprotobynumber(proto); + if (pe != NULL) + expand_label_str(label, len, name, pe->p_name); + else { + snprintf(n, sizeof(n), "%u", proto); + expand_label_str(label, len, name, n); + } + } +} + +void +expand_label_nr(const char *name, char *label, size_t len) +{ + char n[11]; + + if (strstr(label, name) != NULL) { + snprintf(n, sizeof(n), "%u", pf->rule_nr); + expand_label_str(label, len, name, n); + } +} + +void +expand_label(char *label, size_t len, const char *ifname, sa_family_t af, + struct node_host *src_host, struct node_port *src_port, + struct node_host *dst_host, struct node_port *dst_port, + u_int8_t proto) +{ + expand_label_if("$if", label, len, ifname); + expand_label_addr("$srcaddr", label, len, af, src_host); + expand_label_addr("$dstaddr", label, len, af, dst_host); + expand_label_port("$srcport", label, len, src_port); + expand_label_port("$dstport", label, len, dst_port); + expand_label_proto("$proto", label, len, proto); + expand_label_nr("$nr", label, len); +} + +#ifndef __DragonFly__ +int +expand_altq(struct pf_altq *a, struct node_if *interfaces, + struct node_queue *nqueues, struct node_queue_bw bwspec, + struct node_queue_opt *opts) +{ + struct pf_altq pa, pb; + char qname[PF_QNAME_SIZE]; + struct node_queue *n; + struct node_queue_bw bw; + int errs = 0; + + if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_queue, nqueues); + return (0); + } + + LOOP_THROUGH(struct node_if, interface, interfaces, + memcpy(&pa, a, sizeof(struct pf_altq)); + if (strlcpy(pa.ifname, interface->ifname, + sizeof(pa.ifname)) >= sizeof(pa.ifname)) + errx(1, "expand_altq: strlcpy"); + + if (interface->not) { + yyerror("altq on ! is not supported"); + errs++; + } else { + if (eval_pfaltq(pf, &pa, &bwspec, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pa)) + errs++; + + if (pf->opts & PF_OPT_VERBOSE) { + print_altq(&pf->paltq->altq, 0, + &bwspec, opts); + if (nqueues && nqueues->tail) { + printf("queue { "); + LOOP_THROUGH(struct node_queue, queue, + nqueues, + printf("%s ", + queue->queue); + ); + printf("}"); + } + printf("\n"); + } + + if (pa.scheduler == ALTQT_CBQ || + pa.scheduler == ALTQT_HFSC) { + /* now create a root queue */ + memset(&pb, 0, sizeof(struct pf_altq)); + if (strlcpy(qname, "root_", sizeof(qname)) >= + sizeof(qname)) + errx(1, "expand_altq: strlcpy"); + if (strlcat(qname, interface->ifname, + sizeof(qname)) >= sizeof(qname)) + errx(1, "expand_altq: strlcat"); + if (strlcpy(pb.qname, qname, + sizeof(pb.qname)) >= sizeof(pb.qname)) + errx(1, "expand_altq: strlcpy"); + if (strlcpy(pb.ifname, interface->ifname, + sizeof(pb.ifname)) >= sizeof(pb.ifname)) + errx(1, "expand_altq: strlcpy"); + pb.qlimit = pa.qlimit; + pb.scheduler = pa.scheduler; + bw.bw_absolute = pa.ifbandwidth; + bw.bw_percent = 0; + if (eval_pfqueue(pf, &pb, &bw, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pb)) + errs++; + } + + LOOP_THROUGH(struct node_queue, queue, nqueues, + n = calloc(1, sizeof(struct node_queue)); + if (n == NULL) + err(1, "expand_altq: calloc"); + if (pa.scheduler == ALTQT_CBQ || + pa.scheduler == ALTQT_HFSC) + if (strlcpy(n->parent, qname, + sizeof(n->parent)) >= + sizeof(n->parent)) + errx(1, "expand_altq: strlcpy"); + if (strlcpy(n->queue, queue->queue, + sizeof(n->queue)) >= sizeof(n->queue)) + errx(1, "expand_altq: strlcpy"); + if (strlcpy(n->ifname, interface->ifname, + sizeof(n->ifname)) >= sizeof(n->ifname)) + errx(1, "expand_altq: strlcpy"); + n->scheduler = pa.scheduler; + n->next = NULL; + n->tail = n; + if (queues == NULL) + queues = n; + else { + queues->tail->next = n; + queues->tail = n; + } + ); + } + ); + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_queue, nqueues); + + return (errs); +} + +int +expand_queue(struct pf_altq *a, struct node_if *interfaces, + struct node_queue *nqueues, struct node_queue_bw bwspec, + struct node_queue_opt *opts) +{ + struct node_queue *n, *nq; + struct pf_altq pa; + u_int8_t found = 0; + u_int8_t errs = 0; + + if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { + FREE_LIST(struct node_queue, nqueues); + return (0); + } + + if (queues == NULL) { + yyerror("queue %s has no parent", a->qname); + FREE_LIST(struct node_queue, nqueues); + return (1); + } + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_queue, tqueue, queues, + if (!strncmp(a->qname, tqueue->queue, PF_QNAME_SIZE) && + (interface->ifname[0] == 0 || + (!interface->not && !strncmp(interface->ifname, + tqueue->ifname, IFNAMSIZ)) || + (interface->not && strncmp(interface->ifname, + tqueue->ifname, IFNAMSIZ)))) { + /* found ourself in queues */ + found++; + + memcpy(&pa, a, sizeof(struct pf_altq)); + + if (pa.scheduler != ALTQT_NONE && + pa.scheduler != tqueue->scheduler) { + yyerror("exactly one scheduler type " + "per interface allowed"); + return (1); + } + pa.scheduler = tqueue->scheduler; + + /* scheduler dependent error checking */ + switch (pa.scheduler) { + case ALTQT_PRIQ: + if (nqueues != NULL) { + yyerror("priq queues cannot " + "have child queues"); + return (1); + } + if (bwspec.bw_absolute > 0 || + bwspec.bw_percent < 100) { + yyerror("priq doesn't take " + "bandwidth"); + return (1); + } + break; + default: + break; + } + + if (strlcpy(pa.ifname, tqueue->ifname, + sizeof(pa.ifname)) >= sizeof(pa.ifname)) + errx(1, "expand_queue: strlcpy"); + if (strlcpy(pa.parent, tqueue->parent, + sizeof(pa.parent)) >= sizeof(pa.parent)) + errx(1, "expand_queue: strlcpy"); + + if (eval_pfqueue(pf, &pa, &bwspec, opts)) + errs++; + else + if (pfctl_add_altq(pf, &pa)) + errs++; + + for (nq = nqueues; nq != NULL; nq = nq->next) { + if (!strcmp(a->qname, nq->queue)) { + yyerror("queue cannot have " + "itself as child"); + errs++; + continue; + } + n = calloc(1, + sizeof(struct node_queue)); + if (n == NULL) + err(1, "expand_queue: calloc"); + if (strlcpy(n->parent, a->qname, + sizeof(n->parent)) >= + sizeof(n->parent)) + errx(1, "expand_queue strlcpy"); + if (strlcpy(n->queue, nq->queue, + sizeof(n->queue)) >= + sizeof(n->queue)) + errx(1, "expand_queue strlcpy"); + if (strlcpy(n->ifname, tqueue->ifname, + sizeof(n->ifname)) >= + sizeof(n->ifname)) + errx(1, "expand_queue strlcpy"); + n->scheduler = tqueue->scheduler; + n->next = NULL; + n->tail = n; + if (queues == NULL) + queues = n; + else { + queues->tail->next = n; + queues->tail = n; + } + } + if ((pf->opts & PF_OPT_VERBOSE) && ( + (found == 1 && interface->ifname[0] == 0) || + (found > 0 && interface->ifname[0] != 0))) { + print_queue(&pf->paltq->altq, 0, + &bwspec, interface->ifname[0] != 0, + opts); + if (nqueues && nqueues->tail) { + printf("{ "); + LOOP_THROUGH(struct node_queue, + queue, nqueues, + printf("%s ", + queue->queue); + ); + printf("}"); + } + printf("\n"); + } + } + ); + ); + + FREE_LIST(struct node_queue, nqueues); + FREE_LIST(struct node_if, interfaces); + + if (!found) { + yyerror("queue %s has no parent", a->qname); + errs++; + } + + if (errs) + return (1); + else + return (0); +} +#endif + +void +expand_rule(struct pf_rule *r, + struct node_if *interfaces, struct node_host *rpool_hosts, + struct node_proto *protos, struct node_os *src_oses, + struct node_host *src_hosts, struct node_port *src_ports, + struct node_host *dst_hosts, struct node_port *dst_ports, + struct node_uid *uids, struct node_gid *gids, struct node_icmp *icmp_types) +{ + sa_family_t af = r->af; + int added = 0, error = 0; + char ifname[IF_NAMESIZE]; + char label[PF_RULE_LABEL_SIZE]; + char tagname[PF_TAG_NAME_SIZE]; + char match_tagname[PF_TAG_NAME_SIZE]; + struct pf_pooladdr *pa; + struct node_host *h; + u_int8_t flags, flagset, keep_state; + + if (strlcpy(label, r->label, sizeof(label)) >= sizeof(label)) + errx(1, "expand_rule: strlcpy"); + if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname)) + errx(1, "expand_rule: strlcpy"); + if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >= + sizeof(match_tagname)) + errx(1, "expand_rule: strlcpy"); + flags = r->flags; + flagset = r->flagset; + keep_state = r->keep_state; + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_proto, proto, protos, + LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types, + LOOP_THROUGH(struct node_host, src_host, src_hosts, + LOOP_THROUGH(struct node_port, src_port, src_ports, + LOOP_THROUGH(struct node_os, src_os, src_oses, + LOOP_THROUGH(struct node_host, dst_host, dst_hosts, + LOOP_THROUGH(struct node_port, dst_port, dst_ports, + LOOP_THROUGH(struct node_uid, uid, uids, + LOOP_THROUGH(struct node_gid, gid, gids, + + r->af = af; + /* for link-local IPv6 address, interface must match up */ + if ((r->af && src_host->af && r->af != src_host->af) || + (r->af && dst_host->af && r->af != dst_host->af) || + (src_host->af && dst_host->af && + src_host->af != dst_host->af) || + (src_host->ifindex && dst_host->ifindex && + src_host->ifindex != dst_host->ifindex) || + (src_host->ifindex && *interface->ifname && + src_host->ifindex != if_nametoindex(interface->ifname)) || + (dst_host->ifindex && *interface->ifname && + dst_host->ifindex != if_nametoindex(interface->ifname))) + continue; + if (!r->af && src_host->af) + r->af = src_host->af; + else if (!r->af && dst_host->af) + r->af = dst_host->af; + + if (*interface->ifname) + memcpy(r->ifname, interface->ifname, sizeof(r->ifname)); + else if (if_indextoname(src_host->ifindex, ifname)) + memcpy(r->ifname, ifname, sizeof(r->ifname)); + else if (if_indextoname(dst_host->ifindex, ifname)) + memcpy(r->ifname, ifname, sizeof(r->ifname)); + else + memset(r->ifname, '\0', sizeof(r->ifname)); + + if (strlcpy(r->label, label, sizeof(r->label)) >= + sizeof(r->label)) + errx(1, "expand_rule: strlcpy"); + if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >= + sizeof(r->tagname)) + errx(1, "expand_rule: strlcpy"); + if (strlcpy(r->match_tagname, match_tagname, + sizeof(r->match_tagname)) >= sizeof(r->match_tagname)) + errx(1, "expand_rule: strlcpy"); + expand_label(r->label, PF_RULE_LABEL_SIZE, r->ifname, r->af, + src_host, src_port, dst_host, dst_port, proto->proto); + expand_label(r->tagname, PF_TAG_NAME_SIZE, r->ifname, r->af, + src_host, src_port, dst_host, dst_port, proto->proto); + expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r->ifname, + r->af, src_host, src_port, dst_host, dst_port, + proto->proto); + + error += check_netmask(src_host, r->af); + error += check_netmask(dst_host, r->af); + + r->ifnot = interface->not; + r->proto = proto->proto; + r->src.addr = src_host->addr; + r->src.not = src_host->not; + r->src.port[0] = src_port->port[0]; + r->src.port[1] = src_port->port[1]; + r->src.port_op = src_port->op; + r->dst.addr = dst_host->addr; + r->dst.not = dst_host->not; + r->dst.port[0] = dst_port->port[0]; + r->dst.port[1] = dst_port->port[1]; + r->dst.port_op = dst_port->op; + r->uid.op = uid->op; + r->uid.uid[0] = uid->uid[0]; + r->uid.uid[1] = uid->uid[1]; + r->gid.op = gid->op; + r->gid.gid[0] = gid->gid[0]; + r->gid.gid[1] = gid->gid[1]; + r->type = icmp_type->type; + r->code = icmp_type->code; + + if ((keep_state == PF_STATE_MODULATE || + keep_state == PF_STATE_SYNPROXY) && + r->proto && r->proto != IPPROTO_TCP) + r->keep_state = PF_STATE_NORMAL; + else + r->keep_state = keep_state; + + if (r->proto && r->proto != IPPROTO_TCP) { + r->flags = 0; + r->flagset = 0; + } else { + r->flags = flags; + r->flagset = flagset; + } + if (icmp_type->proto && r->proto != icmp_type->proto) { + yyerror("icmp-type mismatch"); + error++; + } + + if (src_os && src_os->os) { + r->os_fingerprint = pfctl_get_fingerprint(src_os->os); + if ((pf->opts & PF_OPT_VERBOSE2) && + r->os_fingerprint == PF_OSFP_NOMATCH) + fprintf(stderr, + "warning: unknown '%s' OS fingerprint\n", + src_os->os); + } else { + r->os_fingerprint = PF_OSFP_ANY; + } + + TAILQ_INIT(&r->rpool.list); + for (h = rpool_hosts; h != NULL; h = h->next) { + pa = calloc(1, sizeof(struct pf_pooladdr)); + if (pa == NULL) + err(1, "expand_rule: calloc"); + pa->addr = h->addr; + if (h->ifname != NULL) { + if (strlcpy(pa->ifname, h->ifname, + sizeof(pa->ifname)) >= + sizeof(pa->ifname)) + errx(1, "expand_rule: strlcpy"); + } else + pa->ifname[0] = 0; + TAILQ_INSERT_TAIL(&r->rpool.list, pa, entries); + } + + if (rule_consistent(r) < 0 || error) + yyerror("skipping rule due to errors"); + else { + r->nr = pf->rule_nr++; + pfctl_add_rule(pf, r); + added++; + } + + )))))))))); + + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_proto, protos); + FREE_LIST(struct node_host, src_hosts); + FREE_LIST(struct node_port, src_ports); + FREE_LIST(struct node_os, src_oses); + FREE_LIST(struct node_host, dst_hosts); + FREE_LIST(struct node_port, dst_ports); + FREE_LIST(struct node_uid, uids); + FREE_LIST(struct node_gid, gids); + FREE_LIST(struct node_icmp, icmp_types); + FREE_LIST(struct node_host, rpool_hosts); + + if (!added) + yyerror("rule expands to no valid combination"); +} + +#undef FREE_LIST +#undef LOOP_THROUGH + +int +check_rulestate(int desired_state) +{ + if (require_order && (rulestate > desired_state)) { + yyerror("Rules must be in order: options, normalization, " + "queueing, translation, filtering"); + return (1); + } + rulestate = desired_state; + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "all", ALL}, + { "allow-opts", ALLOWOPTS}, + { "altq", ALTQ}, + { "anchor", ANCHOR}, + { "antispoof", ANTISPOOF}, + { "any", ANY}, + { "bandwidth", BANDWIDTH}, + { "binat", BINAT}, + { "binat-anchor", BINATANCHOR}, + { "bitmask", BITMASK}, + { "block", BLOCK}, + { "block-policy", BLOCKPOLICY}, + { "cbq", CBQ}, + { "code", CODE}, + { "crop", FRAGCROP}, + { "debug", DEBUG}, + { "drop", DROP}, + { "drop-ovl", FRAGDROP}, + { "dup-to", DUPTO}, + { "fastroute", FASTROUTE}, + { "file", FILENAME}, + { "fingerprints", FINGERPRINTS}, + { "flags", FLAGS}, + { "floating", FLOATING}, + { "for", FOR}, + { "fragment", FRAGMENT}, + { "from", FROM}, + { "global", GLOBAL}, + { "group", GROUP}, + { "group-bound", GRBOUND}, + { "hfsc", HFSC}, + { "hostid", HOSTID}, + { "icmp-type", ICMPTYPE}, + { "icmp6-type", ICMP6TYPE}, + { "if-bound", IFBOUND}, + { "in", IN}, + { "inet", INET}, + { "inet6", INET6}, + { "keep", KEEP}, + { "label", LABEL}, + { "limit", LIMIT}, + { "linkshare", LINKSHARE}, + { "load", LOAD}, + { "log", LOG}, + { "log-all", LOGALL}, + { "loginterface", LOGINTERFACE}, + { "max", MAXIMUM}, + { "max-mss", MAXMSS}, + { "max-src-nodes", MAXSRCNODES}, + { "max-src-states", MAXSRCSTATES}, + { "min-ttl", MINTTL}, + { "modulate", MODULATE}, + { "nat", NAT}, + { "nat-anchor", NATANCHOR}, + { "no", NO}, + { "no-df", NODF}, + { "no-route", NOROUTE}, + { "no-sync", NOSYNC}, + { "on", ON}, + { "optimization", OPTIMIZATION}, + { "os", OS}, + { "out", OUT}, + { "pass", PASS}, + { "port", PORT}, + { "priority", PRIORITY}, + { "priq", PRIQ}, + { "proto", PROTO}, + { "qlimit", QLIMIT}, + { "queue", QUEUE}, + { "quick", QUICK}, + { "random", RANDOM}, + { "random-id", RANDOMID}, + { "rdr", RDR}, + { "rdr-anchor", RDRANCHOR}, + { "realtime", REALTIME}, + { "reassemble", REASSEMBLE}, + { "reply-to", REPLYTO}, + { "require-order", REQUIREORDER}, + { "return", RETURN}, + { "return-icmp", RETURNICMP}, + { "return-icmp6", RETURNICMP6}, + { "return-rst", RETURNRST}, + { "round-robin", ROUNDROBIN}, + { "route-to", ROUTETO}, + { "rule", RULE}, + { "scrub", SCRUB}, + { "set", SET}, + { "source-hash", SOURCEHASH}, + { "source-track", SOURCETRACK}, + { "state", STATE}, + { "state-policy", STATEPOLICY}, + { "static-port", STATICPORT}, + { "sticky-address", STICKYADDRESS}, + { "synproxy", SYNPROXY}, + { "table", TABLE}, + { "tag", TAG}, + { "tagged", TAGGED}, + { "tbrsize", TBRSIZE}, + { "timeout", TIMEOUT}, + { "to", TO}, + { "tos", TOS}, + { "ttl", TTL}, + { "upperlimit", UPPERLIMIT}, + { "user", USER}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) { + if (debug > 1) + fprintf(stderr, "%s: %d\n", s, p->k_val); + return (p->k_val); + } else { + if (debug > 1) + fprintf(stderr, "string: %s\n", s); + return (STRING); + } +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(FILE *f) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + while ((c = getc(f)) == '\\') { + next = getc(f); + if (next != '\n') { + if (isspace(next)) + yyerror("whitespace after \\"); + ungetc(next, f); + break; + } + yylval.lineno = lineno; + lineno++; + } + if (c == '\t' || c == ' ') { + /* Compress blanks to a single space. */ + do { + c = getc(f); + } while (c == '\t' || c == ' '); + ungetc(c, f); + c = ' '; + } + + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(fin); + if (c == '\n') { + lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int endc, c, next; + int token; + +top: + p = buf; + while ((c = lgetc(fin)) == ' ') + ; /* nothing */ + + yylval.lineno = lineno; + if (c == '#') + while ((c = lgetc(fin)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = (char)c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + endc = c; + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + if (c == endc) { + *p = '\0'; + break; + } + if (c == '\n') { + lineno++; + continue; + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + case '<': + next = lgetc(fin); + if (next == '>') { + yylval.v.i = PF_OP_XRG; + return (PORTBINARY); + } + lungetc(next); + break; + case '>': + next = lgetc(fin); + if (next == '<') { + yylval.v.i = PF_OP_IRG; + return (PORTBINARY); + } + lungetc(next); + break; + case '-': + next = lgetc(fin); + if (next == '>') + return (ARROW); + lungetc(next); + break; + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = lineno; + lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +parse_rules(FILE *input, struct pfctl *xpf) +{ + struct sym *sym, *next; + + fin = input; + pf = xpf; + lineno = 1; + errors = 0; + rulestate = PFCTL_STATE_NONE; + returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; + returnicmp6default = + (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; + blockpolicy = PFRULE_DROP; + require_order = 1; + + yyparse(); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entries); + if ((pf->opts & PF_OPT_VERBOSE2) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entries); + free(sym); + } + + return (errors ? -1 : 0); +} + +/* + * Over-designed efficiency is a French and German concept, so how about + * we wait until they discover this ugliness and make it all fancy. + */ +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entries)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entries); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entries); + return (0); +} + +int +pfctl_cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + if ((sym = malloc(strlen(s) - strlen(val) + 1)) == NULL) + err(1, "pfctl_cmdline_symset: malloc"); + + strlcpy(sym, s, strlen(s) - strlen(val) + 1); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entries) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +void +decide_address_family(struct node_host *n, sa_family_t *af) +{ + sa_family_t target_af = 0; + + while (!*af && n != NULL) { + if (n->af) { + if (target_af == 0) + target_af = n->af; + if (target_af != n->af) + return; + } + n = n->next; + } + if (!*af && target_af) + *af = target_af; +} + +void +remove_invalid_hosts(struct node_host **nh, sa_family_t *af) +{ + struct node_host *n = *nh, *prev = NULL; + + while (n != NULL) { + if (*af && n->af && n->af != *af) { + /* unlink and free n */ + struct node_host *next = n->next; + + /* adjust tail pointer */ + if (n == (*nh)->tail) + (*nh)->tail = prev; + /* adjust previous node's next pointer */ + if (prev == NULL) + *nh = next; + else + prev->next = next; + /* free node */ + if (n->ifname != NULL) + free(n->ifname); + free(n); + n = next; + } else { + if (n->af && !*af) + *af = n->af; + prev = n; + n = n->next; + } + } +} + +int +invalid_redirect(struct node_host *nh, sa_family_t af) +{ + if (!af) { + struct node_host *n; + + /* tables and dyniftl are ok without an address family */ + for (n = nh; n != NULL; n = n->next) { + if (n->addr.type != PF_ADDR_TABLE && + n->addr.type != PF_ADDR_DYNIFTL) { + yyerror("address family not given and " + "translation address expands to multiple " + "address families"); + return (1); + } + } + } + if (nh == NULL) { + yyerror("no translation address with matching address family " + "found."); + return (1); + } + return (0); +} + +int +atoul(char *s, u_long *ulvalp) +{ + u_long ulval; + char *ep; + + errno = 0; + ulval = strtoul(s, &ep, 0); + if (s[0] == '\0' || *ep != '\0') + return (-1); + if (errno == ERANGE && ulval == ULONG_MAX) + return (-1); + *ulvalp = ulval; + return (0); +} + +int +getservice(char *n) +{ + struct servent *s; + u_long ulval; + + if (atoul(n, &ulval) == 0) { + if (ulval > 65535) { + yyerror("illegal port value %d", ulval); + return (-1); + } + return (htons(ulval)); + } else { + s = getservbyname(n, "tcp"); + if (s == NULL) + s = getservbyname(n, "udp"); + if (s == NULL) { + yyerror("unknown port %s", n); + return (-1); + } + return (s->s_port); + } +} + +int +rule_label(struct pf_rule *r, char *s) +{ + if (s) { + if (strlcpy(r->label, s, sizeof(r->label)) >= + sizeof(r->label)) { + yyerror("rule label too long (max %d chars)", + sizeof(r->label)-1); + return (-1); + } + } + return (0); +} + +u_int16_t +parseicmpspec(char *w, sa_family_t af) +{ + const struct icmpcodeent *p; + u_long ulval; + u_int8_t icmptype; + + if (af == AF_INET) + icmptype = returnicmpdefault >> 8; + else + icmptype = returnicmp6default >> 8; + + if (atoul(w, &ulval) == -1) { + if ((p = geticmpcodebyname(icmptype, w, af)) == NULL) { + yyerror("unknown icmp code %s", w); + return (0); + } + ulval = p->code; + } + if (ulval > 255) { + yyerror("invalid icmp code %ld", ulval); + return (0); + } + return (icmptype << 8 | ulval); +} + +int +pfctl_load_anchors(int dev, int opts, struct pfr_buffer *trans) +{ + struct loadanchors *la; + + TAILQ_FOREACH(la, &loadanchorshead, entries) { + if (opts & PF_OPT_VERBOSE) + fprintf(stderr, "\nLoading anchor %s:%s from %s\n", + la->anchorname, la->rulesetname, la->filename); + if (pfctl_rules(dev, la->filename, opts, la->anchorname, + la->rulesetname, trans) == -1) + return (-1); + } + + return (0); +} + diff --git a/usr.sbin/pfctl/pf.conf.5 b/usr.sbin/pfctl/pf.conf.5 new file mode 100644 index 0000000000..643e5ae186 --- /dev/null +++ b/usr.sbin/pfctl/pf.conf.5 @@ -0,0 +1,2633 @@ +.\" $OpenBSD: pf.conf.5,v 1.291 2004/02/04 19:38:30 jmc Exp $ +.\" $DragonFly: src/usr.sbin/pfctl/pf.conf.5,v 1.1 2004/09/21 21:25:28 joerg Exp $ +.\" +.\" Copyright (c) 2002, Daniel Hartmeier +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" - Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" - Redistributions in binary form must reproduce the above +.\" copyright notice, this list of conditions and the following +.\" disclaimer in the documentation and/or other materials provided +.\" with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +.\" CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd November 19, 2002 +.Dt PF.CONF 5 +.Os +.Sh NAME +.Nm pf.conf +.Nd packet filter configuration file +.Sh DESCRIPTION +The +.Xr pf 4 +packet filter modifies, drops or passes packets according to rules or +definitions specified in +.Nm pf.conf . +.Sh STATEMENT ORDER +There are seven types of statements in +.Nm pf.conf : +.Bl -tag -width xxxx +.It Cm Macros +User-defined variables may be defined and used later, simplifying +the configuration file. +Macros must be defined before they are referenced in +.Nm pf.conf . +.It Cm Tables +Tables provide a mechanism for increasing the performance and flexibility of +rules with large numbers of source or destination addresses. +.It Cm Options +Options tune the behaviour of the packet filtering engine. +.It Cm Traffic Normalization Li (e.g. Em scrub ) +Traffic normalization protects internal machines against inconsistencies +in Internet protocols and implementations. +.It Cm Queueing +Queueing provides rule-based bandwidth control. +.It Cm Translation Li (Various forms of NAT) +Translation rules specify how addresses are to be mapped or redirected to +other addresses. +.It Cm Packet Filtering +Stateful and stateless packet filtering provides rule-based blocking or +passing of packets. +.El +.Pp +With the exception of +.Cm macros +and +.Cm tables , +the types of statements should be grouped and appear in +.Nm pf.conf +in the order shown above, as this matches the operation of the underlying +packet filtering engine. +By default +.Xr pfctl 8 +enforces this order (see +.Ar set require-order +below). +.Sh MACROS +Much like +.Xr cpp 1 +or +.Xr m4 1 , +macros can be defined that will later be expanded in context. +Macro names must start with a letter, and may contain letters, digits +and underscores. +Macro names may not be reserved words (for example +.Ar pass , +.Ar in , +.Ar out ) . +Macros are not expanded inside quotes. +.Pp +For example, +.Bd -literal -offset indent +ext_if = \&"kue0\&" +all_ifs = \&"{\&" $ext_if lo0 \&"}\&" +pass out on $ext_if from any to any keep state +pass in on $ext_if proto tcp from any to any port 25 keep state +.Ed +.Sh TABLES +Tables are named structures which can hold a collection of addresses and +networks. +Lookups against tables in +.Xr pf 4 +are relatively fast, making a single rule with tables much more efficient, +in terms of +processor usage and memory consumption, than a large number of rules which +differ only in IP address (either created explicitly or automatically by rule +expansion). +.Pp +Tables can be used as the source or destination of filter rules, +.Ar scrub +rules +or +translation rules such as +.Ar nat +or +.Ar rdr +(see below for details on the various rule types). +Tables can also be used for the redirect address of +.Ar nat +and +.Ar rdr +rules and in the routing options of filter rules, but only for +.Ar round-robin +pools. +.Pp +Tables can be defined with any of the following +.Xr pfctl 8 +mechanisms. +As with macros, reserved words may not be used as table names. +.Bl -tag -width "manually" +.It Ar manually +Persistent tables can be manually created with the +.Ar add +or +.Ar replace +option of +.Xr pfctl 8 , +before or after the ruleset has been loaded. +.It Pa pf.conf +Table definitions can be placed directly in this file, and loaded at the +same time as other rules are loaded, atomically. +Table definitions inside +.Nm pf.conf +use the +.Ar table +statement, and are especially useful to define non-persistent tables. +The contents of a pre-existing table defined without a list of addresses +to initialize it is not altered when +.Nm pf.conf +is loaded. +A table initialized with the empty list, +.Li { } , +will be cleared on load. +.El +.Pp +Tables may be defined with the following two attributes: +.Bl -tag -width persist +.It Ar persist +The +.Ar persist +flag forces the kernel to keep the table even when no rules refer to it. +If the flag is not set, the kernel will automatically remove the table +when the last rule referring to it is flushed. +.It Ar const +The +.Ar const +flag prevents the user from altering the contents of the table once it +has been created. +Without that flag, +.Xr pfctl 8 +can be used to add or remove addresses from the table at any time, even +when running with +.Xr securelevel 7 += 2. +.El +.Pp +For example, +.Bd -literal -offset indent +table const { 10/8, 172.16/12, 192.168/16 } +table persist +block on fxp0 from { , } to any +.Ed +.Pp +creates a table called private, to hold RFC 1918 private network +blocks, and a table called badhosts, which is initially empty. +A filter rule is set up to block all traffic coming from addresses listed in +either table. +The private table cannot have its contents changed and the badhosts table +will exist even when no active filter rules reference it. +Addresses may later be added to the badhosts table, so that traffic from +these hosts can be blocked by using +.Bd -literal -offset indent +# pfctl -t badhosts -Tadd 204.92.77.111 +.Ed +.Pp +A table can also be initialized with an address list specified in one or more +external files, using the following syntax: +.Bd -literal -offset indent +table persist file \&"/etc/spammers\&" file \&"/etc/openrelays\&" +block on fxp0 from to any +.Ed +.Pp +The files +.Pa /etc/spammers +and +.Pa /etc/openrelays +list IP addresses, one per line. +Any lines beginning with a # are treated as comments and ignored. +In addition to being specified by IP address, hosts may also be +specified by their hostname. +When the resolver is called to add a hostname to a table, +.Em all +resulting IPv4 and IPv6 addresses are placed into the table. +IP addresses can also be entered in a table by specifying a valid interface +name or the +.Em self +keyword, in which case all addresses assigned to the interface(s) will be +added to the table. +.Sh OPTIONS +.Xr pf 4 +may be tuned for various situations using the +.Ar set +command. +.Bl -tag -width xxxx +.It Ar set timeout +.Pp +.Bl -tag -width interval -compact +.It Ar interval +Interval between purging expired states and fragments. +.It Ar frag +Seconds before an unassembled fragment is expired. +.It Ar src.track +Length of time to retain a source tracking entry after the last state +expires. +.El +.Pp +When a packet matches a stateful connection, the seconds to live for the +connection will be updated to that of the +.Ar proto.modifier +which corresponds to the connection state. +Each packet which matches this state will reset the TTL. +Tuning these values may improve the performance of the +firewall at the risk of dropping valid idle connections. +.Pp +.Bl -tag -width xxxx -compact +.It Ar tcp.first +The state after the first packet. +.It Ar tcp.opening +The state before the destination host ever sends a packet. +.It Ar tcp.established +The fully established state. +.It Ar tcp.closing +The state after the first FIN has been sent. +.It Ar tcp.finwait +The state after both FINs have been exchanged and the connection is closed. +Some hosts (notably web servers on Solaris) send TCP packets even after closing +the connection. +Increasing +.Ar tcp.finwait +(and possibly +.Ar tcp.closing ) +can prevent blocking of such packets. +.It Ar tcp.closed +The state after one endpoint sends an RST. +.El +.Pp +ICMP and UDP are handled in a fashion similar to TCP, but with a much more +limited set of states: +.Pp +.Bl -tag -width xxxx -compact +.It Ar udp.first +The state after the first packet. +.It Ar udp.single +The state if the source host sends more than one packet but the destination +host has never sent one back. +.It Ar udp.multiple +The state if both hosts have sent packets. +.It Ar icmp.first +The state after the first packet. +.It Ar icmp.error +The state after an ICMP error came back in response to an ICMP packet. +.El +.Pp +Other protocols are handled similarly to UDP: +.Pp +.Bl -tag -width xxxx -compact +.It Ar other.first +.It Ar other.single +.It Ar other.multiple +.El +.Pp +Timeout values can be reduced adaptively as the number of state table +entries grows. +.Pp +.Bl -tag -width xxxx -compact +.It Ar adaptive.start +When the number of state entries exceeds this value, adaptive scaling +begins. +All timeout values are scaled linearly with factor +(adaptive.end - number of states) / (adaptive.end - adaptive.start). +.It Ar adaptive.end +When reaching this number of state entries, all timeout values become +zero, effectively purging all state entries immediately. +This value is used to define the scale factor, it should not actually +be reached (set a lower state limit, see below). +.El +.Pp +These values can be defined both globally and for each rule. +When used on a per-rule basis, the values relate to the number of +states created by the rule, otherwise to the total number of +states. +.Pp +For example: +.Bd -literal -offset indent +set timeout tcp.first 120 +set timeout tcp.established 86400 +set timeout { adaptive.start 6000, adaptive.end 12000 } +set limit states 10000 +.Ed +.Pp +With 9000 state table entries, the timeout values are scaled to 50% +(tcp.first 60, tcp.established 43200). +.Pp +.It Ar set loginterface +Enable collection of packet and byte count statistics for the given interface. +These statistics can be viewed using +.Bd -literal -offset indent +# pfctl -s info +.Ed +.Pp +In this example +.Xr pf 4 +collects statistics on the interface named dc0: +.Bd -literal -offset indent +set loginterface dc0 +.Ed +.Pp +One can disable the loginterface using: +.Bd -literal -offset indent +set loginterface none +.Ed +.Pp +.It Ar set limit +Sets hard limits on the memory pools used by the packet filter. +See +.Xr pool 9 +for an explanation of memory pools. +.Pp +For example, +.Bd -literal -offset indent +set limit states 20000 +.Ed +.Pp +sets the maximum number of entries in the memory pool used by state table +entries (generated by +.Ar keep state +rules) to 20000. +Using +.Bd -literal -offset indent +set limit frags 20000 +.Ed +.Pp +sets the maximum number of entries in the memory pool used for fragment +reassembly (generated by +.Ar scrub +rules) to 20000. +Finally, +.Bd -literal -offset indent +set limit src-nodes 2000 +.Ed +.Pp +sets the maximum number of entries in the memory pool used for tracking +source IP addresses (generated by the +.Ar sticky-address +and +.Ar source-track +options) to 2000. +.Pp +These can be combined: +.Bd -literal -offset indent +set limit { states 20000, frags 20000, src-nodes 2000 } +.Ed +.Pp +.It Ar set optimization +Optimize the engine for one of the following network environments: +.Pp +.Bl -tag -width xxxx -compact +.It Ar normal +A normal network environment. +Suitable for almost all networks. +.It Ar high-latency +A high-latency environment (such as a satellite connection). +.It Ar satellite +Alias for +.Ar high-latency . +.It Ar aggressive +Aggressively expire connections. +This can greatly reduce the memory usage of the firewall at the cost of +dropping idle connections early. +.It Ar conservative +Extremely conservative settings. +Avoid dropping legitimate connections at the +expense of greater memory utilization (possibly much greater on a busy +network) and slightly increased processor utilization. +.El +.Pp +For example: +.Bd -literal -offset indent +set optimization aggressive +.Ed +.Pp +.It Ar set block-policy +The +.Ar block-policy +option sets the default behaviour for the packet +.Ar block +action: +.Pp +.Bl -tag -width xxxxxxxx -compact +.It Ar drop +Packet is silently dropped. +.It Ar return +A TCP RST is returned for blocked TCP packets, +an ICMP UNREACHABLE is returned for blocked UDP packets, +and all other packets are silently dropped. +.El +.Pp +For example: +.Bd -literal -offset indent +set block-policy return +.Ed +.It Ar set state-policy +The +.Ar state-policy +option sets the default behaviour for states: +.Pp +.Bl -tag -width group-bound -compact +.It Ar if-bound +States are bound to interface. +.It Ar group-bound +States are bound to interface group (i.e. ppp) +.It Ar floating +States can match packets on any interfaces (the default). +.El +.Pp +For example: +.Bd -literal -offset indent +set state-policy if-bound +.Ed +.It Ar set require-order +By default +.Xr pfctl 8 +enforces an ordering of the statement types in the ruleset to: +.Em options , +.Em normalization , +.Em queueing , +.Em translation , +.Em filtering . +Setting this option to +.Ar no +disables this enforcement. +There may be non-trivial and non-obvious implications to an out of +order ruleset. +Consider carefully before disabling the order enforcement. +.It Ar set fingerprints +Load fingerprints of known operating systems from the given filename. +By default fingerprints of known operating systems are automatically +loaded from +.Xr pf.os 5 +in +.Pa /etc +but can be overridden via this option. +Setting this option may leave a small period of time where the fingerprints +referenced by the currently active ruleset are inconsistent until the new +ruleset finishes loading. +.Pp +For example: +.Pp +.Dl set fingerprints \&"/etc/pf.os.devel\&" +.Pp +.It Ar set debug +Set the debug +.Ar level +to one of the following: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Ar none +Don't generate debug messages. +.It Ar urgent +Generate debug messages only for serious errors. +.It Ar misc +Generate debug messages for various errors. +.It Ar loud +Generate debug messages for common conditions. +.El +.El +.Sh TRAFFIC NORMALIZATION +Traffic normalization is used to sanitize packet content in such +a way that there are no ambiguities in packet interpretation on +the receiving side. +The normalizer does IP fragment reassembly to prevent attacks +that confuse intrusion detection systems by sending overlapping +IP fragments. +Packet normalization is invoked with the +.Ar scrub +directive. +.Pp +.Ar scrub +has the following options: +.Bl -tag -width xxxx +.It Ar no-df +Clears the +.Ar dont-fragment +bit from a matching IP packet. +Some operating systems are known to generate fragmented packets with the +.Ar dont-fragment +bit set. +This is particularly true with NFS. +.Ar Scrub +will drop such fragmented +.Ar dont-fragment +packets unless +.Ar no-df +is specified. +.Pp +Unfortunately some operating systems also generate their +.Ar dont-fragment +packets with a zero IP identification field. +Clearing the +.Ar dont-fragment +bit on packets with a zero IP ID may cause deleterious results if an +upstream router later fragments the packet. +Using the +.Ar random-id +modifier (see below) is recommended in combination with the +.Ar no-df +modifier to ensure unique IP identifiers. +.It Ar min-ttl +Enforces a minimum TTL for matching IP packets. +.It Ar max-mss +Enforces a maximum MSS for matching TCP packets. +.It Ar random-id +Replaces the IP identification field with random values to compensate +for predictable values generated by many hosts. +This option only applies to outgoing packets that are not fragmented +after the optional fragment reassembly. +.It Ar fragment reassemble +Using +.Ar scrub +rules, fragments can be reassembled by normalization. +In this case, fragments are buffered until they form a complete +packet, and only the completed packet is passed on to the filter. +The advantage is that filter rules have to deal only with complete +packets, and can ignore fragments. +The drawback of caching fragments is the additional memory cost. +But the full reassembly method is the only method that currently works +with NAT. +This is the default behavior of a +.Ar scrub +rule if no fragmentation modifier is supplied. +.It Ar fragment crop +The default fragment reassembly method is expensive, hence the option +to crop is provided. +In this case, +.Xr pf 4 +will track the fragments and cache a small range descriptor. +Duplicate fragments are dropped and overlaps are cropped. +Thus data will only occur once on the wire with ambiguities resolving to +the first occurrence. +Unlike the +.Ar fragment reassemble +modifier, fragments are not buffered, they are passed as soon as they +are received. +The +.Ar fragment crop +reassembly mechanism does not yet work with NAT. +.Pp +.It Ar fragment drop-ovl +This option is similar to the +.Ar fragment crop +modifier except that all overlapping or duplicate fragments will be +dropped, and all further corresponding fragments will be +dropped as well. +.It Ar reassemble tcp +Statefully normalizes TCP connections. +.Ar scrub reassemble tcp +rules may not have the direction (in/out) specified. +.Ar reassemble tcp +performs the following normalizations: +.Pp +.Bl -tag -width timeout -compact +.It ttl +Neither side of the connection is allowed to reduce their IP TTL. +An attacker may send a packet such that it reaches the firewall, affects +the firewall state, and expires before reaching the destination host. +.Ar reassemble tcp +will raise the TTL of all packets back up to the highest value seen on +the connection. +.It timeout modulation +Modern TCP stacks will send a timestamp on every TCP packet and echo +the other endpoint's timestamp back to them. +Many operating systems will merely start the timestamp at zero when +first booted, and increment it several times a second. +The uptime of the host can be deduced by reading the timestamp and multiplying +by a constant. +Also observing several different timestamps can be used to count hosts +behind a NAT device. +And spoofing TCP packets into a connection requires knowing or guessing +valid timestamps. +Timestamps merely need to be monotonically increasing and not derived off a +guessable base time. +.Ar reassemble tcp +will cause +.Ar scrub +to modulate the TCP timestamps with a random number. +.El +.El +.Pp +For example, +.Bd -literal -offset indent +scrub in on $ext_if all fragment reassemble +.Ed +.Sh QUEUEING +Packets can be assigned to queues for the purpose of bandwidth +control. +At least two declarations are required to configure queues, and later +any packet filtering rule can reference the defined queues by name. +During the filtering component of +.Nm pf.conf , +the last referenced +.Ar queue +name is where any packets from +.Ar pass +rules will be queued, while for +.Ar block +rules it specifies where any resulting ICMP or TCP RST +packets should be queued. +The +.Ar scheduler +defines the algorithm used to decide which packets get delayed, dropped, or +sent out immediately. +There are three +.Ar schedulers +currently supported. +.Bl -tag -width xxxx +.It Ar cbq +Class Based Queueing. +.Ar Queues +attached to an interface build a tree, thus each +.Ar queue +can have further child +.Ar queues . +Each queue can have a +.Ar priority +and a +.Ar bandwidth +assigned. +.Ar Priority +mainly controls the time packets take to get sent out, while +.Ar bandwidth +has primarily effects on throughput. +.It Ar priq +Priority Queueing. +.Ar Queues +are flat attached to the interface, thus, +.Ar queues +cannot have further child +.Ar queues . +Each +.Ar queue +has a unique +.Ar priority +assigned, ranging from 0 to 15. +Packets in the +.Ar queue +with the highest +.Ar priority +are processed first. +.It Ar hfsc +Hierarchical Fair Service Curve. +.Ar Queues +attached to an interface build a tree, thus each +.Ar queue +can have further child +.Ar queues . +Each queue can have a +.Ar priority +and a +.Ar bandwidth +assigned. +.Ar Priority +mainly controls the time packets take to get sent out, while +.Ar bandwidth +has primarily effects on throughput. +.El +.Pp +The interfaces on which queueing should be activated are declared using +the +.Ar altq on +declaration. +.Ar altq on +has the following keywords: +.Bl -tag -width xxxx +.It Ar +Queueing is enabled on the named interface. +.It Ar +Specifies which queueing scheduler to use. +Currently supported values +are +.Ar cbq +for Class Based Queueing, +.Ar priq +for Priority Queueing and +.Ar hfsc +for the Hierarchical Fair Service Curve scheduler. +.It Ar bandwidth +The maximum bitrate for all queues on an +interface may be specified using the +.Ar bandwidth +keyword. +The value can be specified as an absolute value or as a +percentage of the interface bandwidth. +When using an absolute value, the suffixes +.Ar b , +.Ar Kb , +.Ar Mb , +and +.Ar Gb +are used to represent bits, kilobits, megabits, and +gigabits per second, respectively. +The value must not exceed the interface bandwidth. +If +.Ar bandwidth +is not specified, the interface bandwidth is used. +.It Ar qlimit +The maximum number of packets held in the queue. +The default is 50. +.It Ar tbrsize +Adjusts the size, in bytes, of the token bucket regulator. +If not specified, heuristics based on the +interface bandwidth are used to determine the size. +.It Ar queue +Defines a list of subqueues to create on an interface. +.El +.Pp +In the following example, the interface dc0 +should queue up to 5 Mbit/s in four second-level queues using +Class Based Queueing. +Those four queues will be shown in a later example. +.Bd -literal -offset indent +altq on dc0 cbq bandwidth 5Mb queue { std, http, mail, ssh } +.Ed +.Pp +Once interfaces are activated for queueing using the +.Ar altq +directive, a sequence of +.Ar queue +directives may be defined. +The name associated with a +.Ar queue +must match a queue defined in the +.Ar altq +directive (e.g. mail), or, except for the +.Ar priq +.Ar scheduler , +in a parent +.Ar queue +declaration. +The following keywords can be used: +.Bl -tag -width xxxx +.It Ar on +Specifies the interface the queue operates on. +If not given, it operates on all matching interfaces. +.It Ar bandwidth +Specifies the maximum bitrate to be processed by the queue. +This value must not exceed the value of the parent +.Ar queue +and can be specified as an absolute value or a percentage of the parent +queue's bandwidth. +The +.Ar priq +scheduler does not support bandwidth specification. +.It Ar priority +Between queues a priority level can be set. +For +.Ar cbq +and +.Ar hfsc , +the range is 0 to 7 and for +.Ar priq , +the range is 0 to 15. +The default for all is 1. +.Ar Priq +queues with a higher priority are always served first. +.Ar Cbq +and +.Ar Hfsc +queues with a higher priority are preferred in the case of overload. +.It Ar qlimit +The maximum number of packets held in the queue. +The default is 50. +.El +.Pp +The +.Ar scheduler +can get additional parameters with +.Ar Ns Li (\& Ar No ) . +Parameters are as follows: +.Bl -tag -width Fl +.It Ar default +Packets not matched by another queue are assigned to this one. +Exactly one default queue is required. +.It Ar red +Enable RED (Random Early Detection) on this queue. +RED drops packets with a probability proportional to the average +queue length. +.It Ar rio +Enables RIO on this queue. +RIO is RED with IN/OUT, thus running +RED two times more than RIO would achieve the same effect. +RIO is currently not supported in the GENERIC kernel. +.It Ar ecn +Enables ECN (Explicit Congestion Notification) on this queue. +ECN implies RED. +.El +.Pp +The +.Ar cbq +.Ar scheduler +supports an additional option: +.Bl -tag -width Fl +.It Ar borrow +The queue can borrow bandwidth from the parent. +.El +.Pp +The +.Ar hfsc +.Ar scheduler +supports some additional options: +.Bl -tag -width Fl +.It Ar realtime +The minimum required bandwidth for the queue. +.It Ar upperlimit +The maximum allowed bandwidth for the queue. +.It Ar linkshare +The bandwidth share of a backlogged queue. +.El +.Pp + is an acronym for +.Ar service curve . +.Pp +The format for service curve specifications is +.Ar ( m1 , d , m2 ) . +.Ar m2 +controls the bandwidth assigned to the queue. +.Ar m1 +and +.Ar d +are optional and can be used to control the initial bandwidth assignment. +For the first +.Ar d +milliseconds the queue gets the bandwidth given as +.Ar m1 , +afterwards the value given in +.Ar m2 . +.Pp +Furthermore, with +.Ar cbq +and +.Ar hfsc , +child queues can be specified as in an +.Ar altq +declaration, thus building a tree of queues using a part of +their parent's bandwidth. +.Pp +Packets can be assigned to queues based on filter rules by using the +.Ar queue +keyword. +Normally only one +.Ar queue +is specified; when a second one is specified it will instead be used for +packets which have a +.Em TOS +of +.Em lowdelay +and for TCP ACKs with no data payload. +.Pp +To continue the previous example, the examples below would specify the +four referenced +queues, plus a few child queues. +Interactive +.Xr ssh 1 +sessions get priority over bulk transfers like +.Xr scp 1 +and +.Xr sftp 1 . +The queues may then be referenced by filtering rules (see +.Sx PACKET FILTERING +below). +.Bd -literal +queue std bandwidth 10% cbq(default) +queue http bandwidth 60% priority 2 cbq(borrow red) \e + { employees, developers } +queue developers bandwidth 75% cbq(borrow) +queue employees bandwidth 15% +queue mail bandwidth 10% priority 0 cbq(borrow ecn) +queue ssh bandwidth 20% cbq(borrow) { ssh_interactive, ssh_bulk } +queue ssh_interactive priority 7 +queue ssh_bulk priority 0 + +block return out on dc0 inet all queue std +pass out on dc0 inet proto tcp from $developerhosts to any port 80 \e + keep state queue developers +pass out on dc0 inet proto tcp from $employeehosts to any port 80 \e + keep state queue employees +pass out on dc0 inet proto tcp from any to any port 22 \e + keep state queue(ssh_bulk, ssh_interactive) +pass out on dc0 inet proto tcp from any to any port 25 \e + keep state queue mail +.Ed +.Sh TRANSLATION +Translation rules modify either the source or destination address of the +packets associated with a stateful connection. +A stateful connection is automatically created to track packets matching +such a rule as long as they are not blocked by the filtering section of +.Nm pf.conf . +The translation engine modifies the specified address and/or port in the +packet, recalculates IP, TCP and UDP checksums as necessary, and passes it to +the packet filter for evaluation. +.Pp +Since translation occurs before filtering the filter +engine will see packets as they look after any +addresses and ports have been translated. Filter rules +will therefore have to filter based on the translated +address and port number. +Packets that match a translation rule are only automatically passed if +the +.Ar pass +modifier is given, otherwise they are +still subject to +.Ar block +and +.Ar pass +rules. +.Pp +The state entry created permits +.Xr pf 4 +to keep track of the original address for traffic associated with that state +and correctly direct return traffic for that connection. +.Pp +Various types of translation are possible with pf: +.Bl -tag -width xxxx +.It Ar binat +A +.Ar binat +rule specifies a bidirectional mapping between an external IP netblock +and an internal IP netblock. +.It Ar nat +A +.Ar nat +rule specifies that IP addresses are to be changed as the packet +traverses the given interface. +This technique allows one or more IP addresses +on the translating host to support network traffic for a larger range of +machines on an "inside" network. +Although in theory any IP address can be used on the inside, it is strongly +recommended that one of the address ranges defined by RFC 1918 be used. +These netblocks are: +.Bd -literal +10.0.0.0 - 10.255.255.255 (all of net 10, i.e., 10/8) +172.16.0.0 - 172.31.255.255 (i.e., 172.16/12) +192.168.0.0 - 192.168.255.255 (i.e., 192.168/16) +.Ed +.It Pa rdr +The packet is redirected to another destination and possibly a +different port. +.Ar rdr +rules can optionally specify port ranges instead of single ports. +rdr ... port 2000:2999 -> ... port 4000 +redirects ports 2000 to 2999 (inclusive) to port 4000. +rdr ... port 2000:2999 -> ... port 4000:* +redirects port 2000 to 4000, 2001 to 4001, ..., 2999 to 4999. +.El +.Pp +In addition to modifying the address, some translation rules may modify +source or destination ports for +.Xr tcp 4 +or +.Xr udp 4 +connections; implicitly in the case of +.Ar nat +rules and explicitly in the case of +.Ar rdr +rules. +Port numbers are never translated with a +.Ar binat +rule. +.Pp +For each packet processed by the translator, the translation rules are +evaluated in sequential order, from first to last. +The first matching rule decides what action is taken. +.Pp +The +.Ar no +option prefixed to a translation rule causes packets to remain untranslated, +much in the same way as +.Ar drop quick +works in the packet filter (see below). +If no rule matches the packet it is passed to the filter engine unmodified. +.Pp +Translation rules apply only to packets that pass through +the specified interface, and if no interface is specified, +translation is applied to packets on all interfaces. +For instance, redirecting port 80 on an external interface to an internal +web server will only work for connections originating from the outside. +Connections to the address of the external interface from local hosts will +not be redirected, since such packets do not actually pass through the +external interface. +Redirections cannot reflect packets back through the interface they arrive +on, they can only be redirected to hosts connected to different interfaces +or to the firewall itself. +.Pp +Note that redirecting external incoming connections to the loopback +address, as in +.Bd -literal -offset indent +rdr on ne3 inet proto tcp to port 8025 -> 127.0.0.1 port 25 +.Ed +.Pp +will effectively allow an external host to connect to daemons +bound solely to the loopback address, circumventing the traditional +blocking of such connections on a real interface. +Unless this effect is desired, any of the local non-loopback addresses +should be used as redirection target instead, which allows external +connections only to daemons bound to this address or not bound to +any address. +.Pp +See +.Sx TRANSLATION EXAMPLES +below. +.Sh PACKET FILTERING +.Xr pf 4 +has the ability to +.Ar block +and +.Ar pass +packets based on attributes of their layer 3 (see +.Xr ip 4 +and +.Xr ip6 4 ) +and layer 4 (see +.Xr icmp 4 , +.Xr icmp6 4 , +.Xr tcp 4 , +.Xr udp 4 ) +headers. +In addition, packets may also be +assigned to queues for the purpose of bandwidth control. +.Pp +For each packet processed by the packet filter, the filter rules are +evaluated in sequential order, from first to last. +The last matching rule decides what action is taken. +.Pp +The following actions can be used in the filter: +.Bl -tag -width xxxx +.It Ar block +The packet is blocked. +There are a number of ways in which a +.Ar block +rule can behave when blocking a packet. +The default behaviour is to +.Ar drop +packets silently, however this can be overridden or made +explicit either globally, by setting the +.Ar block-policy +option, or on a per-rule basis with one of the following options: +.Pp +.Bl -tag -width xxxx -compact +.It Ar drop +The packet is silently dropped. +.It Ar return-rst +This applies only to +.Xr tcp 4 +packets, and issues a TCP RST which closes the +connection. +.It Ar return-icmp +.It Ar return-icmp6 +This causes ICMP messages to be returned for packets which match the rule. +By default this is an ICMP UNREACHABLE message, however this +can be overridden by specifying a message as a code or number. +.It Ar return +This causes a TCP RST to be returned for +.Xr tcp 4 +packets and an ICMP UNREACHABLE for UDP and other packets. +.El +.Pp +Options returning packets have no effect if +.Xr pf 4 +operates on a +.Xr bridge 4 . +.It Ar pass +The packet is passed. +.El +.Pp +If no rule matches the packet, the default action is +.Ar pass . +.Pp +To block everything by default and only pass packets +that match explicit rules, one uses +.Bd -literal -offset indent +block all +.Ed +.Pp +as the first filter rule. +.Pp +See +.Sx FILTER EXAMPLES +below. +.Sh PARAMETERS +The rule parameters specify the packets to which a rule applies. +A packet always comes in on, or goes out through, one interface. +Most parameters are optional. +If a parameter is specified, the rule only applies to packets with +matching attributes. +Certain parameters can be expressed as lists, in which case +.Xr pfctl 8 +generates all needed rule combinations. +.Bl -tag -width xxxx +.It Ar in No or Ar out +This rule applies to incoming or outgoing packets. +If neither +.Ar in +nor +.Ar out +are specified, the rule will match packets in both directions. +.It Ar log +In addition to the action specified, a log message is generated. +All packets for that connection are logged, unless the +.Ar keep state , +.Ar modulate state +or +.Ar synproxy state +options are specified, in which case only the +packet that establishes the state is logged. +(See +.Ar keep state , +.Ar modulate state +and +.Ar synproxy state +below). +The logged packets are sent to the +.Xr pflog 4 +interface. +This interface is monitored by the +.Xr pflogd 8 +logging daemon, which dumps the logged packets to the file +.Pa /var/log/pflog +in +.Xr pcap 3 +binary format. +.It Ar log-all +Used with +.Ar keep state , +.Ar modulate state +or +.Ar synproxy state +rules to force logging of all packets for a connection. +As with +.Ar log , +packets are logged to +.Xr pflog 4 . +.It Ar quick +If a packet matches a rule which has the +.Ar quick +option set, this rule +is considered the last matching rule, and evaluation of subsequent rules +is skipped. +.It Ar on +This rule applies only to packets coming in on, or going out through, this +particular interface. +It is also possible to simply give the interface driver name, like ppp or fxp, +to make the rule match packets flowing through a group of interfaces. +.It Ar +This rule applies only to packets of this address family. +Supported values are +.Ar inet +and +.Ar inet6 . +.It Ar proto +This rule applies only to packets of this protocol. +Common protocols are +.Xr icmp 4 , +.Xr icmp6 4 , +.Xr tcp 4 , +and +.Xr udp 4 . +For a list of all the protocol name to number mappings used by +.Xr pfctl 8 , +see the file +.Em /etc/protocols . +.It Xo +.Ar from port os +.Ar to port +.Xc +This rule applies only to packets with the specified source and destination +addresses and ports. +.Pp +Addresses can be specified in CIDR notation (matching netblocks), as +symbolic host names or interface names, or as any of the following keywords: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Ar any +Any address. +.It Ar no-route +Any address which is not currently routable. +.It Ar +Any address that matches the given table. +.El +.Pp +Interface names can have modifiers appended: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Ar :network +Translates to the network(s) attached to the interface. +.It Ar :broadcast +Translates to the interface's broadcast address(es). +.It Ar :peer +Translates to the point to point interface's peer address(es). +.It Ar :0 +Do not include interface aliases. +.El +.Pp +Host names may also have the +.Ar :0 +option appended to restrict the name resolution to the first of each +v4 and v6 address found. +.Pp +Host name resolution and interface to address translation are done at +ruleset load-time. +When the address of an interface (or host name) changes (under DHCP or PPP, +for instance), the ruleset must be reloaded for the change to be reflected +in the kernel. +Surrounding the interface name (and optional modifiers) in parentheses +changes this behaviour. +When the interface name is surrounded by parentheses, the rule is +automatically updated whenever the interface changes its address. +The ruleset does not need to be reloaded. +This is especially useful with +.Ar nat . +.Pp +Ports can be specified either by number or by name. +For example, port 80 can be specified as +.Em www . +For a list of all port name to number mappings used by +.Xr pfctl 8 , +see the file +.Pa /etc/services . +.Pp +Ports and ranges of ports are specified by using these operators: +.Bd -literal -offset indent += (equal) +!= (unequal) +< (less than) +<= (less than or equal) +> (greater than) +>= (greater than or equal) +: (range including boundaries) +>< (range excluding boundaries) +<> (except range) +.Ed +.Pp +><, <> and : +are binary operators (they take two arguments). +For instance: +.Bl -tag -width Fl +.It Ar port 2000:2004 +means +.Sq all ports >= 2000 and <= 2004 , +hence ports 2000, 2001, 2002, 2003 and 2004. +.It Ar port 2000 >< 2004 +means +.Sq all ports > 2000 and < 2004 , +hence ports 2001, 2002 and 2003. +.It Ar port 2000 <> 2004 +means +.Sq all ports < 2000 or > 2004 , +hence ports 1-1999 and 2005-65535. +.El +.Pp +The operating system of the source host can be specified in the case of TCP +rules with the +.Ar OS +modifier. +See the +.Sx OPERATING SYSTEM FINGERPRINTING +section for more information. +.Pp +The host, port and OS specifications are optional, as in the following examples: +.Bd -literal -offset indent +pass in all +pass in from any to any +pass in proto tcp from any port <= 1024 to any +pass in proto tcp from any to any port 25 +pass in proto tcp from 10.0.0.0/8 port > 1024 \e + to ! 10.1.2.3 port != ssh +pass in proto tcp from any os "OpenBSD" flags S/SA +.Ed +.It Ar all +This is equivalent to "from any to any". +.It Ar group +Similar to +.Ar user , +this rule only applies to packets of sockets owned by the specified group. +.It Ar user +This rule only applies to packets of sockets owned by the specified user. +For outgoing connections initiated from the firewall, this is the user +that opened the connection. +For incoming connections to the firewall itself, this is the user that +listens on the destination port. +For forwarded connections, where the firewall is not a connection endpoint, +the user and group are +.Em unknown . +.Pp +All packets, both outgoing and incoming, of one connection are associated +with the same user and group. +Only TCP and UDP packets can be associated with users; for other protocols +these parameters are ignored. +.Pp +User and group refer to the effective (as opposed to the real) IDs, in +case the socket is created by a setuid/setgid process. +User and group IDs are stored when a socket is created; +when a process creates a listening socket as root (for instance, by +binding to a privileged port) and subsequently changes to another +user ID (to drop privileges), the credentials will remain root. +.Pp +User and group IDs can be specified as either numbers or names. +The syntax is similar to the one for ports. +The value +.Em unknown +matches packets of forwarded connections. +.Em unknown +can only be used with the operators +.Cm = +and +.Cm != . +Other constructs like +.Cm user >= unknown +are invalid. +Forwarded packets with unknown user and group ID match only rules +that explicitly compare against +.Em unknown +with the operators +.Cm = +or +.Cm != . +For instance +.Cm user >= 0 +does not match forwarded packets. +The following example allows only selected users to open outgoing +connections: +.Bd -literal -offset indent +block out proto { tcp, udp } all +pass out proto { tcp, udp } all \e + user { < 1000, dhartmei } keep state +.Ed +.It Ar flags / | / +This rule only applies to TCP packets that have the flags +.Ar +set out of set +.Ar . +Flags not specified in +.Ar +are ignored. +The flags are: (F)IN, (S)YN, (R)ST, (P)USH, (A)CK, (U)RG, (E)CE, and C(W)R. +.Bl -tag -width Fl +.It Ar flags S/S +Flag SYN is set. +The other flags are ignored. +.It Ar flags S/SA +Out of SYN and ACK, exactly SYN may be set. +SYN, SYN+PSH and SYN+RST match, but SYN+ACK, ACK and ACK+RST do not. +This is more restrictive than the previous example. +.It Ar flags /SFRA +If the first set is not specified, it defaults to none. +All of SYN, FIN, RST and ACK must be unset. +.El +.It Ar icmp-type code +.It Ar icmp6-type code +This rule only applies to ICMP or ICMPv6 packets with the specified type +and code. +This parameter is only valid for rules that cover protocols ICMP or +ICMP6. +The protocol and the ICMP type indicator (icmp-type or icmp6-type) +must match. +.It Ar allow-opts +By default, packets which contain IP options are blocked. +When +.Ar allow-opts +is specified for a +.Ar pass +rule, packets that pass the filter based on that rule (last matching) +do so even if they contain IP options. +For packets that match state, the rule that initially created the +state is used. +The implicit +.Ar pass +rule that is used when a packet does not match any rules does not +allow IP options. +.It Ar label +Adds a label (name) to the rule, which can be used to identify the rule. +For instance, +pfctl -s labels +shows per-rule statistics for rules that have labels. +.Pp +The following macros can be used in labels: +.Pp +.Bl -tag -width $srcaddr -compact -offset indent +.It Ar $if +The interface. +.It Ar $srcaddr +The source IP address. +.It Ar $dstaddr +The destination IP address. +.It Ar $srcport +The source port specification. +.It Ar $dstport +The destination port specification. +.It Ar $proto +The protocol name. +.It Ar $nr +The rule number. +.El +.Pp +For example: +.Bd -literal -offset indent +ips = \&"{ 1.2.3.4, 1.2.3.5 }\&" +pass in proto tcp from any to $ips \e + port > 1023 label \&"$dstaddr:$dstport\&" +.Ed +.Pp +expands to +.Bd -literal -offset indent +pass in inet proto tcp from any to 1.2.3.4 \e + port > 1023 label \&"1.2.3.4:>1023\&" +pass in inet proto tcp from any to 1.2.3.5 \e + port > 1023 label \&"1.2.3.5:>1023\&" +.Ed +.Pp +The macro expansion for the +.Ar label +directive occurs only at configuration file parse time, not during runtime. +.It Ar queue | ( , ) +Packets matching this rule will be assigned to the specified queue. +If two queues are given, packets which have a +.Em tos +of +.Em lowdelay +and TCP ACKs with no data payload will be assigned to the second one. +See +.Sx QUEUEING +for setup details. +.Pp +For example: +.Bd -literal -offset indent +pass in proto tcp to port 25 queue mail +pass in proto tcp to port 22 queue(ssh_bulk, ssh_prio) +.Ed +.It Ar tag +Packets matching this rule will be tagged with the +specified string. +The tag acts as an internal marker that can be used to +identify these packets later on. +This can be used, for example, to provide trust between +interfaces and to determine if packets have been +processed by translation rules. +Tags are +.Qq sticky , +meaning that the packet will be tagged even if the rule +is not the last matching rule. +Further matching rules can replace the tag with a +new one but will not remove a previously applied tag. +A packet is only ever assigned one tag at a time. +.Ar pass +rules that use the +.Ar tag +keyword must also use +.Ar keep state , +.Ar modulate state +or +.Ar synproxy state . +Packet tagging can be done during +.Ar nat , +.Ar rdr , +or +.Ar binat +rules in addition to filter rules. +Tags take the same macros as labels (see above). +.It Ar tagged +Used with filter rules to specify that packets must already +be tagged with the given tag in order to match the rule. +Inverse tag matching can also be done +by specifying the +.Cm !\& +operator before the +.Ar tagged +keyword. +.El +.Sh ROUTING +If a packet matches a rule with a route option set, the packet filter will +route the packet according to the type of route option. +When such a rule creates state, the route option is also applied to all +packets matching the same connection. +.Bl -tag -width xxxx +.It Ar fastroute +The +.Ar fastroute +option does a normal route lookup to find the next hop for the packet. +.It Ar route-to +The +.Ar route-to +option routes the packet to the specified interface with an optional address +for the next hop. +When a +.Ar route-to +rule creates state, only packets that pass in the same direction as the +filter rule specifies will be routed in this way. +Packets passing in the opposite direction (replies) are not affected +and are routed normally. +.It Ar reply-to +The +.Ar reply-to +option is similar to +.Ar route-to , +but routes packets that pass in the opposite direction (replies) to the +specified interface. +Opposite direction is only defined in the context of a state entry, and +.Ar route-to +is useful only in rules that create state. +It can be used on systems with multiple external connections to +route all outgoing packets of a connection through the interface +the incoming connection arrived through (symmetric routing enforcement). +.It Ar dup-to +The +.Ar dup-to +option creates a duplicate of the packet and routes it like +.Ar route-to . +The original packet gets routed as it normally would. +.El +.Sh POOL OPTIONS +For +.Ar nat +and +.Ar rdr +rules, (as well as for the +.Ar route-to , +.Ar reply-to +and +.Ar dup-to +rule options) for which there is a single redirection address which has a +subnet mask smaller than 32 for IPv4 or 128 for IPv6 (more than one IP +address), a variety of different methods for assigning this address can be +used: +.Bl -tag -width xxxx +.It Ar bitmask +The +.Ar bitmask +option applies the network portion of the redirection address to the address +to be modified (source with +.Ar nat , +destination with +.Ar rdr ) . +.It Ar random +The +.Ar random +option selects an address at random within the defined block of addresses. +.It Ar source-hash +The +.Ar source-hash +option uses a hash of the source address to determine the redirection address, +ensuring that the redirection address is always the same for a given source. +An optional key can be specified after this keyword either in hex or as a +string; by default +.Xr pfctl 8 +randomly generates a key for source-hash every time the +ruleset is reloaded. +.It Ar round-robin +The +.Ar round-robin +option loops through the redirection address(es). +.Pp +When more than one redirection address is specified, +.Ar round-robin +is the only permitted pool type. +.It Ar static-port +With +.Ar nat +rules, the +.Ar static-port +option prevents +.Xr pf 4 +from modifying the source port on TCP and UDP packets. +.El +.Pp +Additionally, the +.Ar sticky-address +option can be specified to help ensure that multiple connections from the +same source are mapped to the same redirection address. +This option can be used with the +.Ar random +and +.Ar round-robin +pool options. +Note that by default these associations are destroyed as soon as there are +no longer states which refer to them; in order to make the mappings last +beyond the lifetime of the states, increase the global options with +.Ar set timeout source-track +See +.Sx STATEFUL TRACKING OPTIONS +for more ways to control the source tracking. +.Sh STATEFUL INSPECTION +.Xr pf 4 +is a stateful packet filter, which means it can track the state of +a connection. +Instead of passing all traffic to port 25, for instance, it is possible +to pass only the initial packet, and then begin to keep state. +Subsequent traffic will flow because the filter is aware of the connection. +.Pp +If a packet matches a +.Ar pass ... keep state +rule, the filter creates a state for this connection and automatically +lets pass all subsequent packets of that connection. +.Pp +Before any rules are evaluated, the filter checks whether the packet +matches any state. +If it does, the packet is passed without evaluation of any rules. +.Pp +States are removed after the connection is closed or has timed out. +.Pp +This has several advantages. +Comparing a packet to a state involves checking its sequence numbers. +If the sequence numbers are outside the narrow windows of expected +values, the packet is dropped. +This prevents spoofing attacks, such as when an attacker sends packets with +a fake source address/port but does not know the connection's sequence +numbers. +.Pp +Also, looking up states is usually faster than evaluating rules. +If there are 50 rules, all of them are evaluated sequentially in O(n). +Even with 50000 states, only 16 comparisons are needed to match a +state, since states are stored in a binary search tree that allows +searches in O(log2 n). +.Pp +For instance: +.Bd -literal -offset indent +block all +pass out proto tcp from any to any flags S/SA keep state +pass in proto tcp from any to any port 25 flags S/SA keep state +.Ed +.Pp +This ruleset blocks everything by default. +Only outgoing connections and incoming connections to port 25 are allowed. +The initial packet of each connection has the SYN +flag set, will be passed and creates state. +All further packets of these connections are passed if they match a state. +.Pp +By default, packets coming in and out of any interface can match a state, +but it is also possible to change that behaviour by assigning states to a +single interface or a group of interfaces. +.Pp +The default policy is specified by the +.Ar state-policy +global option, but this can be adjusted on a per-rule basis by adding one +of the +.Ar if-bound , +.Ar group-bound +or +.Ar floating +keywords to the +.Ar keep state +option. +For example, if a rule is defined as: +.Bd -literal -offset indent +pass out on ppp from any to 10.12/16 keep state (group-bound) +.Ed +.Pp +A state created on ppp0 would match packets an all PPP interfaces, +but not packets flowing through fxp0 or any other interface. +.Pp +Keeping rules +.Ar floating +is the more flexible option when the firewall is in a dynamic routing +environment. +However, this has some security implications since a state created by one +trusted network could allow potentially hostile packets coming in from other +interfaces. +.Pp +Specifying +.Ar flags S/SA +restricts state creation to the initial SYN +packet of the TCP handshake. +One can also be less restrictive, and allow state creation from +intermediate +.Pq non-SYN +packets. +This will cause +.Xr pf 4 +to synchronize to existing connections, for instance +if one flushes the state table. +.Pp +For UDP, which is stateless by nature, +.Ar keep state +will create state as well. +UDP packets are matched to states using only host addresses and ports. +.Pp +ICMP messages fall into two categories: ICMP error messages, which always +refer to a TCP or UDP packet, are matched against the referred to connection. +If one keeps state on a TCP connection, and an ICMP source quench message +referring to this TCP connection arrives, it will be matched to the right +state and get passed. +.Pp +For ICMP queries, +.Ar keep state +creates an ICMP state, and +.Xr pf 4 +knows how to match ICMP replies to states. +For example, +.Bd -literal -offset indent +pass out inet proto icmp all icmp-type echoreq keep state +.Ed +.Pp +allows echo requests (such as those created by +.Xr ping 8 ) +out, creates state, and matches incoming echo replies correctly to states. +.Pp +Note: +.Ar nat , binat No and Ar rdr +rules implicitly create state for connections. +.Sh STATE MODULATION +Much of the security derived from TCP is attributable to how well the +initial sequence numbers (ISNs) are chosen. +Some popular stack implementations choose +.Em very +poor ISNs and thus are normally susceptible to ISN prediction exploits. +By applying a +.Ar modulate state +rule to a TCP connection, +.Xr pf 4 +will create a high quality random sequence number for each connection +endpoint. +.Pp +The +.Ar modulate state +directive implicitly keeps state on the rule and is +only applicable to TCP connections. +.Pp +For instance: +.Bd -literal -offset indent +block all +pass out proto tcp from any to any modulate state +pass in proto tcp from any to any port 25 flags S/SA modulate state +.Ed +.Pp +There are two caveats associated with state modulation: +A +.Ar modulate state +rule can not be applied to a pre-existing but unmodulated connection. +Such an application would desynchronize TCP's strict +sequencing between the two endpoints. +Instead, +.Xr pf 4 +will treat the +.Ar modulate state +modifier as a +.Ar keep state +modifier and the pre-existing connection will be inferred without +the protection conferred by modulation. +.Pp +The other caveat affects currently modulated states when the state table +is lost (firewall reboot, flushing the state table, etc...). +.Xr pf 4 +will not be able to infer a connection again after the state table flushes +the connection's modulator. +When the state is lost, the connection may be left dangling until the +respective endpoints time out the connection. +It is possible on a fast local network for the endpoints to start an ACK +storm while trying to resynchronize after the loss of the modulator. +Using a +.Ar flags S/SA +modifier on +.Ar modulate state +rules between fast networks is suggested to prevent ACK storms. +.Sh SYN PROXY +By default, +.Xr pf 4 +passes packets that are part of a +.Xr tcp 4 +handshake between the endpoints. +The +.Ar synproxy state +option can be used to cause +.Xr pf 4 +itself to complete the handshake with the active endpoint, perform a handshake +with the passive endpoint, and then forward packets between the endpoints. +.Pp +No packets are sent to the passive endpoint before the active endpoint has +completed the handshake, hence so-called SYN floods with spoofed source +addresses will not reach the passive endpoint, as the sender can't complete the +handshake. +.Pp +The proxy is transparent to both endpoints, they each see a single +connection from/to the other endpoint. +.Xr pf 4 +chooses random initial sequence numbers for both handshakes. +Once the handshakes are completed, the sequence number modulators +(see previous section) are used to translate further packets of the +connection. +Hence, +.Ar synproxy state +includes +.Ar modulate state +and +.Ar keep state . +.Pp +Rules with +.Ar synproxy +will not work if +.Xr pf 4 +operates on a +.Xr bridge 4 . +.Pp +Example: +.Bd -literal -offset indent +pass in proto tcp from any to any port www flags S/SA synproxy state +.Ed +.Sh STATEFUL TRACKING OPTIONS +All three of +.Ar keep state , +.Ar modulate state +and +.Ar synproxy state +support the following options: +.Pp +.Bl -tag -width xxxx -compact +.It Ar max +Limits the number of concurrent states the rule may create. +When this limit is reached, further packets matching the rule that would +create state are dropped, until existing states time out. +.It Ar no-sync +Prevent state changes for states created by this rule from appearing on the +.Xr pfsync 4 +interface. +.It Ar +Changes the timeout values used for states created by this rule. +.Pp +When the +.Ar source-track +keyword is specified, the number of states per source IP is tracked. +The following limits can be set: +.Pp +.Bl -tag -width xxxx -compact +.It Ar max-src-nodes +Limits the maximum number of source addresses which can simultaneously +have state table entries. +.It Ar max-src-states +Limits the maximum number of simultaneous state entries that a single +source address can create with this rule. +.El +For a list of all valid timeout names, see +.Sx OPTIONS +above. +.Pp +Multiple options can be specified, separated by commas: +.Bd -literal +pass in proto tcp from any to any \e + port www flags S/SA keep state \e + (max 100, source-track rule, max-src-nodes 75, \e + max-src-states 3, tcp.established 60, tcp.closing 5) +.Ed +.El +.Sh OPERATING SYSTEM FINGERPRINTING +Passive OS Fingerprinting is a mechanism to inspect nuances of a TCP +connection's initial SYN packet and guess at the host's operating system. +Unfortunately these nuances are easily spoofed by an attacker so the +fingerprint is not useful in making security decisions. +But the fingerprint is typically accurate enough to make policy decisions +upon. +.Pp +The fingerprints may be specified by operating system class, by +version, or by subtype/patchlevel. +The class of an operating system is typically the vender or genre +and would be OpenBSD for the +.Xr pf 4 +firewall itself. +The version of the oldest available OpenBSD release on the main ftp site +would be 2.6 and the fingerprint would be written +.Pp +.Dl \&"OpenBSD 2.6\&" +.Pp +The subtype of an operating system is typically used to describe the +patchlevel if that patch led to changes in the TCP stack behavior. +In the case of OpenBSD, the only subtype is for a fingerprint that was +normalized by the +.Ar no-df +scrub option and would be specified as +.Pp +.Dl \&"OpenBSD 3.3 no-df\&" +.Pp +Fingerprints for most popular operating systems are provided by +.Xr pf.os 5 . +Once +.Xr pf 4 +is running, a complete list of known operating system fingerprints may +be listed by running: +.Pp +.Dl # pfctl -so +.Pp +Filter rules can enforce policy at any level of operating system specification +assuming a fingerprint is present. +Policy could limit traffic to approved operating systems or even ban traffic +from hosts that aren't at the latest service pack. +.Pp +The +.Ar unknown +class can also be used as the fingerprint which will match packets for +which no operating system fingerprint is known. +.Pp +Examples: +.Bd -literal -offset indent +pass out proto tcp from any os OpenBSD keep state +block out proto tcp from any os Doors +block out proto tcp from any os "Doors PT" +block out proto tcp from any os "Doors PT SP3" +block out from any os "unknown" +pass on lo0 proto tcp from any os "OpenBSD 3.3 lo0" keep state +.Ed +.Pp +Operating system fingerprinting is limited only to the TCP SYN packet. +This means that it will not work on other protocols and will not match +a currently established connection. +.Pp +Caveat: operating system fingerprints are occasionally wrong. +There are three problems: an attacker can trivially craft his packets to +appear as any operating system he chooses; +an operating system patch could change the stack behavior and no fingerprints +will match it until the database is updated; +and multiple operating systems may have the same fingerprint. +.Sh BLOCKING SPOOFED TRAFFIC +"Spoofing" is the faking of IP addresses, typically for malicious +purposes. +The +.Ar antispoof +directive expands to a set of filter rules which will block all +traffic with a source IP from the network(s) directly connected +to the specified interface(s) from entering the system through +any other interface. +.Pp +For example, the line +.Bd -literal -offset indent +antispoof for lo0 +.Ed +.Pp +expands to +.Bd -literal -offset indent +block drop in on ! lo0 inet from 127.0.0.1/8 to any +block drop in on ! lo0 inet6 from ::1 to any +.Ed +.Pp +For non-loopback interfaces, there are additional rules to block incoming +packets with a source IP address identical to the interface's IP(s). +For example, assuming the interface wi0 had an IP address of 10.0.0.1 and a +netmask of 255.255.255.0, +the line +.Bd -literal -offset indent +antispoof for wi0 inet +.Ed +.Pp +expands to +.Bd -literal -offset indent +block drop in on ! wi0 inet from 10.0.0.0/24 to any +block drop in inet from 10.0.0.1 to any +.Ed +.Pp +Caveat: Rules created by the +.Ar antispoof +directive interfere with packets sent over loopback interfaces +to local addresses. +One should pass these explicitly. +.Sh FRAGMENT HANDLING +The size of IP datagrams (packets) can be significantly larger than the +maximum transmission unit (MTU) of the network. +In cases when it is necessary or more efficient to send such large packets, +the large packet will be fragmented into many smaller packets that will each +fit onto the wire. +Unfortunately for a firewalling device, only the first logical fragment will +contain the necessary header information for the subprotocol that allows +.Xr pf 4 +to filter on things such as TCP ports or to perform NAT. +.Pp +Besides the use of +.Ar scrub +rules as described in +.Sx TRAFFIC NORMALIZATION +above, there are three options for handling fragments in the packet filter. +.Pp +One alternative is to filter individual fragments with filter rules. +If no +.Ar scrub +rule applies to a fragment, it is passed to the filter. +Filter rules with matching IP header parameters decide whether the +fragment is passed or blocked, in the same way as complete packets +are filtered. +Without reassembly, fragments can only be filtered based on IP header +fields (source/destination address, protocol), since subprotocol header +fields are not available (TCP/UDP port numbers, ICMP code/type). +The +.Ar fragment +option can be used to restrict filter rules to apply only to +fragments, but not complete packets. +Filter rules without the +.Ar fragment +option still apply to fragments, if they only specify IP header fields. +For instance, the rule +.Bd -literal -offset indent +pass in proto tcp from any to any port 80 +.Ed +.Pp +never applies to a fragment, even if the fragment is part of a TCP +packet with destination port 80, because without reassembly this information +is not available for each fragment. +This also means that fragments cannot create new or match existing +state table entries, which makes stateful filtering and address +translation (NAT, redirection) for fragments impossible. +.Pp +It's also possible to reassemble only certain fragments by specifying +source or destination addresses or protocols as parameters in +.Ar scrub +rules. +.Pp +In most cases, the benefits of reassembly outweigh the additional +memory cost, and it's recommended to use +.Ar scrub +rules to reassemble +all fragments via the +.Ar fragment reassemble +modifier. +.Pp +The memory allocated for fragment caching can be limited using +.Xr pfctl 8 . +Once this limit is reached, fragments that would have to be cached +are dropped until other entries time out. +The timeout value can also be adjusted. +.Pp +Currently, only IPv4 fragments are supported and IPv6 fragments +are blocked unconditionally. +.Sh ANCHORS AND NAMED RULESETS +Besides the main ruleset, +.Xr pfctl 8 +can load named rulesets into +.Ar anchor +attachment points. +An +.Ar anchor +contains a list of named rulesets. +An +.Ar anchor +has a name which specifies where +.Xr pfctl 8 +can be used to attach sub-rulesets. +A named ruleset contains filter and translation rules, like the +main ruleset. +The main ruleset can reference +.Ar anchor +attachment points +using the following kinds +of rules: +.Bl -tag -width xxxx +.It Ar nat-anchor +Evaluates the +.Ar nat +rules of all named rulesets in the specified +.Ar anchor . +.It Ar rdr-anchor +Evaluates the +.Ar rdr +rules of all named rulesets in the specified +.Ar anchor . +.It Ar binat-anchor +Evaluates the +.Ar binat +rules of all named rulesets in the specified +.Ar anchor . +.It Ar anchor +Evaluates the filter rules of all named rulesets in the specified +.Ar anchor . +.It Ar load anchor : from +Loads the rules from the specified file into the named +ruleset +.Ar +attached to the anchor +.Ar . +.El +.Pp +When evaluation of the main ruleset reaches an +.Ar anchor +rule, +.Xr pf 4 +will proceed to evaluate all rules specified in the +named rulesets attached to that +.Ar anchor . +.Pp +Matching filter rules in named rulesets with the +.Ar quick +option and matching translation rules are final and abort the +evaluation of both the rules in the +.Ar anchor +and the main ruleset. +.Pp +Only the main ruleset can contain +.Ar anchor +rules. +.Pp +When an +.Ar anchor +contains more than one named ruleset, they are evaluated +in the alphabetical order of their names. +.Pp +Rules may contain +.Ar anchor +attachment points which do not contain any rules when the main ruleset +is loaded, and later such named rulesets can be manipulated through +.Xr pfctl 8 +without reloading the main ruleset. +For example, +.Bd -literal -offset indent +ext_if = \&"kue0\&" +block on $ext_if all +anchor spam +pass out on $ext_if all keep state +pass in on $ext_if proto tcp from any \e + to $ext_if port smtp keep state +.Ed +.Pp +blocks all packets on the external interface by default, then evaluates +all rulesets in the +.Ar anchor +named "spam", and finally passes all outgoing connections and +incoming connections to port 25. +.Bd -literal -offset indent +# echo \&"block in quick from 1.2.3.4 to any\&" \&| \e + pfctl -a spam:manual -f - +.Ed +.Pp +loads a single ruleset containing a single rule into the +.Ar anchor , +which blocks all packets from a specific address. +.Pp +The named ruleset can also be populated by adding a +.Ar load anchor +rule after the +.Ar anchor +rule: +.Bd -literal -offset indent +anchor spam +load anchor spam:manual from "/etc/pf-spam.conf" +.Ed +.Pp +When +.Xr pfctl 8 +loads +.Nm pf.conf , +it will also load all the rules from the file +.Pa /etc/pf-spam.conf +into the named ruleset. +.Pp +Optionally, +.Ar anchor +rules can specify the parameter's +direction, interface, address family, protocol and source/destination +address/port +using the same syntax as filter rules. +When parameters are used, the +.Ar anchor +rule is only evaluated for matching packets. +This allows conditional evaluation of named rulesets, like: +.Bd -literal -offset indent +block on $ext_if all +anchor spam proto tcp from any to any port smtp +pass out on $ext_if all keep state +pass in on $ext_if proto tcp from any to $ext_if port smtp keep state +.Ed +.Pp +The rules inside +.Ar anchor +spam are only evaluated for +.Ar tcp +packets with destination port 25. +Hence, +.Bd -literal -offset indent +# echo \&"block in quick from 1.2.3.4 to any" \&| \e + pfctl -a spam:manual -f - +.Ed +.Pp +will only block connections from 1.2.3.4 to port 25. +.Sh TRANSLATION EXAMPLES +This example maps incoming requests on port 80 to port 8080, on +which a daemon is running (because, for example, it is not run as root, +and therefore lacks permission to bind to port 80). +.Bd -literal +# use a macro for the interface name, so it can be changed easily +ext_if = \&"ne3\&" + +# map daemon on 8080 to appear to be on 80 +rdr on $ext_if proto tcp from any to any port 80 -> 127.0.0.1 port 8080 +.Ed +.Pp +If the +.Ar pass +modifier is given, packets matching the translation rule are passed without +inspecting the filter rules: +.Bd -literal +rdr pass on $ext_if proto tcp from any to any port 80 -> 127.0.0.1 \e + port 8080 +.Ed +.Pp +In the example below, vlan12 is configured as 192.168.168.1; +the machine translates all packets coming from 192.168.168.0/24 to 204.92.77.111 +when they are going out any interface except vlan12. +This has the net effect of making traffic from the 192.168.168.0/24 +network appear as though it is the Internet routable address +204.92.77.111 to nodes behind any interface on the router except +for the nodes on vlan12. +(Thus, 192.168.168.1 can talk to the 192.168.168.0/24 nodes.) +.Bd -literal +nat on ! vlan12 from 192.168.168.0/24 to any -> 204.92.77.111 +.Ed +.Pp +In the example below, the machine sits between a fake internal 144.19.74.* +network, and a routable external IP of 204.92.77.100. +The +.Ar no nat +rule excludes protocol AH from being translated. +.Bd -literal +# NO NAT +no nat on $ext_if proto ah from 144.19.74.0/24 to any +nat on $ext_if from 144.19.74.0/24 to any -> 204.92.77.100 +.Ed +.Pp +In the example below, packets bound for one specific server, as well as those +generated by the sysadmins are not proxied; all other connections are. +.Bd -literal +# NO RDR +no rdr on $int_if proto { tcp, udp } from any to $server port 80 +no rdr on $int_if proto { tcp, udp } from $sysadmins to any port 80 +rdr on $int_if proto { tcp, udp } from any to any port 80 -> 127.0.0.1 \e + port 80 +.Ed +.Pp +This longer example uses both a NAT and a redirection. +The external interface has the address 157.161.48.183. +On the internal interface, we are running +.Xr ftp-proxy 8 , +listening for outbound ftp sessions captured to port 8021. +.Bd -literal +# NAT +# Translate outgoing packets' source addresses (any protocol). +# In this case, any address but the gateway's external address is mapped. +nat on $ext_if inet from ! ($ext_if) to any -> ($ext_if) + +# NAT PROXYING +# Map outgoing packets' source port to an assigned proxy port instead of +# an arbitrary port. +# In this case, proxy outgoing isakmp with port 500 on the gateway. +nat on $ext_if inet proto udp from any port = isakmp to any -> ($ext_if) \e + port 500 + +# BINAT +# Translate outgoing packets' source address (any protocol). +# Translate incoming packets' destination address to an internal machine +# (bidirectional). +binat on $ext_if from 10.1.2.150 to any -> ($ext_if) + +# RDR +# Translate incoming packets' destination addresses. +# As an example, redirect a TCP and UDP port to an internal machine. +rdr on $ext_if inet proto tcp from any to ($ext_if) port 8080 \e + -> 10.1.2.151 port 22 +rdr on $ext_if inet proto udp from any to ($ext_if) port 8080 \e + -> 10.1.2.151 port 53 + +# RDR +# Translate outgoing ftp control connections to send them to localhost +# for proxying with ftp-proxy(8) running on port 8021. +rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 port 8021 +.Ed +.Pp +In this example, a NAT gateway is set up to translate internal addresses +using a pool of public addresses (192.0.2.16/28) and to redirect +incoming web server connections to a group of web servers on the internal +network. +.Bd -literal +# NAT LOAD BALANCE +# Translate outgoing packets' source addresses using an address pool. +# A given source address is always translated to the same pool address by +# using the source-hash keyword. +nat on $ext_if inet from any to any -> 192.0.2.16/28 source-hash + +# RDR ROUND ROBIN +# Translate incoming web server connections to a group of web servers on +# the internal network. +rdr on $ext_if proto tcp from any to any port 80 \e + -> { 10.1.2.155, 10.1.2.160, 10.1.2.161 } round-robin +.Ed +.Sh FILTER EXAMPLES +.Bd -literal +# The external interface is kue0 +# (157.161.48.183, the only routable address) +# and the private network is 10.0.0.0/8, for which we are doing NAT. + +# use a macro for the interface name, so it can be changed easily +ext_if = \&"kue0\&" + +# normalize all incoming traffic +scrub in on $ext_if all fragment reassemble + +# block and log everything by default +block return log on $ext_if all + +# block anything coming from source we have no back routes for +block in from no-route to any + +# block and log outgoing packets that do not have our address as source, +# they are either spoofed or something is misconfigured (NAT disabled, +# for instance), we want to be nice and do not send out garbage. +block out log quick on $ext_if from ! 157.161.48.183 to any + +# silently drop broadcasts (cable modem noise) +block in quick on $ext_if from any to 255.255.255.255 + +# block and log incoming packets from reserved address space and invalid +# addresses, they are either spoofed or misconfigured, we cannot reply to +# them anyway (hence, no return-rst). +block in log quick on $ext_if from { 10.0.0.0/8, 172.16.0.0/12, \e + 192.168.0.0/16, 255.255.255.255/32 } to any + +# ICMP + +# pass out/in certain ICMP queries and keep state (ping) +# state matching is done on host addresses and ICMP id (not type/code), +# so replies (like 0/0 for 8/0) will match queries +# ICMP error messages (which always refer to a TCP/UDP packet) are +# handled by the TCP/UDP states +pass on $ext_if inet proto icmp all icmp-type 8 code 0 keep state + +# UDP + +# pass out all UDP connections and keep state +pass out on $ext_if proto udp all keep state + +# pass in certain UDP connections and keep state (DNS) +pass in on $ext_if proto udp from any to any port domain keep state + +# TCP + +# pass out all TCP connections and modulate state +pass out on $ext_if proto tcp all modulate state + +# pass in certain TCP connections and keep state (SSH, SMTP, DNS, IDENT) +pass in on $ext_if proto tcp from any to any port { ssh, smtp, domain, \e + auth } flags S/SA keep state + +# pass in data mode connections for ftp-proxy running on this host. +# (see ftp-proxy(8) for details) +pass in on $ext_if proto tcp from any to 157.161.48.183 port >= 49152 \e + flags S/SA keep state + +# Do not allow Windows 9x SMTP connections since they are typically +# a viral worm. Alternately we could limit these OSes to 1 connection each. +block in on $ext_if proto tcp from any os {"Windows 95", "Windows 98"} \e + to any port smtp + +# Packet Tagging + +# three interfaces: $int_if, $ext_if, and $wifi_if (wireless). NAT is +# being done on $ext_if for all outgoing packets. tag packets in on +# $int_if and pass those tagged packets out on $ext_if. all other +# outgoing packets (i.e., packets from the wireless network) are only +# permitted to access port 80. + +pass in on $int_if from any to any tag INTNET keep state +pass in on $wifi_if from any to any keep state + +block out on $ext_if from any to any +pass out quick on $ext_if tagged INTNET keep state +pass out on $ext_if from any to any port 80 keep state + +# tag incoming packets as they are redirected to spamd(8). use the tag +# to pass those packets through the packet filter. + +rdr on $ext_if inet proto tcp from to port smtp \e + tag SPAMD -> 127.0.0.1 port spamd + +block in on $ext_if +pass in on $ext_if inet proto tcp tagged SPAMD keep state +.Ed +.Sh GRAMMAR +Syntax for +.Nm +in BNF: +.Bd -literal +line = ( option | pf-rule | nat-rule | binat-rule | rdr-rule | + antispoof-rule | altq-rule | queue-rule | anchor-rule | + trans-anchors | load-anchors | table-rule ) + +option = "set" ( [ "timeout" ( timeout | "{" timeout-list "}" ) ] | + [ "optimization" [ "default" | "normal" | + "high-latency" | "satellite" | + "aggressive" | "conservative" ] ] + [ "limit" ( limit-item | "{" limit-list "}" ) ] | + [ "loginterface" ( interface-name | "none" ) ] | + [ "block-policy" ( "drop" | "return" ) ] | + [ "state-policy" ( "if-bound" | "group-bound" | + "floating" ) ] + [ "require-order" ( "yes" | "no" ) ] + [ "fingerprints" filename ] | + [ "debug" ( "none" | "urgent" | "misc" | "loud" ) ] ) + +pf-rule = action [ ( "in" | "out" ) ] + [ "log" | "log-all" ] [ "quick" ] + [ "on" ifspec ] [ route ] [ af ] [ protospec ] + hosts [ filteropt-list ] + +filteropt-list = filteropt-list filteropt | filteropt +filteropt = user | group | flags | icmp-type | icmp6-type | tos | + ( "keep" | "modulate" | "synproxy" ) "state" + [ "(" state-opts ")" ] | + "fragment" | "no-df" | "min-ttl" number | + "max-mss" number | "random-id" | "reassemble tcp" | + fragmentation | "allow-opts" | + "label" string | "tag" string | [ ! ] "tagged" string + "queue" ( string | "(" string [ [ "," ] string ] ")" ) + +nat-rule = [ "no" ] "nat" [ "pass" ] [ "on" ifspec ] [ af ] + [ protospec ] hosts [ "tag" string ] + [ "->" ( redirhost | "{" redirhost-list "}" ) + [ portspec ] [ pooltype ] [ "static-port" ] ] + +binat-rule = [ "no" ] "binat" [ "pass" ] [ "on" interface-name ] + [ af ] [ "proto" ( proto-name | proto-number ) ] + "from" address [ "/" mask-bits ] "to" ipspec + [ "tag" string ] + [ "->" address [ "/" mask-bits ] ] + +rdr-rule = [ "no" ] "rdr" [ "pass" ] [ "on" ifspec ] [ af ] + [ protospec ] hosts [ "tag" string ] + [ "->" ( redirhost | "{" redirhost-list "}" ) + [ portspec ] [ pooltype ] ] + +antispoof-rule = "antispoof" [ "log" ] [ "quick" ] + "for" ( interface-name | "{" interface-list "}" ) + [ af ] [ "label" string ] + +table-rule = "table" "<" string ">" [ tableopts-list ] +tableopts-list = tableopts-list tableopts | tableopts +tableopts = "persist" | "const" | "file" string | + "{" [ tableaddr-list ] "}" +tableaddr-list = tableaddr-list [ "," ] tableaddr-spec | tableaddr-spec +tableaddr-spec = [ "!" ] tableaddr [ "/" mask-bits ] +tableaddr = hostname | ipv4-dotted-quad | ipv6-coloned-hex | + interface-name | "self" + +altq-rule = "altq on" interface-name queueopts-list + "queue" subqueue +queue-rule = "queue" string [ "on" interface-name ] queueopts-list + subqueue + +anchor-rule = "anchor" string [ ( "in" | "out" ) ] [ "on" ifspec ] + [ af ] [ "proto" ] [ protospec ] [ hosts ] + +trans-anchors = ( "nat-anchor" | "rdr-anchor" | "binat-anchor" ) string + [ "on" ifspec ] [ af ] [ "proto" ] [ protospec ] [ hosts ] + +load-anchor = "load anchor" anchorname:rulesetname "from" filename + +queueopts-list = queueopts-list queueopts | queueopts +queueopts = [ "bandwidth" bandwidth-spec ] | + [ "qlimit" number ] | [ "tbrsize" number ] | + [ "priority" number ] | [ schedulers ] +schedulers = ( cbq-def | priq-def | hfsc-def ) +bandwidth-spec = "number" ( "b" | "Kb" | "Mb" | "Gb" | "%" ) + +action = "pass" | "block" [ return ] | "scrub" +return = "drop" | "return" | "return-rst" [ "( ttl" number ")" ] | + "return-icmp" [ "(" icmpcode ["," icmp6code ] ")" ] | + "return-icmp6" [ "(" icmp6code ")" ] +icmpcode = ( icmp-code-name | icmp-code-number ) +icmp6code = ( icmp6-code-name | icmp6-code-number ) + +ifspec = ( [ "!" ] interface-name ) | "{" interface-list "}" +interface-list = [ "!" ] interface-name [ [ "," ] interface-list ] +route = "fastroute" | + ( "route-to" | "reply-to" | "dup-to" ) + ( routehost | "{" routehost-list "}" ) + [ pooltype ] +af = "inet" | "inet6" + +protospec = "proto" ( proto-name | proto-number | + "{" proto-list "}" ) +proto-list = ( proto-name | proto-number ) [ [ "," ] proto-list ] + +hosts = "all" | + "from" ( "any" | "no-route" | "self" | host | + "{" host-list "}" ) [ port ] [ os ] + "to" ( "any" | "no-route" | "self" | host | + "{" host-list "}" ) [ port ] + +ipspec = "any" | host | "{" host-list "}" +host = [ "!" ] ( address [ "/" mask-bits ] | "<" string ">" ) +redirhost = address [ "/" mask-bits ] +routehost = ( interface-name [ address [ "/" mask-bits ] ] ) +address = ( interface-name | "(" interface-name ")" | hostname | + ipv4-dotted-quad | ipv6-coloned-hex ) +host-list = host [ [ "," ] host-list ] +redirhost-list = redirhost [ [ "," ] redirhost-list ] +routehost-list = routehost [ [ "," ] routehost-list ] + +port = "port" ( unary-op | binary-op | "{" op-list "}" ) +portspec = "port" ( number | name ) [ ":" ( "*" | number | name ) ] +os = "os" ( os-name | "{" os-list "}" ) +user = "user" ( unary-op | binary-op | "{" op-list "}" ) +group = "group" ( unary-op | binary-op | "{" op-list "}" ) + +unary-op = [ "=" | "!=" | "<" | "<=" | ">" | ">=" ] + ( name | number ) +binary-op = number ( "<>" | "><" | ":" ) number +op-list = ( unary-op | binary-op ) [ [ "," ] op-list ] + +os-name = operating-system-name +os-list = os-name [ [ "," ] os-list ] + +flags = "flags" [ flag-set ] "/" flag-set +flag-set = [ "F" ] [ "S" ] [ "R" ] [ "P" ] [ "A" ] [ "U" ] [ "E" ] + [ "W" ] + +icmp-type = "icmp-type" ( icmp-type-code | "{" icmp-list "}" ) +icmp6-type = "icmp6-type" ( icmp-type-code | "{" icmp-list "}" ) +icmp-type-code = ( icmp-type-name | icmp-type-number ) + [ "code" ( icmp-code-name | icmp-code-number ) ] +icmp-list = icmp-type-code [ [ "," ] icmp-list ] + +tos = "tos" ( "lowdelay" | "throughput" | "reliability" | + [ "0x" ] number ) + +state-opts = state-opt [ [ "," ] state-opts ] +state-opt = ( "max" number | "no-sync" | timeout | + "source-track" [ ( "rule" | "global" ) ] | + "max-src-nodes" number | "max-src-states" number | + "if-bound" | "group-bound" | "floating" ) + +fragmentation = [ "fragment reassemble" | "fragment crop" | + "fragment drop-ovl" ] + +timeout-list = timeout [ [ "," ] timeout-list ] +timeout = ( "tcp.first" | "tcp.opening" | "tcp.established" | + "tcp.closing" | "tcp.finwait" | "tcp.closed" | + "udp.first" | "udp.single" | "udp.multiple" | + "icmp.first" | "icmp.error" | + "other.first" | "other.single" | "other.multiple" | + "frag" | "interval" | "src.track" | + "adaptive.start" | "adaptive.end" ) number + +limit-list = limit-item [ [ "," ] limit-list ] +limit-item = ( "states" | "frags" | "src-nodes" ) number + +pooltype = ( "bitmask" | "random" | + "source-hash" [ ( hex-key | string-key ) ] | + "round-robin" ) [ sticky-address ] + +subqueue = string | "{" queue-list "}" +queue-list = string [ [ "," ] string ] +cbq-def = "cbq" [ "(" cbq-opt [ [ "," ] cbq-opt ] ")" ] +priq-def = "priq" [ "(" priq-opt [ [ "," ] priq-opt ] ")" ] +hfsc-def = "hfsc" [ "(" hfsc-opt [ [ "," ] hfsc-opt ] ")" ] +cbq-opt = ( "default" | "borrow" | "red" | "ecn" | "rio" ) +priq-opt = ( "default" | "red" | "ecn" | "rio" ) +hfsc-opt = ( "default" | "red" | "ecn" | "rio" | + linkshare-sc | realtime-sc | upperlimit-sc ) +linkshare-sc = "linkshare" sc-spec +realtime-sc = "realtime" sc-spec +upperlimit-sc = "upperlimit" sc-spec +sc-spec = ( bandwidth-spec | + "(" bandwidth-spec number bandwidth-spec ")" ) +.Ed +.Sh FILES +.Bl -tag -width "/etc/protocols" -compact +.It Pa /etc/hosts +Host name database. +.It Pa /etc/pf.conf +Default location of the ruleset file. +.It Pa /etc/pf.os +Default location of OS fingerprints. +.It Pa /etc/protocols +Protocol name database. +.It Pa /etc/services +Service name database. +.It Pa /usr/share/pf +Example rulesets. +.El +.Sh SEE ALSO +.Xr icmp 4 , +.Xr icmp6 4 , +.Xr ip 4 , +.Xr ip6 4 , +.Xr pf 4 , +.Xr pfsync 4 , +.Xr tcp 4 , +.Xr udp 4 , +.Xr hosts 5 , +.Xr pf.os 5 , +.Xr protocols 5 , +.Xr services 5 , +.Xr ftp-proxy 8 , +.Xr pfctl 8 , +.Xr pflogd 8 +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 3.0 . diff --git a/usr.sbin/pfctl/pf.os.5 b/usr.sbin/pfctl/pf.os.5 new file mode 100644 index 0000000000..b7e1ee0e93 --- /dev/null +++ b/usr.sbin/pfctl/pf.os.5 @@ -0,0 +1,243 @@ +.\" $OpenBSD: pf.os.5,v 1.4 2003/08/28 09:41:23 jmc Exp $ +.\" $DragonFly: src/usr.sbin/pfctl/pf.os.5,v 1.1 2004/09/21 21:25:28 joerg Exp $ +.\" +.\" Copyright (c) 2003 Mike Frantzen +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.Dd August 18, 2003 +.Dt PF.OS 5 +.Os +.Sh NAME +.Nm pf.os +.Nd format of the operating system fingerprints file +.Sh DESCRIPTION +The +.Xr pf 4 +firewall and the +.Xr tcpdump 8 +program can both fingerprint the operating system of hosts that +originate an IPv4 TCP connection. +The file consists of newline-separated records, one per fingerprint, +containing nine colon +.Pq Ql \&: +separated fields. +These fields are as follows: +.Pp +.Bl -tag -width Description -offset indent -compact +.It window +The TCP window size. +.It TTL +The IP time to live. +.It df +The presence of the IPv4 don't fragment bit. +.It packet size +The size of the initial TCP packet. +.It TCP options +An ordered list of the TCP options. +.It class +The class of operating system. +.It version +The version of the operating system. +.It subtype +The subtype of patchlevel of the operating system. +.It description +The overall textual description of the operating system, version and subtype. +.El +.Pp +The +.Ar window +field corresponds to the th->th_win field in the TCP header and is the +source host's advertised TCP window size. +It may be between zero and 65,535 inclusive. +The window size may be given as a multiple of a constant by prepending +the size with a percent sign +.Sq % +and the value will be used as a modulus. +Three special values may be used for the window size: +.Pp +.Bl -tag -width xxx -offset indent -compact +.It * +An asterisk will wildcard the value so any window size will match. +.It S +Allow any window size which is a multiple of the maximum segment size (MSS). +.It T +Allow any window size which is a multiple of the maximum transmission unit +(MTU). +.El +.Pp +The +.Ar ttl +value is the initial time to live in the IP header. +The fingerprint code will account for the volatility of the packet's TTL +as it traverses a network. +.Pp +The +.Ar df +bit corresponds to the Don't Fragment bit in an IPv4 header. +It tells intermediate routers not to fragment the packet and is used for +path MTU discovery. +It may be either a zero or a one. +.Pp +The +.Ar packet size +is the literal size of the full IP packet and is a function of all of +the IP and TCP options. +.Pp +The +.Ar TCP options +field is an ordered list of the individual TCP options that appear in the +SYN packet. +Each option is described by a single character separated by a comma and +certain ones may include a value. +The options are: +.Pp +.Bl -tag -width Description -offset indent -compact +.It Mnnn +maximum segment size (MSS) option. +The value is the maximum packet size of the network link which may +include the +.Sq % +modulus or match all MSSes with the +.Sq * +value. +.It N +the NOP option (NO Operation). +.It T[0] +the timestamp option. +Certain operating systems always start with a zero timestamp in which +case a zero value is added to the option; otherwise no value is appended. +.It S +the Selective ACKnowledgement OK (SACKOK) option. +.It Wnnn +window scaling option. +The value is the size of the window scaling which may include the +.Sq % +modulus or match all window scalings with the +.Sq * +value. +.El +.Pp +No TCP options in the fingerprint may be given with a single dot +.Sq \&. . +.Pp +An example of OpenBSD's TCP options are: +.Pp +.Dl M*,N,N,S,N,W0,N,N,T +.Pp +The first option +.Ar M* +is the MSS option and will match all values. +The second and third options +.Ar N +will match two NOPs. +The fourth option +.Ar S +will match the SACKOK option. +The fifth +.Ar N +will match another NOP. +The sixth +.Ar W0 +will match a window scaling option with a zero scaling size. +The seventh and eighth +.Ar N +options will match two NOPs. +And the ninth and final option +.Ar T +will match the timestamp option with any time value. +.Pp +The TCP options in a fingerprint will only match packets with the +exact same TCP options in the same order. +.Pp +The +.Ar class +field is the class, genre or vender of the operating system. +.Pp +The +.Ar version +is the version of the operating system. +It is used to distinguish between different fingerprints of operating +systems of the same class but different versions. +.Pp +The +.Ar subtype +is the subtype or patch level of the operating system version. +It is used to distinguish between different fingerprints of operating +systems of the same class and same version but slightly different +patches or tweaking. +.Pp +The +.Ar description +is a general description of the operating system, its version, +patchlevel and any further useful details. +.Sh EXAMPLES +The fingerprint of a plain +.Ox 3.3 +host is: +.Bd -literal + 16384:64:1:64:M*,N,N,S,N,W0,N,N,T:OpenBSD:3.3::OpenBSD 3.3 +.Ed +.Pp +The fingerprint of an +.Ox 3.3 +host behind a PF scrubbing firewall with a no-df rule would be: +.Bd -literal + 16384:64:0:64:M*,N,N,S,N,W0,N,N,T:OpenBSD:3.3:!df:OpenBSD 3.3 scrub no-df +.Ed +.Pp +An absolutely braindead embedded operating system fingerprint could be: +.Bd -literal + 65535:255:0:40:.:DUMMY:1.1:p3:Dummy embedded OS v1.1p3 +.Ed +.Pp +The +.Xr tcpdump 8 +output of +.Bd -literal + # tcpdump -s128 -c1 -nv 'tcp[13] == 2' + 03:13:48.118526 10.0.0.1.3377 > 10.0.0.0.2: S [tcp sum ok] \e + 534596083:534596083(0) win 57344 (DF) [tos 0x10] \e + (ttl 64, id 11315) +.Ed +.Pp +almost translates into the following fingerprint +.Bd -literal + 57344:64:1:44:M1460: exampleOS:1.0::exampleOS 1.0 +.Ed +.Pp +.Xr tcpdump 8 +does not explicitly give the packet length. +But it can usually be derived by adding the size of the IPv4 header to +the size of the TCP header to the size of the TCP options. +The size of both headers is typically twenty each and the usual +sizes of the TCP options are: +.Pp +.Bl -tag -width timestamp -offset indent -compact +.It mss +four bytes. +.It nop +1 byte. +.It sackOK +two bytes. +.It timestamp +ten bytes. +.It wscale +three bytes. +.El +.Pp +In the above example, the packet size comes out to 44 bytes. +.Sh SEE ALSO +.Xr pf 4 , +.Xr pf.conf 5 , +.Xr pfctl 8 , +.Xr tcpdump 8 diff --git a/usr.sbin/pfctl/pf_print_state.c b/usr.sbin/pfctl/pf_print_state.c new file mode 100644 index 0000000000..45aa0f44fc --- /dev/null +++ b/usr.sbin/pfctl/pf_print_state.c @@ -0,0 +1,310 @@ +/* $OpenBSD: pf_print_state.c,v 1.39 2004/02/10 17:48:08 henning Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pf_print_state.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#define TCPSTATES +#include +#include +#include +#include + +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +void print_name(struct pf_addr *, sa_family_t); + +void +print_addr(struct pf_addr_wrap *addr, sa_family_t af, int verbose) +{ + switch (addr->type) { + case PF_ADDR_DYNIFTL: + printf("(%s", addr->v.ifname); + if (addr->iflags & PFI_AFLAG_NETWORK) + printf(":network"); + if (addr->iflags & PFI_AFLAG_BROADCAST) + printf(":broadcast"); + if (addr->iflags & PFI_AFLAG_PEER) + printf(":peer"); + if (addr->iflags & PFI_AFLAG_NOALIAS) + printf(":0"); + if (verbose) { + if (addr->p.dyncnt <= 0) + printf(":*"); + else + printf(":%d", addr->p.dyncnt); + } + printf(")"); + break; + case PF_ADDR_TABLE: + if (verbose) + if (addr->p.tblcnt == -1) + printf("<%s:*>", addr->v.tblname); + else + printf("<%s:%d>", addr->v.tblname, + addr->p.tblcnt); + else + printf("<%s>", addr->v.tblname); + return; + case PF_ADDR_ADDRMASK: + if (PF_AZERO(&addr->v.a.addr, AF_INET6) && + PF_AZERO(&addr->v.a.mask, AF_INET6)) + printf("any"); + else { + char buf[48]; + + if (inet_ntop(af, &addr->v.a.addr, buf, + sizeof(buf)) == NULL) + printf("?"); + else + printf("%s", buf); + } + break; + case PF_ADDR_NOROUTE: + printf("no-route"); + return; + default: + printf("?"); + return; + } + + /* mask if not _both_ address and mask are zero */ + if (!(PF_AZERO(&addr->v.a.addr, AF_INET6) && + PF_AZERO(&addr->v.a.mask, AF_INET6))) { + int bits = unmask(&addr->v.a.mask, af); + + if (bits != (af == AF_INET ? 32 : 128)) + printf("/%d", bits); + } +} + +void +print_name(struct pf_addr *addr, sa_family_t af) +{ + char hostname[NI_MAXHOST]; + + strlcpy(hostname, "?", sizeof(hostname)); + switch (af) { + case AF_INET: { + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr = addr->v4; + getnameinfo((struct sockaddr *)&sin, sin.sin_len, + hostname, sizeof(hostname), NULL, 0, NI_NOFQDN); + break; + } + case AF_INET6: { + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_len = sizeof(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = addr->v6; + getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len, + hostname, sizeof(hostname), NULL, 0, NI_NOFQDN); + break; + } + } + printf("%s", hostname); +} + +void +print_host(struct pf_state_host *h, sa_family_t af, int opts) +{ + u_int16_t p = ntohs(h->port); + + if (opts & PF_OPT_USEDNS) + print_name(&h->addr, af); + else { + struct pf_addr_wrap aw; + + memset(&aw, 0, sizeof(aw)); + aw.v.a.addr = h->addr; + if (af == AF_INET) + aw.v.a.mask.addr32[0] = 0xffffffff; + else { + memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); + af = AF_INET6; + } + print_addr(&aw, af, opts & PF_OPT_VERBOSE2); + } + + if (p) { + if (af == AF_INET) + printf(":%u", p); + else + printf("[%u]", p); + } +} + +void +print_seq(struct pf_state_peer *p) +{ + if (p->seqdiff) + printf("[%u + %u](+%u)", p->seqlo, p->seqhi - p->seqlo, + p->seqdiff); + else + printf("[%u + %u]", p->seqlo, p->seqhi - p->seqlo); +} + +void +print_state(struct pf_state *s, int opts) +{ + struct pf_state_peer *src, *dst; + struct protoent *p; + int min, sec; + + if (s->direction == PF_OUT) { + src = &s->src; + dst = &s->dst; + } else { + src = &s->dst; + dst = &s->src; + } + printf("%s ", s->u.ifname); + if ((p = getprotobynumber(s->proto)) != NULL) + printf("%s ", p->p_name); + else + printf("%u ", s->proto); + if (PF_ANEQ(&s->lan.addr, &s->gwy.addr, s->af) || + (s->lan.port != s->gwy.port)) { + print_host(&s->lan, s->af, opts); + if (s->direction == PF_OUT) + printf(" -> "); + else + printf(" <- "); + } + print_host(&s->gwy, s->af, opts); + if (s->direction == PF_OUT) + printf(" -> "); + else + printf(" <- "); + print_host(&s->ext, s->af, opts); + + printf(" "); + if (s->proto == IPPROTO_TCP) { + if (src->state <= TCPS_TIME_WAIT && + dst->state <= TCPS_TIME_WAIT) + printf(" %s:%s\n", tcpstates[src->state], + tcpstates[dst->state]); + else if (src->state == PF_TCPS_PROXY_SRC || + dst->state == PF_TCPS_PROXY_SRC) + printf(" PROXY:SRC\n"); + else if (src->state == PF_TCPS_PROXY_DST || + dst->state == PF_TCPS_PROXY_DST) + printf(" PROXY:DST\n"); + else + printf(" \n", + src->state, dst->state); + if (opts & PF_OPT_VERBOSE) { + printf(" "); + print_seq(src); + if (src->wscale && dst->wscale) + printf(" wscale %u", + src->wscale & PF_WSCALE_MASK); + printf(" "); + print_seq(dst); + if (src->wscale && dst->wscale) + printf(" wscale %u", + dst->wscale & PF_WSCALE_MASK); + printf("\n"); + } + } else if (s->proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES && + dst->state < PFUDPS_NSTATES) { + const char *states[] = PFUDPS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else if (s->proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES && + dst->state < PFOTHERS_NSTATES) { + /* XXX ICMP doesn't really have state levels */ + const char *states[] = PFOTHERS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else { + printf(" %u:%u\n", src->state, dst->state); + } + + if (opts & PF_OPT_VERBOSE) { + sec = s->creation % 60; + s->creation /= 60; + min = s->creation % 60; + s->creation /= 60; + printf(" age %.2u:%.2u:%.2u", s->creation, min, sec); + sec = s->expire % 60; + s->expire /= 60; + min = s->expire % 60; + s->expire /= 60; + printf(", expires in %.2u:%.2u:%.2u", s->expire, min, sec); + printf(", %u:%u pkts, %u:%u bytes", + s->packets[0], s->packets[1], s->bytes[0], s->bytes[1]); + if (s->anchor.nr != (uint32_t)(-1)) + printf(", anchor %u", s->anchor.nr); + if (s->rule.nr != (uint32_t)(-1)) + printf(", rule %u", s->rule.nr); + if (s->src_node != NULL) + printf(", source-track"); + if (s->nat_src_node != NULL) + printf(", sticky-address"); + printf("\n"); + } + if (opts & PF_OPT_VERBOSE2) { + printf(" id: %016llx creatorid: %08x\n", + be64toh(s->id), ntohl(s->creatorid)); + } +} + +int +unmask(struct pf_addr *m, sa_family_t af __unused) +{ + int i = 31, j = 0, b = 0; + u_int32_t tmp; + + while (j < 4 && m->addr32[j] == 0xffffffff) { + b += 32; + j++; + } + if (j < 4) { + tmp = ntohl(m->addr32[j]); + for (i = 31; tmp & (1 << i); --i) + b++; + } + return (b); +} diff --git a/usr.sbin/pfctl/pfctl.8 b/usr.sbin/pfctl/pfctl.8 new file mode 100644 index 0000000000..ad607ad615 --- /dev/null +++ b/usr.sbin/pfctl/pfctl.8 @@ -0,0 +1,527 @@ +.\" $OpenBSD: pfctl.8,v 1.110 2004/03/20 09:31:42 david Exp $ +.\" $DragonFly: src/usr.sbin/pfctl/pfctl.8,v 1.1 2004/09/21 21:25:28 joerg Exp $ +.\" +.\" Copyright (c) 2001 Kjell Wooding. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd November 20, 2002 +.Dt PFCTL 8 +.Os +.Sh NAME +.Nm pfctl +.Nd "control the packet filter (PF) and network address translation (NAT) device" +.Sh SYNOPSIS +.Nm pfctl +.Bk -words +.Op Fl AdeghNnOqRrvz +.Op Fl a Ar anchor Ns Op Ar :ruleset +.Op Fl D Ar macro=value +.Op Fl F Ar modifier +.Op Fl f Ar file +.Op Fl i Ar interface +.Op Fl k Ar host +.Op Fl p Ar device +.Op Fl s Ar modifier +.Op Fl T Ar command Op Ar address ... +.Op Fl t Ar table +.Op Fl x Ar level +.Ek +.Sh DESCRIPTION +The +.Nm +utility communicates with the packet filter device using the +ioctl interface described in +.Xr pf 4 . +It allows ruleset and parameter configuration and retrieval of status +information from the packet filter. +.Pp +Packet filtering restricts the types of packets that pass through +network interfaces entering or leaving the host based on filter +rules as described in +.Xr pf.conf 5 . +The packet filter can also replace addresses and ports of packets. +Replacing source addresses and ports of outgoing packets is called +NAT (Network Address Translation) and is used to connect an internal +network (usually reserved address space) to an external one (the +Internet) by making all connections to external hosts appear to +come from the gateway. +Replacing destination addresses and ports of incoming packets +is used to redirect connections to different hosts and/or ports. +A combination of both translations, bidirectional NAT, is also +supported. +Translation rules are described in +.Xr pf.conf 5 . +.Pp +When the variable pf is set to YES in +.Xr rc.conf 8 , +the rule file specified with the variable pf_rules +is loaded automatically by the +.Xr rc 8 +scripts and the packet filter is enabled. +.Pp +The packet filter does not itself forward packets between interfaces. +Forwarding can be enabled by setting the +.Xr sysctl 8 +variables +.Em net.inet.ip.forwarding +and/or +.Em net.inet6.ip6.forwarding , +to 1. +Set them permanently in +.Xr sysctl.conf 5 . +.Pp +The +.Nm +utility provides several commands. +The options are as follows: +.Bl -tag -width Ds +.It Fl A +Load only the queue rules present in the rule file. +Other rules and options are ignored. +.It Fl a Ar anchor Ns Op Ar :ruleset +Apply flags +.Fl f , +.Fl F +and +.Fl s +only to the rules in the specified +.Ar anchor +and optional named ruleset +.Ar ruleset . +In addition to the main ruleset, +.Nm +can load and manipulate additional rulesets by name. +Named rulesets are attached at +.Ar anchor +points, which are also referenced by name. +Evaluation of +.Ar anchor +rules from the main ruleset is described in +.Xr pf.conf 5 . +For example, to show all filter rules inside anchor +.Li foo : +.Bd -literal -offset indent +# pfctl -a foo -s rules +.Ed +.Pp +Private tables can also be put inside subrulesets, either by having table +statements in the +.Xr pf.conf 5 +file that is loaded in the anchor, or by using regular table commands as in: +.Bd -literal -offset indent +# pfctl -a foo:bar -t mytable -T add 1.2.3.4 5.6.7.8 +.Ed +.Pp +When a rule referring to a table is loaded in an anchor, the rule will use the +private table if one is defined, and then fallback to the table defined in the +main ruleset, if there is one. +This is similar to C rules for variables. +It is possible to create distinct tables with the same name in the global +ruleset and in an anchor, but this is often bad design and a warning will be +issued in that case. +.It Fl D Ar macro=value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the ruleset. +.It Fl d +Disable the packet filter. +.It Fl e +Enable the packet filter. +.It Fl F Ar modifier +Flush the filter parameters specified by +.Ar modifier +(may be abbreviated): +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl F Ar nat +Flush the NAT rules. +.It Fl F Ar queue +Flush the queue rules. +.It Fl F Ar rules +Flush the filter rules. +.It Fl F Ar state +Flush the state table (NAT and filter). +.It Fl F Ar Sources +Flush the source tracking table. +.It Fl F Ar info +Flush the filter information (statistics that are not bound to rules). +.It Fl F Ar Tables +Flush the tables. +.It Fl F Ar osfp +Flush the passive operating system fingerprints. +.It Fl F Ar all +Flush all of the above. +.El +.It Fl f Ar file +Load the rules contained in +.Ar file . +This +.Ar file +may contain macros, tables, options, and normalization, queueing, +translation, and filtering rules. +With the exception of macros and tables, the statements must appear in that +order. +.It Fl g +Include output helpful for debugging. +.It Fl h +Help. +.It Fl i Ar interface +Restrict the operation to the given +.Ar interface . +.It Fl k Ar host +Kill all of the state entries originating from the specified +.Ar host . +A second +.Fl k Ar host +option may be specified, which will kill all the state entries +from the first +.Ar host +to the second +.Ar host . +For example, to kill all of the state entries originating from +.Li host : +.Bd -literal -offset indent +# pfctl -k host +.Ed +.Pp +To kill all of the state entries from +.Li host1 +to +.Li host2 : +.Bd -literal -offset indent +# pfctl -k host1 -k host2 +.Ed +.It Fl N +Load only the NAT rules present in the rule file. +Other rules and options are ignored. +.It Fl n +Do not actually load rules, just parse them. +.It Fl O +Load only the options present in the rule file. +Other rules and options are ignored. +.It Fl p Ar device +Use the device file +.Ar device +instead of the default +.Pa /dev/pf . +.It Fl q +Only print errors and warnings. +.It Fl R +Load only the filter rules present in the rule file. +Other rules and options are ignored. +.It Fl r +Perform reverse DNS lookups on states when displaying them. +.It Fl s Ar modifier +Show the filter parameters specified by +.Ar modifier +(may be abbreviated): +.Pp +.Bl -tag -width xxxxxxxxxxxxx -compact +.It Fl s Ar nat +Show the currently loaded NAT rules. +.It Fl s Ar queue +Show the currently loaded queue rules. +When used together with +.Fl v , +per-queue statistics are also shown. +When used together with +.Fl v v , +.Nm +will loop and show updated queue statistics every five seconds, including +measured bandwidth and packets per second. +.It Fl s Ar rules +Show the currently loaded filter rules. +When used together with +.Fl v , +the per-rule statistics (number of evaluations, +packets and bytes) are also shown. +Note that the 'skip step' optimization done automatically by the kernel +will skip evaluation of rules where possible. +Packets passed statefully are counted in the rule that created the state +(even though the rule isn't evaluated more than once for the entire +connection). +.It Fl s Ar Anchors +Show the currently loaded anchors. +If +.Fl a Ar anchor +is specified as well, the named rulesets currently loaded in the specified +anchor are shown instead. +.It Fl s Ar state +Show the contents of the state table. +.It Fl s Ar Sources +Show the contents of the source tracking table. +.It Fl s Ar info +Show filter information (statistics and counters). +When used together with +.Fl v , +source tracking statistics are also shown. +.It Fl s Ar labels +Show per-rule statistics (label, evaluations, packets, bytes) of +filter rules with labels, useful for accounting. +.It Fl s Ar timeouts +Show the current global timeouts. +.It Fl s Ar memory +Show the current pool memory hard limits. +.It Fl s Ar Tables +Show the list of tables. +.It Fl s Ar osfp +Show the list of operating system fingerprints. +.It Fl s Ar Interfaces +Show the list of interfaces and interface drivers available to PF. +When used together with a double +.Fl v , +interface statistics are also shown. +.Fl i +can be used to select an interface or a group of interfaces. +.It Fl s Ar all +Show all of the above, except for the lists of interfaces and operating +system fingerprints. +.El +.It Fl T Ar command Op Ar address ... +Specify the +.Ar command +(may be abbreviated) to apply to the table. +Commands include: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl T Ar kill +Kill a table. +.It Fl T Ar flush +Flush all addresses of a table. +.It Fl T Ar add +Add one or more addresses in a table. +Automatically create a nonexisting table. +.It Fl T Ar delete +Delete one or more addresses from a table. +.It Fl T Ar replace +Replace the addresses of the table. +Automatically create a nonexisting table. +.It Fl T Ar show +Show the content (addresses) of a table. +.It Fl T Ar test +Test if the given addresses match a table. +.It Fl T Ar zero +Clear all the statistics of a table. +.It Fl T Ar load +Load only the table definitions from +.Xr pf.conf 5 . +This is used in conjunction with the +.Fl f +flag, as in: +.Bd -literal -offset indent +# pfctl -Tl -f pf.conf +.Ed +.El +.Pp +For the +.Ar add , +.Ar delete , +.Ar replace +and +.Ar test +commands, the list of addresses can be specified either directly on the command +line and/or in an unformatted text file, using the +.Fl f +flag. +Comments starting with a "#" are allowed in the text file. +With these commands, the +.Fl v +flag can also be used once or twice, in which case +.Nm +will print the +detailed result of the operation for each individual address, prefixed by +one of the following letters: +.Pp +.Bl -tag -width XXX -compact +.It A +The address/network has been added. +.It C +The address/network has been changed (negated). +.It D +The address/network has been deleted. +.It M +The address matches (test operation only). +.It X +The address/network is duplicated and therefore ignored. +.It Y +The address/network cannot be added/deleted due to conflicting "!" attribute. +.It Z +The address/network has been cleared (statistics). +.El +.Pp +Each table maintains a set of counters that can be retrieved using the +.Fl v +flag of +.Nm . +For example, the following commands define a wide open firewall which will keep +track of packets going to or coming from the +.Ox +ftp server. +The following commands configure the firewall and send 10 pings to the ftp +server: +.Bd -literal -offset indent +# printf "table { ftp.openbsd.org }\en \e + pass out to keep state\en" | pfctl -f- +# ping -qc10 ftp.openbsd.org +.Ed +.Pp +We can now use the table +.Ar show +command to output, for each address and packet direction, the number of packets +and bytes that are being passed or blocked by rules referencing the table. +The time at which the current accounting started is also shown with the +.Ar Cleared +line. +.Bd -literal -offset indent +# pfctl -t test -vTshow + 129.128.5.191 + Cleared: Thu Feb 13 18:55:18 2003 + In/Block: [ Packets: 0 Bytes: 0 ] + In/Pass: [ Packets: 10 Bytes: 840 ] + Out/Block: [ Packets: 0 Bytes: 0 ] + Out/Pass: [ Packets: 10 Bytes: 840 ] +.Ed +.Pp +Similarly, it is possible to view global information about the tables +by using the +.Fl v +modifier twice and the +.Ar show Tables +command. +This will display the number of addresses on each table, +the number of rules which reference the table, and the global +packet statistics for the whole table: +.Bd -literal -offset indent +# pfctl -vvsTables +--a-r- test + Addresses: 1 + Cleared: Thu Feb 13 18:55:18 2003 + References: [ Anchors: 0 Rules: 1 ] + Evaluations: [ NoMatch: 3496 Match: 1 ] + In/Block: [ Packets: 0 Bytes: 0 ] + In/Pass: [ Packets: 10 Bytes: 840 ] + In/XPass: [ Packets: 0 Bytes: 0 ] + Out/Block: [ Packets: 0 Bytes: 0 ] + Out/Pass: [ Packets: 10 Bytes: 840 ] + Out/XPass: [ Packets: 0 Bytes: 0 ] +.Ed +.Pp +As we can see here, only one packet \- the initial ping request \- matched the +table; but all packets passing as the result of the state are correctly +accounted for. +Reloading the table(s) or ruleset will not affect packet accounting in any way. +The two +.Ar XPass +counters are incremented instead of the +.Ar Pass +counters when a "stateful" packet is passed but doesn't match the table +anymore. +This will happen in our example if someone flushes the table while the ping +command is running. +.Pp +When used with a single +.Fl v , +.Nm +will only display the first line containing the table flags and name. +The flags are defined as follows: +.Pp +.Bl -tag -width XXX -compact +.It c +For constant tables, which cannot be altered outside +.Xr pf.conf 5 . +.It p +For persistent tables, which don't get automatically flushed when no rules +refer to them. +.It a +For tables which are part of the +.Ar active +tableset. +Tables without this flag do not really exist, cannot contain addresses, and are +only listed if the +.Fl g +flag is given. +.It i +For tables which are part of the +.Ar inactive +tableset. +This flag can only be witnessed briefly during the loading of +.Xr pf.conf 5 . +.It r +For tables which are referenced (used) by rules. +.It h +This flag is set when a table in the main ruleset is hidden by one or more +tables of the same name in sub-rulesets (anchors). +.El +.It Fl t Ar table +Specify the name of the table. +.It Fl v +Produce more verbose output. +A second use of +.Fl v +will produce even more verbose output including ruleset warnings. +See previous section for its effect on table commands. +.It Fl x Ar level +Set the debug +.Ar level +(may be abbreviated) to one of the following: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Fl x Ar none +Don't generate debug messages. +.It Fl x Ar urgent +Generate debug messages only for serious errors. +.It Fl x Ar misc +Generate debug messages for various errors. +.It Fl x Ar loud +Generate debug messages for common conditions. +.El +.It Fl z +Clear per-rule statistics. +.El +.Sh FILES +.Bl -tag -width "/etc/pf.conf" -compact +.It Pa /etc/pf.conf +Packet filter rules file. +.El +.Sh SEE ALSO +.Xr pf 4 , +.Xr pf.conf 5 , +.Xr pf.os 5 , +.Xr sysctl.conf 5 , +.Xr ftp-proxy 8 , +.Xr rc 8 , +.Xr rc.conf 8 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +program and the +.Xr pf 4 +filter mechanism first appeared in +.Ox 3.0 . diff --git a/usr.sbin/pfctl/pfctl.c b/usr.sbin/pfctl/pfctl.c new file mode 100644 index 0000000000..96263cb11b --- /dev/null +++ b/usr.sbin/pfctl/pfctl.c @@ -0,0 +1,1809 @@ +/* $OpenBSD: pfctl.c,v 1.213 2004/03/20 09:31:42 david Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * 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: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#ifndef __DragonFly__ +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +void usage(void); +int pfctl_enable(int, int); +int pfctl_disable(int, int); +int pfctl_clear_stats(int, int); +int pfctl_clear_rules(int, int, char *, char *); +int pfctl_clear_nat(int, int, char *, char *); +int pfctl_clear_altq(int, int); +int pfctl_clear_src_nodes(int, int); +int pfctl_clear_states(int, const char *, int); +int pfctl_kill_states(int, const char *, int); +int pfctl_get_pool(int, struct pf_pool *, u_int32_t, u_int32_t, int, + char *, char *); +void pfctl_print_rule_counters(struct pf_rule *, int); +int pfctl_show_rules(int, int, int, char *, char *); +int pfctl_show_nat(int, int, char *, char *); +int pfctl_show_src_nodes(int, int); +int pfctl_show_states(int, const char *, int); +int pfctl_show_status(int, int); +int pfctl_show_timeouts(int, int); +int pfctl_show_limits(int, int); +int pfctl_debug(int, u_int32_t, int); +int pfctl_clear_rule_counters(int, int); +int pfctl_test_altqsupport(int, int); +int pfctl_show_anchors(int, int, char *); +const char *pfctl_lookup_option(char *, const char **); + +const char *clearopt; +char *rulesopt; +const char *showopt; +const char *debugopt; +char *anchoropt; +const char *pf_device = "/dev/pf"; +char *ifaceopt; +char *tableopt; +const char *tblcmdopt; +int state_killers; +char *state_kill[2]; +int loadopt; +int altqsupport; + +int dev_fd = -1; +int first_title = 1; +int labels = 0; + +const char *infile; + +static const struct { + const char *name; + int index; +} pf_limits[] = { + { "states", PF_LIMIT_STATES }, + { "src-nodes", PF_LIMIT_SRC_NODES }, + { "frags", PF_LIMIT_FRAGS }, + { NULL, 0 } +}; + +struct pf_hint { + const char *name; + int timeout; +}; +static const struct pf_hint pf_hint_normal[] = { + { "tcp.first", 2 * 60 }, + { "tcp.opening", 30 }, + { "tcp.established", 24 * 60 * 60 }, + { "tcp.closing", 15 * 60 }, + { "tcp.finwait", 45 }, + { "tcp.closed", 90 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_satellite[] = { + { "tcp.first", 3 * 60 }, + { "tcp.opening", 30 + 5 }, + { "tcp.established", 24 * 60 * 60 }, + { "tcp.closing", 15 * 60 + 5 }, + { "tcp.finwait", 45 + 5 }, + { "tcp.closed", 90 + 5 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_conservative[] = { + { "tcp.first", 60 * 60 }, + { "tcp.opening", 15 * 60 }, + { "tcp.established", 5 * 24 * 60 * 60 }, + { "tcp.closing", 60 * 60 }, + { "tcp.finwait", 10 * 60 }, + { "tcp.closed", 3 * 60 }, + { NULL, 0 } +}; +static const struct pf_hint pf_hint_aggressive[] = { + { "tcp.first", 30 }, + { "tcp.opening", 5 }, + { "tcp.established", 5 * 60 * 60 }, + { "tcp.closing", 60 }, + { "tcp.finwait", 30 }, + { "tcp.closed", 30 }, + { NULL, 0 } +}; + +static const struct { + const char *name; + const struct pf_hint *hint; +} pf_hints[] = { + { "normal", pf_hint_normal }, + { "satellite", pf_hint_satellite }, + { "high-latency", pf_hint_satellite }, + { "conservative", pf_hint_conservative }, + { "aggressive", pf_hint_aggressive }, + { NULL, NULL } +}; + +static const char *clearopt_list[] = { + "nat", "queue", "rules", "Sources", + "state", "info", "Tables", "osfp", "all", NULL +}; + +static const char *showopt_list[] = { + "nat", "queue", "rules", "Anchors", "Sources", "state", "info", + "Interfaces", "labels", "timeouts", "memory", "Tables", "osfp", + "all", NULL +}; + +static const char *tblcmdopt_list[] = { + "kill", "flush", "add", "delete", "load", "replace", "show", + "test", "zero", NULL +}; + +static const char *debugopt_list[] = { + "none", "urgent", "misc", "loud", NULL +}; + + +void +usage(void) +{ + fprintf(stderr, "usage: %s [-AdeghNnOqRrvz] ", getprogname()); + fprintf(stderr, "[-a anchor[:ruleset]] [-D macro=value]\n"); + fprintf(stderr, " "); + fprintf(stderr, "[-F modifier] [-f file] [-i interface] "); + fprintf(stderr, "[-k host] [-p device]\n"); + fprintf(stderr, " "); + fprintf(stderr, "[-s modifier] [-T command [address ...]] "); + fprintf(stderr, "[-t table] [-x level]\n"); + exit(1); +} + +int +pfctl_enable(int dev, int opts) +{ + if (ioctl(dev, DIOCSTART)) { + if (errno == EEXIST) + errx(1, "pf already enabled"); + else + err(1, "DIOCSTART"); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf enabled\n"); + + if (altqsupport && ioctl(dev, DIOCSTARTALTQ)) + if (errno != EEXIST) + err(1, "DIOCSTARTALTQ"); + + return (0); +} + +int +pfctl_disable(int dev, int opts) +{ + if (ioctl(dev, DIOCSTOP)) { + if (errno == ENOENT) + errx(1, "pf not enabled"); + else + err(1, "DIOCSTOP"); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf disabled\n"); + + if (altqsupport && ioctl(dev, DIOCSTOPALTQ)) + if (errno != ENOENT) + err(1, "DIOCSTOPALTQ"); + + return (0); +} + +int +pfctl_clear_stats(int dev, int opts) +{ + if (ioctl(dev, DIOCCLRSTATUS)) + err(1, "DIOCCLRSTATUS"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf: statistics cleared\n"); + return (0); +} + +int +pfctl_clear_rules(int dev, int opts, char *anchorname, char *rulesetname) +{ + struct pfr_buffer t; + + if (*anchorname && !*rulesetname) { + struct pfioc_ruleset pr; + int mnr, nr, r; + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr.nr; + for (nr = mnr - 1; nr >= 0; --nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr)) + err(1, "DIOCGETRULESET"); + r = pfctl_clear_rules(dev, opts | PF_OPT_QUIET, + anchorname, pr.name); + if (r) + return (r); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "rules cleared\n"); + return (0); + } + memset(&t, 0, sizeof(t)); + t.pfrb_type = PFRB_TRANS; + if (pfctl_add_trans(&t, PF_RULESET_SCRUB, anchorname, rulesetname) || + pfctl_add_trans(&t, PF_RULESET_FILTER, anchorname, rulesetname) || + pfctl_trans(dev, &t, DIOCXBEGIN, 0) || + pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) + err(1, "pfctl_clear_rules"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "rules cleared\n"); + return (0); +} + +int +pfctl_clear_nat(int dev, int opts, char *anchorname, char *rulesetname) +{ + struct pfr_buffer t; + + if (*anchorname && !*rulesetname) { + struct pfioc_ruleset pr; + int mnr, nr, r; + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr.nr; + for (nr = mnr - 1; nr >= 0; --nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr)) + err(1, "DIOCGETRULESET"); + r = pfctl_clear_nat(dev, opts | PF_OPT_QUIET, + anchorname, pr.name); + if (r) + return (r); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "nat cleared\n"); + return (0); + } + memset(&t, 0, sizeof(t)); + t.pfrb_type = PFRB_TRANS; + if (pfctl_add_trans(&t, PF_RULESET_NAT, anchorname, rulesetname) || + pfctl_add_trans(&t, PF_RULESET_BINAT, anchorname, rulesetname) || + pfctl_add_trans(&t, PF_RULESET_RDR, anchorname, rulesetname) || + pfctl_trans(dev, &t, DIOCXBEGIN, 0) || + pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) + err(1, "pfctl_clear_nat"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "nat cleared\n"); + return (0); +} + +#ifndef __DragonFly__ +int +pfctl_clear_altq(int dev, int opts) +{ + struct pfr_buffer t; + + if (!altqsupport) + return (-1); + memset(&t, 0, sizeof(t)); + t.pfrb_type = PFRB_TRANS; + if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "", "") || + pfctl_trans(dev, &t, DIOCXBEGIN, 0) || + pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) + err(1, "pfctl_clear_altq"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "altq cleared\n"); + return (0); +} +#endif + +int +pfctl_clear_src_nodes(int dev, int opts) +{ + if (ioctl(dev, DIOCCLRSRCNODES)) + err(1, "DIOCCLRSRCNODES"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "source tracking entries cleared\n"); + return (0); +} + +int +pfctl_clear_states(int dev, const char *iface, int opts) +{ + struct pfioc_state_kill psk; + + memset(&psk, 0, sizeof(psk)); + if (iface != NULL && strlcpy(psk.psk_ifname, iface, + sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) + errx(1, "invalid interface: %s", iface); + + if (ioctl(dev, DIOCCLRSTATES, &psk)) + err(1, "DIOCCLRSTATES"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "%d states cleared\n", psk.psk_af); + return (0); +} + +int +pfctl_kill_states(int dev, const char *iface, int opts) +{ + struct pfioc_state_kill psk; + struct addrinfo *res[2], *resp[2]; + struct sockaddr last_src, last_dst; + int killed, sources, dests; + int ret_ga; + + killed = sources = dests = 0; + + memset(&psk, 0, sizeof(psk)); + memset(&psk.psk_src.addr.v.a.mask, 0xff, + sizeof(psk.psk_src.addr.v.a.mask)); + memset(&last_src, 0xff, sizeof(last_src)); + memset(&last_dst, 0xff, sizeof(last_dst)); + if (iface != NULL && strlcpy(psk.psk_ifname, iface, + sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) + errx(1, "invalid interface: %s", iface); + + if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { + errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); + /* NOTREACHED */ + } + for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { + if (resp[0]->ai_addr == NULL) + continue; + /* We get lots of duplicates. Catch the easy ones */ + if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) + continue; + last_src = *(struct sockaddr *)resp[0]->ai_addr; + + psk.psk_af = resp[0]->ai_family; + sources++; + + if (psk.psk_af == AF_INET) + psk.psk_src.addr.v.a.addr.v4 = + ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; + else if (psk.psk_af == AF_INET6) + psk.psk_src.addr.v.a.addr.v6 = + ((struct sockaddr_in6 *)resp[0]->ai_addr)-> + sin6_addr; + else + errx(1, "Unknown address family %d", psk.psk_af); + + if (state_killers > 1) { + dests = 0; + memset(&psk.psk_dst.addr.v.a.mask, 0xff, + sizeof(psk.psk_dst.addr.v.a.mask)); + memset(&last_dst, 0xff, sizeof(last_dst)); + if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, + &res[1]))) { + errx(1, "getaddrinfo: %s", + gai_strerror(ret_ga)); + /* NOTREACHED */ + } + for (resp[1] = res[1]; resp[1]; + resp[1] = resp[1]->ai_next) { + if (resp[1]->ai_addr == NULL) + continue; + if (psk.psk_af != resp[1]->ai_family) + continue; + + if (memcmp(&last_dst, resp[1]->ai_addr, + sizeof(last_dst)) == 0) + continue; + last_dst = *(struct sockaddr *)resp[1]->ai_addr; + + dests++; + + if (psk.psk_af == AF_INET) + psk.psk_dst.addr.v.a.addr.v4 = + ((struct sockaddr_in *)resp[1]-> + ai_addr)->sin_addr; + else if (psk.psk_af == AF_INET6) + psk.psk_dst.addr.v.a.addr.v6 = + ((struct sockaddr_in6 *)resp[1]-> + ai_addr)->sin6_addr; + else + errx(1, "Unknown address family %d", + psk.psk_af); + + if (ioctl(dev, DIOCKILLSTATES, &psk)) + err(1, "DIOCKILLSTATES"); + killed += psk.psk_af; + /* fixup psk.psk_af */ + psk.psk_af = resp[1]->ai_family; + } + freeaddrinfo(res[1]); + } else { + if (ioctl(dev, DIOCKILLSTATES, &psk)) + err(1, "DIOCKILLSTATES"); + killed += psk.psk_af; + /* fixup psk.psk_af */ + psk.psk_af = res[0]->ai_family; + } + } + + freeaddrinfo(res[0]); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states from %d sources and %d " + "destinations\n", killed, sources, dests); + return (0); +} + +int +pfctl_get_pool(int dev, struct pf_pool *pool, u_int32_t nr, + u_int32_t ticket, int r_action, char *anchorname, char *rulesetname) +{ + struct pfioc_pooladdr pp; + struct pf_pooladdr *pa; + u_int32_t pnr, mpnr; + + memset(&pp, 0, sizeof(pp)); + memcpy(pp.anchor, anchorname, sizeof(pp.anchor)); + memcpy(pp.ruleset, rulesetname, sizeof(pp.ruleset)); + pp.r_action = r_action; + pp.r_num = nr; + pp.ticket = ticket; + if (ioctl(dev, DIOCGETADDRS, &pp)) { + warn("DIOCGETADDRS"); + return (-1); + } + mpnr = pp.nr; + TAILQ_INIT(&pool->list); + for (pnr = 0; pnr < mpnr; ++pnr) { + pp.nr = pnr; + if (ioctl(dev, DIOCGETADDR, &pp)) { + warn("DIOCGETADDR"); + return (-1); + } + pa = calloc(1, sizeof(struct pf_pooladdr)); + if (pa == NULL) + err(1, "calloc"); + bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr)); + TAILQ_INSERT_TAIL(&pool->list, pa, entries); + } + + return (0); +} + +void +pfctl_clear_pool(struct pf_pool *pool) +{ + struct pf_pooladdr *pa; + + while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { + TAILQ_REMOVE(&pool->list, pa, entries); + free(pa); + } +} + +void +pfctl_print_rule_counters(struct pf_rule *rule, int opts) +{ + if (opts & PF_OPT_DEBUG) { + const char *t[PF_SKIP_COUNT] = { "i", "d", "f", + "p", "sa", "sp", "da", "dp" }; + int i; + + printf(" [ Skip steps: "); + for (i = 0; i < PF_SKIP_COUNT; ++i) { + if (rule->skip[i].nr == rule->nr + 1) + continue; + printf("%s=", t[i]); + if (rule->skip[i].nr == (uint32_t)(-1)) + printf("end "); + else + printf("%u ", rule->skip[i].nr); + } + printf("]\n"); + + printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", + rule->qname, rule->qid, rule->pqname, rule->pqid); + } + if (opts & PF_OPT_VERBOSE) + printf(" [ Evaluations: %-8llu Packets: %-8llu " + "Bytes: %-10llu States: %-6u]\n", + (unsigned long long)rule->evaluations, + (unsigned long long)rule->packets, + (unsigned long long)rule->bytes, rule->states); +} + +void +pfctl_print_title(const char *title) +{ + if (!first_title) + printf("\n"); + first_title = 0; + printf("%s\n", title); +} + +int +pfctl_show_rules(int dev, int opts, int format, char *anchorname, + char *rulesetname) +{ + struct pfioc_rule pr; + u_int32_t nr, mnr, header = 0; + int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); + + if (*anchorname && !*rulesetname) { + struct pfioc_ruleset pr2; + int r; + + memset(&pr2, 0, sizeof(pr2)); + memcpy(pr2.anchor, anchorname, sizeof(pr2.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr2)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + if (opts & PF_OPT_SHOWALL && pr2.nr) + pfctl_print_title("FILTER RULES:"); + mnr = pr2.nr; + for (nr = 0; nr < mnr; ++nr) { + pr2.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr2)) + err(1, "DIOCGETRULESET"); + r = pfctl_show_rules(dev, opts, format, anchorname, + pr2.name); + if (r) + return (r); + } + return (0); + } + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); + if (opts & PF_OPT_SHOWALL) { + pr.rule.action = PF_PASS; + if (ioctl(dev, DIOCGETRULES, &pr)) { + warn("DIOCGETRULES"); + return (-1); + } + header++; + } + pr.rule.action = PF_SCRUB; + if (ioctl(dev, DIOCGETRULES, &pr)) { + warn("DIOCGETRULES"); + return (-1); + } + if (opts & PF_OPT_SHOWALL) { + if (format == 0 && (pr.nr > 0 || header)) + pfctl_print_title("FILTER RULES:"); + else if (format == 1 && labels) + pfctl_print_title("LABEL COUNTERS:"); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULE, &pr)) { + warn("DIOCGETRULE"); + return (-1); + } + + if (pfctl_get_pool(dev, &pr.rule.rpool, + nr, pr.ticket, PF_SCRUB, anchorname, rulesetname) != 0) + return (-1); + + switch (format) { + case 1: + if (pr.rule.label[0]) { + printf("%s ", pr.rule.label); + printf("%llu %llu %llu\n", + (unsigned long long)pr.rule.evaluations, + (unsigned long long)pr.rule.packets, + (unsigned long long)pr.rule.bytes); + } + break; + default: + if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL)) + labels = 1; + print_rule(&pr.rule, rule_numbers); + pfctl_print_rule_counters(&pr.rule, opts); + } + pfctl_clear_pool(&pr.rule.rpool); + } + pr.rule.action = PF_PASS; + if (ioctl(dev, DIOCGETRULES, &pr)) { + warn("DIOCGETRULES"); + return (-1); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULE, &pr)) { + warn("DIOCGETRULE"); + return (-1); + } + + if (pfctl_get_pool(dev, &pr.rule.rpool, + nr, pr.ticket, PF_PASS, anchorname, rulesetname) != 0) + return (-1); + + switch (format) { + case 1: + if (pr.rule.label[0]) { + printf("%s ", pr.rule.label); + printf("%llu %llu %llu\n", + (unsigned long long)pr.rule.evaluations, + (unsigned long long)pr.rule.packets, + (unsigned long long)pr.rule.bytes); + } + break; + default: + if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL)) + labels = 1; + print_rule(&pr.rule, rule_numbers); + pfctl_print_rule_counters(&pr.rule, opts); + } + pfctl_clear_pool(&pr.rule.rpool); + } + return (0); +} + +int +pfctl_show_nat(int dev, int opts, char *anchorname, char *rulesetname) +{ + struct pfioc_rule pr; + u_int32_t mnr, nr; + static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; + int i, dotitle = opts & PF_OPT_SHOWALL; + + if (*anchorname && !*rulesetname) { + struct pfioc_ruleset pr2; + int r; + + memset(&pr2, 0, sizeof(pr2)); + memcpy(pr2.anchor, anchorname, sizeof(pr2.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr2)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr2.nr; + for (nr = 0; nr < mnr; ++nr) { + pr2.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr2)) + err(1, "DIOCGETRULESET"); + r = pfctl_show_nat(dev, opts, anchorname, pr2.name); + if (r) + return (r); + } + return (0); + } + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + memcpy(pr.ruleset, rulesetname, sizeof(pr.ruleset)); + for (i = 0; i < 3; i++) { + pr.rule.action = nattype[i]; + if (ioctl(dev, DIOCGETRULES, &pr)) { + warn("DIOCGETRULES"); + return (-1); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULE, &pr)) { + warn("DIOCGETRULE"); + return (-1); + } + if (pfctl_get_pool(dev, &pr.rule.rpool, nr, + pr.ticket, nattype[i], anchorname, + rulesetname) != 0) + return (-1); + if (dotitle) { + pfctl_print_title("TRANSLATION RULES:"); + dotitle = 0; + } + print_rule(&pr.rule, opts & PF_OPT_VERBOSE2); + pfctl_print_rule_counters(&pr.rule, opts); + pfctl_clear_pool(&pr.rule.rpool); + } + } + return (0); +} + +int +pfctl_show_src_nodes(int dev, int opts) +{ + struct pfioc_src_nodes psn; + struct pf_src_node *p; + char *inbuf = NULL, *newinbuf = NULL; + unsigned len = 0; + int i; + + memset(&psn, 0, sizeof(psn)); + for (;;) { + psn.psn_len = len; + if (len) { + newinbuf = realloc(inbuf, len); + if (newinbuf == NULL) + err(1, "realloc"); + psn.psn_buf = inbuf = newinbuf; + } + if (ioctl(dev, DIOCGETSRCNODES, &psn) < 0) { + warn("DIOCGETSRCNODES"); + return (-1); + } + if (psn.psn_len + sizeof(struct pfioc_src_nodes) < len) + break; + if (len == 0 && psn.psn_len == 0) + return (0); + if (len == 0 && psn.psn_len != 0) + len = psn.psn_len; + if (psn.psn_len == 0) + return (0); /* no src_nodes */ + len *= 2; + } + p = psn.psn_src_nodes; + if (psn.psn_len > 0 && (opts & PF_OPT_SHOWALL)) + pfctl_print_title("SOURCE TRACKING NODES:"); + for (i = 0; i < psn.psn_len; i += sizeof(*p)) { + print_src_node(p, opts); + p++; + } + return (0); +} + +int +pfctl_show_states(int dev, const char *iface, int opts) +{ + struct pfioc_states ps; + struct pf_state *p; + char *inbuf = NULL, *newinbuf = NULL; + unsigned len = 0; + int i, dotitle = (opts & PF_OPT_SHOWALL); + + memset(&ps, 0, sizeof(ps)); + for (;;) { + ps.ps_len = len; + if (len) { + newinbuf = realloc(inbuf, len); + if (newinbuf == NULL) + err(1, "realloc"); + ps.ps_buf = inbuf = newinbuf; + } + if (ioctl(dev, DIOCGETSTATES, &ps) < 0) { + warn("DIOCGETSTATES"); + return (-1); + } + if (ps.ps_len + sizeof(struct pfioc_states) < len) + break; + if (len == 0 && ps.ps_len == 0) + return (0); + if (len == 0 && ps.ps_len != 0) + len = ps.ps_len; + if (ps.ps_len == 0) + return (0); /* no states */ + len *= 2; + } + p = ps.ps_states; + for (i = 0; i < ps.ps_len; i += sizeof(*p), p++) { + if (iface != NULL && strcmp(p->u.ifname, iface)) + continue; + if (dotitle) { + pfctl_print_title("STATES:"); + dotitle = 0; + } + print_state(p, opts); + } + return (0); +} + +int +pfctl_show_status(int dev, int opts) +{ + struct pf_status status; + + if (ioctl(dev, DIOCGETSTATUS, &status)) { + warn("DIOCGETSTATUS"); + return (-1); + } + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("INFO:"); + print_status(&status, opts); + return (0); +} + +int +pfctl_show_timeouts(int dev, int opts) +{ + struct pfioc_tm pt; + int i; + + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("TIMEOUTS:"); + memset(&pt, 0, sizeof(pt)); + for (i = 0; pf_timeouts[i].name; i++) { + pt.timeout = pf_timeouts[i].timeout; + if (ioctl(dev, DIOCGETTIMEOUT, &pt)) + err(1, "DIOCGETTIMEOUT"); + printf("%-20s %10d", pf_timeouts[i].name, pt.seconds); + if (i >= PFTM_ADAPTIVE_START && i <= PFTM_ADAPTIVE_END) + printf(" states"); + else + printf("s"); + printf("\n"); + } + return (0); + +} + +int +pfctl_show_limits(int dev, int opts) +{ + struct pfioc_limit pl; + int i; + + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("LIMITS:"); + memset(&pl, 0, sizeof(pl)); + for (i = 0; pf_limits[i].name; i++) { + pl.index = pf_limits[i].index; + if (ioctl(dev, DIOCGETLIMIT, &pl)) + err(1, "DIOCGETLIMIT"); + printf("%-10s ", pf_limits[i].name); + if (pl.limit == UINT_MAX) + printf("unlimited\n"); + else + printf("hard limit %6u\n", pl.limit); + } + return (0); +} + +/* callbacks for rule/nat/rdr/addr */ +int +pfctl_add_pool(struct pfctl *pf, struct pf_pool *p, sa_family_t af) +{ + struct pf_pooladdr *pa; + + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr)) + err(1, "DIOCBEGINADDRS"); + } + + pf->paddr.af = af; + TAILQ_FOREACH(pa, &p->list, entries) { + memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr)) + err(1, "DIOCADDADDR"); + } + } + return (0); +} + +int +pfctl_add_rule(struct pfctl *pf, struct pf_rule *r) +{ + u_int8_t rs_num; + struct pfioc_rule pr; + + switch (r->action) { + case PF_SCRUB: + if ((loadopt & PFCTL_FLAG_FILTER) == 0) + return (0); + rs_num = PF_RULESET_SCRUB; + break; + case PF_DROP: + case PF_PASS: + if ((loadopt & PFCTL_FLAG_FILTER) == 0) + return (0); + rs_num = PF_RULESET_FILTER; + break; + case PF_NAT: + case PF_NONAT: + if ((loadopt & PFCTL_FLAG_NAT) == 0) + return (0); + rs_num = PF_RULESET_NAT; + break; + case PF_RDR: + case PF_NORDR: + if ((loadopt & PFCTL_FLAG_NAT) == 0) + return (0); + rs_num = PF_RULESET_RDR; + break; + case PF_BINAT: + case PF_NOBINAT: + if ((loadopt & PFCTL_FLAG_NAT) == 0) + return (0); + rs_num = PF_RULESET_BINAT; + break; + default: + errx(1, "Invalid rule type"); + break; + } + + if ((pf->opts & PF_OPT_NOACTION) == 0) { + bzero(&pr, sizeof(pr)); + if (strlcpy(pr.anchor, pf->anchor, sizeof(pr.anchor)) >= + sizeof(pr.anchor) || + strlcpy(pr.ruleset, pf->ruleset, sizeof(pr.ruleset)) >= + sizeof(pr.ruleset)) + errx(1, "pfctl_add_rule: strlcpy"); + 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)) + err(1, "DIOCADDRULE"); + } + if (pf->opts & PF_OPT_VERBOSE) + print_rule(r, pf->opts & PF_OPT_VERBOSE2); + pfctl_clear_pool(&r->rpool); + return (0); +} + +#ifndef __DragonFly__ +int +pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) +{ + if (altqsupport && + (loadopt & PFCTL_FLAG_ALTQ) != 0) { + memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq)); + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) { + if (errno == ENXIO) + errx(1, "qtype not configured"); + else if (errno == ENODEV) + errx(1, "%s: driver does not support " + "altq", a->ifname); + else + err(1, "DIOCADDALTQ"); + } + } + pfaltq_store(&pf->paltq->altq); + } + return (0); +} +#endif + +int +pfctl_rules(int dev, char *filename, int opts, char *anchorname, + char *rulesetname, struct pfr_buffer *trans) +{ +#define ERR(x) do { warn(x); goto _error; } while(0) +#define ERRX(x) do { warnx(x); goto _error; } while(0) + + FILE *fin; + struct pfr_buffer *t, buf; + struct pfioc_altq pa; + struct pfctl pf; + struct pfr_table trs; + int osize; + + if (trans == NULL) { + bzero(&buf, sizeof(buf)); + buf.pfrb_type = PFRB_TRANS; + t = &buf; + osize = 0; + } else { + t = trans; + osize = t->pfrb_size; + } + + memset(&pa, 0, sizeof(pa)); + memset(&pf, 0, sizeof(pf)); + memset(&trs, 0, sizeof(trs)); + if (strlcpy(trs.pfrt_anchor, anchorname, + sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor) || + strlcpy(trs.pfrt_ruleset, rulesetname, + sizeof(trs.pfrt_ruleset)) >= sizeof(trs.pfrt_ruleset)) + ERRX("pfctl_rules: strlcpy"); + if (strcmp(filename, "-") == 0) { + fin = stdin; + infile = "stdin"; + } else { + if ((fin = fopen(filename, "r")) == NULL) { + warn("%s", filename); + return (1); + } + infile = filename; + } + pf.dev = dev; + pf.opts = opts; + pf.loadopt = loadopt; + if (anchorname[0]) + pf.loadopt &= ~PFCTL_FLAG_ALTQ; + pf.paltq = &pa; + pf.trans = t; + pf.rule_nr = 0; + pf.anchor = anchorname; + pf.ruleset = rulesetname; + + if ((opts & PF_OPT_NOACTION) == 0) { + if ((pf.loadopt & PFCTL_FLAG_NAT) != 0) { + if (pfctl_add_trans(t, PF_RULESET_NAT, anchorname, + rulesetname) || + pfctl_add_trans(t, PF_RULESET_BINAT, anchorname, + rulesetname) || + pfctl_add_trans(t, PF_RULESET_RDR, anchorname, + rulesetname)) + ERR("pfctl_rules"); + } + if (((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0))) { + if (pfctl_add_trans(t, PF_RULESET_ALTQ, anchorname, + rulesetname)) + ERR("pfctl_rules"); + } + if ((pf.loadopt & PFCTL_FLAG_FILTER) != 0) { + if (pfctl_add_trans(t, PF_RULESET_SCRUB, anchorname, + rulesetname) || + pfctl_add_trans(t, PF_RULESET_FILTER, anchorname, + rulesetname)) + ERR("pfctl_rules"); + } + if (pf.loadopt & PFCTL_FLAG_TABLE) { + if (pfctl_add_trans(t, PF_RULESET_TABLE, anchorname, + rulesetname)) + ERR("pfctl_rules"); + } + if (pfctl_trans(dev, t, DIOCXBEGIN, osize)) + ERR("DIOCXBEGIN"); + if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ)) + pa.ticket = pfctl_get_ticket(t, PF_RULESET_ALTQ, + anchorname, rulesetname); + if (pf.loadopt & PFCTL_FLAG_TABLE) + pf.tticket = pfctl_get_ticket(t, PF_RULESET_TABLE, + anchorname, rulesetname); + } + if (parse_rules(fin, &pf) < 0) { + if ((opts & PF_OPT_NOACTION) == 0) + ERRX("Syntax error in config file: " + "pf rules not loaded"); + else + goto _error; + } + #ifndef __DragonFly__ + if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0)) + if (check_commit_altq(dev, opts) != 0) + ERRX("errors in altq config"); + #endif + if (fin != stdin) + fclose(fin); + + /* process "load anchor" directives */ + if (!anchorname[0] && !rulesetname[0]) + if (pfctl_load_anchors(dev, opts, t) == -1) + ERRX("load anchors"); + + if (trans == NULL && (opts & PF_OPT_NOACTION) == 0) + if (pfctl_trans(dev, t, DIOCXCOMMIT, 0)) + ERR("DIOCXCOMMIT"); + return (0); + +_error: + if (trans == NULL) { /* main ruleset */ + if ((opts & PF_OPT_NOACTION) == 0) + if (pfctl_trans(dev, t, DIOCXROLLBACK, 0)) + err(1, "DIOCXROLLBACK"); + exit(1); + } else /* sub ruleset */ + return (-1); + +#undef ERR +#undef ERRX +} + +int +pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) +{ + struct pfioc_limit pl; + int i; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + memset(&pl, 0, sizeof(pl)); + for (i = 0; pf_limits[i].name; i++) { + if (strcasecmp(opt, pf_limits[i].name) == 0) { + pl.index = pf_limits[i].index; + pl.limit = limit; + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) { + if (errno == EBUSY) { + warnx("Current pool " + "size exceeds requested " + "hard limit"); + return (1); + } else + err(1, "DIOCSETLIMIT"); + } + } + break; + } + } + if (pf_limits[i].name == NULL) { + warnx("Bad pool name."); + return (1); + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set limit %s %d\n", opt, limit); + + return (0); +} + +int +pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) +{ + struct pfioc_tm pt; + int i; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + memset(&pt, 0, sizeof(pt)); + for (i = 0; pf_timeouts[i].name; i++) { + if (strcasecmp(opt, pf_timeouts[i].name) == 0) { + pt.timeout = pf_timeouts[i].timeout; + break; + } + } + + if (pf_timeouts[i].name == NULL) { + warnx("Bad timeout name."); + return (1); + } + + pt.seconds = seconds; + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) + err(1, "DIOCSETTIMEOUT"); + } + + if (pf->opts & PF_OPT_VERBOSE && ! quiet) + printf("set timeout %s %d\n", opt, seconds); + + return (0); +} + +int +pfctl_set_optimization(struct pfctl *pf, const char *opt) +{ + const struct pf_hint *hint; + int i, r; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + for (i = 0; pf_hints[i].name; i++) + if (strcasecmp(opt, pf_hints[i].name) == 0) + break; + + hint = pf_hints[i].hint; + if (hint == NULL) { + warnx("Bad hint name."); + return (1); + } + + for (i = 0; hint[i].name; i++) + if ((r = pfctl_set_timeout(pf, hint[i].name, + hint[i].timeout, 1))) + return (r); + + if (pf->opts & PF_OPT_VERBOSE) + printf("set optimization %s\n", opt); + + return (0); +} + +int +pfctl_set_logif(struct pfctl *pf, char *ifname) +{ + struct pfioc_if pi; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + memset(&pi, 0, sizeof(pi)); + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (!strcmp(ifname, "none")) + bzero(pi.ifname, sizeof(pi.ifname)); + else { + if (strlcpy(pi.ifname, ifname, + sizeof(pi.ifname)) >= sizeof(pi.ifname)) + errx(1, "pfctl_set_logif: strlcpy"); + } + if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) + err(1, "DIOCSETSTATUSIF"); + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set loginterface %s\n", ifname); + + return (0); +} + +int +pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid) +{ + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + htonl(hostid); + + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(dev_fd, DIOCSETHOSTID, &hostid)) + err(1, "DIOCSETHOSTID"); + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set hostid 0x%08x\n", ntohl(hostid)); + + return (0); +} + +int +pfctl_set_debug(struct pfctl *pf, char *d) +{ + u_int32_t level; + + if ((loadopt & PFCTL_FLAG_OPTION) == 0) + return (0); + + if (!strcmp(d, "none")) + level = PF_DEBUG_NONE; + else if (!strcmp(d, "urgent")) + level = PF_DEBUG_URGENT; + else if (!strcmp(d, "misc")) + level = PF_DEBUG_MISC; + else if (!strcmp(d, "loud")) + level = PF_DEBUG_NOISY; + else { + warnx("unknown debug level \"%s\"", d); + return (-1); + } + + if ((pf->opts & PF_OPT_NOACTION) == 0) + if (ioctl(dev_fd, DIOCSETDEBUG, &level)) + err(1, "DIOCSETDEBUG"); + + if (pf->opts & PF_OPT_VERBOSE) + printf("set debug %s\n", d); + + return (0); +} + +int +pfctl_debug(int dev, u_int32_t level, int opts) +{ + if (ioctl(dev, DIOCSETDEBUG, &level)) + err(1, "DIOCSETDEBUG"); + if ((opts & PF_OPT_QUIET) == 0) { + fprintf(stderr, "debug level set to '"); + switch (level) { + case PF_DEBUG_NONE: + fprintf(stderr, "none"); + break; + case PF_DEBUG_URGENT: + fprintf(stderr, "urgent"); + break; + case PF_DEBUG_MISC: + fprintf(stderr, "misc"); + break; + case PF_DEBUG_NOISY: + fprintf(stderr, "loud"); + break; + default: + fprintf(stderr, ""); + break; + } + fprintf(stderr, "'\n"); + } + return (0); +} + +int +pfctl_clear_rule_counters(int dev, int opts) +{ + if (ioctl(dev, DIOCCLRRULECTRS)) + err(1, "DIOCCLRRULECTRS"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf: rule counters cleared\n"); + return (0); +} + +int +pfctl_test_altqsupport(int dev, int opts) +{ + struct pfioc_altq pa; + + if (ioctl(dev, DIOCGETALTQS, &pa)) { + if (errno == ENODEV) { + if (!(opts & PF_OPT_QUIET)) + fprintf(stderr, "No ALTQ support in kernel\n" + "ALTQ related functions disabled\n"); + return (0); + } else + err(1, "DIOCGETALTQS"); + } + return (1); +} + +int +pfctl_show_anchors(int dev, int opts, char *anchorname) +{ + u_int32_t nr, mnr; + + if (!*anchorname) { + struct pfioc_anchor pa; + + memset(&pa, 0, sizeof(pa)); + if (ioctl(dev, DIOCGETANCHORS, &pa)) { + warn("DIOCGETANCHORS"); + return (-1); + } + mnr = pa.nr; + for (nr = 0; nr < mnr; ++nr) { + pa.nr = nr; + if (ioctl(dev, DIOCGETANCHOR, &pa)) { + warn("DIOCGETANCHOR"); + return (-1); + } + if (!(opts & PF_OPT_VERBOSE) && + !strcmp(pa.name, PF_RESERVED_ANCHOR)) + continue; + printf(" %s\n", pa.name); + } + } else { + struct pfioc_ruleset pr; + + memset(&pr, 0, sizeof(pr)); + memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); + if (ioctl(dev, DIOCGETRULESETS, &pr)) { + if (errno == EINVAL) + fprintf(stderr, "No rulesets in anchor '%s'.\n", + anchorname); + else + err(1, "DIOCGETRULESETS"); + return (-1); + } + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr)) + err(1, "DIOCGETRULESET"); + printf(" %s:%s\n", pr.anchor, pr.name); + } + } + return (0); +} + +const char * +pfctl_lookup_option(char *cmd, const char **list) +{ + if (cmd != NULL && *cmd) + for (; *list; list++) + if (!strncmp(cmd, *list, strlen(cmd))) + return (*list); + return (NULL); +} + +int +main(int argc, char *argv[]) +{ + int error = 0; + int ch; + int mode = O_RDONLY; + int opts = 0; + char anchorname[PF_ANCHOR_NAME_SIZE]; + char rulesetname[PF_RULESET_NAME_SIZE]; + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, + "a:AdD:eqf:F:ghi:k:nNOp:rRs:t:T:vx:z")) != -1) { + switch (ch) { + case 'a': + anchoropt = optarg; + break; + case 'd': + opts |= PF_OPT_DISABLE; + mode = O_RDWR; + break; + case 'D': + if (pfctl_cmdline_symset(optarg) < 0) + warnx("could not parse macro definition %s", + optarg); + break; + case 'e': + opts |= PF_OPT_ENABLE; + mode = O_RDWR; + break; + case 'q': + opts |= PF_OPT_QUIET; + break; + case 'F': + clearopt = pfctl_lookup_option(optarg, clearopt_list); + if (clearopt == NULL) { + warnx("Unknown flush modifier '%s'", optarg); + usage(); + } + mode = O_RDWR; + break; + case 'i': + ifaceopt = optarg; + break; + case 'k': + if (state_killers >= 2) { + warnx("can only specify -k twice"); + usage(); + /* NOTREACHED */ + } + state_kill[state_killers++] = optarg; + mode = O_RDWR; + break; + case 'n': + opts |= PF_OPT_NOACTION; + break; + case 'N': + loadopt |= PFCTL_FLAG_NAT; + break; + case 'r': + opts |= PF_OPT_USEDNS; + break; + case 'f': + rulesopt = optarg; + mode = O_RDWR; + break; + case 'g': + opts |= PF_OPT_DEBUG; + break; + case 'A': + loadopt |= PFCTL_FLAG_ALTQ; + break; + case 'R': + loadopt |= PFCTL_FLAG_FILTER; + break; + case 'O': + loadopt |= PFCTL_FLAG_OPTION; + break; + case 'p': + pf_device = optarg; + break; + case 's': + showopt = pfctl_lookup_option(optarg, showopt_list); + if (showopt == NULL) { + warnx("Unknown show modifier '%s'", optarg); + usage(); + } + break; + case 't': + tableopt = optarg; + break; + case 'T': + tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list); + if (tblcmdopt == NULL) { + warnx("Unknown table command '%s'", optarg); + usage(); + } + break; + case 'v': + if (opts & PF_OPT_VERBOSE) + opts |= PF_OPT_VERBOSE2; + opts |= PF_OPT_VERBOSE; + break; + case 'x': + debugopt = pfctl_lookup_option(optarg, debugopt_list); + if (debugopt == NULL) { + warnx("Unknown debug level '%s'", optarg); + usage(); + } + mode = O_RDWR; + break; + case 'z': + opts |= PF_OPT_CLRRULECTRS; + mode = O_RDWR; + break; + case 'h': + /* FALLTHROUGH */ + default: + usage(); + /* NOTREACHED */ + } + } + + if (tblcmdopt != NULL) { + argc -= optind; + argv += optind; + ch = *tblcmdopt; + if (ch == 'l') { + loadopt |= PFCTL_FLAG_TABLE; + tblcmdopt = NULL; + } else + mode = strchr("acdfkrz", ch) ? O_RDWR : O_RDONLY; + } else if (argc != optind) { + warnx("unknown command line argument: %s ...", argv[optind]); + usage(); + /* NOTREACHED */ + } + if (loadopt == 0) + loadopt = ~0; + + memset(anchorname, 0, sizeof(anchorname)); + memset(rulesetname, 0, sizeof(rulesetname)); + if (anchoropt != NULL) { + char *t; + + if ((t = strchr(anchoropt, ':')) == NULL) { + if (strlcpy(anchorname, anchoropt, + sizeof(anchorname)) >= sizeof(anchorname)) + errx(1, "anchor name '%s' too long", + anchoropt); + } else { + char *p; + + if ((p = strdup(anchoropt)) == NULL) + err(1, "anchoropt: strdup"); + t = strsep(&p, ":"); + if (*t == '\0' || *p == '\0') + errx(1, "anchor '%s' invalid", anchoropt); + if (strlcpy(anchorname, t, sizeof(anchorname)) >= + sizeof(anchorname)) + errx(1, "anchor name '%s' too long", t); + if (strlcpy(rulesetname, p, sizeof(rulesetname)) >= + sizeof(rulesetname)) + errx(1, "ruleset name '%s' too long", p); + free(t); /* not p */ + } + loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE; + } + + if ((opts & PF_OPT_NOACTION) == 0) { + dev_fd = open(pf_device, mode); + if (dev_fd == -1) + err(1, "%s", pf_device); + altqsupport = pfctl_test_altqsupport(dev_fd, opts); + } else { + dev_fd = open(pf_device, O_RDONLY); + if (dev_fd >= 0) + opts |= PF_OPT_DUMMYACTION; + /* turn off options */ + opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); + clearopt = showopt = debugopt = NULL; + altqsupport = 1; + } + + if (opts & PF_OPT_DISABLE) + if (pfctl_disable(dev_fd, opts)) + error = 1; + + if (showopt != NULL) { + switch (*showopt) { + case 'A': + pfctl_show_anchors(dev_fd, opts, anchorname); + break; + case 'r': + pfctl_load_fingerprints(dev_fd, opts); + pfctl_show_rules(dev_fd, opts, 0, anchorname, + rulesetname); + break; + case 'l': + pfctl_load_fingerprints(dev_fd, opts); + pfctl_show_rules(dev_fd, opts, 1, anchorname, + rulesetname); + break; + case 'n': + pfctl_load_fingerprints(dev_fd, opts); + pfctl_show_nat(dev_fd, opts, anchorname, rulesetname); + break; +#ifndef __DragonFly__ + case 'q': + pfctl_show_altq(dev_fd, ifaceopt, opts, + opts & PF_OPT_VERBOSE2); + break; +#endif + case 's': + pfctl_show_states(dev_fd, ifaceopt, opts); + break; + case 'S': + pfctl_show_src_nodes(dev_fd, opts); + break; + case 'i': + pfctl_show_status(dev_fd, opts); + break; + case 't': + pfctl_show_timeouts(dev_fd, opts); + break; + case 'm': + pfctl_show_limits(dev_fd, opts); + break; + case 'a': + opts |= PF_OPT_SHOWALL; + pfctl_load_fingerprints(dev_fd, opts); + + pfctl_show_nat(dev_fd, opts, anchorname, rulesetname); + pfctl_show_rules(dev_fd, opts, 0, anchorname, + rulesetname); +#ifndef __DragonFly__ + pfctl_show_altq(dev_fd, ifaceopt, opts, 0); +#endif + pfctl_show_states(dev_fd, ifaceopt, opts); + pfctl_show_src_nodes(dev_fd, opts); + pfctl_show_status(dev_fd, opts); + pfctl_show_rules(dev_fd, opts, 1, anchorname, rulesetname); + pfctl_show_timeouts(dev_fd, opts); + pfctl_show_limits(dev_fd, opts); + pfctl_show_tables(anchorname, rulesetname, opts); + pfctl_show_fingerprints(opts); + break; + case 'T': + pfctl_show_tables(anchorname, rulesetname, opts); + break; + case 'o': + pfctl_load_fingerprints(dev_fd, opts); + pfctl_show_fingerprints(opts); + break; + case 'I': + pfctl_show_ifaces(ifaceopt, opts); + break; + } + } + + if (clearopt != NULL) { + switch (*clearopt) { + case 'r': + pfctl_clear_rules(dev_fd, opts, anchorname, rulesetname); + break; + case 'n': + pfctl_clear_nat(dev_fd, opts, anchorname, rulesetname); + break; +#ifndef __DragonFly__ + case 'q': + pfctl_clear_altq(dev_fd, opts); + break; +#endif + case 's': + pfctl_clear_states(dev_fd, ifaceopt, opts); + break; + case 'S': + pfctl_clear_src_nodes(dev_fd, opts); + break; + case 'i': + pfctl_clear_stats(dev_fd, opts); + break; + case 'a': + pfctl_clear_rules(dev_fd, opts, anchorname, rulesetname); + pfctl_clear_nat(dev_fd, opts, anchorname, rulesetname); + pfctl_clear_tables(anchorname, rulesetname, opts); + if (!*anchorname && !*rulesetname) { +#ifndef __DragonFly__ + pfctl_clear_altq(dev_fd, opts); +#endif + pfctl_clear_states(dev_fd, ifaceopt, opts); + pfctl_clear_src_nodes(dev_fd, opts); + pfctl_clear_stats(dev_fd, opts); + pfctl_clear_fingerprints(dev_fd, opts); + } + break; + case 'o': + pfctl_clear_fingerprints(dev_fd, opts); + break; + case 'T': + pfctl_clear_tables(anchorname, rulesetname, opts); + break; + } + } + if (state_killers) + pfctl_kill_states(dev_fd, ifaceopt, opts); + + if (tblcmdopt != NULL) { + error = pfctl_command_tables(argc, argv, tableopt, + tblcmdopt, rulesopt, anchorname, rulesetname, opts); + rulesopt = NULL; + } + + if (rulesopt != NULL) + if (pfctl_file_fingerprints(dev_fd, opts, PF_OSFP_FILE)) + error = 1; + + if (rulesopt != NULL) { + if (pfctl_rules(dev_fd, rulesopt, opts, anchorname, rulesetname, + NULL)) + error = 1; + else if (!(opts & PF_OPT_NOACTION) && + (loadopt & PFCTL_FLAG_TABLE)) + warn_namespace_collision(NULL); + } + + if (opts & PF_OPT_ENABLE) + if (pfctl_enable(dev_fd, opts)) + error = 1; + + if (debugopt != NULL) { + switch (*debugopt) { + case 'n': + pfctl_debug(dev_fd, PF_DEBUG_NONE, opts); + break; + case 'u': + pfctl_debug(dev_fd, PF_DEBUG_URGENT, opts); + break; + case 'm': + pfctl_debug(dev_fd, PF_DEBUG_MISC, opts); + break; + case 'l': + pfctl_debug(dev_fd, PF_DEBUG_NOISY, opts); + break; + } + } + + if (opts & PF_OPT_CLRRULECTRS) { + if (pfctl_clear_rule_counters(dev_fd, opts)) + error = 1; + } + exit(error); +} diff --git a/usr.sbin/pfctl/pfctl.h b/usr.sbin/pfctl/pfctl.h new file mode 100644 index 0000000000..e59dbd8efe --- /dev/null +++ b/usr.sbin/pfctl/pfctl.h @@ -0,0 +1,125 @@ +/* $OpenBSD: pfctl.h,v 1.33 2004/02/19 21:37:01 cedric Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl.h,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _PFCTL_H_ +#define _PFCTL_H_ + +enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, + PFRB_IFACES, PFRB_TRANS, PFRB_MAX }; +struct pfr_buffer { + int pfrb_type; /* type of content, see enum above */ + int pfrb_size; /* number of objects in buffer */ + int pfrb_msize; /* maximum number of objects in buffer */ + void *pfrb_caddr; /* malloc'ated memory area */ +}; +#define PFRB_FOREACH(var, buf) \ + for ((var) = pfr_buf_next((buf), NULL); \ + (var) != NULL; \ + (var) = pfr_buf_next((buf), (var))) + +void pfr_set_fd(int); +int pfr_get_fd(void); +int pfr_clr_tables(struct pfr_table *, int *, int); +int pfr_add_tables(struct pfr_table *, int, int *, int); +int pfr_del_tables(struct pfr_table *, int, int *, int); +int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); +int pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int); +int pfr_clr_tstats(struct pfr_table *, int, int *, int); +int pfr_clr_addrs(struct pfr_table *, int *, int); +int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, + int *, int *, int *, int); +int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); +int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); +int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_set_tflags(struct pfr_table *, int, int, int, int *, int *, int); +int pfr_ina_begin(struct pfr_table *, int *, int *, int); +int pfr_ina_commit(struct pfr_table *, int, int *, int *, int); +int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, + int *, int, int); +void pfr_buf_clear(struct pfr_buffer *); +int pfr_buf_add(struct pfr_buffer *, const void *); +const void *pfr_buf_next(struct pfr_buffer *, const void *); +int pfr_buf_grow(struct pfr_buffer *, int); +int pfr_buf_load(struct pfr_buffer *, char *, int, + int (*)(struct pfr_buffer *, char *, int)); +const char *pfr_strerror(int); +int pfi_get_ifaces(const char *, struct pfi_if *, int *, int); +int pfi_clr_istats(const char *, int *, int); + +void pfctl_print_title(const char *); +int pfctl_clear_tables(const char *, const char *, int); +int pfctl_show_tables(const char *, const char *, int); +int pfctl_command_tables(int, char *[], char *, const char *, char *, + const char *, const char *, int); +int pfctl_show_altq(int, const char *, int, int); +void warn_namespace_collision(const char *); +int pfctl_show_ifaces(const char *, int); + +#ifndef DEFAULT_PRIORITY +#define DEFAULT_PRIORITY 1 +#endif + +#ifndef DEFAULT_QLIMIT +#define DEFAULT_QLIMIT 50 +#endif + +/* + * generalized service curve used for admission control + */ +struct segment { + LIST_ENTRY(segment) _next; + double x, y, d, m; +}; + +int check_commit_altq(int, int); +void pfaltq_store(struct pf_altq *); +void pfaltq_free(struct pf_altq *); +struct pf_altq *pfaltq_lookup(const char *); +char *rate2str(double); + +void print_addr(struct pf_addr_wrap *, sa_family_t, int); +void print_host(struct pf_state_host *, sa_family_t, int); +void print_seq(struct pf_state_peer *); +void print_state(struct pf_state *, int); +int unmask(struct pf_addr *, sa_family_t); + +int pfctl_cmdline_symset(char *); +int pfctl_add_trans(struct pfr_buffer *, int, const char *, const char *); +u_int32_t + pfctl_get_ticket(struct pfr_buffer *, int, const char *, const char *); +int pfctl_trans(int, struct pfr_buffer *, u_long, int); + +#endif /* _PFCTL_H_ */ diff --git a/usr.sbin/pfctl/pfctl_altq.c b/usr.sbin/pfctl/pfctl_altq.c new file mode 100644 index 0000000000..5a63180d87 --- /dev/null +++ b/usr.sbin/pfctl/pfctl_altq.c @@ -0,0 +1,1207 @@ +/* $OpenBSD: pfctl_altq.c,v 1.83 2004/03/14 21:51:44 dhartmei Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl_altq.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2002 + * Sony Computer Science Laboratories Inc. + * Copyright (c) 2002, 2003 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +#define is_sc_null(sc) (((sc) == NULL) || ((sc)->m1 == 0 && (sc)->m2 == 0)) + +TAILQ_HEAD(altqs, pf_altq) altqs = TAILQ_HEAD_INITIALIZER(altqs); +LIST_HEAD(gen_sc, segment) rtsc, lssc; + +struct pf_altq *qname_to_pfaltq(const char *, const char *); +u_int32_t qname_to_qid(const char *); + +static int eval_pfqueue_cbq(struct pfctl *, struct pf_altq *); +static int cbq_compute_idletime(struct pfctl *, struct pf_altq *); +static int check_commit_cbq(int, int, struct pf_altq *); +static int print_cbq_opts(const struct pf_altq *); + +static int eval_pfqueue_priq(struct pfctl *, struct pf_altq *); +static int check_commit_priq(int, int, struct pf_altq *); +static int print_priq_opts(const struct pf_altq *); + +static int eval_pfqueue_hfsc(struct pfctl *, struct pf_altq *); +static int check_commit_hfsc(int, int, struct pf_altq *); +static int print_hfsc_opts(const struct pf_altq *, + const struct node_queue_opt *); + +static void gsc_add_sc(struct gen_sc *, struct service_curve *); +static int is_gsc_under_sc(struct gen_sc *, + struct service_curve *); +static void gsc_destroy(struct gen_sc *); +static struct segment *gsc_getentry(struct gen_sc *, double); +static int gsc_add_seg(struct gen_sc *, double, double, double, + double); +static double sc_x2y(struct service_curve *, double); + +u_int32_t getifspeed(char *); +u_long getifmtu(char *); +int eval_queue_opts(struct pf_altq *, struct node_queue_opt *, + u_int32_t); +u_int32_t eval_bwspec(struct node_queue_bw *, u_int32_t); +void print_hfsc_sc(const char *, u_int, u_int, u_int, + const struct node_hfsc_sc *); + +void +pfaltq_store(struct pf_altq *a) +{ + struct pf_altq *altq; + + if ((altq = malloc(sizeof(*altq))) == NULL) + err(1, "malloc"); + memcpy(altq, a, sizeof(struct pf_altq)); + TAILQ_INSERT_TAIL(&altqs, altq, entries); +} + +void +pfaltq_free(struct pf_altq *a) +{ + struct pf_altq *altq; + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(a->ifname, altq->ifname, IFNAMSIZ) == 0 && + strncmp(a->qname, altq->qname, PF_QNAME_SIZE) == 0) { + TAILQ_REMOVE(&altqs, altq, entries); + free(altq); + return; + } + } +} + +struct pf_altq * +pfaltq_lookup(const char *ifname) +{ + struct pf_altq *altq; + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(ifname, altq->ifname, IFNAMSIZ) == 0 && + altq->qname[0] == 0) + return (altq); + } + return (NULL); +} + +struct pf_altq * +qname_to_pfaltq(const char *qname, const char *ifname) +{ + struct pf_altq *altq; + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(ifname, altq->ifname, IFNAMSIZ) == 0 && + strncmp(qname, altq->qname, PF_QNAME_SIZE) == 0) + return (altq); + } + return (NULL); +} + +u_int32_t +qname_to_qid(const char *qname) +{ + struct pf_altq *altq; + + /* + * We guarantee that same named queues on different interfaces + * have the same qid, so we do NOT need to limit matching on + * one interface! + */ + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(qname, altq->qname, PF_QNAME_SIZE) == 0) + return (altq->qid); + } + return (0); +} + +void +print_altq(const struct pf_altq *a, unsigned level, struct node_queue_bw *bw, + struct node_queue_opt *qopts) +{ + if (a->qname[0] != 0) { + print_queue(a, level, bw, 0, qopts); + return; + } + + printf("altq on %s ", a->ifname); + + switch (a->scheduler) { + case ALTQT_CBQ: + if (!print_cbq_opts(a)) + printf("cbq "); + break; + case ALTQT_PRIQ: + if (!print_priq_opts(a)) + printf("priq "); + break; + case ALTQT_HFSC: + if (!print_hfsc_opts(a, qopts)) + printf("hfsc "); + break; + } + + if (bw != NULL && bw->bw_percent > 0) { + if (bw->bw_percent < 100) + printf("bandwidth %u%% ", bw->bw_percent); + } else + printf("bandwidth %s ", rate2str((double)a->ifbandwidth)); + + if (a->qlimit != DEFAULT_QLIMIT) + printf("qlimit %u ", a->qlimit); + printf("tbrsize %u ", a->tbrsize); +} + +void +print_queue(const struct pf_altq *a, unsigned level, struct node_queue_bw *bw, + int print_interface, struct node_queue_opt *qopts) +{ + unsigned i; + + printf("queue "); + for (i = 0; i < level; ++i) + printf(" "); + printf("%s ", a->qname); + if (print_interface) + printf("on %s ", a->ifname); + if (a->scheduler == ALTQT_CBQ || a->scheduler == ALTQT_HFSC) { + if (bw != NULL && bw->bw_percent > 0) { + if (bw->bw_percent < 100) + printf("bandwidth %u%% ", bw->bw_percent); + } else + printf("bandwidth %s ", rate2str((double)a->bandwidth)); + } + if (a->priority != DEFAULT_PRIORITY) + printf("priority %u ", a->priority); + if (a->qlimit != DEFAULT_QLIMIT) + printf("qlimit %u ", a->qlimit); + switch (a->scheduler) { + case ALTQT_CBQ: + print_cbq_opts(a); + break; + case ALTQT_PRIQ: + print_priq_opts(a); + break; + case ALTQT_HFSC: + print_hfsc_opts(a, qopts); + break; + } +} + +/* + * eval_pfaltq computes the discipline parameters. + */ +int +eval_pfaltq(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw, + struct node_queue_opt *opts) +{ + u_int rate, size, errors = 0; + + if (bw->bw_absolute > 0) + pa->ifbandwidth = bw->bw_absolute; + else + if ((rate = getifspeed(pa->ifname)) == 0) { + fprintf(stderr, "cannot determine interface bandwidth " + "for %s, specify an absolute bandwidth\n", + pa->ifname); + errors++; + } else if ((pa->ifbandwidth = eval_bwspec(bw, rate)) == 0) + pa->ifbandwidth = rate; + + errors += eval_queue_opts(pa, opts, pa->ifbandwidth); + + /* if tbrsize is not specified, use heuristics */ + if (pa->tbrsize == 0) { + rate = pa->ifbandwidth; + if (rate <= 1 * 1000 * 1000) + size = 1; + else if (rate <= 10 * 1000 * 1000) + size = 4; + else if (rate <= 200 * 1000 * 1000) + size = 8; + else + size = 24; + size = size * getifmtu(pa->ifname); + if (size > 0xffff) + size = 0xffff; + pa->tbrsize = size; + } + return (errors); +} + +/* + * check_commit_altq does consistency check for each interface + */ +int +check_commit_altq(int dev, int opts) +{ + struct pf_altq *altq; + int error = 0; + + /* call the discipline check for each interface. */ + TAILQ_FOREACH(altq, &altqs, entries) { + if (altq->qname[0] == 0) { + switch (altq->scheduler) { + case ALTQT_CBQ: + error = check_commit_cbq(dev, opts, altq); + break; + case ALTQT_PRIQ: + error = check_commit_priq(dev, opts, altq); + break; + case ALTQT_HFSC: + error = check_commit_hfsc(dev, opts, altq); + break; + default: + break; + } + } + } + return (error); +} + +/* + * eval_pfqueue computes the queue parameters. + */ +int +eval_pfqueue(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw, + struct node_queue_opt *opts) +{ + /* should be merged with expand_queue */ + struct pf_altq *if_pa, *parent; + int error = 0; + + /* find the corresponding interface and copy fields used by queues */ + if ((if_pa = pfaltq_lookup(pa->ifname)) == NULL) { + fprintf(stderr, "altq not defined on %s\n", pa->ifname); + return (1); + } + pa->scheduler = if_pa->scheduler; + pa->ifbandwidth = if_pa->ifbandwidth; + + if (qname_to_pfaltq(pa->qname, pa->ifname) != NULL) { + fprintf(stderr, "queue %s already exists on interface %s\n", + pa->qname, pa->ifname); + return (1); + } + pa->qid = qname_to_qid(pa->qname); + + parent = NULL; + if (pa->parent[0] != 0) { + parent = qname_to_pfaltq(pa->parent, pa->ifname); + if (parent == NULL) { + fprintf(stderr, "parent %s not found for %s\n", + pa->parent, pa->qname); + return (1); + } + pa->parent_qid = parent->qid; + } + if (pa->qlimit == 0) + pa->qlimit = DEFAULT_QLIMIT; + + if (pa->scheduler == ALTQT_CBQ || pa->scheduler == ALTQT_HFSC) { + if ((pa->bandwidth = eval_bwspec(bw, + parent == NULL ? 0 : parent->bandwidth)) == 0) { + fprintf(stderr, "bandwidth for %s invalid (%d / %d)\n", + pa->qname, bw->bw_absolute, bw->bw_percent); + return (1); + } + + if (pa->bandwidth > pa->ifbandwidth) { + fprintf(stderr, "bandwidth for %s higher than " + "interface\n", pa->qname); + return (1); + } + if (parent != NULL && pa->bandwidth > parent->bandwidth) { + fprintf(stderr, "bandwidth for %s higher than parent\n", + pa->qname); + return (1); + } + } + + if (eval_queue_opts(pa, opts, parent == NULL? 0 : parent->bandwidth)) + return (1); + + switch (pa->scheduler) { + case ALTQT_CBQ: + error = eval_pfqueue_cbq(pf, pa); + break; + case ALTQT_PRIQ: + error = eval_pfqueue_priq(pf, pa); + break; + case ALTQT_HFSC: + error = eval_pfqueue_hfsc(pf, pa); + break; + default: + break; + } + return (error); +} + +/* + * CBQ support functions + */ +#define RM_FILTER_GAIN 5 /* log2 of gain, e.g., 5 => 31/32 */ +#define RM_NS_PER_SEC (1000000000) + +static int +eval_pfqueue_cbq(struct pfctl *pf, struct pf_altq *pa) +{ + struct cbq_opts *opts; + u_int ifmtu; + + if (pa->priority >= CBQ_MAXPRI) { + warnx("priority out of range: max %d", CBQ_MAXPRI - 1); + return (-1); + } + + ifmtu = getifmtu(pa->ifname); + opts = &pa->pq_u.cbq_opts; + + if (opts->pktsize == 0) { /* use default */ + opts->pktsize = ifmtu; + if (opts->pktsize > MCLBYTES) /* do what TCP does */ + opts->pktsize &= ~MCLBYTES; + } else if (opts->pktsize > ifmtu) + opts->pktsize = ifmtu; + if (opts->maxpktsize == 0) /* use default */ + opts->maxpktsize = ifmtu; + else if (opts->maxpktsize > ifmtu) + opts->pktsize = ifmtu; + + if (opts->pktsize > opts->maxpktsize) + opts->pktsize = opts->maxpktsize; + + if (pa->parent[0] == 0) + opts->flags |= (CBQCLF_ROOTCLASS | CBQCLF_WRR); + + cbq_compute_idletime(pf, pa); + return (0); +} + +/* + * compute ns_per_byte, maxidle, minidle, and offtime + */ +static int +cbq_compute_idletime(struct pfctl *pf, struct pf_altq *pa) +{ + struct cbq_opts *opts; + double maxidle_s, maxidle, minidle; + double offtime, nsPerByte, ifnsPerByte, ptime, cptime; + double z, g, f, gton, gtom; + u_int minburst, maxburst; + + opts = &pa->pq_u.cbq_opts; + ifnsPerByte = (1.0 / (double)pa->ifbandwidth) * RM_NS_PER_SEC * 8; + minburst = opts->minburst; + maxburst = opts->maxburst; + + if (pa->bandwidth == 0) + f = 0.0001; /* small enough? */ + else + f = ((double) pa->bandwidth / (double) pa->ifbandwidth); + + nsPerByte = ifnsPerByte / f; + ptime = (double)opts->pktsize * ifnsPerByte; + cptime = ptime * (1.0 - f) / f; + + if (nsPerByte * (double)opts->maxpktsize > (double)INT_MAX) { + /* + * this causes integer overflow in kernel! + * (bandwidth < 6Kbps when max_pkt_size=1500) + */ + if (pa->bandwidth != 0 && (pf->opts & PF_OPT_QUIET) == 0) + warnx("queue bandwidth must be larger than %s", + rate2str(ifnsPerByte * (double)opts->maxpktsize / + (double)INT_MAX * (double)pa->ifbandwidth)); + fprintf(stderr, "cbq: queue %s is too slow!\n", + pa->qname); + nsPerByte = (double)(INT_MAX / opts->maxpktsize); + } + + if (maxburst == 0) { /* use default */ + if (cptime > 10.0 * 1000000) + maxburst = 4; + else + maxburst = 16; + } + if (minburst == 0) /* use default */ + minburst = 2; + if (minburst > maxburst) + minburst = maxburst; + + z = (double)(1 << RM_FILTER_GAIN); + g = (1.0 - 1.0 / z); + gton = pow(g, (double)maxburst); + gtom = pow(g, (double)(minburst-1)); + maxidle = ((1.0 / f - 1.0) * ((1.0 - gton) / gton)); + maxidle_s = (1.0 - g); + if (maxidle > maxidle_s) + maxidle = ptime * maxidle; + else + maxidle = ptime * maxidle_s; + if (minburst) + offtime = cptime * (1.0 + 1.0/(1.0 - g) * (1.0 - gtom) / gtom); + else + offtime = cptime; + minidle = -((double)opts->maxpktsize * (double)nsPerByte); + + /* scale parameters */ + maxidle = ((maxidle * 8.0) / nsPerByte) * + pow(2.0, (double)RM_FILTER_GAIN); + offtime = (offtime * 8.0) / nsPerByte * + pow(2.0, (double)RM_FILTER_GAIN); + minidle = ((minidle * 8.0) / nsPerByte) * + pow(2.0, (double)RM_FILTER_GAIN); + + maxidle = maxidle / 1000.0; + offtime = offtime / 1000.0; + minidle = minidle / 1000.0; + + opts->minburst = minburst; + opts->maxburst = maxburst; + opts->ns_per_byte = (u_int)nsPerByte; + opts->maxidle = (u_int)fabs(maxidle); + opts->minidle = (int)minidle; + opts->offtime = (u_int)fabs(offtime); + + return (0); +} + +static int +check_commit_cbq(int dev, int opts, struct pf_altq *pa) +{ + struct pf_altq *altq; + int root_class, default_class; + int error = 0; + + /* + * check if cbq has one root queue and one default queue + * for this interface + */ + root_class = default_class = 0; + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + if (altq->pq_u.cbq_opts.flags & CBQCLF_ROOTCLASS) + root_class++; + if (altq->pq_u.cbq_opts.flags & CBQCLF_DEFCLASS) + default_class++; + } + if (root_class != 1) { + warnx("should have one root queue on %s", pa->ifname); + error++; + } + if (default_class != 1) { + warnx("should have one default queue on %s", pa->ifname); + error++; + } + return (error); +} + +static int +print_cbq_opts(const struct pf_altq *a) +{ + const struct cbq_opts *opts; + + opts = &a->pq_u.cbq_opts; + if (opts->flags) { + printf("cbq("); + if (opts->flags & CBQCLF_RED) + printf(" red"); + if (opts->flags & CBQCLF_ECN) + printf(" ecn"); + if (opts->flags & CBQCLF_RIO) + printf(" rio"); + if (opts->flags & CBQCLF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & CBQCLF_FLOWVALVE) + printf(" flowvalve"); + if (opts->flags & CBQCLF_BORROW) + printf(" borrow"); + if (opts->flags & CBQCLF_WRR) + printf(" wrr"); + if (opts->flags & CBQCLF_EFFICIENT) + printf(" efficient"); + if (opts->flags & CBQCLF_ROOTCLASS) + printf(" root"); + if (opts->flags & CBQCLF_DEFCLASS) + printf(" default"); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * PRIQ support functions + */ +static int +eval_pfqueue_priq(struct pfctl *pf, struct pf_altq *pa) +{ + struct pf_altq *altq; + + if (pa->priority >= PRIQ_MAXPRI) { + warnx("priority out of range: max %d", PRIQ_MAXPRI - 1); + return (-1); + } + /* the priority should be unique for the interface */ + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) == 0 && + altq->qname[0] != 0 && altq->priority == pa->priority) { + warnx("%s and %s have the same priority", + altq->qname, pa->qname); + return (-1); + } + } + + return (0); +} + +static int +check_commit_priq(int dev, int opts, struct pf_altq *pa) +{ + struct pf_altq *altq; + int default_class; + int error = 0; + + /* + * check if priq has one default class for this interface + */ + default_class = 0; + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + if (altq->pq_u.priq_opts.flags & PRCF_DEFAULTCLASS) + default_class++; + } + if (default_class != 1) { + warnx("should have one default queue on %s", pa->ifname); + error++; + } + return (error); +} + +static int +print_priq_opts(const struct pf_altq *a) +{ + const struct priq_opts *opts; + + opts = &a->pq_u.priq_opts; + + if (opts->flags) { + printf("priq("); + if (opts->flags & PRCF_RED) + printf(" red"); + if (opts->flags & PRCF_ECN) + printf(" ecn"); + if (opts->flags & PRCF_RIO) + printf(" rio"); + if (opts->flags & PRCF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & PRCF_DEFAULTCLASS) + printf(" default"); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * HFSC support functions + */ +static int +eval_pfqueue_hfsc(struct pfctl *pf, struct pf_altq *pa) +{ + struct pf_altq *altq, *parent; + struct hfsc_opts *opts; + struct service_curve sc; + + opts = &pa->pq_u.hfsc_opts; + + if (pa->parent[0] == 0) { + /* root queue */ + opts->lssc_m1 = pa->ifbandwidth; + opts->lssc_m2 = pa->ifbandwidth; + opts->lssc_d = 0; + return (0); + } + + LIST_INIT(&rtsc); + LIST_INIT(&lssc); + + /* if link_share is not specified, use bandwidth */ + if (opts->lssc_m2 == 0) + opts->lssc_m2 = pa->bandwidth; + + if ((opts->rtsc_m1 > 0 && opts->rtsc_m2 == 0) || + (opts->lssc_m1 > 0 && opts->lssc_m2 == 0) || + (opts->ulsc_m1 > 0 && opts->ulsc_m2 == 0)) { + warnx("m2 is zero for %s", pa->qname); + return (-1); + } + + if ((opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) || + (opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) || + (opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0)) { + warnx("m1 must be zero for convex curve: %s", pa->qname); + return (-1); + } + + /* + * admission control: + * for the real-time service curve, the sum of the service curves + * should not exceed 80% of the interface bandwidth. 20% is reserved + * not to over-commit the actual interface bandwidth. + * for the link-sharing service curve, the sum of the child service + * curve should not exceed the parent service curve. + * for the upper-limit service curve, the assigned bandwidth should + * be smaller than the interface bandwidth, and the upper-limit should + * be larger than the real-time service curve when both are defined. + */ + parent = qname_to_pfaltq(pa->parent, pa->ifname); + if (parent == NULL) + errx(1, "parent %s not found for %s", pa->parent, pa->qname); + + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + + /* if the class has a real-time service curve, add it. */ + if (opts->rtsc_m2 != 0 && altq->pq_u.hfsc_opts.rtsc_m2 != 0) { + sc.m1 = altq->pq_u.hfsc_opts.rtsc_m1; + sc.d = altq->pq_u.hfsc_opts.rtsc_d; + sc.m2 = altq->pq_u.hfsc_opts.rtsc_m2; + gsc_add_sc(&rtsc, &sc); + } + + if (strncmp(altq->parent, pa->parent, PF_QNAME_SIZE) != 0) + continue; + + /* if the class has a link-sharing service curve, add it. */ + if (opts->lssc_m2 != 0 && altq->pq_u.hfsc_opts.lssc_m2 != 0) { + sc.m1 = altq->pq_u.hfsc_opts.lssc_m1; + sc.d = altq->pq_u.hfsc_opts.lssc_d; + sc.m2 = altq->pq_u.hfsc_opts.lssc_m2; + gsc_add_sc(&lssc, &sc); + } + } + + /* check the real-time service curve. reserve 20% of interface bw */ + if (opts->rtsc_m2 != 0) { + sc.m1 = 0; + sc.d = 0; + sc.m2 = pa->ifbandwidth / 100 * 80; + if (!is_gsc_under_sc(&rtsc, &sc)) { + warnx("real-time sc exceeds the interface bandwidth"); + goto err_ret; + } + } + + /* check the link-sharing service curve. */ + if (opts->lssc_m2 != 0) { + sc.m1 = parent->pq_u.hfsc_opts.lssc_m1; + sc.d = parent->pq_u.hfsc_opts.lssc_d; + sc.m2 = parent->pq_u.hfsc_opts.lssc_m2; + if (!is_gsc_under_sc(&lssc, &sc)) { + warnx("link-sharing sc exceeds parent's sc"); + goto err_ret; + } + } + + /* check the upper-limit service curve. */ + if (opts->ulsc_m2 != 0) { + if (opts->ulsc_m1 > pa->ifbandwidth || + opts->ulsc_m2 > pa->ifbandwidth) { + warnx("upper-limit larger than interface bandwidth"); + goto err_ret; + } + if (opts->rtsc_m2 != 0 && opts->rtsc_m2 > opts->ulsc_m2) { + warnx("upper-limit sc smaller than real-time sc"); + goto err_ret; + } + } + + gsc_destroy(&rtsc); + gsc_destroy(&lssc); + + return (0); + +err_ret: + gsc_destroy(&rtsc); + gsc_destroy(&lssc); + return (-1); +} + +static int +check_commit_hfsc(int dev, int opts, struct pf_altq *pa) +{ + struct pf_altq *altq, *def = NULL; + int default_class; + int error = 0; + + /* check if hfsc has one default queue for this interface */ + default_class = 0; + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + if (altq->parent[0] == 0) /* dummy root */ + continue; + if (altq->pq_u.hfsc_opts.flags & HFCF_DEFAULTCLASS) { + default_class++; + def = altq; + } + } + if (default_class != 1) { + warnx("should have one default queue on %s", pa->ifname); + return (1); + } + /* make sure the default queue is a leaf */ + TAILQ_FOREACH(altq, &altqs, entries) { + if (strncmp(altq->ifname, pa->ifname, IFNAMSIZ) != 0) + continue; + if (altq->qname[0] == 0) /* this is for interface */ + continue; + if (strncmp(altq->parent, def->qname, PF_QNAME_SIZE) == 0) { + warnx("default queue is not a leaf"); + error++; + } + } + return (error); +} + +static int +print_hfsc_opts(const struct pf_altq *a, const struct node_queue_opt *qopts) +{ + const struct hfsc_opts *opts; + const struct node_hfsc_sc *rtsc, *lssc, *ulsc; + + opts = &a->pq_u.hfsc_opts; + if (qopts == NULL) + rtsc = lssc = ulsc = NULL; + else { + rtsc = &qopts->data.hfsc_opts.realtime; + lssc = &qopts->data.hfsc_opts.linkshare; + ulsc = &qopts->data.hfsc_opts.upperlimit; + } + + if (opts->flags || opts->rtsc_m2 != 0 || opts->ulsc_m2 != 0 || + (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || + opts->lssc_d != 0))) { + printf("hfsc("); + if (opts->flags & HFCF_RED) + printf(" red"); + if (opts->flags & HFCF_ECN) + printf(" ecn"); + if (opts->flags & HFCF_RIO) + printf(" rio"); + if (opts->flags & HFCF_CLEARDSCP) + printf(" cleardscp"); + if (opts->flags & HFCF_DEFAULTCLASS) + printf(" default"); + if (opts->rtsc_m2 != 0) + print_hfsc_sc("realtime", opts->rtsc_m1, opts->rtsc_d, + opts->rtsc_m2, rtsc); + if (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth || + opts->lssc_d != 0)) + print_hfsc_sc("linkshare", opts->lssc_m1, opts->lssc_d, + opts->lssc_m2, lssc); + if (opts->ulsc_m2 != 0) + print_hfsc_sc("upperlimit", opts->ulsc_m1, opts->ulsc_d, + opts->ulsc_m2, ulsc); + printf(" ) "); + + return (1); + } else + return (0); +} + +/* + * admission control using generalized service curve + */ +#define INFINITY HUGE_VAL /* positive infinity defined in */ + +/* add a new service curve to a generalized service curve */ +static void +gsc_add_sc(struct gen_sc *gsc, struct service_curve *sc) +{ + if (is_sc_null(sc)) + return; + if (sc->d != 0) + gsc_add_seg(gsc, 0.0, 0.0, (double)sc->d, (double)sc->m1); + gsc_add_seg(gsc, (double)sc->d, 0.0, INFINITY, (double)sc->m2); +} + +/* + * check whether all points of a generalized service curve have + * their y-coordinates no larger than a given two-piece linear + * service curve. + */ +static int +is_gsc_under_sc(struct gen_sc *gsc, struct service_curve *sc) +{ + struct segment *s, *last, *end; + double y; + + if (is_sc_null(sc)) { + if (LIST_EMPTY(gsc)) + return (1); + LIST_FOREACH(s, gsc, _next) { + if (s->m != 0) + return (0); + } + return (1); + } + /* + * gsc has a dummy entry at the end with x = INFINITY. + * loop through up to this dummy entry. + */ + end = gsc_getentry(gsc, INFINITY); + if (end == NULL) + return (1); + last = NULL; + for (s = LIST_FIRST(gsc); s != end; s = LIST_NEXT(s, _next)) { + if (s->y > sc_x2y(sc, s->x)) + return (0); + last = s; + } + /* last now holds the real last segment */ + if (last == NULL) + return (1); + if (last->m > sc->m2) + return (0); + if (last->x < sc->d && last->m > sc->m1) { + y = last->y + (sc->d - last->x) * last->m; + if (y > sc_x2y(sc, sc->d)) + return (0); + } + return (1); +} + +static void +gsc_destroy(struct gen_sc *gsc) +{ + struct segment *s; + + while ((s = LIST_FIRST(gsc)) != NULL) { + LIST_REMOVE(s, _next); + free(s); + } +} + +/* + * return a segment entry starting at x. + * if gsc has no entry starting at x, a new entry is created at x. + */ +static struct segment * +gsc_getentry(struct gen_sc *gsc, double x) +{ + struct segment *new, *prev, *s; + + prev = NULL; + LIST_FOREACH(s, gsc, _next) { + if (s->x == x) + return (s); /* matching entry found */ + else if (s->x < x) + prev = s; + else + break; + } + + /* we have to create a new entry */ + if ((new = calloc(1, sizeof(struct segment))) == NULL) + return (NULL); + + new->x = x; + if (x == INFINITY || s == NULL) + new->d = 0; + else if (s->x == INFINITY) + new->d = INFINITY; + else + new->d = s->x - x; + if (prev == NULL) { + /* insert the new entry at the head of the list */ + new->y = 0; + new->m = 0; + LIST_INSERT_HEAD(gsc, new, _next); + } else { + /* + * the start point intersects with the segment pointed by + * prev. divide prev into 2 segments + */ + if (x == INFINITY) { + prev->d = INFINITY; + if (prev->m == 0) + new->y = prev->y; + else + new->y = INFINITY; + } else { + prev->d = x - prev->x; + new->y = prev->d * prev->m + prev->y; + } + new->m = prev->m; + LIST_INSERT_AFTER(prev, new, _next); + } + return (new); +} + +/* add a segment to a generalized service curve */ +static int +gsc_add_seg(struct gen_sc *gsc, double x, double y, double d, double m) +{ + struct segment *start, *end, *s; + double x2; + + if (d == INFINITY) + x2 = INFINITY; + else + x2 = x + d; + start = gsc_getentry(gsc, x); + end = gsc_getentry(gsc, x2); + if (start == NULL || end == NULL) + return (-1); + + for (s = start; s != end; s = LIST_NEXT(s, _next)) { + s->m += m; + s->y += y + (s->x - x) * m; + } + + end = gsc_getentry(gsc, INFINITY); + for (; s != end; s = LIST_NEXT(s, _next)) { + s->y += m * d; + } + + return (0); +} + +/* get y-projection of a service curve */ +static double +sc_x2y(struct service_curve *sc, double x) +{ + double y; + + if (x <= (double)sc->d) + /* y belongs to the 1st segment */ + y = x * (double)sc->m1; + else + /* y belongs to the 2nd segment */ + y = (double)sc->d * (double)sc->m1 + + (x - (double)sc->d) * (double)sc->m2; + return (y); +} + +/* + * misc utilities + */ +#define R2S_BUFS 8 +#define RATESTR_MAX 16 + +char * +rate2str(double rate) +{ + char *buf; + static char r2sbuf[R2S_BUFS][RATESTR_MAX]; /* ring bufer */ + static int idx = 0; + int i; + static const char unit[] = " KMG"; + + buf = r2sbuf[idx++]; + if (idx == R2S_BUFS) + idx = 0; + + for (i = 0; rate >= 1000 && i <= 3; i++) + rate /= 1000; + + if ((int)(rate * 100) % 100) + snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]); + else + snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]); + + return (buf); +} + +u_int32_t +getifspeed(char *ifname) +{ + int s; + struct ifreq ifr; + struct if_data ifrdat; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + err(1, "socket"); + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= + sizeof(ifr.ifr_name)) + errx(1, "getifspeed: strlcpy"); + ifr.ifr_data = (caddr_t)&ifrdat; + if (ioctl(s, SIOCGIFDATA, (caddr_t)&ifr) == -1) + err(1, "SIOCGIFDATA"); + if (shutdown(s, SHUT_RDWR) == -1) + err(1, "shutdown"); + if (close(s)) + err(1, "close"); + return ((u_int32_t)ifrdat.ifi_baudrate); +} + +u_long +getifmtu(char *ifname) +{ + int s; + struct ifreq ifr; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + err(1, "socket"); + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= + sizeof(ifr.ifr_name)) + errx(1, "getifmtu: strlcpy"); + if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == -1) + err(1, "SIOCGIFMTU"); + if (shutdown(s, SHUT_RDWR) == -1) + err(1, "shutdown"); + if (close(s)) + err(1, "close"); + if (ifr.ifr_mtu > 0) + return (ifr.ifr_mtu); + else { + warnx("could not get mtu for %s, assuming 1500", ifname); + return (1500); + } +} + +int +eval_queue_opts(struct pf_altq *pa, struct node_queue_opt *opts, + u_int32_t ref_bw) +{ + int errors = 0; + + switch (pa->scheduler) { + case ALTQT_CBQ: + pa->pq_u.cbq_opts = opts->data.cbq_opts; + break; + case ALTQT_PRIQ: + pa->pq_u.priq_opts = opts->data.priq_opts; + break; + case ALTQT_HFSC: + pa->pq_u.hfsc_opts.flags = opts->data.hfsc_opts.flags; + if (opts->data.hfsc_opts.linkshare.used) { + pa->pq_u.hfsc_opts.lssc_m1 = + eval_bwspec(&opts->data.hfsc_opts.linkshare.m1, + ref_bw); + pa->pq_u.hfsc_opts.lssc_m2 = + eval_bwspec(&opts->data.hfsc_opts.linkshare.m2, + ref_bw); + pa->pq_u.hfsc_opts.lssc_d = + opts->data.hfsc_opts.linkshare.d; + } + if (opts->data.hfsc_opts.realtime.used) { + pa->pq_u.hfsc_opts.rtsc_m1 = + eval_bwspec(&opts->data.hfsc_opts.realtime.m1, + ref_bw); + pa->pq_u.hfsc_opts.rtsc_m2 = + eval_bwspec(&opts->data.hfsc_opts.realtime.m2, + ref_bw); + pa->pq_u.hfsc_opts.rtsc_d = + opts->data.hfsc_opts.realtime.d; + } + if (opts->data.hfsc_opts.upperlimit.used) { + pa->pq_u.hfsc_opts.ulsc_m1 = + eval_bwspec(&opts->data.hfsc_opts.upperlimit.m1, + ref_bw); + pa->pq_u.hfsc_opts.ulsc_m2 = + eval_bwspec(&opts->data.hfsc_opts.upperlimit.m2, + ref_bw); + pa->pq_u.hfsc_opts.ulsc_d = + opts->data.hfsc_opts.upperlimit.d; + } + break; + default: + warnx("eval_queue_opts: unknown scheduler type %u", + opts->qtype); + errors++; + break; + } + + return (errors); +} + +u_int32_t +eval_bwspec(struct node_queue_bw *bw, u_int32_t ref_bw) +{ + if (bw->bw_absolute > 0) + return (bw->bw_absolute); + + if (bw->bw_percent > 0) + return (ref_bw / 100 * bw->bw_percent); + + return (0); +} + +void +print_hfsc_sc(const char *scname, u_int m1, u_int d, u_int m2, + const struct node_hfsc_sc *sc) +{ + printf(" %s", scname); + + if (d != 0) { + printf("("); + if (sc != NULL && sc->m1.bw_percent > 0) + printf("%u%%", sc->m1.bw_percent); + else + printf("%s", rate2str((double)m1)); + printf(" %u", d); + } + + if (sc != NULL && sc->m2.bw_percent > 0) + printf(" %u%%", sc->m2.bw_percent); + else + printf(" %s", rate2str((double)m2)); + + if (d != 0) + printf(")"); +} diff --git a/usr.sbin/pfctl/pfctl_osfp.c b/usr.sbin/pfctl/pfctl_osfp.c new file mode 100644 index 0000000000..568def0724 --- /dev/null +++ b/usr.sbin/pfctl/pfctl_osfp.c @@ -0,0 +1,1101 @@ +/* $OpenBSD: pfctl_osfp.c,v 1.8 2004/02/27 10:42:00 henning Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl_osfp.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2003 Mike Frantzen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +#ifndef MIN +# define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ +#ifndef MAX +# define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + + +#if 0 +# define DEBUG(fp, str, v...) \ + fprintf(stderr, "%s:%s:%s " str "\n", (fp)->fp_os.fp_class_nm, \ + (fp)->fp_os.fp_version_nm, (fp)->fp_os.fp_subtype_nm , ## v); +#else +# define DEBUG(fp, str, v...) ((void)0) +#endif + + +struct name_entry; +LIST_HEAD(name_list, name_entry); +struct name_entry { + LIST_ENTRY(name_entry) nm_entry; + int nm_num; + char nm_name[PF_OSFP_LEN]; + + struct name_list nm_sublist; + int nm_sublist_num; +}; +struct name_list classes = LIST_HEAD_INITIALIZER(&classes); +int class_count; +int fingerprint_count; + +void add_fingerprint(int, int, struct pf_osfp_ioctl *); +struct name_entry *fingerprint_name_entry(struct name_list *, char *); +void pfctl_flush_my_fingerprints(struct name_list *); +char *get_field(char **, size_t *, int *); +int get_int(char **, size_t *, int *, int *, const char *, + int, int, const char *, int); +int get_str(char **, size_t *, char **, const char *, int, + const char *, int); +int get_tcpopts(const char *, int, const char *, + pf_tcpopts_t *, int *, int *, int *, int *, int *, + int *); +void import_fingerprint(struct pf_osfp_ioctl *); +const char *print_ioctl(struct pf_osfp_ioctl *); +void print_name_list(int, struct name_list *, const char *); +void sort_name_list(int, struct name_list *); +struct name_entry *lookup_name_list(struct name_list *, const char *); + +/* Load fingerprints from a file */ +int +pfctl_file_fingerprints(int dev, int opts, const char *fp_filename) +{ + FILE *in; + char *line; + size_t len, i; + int lineno = 0; + int window, w_mod, ttl, df, psize, p_mod, mss, mss_mod, wscale, + wscale_mod, optcnt, ts0; + pf_tcpopts_t packed_tcpopts; + char *class, *version, *subtype, *desc, *tcpopts; + struct pf_osfp_ioctl fp; + + pfctl_flush_my_fingerprints(&classes); + + if ((in = fopen(fp_filename, "r")) == NULL) { + warn("fopen(%s)", fp_filename); + return (1); + } + class = version = subtype = desc = tcpopts = NULL; + + if ((opts & PF_OPT_NOACTION) == 0) + pfctl_clear_fingerprints(dev, opts); + + while ((line = fgetln(in, &len)) != NULL) { + lineno++; + if (class) + free(class); + if (version) + free(version); + if (subtype) + free(subtype); + if (desc) + free(desc); + if (tcpopts) + free(tcpopts); + class = version = subtype = desc = tcpopts = NULL; + memset(&fp, 0, sizeof(fp)); + + /* Chop off comment */ + for (i = 0; i < len; i++) + if (line[i] == '#') { + len = i; + break; + } + /* Chop off whitespace */ + while (len > 0 && isspace(line[len - 1])) + len--; + while (len > 0 && isspace(line[0])) { + len--; + line++; + } + if (len == 0) + continue; + +#define T_DC 0x01 /* Allow don't care */ +#define T_MSS 0x02 /* Allow MSS multiple */ +#define T_MTU 0x04 /* Allow MTU multiple */ +#define T_MOD 0x08 /* Allow modulus */ + +#define GET_INT(v, mod, n, ty, mx) \ + get_int(&line, &len, &v, mod, n, ty, mx, fp_filename, lineno) +#define GET_STR(v, n, mn) \ + get_str(&line, &len, &v, n, mn, fp_filename, lineno) + + if (GET_INT(window, &w_mod, "window size", T_DC|T_MSS|T_MTU| + T_MOD, 0xffff) || + GET_INT(ttl, NULL, "ttl", 0, 0xff) || + GET_INT(df, NULL, "don't fragment frag", 0, 1) || + GET_INT(psize, &p_mod, "overall packet size", T_MOD|T_DC, + 8192) || + GET_STR(tcpopts, "TCP Options", 1) || + GET_STR(class, "OS class", 1) || + GET_STR(version, "OS version", 0) || + GET_STR(subtype, "OS subtype", 0) || + GET_STR(desc, "OS description", 2)) + continue; + if (get_tcpopts(fp_filename, lineno, tcpopts, &packed_tcpopts, + &optcnt, &mss, &mss_mod, &wscale, &wscale_mod, &ts0)) + continue; + if (len != 0) { + fprintf(stderr, "%s:%d excess field\n", fp_filename, + lineno); + continue; + } + + fp.fp_ttl = ttl; + if (df) + fp.fp_flags |= PF_OSFP_DF; + switch (w_mod) { + case 0: + break; + case T_DC: + fp.fp_flags |= PF_OSFP_WSIZE_DC; + break; + case T_MSS: + fp.fp_flags |= PF_OSFP_WSIZE_MSS; + break; + case T_MTU: + fp.fp_flags |= PF_OSFP_WSIZE_MTU; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSIZE_MOD; + break; + } + fp.fp_wsize = window; + + switch (p_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_PSIZE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_PSIZE_MOD; + } + fp.fp_psize = psize; + + + switch (wscale_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_WSCALE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSCALE_MOD; + } + fp.fp_wscale = wscale; + + switch (mss_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_MSS_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_MSS_MOD; + break; + } + fp.fp_mss = mss; + + fp.fp_tcpopts = packed_tcpopts; + fp.fp_optcnt = optcnt; + if (ts0) + fp.fp_flags |= PF_OSFP_TS0; + + if (class[0] == '@') + fp.fp_os.fp_enflags |= PF_OSFP_GENERIC; + if (class[0] == '*') + fp.fp_os.fp_enflags |= PF_OSFP_NODETAIL; + + if (class[0] == '@' || class[0] == '*') + strlcpy(fp.fp_os.fp_class_nm, class + 1, + sizeof(fp.fp_os.fp_class_nm)); + else + strlcpy(fp.fp_os.fp_class_nm, class, + sizeof(fp.fp_os.fp_class_nm)); + strlcpy(fp.fp_os.fp_version_nm, version, + sizeof(fp.fp_os.fp_version_nm)); + strlcpy(fp.fp_os.fp_subtype_nm, subtype, + sizeof(fp.fp_os.fp_subtype_nm)); + + add_fingerprint(dev, opts, &fp); + } + + if (class) + free(class); + if (version) + free(version); + if (subtype) + free(subtype); + if (desc) + free(desc); + + fclose(in); + + if (opts & PF_OPT_VERBOSE2) + printf("Loaded %d passive OS fingerprints\n", + fingerprint_count); + return (0); +} + +/* flush the kernel's fingerprints */ +void +pfctl_clear_fingerprints(int dev, int opts __unused) +{ + if (ioctl(dev, DIOCOSFPFLUSH)) + err(1, "DIOCOSFPFLUSH"); +} + +/* flush pfctl's view of the fingerprints */ +void +pfctl_flush_my_fingerprints(struct name_list *list) +{ + struct name_entry *nm; + + while ((nm = LIST_FIRST(list)) != NULL) { + LIST_REMOVE(nm, nm_entry); + pfctl_flush_my_fingerprints(&nm->nm_sublist); + fingerprint_count--; + free(nm); + } + class_count = 0; +} + +/* Fetch the active fingerprints from the kernel */ +int +pfctl_load_fingerprints(int dev, int opts __unused) +{ + struct pf_osfp_ioctl io; + int i; + + pfctl_flush_my_fingerprints(&classes); + + for (i = 0; i >= 0; i++) { + memset(&io, 0, sizeof(io)); + io.fp_getnum = i; + if (ioctl(dev, DIOCOSFPGET, &io)) { + if (errno == EBUSY) + break; + warn("DIOCOSFPGET"); + return (1); + } + import_fingerprint(&io); + } + return (0); +} + +/* List the fingerprints */ +void +pfctl_show_fingerprints(int opts) +{ + if (LIST_FIRST(&classes) != NULL) { + if (opts & PF_OPT_SHOWALL) { + pfctl_print_title("OS FINGERPRINTS:"); + printf("%u fingerprints loaded\n", fingerprint_count); + } else { + printf("Class\tVersion\tSubtype(subversion)\n"); + printf("-----\t-------\t-------------------\n"); + sort_name_list(opts, &classes); + print_name_list(opts, &classes, ""); + } + } +} + +/* Lookup a fingerprint */ +pf_osfp_t +pfctl_get_fingerprint(const char *name) +{ + struct name_entry *nm, *class_nm, *version_nm, *subtype_nm; + pf_osfp_t ret = PF_OSFP_NOMATCH; + int class, version, subtype; + int unp_class, unp_version, unp_subtype; + int wr_len, version_len, subtype_len; + char *ptr, *wr_name; + + if (strcasecmp(name, "unknown") == 0) + return (PF_OSFP_UNKNOWN); + + /* Try most likely no version and no subtype */ + if ((nm = lookup_name_list(&classes, name))) { + class = nm->nm_num; + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + goto found; + } else { + + /* Chop it up into class/version/subtype */ + + if ((wr_name = strdup(name)) == NULL) + err(1, "malloc"); + if ((ptr = index(wr_name, ' ')) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + *ptr++ = '\0'; + + /* The class is easy to find since it is delimited by a space */ + if ((class_nm = lookup_name_list(&classes, wr_name)) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + class = class_nm->nm_num; + + /* Try no subtype */ + if ((version_nm = lookup_name_list(&class_nm->nm_sublist, ptr))) + { + version = version_nm->nm_num; + subtype = PF_OSFP_ANY; + free(wr_name); + goto found; + } + + + /* + * There must be a version and a subtype. + * We'll do some fuzzy matching to pick up things like: + * Linux 2.2.14 (version=2.2 subtype=14) + * FreeBSD 4.0-STABLE (version=4.0 subtype=STABLE) + * Windows 2000 SP2 (version=2000 subtype=SP2) + */ +#define CONNECTOR(x) ((x) == '.' || (x) == ' ' || (x) == '\t' || (x) == '-') + wr_len = strlen(ptr); + LIST_FOREACH(version_nm, &class_nm->nm_sublist, nm_entry) { + version_len = strlen(version_nm->nm_name); + if (wr_len < version_len + 2 || + !CONNECTOR(ptr[version_len])) + continue; + /* first part of the string must be version */ + if (strncasecmp(ptr, version_nm->nm_name, + version_len)) + continue; + + LIST_FOREACH(subtype_nm, &version_nm->nm_sublist, + nm_entry) { + subtype_len = strlen(subtype_nm->nm_name); + if (wr_len != version_len + subtype_len + 1) + continue; + + /* last part of the string must be subtype */ + if (strcasecmp(&ptr[version_len+1], + subtype_nm->nm_name) != 0) + continue; + + /* Found it!! */ + version = version_nm->nm_num; + subtype = subtype_nm->nm_num; + free(wr_name); + goto found; + } + } + + free(wr_name); + return (PF_OSFP_NOMATCH); + } + +found: + PF_OSFP_PACK(ret, class, version, subtype); + if (ret != PF_OSFP_NOMATCH) { + PF_OSFP_UNPACK(ret, unp_class, unp_version, unp_subtype); + if (class != unp_class) { + fprintf(stderr, "warning: fingerprint table overflowed " + "classes\n"); + return (PF_OSFP_NOMATCH); + } + if (version != unp_version) { + fprintf(stderr, "warning: fingerprint table overflowed " + "versions\n"); + return (PF_OSFP_NOMATCH); + } + if (subtype != unp_subtype) { + fprintf(stderr, "warning: fingerprint table overflowed " + "subtypes\n"); + return (PF_OSFP_NOMATCH); + } + } + if (ret == PF_OSFP_ANY) { + /* should never happen */ + fprintf(stderr, "warning: fingerprint packed to 'any'\n"); + return (PF_OSFP_NOMATCH); + } + + return (ret); +} + +/* Lookup a fingerprint name by ID */ +char * +pfctl_lookup_fingerprint(pf_osfp_t fp, char *buf, size_t len) +{ + int class, version, subtype; + struct name_list *list; + struct name_entry *nm; + + char *class_name, *version_name, *subtype_name; + class_name = version_name = subtype_name = NULL; + + if (fp == PF_OSFP_UNKNOWN) { + strlcpy(buf, "unknown", len); + return (buf); + } + if (fp == PF_OSFP_ANY) { + strlcpy(buf, "any", len); + return (buf); + } + + PF_OSFP_UNPACK(fp, class, version, subtype); + if (class >= (1 << _FP_CLASS_BITS) || + version >= (1 << _FP_VERSION_BITS) || + subtype >= (1 << _FP_SUBTYPE_BITS)) { + warnx("PF_OSFP_UNPACK(0x%x) failed!!", fp); + strlcpy(buf, "nomatch", len); + return (buf); + } + + LIST_FOREACH(nm, &classes, nm_entry) { + if (nm->nm_num == class) { + class_name = nm->nm_name; + if (version == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == version) { + version_name = nm->nm_name; + if (subtype == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == subtype) { + subtype_name = + nm->nm_name; + goto found; + } + } /* foreach subtype */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach version */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach class */ + + strlcpy(buf, "nomatch", len); + return (buf); + +found: + snprintf(buf, len, "%s", class_name); + if (version_name) { + strlcat(buf, " ", len); + strlcat(buf, version_name, len); + if (subtype_name) { + if (index(version_name, ' ')) + strlcat(buf, " ", len); + else if (index(version_name, '.') && + isdigit(*subtype_name)) + strlcat(buf, ".", len); + else + strlcat(buf, " ", len); + strlcat(buf, subtype_name, len); + } + } + return (buf); +} + +/* lookup a name in a list */ +struct name_entry * +lookup_name_list(struct name_list *list, const char *name) +{ + struct name_entry *nm; + LIST_FOREACH(nm, list, nm_entry) + if (strcasecmp(name, nm->nm_name) == 0) + return (nm); + + return (NULL); +} + + +void +add_fingerprint(int dev, int opts, struct pf_osfp_ioctl *fp) +{ + struct pf_osfp_ioctl fptmp; + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + +/* We expand #-# or #.#-#.# version/subtypes into multiple fingerprints */ +#define EXPAND(field) do { \ + int _dot = -1, _start = -1, _end = -1, _i = 0; \ + /* pick major version out of #.# */ \ + if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.') { \ + _dot = fp->field[_i] - '0'; \ + _i += 2; \ + } \ + if (isdigit(fp->field[_i])) \ + _start = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit(fp->field[_i])) \ + _start = (_start * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i++] != '-') \ + break; \ + if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.' && \ + fp->field[_i] - '0' == _dot) \ + _i += 2; \ + else if (_dot != -1) \ + break; \ + if (isdigit(fp->field[_i])) \ + _end = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit(fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (isdigit(fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i] != '\0') \ + break; \ + memcpy(&fptmp, fp, sizeof(fptmp)); \ + for (;_start <= _end; _start++) { \ + memset(fptmp.field, 0, sizeof(fptmp.field)); \ + fptmp.fp_os.fp_enflags |= PF_OSFP_EXPANDED; \ + if (_dot == -1) \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d", _start); \ + else \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d.%d", _dot, _start); \ + add_fingerprint(dev, opts, &fptmp); \ + } \ +} while(0) + + /* We allow "#-#" as a version or subtype and we'll expand it */ + EXPAND(fp_os.fp_version_nm); + EXPAND(fp_os.fp_subtype_nm); + + if (strcasecmp(fp->fp_os.fp_class_nm, "nomatch") == 0) + errx(1, "fingerprint class \"nomatch\" is reserved"); + + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) + nm_class->nm_num = ++class_count; + class = nm_class->nm_num; + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) + nm_version->nm_num = ++nm_class->nm_sublist_num; + version = nm_version->nm_num; + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) + nm_subtype->nm_num = + ++nm_version->nm_sublist_num; + subtype = nm_subtype->nm_num; + } + } + + + DEBUG(fp, "\tsignature %d:%d:%d %s", class, version, subtype, + print_ioctl(fp)); + + PF_OSFP_PACK(fp->fp_os.fp_os, class, version, subtype); + fingerprint_count++; + +#ifdef FAKE_PF_KERNEL + /* Linked to the sys/net/pf/pf_osfp.c. Call pf_osfp_add() */ + if ((errno = pf_osfp_add(fp))) +#else + if ((opts & PF_OPT_NOACTION) == 0 && ioctl(dev, DIOCOSFPADD, fp)) +#endif /* FAKE_PF_KERNEL */ + { + if (errno == EEXIST) { + warn("Duplicate signature for %s %s %s", + fp->fp_os.fp_class_nm, + fp->fp_os.fp_version_nm, + fp->fp_os.fp_subtype_nm); + + } else { + err(1, "DIOCOSFPADD"); + } + } +} + +/* import a fingerprint from the kernel */ +void +import_fingerprint(struct pf_osfp_ioctl *fp) +{ + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + + PF_OSFP_UNPACK(fp->fp_os.fp_os, class, version, subtype); + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) { + nm_class->nm_num = class; + class_count = MAX(class_count, class); + } + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) { + nm_version->nm_num = version; + nm_class->nm_sublist_num = MAX(nm_class->nm_sublist_num, + version); + } + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) { + nm_subtype->nm_num = subtype; + nm_version->nm_sublist_num = + MAX(nm_version->nm_sublist_num, subtype); + } + } + } + + + fingerprint_count++; + DEBUG(fp, "import signature %d:%d:%d", class, version, subtype); +} + +/* Find an entry for a fingerprints class/version/subtype */ +struct name_entry * +fingerprint_name_entry(struct name_list *list, char *name) +{ + struct name_entry *nm_entry; + + if (name == NULL || strlen(name) == 0) + return (NULL); + + LIST_FOREACH(nm_entry, list, nm_entry) { + if (strcasecmp(nm_entry->nm_name, name) == 0) { + /* We'll move this to the front of the list later */ + LIST_REMOVE(nm_entry, nm_entry); + break; + } + } + if (nm_entry == NULL) { + nm_entry = calloc(1, sizeof(*nm_entry)); + if (nm_entry == NULL) + err(1, "calloc"); + LIST_INIT(&nm_entry->nm_sublist); + strlcpy(nm_entry->nm_name, name, + sizeof(nm_entry->nm_name)); + } + LIST_INSERT_HEAD(list, nm_entry, nm_entry); + return (nm_entry); +} + + +void +print_name_list(int opts, struct name_list *nml, const char *prefix) +{ + char newprefix[32]; + struct name_entry *nm; + + LIST_FOREACH(nm, nml, nm_entry) { + snprintf(newprefix, sizeof(newprefix), "%s%s\t", prefix, + nm->nm_name); + printf("%s\n", newprefix); + print_name_list(opts, &nm->nm_sublist, newprefix); + } +} + +void +sort_name_list(int opts, struct name_list *nml) +{ + struct name_list new; + struct name_entry *nm, *nmsearch, *nmlast; + + /* yes yes, it's a very slow sort. so sue me */ + + LIST_INIT(&new); + + while ((nm = LIST_FIRST(nml)) != NULL) { + LIST_REMOVE(nm, nm_entry); + nmlast = NULL; + LIST_FOREACH(nmsearch, &new, nm_entry) { + if (strcasecmp(nmsearch->nm_name, nm->nm_name) > 0) { + LIST_INSERT_BEFORE(nmsearch, nm, nm_entry); + break; + } + nmlast = nmsearch; + } + if (nmsearch == NULL) { + if (nmlast) + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + else + LIST_INSERT_HEAD(&new, nm, nm_entry); + } + + sort_name_list(opts, &nm->nm_sublist); + } + nmlast = NULL; + while ((nm = LIST_FIRST(&new)) != NULL) { + LIST_REMOVE(nm, nm_entry); + if (nmlast == NULL) + LIST_INSERT_HEAD(nml, nm, nm_entry); + else + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + nmlast = nm; + } + return; +} + +/* parse the next integer in a formatted config file line */ +int +get_int(char **line, size_t *len, int *var, int *mod, + const char *name, int flags, int max, const char *filename, int lineno) +{ + int fieldlen, i; + char *field; + long val = 0; + + if (mod) + *mod = 0; + *var = 0; + + field = get_field(line, len, &fieldlen); + if (field == NULL) + return (1); + if (fieldlen == 0) { + fprintf(stderr, "%s:%d empty %s\n", filename, lineno, name); + return (1); + } + + i = 0; + if ((*field == '%' || *field == 'S' || *field == 'T' || *field == '*') + && fieldlen >= 1) { + switch (*field) { + case 'S': + if (mod && (flags & T_MSS)) + *mod = T_MSS; + if (fieldlen == 1) + return (0); + break; + case 'T': + if (mod && (flags & T_MTU)) + *mod = T_MTU; + if (fieldlen == 1) + return (0); + break; + case '*': + if (fieldlen != 1) { + fprintf(stderr, "%s:%d long '%c' %s\n", + filename, lineno, *field, name); + return (1); + } + if (mod && (flags & T_DC)) { + *mod = T_DC; + return (0); + } + case '%': + if (mod && (flags & T_MOD)) + *mod = T_MOD; + if (fieldlen == 1) { + fprintf(stderr, "%s:%d modulus %s must have a " + "value\n", filename, lineno, name); + return (1); + } + break; + } + if (mod == NULL || *mod == 0) { + fprintf(stderr, "%s:%d does not allow %c' %s\n", + filename, lineno, *field, name); + return (1); + } + i++; + } + + for (; i < fieldlen; i++) { + if (field[i] < '0' || field[i] > '9') { + fprintf(stderr, "%s:%d non-digit character in %s\n", + filename, lineno, name); + return (1); + } + val = val * 10 + field[i] - '0'; + if (val < 0) { + fprintf(stderr, "%s:%d %s overflowed\n", filename, + lineno, name); + return (1); + } + } + + if (val > max) { + fprintf(stderr, "%s:%d %s value %ld > %d\n", filename, lineno, + name, val, max); + return (1); + } + *var = (int)val; + + return (0); +} + +/* parse the next string in a formatted config file line */ +int +get_str(char **line, size_t *len, char **v, const char *name, int minlen, + const char *filename, int lineno) +{ + int fieldlen; + char *ptr; + + ptr = get_field(line, len, &fieldlen); + if (ptr == NULL) + return (1); + if (fieldlen < minlen) { + fprintf(stderr, "%s:%d too short %s\n", filename, lineno, name); + return (1); + } + if ((*v = malloc(fieldlen + 1)) == NULL) { + perror("malloc()"); + return (1); + } + memcpy(*v, ptr, fieldlen); + (*v)[fieldlen] = '\0'; + + return (0); +} + +/* Parse out the TCP opts */ +int +get_tcpopts(const char *filename, int lineno, const char *tcpopts, + pf_tcpopts_t *packed, int *optcnt, int *mss, int *mss_mod, int *wscale, + int *wscale_mod, int *ts0) +{ + int i, opt; + + *packed = 0; + *optcnt = 0; + *wscale = 0; + *wscale_mod = T_DC; + *mss = 0; + *mss_mod = T_DC; + *ts0 = 0; + if (strcmp(tcpopts, ".") == 0) + return (0); + + for (i = 0; tcpopts[i] && (size_t)*optcnt < PF_OSFP_MAX_OPTS;) { + switch ((opt = toupper(tcpopts[i++]))) { + case 'N': /* FALLTHROUGH */ + case 'S': + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'N' ? PF_OSFP_TCPOPT_NOP : + PF_OSFP_TCPOPT_SACK); + break; + case 'W': /* FALLTHROUGH */ + case 'M': { + int *this_mod, *this; + + if (opt == 'W') { + this = wscale; + this_mod = wscale_mod; + } else { + this = mss; + this_mod = mss_mod; + } + *this = 0; + *this_mod = 0; + + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'W' ? PF_OSFP_TCPOPT_WSCALE : + PF_OSFP_TCPOPT_MSS); + if (tcpopts[i] == '*' && (tcpopts[i + 1] == '\0' || + tcpopts[i + 1] == ',')) { + *this_mod = T_DC; + i++; + break; + } + + if (tcpopts[i] == '%') { + *this_mod = T_MOD; + i++; + } + do { + if (!isdigit(tcpopts[i])) { + fprintf(stderr, "%s:%d unknown " + "character '%c' in %c TCP opt\n", + filename, lineno, tcpopts[i], opt); + return (1); + } + *this = (*this * 10) + tcpopts[i++] - '0'; + } while(tcpopts[i] != ',' && tcpopts[i] != '\0'); + break; + } + case 'T': + if (tcpopts[i] == '0') { + *ts0 = 1; + i++; + } + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + PF_OSFP_TCPOPT_TS; + break; + } + (*optcnt) ++; + if (tcpopts[i] == '\0') + break; + if (tcpopts[i] != ',') { + fprintf(stderr, "%s:%d unknown option to %c TCP opt\n", + filename, lineno, opt); + return (1); + } + i++; + } + + return (0); +} + +/* rip the next field ouf of a formatted config file line */ +char * +get_field(char **line, size_t *len, int *fieldlen) +{ + char *ret, *ptr = *line; + size_t plen = *len; + + + while (plen && isspace(*ptr)) { + plen--; + ptr++; + } + ret = ptr; + *fieldlen = 0; + + for (; plen > 0 && *ptr != ':'; plen--, ptr++) + (*fieldlen)++; + if (plen) { + *line = ptr + 1; + *len = plen - 1; + } else { + *len = 0; + } + while (*fieldlen && isspace(ret[*fieldlen - 1])) + (*fieldlen)--; + return (ret); +} + + +const char * +print_ioctl(struct pf_osfp_ioctl *fp) +{ + static char buf[1024]; + char tmp[32]; + int i, opt; + + *buf = '\0'; + if (fp->fp_flags & PF_OSFP_WSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MSS) + strlcat(buf, "S", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MTU) + strlcat(buf, "T", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wsize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "%d", fp->fp_ttl); + strlcat(buf, tmp, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_DF) + strlcat(buf, "1", sizeof(buf)); + else + strlcat(buf, "0", sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_PSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_PSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_psize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_optcnt == 0) + strlcat(buf, ".", sizeof(buf)); + for (i = fp->fp_optcnt - 1; i >= 0; i--) { + opt = fp->fp_tcpopts >> (i * PF_OSFP_TCPOPT_BITS); + opt &= (1 << PF_OSFP_TCPOPT_BITS) - 1; + switch (opt) { + case PF_OSFP_TCPOPT_NOP: + strlcat(buf, "N", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_SACK: + strlcat(buf, "S", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_TS: + strlcat(buf, "T", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_TS0) + strlcat(buf, "0", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_MSS: + strlcat(buf, "M", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_MSS_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_MSS_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_mss); + strlcat(buf, tmp, sizeof(buf)); + } + break; + case PF_OSFP_TCPOPT_WSCALE: + strlcat(buf, "W", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_WSCALE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSCALE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wscale); + strlcat(buf, tmp, sizeof(buf)); + } + break; + } + + if (i != 0) + strlcat(buf, ",", sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + strlcat(buf, fp->fp_os.fp_class_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_version_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_subtype_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "TcpOpts %d 0x%llx", fp->fp_optcnt, + (long long int)fp->fp_tcpopts); + strlcat(buf, tmp, sizeof(buf)); + + return (buf); +} diff --git a/usr.sbin/pfctl/pfctl_parser.c b/usr.sbin/pfctl/pfctl_parser.c new file mode 100644 index 0000000000..607f48f608 --- /dev/null +++ b/usr.sbin/pfctl/pfctl_parser.c @@ -0,0 +1,1597 @@ +/* $OpenBSD: pfctl_parser.c,v 1.194.2.1 2004/05/05 04:00:50 brad Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl_parser.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * 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: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +void print_op (u_int8_t, const char *, const char *); +void print_port (u_int8_t, u_int16_t, u_int16_t, const char *); +void print_ugid (u_int8_t, unsigned, unsigned, const char *, unsigned); +void print_flags (u_int8_t); +void print_fromto(struct pf_rule_addr *, pf_osfp_t, + struct pf_rule_addr *, u_int8_t, u_int8_t, int); +int ifa_skip_if(const char *filter, struct node_host *p); + +struct node_host *host_if(const char *, int); +static struct node_host *host_v4(const char *); +struct node_host *host_v6(const char *, int); +struct node_host *host_dns(const char *, int, int); + +const char *tcpflags = "FSRPAUEW"; + +static const struct icmptypeent icmp_type[] = { + { "echoreq", ICMP_ECHO }, + { "echorep", ICMP_ECHOREPLY }, + { "unreach", ICMP_UNREACH }, + { "squench", ICMP_SOURCEQUENCH }, + { "redir", ICMP_REDIRECT }, + { "althost", ICMP_ALTHOSTADDR }, + { "routeradv", ICMP_ROUTERADVERT }, + { "routersol", ICMP_ROUTERSOLICIT }, + { "timex", ICMP_TIMXCEED }, + { "paramprob", ICMP_PARAMPROB }, + { "timereq", ICMP_TSTAMP }, + { "timerep", ICMP_TSTAMPREPLY }, + { "inforeq", ICMP_IREQ }, + { "inforep", ICMP_IREQREPLY }, + { "maskreq", ICMP_MASKREQ }, + { "maskrep", ICMP_MASKREPLY }, + { "trace", ICMP_TRACEROUTE }, + { "dataconv", ICMP_DATACONVERR }, + { "mobredir", ICMP_MOBILE_REDIRECT }, + { "ipv6-where", ICMP_IPV6_WHEREAREYOU }, + { "ipv6-here", ICMP_IPV6_IAMHERE }, + { "mobregreq", ICMP_MOBILE_REGREQUEST }, + { "mobregrep", ICMP_MOBILE_REGREPLY }, + { "skip", ICMP_SKIP }, + { "photuris", ICMP_PHOTURIS } +}; + +static const struct icmptypeent icmp6_type[] = { + { "unreach", ICMP6_DST_UNREACH }, + { "toobig", ICMP6_PACKET_TOO_BIG }, + { "timex", ICMP6_TIME_EXCEEDED }, + { "paramprob", ICMP6_PARAM_PROB }, + { "echoreq", ICMP6_ECHO_REQUEST }, + { "echorep", ICMP6_ECHO_REPLY }, + { "groupqry", ICMP6_MEMBERSHIP_QUERY }, + { "listqry", MLD_LISTENER_QUERY }, + { "grouprep", ICMP6_MEMBERSHIP_REPORT }, + { "listenrep", MLD_LISTENER_REPORT }, + { "groupterm", ICMP6_MEMBERSHIP_REDUCTION }, + { "listendone", MLD_LISTENER_DONE }, + { "routersol", ND_ROUTER_SOLICIT }, + { "routeradv", ND_ROUTER_ADVERT }, + { "neighbrsol", ND_NEIGHBOR_SOLICIT }, + { "neighbradv", ND_NEIGHBOR_ADVERT }, + { "redir", ND_REDIRECT }, + { "routrrenum", ICMP6_ROUTER_RENUMBERING }, + { "wrureq", ICMP6_WRUREQUEST }, + { "wrurep", ICMP6_WRUREPLY }, + { "fqdnreq", ICMP6_FQDN_QUERY }, + { "fqdnrep", ICMP6_FQDN_REPLY }, + { "niqry", ICMP6_NI_QUERY }, + { "nirep", ICMP6_NI_REPLY }, + { "mtraceresp", MLD_MTRACE_RESP }, + { "mtrace", MLD_MTRACE } +}; + +static const struct icmpcodeent icmp_code[] = { + { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET }, + { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST }, + { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL }, + { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT }, + { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG }, + { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL }, + { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN }, + { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN }, + { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED }, + { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB }, + { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB }, + { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET }, + { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST }, + { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB }, + { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE }, + { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF }, + { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET }, + { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST }, + { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET }, + { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST }, + { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL }, + { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON }, + { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS }, + { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS }, + { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR }, + { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT }, + { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH }, + { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX }, + { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED }, + { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED } +}; + +static const struct icmpcodeent icmp6_code[] = { + { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN }, + { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE }, + { "notnbr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOTNEIGHBOR }, + { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE }, + { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR }, + { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT }, + { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT }, + { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY }, + { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER }, + { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER }, + { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK }, + { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER } +}; + +const struct pf_timeout pf_timeouts[] = { + { "tcp.first", PFTM_TCP_FIRST_PACKET }, + { "tcp.opening", PFTM_TCP_OPENING }, + { "tcp.established", PFTM_TCP_ESTABLISHED }, + { "tcp.closing", PFTM_TCP_CLOSING }, + { "tcp.finwait", PFTM_TCP_FIN_WAIT }, + { "tcp.closed", PFTM_TCP_CLOSED }, + { "udp.first", PFTM_UDP_FIRST_PACKET }, + { "udp.single", PFTM_UDP_SINGLE }, + { "udp.multiple", PFTM_UDP_MULTIPLE }, + { "icmp.first", PFTM_ICMP_FIRST_PACKET }, + { "icmp.error", PFTM_ICMP_ERROR_REPLY }, + { "other.first", PFTM_OTHER_FIRST_PACKET }, + { "other.single", PFTM_OTHER_SINGLE }, + { "other.multiple", PFTM_OTHER_MULTIPLE }, + { "frag", PFTM_FRAG }, + { "interval", PFTM_INTERVAL }, + { "adaptive.start", PFTM_ADAPTIVE_START }, + { "adaptive.end", PFTM_ADAPTIVE_END }, + { "src.track", PFTM_SRC_NODE }, + { NULL, 0 } +}; + +const struct icmptypeent * +geticmptypebynumber(u_int8_t type, sa_family_t af) +{ + unsigned int i; + + if (af != AF_INET6) { + for (i=0; i < (sizeof (icmp_type) / sizeof(icmp_type[0])); + i++) { + if (type == icmp_type[i].type) + return (&icmp_type[i]); + } + } else { + for (i=0; i < (sizeof (icmp6_type) / + sizeof(icmp6_type[0])); i++) { + if (type == icmp6_type[i].type) + return (&icmp6_type[i]); + } + } + return (NULL); +} + +const struct icmptypeent * +geticmptypebyname(char *w, sa_family_t af) +{ + unsigned int i; + + if (af != AF_INET6) { + for (i=0; i < (sizeof (icmp_type) / sizeof(icmp_type[0])); + i++) { + if (!strcmp(w, icmp_type[i].name)) + return (&icmp_type[i]); + } + } else { + for (i=0; i < (sizeof (icmp6_type) / + sizeof(icmp6_type[0])); i++) { + if (!strcmp(w, icmp6_type[i].name)) + return (&icmp6_type[i]); + } + } + return (NULL); +} + +const struct icmpcodeent * +geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af) +{ + unsigned int i; + + if (af != AF_INET6) { + for (i=0; i < (sizeof (icmp_code) / sizeof(icmp_code[0])); + i++) { + if (type == icmp_code[i].type && + code == icmp_code[i].code) + return (&icmp_code[i]); + } + } else { + for (i=0; i < (sizeof (icmp6_code) / + sizeof(icmp6_code[0])); i++) { + if (type == icmp6_code[i].type && + code == icmp6_code[i].code) + return (&icmp6_code[i]); + } + } + return (NULL); +} + +const struct icmpcodeent * +geticmpcodebyname(u_long type, char *w, sa_family_t af) +{ + unsigned int i; + + if (af != AF_INET6) { + for (i=0; i < (sizeof (icmp_code) / sizeof(icmp_code[0])); + i++) { + if (type == icmp_code[i].type && + !strcmp(w, icmp_code[i].name)) + return (&icmp_code[i]); + } + } else { + for (i=0; i < (sizeof (icmp6_code) / + sizeof(icmp6_code[0])); i++) { + if (type == icmp6_code[i].type && + !strcmp(w, icmp6_code[i].name)) + return (&icmp6_code[i]); + } + } + return (NULL); +} + +void +print_op(u_int8_t op, const char *a1, const char *a2) +{ + if (op == PF_OP_IRG) + printf(" %s >< %s", a1, a2); + else if (op == PF_OP_XRG) + printf(" %s <> %s", a1, a2); + else if (op == PF_OP_EQ) + printf(" = %s", a1); + else if (op == PF_OP_NE) + printf(" != %s", a1); + else if (op == PF_OP_LT) + printf(" < %s", a1); + else if (op == PF_OP_LE) + printf(" <= %s", a1); + else if (op == PF_OP_GT) + printf(" > %s", a1); + else if (op == PF_OP_GE) + printf(" >= %s", a1); + else if (op == PF_OP_RRG) + printf(" %s:%s", a1, a2); +} + +void +print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto) +{ + char a1[6], a2[6]; + struct servent *s; + + s = getservbyport(p1, proto); + p1 = ntohs(p1); + p2 = ntohs(p2); + snprintf(a1, sizeof(a1), "%u", p1); + snprintf(a2, sizeof(a2), "%u", p2); + printf(" port"); + if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE)) + print_op(op, s->s_name, a2); + else + print_op(op, a1, a2); +} + +void +print_ugid(u_int8_t op, unsigned u1, unsigned u2, const char *t, unsigned umax) +{ + char a1[11], a2[11]; + + snprintf(a1, sizeof(a1), "%u", u1); + snprintf(a2, sizeof(a2), "%u", u2); + printf(" %s", t); + if (u1 == umax && (op == PF_OP_EQ || op == PF_OP_NE)) + print_op(op, "unknown", a2); + else + print_op(op, a1, a2); +} + +void +print_flags(u_int8_t f) +{ + int i; + + for (i = 0; tcpflags[i]; ++i) + if (f & (1 << i)) + printf("%c", tcpflags[i]); +} + +void +print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst, + sa_family_t af, u_int8_t proto, int verbose) +{ + char buf[PF_OSFP_LEN*3]; + if (src->addr.type == PF_ADDR_ADDRMASK && + dst->addr.type == PF_ADDR_ADDRMASK && + PF_AZERO(&src->addr.v.a.addr, AF_INET6) && + PF_AZERO(&src->addr.v.a.mask, AF_INET6) && + PF_AZERO(&dst->addr.v.a.addr, AF_INET6) && + PF_AZERO(&dst->addr.v.a.mask, AF_INET6) && + !src->not && !dst->not && + !src->port_op && !dst->port_op && + osfp == PF_OSFP_ANY) + printf(" all"); + else { + printf(" from "); + if (src->not) + printf("! "); + print_addr(&src->addr, af, verbose); + if (src->port_op) + print_port(src->port_op, src->port[0], + src->port[1], + proto == IPPROTO_TCP ? "tcp" : "udp"); + if (osfp != PF_OSFP_ANY) + printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf, + sizeof(buf))); + + printf(" to "); + if (dst->not) + printf("! "); + print_addr(&dst->addr, af, verbose); + if (dst->port_op) + print_port(dst->port_op, dst->port[0], + dst->port[1], + proto == IPPROTO_TCP ? "tcp" : "udp"); + } +} + +void +print_pool(struct pf_pool *pool, u_int16_t p1, u_int16_t p2, + sa_family_t af, int id) +{ + struct pf_pooladdr *pooladdr; + + if ((TAILQ_FIRST(&pool->list) != NULL) && + TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) + printf("{ "); + TAILQ_FOREACH(pooladdr, &pool->list, entries){ + switch (id) { + case PF_NAT: + case PF_RDR: + case PF_BINAT: + print_addr(&pooladdr->addr, af, 0); + break; + case PF_PASS: + if (PF_AZERO(&pooladdr->addr.v.a.addr, af)) + printf("%s", pooladdr->ifname); + else { + printf("(%s ", pooladdr->ifname); + print_addr(&pooladdr->addr, af, 0); + printf(")"); + } + break; + default: + break; + } + if (TAILQ_NEXT(pooladdr, entries) != NULL) + printf(", "); + else if (TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) + printf(" }"); + } + switch (id) { + case PF_NAT: + if ((p1 != PF_NAT_PROXY_PORT_LOW || + p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) { + if (p1 == p2) + printf(" port %u", p1); + else + printf(" port %u:%u", p1, p2); + } + break; + case PF_RDR: + if (p1) { + printf(" port %u", p1); + if (p2 && (p2 != p1)) + printf(":%u", p2); + } + break; + default: + break; + } + switch (pool->opts & PF_POOL_TYPEMASK) { + case PF_POOL_NONE: + break; + case PF_POOL_BITMASK: + printf(" bitmask"); + break; + case PF_POOL_RANDOM: + printf(" random"); + break; + case PF_POOL_SRCHASH: + printf(" source-hash 0x%08x%08x%08x%08x", + pool->key.key32[0], pool->key.key32[1], + pool->key.key32[2], pool->key.key32[3]); + break; + case PF_POOL_ROUNDROBIN: + printf(" round-robin"); + break; + } + if (pool->opts & PF_POOL_STICKYADDR) + printf(" sticky-address"); + if (id == PF_NAT && p1 == 0 && p2 == 0) + printf(" static-port"); +} + +const char *pf_reasons[PFRES_MAX+1] = PFRES_NAMES; +const char *pf_fcounters[FCNT_MAX+1] = FCNT_NAMES; +const char *pf_scounters[FCNT_MAX+1] = FCNT_NAMES; + +void +print_status(struct pf_status *s, int opts) +{ + char statline[80]; + const char *running; + time_t runtime; + int i; + + runtime = time(NULL) - s->since; + running = s->running ? "Enabled" : "Disabled"; + + if (s->since) { + unsigned sec, min, hrs, day = runtime; + + sec = day % 60; + day /= 60; + min = day % 60; + day /= 60; + hrs = day % 24; + day /= 24; + snprintf(statline, sizeof(statline), + "Status: %s for %u days %.2u:%.2u:%.2u", + running, day, hrs, min, sec); + } else + snprintf(statline, sizeof(statline), "Status: %s", running); + printf("%-44s", statline); + switch (s->debug) { + case PF_DEBUG_NONE: + printf("%15s\n\n", "Debug: None"); + break; + case PF_DEBUG_URGENT: + printf("%15s\n\n", "Debug: Urgent"); + break; + case PF_DEBUG_MISC: + printf("%15s\n\n", "Debug: Misc"); + break; + case PF_DEBUG_NOISY: + printf("%15s\n\n", "Debug: Loud"); + break; + } + printf("Hostid: 0x%08x\n\n", ntohl(s->hostid)); + if (s->ifname[0] != 0) { + printf("Interface Stats for %-16s %5s %16s\n", + s->ifname, "IPv4", "IPv6"); + printf(" %-25s %14llu %16llu\n", "Bytes In", + (unsigned long long)s->bcounters[0][0], + (unsigned long long)s->bcounters[1][0]); + printf(" %-25s %14llu %16llu\n", "Bytes Out", + (unsigned long long)s->bcounters[0][1], + (unsigned long long)s->bcounters[1][1]); + printf(" Packets In\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + (unsigned long long)s->pcounters[0][0][PF_PASS], + (unsigned long long)s->pcounters[1][0][PF_PASS]); + printf(" %-23s %14llu %16llu\n", "Blocked", + (unsigned long long)s->pcounters[0][0][PF_DROP], + (unsigned long long)s->pcounters[1][0][PF_DROP]); + printf(" Packets Out\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + (unsigned long long)s->pcounters[0][1][PF_PASS], + (unsigned long long)s->pcounters[1][1][PF_PASS]); + printf(" %-23s %14llu %16llu\n\n", "Blocked", + (unsigned long long)s->pcounters[0][1][PF_DROP], + (unsigned long long)s->pcounters[1][1][PF_DROP]); + } + printf("%-27s %14s %16s\n", "State Table", "Total", "Rate"); + printf(" %-25s %14u %14s\n", "current entries", s->states, ""); + for (i = 0; i < FCNT_MAX; i++) { + printf(" %-25s %14llu ", pf_fcounters[i], + (unsigned long long)s->fcounters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->fcounters[i] / (double)runtime); + else + printf("%14s\n", ""); + } + if (opts & PF_OPT_VERBOSE) { + printf("Source Tracking Table\n"); + printf(" %-25s %14u %14s\n", "current entries", + s->src_nodes, ""); + for (i = 0; i < SCNT_MAX; i++) { + printf(" %-25s %14lld ", pf_scounters[i], + s->scounters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->scounters[i] / (double)runtime); + else + printf("%14s\n", ""); + } + } + printf("Counters\n"); + for (i = 0; i < PFRES_MAX; i++) { + printf(" %-25s %14llu ", pf_reasons[i], + (unsigned long long)s->counters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->counters[i] / (double)runtime); + else + printf("%14s\n", ""); + } +} + +void +print_src_node(struct pf_src_node *sn, int opts) +{ + struct pf_addr_wrap aw; + int min, sec; + + memset(&aw, 0, sizeof(aw)); + if (sn->af == AF_INET) + aw.v.a.mask.addr32[0] = 0xffffffff; + else + memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); + + aw.v.a.addr = sn->addr; + print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2); + printf(" -> "); + aw.v.a.addr = sn->raddr; + print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2); + printf(" (%d states)\n", sn->states); + if (opts & PF_OPT_VERBOSE) { + sec = sn->creation % 60; + sn->creation /= 60; + min = sn->creation % 60; + sn->creation /= 60; + printf(" age %.2u:%.2u:%.2u", sn->creation, min, sec); + if (sn->states == 0) { + sec = sn->expire % 60; + sn->expire /= 60; + min = sn->expire % 60; + sn->expire /= 60; + printf(", expires in %.2u:%.2u:%.2u", + sn->expire, min, sec); + } + printf(", %u pkts, %u bytes", sn->packets, sn->bytes); + switch (sn->ruletype) { + case PF_NAT: + if (sn->rule.nr != (uint32_t)(-1)) + printf(", nat rule %u", sn->rule.nr); + break; + case PF_RDR: + if (sn->rule.nr != (uint32_t)(-1)) + printf(", rdr rule %u", sn->rule.nr); + break; + case PF_PASS: + if (sn->rule.nr != (uint32_t)(-1)) + printf(", filter rule %u", sn->rule.nr); + break; + } + printf("\n"); + } +} + +void +print_rule(struct pf_rule *r, int verbose) +{ + static const char *actiontypes[] = { "pass", "block", "scrub", "nat", + "no nat", "binat", "no binat", "rdr", "no rdr" }; + static const char *anchortypes[] = { "anchor", "anchor", "anchor", + "nat-anchor", "nat-anchor", "binat-anchor", "binat-anchor", + "rdr-anchor", "rdr-anchor" }; + int i, opts; + + if (verbose) + printf("@%d ", r->nr); + if (r->action > PF_NORDR) + printf("action(%d)", r->action); + else if (r->anchorname[0]) + printf("%s %s", anchortypes[r->action], r->anchorname); + else { + printf("%s", actiontypes[r->action]); + if (r->natpass) + printf(" pass"); + } + if (r->action == PF_DROP) { + if (r->rule_flag & PFRULE_RETURN) + printf(" return"); + else if (r->rule_flag & PFRULE_RETURNRST) { + if (!r->return_ttl) + printf(" return-rst"); + else + printf(" return-rst(ttl %d)", r->return_ttl); + } else if (r->rule_flag & PFRULE_RETURNICMP) { + const struct icmpcodeent *ic, *ic6; + + ic = geticmpcodebynumber(r->return_icmp >> 8, + r->return_icmp & 255, AF_INET); + ic6 = geticmpcodebynumber(r->return_icmp6 >> 8, + r->return_icmp6 & 255, AF_INET6); + + switch (r->af) { + case AF_INET: + printf(" return-icmp"); + if (ic == NULL) + printf("(%u)", r->return_icmp & 255); + else + printf("(%s)", ic->name); + break; + case AF_INET6: + printf(" return-icmp6"); + if (ic6 == NULL) + printf("(%u)", r->return_icmp6 & 255); + else + printf("(%s)", ic6->name); + break; + default: + printf(" return-icmp"); + if (ic == NULL) + printf("(%u, ", r->return_icmp & 255); + else + printf("(%s, ", ic->name); + if (ic6 == NULL) + printf("%u)", r->return_icmp6 & 255); + else + printf("%s)", ic6->name); + break; + } + } else + printf(" drop"); + } + if (r->direction == PF_IN) + printf(" in"); + else if (r->direction == PF_OUT) + printf(" out"); + if (r->log == 1) + printf(" log"); + else if (r->log == 2) + printf(" log-all"); + if (r->quick) + printf(" quick"); + if (r->ifname[0]) { + if (r->ifnot) + printf(" on ! %s", r->ifname); + else + printf(" on %s", r->ifname); + } + if (r->rt) { + if (r->rt == PF_ROUTETO) + printf(" route-to"); + else if (r->rt == PF_REPLYTO) + printf(" reply-to"); + else if (r->rt == PF_DUPTO) + printf(" dup-to"); + else if (r->rt == PF_FASTROUTE) + printf(" fastroute"); + if (r->rt != PF_FASTROUTE) { + printf(" "); + print_pool(&r->rpool, 0, 0, r->af, PF_PASS); + } + } + if (r->af) { + if (r->af == AF_INET) + printf(" inet"); + else + printf(" inet6"); + } + if (r->proto) { + struct protoent *p; + + if ((p = getprotobynumber(r->proto)) != NULL) + printf(" proto %s", p->p_name); + else + printf(" proto %u", r->proto); + } + print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto, + verbose); + if (r->uid.op) + print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user", + UID_MAX); + if (r->gid.op) + print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group", + GID_MAX); + if (r->flags || r->flagset) { + printf(" flags "); + print_flags(r->flags); + printf("/"); + print_flags(r->flagset); + } + if (r->type) { + const struct icmptypeent *it; + + it = geticmptypebynumber(r->type-1, r->af); + if (r->af != AF_INET6) + printf(" icmp-type"); + else + printf(" icmp6-type"); + if (it != NULL) + printf(" %s", it->name); + else + printf(" %u", r->type-1); + if (r->code) { + const struct icmpcodeent *ic; + + ic = geticmpcodebynumber(r->type-1, r->code-1, r->af); + if (ic != NULL) + printf(" code %s", ic->name); + else + printf(" code %u", r->code-1); + } + } + if (r->tos) + printf(" tos 0x%2.2x", r->tos); + if (r->keep_state == PF_STATE_NORMAL) + printf(" keep state"); + else if (r->keep_state == PF_STATE_MODULATE) + printf(" modulate state"); + else if (r->keep_state == PF_STATE_SYNPROXY) + printf(" synproxy state"); + opts = 0; + if (r->max_states || r->max_src_nodes || r->max_src_states) + opts = 1; + if (r->rule_flag & PFRULE_NOSYNC) + opts = 1; + if (r->rule_flag & PFRULE_SRCTRACK) + opts = 1; + if (r->rule_flag & (PFRULE_IFBOUND | PFRULE_GRBOUND)) + opts = 1; + for (i = 0; !opts && i < PFTM_MAX; ++i) + if (r->timeout[i]) + opts = 1; + if (opts) { + printf(" ("); + if (r->max_states) { + printf("max %u", r->max_states); + opts = 0; + } + if (r->rule_flag & PFRULE_NOSYNC) { + if (!opts) + printf(", "); + printf("no-sync"); + opts = 0; + } + if (r->rule_flag & PFRULE_SRCTRACK) { + if (!opts) + printf(", "); + printf("source-track"); + if (r->rule_flag & PFRULE_RULESRCTRACK) + printf(" rule"); + else + printf(" global"); + opts = 0; + } + if (r->max_src_states) { + if (!opts) + printf(", "); + printf("max-src-states %u", r->max_src_states); + opts = 0; + } + if (r->max_src_nodes) { + if (!opts) + printf(", "); + printf("max-src-nodes %u", r->max_src_nodes); + opts = 0; + } + if (r->rule_flag & PFRULE_IFBOUND) { + if (!opts) + printf(", "); + printf("if-bound"); + opts = 0; + } + if (r->rule_flag & PFRULE_GRBOUND) { + if (!opts) + printf(", "); + printf("group-bound"); + opts = 0; + } + for (i = 0; i < PFTM_MAX; ++i) + if (r->timeout[i]) { + if (!opts) + printf(", "); + opts = 0; + printf("%s %u", pf_timeouts[i].name, + r->timeout[i]); + } + printf(")"); + } + if (r->rule_flag & PFRULE_FRAGMENT) + printf(" fragment"); + if (r->rule_flag & PFRULE_NODF) + printf(" no-df"); + if (r->rule_flag & PFRULE_RANDOMID) + printf(" random-id"); + if (r->min_ttl) + printf(" min-ttl %d", r->min_ttl); + if (r->max_mss) + printf(" max-mss %d", r->max_mss); + if (r->allow_opts) + printf(" allow-opts"); + if (r->action == PF_SCRUB) { + if (r->rule_flag & PFRULE_REASSEMBLE_TCP) + printf(" reassemble tcp"); + + if (r->rule_flag & PFRULE_FRAGDROP) + printf(" fragment drop-ovl"); + else if (r->rule_flag & PFRULE_FRAGCROP) + printf(" fragment crop"); + else + printf(" fragment reassemble"); + } + if (r->label[0]) + printf(" label \"%s\"", r->label); + if (r->qname[0] && r->pqname[0]) + printf(" queue(%s, %s)", r->qname, r->pqname); + else if (r->qname[0]) + printf(" queue %s", r->qname); + if (r->tagname[0]) + printf(" tag %s", r->tagname); + if (r->match_tagname[0]) { + if (r->match_tag_not) + printf(" !"); + printf(" tagged %s", r->match_tagname); + } + if (!r->anchorname[0] && (r->action == PF_NAT || + r->action == PF_BINAT || r->action == PF_RDR)) { + printf(" -> "); + print_pool(&r->rpool, r->rpool.proxy_port[0], + r->rpool.proxy_port[1], r->af, r->action); + } + printf("\n"); +} + +void +print_tabledef(const char *name, int flags, int addrs, + struct node_tinithead *nodes) +{ + struct node_tinit *ti, *nti; + struct node_host *h; + + printf("table <%s>", name); + if (flags & PFR_TFLAG_CONST) + printf(" const"); + if (flags & PFR_TFLAG_PERSIST) + printf(" persist"); + SIMPLEQ_FOREACH(ti, nodes, entries) { + if (ti->file) { + printf(" file \"%s\"", ti->file); + continue; + } + printf(" {"); + for (;;) { + for (h = ti->host; h != NULL; h = h->next) { + printf(h->not ? " !" : " "); + print_addr(&h->addr, h->af, 0); + } + nti = SIMPLEQ_NEXT(ti, entries); + if (nti != NULL && nti->file == NULL) + ti = nti; /* merge lists */ + else + break; + } + printf(" }"); + } + if (addrs && SIMPLEQ_EMPTY(nodes)) + printf(" { }"); + printf("\n"); +} + +int +parse_flags(char *s) +{ + char *p, *q; + u_int8_t f = 0; + + for (p = s; *p; p++) { + if ((q = strchr(tcpflags, *p)) == NULL) + return -1; + else + f |= 1 << (q - tcpflags); + } + return (f ? f : PF_TH_ALL); +} + +void +set_ipmask(struct node_host *h, u_int8_t b) +{ + struct pf_addr *m, *n; + int i, j = 0; + + m = &h->addr.v.a.mask; + + for (i = 0; i < 4; i++) + m->addr32[i] = 0; + + while (b >= 32) { + m->addr32[j++] = 0xffffffff; + b -= 32; + } + for (i = 31; i > 31-b; --i) + m->addr32[j] |= (1 << i); + if (b) + m->addr32[j] = htonl(m->addr32[j]); + + /* Mask off bits of the address that will never be used. */ + n = &h->addr.v.a.addr; + if (h->addr.type == PF_ADDR_ADDRMASK) + for (i = 0; i < 4; i++) + n->addr32[i] = n->addr32[i] & m->addr32[i]; +} + +int +check_netmask(struct node_host *h, sa_family_t af) +{ + struct node_host *n = NULL; + struct pf_addr *m; + + for (n = h; n != NULL; n = n->next) { + if (h->addr.type == PF_ADDR_TABLE) + continue; + m = &h->addr.v.a.mask; + /* fix up netmask for dynaddr */ + if (af == AF_INET && h->addr.type == PF_ADDR_DYNIFTL && + unmask(m, AF_INET6) > 32) + set_ipmask(n, 32); + /* netmasks > 32 bit are invalid on v4 */ + if (af == AF_INET && + (m->addr32[1] || m->addr32[2] || m->addr32[3])) { + fprintf(stderr, "netmask %u invalid for IPv4 address\n", + unmask(m, AF_INET6)); + return (1); + } + } + return (0); +} + +/* interface lookup routines */ + +struct node_host *iftab; + +void +ifa_load(void) +{ + struct ifaddrs *ifap, *ifa; + struct node_host *n = NULL, *h = NULL; + struct pfr_buffer b; + const struct pfi_if *p; + + if (getifaddrs(&ifap) < 0) + err(1, "getifaddrs"); + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (!(ifa->ifa_addr->sa_family == AF_INET || + ifa->ifa_addr->sa_family == AF_INET6 || + ifa->ifa_addr->sa_family == AF_LINK)) + continue; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "address: calloc"); + n->af = ifa->ifa_addr->sa_family; + n->ifa_flags = ifa->ifa_flags; +#ifdef __KAME__ + if (n->af == AF_INET6 && + IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_addr) && + ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id == + 0) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 | + sin6->sin6_addr.s6_addr[3]; + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + } +#endif + n->ifindex = 0; + if (n->af == AF_INET) { + memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *) + ifa->ifa_addr)->sin_addr.s_addr, + sizeof(struct in_addr)); + memcpy(&n->addr.v.a.mask, &((struct sockaddr_in *) + ifa->ifa_netmask)->sin_addr.s_addr, + sizeof(struct in_addr)); + if (ifa->ifa_broadaddr != NULL) + memcpy(&n->bcast, &((struct sockaddr_in *) + ifa->ifa_broadaddr)->sin_addr.s_addr, + sizeof(struct in_addr)); + if (ifa->ifa_dstaddr != NULL) + memcpy(&n->peer, &((struct sockaddr_in *) + ifa->ifa_dstaddr)->sin_addr.s_addr, + sizeof(struct in_addr)); + } else if (n->af == AF_INET6) { + memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + memcpy(&n->addr.v.a.mask, &((struct sockaddr_in6 *) + ifa->ifa_netmask)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + if (ifa->ifa_broadaddr != NULL) + memcpy(&n->bcast, &((struct sockaddr_in6 *) + ifa->ifa_broadaddr)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + if (ifa->ifa_dstaddr != NULL) + memcpy(&n->peer, &((struct sockaddr_in6 *) + ifa->ifa_dstaddr)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + n->ifindex = ((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_scope_id; + } + if ((n->ifname = strdup(ifa->ifa_name)) == NULL) + err(1, "ifa_load: strdup"); + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + + /* add interface groups, including clonable and dynamic stuff */ + bzero(&b, sizeof(b)); + b.pfrb_type = PFRB_IFACES; + for (;;) { + if (pfr_buf_grow(&b, b.pfrb_size)) + err(1, "ifa_load: pfr_buf_grow"); + b.pfrb_size = b.pfrb_msize; + if (pfi_get_ifaces(NULL, b.pfrb_caddr, &b.pfrb_size, + PFI_FLAG_GROUP)) + err(1, "ifa_load: pfi_get_ifaces"); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(p, &b) { + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "address: calloc"); + n->af = AF_LINK; + n->ifa_flags = PF_IFA_FLAG_GROUP; + if (p->pfif_flags & PFI_IFLAG_DYNAMIC) + n->ifa_flags |= PF_IFA_FLAG_DYNAMIC; + if (p->pfif_flags & PFI_IFLAG_CLONABLE) + n->ifa_flags |= PF_IFA_FLAG_CLONABLE; + if (!strcmp(p->pfif_name, "lo")) + n->ifa_flags |= IFF_LOOPBACK; + if ((n->ifname = strdup(p->pfif_name)) == NULL) + err(1, "ifa_load: strdup"); + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + + iftab = h; + freeifaddrs(ifap); +} + +struct node_host * +ifa_exists(const char *ifa_name, int group_ok) +{ + struct node_host *n; + char *p, buf[IFNAMSIZ]; + int group; + + group = !isdigit(ifa_name[strlen(ifa_name) - 1]); + if (group && !group_ok) + return (NULL); + if (iftab == NULL) + ifa_load(); + + for (n = iftab; n; n = n->next) { + if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ)) + return (n); + } + if (!group) { + /* look for clonable and/or dynamic interface */ + strlcpy(buf, ifa_name, sizeof(buf)); + for (p = buf + strlen(buf) - 1; p > buf && isdigit(*p); p--) + *p = '\0'; + for (n = iftab; n != NULL; n = n->next) + if (n->af == AF_LINK && + !strncmp(n->ifname, buf, IFNAMSIZ)) + break; + if (n != NULL && n->ifa_flags & + (PF_IFA_FLAG_DYNAMIC | PF_IFA_FLAG_CLONABLE)) + return (n); /* XXX */ + } + return (NULL); +} + +struct node_host * +ifa_lookup(const char *ifa_name, int flags) +{ + struct node_host *p = NULL, *h = NULL, *n = NULL; + int got4 = 0, got6 = 0; + const char *last_if = NULL; + + if (!strncmp(ifa_name, "self", IFNAMSIZ)) + ifa_name = NULL; + + if (iftab == NULL) + ifa_load(); + + for (p = iftab; p; p = p->next) { + if (ifa_skip_if(ifa_name, p)) + continue; + if ((flags & PFI_AFLAG_BROADCAST) && p->af != AF_INET) + continue; + if ((flags & PFI_AFLAG_BROADCAST) && + !(p->ifa_flags & IFF_BROADCAST)) + continue; + if ((flags & PFI_AFLAG_PEER) && + !(p->ifa_flags & IFF_POINTOPOINT)) + continue; + if ((flags & PFI_AFLAG_NETWORK) && p->ifindex > 0) + continue; + if (last_if == NULL || strcmp(last_if, p->ifname)) + got4 = got6 = 0; + last_if = p->ifname; + if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET && got4) + continue; + if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && got6) + continue; + if (p->af == AF_INET) + got4 = 1; + else + got6 = 1; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "address: calloc"); + n->af = p->af; + if (flags & PFI_AFLAG_BROADCAST) + memcpy(&n->addr.v.a.addr, &p->bcast, + sizeof(struct pf_addr)); + else if (flags & PFI_AFLAG_PEER) + memcpy(&n->addr.v.a.addr, &p->peer, + sizeof(struct pf_addr)); + else + memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr, + sizeof(struct pf_addr)); + if (flags & PFI_AFLAG_NETWORK) + set_ipmask(n, unmask(&p->addr.v.a.mask, n->af)); + else { + if (n->af == AF_INET) { + if (p->ifa_flags & IFF_LOOPBACK && + p->ifa_flags & IFF_LINK1) + memcpy(&n->addr.v.a.mask, + &p->addr.v.a.mask, + sizeof(struct pf_addr)); + else + set_ipmask(n, 32); + } else + set_ipmask(n, 128); + } + n->ifindex = p->ifindex; + + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + return (h); +} + +int +ifa_skip_if(const char *filter, struct node_host *p) +{ + int n; + + if (p->af != AF_INET && p->af != AF_INET6) + return (1); + if (filter == NULL || !*filter) + return (0); + if (!strcmp(p->ifname, filter)) + return (0); /* exact match */ + n = strlen(filter); + if (n < 1 || n >= IFNAMSIZ) + return (1); /* sanity check */ + if (filter[n-1] >= '0' && filter[n-1] <= '9') + return (1); /* only do exact match in that case */ + if (strncmp(p->ifname, filter, n)) + return (1); /* prefix doesn't match */ + return (p->ifname[n] < '0' || p->ifname[n] > '9'); +} + + +struct node_host * +host(const char *s) +{ + struct node_host *h = NULL; + int mask, v4mask, v6mask, cont = 1; + char *p, *q, *ps; + + if ((p = strrchr(s, '/')) != NULL) { + mask = strtol(p+1, &q, 0); + if (!q || *q || mask > 128 || q == (p+1)) { + fprintf(stderr, "invalid netmask\n"); + return (NULL); + } + if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) + err(1, "host: malloc"); + strlcpy(ps, s, strlen(s) - strlen(p) + 1); + v4mask = v6mask = mask; + } else { + if ((ps = strdup(s)) == NULL) + err(1, "host: strdup"); + v4mask = 32; + v6mask = 128; + mask = -1; + } + + /* interface with this name exists? */ + if (cont && (h = host_if(ps, mask)) != NULL) + cont = 0; + + /* IPv4 address? */ + if (cont && (h = host_v4(s)) != NULL) + cont = 0; + + /* IPv6 address? */ + if (cont && (h = host_v6(ps, v6mask)) != NULL) + cont = 0; + + /* dns lookup */ + if (cont && (h = host_dns(ps, v4mask, v6mask)) != NULL) + cont = 0; + free(ps); + + if (h == NULL || cont == 1) { + fprintf(stderr, "no IP address found for %s\n", s); + return (NULL); + } + return (h); +} + +struct node_host * +host_if(const char *s, int mask) +{ + struct node_host *n, *h = NULL; + char *p, *ps; + int flags = 0; + + if ((ps = strdup(s)) == NULL) + err(1, "host_if: strdup"); + while ((p = strrchr(ps, ':')) != 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 { + free(ps); + return (NULL); + } + *p = '\0'; + } + if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { /* Yep! */ + fprintf(stderr, "illegal combination of interface modifiers\n"); + free(ps); + return (NULL); + } + if ((flags & (PFI_AFLAG_NETWORK|PFI_AFLAG_BROADCAST)) && mask > -1) { + fprintf(stderr, "network or broadcast lookup, but " + "extra netmask given\n"); + free(ps); + return (NULL); + } + if (ifa_exists(ps, 1) || !strncmp(ps, "self", IFNAMSIZ)) { + /* interface with this name exists */ + h = ifa_lookup(ps, flags); + for (n = h; n != NULL && mask > -1; n = n->next) + set_ipmask(n, mask); + } + + free(ps); + return (h); +} + +static struct node_host * +host_v4(const char *s) +{ + struct node_host *h = NULL; + struct in_addr ina; + int bits = 32; + + memset(&ina, 0, sizeof(struct in_addr)); + if (strrchr(s, '/') != NULL) { + if ((bits = inet_net_pton(AF_INET, s, &ina, sizeof(ina))) == -1) + return (NULL); + } else { + if (inet_pton(AF_INET, s, &ina) != 1) + return (NULL); + } + + h = calloc(1, sizeof(struct node_host)); + if (h == NULL) + err(1, "address: calloc"); + h->ifname = NULL; + h->af = AF_INET; + h->addr.v.a.addr.addr32[0] = ina.s_addr; + set_ipmask(h, bits); + h->next = NULL; + h->tail = h; + + return (h); +} + +struct node_host * +host_v6(const char *s, int mask) +{ + struct addrinfo hints, *res; + struct node_host *h = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(s, "0", &hints, &res) == 0) { + h = calloc(1, sizeof(struct node_host)); + if (h == NULL) + err(1, "address: calloc"); + h->ifname = NULL; + h->af = AF_INET6; + memcpy(&h->addr.v.a.addr, + &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, + sizeof(h->addr.v.a.addr)); + h->ifindex = + ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; + set_ipmask(h, mask); + freeaddrinfo(res); + h->next = NULL; + h->tail = h; + } + + return (h); +} + +struct node_host * +host_dns(const char *s, int v4mask, int v6mask) +{ + struct addrinfo hints, *res0, *res; + struct node_host *n, *h = NULL; + int error, noalias = 0; + int got4 = 0, got6 = 0; + char *p, *ps; + + if ((ps = strdup(s)) == NULL) + err(1, "host_if: strdup"); + if ((p = strrchr(ps, ':')) != NULL && !strcmp(p, ":0")) { + noalias = 1; + *p = '\0'; + } + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; /* DUMMY */ + error = getaddrinfo(ps, NULL, &hints, &res0); + if (error) + return (h); + + for (res = res0; res; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + if (noalias) { + if (res->ai_family == AF_INET) { + if (got4) + continue; + got4 = 1; + } else { + if (got6) + continue; + got6 = 1; + } + } + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "host_dns: calloc"); + n->ifname = NULL; + n->af = res->ai_family; + if (res->ai_family == AF_INET) { + memcpy(&n->addr.v.a.addr, + &((struct sockaddr_in *) + res->ai_addr)->sin_addr.s_addr, + sizeof(struct in_addr)); + set_ipmask(n, v4mask); + } else { + memcpy(&n->addr.v.a.addr, + &((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + n->ifindex = + ((struct sockaddr_in6 *) + res->ai_addr)->sin6_scope_id; + set_ipmask(n, v6mask); + } + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + freeaddrinfo(res0); + free(ps); + + return (h); +} + +/* + * convert a hostname to a list of addresses and put them in the given buffer. + * test: + * if set to 1, only simple addresses are accepted (no netblock, no "!"). + */ +int +append_addr(struct pfr_buffer *b, char *s, int test) +{ + char *r; + struct node_host *h, *n; + int rv, not = 0; + + for (r = s; *r == '!'; r++) + not = !not; + if ((n = host(r)) == NULL) { + errno = 0; + return (-1); + } + rv = append_addr_host(b, n, test, not); + do { + h = n; + n = n->next; + free(h); + } while (n != NULL); + return (rv); +} + +/* + * same as previous function, but with a pre-parsed input and the ability + * to "negate" the result. Does not free the node_host list. + * not: + * setting it to 1 is equivalent to adding "!" in front of parameter s. + */ +int +append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not) +{ + int bits; + struct pfr_addr addr; + + do { + bzero(&addr, sizeof(addr)); + addr.pfra_not = n->not ^ not; + addr.pfra_af = n->af; + addr.pfra_net = unmask(&n->addr.v.a.mask, n->af); + switch (n->af) { + case AF_INET: + addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0]; + bits = 32; + break; + case AF_INET6: + memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6, + sizeof(struct in6_addr)); + bits = 128; + break; + default: + errno = EINVAL; + return (-1); + } + if ((test && (not || addr.pfra_net != bits)) || + addr.pfra_net > bits) { + errno = EINVAL; + return (-1); + } + if (pfr_buf_add(b, &addr)) + return (-1); + } while ((n = n->next) != NULL); + + return (0); +} + +int +pfctl_add_trans(struct pfr_buffer *buf, int rs_num, const char *anchor, + const char *ruleset) +{ + struct pfioc_trans_e trans; + + bzero(&trans, sizeof(trans)); + trans.rs_num = rs_num; + if (strlcpy(trans.anchor, anchor, + sizeof(trans.anchor)) >= sizeof(trans.anchor) || + strlcpy(trans.ruleset, ruleset, + sizeof(trans.ruleset)) >= sizeof(trans.ruleset)) + errx(1, "pfctl_add_trans: strlcpy"); + + return pfr_buf_add(buf, &trans); +} + +u_int32_t +pfctl_get_ticket(struct pfr_buffer *buf, int rs_num, const char *anchor, + const char *ruleset) +{ + const struct pfioc_trans_e *p; + + PFRB_FOREACH(p, buf) + if (rs_num == p->rs_num && !strcmp(anchor, p->anchor) && + !strcmp(ruleset, p->ruleset)) + return (p->ticket); + errx(1, "pfr_get_ticket: assertion failed"); +} + +int +pfctl_trans(int dev, struct pfr_buffer *buf, u_long cmd, int from) +{ + struct pfioc_trans trans; + + bzero(&trans, sizeof(trans)); + trans.size = buf->pfrb_size - from; + trans.esize = sizeof(struct pfioc_trans_e); + trans.array = ((struct pfioc_trans_e *)buf->pfrb_caddr) + from; + return ioctl(dev, cmd, &trans); +} diff --git a/usr.sbin/pfctl/pfctl_parser.h b/usr.sbin/pfctl/pfctl_parser.h new file mode 100644 index 0000000000..082462d7f1 --- /dev/null +++ b/usr.sbin/pfctl/pfctl_parser.h @@ -0,0 +1,262 @@ +/* $OpenBSD: pfctl_parser.h,v 1.74 2004/02/10 22:26:56 dhartmei Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl_parser.h,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _PFCTL_PARSER_H_ +#define _PFCTL_PARSER_H_ + +#define PF_OSFP_FILE "/etc/pf.os" + +#define PF_OPT_DISABLE 0x0001 +#define PF_OPT_ENABLE 0x0002 +#define PF_OPT_VERBOSE 0x0004 +#define PF_OPT_NOACTION 0x0008 +#define PF_OPT_QUIET 0x0010 +#define PF_OPT_CLRRULECTRS 0x0020 +#define PF_OPT_USEDNS 0x0040 +#define PF_OPT_VERBOSE2 0x0080 +#define PF_OPT_DUMMYACTION 0x0100 +#define PF_OPT_DEBUG 0x0200 +#define PF_OPT_SHOWALL 0x0400 + +#define PF_TH_ALL 0xFF + +#define PF_NAT_PROXY_PORT_LOW 50001 +#define PF_NAT_PROXY_PORT_HIGH 65535 + +#define FCNT_NAMES { \ + "searches", \ + "inserts", \ + "removals", \ + NULL \ +} + +struct pfctl { + int dev; + int opts; + int loadopt; + u_int32_t tticket; /* table ticket */ + int tdirty; /* kernel dirty */ + u_int32_t rule_nr; + struct pfioc_pooladdr paddr; + struct pfioc_altq *paltq; + struct pfioc_queue *pqueue; + struct pfr_buffer *trans; + const char *anchor; + const char *ruleset; +}; + +struct node_if { + char ifname[IFNAMSIZ]; + u_int8_t not; + u_int ifa_flags; + struct node_if *next; + struct node_if *tail; +}; + +struct node_host { + struct pf_addr_wrap addr; + struct pf_addr bcast; + struct pf_addr peer; + sa_family_t af; + u_int8_t not; + u_int32_t ifindex; /* link-local IPv6 addrs */ + char *ifname; + u_int ifa_flags; + struct node_host *next; + struct node_host *tail; +}; +/* special flags used by ifa_exists */ +#define PF_IFA_FLAG_GROUP 0x10000 +#define PF_IFA_FLAG_DYNAMIC 0x20000 +#define PF_IFA_FLAG_CLONABLE 0x40000 + +struct node_os { + char *os; + pf_osfp_t fingerprint; + struct node_os *next; + struct node_os *tail; +}; + +struct node_queue_bw { + u_int32_t bw_absolute; + u_int16_t bw_percent; +}; + +struct node_hfsc_sc { + struct node_queue_bw m1; /* slope of 1st segment; bps */ + u_int d; /* x-projection of m1; msec */ + struct node_queue_bw m2; /* slope of 2nd segment; bps */ + u_int8_t used; +}; + +struct node_hfsc_opts { + struct node_hfsc_sc realtime; + struct node_hfsc_sc linkshare; + struct node_hfsc_sc upperlimit; + int flags; +}; + +struct node_queue_opt { + int qtype; + union { + struct cbq_opts cbq_opts; + struct priq_opts priq_opts; + struct node_hfsc_opts hfsc_opts; + } data; +}; + +/* XXX should be in machine/limits.h */ +#include +#define UID_MAX UINT_MAX +#define GID_MAX UINT_MAX + +/* + * XXX + * Absolutely this is not correct location to define this. + * Should we use an another sperate header file? + * NOTE: This is also defined in sys/bus/usb/usb_port.h + * perhaps place in sys/queue.h? + */ +#define SIMPLEQ_HEAD STAILQ_HEAD +#define SIMPLEQ_HEAD_INITIALIZER STAILQ_HEAD_INITIALIZER +#define SIMPLEQ_ENTRY STAILQ_ENTRY +#define SIMPLEQ_FIRST STAILQ_FIRST +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY STAILQ_EMPTY +#define SIMPLEQ_NEXT STAILQ_NEXT +/*#define SIMPLEQ_FOREACH STAILQ_FOREACH*/ +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) +#define SIMPLEQ_INIT STAILQ_INIT +#define SIMPLEQ_INSERT_HEAD STAILQ_INSERT_HEAD +#define SIMPLEQ_INSERT_TAIL STAILQ_INSERT_TAIL +#define SIMPLEQ_INSERT_AFTER STAILQ_INSERT_AFTER +#define SIMPLEQ_REMOVE_HEAD STAILQ_REMOVE_HEAD + +SIMPLEQ_HEAD(node_tinithead, node_tinit); +struct node_tinit { /* table initializer */ + SIMPLEQ_ENTRY(node_tinit) entries; + struct node_host *host; + char *file; +}; + +struct pfr_buffer; /* forward definition */ + +int pfctl_rules(int, char *, int, char *, char *, struct pfr_buffer *); + +int pfctl_add_rule(struct pfctl *, struct pf_rule *); +int pfctl_add_altq(struct pfctl *, struct pf_altq *); +int pfctl_add_pool(struct pfctl *, struct pf_pool *, sa_family_t); +void pfctl_clear_pool(struct pf_pool *); + +int pfctl_set_timeout(struct pfctl *, const char *, int, int); +int pfctl_set_optimization(struct pfctl *, const char *); +int pfctl_set_limit(struct pfctl *, const char *, unsigned int); +int pfctl_set_logif(struct pfctl *, char *); +int pfctl_set_hostid(struct pfctl *, u_int32_t); +int pfctl_set_debug(struct pfctl *, char *); + +int parse_rules(FILE *, struct pfctl *); +int parse_flags(char *); +int pfctl_load_anchors(int, int, struct pfr_buffer *); + +void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int); +void print_src_node(struct pf_src_node *, int); +void print_rule(struct pf_rule *, int); +void print_tabledef(const char *, int, int, struct node_tinithead *); +void print_status(struct pf_status *, int); + +int eval_pfaltq(struct pfctl *, struct pf_altq *, struct node_queue_bw *, + struct node_queue_opt *); +int eval_pfqueue(struct pfctl *, struct pf_altq *, struct node_queue_bw *, + struct node_queue_opt *); + +void print_altq(const struct pf_altq *, unsigned, struct node_queue_bw *, + struct node_queue_opt *); +void print_queue(const struct pf_altq *, unsigned, struct node_queue_bw *, + int, struct node_queue_opt *); + +int pfctl_define_table(char *, int, int, const char *, const char *, + struct pfr_buffer *, u_int32_t); + +void pfctl_clear_fingerprints(int, int); +int pfctl_file_fingerprints(int, int, const char *); +pf_osfp_t pfctl_get_fingerprint(const char *); +int pfctl_load_fingerprints(int, int); +char *pfctl_lookup_fingerprint(pf_osfp_t, char *, size_t); +void pfctl_show_fingerprints(int); + + +struct icmptypeent { + const char *name; + u_int8_t type; +}; + +struct icmpcodeent { + const char *name; + u_int8_t type; + u_int8_t code; +}; + +const struct icmptypeent *geticmptypebynumber(u_int8_t, u_int8_t); +const struct icmptypeent *geticmptypebyname(char *, u_int8_t); +const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, u_int8_t); +const struct icmpcodeent *geticmpcodebyname(u_long, char *, u_int8_t); + +struct pf_timeout { + const char *name; + int timeout; +}; + +#define PFCTL_FLAG_FILTER 0x02 +#define PFCTL_FLAG_NAT 0x04 +#define PFCTL_FLAG_OPTION 0x08 +#define PFCTL_FLAG_ALTQ 0x10 +#define PFCTL_FLAG_TABLE 0x20 + +extern const struct pf_timeout pf_timeouts[]; + +void set_ipmask(struct node_host *, u_int8_t); +int check_netmask(struct node_host *, sa_family_t); +void ifa_load(void); +struct node_host *ifa_exists(const char *, int); +struct node_host *ifa_lookup(const char *, int); +struct node_host *host(const char *); + +int append_addr(struct pfr_buffer *, char *, int); +int append_addr_host(struct pfr_buffer *, + struct node_host *, int, int); + +#endif /* _PFCTL_PARSER_H_ */ diff --git a/usr.sbin/pfctl/pfctl_qstats.c b/usr.sbin/pfctl/pfctl_qstats.c new file mode 100644 index 0000000000..0a6cdf122d --- /dev/null +++ b/usr.sbin/pfctl/pfctl_qstats.c @@ -0,0 +1,415 @@ +/* $OpenBSD: pfctl_qstats.c,v 1.29 2004/03/15 15:25:44 dhartmei Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl_qstats.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "pfctl.h" +#include "pfctl_parser.h" + +union class_stats { + class_stats_t cbq_stats; + struct priq_classstats priq_stats; + struct hfsc_classstats hfsc_stats; +}; + +#define AVGN_MAX 8 +#define STAT_INTERVAL 5 + +struct queue_stats { + union class_stats data; + int avgn; + double avg_bytes; + double avg_packets; + u_int64_t prev_bytes; + u_int64_t prev_packets; +}; + +struct pf_altq_node { + struct pf_altq altq; + struct pf_altq_node *next; + struct pf_altq_node *children; + struct queue_stats qstats; +}; + +int pfctl_update_qstats(int, struct pf_altq_node **); +void pfctl_insert_altq_node(struct pf_altq_node **, + const struct pf_altq, const struct queue_stats); +struct pf_altq_node *pfctl_find_altq_node(struct pf_altq_node *, + const char *, const char *); +void pfctl_print_altq_node(int, const struct pf_altq_node *, + unsigned, int); +void print_cbqstats(struct queue_stats); +void print_priqstats(struct queue_stats); +void print_hfscstats(struct queue_stats); +void pfctl_free_altq_node(struct pf_altq_node *); +void pfctl_print_altq_nodestat(int, + const struct pf_altq_node *); + +void update_avg(struct pf_altq_node *); + +int +pfctl_show_altq(int dev, const char *iface, int opts, int verbose2) +{ + struct pf_altq_node *root = NULL, *node; + int nodes, dotitle = (opts & PF_OPT_SHOWALL); + + + if ((nodes = pfctl_update_qstats(dev, &root)) < 0) + return (-1); + + for (node = root; node != NULL; node = node->next) { + if (iface != NULL && strcmp(node->altq.ifname, iface)) + continue; + if (dotitle) { + pfctl_print_title("ALTQ:"); + dotitle = 0; + } + pfctl_print_altq_node(dev, node, 0, opts); + } + + while (verbose2) { + printf("\n"); + fflush(stdout); + sleep(STAT_INTERVAL); + if (pfctl_update_qstats(dev, &root) == -1) + return (-1); + for (node = root; node != NULL; node = node->next) { + if (iface != NULL && strcmp(node->altq.ifname, iface)) + continue; + pfctl_print_altq_node(dev, node, 0, opts); + } + } + pfctl_free_altq_node(root); + return (0); +} + +int +pfctl_update_qstats(int dev, struct pf_altq_node **root) +{ + struct pf_altq_node *node; + struct pfioc_altq pa; + struct pfioc_qstats pq; + u_int32_t mnr, nr; + struct queue_stats qstats; + static u_int32_t last_ticket; + + memset(&pa, 0, sizeof(pa)); + memset(&pq, 0, sizeof(pq)); + memset(&qstats, 0, sizeof(qstats)); + if (ioctl(dev, DIOCGETALTQS, &pa)) { + warn("DIOCGETALTQS"); + return (-1); + } + + /* if a new set is found, start over */ + if (pa.ticket != last_ticket && *root != NULL) { + pfctl_free_altq_node(*root); + *root = NULL; + } + last_ticket = pa.ticket; + + mnr = pa.nr; + for (nr = 0; nr < mnr; ++nr) { + pa.nr = nr; + if (ioctl(dev, DIOCGETALTQ, &pa)) { + warn("DIOCGETALTQ"); + return (-1); + } + if (pa.altq.qid > 0) { + pq.nr = nr; + pq.ticket = pa.ticket; + pq.buf = &qstats.data; + pq.nbytes = sizeof(qstats.data); + if (ioctl(dev, DIOCGETQSTATS, &pq)) { + warn("DIOCGETQSTATS"); + return (-1); + } + if ((node = pfctl_find_altq_node(*root, pa.altq.qname, + pa.altq.ifname)) != NULL) { + memcpy(&node->qstats.data, &qstats.data, + sizeof(qstats.data)); + update_avg(node); + } else { + pfctl_insert_altq_node(root, pa.altq, qstats); + } + } + } + return (mnr); +} + +void +pfctl_insert_altq_node(struct pf_altq_node **root, + const struct pf_altq altq, const struct queue_stats qstats) +{ + struct pf_altq_node *node; + + node = calloc(1, sizeof(struct pf_altq_node)); + if (node == NULL) + err(1, "pfctl_insert_altq_node: calloc"); + memcpy(&node->altq, &altq, sizeof(struct pf_altq)); + memcpy(&node->qstats, &qstats, sizeof(qstats)); + node->next = node->children = NULL; + + if (*root == NULL) + *root = node; + else if (!altq.parent[0]) { + struct pf_altq_node *prev = *root; + + while (prev->next != NULL) + prev = prev->next; + prev->next = node; + } else { + struct pf_altq_node *parent; + + parent = pfctl_find_altq_node(*root, altq.parent, altq.ifname); + if (parent == NULL) + errx(1, "parent %s not found", altq.parent); + if (parent->children == NULL) + parent->children = node; + else { + struct pf_altq_node *prev = parent->children; + + while (prev->next != NULL) + prev = prev->next; + prev->next = node; + } + } + update_avg(node); +} + +struct pf_altq_node * +pfctl_find_altq_node(struct pf_altq_node *root, const char *qname, + const char *ifname) +{ + struct pf_altq_node *node, *child; + + for (node = root; node != NULL; node = node->next) { + if (!strcmp(node->altq.qname, qname) + && !(strcmp(node->altq.ifname, ifname))) + return (node); + if (node->children != NULL) { + child = pfctl_find_altq_node(node->children, qname, + ifname); + if (child != NULL) + return (child); + } + } + return (NULL); +} + +void +pfctl_print_altq_node(int dev, const struct pf_altq_node *node, unsigned level, + int opts) +{ + const struct pf_altq_node *child; + + if (node == NULL) + return; + + print_altq(&node->altq, level, NULL, NULL); + + if (node->children != NULL) { + printf("{"); + for (child = node->children; child != NULL; + child = child->next) { + printf("%s", child->altq.qname); + if (child->next != NULL) + printf(", "); + } + printf("}"); + } + printf("\n"); + + if (opts & PF_OPT_VERBOSE) + pfctl_print_altq_nodestat(dev, node); + + if (opts & PF_OPT_DEBUG) + printf(" [ qid=%u ifname=%s ifbandwidth=%s ]\n", + node->altq.qid, node->altq.ifname, + rate2str((double)(node->altq.ifbandwidth))); + + for (child = node->children; child != NULL; + child = child->next) + pfctl_print_altq_node(dev, child, level + 1, opts); +} + +void +pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a) +{ + if (a->altq.qid == 0) + return; + + switch (a->altq.scheduler) { + case ALTQT_CBQ: + print_cbqstats(a->qstats); + break; + case ALTQT_PRIQ: + print_priqstats(a->qstats); + break; + case ALTQT_HFSC: + print_hfscstats(a->qstats); + break; + } +} + +void +print_cbqstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)cur.data.cbq_stats.xmit_cnt.packets, + (unsigned long long)cur.data.cbq_stats.xmit_cnt.bytes, + (unsigned long long)cur.data.cbq_stats.drop_cnt.packets, + (unsigned long long)cur.data.cbq_stats.drop_cnt.bytes); + printf(" [ qlength: %3d/%3d borrows: %6u suspends: %6u ]\n", + cur.data.cbq_stats.qcnt, cur.data.cbq_stats.qmax, + cur.data.cbq_stats.borrows, cur.data.cbq_stats.delays); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +print_priqstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)cur.data.priq_stats.xmitcnt.packets, + (unsigned long long)cur.data.priq_stats.xmitcnt.bytes, + (unsigned long long)cur.data.priq_stats.dropcnt.packets, + (unsigned long long)cur.data.priq_stats.dropcnt.bytes); + printf(" [ qlength: %3d/%3d ]\n", + cur.data.priq_stats.qlength, cur.data.priq_stats.qlimit); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +print_hfscstats(struct queue_stats cur) +{ + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)cur.data.hfsc_stats.xmit_cnt.packets, + (unsigned long long)cur.data.hfsc_stats.xmit_cnt.bytes, + (unsigned long long)cur.data.hfsc_stats.drop_cnt.packets, + (unsigned long long)cur.data.hfsc_stats.drop_cnt.bytes); + printf(" [ qlength: %3d/%3d ]\n", + cur.data.hfsc_stats.qlength, cur.data.hfsc_stats.qlimit); + + if (cur.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + cur.avg_packets / STAT_INTERVAL, + rate2str((8 * cur.avg_bytes) / STAT_INTERVAL)); +} + +void +pfctl_free_altq_node(struct pf_altq_node *node) +{ + while (node != NULL) { + struct pf_altq_node *prev; + + if (node->children != NULL) + pfctl_free_altq_node(node->children); + prev = node; + node = node->next; + free(prev); + } +} + +void +update_avg(struct pf_altq_node *a) +{ + struct queue_stats *qs; + u_int64_t b, p; + int n; + + if (a->altq.qid == 0) + return; + + qs = &a->qstats; + n = qs->avgn; + + switch (a->altq.scheduler) { + case ALTQT_CBQ: + b = qs->data.cbq_stats.xmit_cnt.bytes; + p = qs->data.cbq_stats.xmit_cnt.packets; + break; + case ALTQT_PRIQ: + b = qs->data.priq_stats.xmitcnt.bytes; + p = qs->data.priq_stats.xmitcnt.packets; + break; + case ALTQT_HFSC: + b = qs->data.hfsc_stats.xmit_cnt.bytes; + p = qs->data.hfsc_stats.xmit_cnt.packets; + break; + default: + b = 0; + p = 0; + break; + } + + if (n == 0) { + qs->prev_bytes = b; + qs->prev_packets = p; + qs->avgn++; + return; + } + + if (b >= qs->prev_bytes) + qs->avg_bytes = ((qs->avg_bytes * (n - 1)) + + (b - qs->prev_bytes)) / n; + + if (p >= qs->prev_packets) + qs->avg_packets = ((qs->avg_packets * (n - 1)) + + (p - qs->prev_packets)) / n; + + qs->prev_bytes = b; + qs->prev_packets = p; + if (n < AVGN_MAX) + qs->avgn++; +} diff --git a/usr.sbin/pfctl/pfctl_radix.c b/usr.sbin/pfctl/pfctl_radix.c new file mode 100644 index 0000000000..c3349cd97b --- /dev/null +++ b/usr.sbin/pfctl/pfctl_radix.c @@ -0,0 +1,672 @@ +/* $OpenBSD: pfctl_radix.c,v 1.24 2004/02/10 18:29:30 henning Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl_radix.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2002 Cedric Berger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl.h" + +#define BUF_SIZE 256 + +extern int dev_fd; + +static int pfr_next_token(char buf[], FILE *); + + +int +pfr_clr_tables(struct pfr_table *filter, int *ndel, int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + if (ioctl(dev_fd, DIOCRCLRTABLES, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev_fd, DIOCRADDTABLES, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +int +pfr_del_tables(struct pfr_table *tbl, int size, int *ndel, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev_fd, DIOCRDELTABLES, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev_fd, DIOCRGETTABLES, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_get_tstats(struct pfr_table *filter, struct pfr_tstats *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev_fd, DIOCRGETTSTATS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + if (ioctl(dev_fd, DIOCRCLRADDRS, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev_fd, DIOCRADDADDRS, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +int +pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *ndel, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev_fd, DIOCRDELADDRS, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *size2, int *nadd, int *ndel, int *nchange, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_size2 = (size2 != NULL) ? *size2 : 0; + if (ioctl(dev_fd, DIOCRSETADDRS, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (ndel != NULL) + *ndel = io.pfrio_ndel; + if (nchange != NULL) + *nchange = io.pfrio_nchange; + if (size2 != NULL) + *size2 = io.pfrio_size2; + return (0); +} + +int +pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || + (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev_fd, DIOCRGETADDRS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || + (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev_fd, DIOCRGETASTATS, &io)) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nzero, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev_fd, DIOCRCLRASTATS, &io)) + return (-1); + if (nzero != NULL) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_clr_tstats(struct pfr_table *tbl, int size, int *nzero, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && !tbl)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev_fd, DIOCRCLRTSTATS, &io)) + return (-1); + if (nzero) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_set_tflags(struct pfr_table *tbl, int size, int setflag, int clrflag, + int *nchange, int *ndel, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && !tbl)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + io.pfrio_setflag = setflag; + io.pfrio_clrflag = clrflag; + if (ioctl(dev_fd, DIOCRSETTFLAGS, &io)) + return (-1); + if (nchange) + *nchange = io.pfrio_nchange; + if (ndel) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nmatch, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev_fd, DIOCRTSTADDRS, &io)) + return (-1); + if (nmatch) + *nmatch = io.pfrio_nmatch; + return (0); +} + +int +pfr_ina_begin(struct pfr_table *trs, int *ticket, int *ndel, int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + if (trs != NULL) + io.pfrio_table = *trs; + io.pfrio_flags = flags; + if (ioctl(dev_fd, DIOCRINABEGIN, &io)) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + if (ticket != NULL) + *ticket = io.pfrio_ticket; + return (0); +} + +int +pfr_ina_commit(struct pfr_table *trs, int ticket, int *nadd, int *nchange, + int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + if (trs != NULL) + io.pfrio_table = *trs; + io.pfrio_flags = flags; + io.pfrio_ticket = ticket; + if (ioctl(dev_fd, DIOCRINACOMMIT, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (nchange != NULL) + *nchange = io.pfrio_nchange; + return (0); +} + +int +pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int *naddr, int ticket, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_ticket = ticket; + if (ioctl(dev_fd, DIOCRINADEFINE, &io)) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (naddr != NULL) + *naddr = io.pfrio_naddr; + return (0); +} + +/* interface management code */ + +int +pfi_get_ifaces(const char *filter, struct pfi_if *buf, int *size, int flags) +{ + struct pfioc_iface io; + + if (size == NULL || *size < 0 || (*size && buf == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfiio_flags = flags; + if (filter != NULL) + if (strlcpy(io.pfiio_name, filter, sizeof(io.pfiio_name)) >= + sizeof(io.pfiio_name)) { + errno = EINVAL; + return (-1); + } + io.pfiio_buffer = buf; + io.pfiio_esize = sizeof(*buf); + io.pfiio_size = *size; + if (ioctl(dev_fd, DIOCIGETIFACES, &io)) + return (-1); + *size = io.pfiio_size; + return (0); +} + +/* buffer management code */ + +size_t buf_esize[PFRB_MAX] = { 0, + sizeof(struct pfr_table), sizeof(struct pfr_tstats), + sizeof(struct pfr_addr), sizeof(struct pfr_astats), + sizeof(struct pfi_if), sizeof(struct pfioc_trans_e) +}; + +/* + * add one element to the buffer + */ +int +pfr_buf_add(struct pfr_buffer *b, const void *e) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX || + e == NULL) { + errno = EINVAL; + return (-1); + } + bs = buf_esize[b->pfrb_type]; + if (b->pfrb_size == b->pfrb_msize) + if (pfr_buf_grow(b, 0)) + return (-1); + memcpy(((caddr_t)b->pfrb_caddr) + bs * b->pfrb_size, e, bs); + b->pfrb_size++; + return (0); +} + +/* + * return next element of the buffer (or first one if prev is NULL) + * see PFRB_FOREACH macro + */ +const void * +pfr_buf_next(struct pfr_buffer *b, const void *prev) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) + return (NULL); + if (b->pfrb_size == 0) + return (NULL); + if (prev == NULL) + return (b->pfrb_caddr); + bs = buf_esize[b->pfrb_type]; + if ((((c_caddr_t)prev)-((c_caddr_t)b->pfrb_caddr)) / bs + 1 >= + (size_t)b->pfrb_size) + return (NULL); + return (((c_caddr_t)prev) + bs); +} + +/* + * minsize: + * 0: make the buffer somewhat bigger + * n: make room for "n" entries in the buffer + */ +int +pfr_buf_grow(struct pfr_buffer *b, int minsize) +{ + caddr_t p; + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) { + errno = EINVAL; + return (-1); + } + if (minsize != 0 && minsize <= b->pfrb_msize) + return (0); + bs = buf_esize[b->pfrb_type]; + if (!b->pfrb_msize) { + if (minsize < 64) + minsize = 64; + b->pfrb_caddr = calloc(bs, minsize); + if (b->pfrb_caddr == NULL) + return (-1); + b->pfrb_msize = minsize; + } else { + if (minsize == 0) + minsize = b->pfrb_msize * 2; + if (minsize < 0 || (size_t)minsize >= SIZE_T_MAX / bs) { + /* msize overflow */ + errno = ENOMEM; + return (-1); + } + p = realloc(b->pfrb_caddr, minsize * bs); + if (p == NULL) + return (-1); + bzero(p + b->pfrb_msize * bs, (minsize - b->pfrb_msize) * bs); + b->pfrb_caddr = p; + b->pfrb_msize = minsize; + } + return (0); +} + +/* + * reset buffer and free memory. + */ +void +pfr_buf_clear(struct pfr_buffer *b) +{ + if (b == NULL) + return; + if (b->pfrb_caddr != NULL) + free(b->pfrb_caddr); + b->pfrb_caddr = NULL; + b->pfrb_size = b->pfrb_msize = 0; +} + +int +pfr_buf_load(struct pfr_buffer *b, char *file, int nonetwork, + int (*append_addr)(struct pfr_buffer *, char *, int)) +{ + FILE *fp; + char buf[BUF_SIZE]; + int rv; + + if (file == NULL) + return (0); + if (!strcmp(file, "-")) + fp = stdin; + else { + fp = fopen(file, "r"); + if (fp == NULL) + return (-1); + } + while ((rv = pfr_next_token(buf, fp)) == 1) + if (append_addr(b, buf, nonetwork)) { + rv = -1; + break; + } + if (fp != stdin) + fclose(fp); + return (rv); +} + +int +pfr_next_token(char buf[BUF_SIZE], FILE *fp) +{ + static char next_ch = ' '; + int i = 0; + + for (;;) { + /* skip spaces */ + while (isspace(next_ch) && !feof(fp)) + next_ch = fgetc(fp); + /* remove from '#' until end of line */ + if (next_ch == '#') + while (!feof(fp)) { + next_ch = fgetc(fp); + if (next_ch == '\n') + break; + } + else + break; + } + if (feof(fp)) { + next_ch = ' '; + return (0); + } + do { + if (i < BUF_SIZE) + buf[i++] = next_ch; + next_ch = fgetc(fp); + } while (!feof(fp) && !isspace(next_ch)); + if (i >= BUF_SIZE) { + errno = EINVAL; + return (-1); + } + buf[i] = '\0'; + return (1); +} + +const char * +pfr_strerror(int errnum) +{ + switch (errnum) { + case ESRCH: + return "Table does not exist"; + case ENOENT: + return "Anchor or Ruleset does not exist"; + default: + return strerror(errnum); + } +} diff --git a/usr.sbin/pfctl/pfctl_table.c b/usr.sbin/pfctl/pfctl_table.c new file mode 100644 index 0000000000..6dcf0e1319 --- /dev/null +++ b/usr.sbin/pfctl/pfctl_table.c @@ -0,0 +1,612 @@ +/* $OpenBSD: pfctl_table.c,v 1.59 2004/03/15 15:25:44 dhartmei Exp $ */ +/* $DragonFly: src/usr.sbin/pfctl/pfctl_table.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2002 Cedric Berger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +extern void usage(void); +static int pfctl_table(int, char *[], char *, const char *, char *, + const char *, const char *, int); +static void print_table(const struct pfr_table *, int, int); +static void print_tstats(const struct pfr_tstats *, int); +static int load_addr(struct pfr_buffer *, int, char *[], char *, int); +static void print_addrx(const struct pfr_addr *, const struct pfr_addr *, + int); +static void print_astats(const struct pfr_astats *, int); +static void radix_perror(void); +static void xprintf(int, const char *, ...); +static void print_iface(const struct pfi_if *, int); +static void oprintf(int, int, const char *, int *, int); + +static const char *stats_text[PFR_DIR_MAX][PFR_OP_TABLE_MAX] = { + { "In/Block:", "In/Pass:", "In/XPass:" }, + { "Out/Block:", "Out/Pass:", "Out/XPass:" } +}; + +static const char *istats_text[2][2][2] = { + { { "In4/Pass:", "In4/Block:" }, { "Out4/Pass:", "Out4/Block:" } }, + { { "In6/Pass:", "In6/Block:" }, { "Out6/Pass:", "Out6/Block:" } } +}; + +#define RVTEST(fct) do { \ + if ((!(opts & PF_OPT_NOACTION) || \ + (opts & PF_OPT_DUMMYACTION)) && \ + (fct)) { \ + radix_perror(); \ + goto _error; \ + } \ + } while (0) + +#define CREATE_TABLE do { \ + table.pfrt_flags |= PFR_TFLAG_PERSIST; \ + RVTEST(pfr_add_tables(&table, 1, &nadd, flags)); \ + if (nadd) { \ + warn_namespace_collision(table.pfrt_name); \ + xprintf(opts, "%d table created", nadd); \ + if (opts & PF_OPT_NOACTION) \ + return (0); \ + } \ + table.pfrt_flags &= ~PFR_TFLAG_PERSIST; \ + } while(0) + +int +pfctl_clear_tables(const char *anchor, const char *ruleset, int opts) +{ + return pfctl_table(0, NULL, NULL, "-F", NULL, anchor, ruleset, opts); +} + +int +pfctl_show_tables(const char *anchor, const char *ruleset, int opts) +{ + return pfctl_table(0, NULL, NULL, "-s", NULL, anchor, ruleset, opts); +} + +int +pfctl_command_tables(int argc, char *argv[], char *tname, + const char *command, char *file, const char *anchor, const char *ruleset, + int opts) +{ + if (tname == NULL || command == NULL) + usage(); + return pfctl_table(argc, argv, tname, command, file, anchor, ruleset, + opts); +} + +int +pfctl_table(int argc, char *argv[], char *tname, const char *command, + char *file, const char *anchor, const char *ruleset, int opts) +{ + struct pfr_table table; + struct pfr_buffer b, b2; + const struct pfr_addr *a, *a2; + int nadd = 0, ndel = 0, nchange = 0, nzero = 0; + int rv = 0, flags = 0, nmatch = 0; + const void *p; + + if (command == NULL) + usage(); + if (opts & PF_OPT_NOACTION) + flags |= PFR_FLAG_DUMMY; + + bzero(&b, sizeof(b)); + bzero(&b2, sizeof(b2)); + bzero(&table, sizeof(table)); + if (tname != NULL) { + if (strlen(tname) >= PF_TABLE_NAME_SIZE) + usage(); + if (strlcpy(table.pfrt_name, tname, + sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) + errx(1, "pfctl_table: strlcpy"); + } + if (strlcpy(table.pfrt_anchor, anchor, + sizeof(table.pfrt_anchor)) >= sizeof(table.pfrt_anchor) || + strlcpy(table.pfrt_ruleset, ruleset, + sizeof(table.pfrt_ruleset)) >= sizeof(table.pfrt_ruleset)) + errx(1, "pfctl_table: strlcpy"); + + if (!strcmp(command, "-F")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_clr_tables(&table, &ndel, flags)); + xprintf(opts, "%d tables deleted", ndel); + } else if (!strcmp(command, "-s")) { + b.pfrb_type = (opts & PF_OPT_VERBOSE2) ? + PFRB_TSTATS : PFRB_TABLES; + if (argc || file != NULL) + usage(); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (opts & PF_OPT_VERBOSE2) + RVTEST(pfr_get_tstats(&table, + b.pfrb_caddr, &b.pfrb_size, flags)); + else + RVTEST(pfr_get_tables(&table, + b.pfrb_caddr, &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + + if (opts & PF_OPT_SHOWALL && b.pfrb_size > 0) + pfctl_print_title("TABLES:"); + + PFRB_FOREACH(p, &b) + if (opts & PF_OPT_VERBOSE2) + print_tstats(p, opts & PF_OPT_DEBUG); + else + print_table(p, opts & PF_OPT_VERBOSE, + opts & PF_OPT_DEBUG); + } else if (!strcmp(command, "kill")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_del_tables(&table, 1, &ndel, flags)); + xprintf(opts, "%d table deleted", ndel); + } else if (!strcmp(command, "flush")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_clr_addrs(&table, &ndel, flags)); + xprintf(opts, "%d addresses deleted", ndel); + } else if (!strcmp(command, "add")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0)) + goto _error; + CREATE_TABLE; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_add_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nadd, flags)); + xprintf(opts, "%d/%d addresses added", nadd, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "delete")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0)) + goto _error; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_del_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &ndel, flags)); + xprintf(opts, "%d/%d addresses deleted", ndel, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "replace")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0)) + goto _error; + CREATE_TABLE; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + for (;;) { + int sz2 = b.pfrb_msize; + + RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &sz2, &nadd, &ndel, &nchange, flags)); + if (sz2 <= b.pfrb_msize) { + b.pfrb_size = sz2; + break; + } else + pfr_buf_grow(&b, sz2); + } + if (nadd) + xprintf(opts, "%d addresses added", nadd); + if (ndel) + xprintf(opts, "%d addresses deleted", ndel); + if (nchange) + xprintf(opts, "%d addresses changed", nchange); + if (!nadd && !ndel && !nchange) + xprintf(opts, "no changes"); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "show")) { + b.pfrb_type = (opts & PF_OPT_VERBOSE) ? + PFRB_ASTATS : PFRB_ADDRS; + if (argc || file != NULL) + usage(); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (opts & PF_OPT_VERBOSE) + RVTEST(pfr_get_astats(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + else + RVTEST(pfr_get_addrs(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(p, &b) + if (opts & PF_OPT_VERBOSE) + print_astats(p, opts & PF_OPT_USEDNS); + else + print_addrx(p, NULL, opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "test")) { + b.pfrb_type = PFRB_ADDRS; + b2.pfrb_type = PFRB_ADDRS; + + if (load_addr(&b, argc, argv, file, 1)) + goto _error; + if (opts & PF_OPT_VERBOSE2) { + flags |= PFR_FLAG_REPLACE; + PFRB_FOREACH(a, &b) + if (pfr_buf_add(&b2, a)) + err(1, "duplicate buffer"); + } + RVTEST(pfr_tst_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nmatch, flags)); + xprintf(opts, "%d/%d addresses match", nmatch, b.pfrb_size); + if (opts & PF_OPT_VERBOSE && !(opts & PF_OPT_VERBOSE2)) + PFRB_FOREACH(a, &b) + if (a->pfra_fback == PFR_FB_MATCH) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + if (opts & PF_OPT_VERBOSE2) { + a2 = NULL; + PFRB_FOREACH(a, &b) { + a2 = pfr_buf_next(&b2, a2); + print_addrx(a2, a, opts & PF_OPT_USEDNS); + } + } + if (nmatch < b.pfrb_size) + rv = 2; + } else if (!strcmp(command, "zero")) { + if (argc || file != NULL) + usage(); + flags |= PFR_FLAG_ADDRSTOO; + RVTEST(pfr_clr_tstats(&table, 1, &nzero, flags)); + xprintf(opts, "%d table/stats cleared", nzero); + } else + warnx("pfctl_table: unknown command '%s'", command); + goto _cleanup; + +_error: + rv = -1; +_cleanup: + pfr_buf_clear(&b); + pfr_buf_clear(&b2); + return (rv); +} + +void +print_table(const struct pfr_table *ta, int verbose, int debug) +{ + if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE)) + return; + if (verbose) { + printf("%c%c%c%c%c%c\t%s", + (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-', + (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-', + (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-', + (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-', + (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-', + (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-', + ta->pfrt_name); + if (ta->pfrt_anchor[0]) + printf("\t%s", ta->pfrt_anchor); + if (ta->pfrt_ruleset[0]) + printf(":%s", ta->pfrt_ruleset); + puts(""); + } else + puts(ta->pfrt_name); +} + +void +print_tstats(const struct pfr_tstats *ts, int debug) +{ + time_t rtime = ts->pfrts_tzero; + int dir, op; + + if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE)) + return; + print_table(&ts->pfrts_t, 1, debug); + printf("\tAddresses: %d\n", ts->pfrts_cnt); + printf("\tCleared: %s", ctime(&rtime)); + printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n", + ts->pfrts_refcnt[PFR_REFCNT_ANCHOR], + ts->pfrts_refcnt[PFR_REFCNT_RULE]); + printf("\tEvaluations: [ NoMatch: %-18llu Match: %-18llu ]\n", + (unsigned long long)ts->pfrts_nomatch, + (unsigned long long)ts->pfrts_match); + for (dir = 0; dir < PFR_DIR_MAX; dir++) + for (op = 0; op < PFR_OP_TABLE_MAX; op++) + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + stats_text[dir][op], + (unsigned long long)ts->pfrts_packets[dir][op], + (unsigned long long)ts->pfrts_bytes[dir][op]); +} + +int +load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file, + int nonetwork) +{ + while (argc--) + if (append_addr(b, *argv++, nonetwork)) { + if (errno) + warn("cannot decode %s", argv[-1]); + return (-1); + } + if (pfr_buf_load(b, file, nonetwork, append_addr)) { + warn("cannot load %s", file); + return (-1); + } + return (0); +} + +void +print_addrx(const struct pfr_addr *ad, const struct pfr_addr *rad, int dns) +{ + char ch, buf[256] = "{error}"; + char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y' }; + unsigned int fback, hostnet; + + fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback; + ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?'; + hostnet = (ad->pfra_af == AF_INET6) ? 128 : 32; + inet_ntop(ad->pfra_af, &ad->pfra_u, buf, sizeof(buf)); + printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf); + if (ad->pfra_net < hostnet) + printf("/%d", ad->pfra_net); + if (rad != NULL && fback != PFR_FB_NONE) { + if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf)) + errx(1, "print_addrx: strlcpy"); + inet_ntop(rad->pfra_af, &rad->pfra_u, buf, sizeof(buf)); + printf("\t%c%s", (rad->pfra_not?'!':' '), buf); + if (rad->pfra_net < hostnet) + printf("/%d", rad->pfra_net); + } + if (rad != NULL && fback == PFR_FB_NONE) + printf("\t nomatch"); + if (dns && ad->pfra_net == hostnet) { + char hostname[NI_MAXHOST]; + union sockaddr_union sa; + + strlcpy(hostname, "?", sizeof(hostname)); + bzero(&sa, sizeof(sa)); + sa.sa.sa_family = ad->pfra_af; + if (sa.sa.sa_family == AF_INET) { + sa.sa.sa_len = sizeof(sa.sin); + sa.sin.sin_addr = ad->pfra_ip4addr; + } else { + sa.sa.sa_len = sizeof(sa.sin6); + sa.sin6.sin6_addr = ad->pfra_ip6addr; + } + if (getnameinfo(&sa.sa, sa.sa.sa_len, hostname, sizeof(hostname), + NULL, 0, NI_NAMEREQD) == 0) + printf("\t(%s)", hostname); + } + printf("\n"); +} + +void +print_astats(const struct pfr_astats *as, int dns) +{ + time_t rtime = as->pfras_tzero; + int dir, op; + + print_addrx(&as->pfras_a, NULL, dns); + printf("\tCleared: %s", ctime(&rtime)); + for (dir = 0; dir < PFR_DIR_MAX; dir++) + for (op = 0; op < PFR_OP_ADDR_MAX; op++) + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + stats_text[dir][op], + (unsigned long long)as->pfras_packets[dir][op], + (unsigned long long)as->pfras_bytes[dir][op]); +} + +void +radix_perror(void) +{ + fprintf(stderr, "%s: %s.\n", getprogname(), pfr_strerror(errno)); +} + +int +pfctl_define_table(char *name, int flags, int addrs, const char *anchor, + const char *ruleset, struct pfr_buffer *ab, u_int32_t ticket) +{ + struct pfr_table tbl; + + bzero(&tbl, sizeof(tbl)); + if (strlcpy(tbl.pfrt_name, name, sizeof(tbl.pfrt_name)) >= + sizeof(tbl.pfrt_name) || strlcpy(tbl.pfrt_anchor, anchor, + sizeof(tbl.pfrt_anchor)) >= sizeof(tbl.pfrt_anchor) || + strlcpy(tbl.pfrt_ruleset, ruleset, sizeof(tbl.pfrt_ruleset)) >= + sizeof(tbl.pfrt_ruleset)) + errx(1, "pfctl_define_table: strlcpy"); + tbl.pfrt_flags = flags; + + return pfr_ina_define(&tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, + NULL, ticket, addrs ? PFR_FLAG_ADDRSTOO : 0); +} + +void +warn_namespace_collision(const char *filter) +{ + struct pfr_buffer b; + const struct pfr_table *t; + const char *name = NULL, *lastcoll = NULL; + int coll = 0; + + bzero(&b, sizeof(b)); + b.pfrb_type = PFRB_TABLES; + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (pfr_get_tables(NULL, b.pfrb_caddr, + &b.pfrb_size, PFR_FLAG_ALLRSETS)) + err(1, "pfr_get_tables"); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(t, &b) { + if (!(t->pfrt_flags & PFR_TFLAG_ACTIVE)) + continue; + if (filter != NULL && strcmp(filter, t->pfrt_name)) + continue; + if (!t->pfrt_anchor[0]) + name = t->pfrt_name; + else if (name != NULL && !strcmp(name, t->pfrt_name)) { + coll++; + lastcoll = name; + name = NULL; + } + } + if (coll == 1) + warnx("warning: namespace collision with <%s> global table.", + lastcoll); + else if (coll > 1) + warnx("warning: namespace collisions with %d global tables.", + coll); + pfr_buf_clear(&b); +} + +void +xprintf(int opts, const char *fmt, ...) +{ + va_list args; + + if (opts & PF_OPT_QUIET) + return; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + if (opts & PF_OPT_DUMMYACTION) + fprintf(stderr, " (dummy).\n"); + else if (opts & PF_OPT_NOACTION) + fprintf(stderr, " (syntax only).\n"); + else + fprintf(stderr, ".\n"); +} + + +/* interface stuff */ + +int +pfctl_show_ifaces(const char *filter, int opts) +{ + struct pfr_buffer b; + const struct pfi_if *p; + int i = 0, f = PFI_FLAG_GROUP|PFI_FLAG_INSTANCE; + + if (filter != NULL && *filter && !isdigit(filter[strlen(filter)-1])) + f &= ~PFI_FLAG_INSTANCE; + bzero(&b, sizeof(b)); + b.pfrb_type = PFRB_IFACES; + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (pfi_get_ifaces(filter, b.pfrb_caddr, &b.pfrb_size, f)) { + radix_perror(); + return (1); + } + if (b.pfrb_size <= b.pfrb_msize) + break; + i++; + } + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("INTERFACES:"); + PFRB_FOREACH(p, &b) + print_iface(p, opts); + return (0); +} + +void +print_iface(const struct pfi_if *p, int opts) +{ + time_t tzero = p->pfif_tzero; + int flags = (opts & PF_OPT_VERBOSE) ? p->pfif_flags : 0; + int first = 1; + int i, af, dir, act; + + printf("%s", p->pfif_name); + oprintf(flags, PFI_IFLAG_INSTANCE, "instance", &first, 0); + oprintf(flags, PFI_IFLAG_GROUP, "group", &first, 0); + oprintf(flags, PFI_IFLAG_CLONABLE, "clonable", &first, 0); + oprintf(flags, PFI_IFLAG_DYNAMIC, "dynamic", &first, 0); + oprintf(flags, PFI_IFLAG_ATTACHED, "attached", &first, 1); + printf("\n"); + + if (!(opts & PF_OPT_VERBOSE2)) + return; + printf("\tCleared: %s", ctime(&tzero)); + printf("\tReferences: [ States: %-18d Rules: %-18d ]\n", + p->pfif_states, p->pfif_rules); + for (i = 0; i < 8; i++) { + af = (i>>2) & 1; + dir = (i>>1) &1; + act = i & 1; + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + istats_text[af][dir][act], + (unsigned long long)p->pfif_packets[af][dir][act], + (unsigned long long)p->pfif_bytes[af][dir][act]); + } +} + +void +oprintf(int flags, int flag, const char *s, int *first, int last) +{ + if (flags & flag) { + printf(*first ? "\t(%s" : ", %s", s); + *first = 0; + } + if (last && !*first) + printf(")"); +} + diff --git a/usr.sbin/pflogd/Makefile b/usr.sbin/pflogd/Makefile new file mode 100644 index 0000000000..45c178ace8 --- /dev/null +++ b/usr.sbin/pflogd/Makefile @@ -0,0 +1,12 @@ +# $OpenBSD: Makefile,v 1.6 2003/11/20 23:23:09 avsm Exp $ +# $DragonFly: src/usr.sbin/pflogd/Makefile,v 1.1 2004/09/21 21:25:28 joerg Exp $ + +LDADD+= -lpcap -lutil +DPAPP+= ${LIBPCAP} ${LIBUTIL} + +PROG= pflogd +SRCS= pflogd.c privsep.c privsep_fdpass.c +MAN= pflogd.8 +WARNS?= 6 + +.include diff --git a/usr.sbin/pflogd/pflogd.8 b/usr.sbin/pflogd/pflogd.8 new file mode 100644 index 0000000000..b802c3574f --- /dev/null +++ b/usr.sbin/pflogd/pflogd.8 @@ -0,0 +1,193 @@ +.\" $OpenBSD: pflogd.8,v 1.24 2004/01/16 10:45:49 jmc Exp $ +.\" $DragonFly: src/usr.sbin/pflogd/pflogd.8,v 1.1 2004/09/21 21:25:28 joerg Exp $ +.\" +.\" Copyright (c) 2001 Can Erkin Acar. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd July 9, 2001 +.Dt PFLOGD 8 +.Os +.Sh NAME +.Nm pflogd +.Nd packet filter logging daemon +.Sh SYNOPSIS +.Nm pflogd +.Op Fl Dx +.Op Fl d Ar delay +.Op Fl f Ar filename +.Op Fl s Ar snaplen +.Op Ar expression +.Sh DESCRIPTION +.Nm +is a background daemon which reads packets logged by +.Xr pf 4 +to the packet logging interface +.Pa pflog0 +and writes the packets to a logfile (normally +.Pa /var/log/pflog ) +in +.Xr tcpdump 8 +binary format. +These logs can be reviewed later using the +.Fl r +option of +.Xr tcpdump 8 , +hopefully offline in case there are bugs in the packet parsing code of +.Xr tcpdump 8 . +.Pp +.Nm +closes and then re-opens the log file when it receives +.Dv SIGHUP , +permitting +.Xr newsyslog 8 +to rotate logfiles automatically. +.Dv SIGALRM +causes +.Nm +to flush the current logfile buffers to the disk, thus making the most +recent logs available. +The buffers are also flushed every +.Ar delay +seconds. +.Pp +If the log file contains data after a restart or a +.Dv SIGHUP , +new logs are appended to the existing file. +If the existing log file was created with a different snaplen, +.Nm +temporarily uses the old snaplen to keep the log file consistent. +.Pp +.Nm +tries to preserve the integrity of the log file against I/O errors. +Furthermore, integrity of an existing log file is verified before +appending. +If there is an invalid log file or an I/O error, logging is suspended until a +.Dv SIGHUP +or a +.Dv SIGALRM +is received. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D +Debugging mode. +.Nm +does not disassociate from the controlling terminal. +.It Fl d Ar delay +Time in seconds to delay between automatic flushes of the file. +This may be specified with a value between 5 and 3600 seconds. +If not specified, the default is 60 seconds. +.It Fl f Ar filename +Log output filename. +Default is +.Pa /var/log/pflog . +.It Fl s Ar snaplen +Analyze at most the first +.Ar snaplen +bytes of data from each packet rather than the default of 96. +The default of 96 is adequate for IP, ICMP, TCP, and UDP headers but may +truncate protocol information for other protocols. +Other file parsers may desire a higher snaplen. +.It Fl x +Check the integrity of an existing log file, and return. +.It Ar expression +Selects which packets will be dumped, using the regular language of +.Xr tcpdump 8 . +.El +.Sh FILES +.Bl -tag -width /var/run/pflogd.pid -compact +.It Pa /var/run/pflogd.pid +Process ID of the currently running +.Nm . +.It Pa /var/log/pflog +Default log file. +.El +.Sh EXAMPLES +Log specific tcp packets to a different log file with a large snaplen +(useful with a log-all rule to dump complete sessions): +.Bd -literal -offset indent +# pflogd -s 1600 -f suspicious.log port 80 and host evilhost +.Ed +.Pp +Display binary logs: +.Bd -literal -offset indent +# tcpdump -n -e -ttt -r /var/log/pflog +.Ed +.Pp +Display the logs in real time (this does not interfere with the +operation of +.Nm ) : +.Bd -literal -offset indent +# tcpdump -n -e -ttt -i pflog0 +.Ed +.Pp +Tcpdump has been extended to be able to filter on the pfloghdr +structure defined in +.Aq Ar net/if_pflog.h . +Tcpdump can restrict the output +to packets logged on a specified interface, a rule number, a reason, +a direction, an IP family or an action. +.Pp +.Bl -tag -width "reason match " -compact +.It ip +Address family equals IPv4. +.It ip6 +Address family equals IPv6. +.It ifname kue0 +Interface name equals "kue0". +.It on kue0 +Interface name equals "kue0". +.It rulenum 10 +Rule number equals 10. +.It reason match +Reason equals match. +Also accepts "bad-offset", "fragment", "short", "normalize" and "memory". +.It action pass +Action equals pass. +Also accepts "block". +.It inbound +The direction was inbound. +.It outbound +The direction was outbound. +.El +.Pp +Display the logs in real time of inbound packets that were blocked on +the wi0 interface: +.Bd -literal -offset indent +# tcpdump -n -e -ttt -i pflog0 inbound and action block and on wi0 +.Ed +.Sh SEE ALSO +.Xr pcap 3 , +.Xr pf 4 , +.Xr pflog 4 , +.Xr pf.conf 5 , +.Xr newsyslog 8 , +.Xr tcpdump 8 +.Sh HISTORY +The +.Nm +command appeared in +.Ox 3.0 . +.Sh AUTHORS +Can Erkin Acar diff --git a/usr.sbin/pflogd/pflogd.c b/usr.sbin/pflogd/pflogd.c new file mode 100644 index 0000000000..2a521a6c95 --- /dev/null +++ b/usr.sbin/pflogd/pflogd.c @@ -0,0 +1,655 @@ +/* $OpenBSD: pflogd.c,v 1.27 2004/02/13 19:01:57 otto Exp $ */ +/* $DragonFly: src/usr.sbin/pflogd/pflogd.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2001 Theo de Raadt + * Copyright (c) 2001 Can Erkin Acar + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "pflogd.h" + +pcap_t *hpcap; +static FILE *dpcap; + +int Debug = 0; +static int snaplen = DEF_SNAPLEN; +static int cur_snaplen = DEF_SNAPLEN; + +volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup; + +const char *filename = PFLOGD_LOG_FILE; +const char *interface = PFLOGD_DEFAULT_IF; +char *filter = NULL; + +char errbuf[PCAP_ERRBUF_SIZE]; + +int log_debug = 0; +unsigned int delay = FLUSH_DELAY; + +char *copy_argv(char * const *); +void dump_packet(u_char *, const struct pcap_pkthdr *, const u_char *); +void dump_packet_nobuf(u_char *, const struct pcap_pkthdr *, const u_char *); +int flush_buffer(FILE *); +int init_pcap(void); +void purge_buffer(void); +int reset_dump(void); +int scan_dump(FILE *, off_t); +int set_snaplen(int); +void set_suspended(int); +void sig_alrm(int); +void sig_close(int); +void sig_hup(int); +void usage(void); + +/* buffer must always be greater than snaplen */ +static int bufpkt = 0; /* number of packets in buffer */ +static size_t buflen = 0; /* allocated size of buffer */ +static char *buffer = NULL; /* packet buffer */ +static char *bufpos = NULL; /* position in buffer */ +static size_t bufleft = 0; /* bytes left in buffer */ + +/* if error, stop logging but count dropped packets */ +static int suspended = -1; +static long packets_dropped = 0; + +void +set_suspended(int s) +{ + if (suspended == s) + return; + + suspended = s; + setproctitle("[%s] -s %d -f %s", + suspended ? "suspended" : "running", cur_snaplen, filename); +} + +char * +copy_argv(char * const *argv) +{ + size_t len = 0, n; + char *buf; + + if (argv == NULL) + return (NULL); + + for (n = 0; argv[n]; n++) + len += strlen(argv[n])+1; + if (len == 0) + return (NULL); + + buf = malloc(len); + if (buf == NULL) + return (NULL); + + strlcpy(buf, argv[0], len); + for (n = 1; argv[n]; n++) { + strlcat(buf, " ", len); + strlcat(buf, argv[n], len); + } + return (buf); +} + +void +logmsg(int pri, const char *message, ...) +{ + va_list ap; + va_start(ap, message); + + if (log_debug) { + vfprintf(stderr, message, ap); + fprintf(stderr, "\n"); + } else + vsyslog(pri, message, ap); + va_end(ap); +} + +void +usage(void) +{ + fprintf(stderr, "usage: pflogd [-Dx] [-d delay] [-f filename] "); + fprintf(stderr, "[-s snaplen] [expression]\n"); + exit(1); +} + +void +sig_close(int sig __unused) +{ + gotsig_close = 1; +} + +void +sig_hup(int sig __unused) +{ + gotsig_hup = 1; +} + +void +sig_alrm(int sig __unused) +{ + gotsig_alrm = 1; +} + +void +set_pcap_filter(void) +{ + struct bpf_program bprog; + + if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0) + logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); + else { + if (pcap_setfilter(hpcap, &bprog) < 0) + logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); + pcap_freecode(&bprog); + } +} + +int +init_pcap(void) +{ + hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf); + if (hpcap == NULL) { + logmsg(LOG_ERR, "Failed to initialize: %s", errbuf); + return (-1); + } + + if (pcap_datalink(hpcap) != DLT_PFLOG) { + logmsg(LOG_ERR, "Invalid datalink type"); + pcap_close(hpcap); + hpcap = NULL; + return (-1); + } + + set_pcap_filter(); + + cur_snaplen = snaplen = pcap_snapshot(hpcap); + + /* From contrib/pf/pflogd.c 1.5 FreeBSD: BPF locking is not + * (yet) supported. + */ + #ifndef __DragonFly__ + /* lock */ + if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) { + logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno)); + return (-1); + } + #endif + + return (0); +} + +int +set_snaplen(int snap) +{ + if (priv_set_snaplen(snap)) + return (1); + + if (cur_snaplen > snap) + purge_buffer(); + + cur_snaplen = snap; + + return (0); +} + +int +reset_dump(void) +{ + struct pcap_file_header hdr; + struct stat st; + int fd; + FILE *fp; + + if (hpcap == NULL) + return (-1); + + if (dpcap) { + flush_buffer(dpcap); + fclose(dpcap); + dpcap = NULL; + } + + /* + * Basically reimplement pcap_dump_open() because it truncates + * files and duplicates headers and such. + */ + fd = priv_open_log(); + if (fd < 0) + return (1); + + fp = fdopen(fd, "a+"); + + if (fp == NULL) { + logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno)); + return (1); + } + if (fstat(fileno(fp), &st) == -1) { + logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno)); + return (1); + } + + /* set FILE unbuffered, we do our own buffering */ + if (setvbuf(fp, NULL, _IONBF, 0)) { + logmsg(LOG_ERR, "Failed to set output buffers"); + return (1); + } + +#define TCPDUMP_MAGIC 0xa1b2c3d4 + + if (st.st_size == 0) { + if (snaplen != cur_snaplen) { + logmsg(LOG_NOTICE, "Using snaplen %d", snaplen); + if (set_snaplen(snaplen)) { + logmsg(LOG_WARNING, + "Failed, using old settings"); + } + } + hdr.magic = TCPDUMP_MAGIC; + hdr.version_major = PCAP_VERSION_MAJOR; + hdr.version_minor = PCAP_VERSION_MINOR; + hdr.thiszone = hpcap->tzoff; + hdr.snaplen = hpcap->snapshot; + hdr.sigfigs = 0; + hdr.linktype = hpcap->linktype; + + if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) { + fclose(fp); + return (1); + } + } else if (scan_dump(fp, st.st_size)) { + /* XXX move file and continue? */ + fclose(fp); + return (1); + } + + dpcap = fp; + + set_suspended(0); + flush_buffer(fp); + + return (0); +} + +int +scan_dump(FILE *fp, off_t size) +{ + struct pcap_file_header hdr; + struct pcap_pkthdr ph; + off_t pos; + + /* + * Must read the file, compare the header against our new + * options (in particular, snaplen) and adjust our options so + * that we generate a correct file. Furthermore, check the file + * for consistency so that we can append safely. + * + * XXX this may take a long time for large logs. + */ + (void) fseek(fp, 0L, SEEK_SET); + + if (fread((char *)&hdr, sizeof(hdr), 1, fp) != 1) { + logmsg(LOG_ERR, "Short file header"); + return (1); + } + + if (hdr.magic != TCPDUMP_MAGIC || + hdr.version_major != PCAP_VERSION_MAJOR || + hdr.version_minor != PCAP_VERSION_MINOR || + (int)hdr.linktype != hpcap->linktype || + hdr.snaplen > PFLOGD_MAXSNAPLEN) { + logmsg(LOG_ERR, "Invalid/incompatible log file, move it away"); + return (1); + } + + pos = sizeof(hdr); + + while (!feof(fp)) { + off_t len = fread((char *)&ph, 1, sizeof(ph), fp); + if (len == 0) + break; + + if (len != sizeof(ph)) + goto error; + if (ph.caplen > hdr.snaplen || ph.caplen > PFLOGD_MAXSNAPLEN) + goto error; + pos += sizeof(ph) + ph.caplen; + if (pos > size) + goto error; + fseek(fp, ph.caplen, SEEK_CUR); + } + + if (pos != size) + goto error; + + if ((int)hdr.snaplen != cur_snaplen) { + logmsg(LOG_WARNING, + "Existing file has different snaplen %u, using it", + hdr.snaplen); + if (set_snaplen(hdr.snaplen)) { + logmsg(LOG_WARNING, + "Failed, using old settings, offset %llu", + (unsigned long long) size); + } + } + + return (0); + + error: + logmsg(LOG_ERR, "Corrupted log file."); + return (1); +} + +/* dump a packet directly to the stream, which is unbuffered */ +void +dump_packet_nobuf(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) +{ + FILE *f = (FILE *)user; + + if (suspended) { + packets_dropped++; + return; + } + + if (fwrite(h, sizeof(*h), 1, f) != 1) { + /* try to undo header to prevent corruption */ + off_t pos = ftello(f); + if (pos < sizeof(*h) || + ftruncate(fileno(f), pos - sizeof(*h))) { + logmsg(LOG_ERR, "Write failed, corrupted logfile!"); + set_suspended(1); + gotsig_close = 1; + return; + } + goto error; + } + + if (fwrite(sp, h->caplen, 1, f) != 1) + goto error; + + return; + +error: + set_suspended(1); + packets_dropped ++; + logmsg(LOG_ERR, "Logging suspended: fwrite: %s", strerror(errno)); +} + +int +flush_buffer(FILE *f) +{ + off_t offset; + int len = bufpos - buffer; + + if (len <= 0) + return (0); + + offset = ftello(f); + if (offset == (off_t)-1) { + set_suspended(1); + logmsg(LOG_ERR, "Logging suspended: ftello: %s", + strerror(errno)); + return (1); + } + + if (fwrite(buffer, len, 1, f) != 1) { + set_suspended(1); + logmsg(LOG_ERR, "Logging suspended: fwrite: %s", + strerror(errno)); + ftruncate(fileno(f), offset); + return (1); + } + + set_suspended(0); + bufpos = buffer; + bufleft = buflen; + bufpkt = 0; + + return (0); +} + +void +purge_buffer(void) +{ + packets_dropped += bufpkt; + + set_suspended(0); + bufpos = buffer; + bufleft = buflen; + bufpkt = 0; +} + +/* append packet to the buffer, flushing if necessary */ +void +dump_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) +{ + FILE *f = (FILE *)user; + size_t len = sizeof(*h) + h->caplen; + + if (len < sizeof(*h) || h->caplen > (size_t)cur_snaplen) { + logmsg(LOG_NOTICE, "invalid size %u (%u/%u), packet dropped", + len, cur_snaplen, snaplen); + packets_dropped++; + return; + } + + if (len <= bufleft) + goto append; + + if (suspended) { + packets_dropped++; + return; + } + + if (flush_buffer(f)) { + packets_dropped++; + return; + } + + if (len > bufleft) { + dump_packet_nobuf(user, h, sp); + return; + } + + append: + memcpy(bufpos, h, sizeof(*h)); + memcpy(bufpos + sizeof(*h), sp, h->caplen); + + bufpos += len; + bufleft -= len; + bufpkt++; + + return; +} + +int +main(int argc, char **argv) +{ + struct pcap_stat pstat; + int ch, np, Xflag = 0; + pcap_handler phandler = dump_packet; + + /* Neither FreeBSD nor DFly have this; Max seems to think this may + * be a paranoid check. Comment it out: + closefrom(STDERR_FILENO + 1); + */ + + while ((ch = getopt(argc, argv, "Dxd:s:f:")) != -1) { + switch (ch) { + case 'D': + Debug = 1; + break; + case 'd': + delay = atoi(optarg); + if (delay < 5 || delay > 60*60) + usage(); + break; + case 'f': + filename = optarg; + break; + case 's': + snaplen = atoi(optarg); + if (snaplen <= 0) + snaplen = DEF_SNAPLEN; + if (snaplen > PFLOGD_MAXSNAPLEN) + snaplen = PFLOGD_MAXSNAPLEN; + break; + case 'x': + Xflag++; + break; + default: + usage(); + } + + } + + log_debug = Debug; + argc -= optind; + argv += optind; + + if (!Debug) { + openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON); + if (daemon(0, 0)) { + logmsg(LOG_WARNING, "Failed to become daemon: %s", + strerror(errno)); + } + pidfile(NULL); + } + + (void)umask(S_IRWXG | S_IRWXO); + + /* filter will be used by the privileged process */ + if (argc) { + filter = copy_argv(argv); + if (filter == NULL) + logmsg(LOG_NOTICE, "Failed to form filter expression"); + } + + /* initialize pcap before dropping privileges */ + if (init_pcap()) { + logmsg(LOG_ERR, "Exiting, init failure"); + exit(1); + } + + /* Privilege separation begins here */ + if (priv_init()) { + logmsg(LOG_ERR, "unable to privsep"); + exit(1); + } + + setproctitle("[initializing]"); + /* Process is now unprivileged and inside a chroot */ + signal(SIGTERM, sig_close); + signal(SIGINT, sig_close); + signal(SIGQUIT, sig_close); + signal(SIGALRM, sig_alrm); + signal(SIGHUP, sig_hup); + alarm(delay); + + buffer = malloc(PFLOGD_BUFSIZE); + + if (buffer == NULL) { + logmsg(LOG_WARNING, "Failed to allocate output buffer"); + phandler = dump_packet_nobuf; + } else { + bufleft = buflen = PFLOGD_BUFSIZE; + bufpos = buffer; + bufpkt = 0; + } + + if (reset_dump()) { + if (Xflag) + return (1); + + logmsg(LOG_ERR, "Logging suspended: open error"); + set_suspended(1); + } else if (Xflag) + return (0); + + while (1) { + np = pcap_dispatch(hpcap, PCAP_NUM_PKTS, + dump_packet, (u_char *)dpcap); + if (np < 0) + logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap)); + + if (gotsig_close) + break; + if (gotsig_hup) { + if (reset_dump()) { + logmsg(LOG_ERR, + "Logging suspended: open error"); + set_suspended(1); + } + gotsig_hup = 0; + } + + if (gotsig_alrm) { + if (dpcap) + flush_buffer(dpcap); + gotsig_alrm = 0; + alarm(delay); + } + } + + logmsg(LOG_NOTICE, "Exiting"); + if (dpcap) { + flush_buffer(dpcap); + fclose(dpcap); + } + purge_buffer(); + + if (pcap_stats(hpcap, &pstat) < 0) + logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap)); + else + logmsg(LOG_NOTICE, + "%u packets received, %u/%u dropped (kernel/pflogd)", + pstat.ps_recv, pstat.ps_drop, packets_dropped); + + pcap_close(hpcap); + if (!Debug) + closelog(); + return (0); +} diff --git a/usr.sbin/pflogd/pflogd.h b/usr.sbin/pflogd/pflogd.h new file mode 100644 index 0000000000..207454bbe6 --- /dev/null +++ b/usr.sbin/pflogd/pflogd.h @@ -0,0 +1,48 @@ +/* $OpenBSD: pflogd.h,v 1.2 2004/01/15 20:15:14 canacar Exp $ */ +/* $DragonFly: src/usr.sbin/pflogd/pflogd.h,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2003 Can Erkin Acar + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#define DEF_SNAPLEN 116 /* default plus allow for larger header of pflog */ +#define PCAP_TO_MS 500 /* pcap read timeout (ms) */ +#define PCAP_NUM_PKTS 1000 /* max number of packets to process at each loop */ +#define PCAP_OPT_FIL 1 /* filter optimization */ +#define FLUSH_DELAY 60 /* flush delay */ + +#define PFLOGD_LOG_FILE "/var/log/pflog" +#define PFLOGD_DEFAULT_IF "pflog0" + +#define PFLOGD_MAXSNAPLEN INT_MAX +#define PFLOGD_BUFSIZE 65536 /* buffer size for incoming packets */ + +void logmsg(int priority, const char *message, ...); + +/* Privilege separation */ +int priv_init(void); +int priv_set_snaplen(int snaplen); +int priv_open_log(void); +pcap_t *pcap_open_live_fd(int fd, int snaplen, char *ebuf); + +void set_pcap_filter(void); +/* File descriptor send/recv */ +void send_fd(int, int); +int receive_fd(int); + +extern int Debug; diff --git a/usr.sbin/pflogd/privsep.c b/usr.sbin/pflogd/privsep.c new file mode 100644 index 0000000000..c4d90298f4 --- /dev/null +++ b/usr.sbin/pflogd/privsep.c @@ -0,0 +1,309 @@ +/* $OpenBSD: privsep.c,v 1.8 2004/03/14 19:17:05 otto Exp $ */ +/* $DragonFly: src/usr.sbin/pflogd/privsep.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright (c) 2003 Can Erkin Acar + * Copyright (c) 2003 Anil Madhavapeddy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pflogd.h" + +enum cmd_types { + PRIV_SET_SNAPLEN, /* set the snaplength */ + PRIV_OPEN_LOG /* open logfile for appending */ +}; + +static int priv_fd = -1; +static volatile pid_t child_pid = -1; + +volatile sig_atomic_t gotsig_chld = 0; + +static void sig_pass_to_chld(int); +static void sig_chld(int); +static int may_read(int, void *, size_t); +static void must_read(int, void *, size_t); +static void must_write(int, void *, size_t); +static int set_snaplen(int snap); + +/* bpf filter expression common to parent and child */ +extern char *filter; +extern char *errbuf; +extern char *filename; +extern pcap_t *hpcap; + +/* based on syslogd privsep */ +int +priv_init(void) +{ + int i, fd, socks[2], cmd; + int snaplen, ret; + struct passwd *pw; + + for (i = 1; i < NSIG; i++) + signal(i, SIG_DFL); + + /* Create sockets */ + if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) + err(1, "socketpair() failed"); + + pw = getpwnam("_pflogd"); + if (pw == NULL) + errx(1, "unknown user _pflogd"); + endpwent(); + + child_pid = fork(); + if (child_pid < 0) + err(1, "fork() failed"); + + if (!child_pid) { + gid_t gidset[1]; + + /* Child - drop privileges and return */ + if (chroot(pw->pw_dir) != 0) + err(1, "unable to chroot"); + if (chdir("/") != 0) + err(1, "unable to chdir"); + + gidset[0] = pw->pw_gid; + if (setgroups(1, gidset) == -1) + err(1, "setgroups() failed"); + if (setegid(pw->pw_gid) == -1) + err(1, "setegid() failed"); + if (setgid(pw->pw_gid) == -1) + err(1, "setgid() failed"); + if (seteuid(pw->pw_uid) == -1) + err(1, "seteuid() failed"); + if (setuid(pw->pw_uid) == -1) + err(1, "setuid() failed"); + close(socks[0]); + priv_fd = socks[1]; + return 0; + } + + /* Father */ + /* Pass ALRM/TERM/HUP through to child, and accept CHLD */ + signal(SIGALRM, sig_pass_to_chld); + signal(SIGTERM, sig_pass_to_chld); + signal(SIGHUP, sig_pass_to_chld); + signal(SIGCHLD, sig_chld); + + setproctitle("[priv]"); + close(socks[1]); + + while (!gotsig_chld) { + if (may_read(socks[0], &cmd, sizeof(int))) + break; + switch (cmd) { + case PRIV_SET_SNAPLEN: + logmsg(LOG_DEBUG, + "[priv]: msg PRIV_SET_SNAPLENGTH received"); + must_read(socks[0], &snaplen, sizeof(int)); + + ret = set_snaplen(snaplen); + if (ret) { + logmsg(LOG_NOTICE, + "[priv]: set_snaplen failed for snaplen %d", + snaplen); + } + + must_write(socks[0], &ret, sizeof(int)); + break; + + case PRIV_OPEN_LOG: + logmsg(LOG_DEBUG, + "[priv]: msg PRIV_OPEN_LOG received"); + /* create or append logs but do not follow symlinks */ + fd = open(filename, + O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK|O_NOFOLLOW, + 0600); + if (fd < 0) + logmsg(LOG_NOTICE, + "[priv]: failed to open %s: %s", + filename, strerror(errno)); + send_fd(socks[0], fd); + close(fd); + break; + + default: + logmsg(LOG_ERR, "[priv]: unknown command %d", cmd); + _exit(1); + /* NOTREACHED */ + } + } + + _exit(1); +} + +/* this is called from parent */ +static int +set_snaplen(int snap) +{ + if (hpcap == NULL) + return (1); + + hpcap->snapshot = snap; + set_pcap_filter(); + + return 0; +} + + +/* + * send the snaplength to privileged process + */ +int +priv_set_snaplen(int snaplen) +{ + int cmd, ret; + + if (priv_fd < 0) + errx(1, "%s: called from privileged portion", __func__); + + cmd = PRIV_SET_SNAPLEN; + + must_write(priv_fd, &cmd, sizeof(int)); + must_write(priv_fd, &snaplen, sizeof(int)); + + must_read(priv_fd, &ret, sizeof(int)); + + /* also set hpcap->snapshot in child */ + if (ret == 0) + hpcap->snapshot = snaplen; + + return (ret); +} + +/* Open log-file */ +int +priv_open_log(void) +{ + int cmd, fd; + + if (priv_fd < 0) + errx(1, "%s: called from privileged portion\n", __func__); + + cmd = PRIV_OPEN_LOG; + must_write(priv_fd, &cmd, sizeof(int)); + fd = receive_fd(priv_fd); + + return (fd); +} + +/* If priv parent gets a TERM or HUP, pass it through to child instead */ +static void +sig_pass_to_chld(int sig) +{ + int oerrno = errno; + + if (child_pid != -1) + kill(child_pid, sig); + errno = oerrno; +} + +/* if parent gets a SIGCHLD, it will exit */ +static void +sig_chld(int sig __unused) +{ + gotsig_chld = 1; +} + +/* Read all data or return 1 for error. */ +static int +may_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res; + size_t pos = 0; + + while (n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + return (1); + default: + pos += res; + } + } + return (0); +} + +/* Read data with the assertion that it all must come through, or + * else abort the process. Based on atomicio() from openssh. */ +static void +must_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res; + size_t pos = 0; + + while (n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} + +/* Write data with the assertion that it all has to be written, or + * else abort the process. Based on atomicio() from openssh. */ +static void +must_write(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res; + size_t pos = 0; + + while (n > pos) { + res = write(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} diff --git a/usr.sbin/pflogd/privsep_fdpass.c b/usr.sbin/pflogd/privsep_fdpass.c new file mode 100644 index 0000000000..50bfad0627 --- /dev/null +++ b/usr.sbin/pflogd/privsep_fdpass.c @@ -0,0 +1,121 @@ +/* $OpenBSD: privsep_fdpass.c,v 1.1 2003/10/22 18:51:55 canacar Exp $ */ +/* $DragonFly: src/usr.sbin/pflogd/privsep_fdpass.c,v 1.1 2004/09/21 21:25:28 joerg Exp $ */ + +/* + * Copyright 2001 Niels Provos + * All rights reserved. + * + * Copyright (c) 2002 Matthieu Herrb + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pflogd.h" + +void +send_fd(int sock, int fd) +{ + struct msghdr msg; + char tmp[CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsg; + struct iovec vec; + int result = 0; + ssize_t n; + + memset(&msg, 0, sizeof(msg)); + + if (fd >= 0) { + msg.msg_control = (caddr_t)tmp; + msg.msg_controllen = CMSG_LEN(sizeof(int)); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = fd; + } else { + result = errno; + } + + vec.iov_base = (caddr_t)&result; + vec.iov_len = sizeof(int); + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + + if ((n = sendmsg(sock, &msg, 0)) == -1) + warn("%s: sendmsg(%d)", __func__, sock); + if (n != sizeof(int)) + warnx("%s: sendmsg: expected sent 1 got %ld", + __func__, (long)n); +} + +int +receive_fd(int sock) +{ + struct msghdr msg; + char tmp[CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsg; + struct iovec vec; + ssize_t n; + int result; + int fd; + + memset(&msg, 0, sizeof(msg)); + vec.iov_base = (caddr_t)&result; + vec.iov_len = sizeof(int); + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_control = tmp; + msg.msg_controllen = sizeof(tmp); + + if ((n = recvmsg(sock, &msg, 0)) == -1) + warn("%s: recvmsg", __func__); + if (n != sizeof(int)) + warnx("%s: recvmsg: expected received 1 got %ld", + __func__, (long)n); + if (result == 0) { + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg->cmsg_type != SCM_RIGHTS) + warnx("%s: expected type %d got %d", __func__, + SCM_RIGHTS, cmsg->cmsg_type); + fd = (*(int *)CMSG_DATA(cmsg)); + return fd; + } else { + errno = result; + return -1; + } +}