summaryrefslogtreecommitdiffstats
path: root/contrib/pf/ftp-proxy
diff options
context:
space:
mode:
authormlaier <mlaier@FreeBSD.org>2004-02-28 16:52:45 +0000
committermlaier <mlaier@FreeBSD.org>2004-02-28 16:52:45 +0000
commit2135f6a83c8e1b39db8af0b470ab6d0349144be9 (patch)
tree73e1e06a1e5e925889f30ef537d21f7d3edd736d /contrib/pf/ftp-proxy
downloadFreeBSD-src-2135f6a83c8e1b39db8af0b470ab6d0349144be9.zip
FreeBSD-src-2135f6a83c8e1b39db8af0b470ab6d0349144be9.tar.gz
Vendor import of OpenBSD's pf userland as of OpenBSD 3.4
Approved by: bms(mentor), core(in general)
Diffstat (limited to 'contrib/pf/ftp-proxy')
-rw-r--r--contrib/pf/ftp-proxy/ftp-proxy.8253
-rw-r--r--contrib/pf/ftp-proxy/ftp-proxy.c1320
-rw-r--r--contrib/pf/ftp-proxy/getline.c259
-rw-r--r--contrib/pf/ftp-proxy/util.c296
-rw-r--r--contrib/pf/ftp-proxy/util.h68
5 files changed, 2196 insertions, 0 deletions
diff --git a/contrib/pf/ftp-proxy/ftp-proxy.8 b/contrib/pf/ftp-proxy/ftp-proxy.8
new file mode 100644
index 0000000..2832ddb
--- /dev/null
+++ b/contrib/pf/ftp-proxy/ftp-proxy.8
@@ -0,0 +1,253 @@
+.\" $OpenBSD: ftp-proxy.8,v 1.37 2003/09/05 12:27:47 jmc Exp $
+.\"
+.\" Copyright (c) 1996-2001
+.\" Obtuse Systems Corporation, All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL OBTUSE OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd August 17, 2001
+.Dt FTP-PROXY 8
+.Os
+.Sh NAME
+.Nm ftp-proxy
+.Nd Internet File Transfer Protocol proxy server
+.Sh SYNOPSIS
+.Nm ftp-proxy
+.Op Fl AnrVw
+.Op Fl D Ar debuglevel
+.Op Fl g Ar group
+.Op Fl m Ar minport
+.Op Fl M Ar maxport
+.Op Fl t Ar timeout
+.Op Fl u Ar user
+.Sh DESCRIPTION
+.Nm
+is a proxy for the Internet File Transfer Protocol.
+The proxy uses
+.Xr pf 4
+and expects to have the FTP control connection as described in
+.Xr services 5
+redirected to it via a
+.Xr pf 4
+.Em rdr
+command.
+An example of how to do that is further down in this document.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl A
+Permit only anonymous FTP connections.
+The proxy will allow connections to log in to other sites as the user
+.Qq ftp
+or
+.Qq anonymous
+only.
+Any attempt to log in as another user will be blocked by the proxy.
+.It Fl D Ar debuglevel
+Specify a debug level, where the proxy emits verbose debug output
+into
+.Xr syslogd 8
+at level
+.Dv LOG_DEBUG .
+Meaningful values of debuglevel are 0-3, where 0 is no debug output and
+3 is lots of debug output, the default being 0.
+.It Fl g Ar group
+Specify the named group to drop group privileges to, after doing
+.Xr pf 4
+lookups which require root.
+By default,
+.Nm
+uses the default group of the user it drops privilege to.
+.It Fl m Ar minport
+Specify the lower end of the port range the proxy will use for all
+data connections it establishes.
+The default is
+.Dv IPPORT_HIFIRSTAUTO
+defined in
+.Aq Pa netinet/in.h
+as 49152.
+.It Fl M Ar maxport
+Specify the upper end of the port range the proxy will use for the
+data connections it establishes.
+The default is
+.Dv IPPORT_HILASTAUTO
+defined in
+.Aq Pa netinet/in.h
+as 65535.
+.It Fl n
+Activate network address translation
+.Pq NAT
+mode.
+In this mode, the proxy will not attempt to proxy passive mode
+.Pq PASV or EPSV
+data connections.
+In order for this to work, the machine running the proxy will need to
+be forwarding packets and doing network address translation to allow
+the outbound passive connections from the client to reach the server.
+See
+.Xr pf.conf 5
+for more details on NAT.
+The proxy only ignores passive mode data connections when using this flag;
+it will still proxy PORT and EPRT mode data connections.
+Without this flag,
+.Nm
+does not require any IP forwarding or NAT beyond the
+.Em rdr
+necessary to capture the FTP control connection.
+.It Fl r
+Use reverse host
+.Pq reverse DNS
+lookups for logging and libwrap use.
+By default,
+the proxy does not look up hostnames for libwrap or logging purposes.
+.It Fl t Ar timeout
+Specifies a timeout, in seconds.
+The proxy will exit and close open connections if it sees no data
+for the duration of the timeout.
+The default is 0, which means the proxy will not time out.
+.It Fl u Ar user
+Specify the named user to drop privilege to, after doing
+.Xr pf 4
+lookups which require root privilege.
+By default,
+.Nm
+drops privilege to the user
+.Em proxy .
+.Pp
+Running as root means that the source of data connections the proxy makes
+for PORT and EPRT will be the RFC mandated port 20.
+When running as a non-root user, the source of the data connections from
+.Nm
+will be chosen randomly from the range
+.Ar minport
+to
+.Ar maxport
+as described above.
+.It Fl V
+Be verbose.
+With this option the proxy logs the control commands
+sent by clients and the replies sent by the servers to
+.Xr syslogd 8 .
+.It Fl w
+Use the tcp wrapper access control library
+.Xr hosts_access 3 ,
+allowing connections to be allowed or denied based on the tcp wrapper's
+.Xr hosts.allow 5
+and
+.Xr hosts.deny 5
+files.
+The proxy does libwrap operations after determining the destination
+of the captured control connection, so that tcp wrapper rules may
+be written based on the destination as well as the source of FTP connections.
+.El
+.Pp
+.Nm ftp-proxy
+is run from
+.Xr inetd 8
+and requires that FTP connections are redirected to it using a
+.Em rdr
+rule.
+A typical way to do this would be to use a
+.Xr pf.conf 5
+rule such as
+.Bd -literal -offset 2n
+int_if = xl0
+rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 port 8021
+.Ed
+.Pp
+.Xr inetd 8
+must then be configured to run
+.Nm
+on the port from above using
+.Bd -literal -offset 2n
+127.0.0.1:8021 stream tcp nowait root /usr/libexec/ftp-proxy ftp-proxy
+.Ed
+.Pp
+in
+.Xr inetd.conf 5 .
+.Pp
+.Nm
+accepts the redirected control connections and forwards them
+to the server.
+The proxy replaces the address and port number that the client
+sends through the control connection to the server with its own
+address and proxy port, where it listens for the data connection.
+When the server opens the data connection back to this port, the
+proxy forwards it to the client.
+The
+.Xr pf.conf 5
+rules need to let pass connections to these proxy ports
+(see options
+.Fl u , m ,
+and
+.Fl M
+above) in on the external interface.
+The following example allows only ports 49152 to 65535 to pass in
+statefully:
+.Bd -literal -offset indent
+block in on $ext_if proto tcp all
+pass in on $ext_if inet proto tcp from any to $ext_if \e
+ port > 49151 keep state
+.Ed
+.Pp
+Alternatively, rules can make use of the fact that by default,
+.Nm
+runs as user
+.Qq proxy
+to allow the backchannel connections, as in the following example:
+.Bd -literal -offset indent
+block in on $ext_if proto tcp all
+pass in on $ext_if inet proto tcp from any to $ext_if \e
+ user proxy keep state
+.Ed
+.Pp
+These examples do not cover the connections from the proxy to the
+foreign FTP server.
+If one does not pass outgoing connections by default additional rules
+are needed.
+.Sh SEE ALSO
+.Xr ftp 1 ,
+.Xr pf 4 ,
+.Xr hosts.allow 5 ,
+.Xr hosts.deny 5 ,
+.Xr inetd.conf 5 ,
+.Xr pf.conf 5 ,
+.Xr inetd 8 ,
+.Xr pfctl 8 ,
+.Xr syslogd 8
+.Sh BUGS
+Extended Passive mode
+.Pq EPSV
+is not supported by the proxy and will not work unless the proxy is run
+in network address translation mode.
+When not in network address translation mode, the proxy returns an error
+to the client, hopefully forcing the client to revert to passive mode
+.Pq PASV
+which is supported.
+EPSV will work in network address translation mode, assuming a
+.Xr pf.conf 5
+setup which allows the EPSV connections through to their destinations.
+.Pp
+IPv6 is not yet supported.
diff --git a/contrib/pf/ftp-proxy/ftp-proxy.c b/contrib/pf/ftp-proxy/ftp-proxy.c
new file mode 100644
index 0000000..88b6fd1
--- /dev/null
+++ b/contrib/pf/ftp-proxy/ftp-proxy.c
@@ -0,0 +1,1320 @@
+/* $OpenBSD: ftp-proxy.c,v 1.33 2003/08/22 21:50:34 david Exp $ */
+
+/*
+ * Copyright (c) 1996-2001
+ * Obtuse Systems Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the Obtuse Systems nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * ftp proxy, Originally based on juniper_ftp_proxy from the Obtuse
+ * Systems juniper firewall, written by Dan Boulet <danny@obtuse.com>
+ * and Bob Beck <beck@obtuse.com>
+ *
+ * This version basically passes everything through unchanged except
+ * for the PORT and the * "227 Entering Passive Mode" reply.
+ *
+ * A PORT command is handled by noting the IP address and port number
+ * specified and then configuring a listen port on some very high port
+ * number and telling the server about it using a PORT message.
+ * We then watch for an in-bound connection on the port from the server
+ * and connect to the client's port when it happens.
+ *
+ * A "227 Entering Passive Mode" reply is handled by noting the IP address
+ * and port number specified and then configuring a listen port on some
+ * very high port number and telling the client about it using a
+ * "227 Entering Passive Mode" reply.
+ * We then watch for an in-bound connection on the port from the client
+ * and connect to the server's port when it happens.
+ *
+ * supports tcp wrapper lookups/access control with the -w flag using
+ * the real destination address - the tcp wrapper stuff is done after
+ * the real destination address is retrieved from pf
+ *
+ */
+
+/*
+ * TODO:
+ * Plenty, this is very basic, with the idea to get it in clean first.
+ *
+ * - IPv6 and EPASV support
+ * - Content filter support
+ * - filename filter support
+ * - per-user rules perhaps.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#ifdef LIBWRAP
+#include <tcpd.h>
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_NOTICE;
+#endif /* LIBWRAP */
+
+int min_port = IPPORT_HIFIRSTAUTO;
+int max_port = IPPORT_HILASTAUTO;
+
+#define STARTBUFSIZE 1024 /* Must be at least 3 */
+
+/*
+ * Variables used to support PORT mode connections.
+ *
+ * This gets a bit complicated.
+ *
+ * If PORT mode is on then client_listen_sa describes the socket that
+ * the real client is listening on and server_listen_sa describes the
+ * socket that we are listening on (waiting for the real server to connect
+ * with us).
+ *
+ * If PASV mode is on then client_listen_sa describes the socket that
+ * we are listening on (waiting for the real client to connect to us on)
+ * and server_listen_sa describes the socket that the real server is
+ * listening on.
+ *
+ * If the socket we are listening on gets a connection then we connect
+ * to the other side's socket. Similarly, if a connected socket is
+ * shutdown then we shutdown the other side's socket.
+ */
+
+double xfer_start_time;
+
+struct sockaddr_in real_server_sa;
+struct sockaddr_in client_listen_sa;
+struct sockaddr_in server_listen_sa;
+
+int client_listen_socket = -1; /* Only used in PASV mode */
+int client_data_socket = -1; /* Connected socket to real client */
+int server_listen_socket = -1; /* Only used in PORT mode */
+int server_data_socket = -1; /* Connected socket to real server */
+int client_data_bytes, server_data_bytes;
+
+int AnonFtpOnly;
+int Verbose;
+int NatMode;
+
+char ClientName[NI_MAXHOST];
+char RealServerName[NI_MAXHOST];
+char OurName[NI_MAXHOST];
+
+char *User = "proxy";
+char *Group;
+
+extern int Debug_Level;
+extern int Use_Rdns;
+extern char *__progname;
+
+typedef enum {
+ UNKNOWN_MODE,
+ PORT_MODE,
+ PASV_MODE,
+ EPRT_MODE,
+ EPSV_MODE
+} connection_mode_t;
+
+connection_mode_t connection_mode;
+
+extern void debuglog(int debug_level, const char *fmt, ...);
+double wallclock_time(void);
+void show_xfer_stats(void);
+void log_control_command (char *cmd, int client);
+int new_dataconn(int server);
+void do_client_cmd(struct csiob *client, struct csiob *server);
+void do_server_reply(struct csiob *server, struct csiob *client);
+static void
+usage(void)
+{
+ syslog(LOG_NOTICE,
+ "usage: %s [-AnrVw] [-D debuglevel] [-g group] %s %s",
+ __progname, "[-m minport] [-M maxport] [-t timeout]",
+ "[-u user]");
+ exit(EX_USAGE);
+}
+
+static void
+close_client_data(void)
+{
+ if (client_data_socket >= 0) {
+ shutdown(client_data_socket, 2);
+ close(client_data_socket);
+ client_data_socket = -1;
+ }
+}
+
+static void
+close_server_data(void)
+{
+ if (server_data_socket >= 0) {
+ shutdown(server_data_socket, 2);
+ close(server_data_socket);
+ server_data_socket = -1;
+ }
+}
+
+static void
+drop_privs(void)
+{
+ struct passwd *pw;
+ struct group *gr;
+ uid_t uid = 0;
+ gid_t gid = 0;
+
+ if (User != NULL) {
+ pw = getpwnam(User);
+ if (pw == NULL) {
+ syslog(LOG_ERR, "cannot find user %s", User);
+ exit(EX_USAGE);
+ }
+ uid = pw->pw_uid;
+ gid = pw->pw_gid;
+ }
+
+ if (Group != NULL) {
+ gr = getgrnam(Group);
+ if (gr == NULL) {
+ syslog(LOG_ERR, "cannot find group %s", Group);
+ exit(EX_USAGE);
+ }
+ gid = gr->gr_gid;
+ }
+
+ if (gid != 0 && (setegid(gid) == -1 || setgid(gid) == -1)) {
+ syslog(LOG_ERR, "cannot drop group privs (%m)");
+ exit(EX_CONFIG);
+ }
+
+ if (uid != 0 && (seteuid(uid) == -1 || setuid(uid) == -1)) {
+ syslog(LOG_ERR, "cannot drop root privs (%m)");
+ exit(EX_CONFIG);
+ }
+}
+
+#ifdef LIBWRAP
+/*
+ * Check a connection against the tcpwrapper, log if we're going to
+ * reject it, returns: 0 -> reject, 1 -> accept. We add in hostnames
+ * if we are set to do reverse DNS, otherwise no.
+ */
+static int
+check_host(struct sockaddr_in *client_sin, struct sockaddr_in *server_sin)
+{
+ char cname[NI_MAXHOST];
+ char sname[NI_MAXHOST];
+ struct request_info request;
+ int i;
+
+ request_init(&request, RQ_DAEMON, __progname, RQ_CLIENT_SIN,
+ client_sin, RQ_SERVER_SIN, server_sin, RQ_CLIENT_ADDR,
+ inet_ntoa(client_sin->sin_addr), 0);
+
+ if (Use_Rdns) {
+ /*
+ * We already looked these up, but we have to do it again
+ * for tcp wrapper, to ensure that we get the DNS name, since
+ * the tcp wrapper cares about these things, and we don't
+ * want to pass in a printed address as a name.
+ */
+ i = getnameinfo((struct sockaddr *) &client_sin->sin_addr,
+ sizeof(&client_sin->sin_addr), cname, sizeof(cname),
+ NULL, 0, NI_NAMEREQD);
+
+ if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN)
+ strlcpy(cname, STRING_UNKNOWN, sizeof(cname));
+
+ i = getnameinfo((struct sockaddr *)&server_sin->sin_addr,
+ sizeof(&server_sin->sin_addr), sname, sizeof(sname),
+ NULL, 0, NI_NAMEREQD);
+
+ if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN)
+ strlcpy(sname, STRING_UNKNOWN, sizeof(sname));
+ } else {
+ /*
+ * ensure the TCP wrapper doesn't start doing
+ * reverse DNS lookups if we aren't supposed to.
+ */
+ strlcpy(cname, STRING_UNKNOWN, sizeof(cname));
+ strlcpy(sname, STRING_UNKNOWN, sizeof(sname));
+ }
+
+ request_set(&request, RQ_SERVER_ADDR, inet_ntoa(server_sin->sin_addr),
+ 0);
+ request_set(&request, RQ_CLIENT_NAME, cname, RQ_SERVER_NAME, sname, 0);
+
+ if (!hosts_access(&request)) {
+ syslog(LOG_NOTICE, "tcpwrappers rejected: %s -> %s",
+ ClientName, RealServerName);
+ return(0);
+ }
+ return(1);
+}
+#endif /* LIBWRAP */
+
+double
+wallclock_time(void)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ return(tv.tv_sec + tv.tv_usec / 1e6);
+}
+
+/*
+ * Show the stats for this data transfer
+ */
+void
+show_xfer_stats(void)
+{
+ char tbuf[1000];
+ double delta;
+ size_t len;
+ int i;
+
+ if (!Verbose)
+ return;
+
+ delta = wallclock_time() - xfer_start_time;
+
+ if (delta < 0.001)
+ delta = 0.001;
+
+ if (client_data_bytes == 0 && server_data_bytes == 0) {
+ syslog(LOG_INFO,
+ "data transfer complete (no bytes transferred)");
+ return;
+ }
+
+ len = sizeof(tbuf);
+
+ if (delta >= 60) {
+ int idelta;
+
+ idelta = delta + 0.5;
+ if (idelta >= 60*60) {
+ i = snprintf(tbuf, len,
+ "data transfer complete (%dh %dm %ds",
+ idelta / (60*60), (idelta % (60*60)) / 60,
+ idelta % 60);
+ if (i >= len)
+ goto logit;
+ len -= i;
+ } else {
+ i = snprintf(tbuf, len,
+ "data transfer complete (%dm %ds", idelta / 60,
+ idelta % 60);
+ if (i >= len)
+ goto logit;
+ len -= i;
+ }
+ } else {
+ i = snprintf(tbuf, len, "data transfer complete (%.1fs",
+ delta);
+ if (i >= len)
+ goto logit;
+ len -= i;
+ }
+
+ if (client_data_bytes > 0) {
+ i = snprintf(&tbuf[strlen(tbuf)], len,
+ ", %d bytes to server) (%.1fKB/s", client_data_bytes,
+ (client_data_bytes / delta) / (double)1024);
+ if (i >= len)
+ goto logit;
+ len -= i;
+ }
+ if (server_data_bytes > 0) {
+ i = snprintf(&tbuf[strlen(tbuf)], len,
+ ", %d bytes to client) (%.1fKB/s", server_data_bytes,
+ (server_data_bytes / delta) / (double)1024);
+ if (i >= len)
+ goto logit;
+ len -= i;
+ }
+ strlcat(tbuf, ")", sizeof(tbuf));
+ logit:
+ syslog(LOG_INFO, "%s", tbuf);
+}
+
+void
+log_control_command (char *cmd, int client)
+{
+ /* log an ftp control command or reply */
+ char *logstring;
+ int level = LOG_DEBUG;
+
+ if (!Verbose)
+ return;
+
+ /* don't log passwords */
+ if (strncasecmp(cmd, "pass ", 5) == 0)
+ logstring = "PASS XXXX";
+ else
+ logstring = cmd;
+ if (client) {
+ /* log interesting stuff at LOG_INFO, rest at LOG_DEBUG */
+ if ((strncasecmp(cmd, "user ", 5) == 0) ||
+ (strncasecmp(cmd, "retr ", 5) == 0) ||
+ (strncasecmp(cmd, "cwd ", 4) == 0) ||
+ (strncasecmp(cmd, "stor " ,5) == 0))
+ level = LOG_INFO;
+ }
+ syslog(level, "%s %s", client ? "client:" : " server:",
+ logstring);
+}
+
+/*
+ * set ourselves up for a new data connection. Direction is toward client if
+ * "server" is 0, towards server otherwise.
+ */
+int
+new_dataconn(int server)
+{
+ /*
+ * Close existing data conn.
+ */
+
+ if (client_listen_socket != -1) {
+ close(client_listen_socket);
+ client_listen_socket = -1;
+ }
+ close_client_data();
+
+ if (server_listen_socket != -1) {
+ close(server_listen_socket);
+ server_listen_socket = -1;
+ }
+ close_server_data();
+
+ if (server) {
+ bzero(&server_listen_sa, sizeof(server_listen_sa));
+ server_listen_socket = get_backchannel_socket(SOCK_STREAM,
+ min_port, max_port, -1, 1, &server_listen_sa);
+
+ if (server_listen_socket == -1) {
+ syslog(LOG_INFO, "server socket bind() failed (%m)");
+ exit(EX_OSERR);
+ }
+ if (listen(server_listen_socket, 5) != 0) {
+ syslog(LOG_INFO, "server socket listen() failed (%m)");
+ exit(EX_OSERR);
+ }
+ } else {
+ bzero(&client_listen_sa, sizeof(client_listen_sa));
+ client_listen_socket = get_backchannel_socket(SOCK_STREAM,
+ min_port, max_port, -1, 1, &client_listen_sa);
+
+ if (client_listen_socket == -1) {
+ syslog(LOG_NOTICE,
+ "cannot get client listen socket (%m)");
+ exit(EX_OSERR);
+ }
+ if (listen(client_listen_socket, 5) != 0) {
+ syslog(LOG_NOTICE,
+ "cannot listen on client socket (%m)");
+ exit(EX_OSERR);
+ }
+ }
+ return(0);
+}
+
+static void
+connect_pasv_backchannel(void)
+{
+ struct sockaddr_in listen_sa;
+ socklen_t salen;
+
+ /*
+ * We are about to accept a connection from the client.
+ * This is a PASV data connection.
+ */
+ debuglog(2, "client listen socket ready");
+
+ close_server_data();
+ close_client_data();
+
+ salen = sizeof(listen_sa);
+ client_data_socket = accept(client_listen_socket,
+ (struct sockaddr *)&listen_sa, &salen);
+
+ if (client_data_socket < 0) {
+ syslog(LOG_NOTICE, "accept() failed (%m)");
+ exit(EX_OSERR);
+ }
+ close(client_listen_socket);
+ client_listen_socket = -1;
+ memset(&listen_sa, 0, sizeof(listen_sa));
+
+ server_data_socket = get_backchannel_socket(SOCK_STREAM, min_port,
+ max_port, -1, 1, &listen_sa);
+ if (server_data_socket < 0) {
+ syslog(LOG_NOTICE, "get_backchannel_socket() failed (%m)");
+ exit(EX_OSERR);
+ }
+ if (connect(server_data_socket, (struct sockaddr *) &server_listen_sa,
+ sizeof(server_listen_sa)) != 0) {
+ syslog(LOG_NOTICE, "connect() failed (%m)");
+ exit(EX_NOHOST);
+ }
+ client_data_bytes = 0;
+ server_data_bytes = 0;
+ xfer_start_time = wallclock_time();
+}
+
+static void
+connect_port_backchannel(void)
+{
+ struct sockaddr_in listen_sa;
+ socklen_t salen;
+
+ /*
+ * We are about to accept a connection from the server.
+ * This is a PORT or EPRT data connection.
+ */
+ debuglog(2, "server listen socket ready");
+
+ close_server_data();
+ close_client_data();
+
+ salen = sizeof(listen_sa);
+ server_data_socket = accept(server_listen_socket,
+ (struct sockaddr *)&listen_sa, &salen);
+ if (server_data_socket < 0) {
+ syslog(LOG_NOTICE, "accept() failed (%m)");
+ exit(EX_OSERR);
+ }
+ close(server_listen_socket);
+ server_listen_socket = -1;
+
+ if (getuid() != 0) {
+ /*
+ * We're not running as root, so we get a backchannel
+ * socket bound in our designated range, instead of
+ * getting one bound to port 20 - This is deliberately
+ * not RFC compliant.
+ */
+ bzero(&listen_sa.sin_addr, sizeof(struct in_addr));
+ client_data_socket = get_backchannel_socket(SOCK_STREAM,
+ min_port, max_port, -1, 1, &listen_sa);
+ if (client_data_socket < 0) {
+ syslog(LOG_NOTICE, "get_backchannel_socket() failed (%m)");
+ exit(EX_OSERR);
+ }
+
+ } else {
+
+ /*
+ * We're root, get our backchannel socket bound to port
+ * 20 here, so we're fully RFC compliant.
+ */
+ client_data_socket = socket(AF_INET, SOCK_STREAM, 0);
+
+ salen = 1;
+ listen_sa.sin_family = AF_INET;
+ bzero(&listen_sa.sin_addr, sizeof(struct in_addr));
+ listen_sa.sin_port = htons(20);
+
+ if (setsockopt(client_data_socket, SOL_SOCKET, SO_REUSEADDR,
+ &salen, sizeof(salen)) == -1) {
+ syslog(LOG_NOTICE, "setsockopt() failed (%m)");
+ exit(EX_OSERR);
+ }
+
+ if (bind(client_data_socket, (struct sockaddr *)&listen_sa,
+ sizeof(listen_sa)) == - 1) {
+ syslog(LOG_NOTICE, "data channel bind() failed (%m)");
+ exit(EX_OSERR);
+ }
+ }
+
+ if (connect(client_data_socket, (struct sockaddr *) &client_listen_sa,
+ sizeof(client_listen_sa)) != 0) {
+ syslog(LOG_INFO, "cannot connect data channel (%m)");
+ exit(EX_NOHOST);
+ }
+
+ client_data_bytes = 0;
+ server_data_bytes = 0;
+ xfer_start_time = wallclock_time();
+}
+
+void
+do_client_cmd(struct csiob *client, struct csiob *server)
+{
+ int i, j, rv;
+ char tbuf[100];
+ char *sendbuf = NULL;
+
+ log_control_command((char *)client->line_buffer, 1);
+
+ /* client->line_buffer is an ftp control command.
+ * There is no reason for these to be very long.
+ * In the interest of limiting buffer overrun attempts,
+ * we catch them here.
+ */
+ if (strlen((char *)client->line_buffer) > 512) {
+ syslog(LOG_NOTICE, "excessively long control command");
+ exit(EX_DATAERR);
+ }
+
+ /*
+ * Check the client user provided if needed
+ */
+ if (AnonFtpOnly && strncasecmp((char *)client->line_buffer, "user ",
+ strlen("user ")) == 0) {
+ char *cp;
+
+ cp = (char *) client->line_buffer + strlen("user ");
+ if ((strcasecmp(cp, "ftp\r\n") != 0) &&
+ (strcasecmp(cp, "anonymous\r\n") != 0)) {
+ /*
+ * this isn't anonymous - give the client an
+ * error before they send a password
+ */
+ snprintf(tbuf, sizeof(tbuf),
+ "500 Only anonymous FTP is allowed\r\n");
+ j = 0;
+ i = strlen(tbuf);
+ do {
+ rv = send(client->fd, tbuf + j, i - j, 0);
+ if (rv == -1 && errno != EAGAIN &&
+ errno != EINTR)
+ break;
+ else if (rv != -1)
+ j += rv;
+ } while (j >= 0 && j < i);
+ sendbuf = NULL;
+ } else
+ sendbuf = (char *)client->line_buffer;
+ } else if ((strncasecmp((char *)client->line_buffer, "eprt ",
+ strlen("eprt ")) == 0)) {
+
+ /* Watch out for EPRT commands */
+ char *line = NULL, *q, *p, *result[3], delim;
+ struct addrinfo hints, *res = NULL;
+ unsigned long proto;
+
+ j = 0;
+ line = strdup((char *)client->line_buffer+strlen("eprt "));
+ if (line == NULL) {
+ syslog(LOG_ERR, "insufficient memory");
+ exit(EX_UNAVAILABLE);
+ }
+ p = line;
+ delim = p[0];
+ p++;
+
+ memset(result,0, sizeof(result));
+ for (i = 0; i < 3; i++) {
+ q = strchr(p, delim);
+ if (!q || *q != delim)
+ goto parsefail;
+ *q++ = '\0';
+ result[i] = p;
+ p = q;
+ }
+
+ proto = strtoul(result[0], &p, 10);
+ if (!*result[0] || *p)
+ goto protounsupp;
+
+ memset(&hints, 0, sizeof(hints));
+ if (proto != 1) /* 1 == AF_INET - all we support for now */
+ goto protounsupp;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_NUMERICHOST; /*no DNS*/
+ if (getaddrinfo(result[1], result[2], &hints, &res))
+ goto parsefail;
+ if (res->ai_next)
+ goto parsefail;
+ if (sizeof(client_listen_sa) < res->ai_addrlen)
+ goto parsefail;
+ memcpy(&client_listen_sa, res->ai_addr, res->ai_addrlen);
+
+ debuglog(1, "client wants us to use %s:%u",
+ inet_ntoa(client_listen_sa.sin_addr),
+ htons(client_listen_sa.sin_port));
+
+ /*
+ * Configure our own listen socket and tell the server about it
+ */
+ new_dataconn(1);
+ connection_mode = EPRT_MODE;
+
+ debuglog(1, "we want server to use %s:%u",
+ inet_ntoa(server->sa.sin_addr),
+ ntohs(server_listen_sa.sin_port));
+
+ snprintf(tbuf, sizeof(tbuf), "EPRT |%d|%s|%u|\r\n", 1,
+ inet_ntoa(server->sa.sin_addr),
+ ntohs(server_listen_sa.sin_port));
+ debuglog(1, "to server (modified): %s", tbuf);
+ sendbuf = tbuf;
+ goto out;
+parsefail:
+ snprintf(tbuf, sizeof(tbuf),
+ "500 Invalid argument; rejected\r\n");
+ sendbuf = NULL;
+ goto out;
+protounsupp:
+ /* we only support AF_INET for now */
+ if (proto == 2)
+ snprintf(tbuf, sizeof(tbuf),
+ "522 Protocol not supported, use (1)\r\n");
+ else
+ snprintf(tbuf, sizeof(tbuf),
+ "501 Protocol not supported\r\n");
+ sendbuf = NULL;
+out:
+ if (line)
+ free(line);
+ if (res)
+ freeaddrinfo(res);
+ if (sendbuf == NULL) {
+ debuglog(1, "to client (modified): %s", tbuf);
+ i = strlen(tbuf);
+ do {
+ rv = send(client->fd, tbuf + j, i - j, 0);
+ if (rv == -1 && errno != EAGAIN &&
+ errno != EINTR)
+ break;
+ else if (rv != -1)
+ j += rv;
+ } while (j >= 0 && j < i);
+ }
+ } else if (!NatMode && (strncasecmp((char *)client->line_buffer,
+ "epsv", strlen("epsv")) == 0)) {
+
+ /*
+ * If we aren't in NAT mode, deal with EPSV.
+ * EPSV is a problem - Unlike PASV, the reply from the
+ * server contains *only* a port, we can't modify the reply
+ * to the client and get the client to connect to us without
+ * resorting to using a dynamic rdr rule we have to add in
+ * for the reply to this connection, and take away afterwards.
+ * so this will wait until we have the right solution for rule
+ * additions/deletions in pf.
+ *
+ * in the meantime we just tell the client we don't do it,
+ * and most clients should fall back to using PASV.
+ */
+
+ snprintf(tbuf, sizeof(tbuf),
+ "500 EPSV command not understood\r\n");
+ debuglog(1, "to client (modified): %s", tbuf);
+ j = 0;
+ i = strlen(tbuf);
+ do {
+ rv = send(client->fd, tbuf + j, i - j, 0);
+ if (rv == -1 && errno != EAGAIN && errno != EINTR)
+ break;
+ else if (rv != -1)
+ j += rv;
+ } while (j >= 0 && j < i);
+ sendbuf = NULL;
+ } else if (strncasecmp((char *)client->line_buffer, "port ",
+ strlen("port ")) == 0) {
+ unsigned int values[6];
+ char *tailptr;
+
+ debuglog(1, "Got a PORT command");
+
+ tailptr = (char *)&client->line_buffer[strlen("port ")];
+ values[0] = 0;
+
+ i = sscanf(tailptr, "%u,%u,%u,%u,%u,%u", &values[0],
+ &values[1], &values[2], &values[3], &values[4],
+ &values[5]);
+ if (i != 6) {
+ syslog(LOG_INFO, "malformed PORT command (%s)",
+ client->line_buffer);
+ exit(EX_DATAERR);
+ }
+
+ for (i = 0; i<6; i++) {
+ if (values[i] > 255) {
+ syslog(LOG_INFO,
+ "malformed PORT command (%s)",
+ client->line_buffer);
+ exit(EX_DATAERR);
+ }
+ }
+
+ client_listen_sa.sin_family = AF_INET;
+ client_listen_sa.sin_addr.s_addr = htonl((values[0] << 24) |
+ (values[1] << 16) | (values[2] << 8) |
+ (values[3] << 0));
+
+ client_listen_sa.sin_port = htons((values[4] << 8) |
+ values[5]);
+ debuglog(1, "client wants us to use %u.%u.%u.%u:%u",
+ values[0], values[1], values[2], values[3],
+ (values[4] << 8) | values[5]);
+
+ /*
+ * Configure our own listen socket and tell the server about it
+ */
+ new_dataconn(1);
+ connection_mode = PORT_MODE;
+
+ debuglog(1, "we want server to use %s:%u",
+ inet_ntoa(server->sa.sin_addr),
+ ntohs(server_listen_sa.sin_port));
+
+ snprintf(tbuf, sizeof(tbuf), "PORT %u,%u,%u,%u,%u,%u\r\n",
+ ((u_char *)&server->sa.sin_addr.s_addr)[0],
+ ((u_char *)&server->sa.sin_addr.s_addr)[1],
+ ((u_char *)&server->sa.sin_addr.s_addr)[2],
+ ((u_char *)&server->sa.sin_addr.s_addr)[3],
+ ((u_char *)&server_listen_sa.sin_port)[0],
+ ((u_char *)&server_listen_sa.sin_port)[1]);
+
+ debuglog(1, "to server (modified): %s", tbuf);
+
+ sendbuf = tbuf;
+ } else
+ sendbuf = (char *)client->line_buffer;
+
+ /*
+ *send our (possibly modified) control command in sendbuf
+ * on it's way to the server
+ */
+ if (sendbuf != NULL) {
+ j = 0;
+ i = strlen(sendbuf);
+ do {
+ rv = send(server->fd, sendbuf + j, i - j, 0);
+ if (rv == -1 && errno != EAGAIN && errno != EINTR)
+ break;
+ else if (rv != -1)
+ j += rv;
+ } while (j >= 0 && j < i);
+ }
+}
+
+void
+do_server_reply(struct csiob *server, struct csiob *client)
+{
+ int code, i, j, rv;
+ struct in_addr *iap;
+ static int continuing = 0;
+ char tbuf[100], *sendbuf, *p;
+
+ log_control_command((char *)server->line_buffer, 0);
+
+ if (strlen((char *)server->line_buffer) > 512) {
+ /*
+ * someone's playing games. Have a cow in the syslogs and
+ * exit - we don't pass this on for fear of hurting
+ * our other end, which might be poorly implemented.
+ */
+ syslog(LOG_NOTICE, "long FTP control reply");
+ exit(EX_DATAERR);
+ }
+
+ /*
+ * Watch out for "227 Entering Passive Mode ..." replies
+ */
+ code = strtol((char *)server->line_buffer, &p, 10);
+ if (isspace(server->line_buffer[0]))
+ code = 0;
+ if (!*(server->line_buffer) || (*p != ' ' && *p != '-')) {
+ if (continuing)
+ goto sendit;
+ syslog(LOG_INFO, "malformed control reply");
+ exit(EX_DATAERR);
+ }
+ if (code <= 0 || code > 999) {
+ if (continuing)
+ goto sendit;
+ syslog(LOG_INFO, "invalid server reply code %d", code);
+ exit(EX_DATAERR);
+ }
+ if (*p == '-')
+ continuing = 1;
+ else
+ continuing = 0;
+ if (code == 227 && !NatMode) {
+ unsigned int values[6];
+ char *tailptr;
+
+ debuglog(1, "Got a PASV reply");
+ debuglog(1, "{%s}", (char *)server->line_buffer);
+
+ tailptr = (char *)strchr((char *)server->line_buffer, '(');
+ if (tailptr == NULL) {
+ tailptr = strrchr((char *)server->line_buffer, ' ');
+ if (tailptr == NULL) {
+ syslog(LOG_NOTICE, "malformed 227 reply");
+ exit(EX_DATAERR);
+ }
+ }
+ tailptr++; /* skip past space or ( */
+
+ values[0] = 0;
+
+ i = sscanf(tailptr, "%u,%u,%u,%u,%u,%u", &values[0],
+ &values[1], &values[2], &values[3], &values[4],
+ &values[5]);
+ if (i != 6) {
+ syslog(LOG_INFO, "malformed PASV reply (%s)",
+ client->line_buffer);
+ exit(EX_DATAERR);
+ }
+ for (i = 0; i<6; i++)
+ if (values[i] > 255) {
+ syslog(LOG_INFO, "malformed PASV reply(%s)",
+ client->line_buffer);
+ exit(EX_DATAERR);
+ }
+
+ server_listen_sa.sin_family = AF_INET;
+ server_listen_sa.sin_addr.s_addr = htonl((values[0] << 24) |
+ (values[1] << 16) | (values[2] << 8) | (values[3] << 0));
+ server_listen_sa.sin_port = htons((values[4] << 8) |
+ values[5]);
+
+ debuglog(1, "server wants us to use %s:%u",
+ inet_ntoa(server_listen_sa.sin_addr), (values[4] << 8) |
+ values[5]);
+
+ new_dataconn(0);
+ connection_mode = PASV_MODE;
+ iap = &(server->sa.sin_addr);
+
+ debuglog(1, "we want client to use %s:%u", inet_ntoa(*iap),
+ htons(client_listen_sa.sin_port));
+
+ snprintf(tbuf, sizeof(tbuf),
+ "227 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n",
+ ((u_char *)iap)[0], ((u_char *)iap)[1],
+ ((u_char *)iap)[2], ((u_char *)iap)[3],
+ ((u_char *)&client_listen_sa.sin_port)[0],
+ ((u_char *)&client_listen_sa.sin_port)[1]);
+ debuglog(1, "to client (modified): %s", tbuf);
+ sendbuf = tbuf;
+ } else {
+ sendit:
+ sendbuf = (char *)server->line_buffer;
+ }
+
+ /*
+ * send our (possibly modified) control command in sendbuf
+ * on it's way to the client
+ */
+ j = 0;
+ i = strlen(sendbuf);
+ do {
+ rv = send(client->fd, sendbuf + j, i - j, 0);
+ if (rv == -1 && errno != EAGAIN && errno != EINTR)
+ break;
+ else if (rv != -1)
+ j += rv;
+ } while (j >= 0 && j < i);
+
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct csiob client_iob, server_iob;
+ struct sigaction new_sa, old_sa;
+ int sval, ch, flags, i;
+ socklen_t salen;
+ int one = 1;
+ long timeout_seconds = 0;
+ struct timeval tv;
+#ifdef LIBWRAP
+ int use_tcpwrapper = 0;
+#endif /* LIBWRAP */
+
+ while ((ch = getopt(argc, argv, "D:g:m:M:t:u:AnVwr")) != -1) {
+ char *p;
+ switch (ch) {
+ case 'A':
+ AnonFtpOnly = 1; /* restrict to anon usernames only */
+ break;
+ case 'D':
+ Debug_Level = strtol(optarg, &p, 10);
+ if (!*optarg || *p)
+ usage();
+ break;
+ case 'g':
+ Group = optarg;
+ break;
+ case 'm':
+ min_port = strtol(optarg, &p, 10);
+ if (!*optarg || *p)
+ usage();
+ if (min_port < 0 || min_port > USHRT_MAX)
+ usage();
+ break;
+ case 'M':
+ max_port = strtol(optarg, &p, 10);
+ if (!*optarg || *p)
+ usage();
+ if (max_port < 0 || max_port > USHRT_MAX)
+ usage();
+ break;
+ case 'n':
+ NatMode = 1; /* pass all passives, we're using NAT */
+ break;
+ case 'r':
+ Use_Rdns = 1; /* look up hostnames */
+ break;
+ case 't':
+ timeout_seconds = strtol(optarg, &p, 10);
+ if (!*optarg || *p)
+ usage();
+ break;
+ case 'u':
+ User = optarg;
+ break;
+ case 'V':
+ Verbose = 1;
+ break;
+#ifdef LIBWRAP
+ case 'w':
+ use_tcpwrapper = 1; /* do the libwrap thing */
+ break;
+#endif /* LIBWRAP */
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (max_port < min_port)
+ usage();
+
+ openlog(__progname, LOG_NDELAY|LOG_PID, LOG_DAEMON);
+
+ setlinebuf(stdout);
+ setlinebuf(stderr);
+
+ memset(&client_iob, 0, sizeof(client_iob));
+ memset(&server_iob, 0, sizeof(server_iob));
+
+ if (get_proxy_env(0, &real_server_sa, &client_iob.sa) == -1)
+ exit(EX_PROTOCOL);
+
+ /*
+ * We may now drop root privs, as we have done our ioctl for
+ * pf. If we do drop root, we can't make backchannel connections
+ * for PORT and EPRT come from port 20, which is not strictly
+ * RFC compliant. This shouldn't cause problems for all but
+ * the stupidest ftp clients and the stupidest packet filters.
+ */
+ drop_privs();
+
+ /*
+ * We check_host after get_proxy_env so that checks are done
+ * against the original destination endpoint, not the endpoint
+ * of our side of the rdr. This allows the use of tcpwrapper
+ * rules to restrict destinations as well as sources of connections
+ * for ftp.
+ */
+ if (Use_Rdns)
+ flags = 0;
+ else
+ flags = NI_NUMERICHOST | NI_NUMERICSERV;
+
+ i = getnameinfo((struct sockaddr *)&client_iob.sa,
+ sizeof(client_iob.sa), ClientName, sizeof(ClientName), NULL, 0,
+ flags);
+
+ if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
+ debuglog(2, "name resolution failure (client)");
+ exit(EX_OSERR);
+ }
+
+ i = getnameinfo((struct sockaddr *)&real_server_sa,
+ sizeof(real_server_sa), RealServerName, sizeof(RealServerName),
+ NULL, 0, flags);
+
+ if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
+ debuglog(2, "name resolution failure (server)");
+ exit(EX_OSERR);
+ }
+
+#ifdef LIBWRAP
+ if (use_tcpwrapper && !check_host(&client_iob.sa, &real_server_sa))
+ exit(EX_NOPERM);
+#endif
+
+ client_iob.fd = 0;
+
+ syslog(LOG_INFO, "accepted connection from %s:%u to %s:%u", ClientName,
+ ntohs(client_iob.sa.sin_port), RealServerName,
+ ntohs(real_server_sa.sin_port));
+
+ server_iob.fd = get_backchannel_socket(SOCK_STREAM, min_port, max_port,
+ -1, 1, &server_iob.sa);
+
+ if (connect(server_iob.fd, (struct sockaddr *)&real_server_sa,
+ sizeof(real_server_sa)) != 0) {
+ syslog(LOG_INFO, "cannot connect to %s:%u (%m)", RealServerName,
+ ntohs(real_server_sa.sin_port));
+ exit(EX_NOHOST);
+ }
+
+ /*
+ * Now that we are connected to the real server, get the name
+ * of our end of the server socket so we know our IP address
+ * from the real server's perspective.
+ */
+ salen = sizeof(server_iob.sa);
+ getsockname(server_iob.fd, (struct sockaddr *)&server_iob.sa, &salen);
+
+ i = getnameinfo((struct sockaddr *)&server_iob.sa,
+ sizeof(server_iob.sa), OurName, sizeof(OurName), NULL, 0, flags);
+
+ if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
+ debuglog(2, "name resolution failure (local)");
+ exit(EX_OSERR);
+ }
+
+ debuglog(1, "local socket is %s:%u", OurName,
+ ntohs(server_iob.sa.sin_port));
+
+ /* ignore SIGPIPE */
+ bzero(&new_sa, sizeof(new_sa));
+ new_sa.sa_handler = SIG_IGN;
+ (void)sigemptyset(&new_sa.sa_mask);
+ new_sa.sa_flags = SA_RESTART;
+ if (sigaction(SIGPIPE, &new_sa, &old_sa) != 0) {
+ syslog(LOG_ERR, "sigaction() failed (%m)");
+ exit(EX_OSERR);
+ }
+
+ if (setsockopt(client_iob.fd, SOL_SOCKET, SO_OOBINLINE, (char *)&one,
+ sizeof(one)) == -1) {
+ syslog(LOG_NOTICE, "cannot set SO_OOBINLINE (%m)");
+ exit(EX_OSERR);
+ }
+
+ client_iob.line_buffer_size = STARTBUFSIZE;
+ client_iob.line_buffer = malloc(client_iob.line_buffer_size);
+ client_iob.io_buffer_size = STARTBUFSIZE;
+ client_iob.io_buffer = malloc(client_iob.io_buffer_size);
+ client_iob.next_byte = 0;
+ client_iob.io_buffer_len = 0;
+ client_iob.alive = 1;
+ client_iob.who = "client";
+ client_iob.send_oob_flags = 0;
+ client_iob.real_sa = client_iob.sa;
+
+ server_iob.line_buffer_size = STARTBUFSIZE;
+ server_iob.line_buffer = malloc(server_iob.line_buffer_size);
+ server_iob.io_buffer_size = STARTBUFSIZE;
+ server_iob.io_buffer = malloc(server_iob.io_buffer_size);
+ server_iob.next_byte = 0;
+ server_iob.io_buffer_len = 0;
+ server_iob.alive = 1;
+ server_iob.who = "server";
+ server_iob.send_oob_flags = MSG_OOB;
+ server_iob.real_sa = real_server_sa;
+
+ if (client_iob.line_buffer == NULL || client_iob.io_buffer == NULL ||
+ server_iob.line_buffer == NULL || server_iob.io_buffer == NULL) {
+ syslog (LOG_NOTICE, "insufficient memory");
+ exit(EX_UNAVAILABLE);
+ }
+
+ while (client_iob.alive || server_iob.alive) {
+ int maxfd = 0;
+ fd_set *fdsp;
+
+ if (client_iob.fd > maxfd)
+ maxfd = client_iob.fd;
+ if (client_listen_socket > maxfd)
+ maxfd = client_listen_socket;
+ if (client_data_socket > maxfd)
+ maxfd = client_data_socket;
+ if (server_iob.fd > maxfd)
+ maxfd = server_iob.fd;
+ if (server_listen_socket > maxfd)
+ maxfd = server_listen_socket;
+ if (server_data_socket > maxfd)
+ maxfd = server_data_socket;
+
+ debuglog(3, "client is %s; server is %s",
+ client_iob.alive ? "alive" : "dead",
+ server_iob.alive ? "alive" : "dead");
+
+ fdsp = (fd_set *)calloc(howmany(maxfd + 1, NFDBITS),
+ sizeof(fd_mask));
+ if (fdsp == NULL) {
+ syslog(LOG_NOTICE, "insufficient memory");
+ exit(EX_UNAVAILABLE);
+ }
+
+ if (client_iob.alive && telnet_getline(&client_iob,
+ &server_iob)) {
+ debuglog(3, "client line buffer is \"%s\"",
+ (char *)client_iob.line_buffer);
+ if (client_iob.line_buffer[0] != '\0')
+ do_client_cmd(&client_iob, &server_iob);
+ } else if (server_iob.alive && telnet_getline(&server_iob,
+ &client_iob)) {
+ debuglog(3, "server line buffer is \"%s\"",
+ (char *)server_iob.line_buffer);
+ if (server_iob.line_buffer[0] != '\0')
+ do_server_reply(&server_iob, &client_iob);
+ } else {
+ if (client_iob.alive) {
+ FD_SET(client_iob.fd, fdsp);
+ if (client_listen_socket >= 0)
+ FD_SET(client_listen_socket, fdsp);
+ if (client_data_socket >= 0)
+ FD_SET(client_data_socket, fdsp);
+ }
+ if (server_iob.alive) {
+ FD_SET(server_iob.fd, fdsp);
+ if (server_listen_socket >= 0)
+ FD_SET(server_listen_socket, fdsp);
+ if (server_data_socket >= 0)
+ FD_SET(server_data_socket, fdsp);
+ }
+ tv.tv_sec = timeout_seconds;
+ tv.tv_usec = 0;
+
+ doselect:
+ sval = select(maxfd + 1, fdsp, NULL, NULL,
+ (tv.tv_sec == 0) ? NULL : &tv);
+ if (sval == 0) {
+ /*
+ * This proxy has timed out. Expire it
+ * quietly with an obituary in the syslogs
+ * for any passing mourners.
+ */
+ syslog(LOG_INFO,
+ "timeout: no data for %ld seconds",
+ timeout_seconds);
+ exit(EX_OK);
+ }
+ if (sval == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ goto doselect;
+ syslog(LOG_NOTICE,
+ "select() failed (%m)");
+ exit(EX_OSERR);
+ }
+ if (client_data_socket >= 0 &&
+ FD_ISSET(client_data_socket, fdsp)) {
+ int rval;
+
+ debuglog(3, "transfer: client to server");
+ rval = xfer_data("client to server",
+ client_data_socket,
+ server_data_socket,
+ client_iob.sa.sin_addr,
+ real_server_sa.sin_addr);
+ if (rval <= 0) {
+ close_client_data();
+ close_server_data();
+ show_xfer_stats();
+ } else
+ client_data_bytes += rval;
+ }
+ if (server_data_socket >= 0 &&
+ FD_ISSET(server_data_socket, fdsp)) {
+ int rval;
+
+ debuglog(3, "transfer: server to client");
+ rval = xfer_data("server to client",
+ server_data_socket,
+ client_data_socket,
+ real_server_sa.sin_addr,
+ client_iob.sa.sin_addr);
+ if (rval <= 0) {
+ close_client_data();
+ close_server_data();
+ show_xfer_stats();
+ } else
+ server_data_bytes += rval;
+ }
+ if (server_listen_socket >= 0 &&
+ FD_ISSET(server_listen_socket, fdsp)) {
+ connect_port_backchannel();
+ }
+ if (client_listen_socket >= 0 &&
+ FD_ISSET(client_listen_socket, fdsp)) {
+ connect_pasv_backchannel();
+ }
+ if (client_iob.alive &&
+ FD_ISSET(client_iob.fd, fdsp)) {
+ client_iob.data_available = 1;
+ }
+ if (server_iob.alive &&
+ FD_ISSET(server_iob.fd, fdsp)) {
+ server_iob.data_available = 1;
+ }
+ }
+ free(fdsp);
+ if (client_iob.got_eof) {
+ shutdown(server_iob.fd, 1);
+ shutdown(client_iob.fd, 0);
+ client_iob.got_eof = 0;
+ client_iob.alive = 0;
+ }
+ if (server_iob.got_eof) {
+ shutdown(client_iob.fd, 1);
+ shutdown(server_iob.fd, 0);
+ server_iob.got_eof = 0;
+ server_iob.alive = 0;
+ }
+ }
+
+ if (Verbose)
+ syslog(LOG_INFO, "session ended");
+
+ exit(EX_OK);
+}
diff --git a/contrib/pf/ftp-proxy/getline.c b/contrib/pf/ftp-proxy/getline.c
new file mode 100644
index 0000000..2be3883
--- /dev/null
+++ b/contrib/pf/ftp-proxy/getline.c
@@ -0,0 +1,259 @@
+/* $OpenBSD: getline.c,v 1.15 2003/06/28 01:04:57 deraadt Exp $ */
+
+/*
+ * Copyright (c) 1985, 1988 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ftpcmd.y 5.24 (Berkeley) 2/25/91
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/telnet.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "util.h"
+
+int refill_buffer(struct csiob *iobp);
+
+/*
+ * Refill the io buffer if we KNOW that data is available
+ *
+ * Returns 1 if any new data was obtained, 0 otherwise.
+ */
+
+int
+refill_buffer(struct csiob *iobp)
+{
+ int rqlen, rlen;
+
+ if (!(iobp->data_available))
+ return(0);
+
+ if (iobp->got_eof)
+ return(0);
+
+ /*
+ * The buffer has been entirely consumed if next_byte == io_buffer_len.
+ * Otherwise, there is some still-to-be-used data in io_buffer.
+ * Shuffle it to the start of the buffer.
+ * Note that next_byte will never exceed io_buffer_len.
+ * Also, note that we MUST use bcopy because the two regions could
+ * overlap (memcpy isn't defined to work properly with overlapping
+ * regions).
+ */
+ if (iobp->next_byte < iobp->io_buffer_len) {
+ int dst_ix = 0;
+ int src_ix = iobp->next_byte;
+ int amount = iobp->io_buffer_len - iobp->next_byte;
+
+ bcopy(&iobp->io_buffer[src_ix], &iobp->io_buffer[dst_ix],
+ amount);
+ iobp->io_buffer_len = amount;
+ } else if (iobp->next_byte == iobp->io_buffer_len)
+ iobp->io_buffer_len = 0;
+ else {
+ syslog(LOG_ERR, "next_byte(%d) > io_buffer_len(%d)",
+ iobp->next_byte, iobp->io_buffer_len);
+ exit(EX_OSERR);
+ }
+
+ iobp->next_byte = 0;
+
+ /* don't do tiny reads, grow first if we need to */
+ rqlen = iobp->io_buffer_size - iobp->io_buffer_len;
+ if (rqlen <= 128) {
+ char *tmp;
+
+ iobp->io_buffer_size += 128;
+ tmp = realloc(iobp->io_buffer, iobp->io_buffer_size);
+ if (tmp == NULL) {
+ syslog(LOG_INFO, "Insufficient memory");
+ exit(EX_UNAVAILABLE);
+ }
+ iobp->io_buffer = tmp;
+ rqlen = iobp->io_buffer_size - iobp->io_buffer_len;
+ }
+
+ /*
+ * Always leave an unused byte at the end of the buffer
+ * because the debug output uses that byte from time to time
+ * to ensure that something that is being printed is \0 terminated.
+ */
+ rqlen -= 1;
+
+ doread:
+ rlen = read(iobp->fd, &iobp->io_buffer[iobp->io_buffer_len], rqlen);
+ iobp->data_available = 0;
+ switch (rlen) {
+ case -1:
+ if (errno == EAGAIN || errno == EINTR)
+ goto doread;
+ if (errno != ECONNRESET) {
+ syslog(LOG_INFO, "read() failed on socket from %s (%m)",
+ iobp->who);
+ exit(EX_DATAERR);
+ }
+ /* fall through to EOF case */
+ case 0:
+ iobp->got_eof = 1;
+ return(0);
+ break;
+ default:
+ iobp->io_buffer_len += rlen;
+ break;
+ }
+ return(1);
+}
+
+/*
+ * telnet_getline - a hacked up version of fgets to ignore TELNET escape codes.
+ *
+ * This code is derived from the getline routine found in the UC Berkeley
+ * ftpd code.
+ *
+ */
+
+int
+telnet_getline(struct csiob *iobp, struct csiob *telnet_passthrough)
+{
+ unsigned char ch;
+ int ix;
+ char tbuf[100];
+
+ iobp->line_buffer[0] = '\0';
+
+ /*
+ * If the buffer is empty then refill it right away.
+ */
+ if (iobp->next_byte == iobp->io_buffer_len)
+ if (!refill_buffer(iobp))
+ return(0);
+
+ /*
+ * Is there a telnet command in the buffer?
+ */
+ ch = iobp->io_buffer[iobp->next_byte];
+ if (ch == IAC) {
+ /*
+ * Yes - buffer must have at least three bytes in it
+ */
+ if (iobp->io_buffer_len - iobp->next_byte < 3) {
+ if (!refill_buffer(iobp))
+ return(0);
+ if (iobp->io_buffer_len - iobp->next_byte < 3)
+ return(0);
+ }
+
+ iobp->next_byte++;
+ ch = iobp->io_buffer[iobp->next_byte++];
+
+ switch (ch) {
+ case WILL:
+ case WONT:
+ case DO:
+ case DONT:
+ tbuf[0] = IAC;
+ tbuf[1] = ch;
+ tbuf[2] = iobp->io_buffer[iobp->next_byte++];
+ (void)send(telnet_passthrough->fd, tbuf, 3,
+ telnet_passthrough->send_oob_flags);
+ break;
+ case IAC:
+ break;
+ default:
+ break;
+ }
+ return(1);
+ } else {
+ int clen;
+
+ /*
+ * Is there a newline in the buffer?
+ */
+ for (ix = iobp->next_byte; ix < iobp->io_buffer_len;
+ ix += 1) {
+ if (iobp->io_buffer[ix] == '\n')
+ break;
+ if (iobp->io_buffer[ix] == '\0') {
+ syslog(LOG_INFO,
+ "got NUL byte from %s - bye!",
+ iobp->who);
+ exit(EX_DATAERR);
+ }
+ }
+
+ if (ix == iobp->io_buffer_len) {
+ if (!refill_buffer(iobp))
+ return(0);
+ /*
+ * Empty line returned
+ * will try again soon!
+ */
+ return(1);
+ }
+
+ /*
+ * Expand the line buffer if it isn't big enough. We
+ * use a fudge factor of 5 rather than trying to
+ * figure out exactly how to account for the '\0 \r\n' and
+ * such. The correct fudge factor is 0, 1 or 2 but
+ * anything higher also works. We also grow it by a
+ * bunch to avoid having to do this often. Yes this is
+ * nasty.
+ */
+ if (ix - iobp->next_byte > iobp->line_buffer_size - 5) {
+ char *tmp;
+
+ iobp->line_buffer_size = 256 + ix - iobp->next_byte;
+ tmp = realloc(iobp->line_buffer,
+ iobp->line_buffer_size);
+ if (tmp == NULL) {
+ syslog(LOG_INFO, "Insufficient memory");
+ exit(EX_UNAVAILABLE);
+ }
+ iobp->line_buffer = tmp;
+ }
+
+ /* +1 is for the newline */
+ clen = (ix+1) - iobp->next_byte;
+ memcpy(iobp->line_buffer, &iobp->io_buffer[iobp->next_byte],
+ clen);
+ iobp->next_byte += clen;
+ iobp->line_buffer[clen] = '\0';
+ return(1);
+ }
+}
diff --git a/contrib/pf/ftp-proxy/util.c b/contrib/pf/ftp-proxy/util.c
new file mode 100644
index 0000000..3c8b20e
--- /dev/null
+++ b/contrib/pf/ftp-proxy/util.c
@@ -0,0 +1,296 @@
+/* $OpenBSD: util.c,v 1.16 2003/06/28 01:04:57 deraadt Exp $ */
+
+/*
+ * Copyright (c) 1996-2001
+ * Obtuse Systems Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the Obtuse Systems nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OBTUSE SYSTEMS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OBTUSE
+ * SYSTEMS CORPORATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/file.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "util.h"
+
+int Debug_Level;
+int Use_Rdns;
+
+void debuglog(int debug_level, const char *fmt, ...);
+
+void
+debuglog(int debug_level, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+ if (Debug_Level >= debug_level)
+ vsyslog(LOG_DEBUG, fmt, ap);
+ va_end(ap);
+}
+
+int
+get_proxy_env(int connected_fd, struct sockaddr_in *real_server_sa_ptr,
+ struct sockaddr_in *client_sa_ptr)
+{
+ struct pfioc_natlook natlook;
+ int slen, fd;
+
+ slen = sizeof(*real_server_sa_ptr);
+ if (getsockname(connected_fd, (struct sockaddr *)real_server_sa_ptr,
+ &slen) != 0) {
+ syslog(LOG_ERR, "getsockname() failed (%m)");
+ return(-1);
+ }
+ slen = sizeof(*client_sa_ptr);
+ if (getpeername(connected_fd, (struct sockaddr *)client_sa_ptr,
+ &slen) != 0) {
+ syslog(LOG_ERR, "getpeername() failed (%m)");
+ return(-1);
+ }
+
+ /*
+ * Build up the pf natlook structure.
+ * Just for IPv4 right now
+ */
+ memset((void *)&natlook, 0, sizeof(natlook));
+ natlook.af = AF_INET;
+ natlook.saddr.addr32[0] = client_sa_ptr->sin_addr.s_addr;
+ natlook.daddr.addr32[0] = real_server_sa_ptr->sin_addr.s_addr;
+ natlook.proto = IPPROTO_TCP;
+ natlook.sport = client_sa_ptr->sin_port;
+ natlook.dport = real_server_sa_ptr->sin_port;
+ natlook.direction = PF_OUT;
+
+ /*
+ * Open the pf device and lookup the mapping pair to find
+ * the original address we were supposed to connect to.
+ */
+ fd = open("/dev/pf", O_RDWR);
+ if (fd == -1) {
+ syslog(LOG_ERR, "cannot open /dev/pf (%m)");
+ exit(EX_UNAVAILABLE);
+ }
+
+ if (ioctl(fd, DIOCNATLOOK, &natlook) == -1) {
+ syslog(LOG_INFO,
+ "pf nat lookup failed %s:%hu (%m)",
+ inet_ntoa(client_sa_ptr->sin_addr),
+ ntohs(client_sa_ptr->sin_port));
+ close(fd);
+ return(-1);
+ }
+ close(fd);
+
+ /*
+ * Now jam the original address and port back into the into
+ * destination sockaddr_in for the proxy to deal with.
+ */
+ memset((void *)real_server_sa_ptr, 0, sizeof(struct sockaddr_in));
+ real_server_sa_ptr->sin_port = natlook.rdport;
+ real_server_sa_ptr->sin_addr.s_addr = natlook.rdaddr.addr32[0];
+ real_server_sa_ptr->sin_len = sizeof(struct sockaddr_in);
+ real_server_sa_ptr->sin_family = AF_INET;
+ return(0);
+}
+
+
+/*
+ * Transfer one unit of data across a pair of sockets
+ *
+ * A unit of data is as much as we get with a single read(2) call.
+ */
+int
+xfer_data(const char *what_read,int from_fd, int to_fd, struct in_addr from,
+ struct in_addr to)
+{
+ int rlen, offset, xerrno, mark, flags = 0;
+ char tbuf[4096];
+
+ /*
+ * Are we at the OOB mark?
+ */
+ if (ioctl(from_fd, SIOCATMARK, &mark) < 0) {
+ xerrno = errno;
+ syslog(LOG_ERR, "cannot ioctl(SIOCATMARK) socket from %s (%m)",
+ what_read);
+ errno = xerrno;
+ return(-1);
+ }
+ if (mark)
+ flags = MSG_OOB; /* Yes - at the OOB mark */
+
+snarf:
+ rlen = recv(from_fd, tbuf, sizeof(tbuf), flags);
+ if (rlen == -1 && flags == MSG_OOB && errno == EINVAL) {
+ /* OOB didn't work */
+ flags = 0;
+ rlen = recv(from_fd, tbuf, sizeof(tbuf), flags);
+ }
+ if (rlen == 0) {
+ debuglog(3, "EOF on read socket");
+ return(0);
+ } else if (rlen == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ goto snarf;
+ xerrno = errno;
+ syslog(LOG_ERR, "xfer_data (%s): failed (%m) with flags 0%o",
+ what_read, flags);
+ errno = xerrno;
+ return(-1);
+ } else {
+ offset = 0;
+ debuglog(3, "got %d bytes from socket", rlen);
+
+ while (offset < rlen) {
+ int wlen;
+ fling:
+ wlen = send(to_fd, &tbuf[offset], rlen - offset,
+ flags);
+ if (wlen == 0) {
+ debuglog(3, "zero-length write");
+ goto fling;
+ } else if (wlen == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ goto fling;
+ xerrno = errno;
+ syslog(LOG_INFO, "write failed (%m)");
+ errno = xerrno;
+ return(-1);
+ } else {
+ debuglog(3, "wrote %d bytes to socket",wlen);
+ offset += wlen;
+ }
+ }
+ return(offset);
+ }
+}
+
+/*
+ * get_backchannel_socket gets us a socket bound somewhere in a
+ * particular range of ports
+ */
+int
+get_backchannel_socket(int type, int min_port, int max_port, int start_port,
+ int direction, struct sockaddr_in *sap)
+{
+ int count;
+
+ /*
+ * Make sure that direction is 'defined' and that min_port is not
+ * greater than max_port.
+ */
+ if (direction != -1)
+ direction = 1;
+
+ /* by default we go up by one port until we find one */
+ if (min_port > max_port) {
+ errno = EINVAL;
+ return(-1);
+ }
+
+ count = 1 + max_port - min_port;
+
+ /*
+ * Pick a port we can bind to from within the range we want.
+ * If the caller specifies -1 as the starting port number then
+ * we pick one somewhere in the range to try.
+ * This is an optimization intended to speedup port selection and
+ * has NOTHING to do with security.
+ */
+ if (start_port == -1)
+ start_port = (arc4random() % count) + min_port;
+
+ if (start_port < min_port || start_port > max_port) {
+ errno = EINVAL;
+ return(-1);
+ }
+
+ while (count-- > 0) {
+ struct sockaddr_in sa;
+ int one, fd;
+
+ fd = socket(AF_INET, type, 0);
+
+ bzero(&sa, sizeof sa);
+ sa.sin_family = AF_INET;
+ if (sap == NULL)
+ sa.sin_addr.s_addr = INADDR_ANY;
+ else
+ sa.sin_addr.s_addr = sap->sin_addr.s_addr;
+
+ /*
+ * Indicate that we want to reuse a port if it happens that the
+ * port in question was a listen port recently.
+ */
+ one = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one,
+ sizeof(one)) == -1)
+ return(-1);
+
+ sa.sin_port = htons(start_port);
+
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == 0) {
+ if (sap != NULL)
+ *sap = sa;
+ return(fd);
+ }
+
+ if (errno != EADDRINUSE)
+ return(-1);
+
+ /* if it's in use, try the next port */
+ close(fd);
+
+ start_port += direction;
+ if (start_port < min_port)
+ start_port = max_port;
+ else if (start_port > max_port)
+ start_port = min_port;
+ }
+ errno = EAGAIN;
+ return(-1);
+}
diff --git a/contrib/pf/ftp-proxy/util.h b/contrib/pf/ftp-proxy/util.h
new file mode 100644
index 0000000..597cd18
--- /dev/null
+++ b/contrib/pf/ftp-proxy/util.h
@@ -0,0 +1,68 @@
+/* $OpenBSD: util.h,v 1.3 2002/05/23 10:22:14 deraadt Exp $ */
+
+/*
+ * Copyright (c) 1996-2001
+ * Obtuse Systems Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the Obtuse Systems nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+struct proxy_channel {
+ int pc_to_fd, pc_from_fd;
+ int pc_alive;
+ int pc_nextbyte;
+ int pc_flags;
+ int pc_length;
+ int pc_size;
+ struct sockaddr_in pc_from_sa, pc_to_sa;
+ int (*pc_filter)( void ** databuf, int datalen);
+ char *pc_buffer;
+};
+
+struct csiob {
+ int fd;
+ int line_buffer_size, io_buffer_size, io_buffer_len, next_byte;
+ unsigned char *io_buffer, *line_buffer;
+ struct sockaddr_in sa, real_sa;
+ char *who;
+ char alive, got_eof, data_available;
+ int send_oob_flags;
+};
+
+extern int telnet_getline(struct csiob *iobp,
+ struct csiob *telnet_passthrough);
+
+extern int get_proxy_env(int fd, struct sockaddr_in *server_sa_ptr,
+ struct sockaddr_in *client_sa_ptr);
+
+extern int get_backchannel_socket(int type, int min_port, int max_port,
+ int start_port, int direction, struct sockaddr_in *sap);
+
+extern int xfer_data(const char *what_read, int from_fd, int to_fd,
+ struct in_addr from, struct in_addr to);
+
+extern char *ProgName;
+
+
OpenPOWER on IntegriCloud