Fri Sep 19 19:22:39 2008 UTC ()
Pull in some things missed in a previous sync


(wrstuden)
diff -r0 -r1.2.28.3 src/dist/pf/libexec/ftp-proxy/Makefile
diff -r0 -r1.10.8.3 src/dist/pf/libexec/ftp-proxy/ftp-proxy.8
diff -r0 -r1.12.18.3 src/dist/pf/libexec/ftp-proxy/ftp-proxy.c
diff -r0 -r1.4.2.3 src/dist/pf/libexec/ftp-proxy/ipf.c
diff -r0 -r1.2.2.2 src/dist/pf/libexec/tftp-proxy/Makefile
diff -r0 -r1.2.2.2 src/dist/pf/libexec/tftp-proxy/filter.c
diff -r0 -r1.2.2.2 src/dist/pf/libexec/tftp-proxy/filter.h
diff -r0 -r1.2.2.2 src/dist/pf/libexec/tftp-proxy/tftp-proxy.8
diff -r0 -r1.2.2.2 src/dist/pf/libexec/tftp-proxy/tftp-proxy.c

File Added: src/dist/pf/libexec/ftp-proxy/Attic/Makefile
#	$NetBSD: Makefile,v 1.2.28.3 2008/09/19 19:22:39 wrstuden Exp $
#	$OpenBSD: Makefile,v 1.4 2003/11/20 23:23:09 avsm Exp $
#	@(#)Makefile	8.2 (Berkeley) 4/4/94

PROG=	ftp-proxy
CFLAGS+=-Wall
SRCS=	ftp-proxy.c getline.c util.c
MAN=	ftp-proxy.8

.include <bsd.own.mk>

.if (${TCP_WRAPPERS:L} == "yes")
CFLAGS+= -DLIBWRAP
LDADD+=	-lwrap
DPADD+=	${LIBWRAP}
.endif

.include <bsd.prog.mk>

File Added: src/dist/pf/libexec/ftp-proxy/Attic/ftp-proxy.8
.\"	$NetBSD: ftp-proxy.8,v 1.10.8.3 2008/09/19 19:22:39 wrstuden Exp $
.\"	$OpenBSD: ftp-proxy.8,v 1.40 2004/03/16 08:50:07 jmc Exp $
.\"
.\" Copyright (c) 1996-2001
.\"	Obtuse Systems Corporation, All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\"    notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\"    notice, this list of conditions and the following disclaimer in the
.\"    documentation and/or other materials provided with the distribution.
.\" 3. Neither the name of the University nor the names of its contributors
.\"    may be used to endorse or promote products derived from this software
.\"    without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED.  IN NO EVENT SHALL OBTUSE OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd August 17, 2001
.Dt FTP-PROXY 8
.Os
.Sh NAME
.Nm ftp-proxy
.Nd Internet File Transfer Protocol proxy server
.Sh SYNOPSIS
.Nm ftp-proxy
.Op Fl AnrVw
.Op Fl 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.

File Added: src/dist/pf/libexec/ftp-proxy/Attic/ftp-proxy.c
/*	$NetBSD: ftp-proxy.c,v 1.12.18.3 2008/09/19 19:22:39 wrstuden Exp $	*/
/*	$OpenBSD: ftp-proxy.c,v 1.35 2004/03/14 21:51:44 dhartmei Exp $ */

/*
 * Copyright (c) 1996-2001
 *	Obtuse Systems Corporation.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Obtuse Systems nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/*
 * ftp proxy, Originally based on juniper_ftp_proxy from the Obtuse
 * Systems juniper firewall, written by Dan Boulet <danny@obtuse.com>
 * and Bob Beck <beck@obtuse.com>
 *
 * This version basically passes everything through unchanged except
 * for the PORT and the * "227 Entering Passive Mode" reply.
 *
 * A PORT command is handled by noting the IP address and port number
 * specified and then configuring a listen port on some very high port
 * number and telling the server about it using a PORT message.
 * We then watch for an in-bound connection on the port from the server
 * and connect to the client's port when it happens.
 *
 * A "227 Entering Passive Mode" reply is handled by noting the IP address
 * and port number specified and then configuring a listen port on some
 * very high port number and telling the client about it using a
 * "227 Entering Passive Mode" reply.
 * We then watch for an in-bound connection on the port from the client
 * and connect to the server's port when it happens.
 *
 * supports tcp wrapper lookups/access control with the -w flag using
 * the real destination address - the tcp wrapper stuff is done after
 * the real destination address is retrieved from pf
 *
 */

/*
 * TODO:
 * Plenty, this is very basic, with the idea to get it in clean first.
 *
 * - IPv6 and EPASV support
 * - Content filter support
 * - filename filter support
 * - per-user rules perhaps.
 */

#include <sys/param.h>
#include <sys/time.h>
#include <sys/socket.h>

#include <net/if.h>
#include <netinet/in.h>

#include <arpa/inet.h>

#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <unistd.h>

#include "util.h"

#ifdef LIBWRAP
#include <tcpd.h>
int allow_severity = LOG_INFO;
int deny_severity = LOG_NOTICE;
#endif /* LIBWRAP */

int min_port = IPPORT_ANONMIN;
int max_port = IPPORT_ANONMAX;

#define STARTBUFSIZE  1024	/* Must be at least 3 */

/*
 * Variables used to support PORT mode connections.
 *
 * This gets a bit complicated.
 *
 * If PORT mode is on then client_listen_sa describes the socket that
 * the real client is listening on and server_listen_sa describes the
 * socket that we are listening on (waiting for the real server to connect
 * with us).
 *
 * If PASV mode is on then client_listen_sa describes the socket that
 * we are listening on (waiting for the real client to connect to us on)
 * and server_listen_sa describes the socket that the real server is
 * listening on.
 *
 * If the socket we are listening on gets a connection then we connect
 * to the other side's socket.  Similarly, if a connected socket is
 * shutdown then we shutdown the other side's socket.
 */

double xfer_start_time;

struct sockaddr_in real_server_sa;
struct sockaddr_in client_listen_sa;
struct sockaddr_in server_listen_sa;

int client_listen_socket = -1;	/* Only used in PASV mode */
int client_data_socket = -1;	/* Connected socket to real client */
int server_listen_socket = -1;	/* Only used in PORT mode */
int server_data_socket = -1;	/* Connected socket to real server */
int client_data_bytes, server_data_bytes;

int AnonFtpOnly;
int Verbose;
int NatMode;

char ClientName[NI_MAXHOST];
char RealServerName[NI_MAXHOST];
char OurName[NI_MAXHOST];

char *User = "proxy";
char *Group;

extern int Debug_Level;
extern int Use_Rdns;
extern 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 (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 >= len)
				goto logit;
			len -= i;
		} else {
			i = snprintf(tbuf, len,
			    "data transfer complete (%dm %ds", idelta / 60,
			    idelta % 60);
			if (i >= len)
				goto logit;
			len -= i;
		}
	} else {
		i = snprintf(tbuf, len, "data transfer complete (%.1fs",
		    delta);
		if (i >= len)
			goto logit;
		len -= i;
	}

	if (client_data_bytes > 0) {
		i = snprintf(&tbuf[strlen(tbuf)], len,
		    ", %d bytes to server) (%.1fKB/s", client_data_bytes,
		    (client_data_bytes / delta) / (double)1024);
		if (i >= len)
			goto logit;
		len -= i;
	}
	if (server_data_bytes > 0) {
		i = snprintf(&tbuf[strlen(tbuf)], len,
		    ", %d bytes to client) (%.1fKB/s", server_data_bytes,
		    (server_data_bytes / delta) / (double)1024);
		if (i >= len)
			goto logit;
		len -= i;
	}
	strlcat(tbuf, ")", sizeof(tbuf));
 logit:
	syslog(LOG_INFO, "%s", tbuf);
}

void
log_control_command (char *cmd, int client)
{
	/* log an ftp control command or reply */
	char *logstring;
	int level = LOG_DEBUG;

	if (!Verbose)
		return;

	/* don't log passwords */
	if (strncasecmp(cmd, "pass ", 5) == 0)
		logstring = "PASS XXXX";
	else
		logstring = cmd;
	if (client) {
		/* log interesting stuff at LOG_INFO, rest at LOG_DEBUG */
		if ((strncasecmp(cmd, "user ", 5) == 0) ||
		    (strncasecmp(cmd, "retr ", 5) == 0) ||
		    (strncasecmp(cmd, "cwd ", 4) == 0) ||
		    (strncasecmp(cmd, "stor " ,5) == 0))
			level = LOG_INFO;
	}
	syslog(level, "%s %s", client ? "client:" : " server:",
	    logstring);
}

/*
 * set ourselves up for a new data connection. Direction is toward client if
 * "server" is 0, towards server otherwise.
 */
int
new_dataconn(int server)
{
	/*
	 * Close existing data conn.
	 */

	if (client_listen_socket != -1) {
		close(client_listen_socket);
		client_listen_socket = -1;
	}
	close_client_data();

	if (server_listen_socket != -1) {
		close(server_listen_socket);
		server_listen_socket = -1;
	}
	close_server_data();

	if (server) {
		bzero(&server_listen_sa, sizeof(server_listen_sa));
		server_listen_socket = get_backchannel_socket(SOCK_STREAM,
		    min_port, max_port, -1, 1, &server_listen_sa);

		if (server_listen_socket == -1) {
			syslog(LOG_INFO, "server socket bind() failed (%m)");
			exit(EX_OSERR);
		}
		if (listen(server_listen_socket, 5) != 0) {
			syslog(LOG_INFO, "server socket listen() failed (%m)");
			exit(EX_OSERR);
		}
	} else {
		bzero(&client_listen_sa, sizeof(client_listen_sa));
		client_listen_socket = get_backchannel_socket(SOCK_STREAM,
		    min_port, max_port, -1, 1, &client_listen_sa);

		if (client_listen_socket == -1) {
			syslog(LOG_NOTICE,
			    "cannot get client listen socket (%m)");
			exit(EX_OSERR);
		}
		if (listen(client_listen_socket, 5) != 0) {
			syslog(LOG_NOTICE,
			    "cannot listen on client socket (%m)");
			exit(EX_OSERR);
		}
	}
	return(0);
}

static void
connect_pasv_backchannel(void)
{
	struct sockaddr_in listen_sa;
	socklen_t salen;

	/*
	 * We are about to accept a connection from the client.
	 * This is a PASV data connection.
	 */
	debuglog(2, "client listen socket ready");

	close_server_data();
	close_client_data();

	salen = sizeof(listen_sa);
	client_data_socket = accept(client_listen_socket,
	    (struct sockaddr *)&listen_sa, &salen);

	if (client_data_socket < 0) {
		syslog(LOG_NOTICE, "accept() failed (%m)");
		exit(EX_OSERR);
	}
	close(client_listen_socket);
	client_listen_socket = -1;
	memset(&listen_sa, 0, sizeof(listen_sa));

	server_data_socket = get_backchannel_socket(SOCK_STREAM, min_port,
	    max_port, -1, 1, &listen_sa);
	if (server_data_socket < 0) {
		syslog(LOG_NOTICE, "get_backchannel_socket() failed (%m)");
		exit(EX_OSERR);
	}
	if (connect(server_data_socket, (struct sockaddr *) &server_listen_sa,
	    sizeof(server_listen_sa)) != 0) {
		syslog(LOG_NOTICE, "connect() failed (%m)");
		exit(EX_NOHOST);
	}
	client_data_bytes = 0;
	server_data_bytes = 0;
	xfer_start_time = wallclock_time();
}

static void
connect_port_backchannel(void)
{
	struct sockaddr_in listen_sa;
	socklen_t salen;

	/*
	 * We are about to accept a connection from the server.
	 * This is a PORT or EPRT data connection.
	 */
	debuglog(2, "server listen socket ready");

	close_server_data();
	close_client_data();

	salen = sizeof(listen_sa);
	server_data_socket = accept(server_listen_socket,
	    (struct sockaddr *)&listen_sa, &salen);
	if (server_data_socket < 0) {
		syslog(LOG_NOTICE, "accept() failed (%m)");
		exit(EX_OSERR);
	}
	close(server_listen_socket);
	server_listen_socket = -1;

	if (getuid() != 0)  {
		/*
		 * We're not running as root, so we get a backchannel
		 * socket bound in our designated range, instead of
		 * getting one bound to port 20 - This is deliberately
		 * not RFC compliant.
		 */
		bzero(&listen_sa.sin_addr, sizeof(struct in_addr));
		client_data_socket =  get_backchannel_socket(SOCK_STREAM,
		    min_port, max_port, -1, 1, &listen_sa);
		if (client_data_socket < 0) {
			syslog(LOG_NOTICE,  "get_backchannel_socket() failed (%m)");
			exit(EX_OSERR);
		}

	} else {

		/*
		 * We're root, get our backchannel socket bound to port
		 * 20 here, so we're fully RFC compliant.
		 */
		client_data_socket = socket(AF_INET, SOCK_STREAM, 0);

		salen = 1;
		listen_sa.sin_family = AF_INET;
		bzero(&listen_sa.sin_addr, sizeof(struct in_addr));
		listen_sa.sin_port = htons(20);

		if (setsockopt(client_data_socket, SOL_SOCKET, SO_REUSEADDR,
		    &salen, sizeof(salen)) == -1) {
			syslog(LOG_NOTICE, "setsockopt() failed (%m)");
			exit(EX_OSERR);
		}

		if (bind(client_data_socket, (struct sockaddr *)&listen_sa,
		    sizeof(listen_sa)) == - 1) {
			syslog(LOG_NOTICE, "data channel bind() failed (%m)");
			exit(EX_OSERR);
		}
	}

	if (connect(client_data_socket, (struct sockaddr *) &client_listen_sa,
	    sizeof(client_listen_sa)) != 0) {
		syslog(LOG_INFO, "cannot connect data channel (%m)");
		exit(EX_NOHOST);
	}

	client_data_bytes = 0;
	server_data_bytes = 0;
	xfer_start_time = wallclock_time();
}

void
do_client_cmd(struct csiob *client, struct csiob *server)
{
	int i, j, rv;
	char tbuf[100];
	char *sendbuf = NULL;

	log_control_command((char *)client->line_buffer, 1);

	/* client->line_buffer is an ftp control command.
	 * There is no reason for these to be very long.
	 * In the interest of limiting buffer overrun attempts,
	 * we catch them here.
	 */
	if (strlen((char *)client->line_buffer) > 512) {
		syslog(LOG_NOTICE, "excessively long control command");
		exit(EX_DATAERR);
	}

	/*
	 * Check the client user provided if needed
	 */
	if (AnonFtpOnly && strncasecmp((char *)client->line_buffer, "user ",
	    strlen("user ")) == 0) {
		char *cp;

		cp = (char *) client->line_buffer + strlen("user ");
		if ((strcasecmp(cp, "ftp\r\n") != 0) &&
		    (strcasecmp(cp, "anonymous\r\n") != 0)) {
			/*
			 * this isn't anonymous - give the client an
			 * error before they send a password
			 */
			snprintf(tbuf, sizeof(tbuf),
			    "500 Only anonymous FTP is allowed\r\n");
			j = 0;
			i = strlen(tbuf);
			do {
				rv = send(client->fd, tbuf + j, i - j, 0);
				if (rv == -1 && errno != EAGAIN &&
				    errno != EINTR)
					break;
				else if (rv != -1)
					j += rv;
			} while (j >= 0 && j < i);
			sendbuf = NULL;
		} else
			sendbuf = (char *)client->line_buffer;
	} else if ((strncasecmp((char *)client->line_buffer, "eprt ",
	    strlen("eprt ")) == 0)) {

		/* Watch out for EPRT commands */
		char *line = NULL,  *q, *p, *result[3], delim;
		struct addrinfo hints, *res = NULL;
		unsigned long proto;

		j = 0;
		line = strdup((char *)client->line_buffer+strlen("eprt "));
		if (line == NULL) {
			syslog(LOG_ERR, "insufficient memory");
			exit(EX_UNAVAILABLE);
		}
		p = line;
		delim = p[0];
		p++;

		memset(result,0, sizeof(result));
		for (i = 0; i < 3; i++) {
			q = strchr(p, delim);
			if (!q || *q != delim)
				goto parsefail;
			*q++ = '\0';
			result[i] = p;
			p = q;
		}

		proto = strtoul(result[0], &p, 10);
		if (!*result[0] || *p)
			goto protounsupp;

		memset(&hints, 0, sizeof(hints));
		if (proto != 1) /* 1 == AF_INET - all we support for now */
			goto protounsupp;
		hints.ai_family = AF_INET;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags = AI_NUMERICHOST;	/*no DNS*/
		if (getaddrinfo(result[1], result[2], &hints, &res))
			goto parsefail;
		if (res->ai_next)
			goto parsefail;
		if (sizeof(client_listen_sa) < res->ai_addrlen)
			goto parsefail;
		memcpy(&client_listen_sa, res->ai_addr, res->ai_addrlen);

		debuglog(1, "client wants us to use %s:%u",
		    inet_ntoa(client_listen_sa.sin_addr),
		    htons(client_listen_sa.sin_port));

		/*
		 * Configure our own listen socket and tell the server about it
		 */
		new_dataconn(1);
		connection_mode = EPRT_MODE;

		debuglog(1, "we want server to use %s:%u",
		    inet_ntoa(server->sa.sin_addr),
		    ntohs(server_listen_sa.sin_port));

		snprintf(tbuf, sizeof(tbuf), "EPRT |%d|%s|%u|\r\n", 1,
		    inet_ntoa(server->sa.sin_addr),
		    ntohs(server_listen_sa.sin_port));
		debuglog(1, "to server (modified): %s", tbuf);
		sendbuf = tbuf;
		goto out;
parsefail:
		snprintf(tbuf, sizeof(tbuf),
		    "500 Invalid argument; rejected\r\n");
		sendbuf = NULL;
		goto out;
protounsupp:
		/* we only support AF_INET for now */
		if (proto == 2)
			snprintf(tbuf, sizeof(tbuf),
			    "522 Protocol not supported, use (1)\r\n");
		else
			snprintf(tbuf, sizeof(tbuf),
			    "501 Protocol not supported\r\n");
		sendbuf = NULL;
out:
		if (line)
			free(line);
		if (res)
			freeaddrinfo(res);
		if (sendbuf == NULL) {
			debuglog(1, "to client (modified): %s", tbuf);
			i = strlen(tbuf);
			do {
				rv = send(client->fd, tbuf + j, i - j, 0);
				if (rv == -1 && errno != EAGAIN &&
				    errno != EINTR)
					break;
				else if (rv != -1)
					j += rv;
			} while (j >= 0 && j < i);
		}
	} else if (!NatMode && (strncasecmp((char *)client->line_buffer,
	    "epsv", strlen("epsv")) == 0)) {

		/*
		 * If we aren't in NAT mode, deal with EPSV.
		 * EPSV is a problem - Unlike PASV, the reply from the
		 * server contains *only* a port, we can't modify the reply
		 * to the client and get the client to connect to us without
		 * resorting to using a dynamic rdr rule we have to add in
		 * for the reply to this connection, and take away afterwards.
		 * so this will wait until we have the right solution for rule
		 * additions/deletions in pf.
		 *
		 * in the meantime we just tell the client we don't do it,
		 * and most clients should fall back to using PASV.
		 */

		snprintf(tbuf, sizeof(tbuf),
		    "500 EPSV command not understood\r\n");
		debuglog(1, "to client (modified): %s", tbuf);
		j = 0;
		i = strlen(tbuf);
		do {
			rv = send(client->fd, tbuf + j, i - j, 0);
			if (rv == -1 && errno != EAGAIN && errno != EINTR)
				break;
			else if (rv != -1)
				j += rv;
		} while (j >= 0 && j < i);
		sendbuf = NULL;
	} else if (strncasecmp((char *)client->line_buffer, "port ",
	    strlen("port ")) == 0) {
		unsigned int values[6];
		char *tailptr;

		debuglog(1, "Got a PORT command");

		tailptr = (char *)&client->line_buffer[strlen("port ")];
		values[0] = 0;

		i = sscanf(tailptr, "%u,%u,%u,%u,%u,%u", &values[0],
		    &values[1], &values[2], &values[3], &values[4],
		    &values[5]);
		if (i != 6) {
			syslog(LOG_INFO, "malformed PORT command (%s)",
			    client->line_buffer);
			exit(EX_DATAERR);
		}

		for (i = 0; i<6; i++) {
			if (values[i] > 255) {
				syslog(LOG_INFO,
				    "malformed PORT command (%s)",
				    client->line_buffer);
				exit(EX_DATAERR);
			}
		}

		client_listen_sa.sin_family = AF_INET;
		client_listen_sa.sin_addr.s_addr = htonl((values[0] << 24) |
		    (values[1] << 16) | (values[2] <<  8) |
		    (values[3] <<  0));

		client_listen_sa.sin_port = htons((values[4] << 8) |
		    values[5]);
		debuglog(1, "client wants us to use %u.%u.%u.%u:%u",
		    values[0], values[1], values[2], values[3],
		    (values[4] << 8) | values[5]);

		/*
		 * Configure our own listen socket and tell the server about it
		 */
		new_dataconn(1);
		connection_mode = PORT_MODE;

		debuglog(1, "we want server to use %s:%u",
		    inet_ntoa(server->sa.sin_addr),
		    ntohs(server_listen_sa.sin_port));

		snprintf(tbuf, sizeof(tbuf), "PORT %u,%u,%u,%u,%u,%u\r\n",
		    ((u_char *)&server->sa.sin_addr.s_addr)[0],
		    ((u_char *)&server->sa.sin_addr.s_addr)[1],
		    ((u_char *)&server->sa.sin_addr.s_addr)[2],
		    ((u_char *)&server->sa.sin_addr.s_addr)[3],
		    ((u_char *)&server_listen_sa.sin_port)[0],
		    ((u_char *)&server_listen_sa.sin_port)[1]);

		debuglog(1, "to server (modified): %s", tbuf);

		sendbuf = tbuf;
	} else
		sendbuf = (char *)client->line_buffer;

	/*
	 *send our (possibly modified) control command in sendbuf
	 * on it's way to the server
	 */
	if (sendbuf != NULL) {
		j = 0;
		i = strlen(sendbuf);
		do {
			rv = send(server->fd, sendbuf + j, i - j, 0);
			if (rv == -1 && errno != EAGAIN && errno != EINTR)
				break;
			else if (rv != -1)
				j += rv;
		} while (j >= 0 && j < i);
	}
}

void
do_server_reply(struct csiob *server, struct csiob *client)
{
	int code, i, j, rv;
	struct in_addr *iap;
	static int continuing = 0;
	char tbuf[100], *sendbuf, *p;

	log_control_command((char *)server->line_buffer, 0);

	if (strlen((char *)server->line_buffer) > 512) {
		/*
		 * someone's playing games. Have a cow in the syslogs and
		 * exit - we don't pass this on for fear of hurting
		 * our other end, which might be poorly implemented.
		 */
		syslog(LOG_NOTICE, "long FTP control reply");
		exit(EX_DATAERR);
	}

	/*
	 * Watch out for "227 Entering Passive Mode ..." replies
	 */
	code = strtol((char *)server->line_buffer, &p, 10);
	if (isspace(server->line_buffer[0]))
		code = 0;
	if (!*(server->line_buffer) || (*p != ' ' && *p != '-')) {
		if (continuing)
			goto sendit;
		syslog(LOG_INFO, "malformed control reply");
		exit(EX_DATAERR);
	}
	if (code <= 0 || code > 999) {
		if (continuing)
			goto sendit;
		syslog(LOG_INFO, "invalid server reply code %d", code);
		exit(EX_DATAERR);
	}
	if (*p == '-')
		continuing = 1;
	else
		continuing = 0;
	if (code == 227 && !NatMode) {
		unsigned int values[6];
		char *tailptr;

		debuglog(1, "Got a PASV reply");
		debuglog(1, "{%s}", (char *)server->line_buffer);

		tailptr = (char *)strchr((char *)server->line_buffer, '(');
		if (tailptr == NULL) {
			tailptr = strrchr((char *)server->line_buffer, ' ');
			if (tailptr == NULL) {
				syslog(LOG_NOTICE, "malformed 227 reply");
				exit(EX_DATAERR);
			}
		}
		tailptr++; /* skip past space or ( */

		values[0] = 0;

		i = sscanf(tailptr, "%u,%u,%u,%u,%u,%u", &values[0],
		    &values[1], &values[2], &values[3], &values[4],
		    &values[5]);
		if (i != 6) {
			syslog(LOG_INFO, "malformed PASV reply (%s)",
			    client->line_buffer);
			exit(EX_DATAERR);
		}
		for (i = 0; i<6; i++)
			if (values[i] > 255) {
				syslog(LOG_INFO, "malformed PASV reply(%s)",
				    client->line_buffer);
				exit(EX_DATAERR);
			}

		server_listen_sa.sin_family = AF_INET;
		server_listen_sa.sin_addr.s_addr = htonl((values[0] << 24) |
		    (values[1] << 16) | (values[2] <<  8) | (values[3] <<  0));
		server_listen_sa.sin_port = htons((values[4] << 8) |
		    values[5]);

		debuglog(1, "server wants us to use %s:%u",
		    inet_ntoa(server_listen_sa.sin_addr), (values[4] << 8) |
		    values[5]);

		new_dataconn(0);
		connection_mode = PASV_MODE;
		iap = &(server->sa.sin_addr);

		debuglog(1, "we want client to use %s:%u", inet_ntoa(*iap),
		    htons(client_listen_sa.sin_port));

		snprintf(tbuf, sizeof(tbuf),
		    "227 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n",
		    ((u_char *)iap)[0], ((u_char *)iap)[1],
		    ((u_char *)iap)[2], ((u_char *)iap)[3],
		    ((u_char *)&client_listen_sa.sin_port)[0],
		    ((u_char *)&client_listen_sa.sin_port)[1]);
		debuglog(1, "to client (modified): %s", tbuf);
		sendbuf = tbuf;
	} else {
 sendit:
		sendbuf = (char *)server->line_buffer;
	}

	/*
	 * send our (possibly modified) control command in sendbuf
	 * on it's way to the client
	 */
	j = 0;
	i = strlen(sendbuf);
	do {
		rv = send(client->fd, sendbuf + j, i - j, 0);
		if (rv == -1 && errno != EAGAIN && errno != EINTR)
			break;
		else if (rv != -1)
			j += rv;
	} while (j >= 0 && j < i);

}

int
main(int argc, char *argv[])
{
	struct csiob client_iob, server_iob;
	struct sigaction new_sa, old_sa;
	int sval, ch, flags, i;
	socklen_t salen;
	int one = 1;
	long timeout_seconds = 0;
	struct timeval tv;
#ifdef LIBWRAP
	int use_tcpwrapper = 0;
#endif /* LIBWRAP */

	while ((ch = getopt(argc, argv, "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,
				    client_iob.sa.sin_addr,
				    real_server_sa.sin_addr);
				if (rval <= 0) {
					close_client_data();
					close_server_data();
					show_xfer_stats();
				} else
					client_data_bytes += rval;
			}
			if (server_data_socket >= 0 &&
			    FD_ISSET(server_data_socket, fdsp)) {
				int rval;

				debuglog(3, "transfer: server to client");
				rval = xfer_data("server to client",
				    server_data_socket,
				    client_data_socket,
				    real_server_sa.sin_addr,
				    client_iob.sa.sin_addr);
				if (rval <= 0) {
					close_client_data();
					close_server_data();
					show_xfer_stats();
				} else
					server_data_bytes += rval;
			}
			if (server_listen_socket >= 0 &&
			    FD_ISSET(server_listen_socket, fdsp)) {
				connect_port_backchannel();
			}
			if (client_listen_socket >= 0 &&
			    FD_ISSET(client_listen_socket, fdsp)) {
				connect_pasv_backchannel();
			}
			if (client_iob.alive &&
			    FD_ISSET(client_iob.fd, fdsp)) {
				client_iob.data_available = 1;
			}
			if (server_iob.alive &&
			    FD_ISSET(server_iob.fd, fdsp)) {
				server_iob.data_available = 1;
			}
		}
		free(fdsp);
		if (client_iob.got_eof) {
			shutdown(server_iob.fd, 1);
			shutdown(client_iob.fd, 0);
			client_iob.got_eof = 0;
			client_iob.alive = 0;
		}
		if (server_iob.got_eof) {
			shutdown(client_iob.fd, 1);
			shutdown(server_iob.fd, 0);
			server_iob.got_eof = 0;
			server_iob.alive = 0;
		}
	}

	if (Verbose)
		syslog(LOG_INFO, "session ended");

	exit(EX_OK);
}

File Added: src/dist/pf/libexec/ftp-proxy/Attic/ipf.c
/*	$NetBSD: ipf.c,v 1.4.2.3 2008/09/19 19:22:39 wrstuden Exp $	*/

/*
 * Copyright (c) 2004 The NetBSD Foundation, Inc.
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by the NetBSD
 *        Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <net/if.h>
#include <netinet/ipl.h>
#include <netinet/ip_compat.h>
#include <netinet/ip_fil.h>
#include <netinet/ip_nat.h>

#include <arpa/inet.h>

#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <unistd.h>

#include "util.h"

extern int ReverseMode;

static natlookup_t natlook;
static int natfd;

int
ipf_get_proxy_env(int connected_fd, struct sockaddr_in *server,
    struct sockaddr_in *client, struct sockaddr_in *proxy_sa_ptr)
{
	socklen_t namelen;
	ipfobj_t obj;

	/*
	 * Get IP# and port # of the local end of the connection
	 * (at the origin)
	 */
	namelen = sizeof(*proxy_sa_ptr);
	if (getsockname(connected_fd, (struct sockaddr *)proxy_sa_ptr,
			&namelen) != 0) {
		syslog(LOG_ERR, "getsockname() failed (%m)");
		exit(EX_OSERR);
	}

	/*
	 * Get IP# and port # of the remote end of the connection
	 * (at the origin)
	 */
	namelen = sizeof(*client);
	if (getpeername(connected_fd, (struct sockaddr *)client,
			&namelen) != 0) {
		syslog(LOG_ERR, "getpeername() failed (%m)");
		exit(EX_OSERR);
	}

	if (ReverseMode)
		return(0);

	/*
	 * Build up the ipf object description structure.
	 */
	memset((void *)&obj, 0, sizeof(obj));
	obj.ipfo_rev = IPFILTER_VERSION;
	obj.ipfo_size = sizeof(natlook);
	obj.ipfo_ptr = &natlook;
	obj.ipfo_type = IPFOBJ_NATLOOKUP;
	/*
	 * Build up the ipf natlook structure.
	 */
	memset((void *)&natlook, 0, sizeof(natlook));
	natlook.nl_flags = IPN_TCPUDP;
	natlook.nl_outip = client->sin_addr;
	natlook.nl_inip = proxy_sa_ptr->sin_addr;
	natlook.nl_outport = ntohs(client->sin_port);
	natlook.nl_inport = ntohs(proxy_sa_ptr->sin_port);

	/*
	 * Open the NAT device and lookup the mapping pair.
	 */
	natfd = open(IPNAT_NAME, O_RDONLY);
	if (natfd == -1) {
		syslog(LOG_ERR, "cannot open %s (%m)", IPNAT_NAME);
		exit(EX_UNAVAILABLE);
	}

	if (ioctl(natfd, SIOCGNATL, &obj) == -1) {
		syslog(LOG_INFO,
		    "ipf nat lookup failed %s:%hu (%m)",
		    inet_ntoa(client->sin_addr),
		    ntohs(client->sin_port));
		exit(EX_OSERR);
	}

	/*
	 * Return the real destination address and port number in the sockaddr
	 * passed in.
	 */
	memset((void *)server, 0, sizeof(struct sockaddr_in));
	server->sin_port = natlook.nl_realport;
	server->sin_addr = natlook.nl_realip;
	server->sin_len = sizeof(struct sockaddr_in);
	server->sin_family = AF_INET;
	return(0);
}


#if 0
/*
 * This code is currently #if 0'd out because it will cause a mismatch between
 * the IP address the FTP server sees on its command channel and its data
 * channels.  Why will it see that?  Because the proxy code has not yet been
 * changed to "fix" the data channels.
 */
/*
 * To make the proxy appear to be "more transparent", create a NAT table entry
 * that just maps the outgoing connection to the ftp server, making it look
 * like the client is actually connecting, not the proxy server.
 */
/*
 * Another possibility here is to create a state table entry using SIOCSTPUT
 * so we don't need to have an ipf rule that allows great swathes of outbound
 * connections to any port.
 */
int
ipf_tconnect(const char *extif, const int fd, const struct sockaddr *sock,
    socklen_t socklen)
{
	nat_save_t ns, *nsp = &ns;
	struct sockaddr_in usin;
	u_32_t sum1, sum2, sumd;
	int onoff, ofd, slen;
	natlookup_t *nlp;
	nat_t *nat;

	memset((void *)&ns, 0, sizeof(ns));

	nlp = &natlook;
	nat = &nsp->ipn_nat;
	nat->nat_p = IPPROTO_TCP;
	nat->nat_dir = NAT_OUTBOUND;
	if ((extif != NULL) && (*extif != '\0')) {
		strlcpy(nat->nat_ifnames[0], extif,
			sizeof(nat->nat_ifnames[0]));
		strlcpy(nat->nat_ifnames[1], extif,
			sizeof(nat->nat_ifnames[1]));
	}

	ofd = socket(AF_INET, SOCK_DGRAM, 0);
	memset((void *)&usin, 0, sizeof(usin));
	usin.sin_family = AF_INET;
	usin.sin_addr = nlp->nl_realip;
	usin.sin_port = nlp->nl_realport;
	(void) connect(ofd, (struct sockaddr *)&usin, sizeof(usin));
	slen = sizeof(usin);
	(void) getsockname(ofd, (struct sockaddr *)&usin, &slen);
	close(ofd);

	usin.sin_port = 0;
	if (bind(fd, sock, slen)) {
		syslog(LOG_ERR, "error binding outbound socket (%s):%m",
		    inet_ntoa(usin.sin_addr));
		exit(EX_OSERR);
	}
	slen = sizeof(usin);
	if (getsockname(fd, (struct sockaddr *)&usin, &slen)) {
		syslog(LOG_ERR, "getsockname error on outbound socket: %m");
		exit(EX_OSERR);
	}

	nat->nat_inip = usin.sin_addr;
	nat->nat_outip = nlp->nl_outip;
	nat->nat_oip = nlp->nl_realip;

	sum1 = LONG_SUM(ntohl(usin.sin_addr.s_addr)) + ntohs(usin.sin_port);
	sum2 = LONG_SUM(ntohl(nat->nat_outip.s_addr)) + ntohs(nlp->nl_outport);
	CALC_SUMD(sum1, sum2, sumd);
	nat->nat_sumd[0] = (sumd & 0xffff) + (sumd >> 16);
	nat->nat_sumd[1] = nat->nat_sumd[0];

	sum1 = LONG_SUM(ntohl(usin.sin_addr.s_addr));
	sum2 = LONG_SUM(ntohl(nat->nat_outip.s_addr));
	CALC_SUMD(sum1, sum2, sumd);
	nat->nat_ipsumd = (sumd & 0xffff) + (sumd >> 16);

	nat->nat_inport = usin.sin_port;
	nat->nat_outport = nlp->nl_outport;
	nat->nat_oport = nlp->nl_realport;

	nat->nat_flags = IPN_TCPUDP;

	onoff = 1;
	if (ioctl(fd, SIOCSTLCK, &onoff) != 0) {
		syslog(LOG_ERR, "Cannot set lock on NAT device: %m");
		exit(EX_OSERR);
	}
	if (ioctl(fd, SIOCSTPUT, &nsp) != 0) {
		syslog(LOG_ERR, "");
		syslog(LOG_ERR, "Cannot add new NAT entry: %m");
		exit(EX_OSERR);
	}
	onoff = 0;
	if (ioctl(fd, SIOCSTLCK, &onoff) != 0) {
		syslog(LOG_ERR, "Cannot unset lock on NAT device: %m");
		exit(EX_OSERR);
	}

	usin.sin_addr = nlp->nl_realip;
	usin.sin_port = nlp->nl_realport;
	return connect(fd, (struct sockaddr *)&usin, sizeof(usin));
}
#endif

File Added: src/dist/pf/libexec/tftp-proxy/Makefile
#	$NetBSD: Makefile,v 1.2.2.2 2008/09/19 19:22:39 wrstuden Exp $
#	$OpenBSD: Makefile,v 1.1 2005/12/28 19:07:07 jcs Exp $

PROG=	tftp-proxy
SRCS=	tftp-proxy.c filter.c
MAN=	tftp-proxy.8

.include <bsd.prog.mk>

File Added: src/dist/pf/libexec/tftp-proxy/filter.c
/*	$NetBSD: filter.c,v 1.2.2.2 2008/09/19 19:22:39 wrstuden Exp $ */
/*	$OpenBSD: filter.c,v 1.2 2007/06/23 15:51:21 jcs Exp $ */

/*
 * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl>
 *
 * 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 <syslog.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <net/if.h>
#include <net/pfvar.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "filter.h"

/* From netinet/in.h, but only _KERNEL_ gets them. */
#define satosin(sa)	((struct sockaddr_in *)(sa))
#define satosin6(sa)	((struct sockaddr_in6 *)(sa))

enum { TRANS_FILTER = 0, TRANS_NAT, TRANS_RDR, TRANS_SIZE };

int prepare_rule(u_int32_t, int, struct sockaddr *, struct sockaddr *,
    u_int16_t, u_int8_t);
int server_lookup4(struct sockaddr_in *, struct sockaddr_in *,
    struct sockaddr_in *, u_int8_t);
int server_lookup6(struct sockaddr_in6 *, struct sockaddr_in6 *,
    struct sockaddr_in6 *, u_int8_t);

static struct pfioc_pooladdr	pfp;
static struct pfioc_rule	pfr;
static struct pfioc_trans	pft;
static struct pfioc_trans_e	pfte[TRANS_SIZE];
static int dev, rule_log;
static char *qname;

int
add_filter(u_int32_t id, u_int8_t dir, struct sockaddr *src,
    struct sockaddr *dst, u_int16_t d_port, u_int8_t proto)
{
	if (!src || !dst || !d_port || !proto) {
		errno = EINVAL;
		return (-1);
	}

	if (prepare_rule(id, PF_RULESET_FILTER, src, dst, d_port, proto) == -1)
		return (-1);

	pfr.rule.direction = dir;
	if (ioctl(dev, DIOCADDRULE, &pfr) == -1)
		return (-1);

	return (0);
}

int
add_nat(u_int32_t id, struct sockaddr *src, struct sockaddr *dst,
    u_int16_t d_port, struct sockaddr *nat, u_int16_t nat_range_low,
    u_int16_t nat_range_high, u_int8_t proto)
{
	if (!src || !dst || !d_port || !nat || !nat_range_low || !proto ||
	    (src->sa_family != nat->sa_family)) {
		errno = EINVAL;
		return (-1);
	}

	if (prepare_rule(id, PF_RULESET_NAT, src, dst, d_port, proto) == -1)
		return (-1);

	if (nat->sa_family == AF_INET) {
		memcpy(&pfp.addr.addr.v.a.addr.v4,
		    &satosin(nat)->sin_addr.s_addr, 4);
		memset(&pfp.addr.addr.v.a.mask.addr8, 255, 4);
	} else {
		memcpy(&pfp.addr.addr.v.a.addr.v6,
		    &satosin6(nat)->sin6_addr.s6_addr, 16);
		memset(&pfp.addr.addr.v.a.mask.addr8, 255, 16);
	}
	if (ioctl(dev, DIOCADDADDR, &pfp) == -1)
		return (-1);

	pfr.rule.rpool.proxy_port[0] = nat_range_low;
	pfr.rule.rpool.proxy_port[1] = nat_range_high;
	if (ioctl(dev, DIOCADDRULE, &pfr) == -1)
		return (-1);

	return (0);
}

int
add_rdr(u_int32_t id, struct sockaddr *src, struct sockaddr *dst,
    u_int16_t d_port, struct sockaddr *rdr, u_int16_t rdr_port, u_int8_t proto)
{
	if (!src || !dst || !d_port || !rdr || !rdr_port || !proto ||
	    (src->sa_family != rdr->sa_family)) {
		errno = EINVAL;
		return (-1);
	}

	if (prepare_rule(id, PF_RULESET_RDR, src, dst, d_port, proto) == -1)
		return (-1);

	if (rdr->sa_family == AF_INET) {
		memcpy(&pfp.addr.addr.v.a.addr.v4,
		    &satosin(rdr)->sin_addr.s_addr, 4);
		memset(&pfp.addr.addr.v.a.mask.addr8, 255, 4);
	} else {
		memcpy(&pfp.addr.addr.v.a.addr.v6,
		    &satosin6(rdr)->sin6_addr.s6_addr, 16);
		memset(&pfp.addr.addr.v.a.mask.addr8, 255, 16);
	}
	if (ioctl(dev, DIOCADDADDR, &pfp) == -1)
		return (-1);

	pfr.rule.rpool.proxy_port[0] = rdr_port;
	if (ioctl(dev, DIOCADDRULE, &pfr) == -1)
		return (-1);

	return (0);
}

int
do_commit(void)
{
	if (ioctl(dev, DIOCXCOMMIT, &pft) == -1)
		return (-1);

	return (0);
}

int
do_rollback(void)
{
	if (ioctl(dev, DIOCXROLLBACK, &pft) == -1)
		return (-1);
	
	return (0);
}

void
init_filter(char *opt_qname, int opt_verbose)
{
	struct pf_status status;

	qname = opt_qname;

	if (opt_verbose == 1)
		rule_log = PF_LOG;
	else if (opt_verbose == 2)
		rule_log = PF_LOG_ALL;

	dev = open("/dev/pf", O_RDWR);	
	if (dev == -1) {
		syslog(LOG_ERR, "can't open /dev/pf");
		exit(1);
	}
	if (ioctl(dev, DIOCGETSTATUS, &status) == -1) {
		syslog(LOG_ERR, "DIOCGETSTATUS");
		exit(1);
	}
	if (!status.running) {
		syslog(LOG_ERR, "pf is disabled");
		exit(1);
	}
}

int
prepare_commit(u_int32_t id)
{
	char an[PF_ANCHOR_NAME_SIZE];
	int i;

	memset(&pft, 0, sizeof pft);
	pft.size = TRANS_SIZE;
	pft.esize = sizeof pfte[0];
	pft.array = pfte;

	snprintf(an, PF_ANCHOR_NAME_SIZE, "%s/%d.%d", FTP_PROXY_ANCHOR,
	    getpid(), id);
	for (i = 0; i < TRANS_SIZE; i++) {
		memset(&pfte[i], 0, sizeof pfte[0]);
		strlcpy(pfte[i].anchor, an, PF_ANCHOR_NAME_SIZE);
		switch (i) {
		case TRANS_FILTER:
			pfte[i].rs_num = PF_RULESET_FILTER;
			break;
		case TRANS_NAT:
			pfte[i].rs_num = PF_RULESET_NAT;
			break;
		case TRANS_RDR:
			pfte[i].rs_num = PF_RULESET_RDR;
			break;
		default:
			errno = EINVAL;
			return (-1);
		}
	}

	if (ioctl(dev, DIOCXBEGIN, &pft) == -1)
		return (-1);

	return (0);
}
	
int
prepare_rule(u_int32_t id, int rs_num, struct sockaddr *src,
    struct sockaddr *dst, u_int16_t d_port, u_int8_t proto)
{
	char an[PF_ANCHOR_NAME_SIZE];

	if ((src->sa_family != AF_INET && src->sa_family != AF_INET6) ||
	    (src->sa_family != dst->sa_family)) {
	    	errno = EPROTONOSUPPORT;
		return (-1);
	}

	memset(&pfp, 0, sizeof pfp);
	memset(&pfr, 0, sizeof pfr);
	snprintf(an, PF_ANCHOR_NAME_SIZE, "%s/%d.%d", FTP_PROXY_ANCHOR,
	    getpid(), id);
	strlcpy(pfp.anchor, an, PF_ANCHOR_NAME_SIZE);
	strlcpy(pfr.anchor, an, PF_ANCHOR_NAME_SIZE);

	switch (rs_num) {
	case PF_RULESET_FILTER:
		pfr.ticket = pfte[TRANS_FILTER].ticket;
		break;
	case PF_RULESET_NAT:
		pfr.ticket = pfte[TRANS_NAT].ticket;
		break;
	case PF_RULESET_RDR:
		pfr.ticket = pfte[TRANS_RDR].ticket;
		break;
	default:
		errno = EINVAL;
		return (-1);
	}
	if (ioctl(dev, DIOCBEGINADDRS, &pfp) == -1)
		return (-1);
	pfr.pool_ticket = pfp.ticket;

	/* Generic for all rule types. */
	pfr.rule.af = src->sa_family;
	pfr.rule.proto = proto;
	pfr.rule.src.addr.type = PF_ADDR_ADDRMASK;
	pfr.rule.dst.addr.type = PF_ADDR_ADDRMASK;
	if (src->sa_family == AF_INET) {
		memcpy(&pfr.rule.src.addr.v.a.addr.v4,
		    &satosin(src)->sin_addr.s_addr, 4);
		memset(&pfr.rule.src.addr.v.a.mask.addr8, 255, 4);
		memcpy(&pfr.rule.dst.addr.v.a.addr.v4,
		    &satosin(dst)->sin_addr.s_addr, 4);
		memset(&pfr.rule.dst.addr.v.a.mask.addr8, 255, 4);
	} else {
		memcpy(&pfr.rule.src.addr.v.a.addr.v6,
		    &satosin6(src)->sin6_addr.s6_addr, 16);
		memset(&pfr.rule.src.addr.v.a.mask.addr8, 255, 16);
		memcpy(&pfr.rule.dst.addr.v.a.addr.v6,
		    &satosin6(dst)->sin6_addr.s6_addr, 16);
		memset(&pfr.rule.dst.addr.v.a.mask.addr8, 255, 16);
	}
	pfr.rule.dst.port_op = PF_OP_EQ;
	pfr.rule.dst.port[0] = htons(d_port);

	switch (rs_num) {
	case PF_RULESET_FILTER:
		/*
		 * pass quick [log] inet[6] proto tcp \
		 *     from $src to $dst port = $d_port flags S/SAFR keep state
		 *     (max 1) [queue qname]
		 */
		pfr.rule.action = PF_PASS;
		pfr.rule.quick = 1;
		pfr.rule.log = rule_log;
		pfr.rule.keep_state = 1;
		pfr.rule.flags = (proto == IPPROTO_TCP ? TH_SYN : 0);
		pfr.rule.flagset = (proto == IPPROTO_TCP ?
		    (TH_SYN|TH_ACK|TH_FIN|TH_RST) : 0);
		pfr.rule.max_states = 1;
		if (qname != NULL)
			strlcpy(pfr.rule.qname, qname, sizeof pfr.rule.qname);
		break;
	case PF_RULESET_NAT:
		/*
		 * nat inet[6] proto tcp from $src to $dst port $d_port -> $nat
		 */
		pfr.rule.action = PF_NAT;
		break;
	case PF_RULESET_RDR:
		/*
		 * rdr inet[6] proto tcp from $src to $dst port $d_port -> $rdr
		 */
		pfr.rule.action = PF_RDR;
		break;
	default:
		errno = EINVAL;
		return (-1);
	}

	return (0);
}

int
server_lookup(struct sockaddr *client, struct sockaddr *proxy,
    struct sockaddr *server, u_int8_t proto)
{
	if (client->sa_family == AF_INET)
		return (server_lookup4(satosin(client), satosin(proxy),
		    satosin(server), proto));

	if (client->sa_family == AF_INET6)
		return (server_lookup6(satosin6(client), satosin6(proxy),
		    satosin6(server), proto));

	errno = EPROTONOSUPPORT;
	return (-1);
}

int
server_lookup4(struct sockaddr_in *client, struct sockaddr_in *proxy,
    struct sockaddr_in *server, u_int8_t proto)
{
	struct pfioc_natlook pnl;

	memset(&pnl, 0, sizeof pnl);
	pnl.direction = PF_OUT;
	pnl.af = AF_INET;
	pnl.proto = proto;
	memcpy(&pnl.saddr.v4, &client->sin_addr.s_addr, sizeof pnl.saddr.v4);
	memcpy(&pnl.daddr.v4, &proxy->sin_addr.s_addr, sizeof pnl.daddr.v4);
	pnl.sport = client->sin_port;
	pnl.dport = proxy->sin_port;

	if (ioctl(dev, DIOCNATLOOK, &pnl) == -1)
		return (-1);

	memset(server, 0, sizeof(struct sockaddr_in));
	server->sin_len = sizeof(struct sockaddr_in);
	server->sin_family = AF_INET;
	memcpy(&server->sin_addr.s_addr, &pnl.rdaddr.v4,
	    sizeof server->sin_addr.s_addr);
	server->sin_port = pnl.rdport;

	return (0);
}

int
server_lookup6(struct sockaddr_in6 *client, struct sockaddr_in6 *proxy,
    struct sockaddr_in6 *server, u_int8_t proto)
{
	struct pfioc_natlook pnl;

	memset(&pnl, 0, sizeof pnl);
	pnl.direction = PF_OUT;
	pnl.af = AF_INET6;
	pnl.proto = proto;
	memcpy(&pnl.saddr.v6, &client->sin6_addr.s6_addr, sizeof pnl.saddr.v6);
	memcpy(&pnl.daddr.v6, &proxy->sin6_addr.s6_addr, sizeof pnl.daddr.v6);
	pnl.sport = client->sin6_port;
	pnl.dport = proxy->sin6_port;
	
	if (ioctl(dev, DIOCNATLOOK, &pnl) == -1)
		return (-1);

	memset(server, 0, sizeof(struct sockaddr_in6));
	server->sin6_len = sizeof(struct sockaddr_in6);
	server->sin6_family = AF_INET6;
	memcpy(&server->sin6_addr.s6_addr, &pnl.rdaddr.v6,
	    sizeof server->sin6_addr);
	server->sin6_port = pnl.rdport;

	return (0);
}

File Added: src/dist/pf/libexec/tftp-proxy/filter.h
/*	$NetBSD: filter.h,v 1.2.2.2 2008/09/19 19:22:39 wrstuden Exp $ */
/*	$OpenBSD: filter.h,v 1.1 2005/12/28 19:07:07 jcs Exp $ */

/*
 * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl>
 *
 * 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.
 */

#define	FTP_PROXY_ANCHOR "tftp-proxy"

int add_filter(u_int32_t, u_int8_t, struct sockaddr *, struct sockaddr *,
    u_int16_t, u_int8_t);
int add_nat(u_int32_t, struct sockaddr *, struct sockaddr *, u_int16_t,
    struct sockaddr *, u_int16_t, u_int16_t, u_int8_t);
int add_rdr(u_int32_t, struct sockaddr *, struct sockaddr *, u_int16_t,
    struct sockaddr *, u_int16_t, u_int8_t);
int do_commit(void);
int do_rollback(void);
void init_filter(char *, int);
int prepare_commit(u_int32_t);
int server_lookup(struct sockaddr *, struct sockaddr *, struct sockaddr *,
    u_int8_t);

File Added: src/dist/pf/libexec/tftp-proxy/tftp-proxy.8
.\"	$NetBSD: tftp-proxy.8,v 1.2.2.2 2008/09/19 19:22:39 wrstuden Exp $
.\"	$OpenBSD: tftp-proxy.8,v 1.2 2007/05/31 19:19:41 jmc Exp $
.\"
.\" Copyright (c) 2005 joshua stein <jcs@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.
.\" 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 $Mdocdate$
.Dt TFTP-PROXY 8
.Os
.Sh NAME
.Nm tftp-proxy
.Nd Internet Trivial File Transfer Protocol proxy
.Sh SYNOPSIS
.Nm tftp-proxy
.Op Fl v
.Op Fl w Ar transwait
.Sh DESCRIPTION
.Nm
is a proxy for the Internet Trivial File Transfer Protocol invoked by
the
.Xr inetd 8
internet server.
TFTP connections should be redirected to the proxy using the
.Xr pf 4
.Ar rdr
command, after which the proxy connects to the server on behalf of
the client.
.Pp
The proxy establishes a
.Xr pf 4
.Ar rdr
rule using the
.Ar anchor
facility to rewrite packets between the client and the server.
Once the rule is established,
.Nm
forwards the initial request from the client to the server to begin the
transfer.
After
.Ar transwait
seconds, the
.Xr pf 4
NAT state is assumed to have been established and the
.Ar rdr
rule is deleted and the program exits.
Once the transfer between the client and the server is completed, the
NAT state will naturally expire.
.Pp
Assuming the TFTP command request is from $client to $server, the
proxy connected to the server using the $proxy source address, and
$port is negotiated,
.Nm
adds the following rule to the anchor:
.Bd -literal -offset indent
rdr proto udp from $server to $proxy port $port -\*(Gt $client
.Ed
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl v
Log the connection and request information to
.Xr syslogd 8 .
.It Fl w Ar transwait
Number of seconds to wait for the data transmission to begin before
removing the
.Xr pf 4
.Ar rdr
rule.
The default is 2 seconds.
.El
.Sh CONFIGURATION
To make use of the proxy,
.Xr pf.conf 5
needs the following rules.
The anchors are mandatory.
Adjust the rules as needed for your configuration.
.Pp
In the NAT section:
.Bd -literal -offset indent
nat on $ext_if from $int_if -\*(Gt ($ext_if:0)

no nat on $ext_if to port tftp

rdr-anchor "tftp-proxy/*"
rdr on $int_if proto udp from $lan to any port tftp -\*(Gt \e
    127.0.0.1 port 6969
.Ed
.Pp
In the filter section, an anchor must be added to hold the pass rules:
.Bd -literal -offset indent
anchor "tftp-proxy/*"
.Ed
.Pp
.Xr inetd 8
must be configured to spawn the proxy on the port that packets are
being forwarded to by
.Xr pf 4 .
An example
.Xr inetd.conf 5
entry follows:
.Bd -literal -offset indent
127.0.0.1:6969	dgram	udp	wait	root \e
	/usr/libexec/tftp-proxy	tftp-proxy
.Ed
.Sh SEE ALSO
.Xr tftp 1 ,
.Xr pf 4 ,
.Xr pf.conf 5 ,
.Xr ftp-proxy 8 ,
.Xr inetd 8 ,
.Xr syslogd 8 ,
.Xr tftpd 8
.Sh CAVEATS
.Nm
chroots to
.Pa /var/chroot/tftp-proxy
and changes to user
.Dq _proxy
to drop privileges.

File Added: src/dist/pf/libexec/tftp-proxy/tftp-proxy.c
/* $NetBSD: tftp-proxy.c,v 1.2.2.2 2008/09/19 19:22:39 wrstuden Exp $ */
/* $OpenBSD: tftp-proxy.c,v 1.2 2006/12/20 03:33:38 joel Exp $
 *
 * Copyright (c) 2005 DLS Internet Services
 * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl>
 *
 * 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.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <unistd.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/tftp.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/pfvar.h>

#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <syslog.h>
#include <string.h>
#include <stdlib.h>

#include "filter.h"

#define CHROOT_DIR	"/var/chroot/tftp-proxy"
#define NOPRIV_USER	"_proxy"

#define PF_NAT_PROXY_PORT_LOW	50001
#define PF_NAT_PROXY_PORT_HIGH	65535

#define DEFTRANSWAIT	2
#define NTOP_BUFS	4
#ifndef PKTSIZE
#define PKTSIZE		SEGSIZE+4
#endif /* !PKTSIZE */

#ifndef IPPORT_HIFIRSTAUTO
#define IPPORT_HIFIRSTAUTO	IPPORT_ANONMIN
#endif /* IPPORT_HIFIRSTAUTO */

#ifndef IPPORT_HILASTAUTO
#define IPPORT_HILASTAUTO	IPPORT_ANONMAX
#endif /* IPPORT_HILASTAUTO */

const char *opcode(int);
const char *sock_ntop(struct sockaddr *);
u_int16_t pick_proxy_port(void);
static void usage(void);

extern	char *__progname;
char	ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN];
int	verbose = 0;

int
main(int argc, char *argv[])
{
	int c, fd = 0, on = 1, out_fd = 0, peer, reqsize = 0;
	int transwait = DEFTRANSWAIT;
	char *p;
	struct tftphdr *tp;
	struct passwd *pw;
	size_t cbuflen;
	char *cbuf;
	char req[PKTSIZE];
	struct cmsghdr *cmsg;
	struct msghdr msg;
	struct iovec iov;

	struct sockaddr_storage from, proxy, server, proxy_to_server, s_in;
	struct sockaddr_in sock_out;
	socklen_t j;
	in_port_t bindport;

	openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);

	while ((c = getopt(argc, argv, "vw:")) != -1)
		switch (c) {
		case 'v':
			verbose++;
			break;
		case 'w':
			transwait = strtoll(optarg, &p, 10);
			if (transwait < 1) {
				syslog(LOG_ERR, "invalid -w value");
				exit(1);
			}
			break;
		default:
			usage();
			break;
		}

	/* open /dev/pf */
	init_filter(NULL, verbose);

	tzset();

	pw = getpwnam(NOPRIV_USER);
	if (!pw) {
		syslog(LOG_ERR, "no such user %s: %m", NOPRIV_USER);
		exit(1);
	}
	if (chroot(CHROOT_DIR) || chdir("/")) {
		syslog(LOG_ERR, "chroot %s: %m", CHROOT_DIR);
		exit(1);
	}
#ifdef __NetBSD__
	if (setgroups(1, &pw->pw_gid) ||
	    setgid(pw->pw_gid) ||
	    setuid(pw->pw_uid)) {
		syslog(LOG_ERR, "can't revoke privs: %m");
		exit(1);
	}
#else
	if (setgroups(1, &pw->pw_gid) ||
	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) {
		syslog(LOG_ERR, "can't revoke privs: %m");
		exit(1);
	}
#endif /* !__NetBSD__ */

	/* non-blocking io */
	if (ioctl(fd, FIONBIO, &on) < 0) {
		syslog(LOG_ERR, "ioctl(FIONBIO): %m");
		exit(1);
	}

	if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) == -1) {
		syslog(LOG_ERR, "setsockopt(IP_RECVDSTADDR): %m");
		exit(1);
	}

	j = sizeof(s_in);
	if (getsockname(fd, (struct sockaddr *)&s_in, &j) == -1) {
		syslog(LOG_ERR, "getsockname: %m");
		exit(1);
	}

	bindport = ((struct sockaddr_in *)&s_in)->sin_port;

	/* req will be pushed back out at the end, unchanged */
	j = sizeof(from);
	if ((reqsize = recvfrom(fd, req, sizeof(req), MSG_PEEK,
	    (struct sockaddr *)&from, &j)) < 0) {
		syslog(LOG_ERR, "recvfrom: %m");
		exit(1);
	}


	bzero(&msg, sizeof(msg));
	iov.iov_base = req;
	iov.iov_len = sizeof(req);
	msg.msg_name = &from;
	msg.msg_namelen = sizeof(from);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	cbuflen = CMSG_SPACE(sizeof(struct sockaddr_storage));
	if ((cbuf = malloc(cbuflen)) == NULL) {
		syslog(LOG_ERR, "malloc: %m");
		exit(1);
	}

	msg.msg_control = cbuf;
	msg.msg_controllen = cbuflen;

	if (recvmsg(fd, &msg, 0) < 0) {
		syslog(LOG_ERR, "recvmsg: %m");
		exit(1);
	}

	close(fd);
	close(1);

	peer = socket(from.ss_family, SOCK_DGRAM, 0);
	if (peer < 0) {
		syslog(LOG_ERR, "socket: %m");
		exit(1);
	}
	memset(&s_in, 0, sizeof(s_in));
	s_in.ss_family = from.ss_family;
	s_in.ss_len = from.ss_len;

	/* get local address if possible */
	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
	    cmsg = CMSG_NXTHDR(&msg, cmsg)) {
		if (cmsg->cmsg_level == IPPROTO_IP &&
		    cmsg->cmsg_type == IP_RECVDSTADDR) {
			memcpy(&((struct sockaddr_in *)&s_in)->sin_addr,
			    CMSG_DATA(cmsg), sizeof(struct in_addr));
			break;
		}
	}

	if (bind(peer, (struct sockaddr *)&s_in, s_in.ss_len) < 0) {
		syslog(LOG_ERR, "bind: %m");
		exit(1);
	}
	if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) {
		syslog(LOG_ERR, "connect: %m");
		exit(1);
	}

	tp = (struct tftphdr *)req;
	if (!(ntohs(tp->th_opcode) == RRQ || ntohs(tp->th_opcode) == WRQ)) {
		/* not a tftp request, bail */
		if (verbose) {
			syslog(LOG_WARNING, "not a valid tftp request");
			exit(1);
		} else
			/* exit 0 so inetd doesn't log anything */
			exit(0);
	}

	j = sizeof(struct sockaddr_storage);
	if (getsockname(fd, (struct sockaddr *)&proxy, &j) == -1) {
		syslog(LOG_ERR, "getsockname: %m");
		exit(1);
	}

	((struct sockaddr_in *)&proxy)->sin_port = bindport;

	/* find the un-rdr'd server and port the client wanted */
	if (server_lookup((struct sockaddr *)&from,
	    (struct sockaddr *)&proxy, (struct sockaddr *)&server,
	    IPPROTO_UDP) != 0) {
		syslog(LOG_ERR, "pf connection lookup failed (no rdr?)");
		exit(1);
	}

	/* establish a new outbound connection to the remote server */
	if ((out_fd = socket(((struct sockaddr *)&from)->sa_family,
	    SOCK_DGRAM, IPPROTO_UDP)) < 0) {
		syslog(LOG_ERR, "couldn't create new socket");
		exit(1);
	}

	bzero((char *)&sock_out, sizeof(sock_out));
	sock_out.sin_family = from.ss_family;
	sock_out.sin_port = htons(pick_proxy_port());
	if (bind(out_fd, (struct sockaddr *)&sock_out, sizeof(sock_out)) < 0) {
		syslog(LOG_ERR, "couldn't bind to new socket: %m");
		exit(1);
	}

	if (connect(out_fd, (struct sockaddr *)&server,
	    ((struct sockaddr *)&server)->sa_len) < 0 && errno != EINPROGRESS) {
		syslog(LOG_ERR, "couldn't connect to remote server: %m");
		exit(1);
	}

	j = sizeof(struct sockaddr_storage);
	if ((getsockname(out_fd, (struct sockaddr *)&proxy_to_server,
	    &j)) < 0) {
		syslog(LOG_ERR, "getsockname: %m");
		exit(1);
	}

	if (verbose)
		syslog(LOG_INFO, "%s:%d -> %s:%d/%s:%d -> %s:%d \"%s %s\"",
			sock_ntop((struct sockaddr *)&from),
			ntohs(((struct sockaddr_in *)&from)->sin_port),
			sock_ntop((struct sockaddr *)&proxy),
			ntohs(((struct sockaddr_in *)&proxy)->sin_port),
			sock_ntop((struct sockaddr *)&proxy_to_server),
			ntohs(((struct sockaddr_in *)&proxy_to_server)->sin_port),
			sock_ntop((struct sockaddr *)&server),
			ntohs(((struct sockaddr_in *)&server)->sin_port),
			opcode(ntohs(tp->th_opcode)),
			tp->th_stuff);

	/* get ready to add rdr and pass rules */
	if (prepare_commit(1) == -1) {
		syslog(LOG_ERR, "couldn't prepare pf commit");
		exit(1);
	}

	/* rdr from server to us on our random port -> client on its port */
	if (add_rdr(1, (struct sockaddr *)&server,
	    (struct sockaddr *)&proxy_to_server, ntohs(sock_out.sin_port),
	    (struct sockaddr *)&from,
	    ntohs(((struct sockaddr_in *)&from)->sin_port),
	    IPPROTO_UDP) == -1) {
		syslog(LOG_ERR, "couldn't add rdr");
		exit(1);
	}

	/* explicitly allow the packets to return back to the client (which pf
	 * will see post-rdr) */
	if (add_filter(1, PF_IN, (struct sockaddr *)&server,
	    (struct sockaddr *)&from,
	    ntohs(((struct sockaddr_in *)&from)->sin_port),
	    IPPROTO_UDP) == -1) {
		syslog(LOG_ERR, "couldn't add pass in");
		exit(1);
	}
	if (add_filter(1, PF_OUT, (struct sockaddr *)&server,
	    (struct sockaddr *)&from,
	    ntohs(((struct sockaddr_in *)&from)->sin_port),
	    IPPROTO_UDP) == -1) {
		syslog(LOG_ERR, "couldn't add pass out");
		exit(1);
	}

	/* and just in case, to pass out from us to the server */
	if (add_filter(1, PF_OUT, (struct sockaddr *)&proxy_to_server,
	    (struct sockaddr *)&server,
	    ntohs(((struct sockaddr_in *)&server)->sin_port),
	    IPPROTO_UDP) == -1) {
		syslog(LOG_ERR, "couldn't add pass out");
		exit(1);
	}

	if (do_commit() == -1) {
		syslog(LOG_ERR, "couldn't commit pf rules");
		exit(1);
	}

	/* forward the initial tftp request and start the insanity */
	if (send(out_fd, tp, reqsize, 0) < 0) {
		syslog(LOG_ERR, "couldn't forward tftp packet: %m");
		exit(1);
	}

	/* allow the transfer to start to establish a state */
	sleep(transwait);

	/* delete our rdr rule and clean up */
	prepare_commit(1);
	do_commit();

	return(0);
}

const char *
opcode(int code)
{
	static char str[6];

	switch (code) {
	case 1:
		(void)snprintf(str, sizeof(str), "RRQ");
		break;
	case 2:
		(void)snprintf(str, sizeof(str), "WRQ");
		break;
	default:
		(void)snprintf(str, sizeof(str), "(%d)", code);
		break;
	}

	return (str);
}

const char *
sock_ntop(struct sockaddr *sa)
{
	static int n = 0;

	/* Cycle to next buffer. */
	n = (n + 1) % NTOP_BUFS;
	ntop_buf[n][0] = '\0';

	if (sa->sa_family == AF_INET) {
		struct sockaddr_in *sin = (struct sockaddr_in *)sa;

		return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n],
		    sizeof ntop_buf[0]));
	}

	if (sa->sa_family == AF_INET6) {
		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;

		return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n],
		    sizeof ntop_buf[0]));
	}

	return (NULL);
}

u_int16_t
pick_proxy_port(void)
{
	return (IPPORT_HIFIRSTAUTO + (arc4random() %
	    (IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO)));
}

static void
usage(void)
{
	syslog(LOG_ERR, "usage: %s [-v] [-w transwait]", __progname);
	exit(1);
}