summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Beck <beck@cvs.openbsd.org>2001-08-19 04:11:13 +0000
committerBob Beck <beck@cvs.openbsd.org>2001-08-19 04:11:13 +0000
commit39a444a369d31d9b46d53363b2c4048743e38516 (patch)
treef4084ba7c88e380c0194129f6ecc17ea4927bf16
parent2227a0f057f47fc26716273fd678ccec599a6b7b (diff)
transparent ftp proxy, based on Obtuse Systems juniper stuff with much
modernizing and cleanup. still needs looking at. Currently supports PORT PASV EPRT data connections with only a pf rdr to capture the control connection. (I.E. you don't need ip forwarding or other NAT stuff). Runs from inetd. Supports all passive (EPSV PASV) when using -n flag, where the proxy ignores passive mode data connections (and assumes nat will get them through). Todo yet: More audit IpV6 Handle EPSV in proxy (with an rdr added then removed) Option to Daemonize and bind only to the loopback More Content/Login filtering, etc. etc. and more bloat
-rw-r--r--libexec/ftp-proxy/Makefile14
-rw-r--r--libexec/ftp-proxy/ftp-proxy.8154
-rw-r--r--libexec/ftp-proxy/ftp-proxy.c1320
-rw-r--r--libexec/ftp-proxy/getline.c279
-rw-r--r--libexec/ftp-proxy/util.c322
-rw-r--r--libexec/ftp-proxy/util.h68
6 files changed, 2157 insertions, 0 deletions
diff --git a/libexec/ftp-proxy/Makefile b/libexec/ftp-proxy/Makefile
new file mode 100644
index 00000000000..d03a20324cd
--- /dev/null
+++ b/libexec/ftp-proxy/Makefile
@@ -0,0 +1,14 @@
+# $OpenBSD: Makefile,v 1.1 2001/08/19 04:11:11 beck Exp $
+# @(#)Makefile 8.2 (Berkeley) 4/4/94
+
+PROG= ftp-proxy
+CFLAGS+=-Wall -Werror
+SRCS= ftp-proxy.c getline.c util.c
+MAN= ftp-proxy.8
+
+.include <bsd.own.mk>
+
+LDADD+= -lwrap
+DPADD+= ${LIBWRAP}
+
+.include <bsd.prog.mk>
diff --git a/libexec/ftp-proxy/ftp-proxy.8 b/libexec/ftp-proxy/ftp-proxy.8
new file mode 100644
index 00000000000..0282888e909
--- /dev/null
+++ b/libexec/ftp-proxy/ftp-proxy.8
@@ -0,0 +1,154 @@
+.\" $OpenBSD: ftp-proxy.8,v 1.1 2001/08/19 04:11:11 beck 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 Aug 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 AnVwr
+.Op Fl t Ar timeout
+.Op Fl D Ar debuglevel
+.Op Fl m Ar minport
+.Op Fl M Ar maxport
+.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 pf rdr command.
+.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 "ftp" or "anonymous" only. Any
+attempt to log in as another user will be blocked by the proxy.
+.It Fl n
+Activate network address translation mode. in this mode, the proxy
+will not attempt to proxy passive mode (PASV or EPSV) data connections,
+for this to work on a gateway you 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 nat.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 ftp-proxy
+does not require any ip forwarding or NAT beyond the rdr necessary to
+capture the ftp control connection.
+.It Fl V
+Be verbose. With this option the proxy logs the control commands
+sent by clients and the replies send by the servers to
+.Xr syslog 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.
+.It Fl r
+Use reverse host (reverse DNS) lookups for logging and libwrap use
+By default the proxy does not look up hostnames for libwrap or logging
+purposes.
+.It Fl m minport
+specify the lower end of the port range the proxy will use for all
+data connections it establishes. The default is
+.Ev IPPORT_HIFIRSTAUTO
+defined in <netinet/in.h>
+as 49152
+.It Fl M maxport
+specify the upper end of the port range the proxy will use for the
+data connections it establishes. The default is
+.Ev IPPORT_HILASTAUTO
+defined in <netinet/in.h>
+as 65535
+.It Fl t timeout
+specifies a timeout, in seconds. The proxy will exit
+and close open connections if it sees no data the duration of
+the timeout. The default is 0, which means the proxy will not time out.
+.It Fl D debuglevel
+specify a debug level, where the proxy emits verbose debug output
+into
+.Xr syslog 8
+at level LOG_DEBUG. meaningful values of debuglevel are 0-3, where 0
+is no debug output and 3 is lots of debug output. the default is 0
+.El
+.Pp
+ftp-proxy is run from
+.Xr inetd 8
+and requires that ftp connections are redirected to it using an rdr
+rule. A typical way to do this would be to use a rule such as
+.Pp
+rdr on xl0 from any to any port 21 -> 127.0.0.1 port 8081
+.Pp
+In
+.Xr nat.conf 5
+(this assumes you are on a gateway where xl0 would be your inside interface).
+You would then need to configure
+.Xr inetd 8
+to run ftp-proxy on the port from above
+using
+.Pp
+8081 stream tcp nowait root /usr/libexec/ftp-proxy ftp-proxy
+.Pp
+in
+.Xr inetd.conf 5
+.Sh SEE ALSO
+.Xr ftp 1 ,
+.Xr host.allow 5 ,
+.Xr host.deny 5 ,
+.Xr nat.conf 5 ,
+.Xr pfctl 8 ,
+.Xr syslogd 8
+.Sh BUGS
+.Pp
+Extended Passive mode (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 (PASV)
+which is supported. EPSV will work in network address translation mode,
+assuming a
+.Xr nat.conf 5
+setup which allows the EPSV connections through to their destinations.
+.Pp
+IPv6 is not yet supported. This will have to wait for corresponding
+support from
+.Xr pf 4
+
diff --git a/libexec/ftp-proxy/ftp-proxy.c b/libexec/ftp-proxy/ftp-proxy.c
new file mode 100644
index 00000000000..54e12936614
--- /dev/null
+++ b/libexec/ftp-proxy/ftp-proxy.c
@@ -0,0 +1,1320 @@
+/* $OpenBSD: ftp-proxy.c,v 1.1 2001/08/19 04:11:12 beck 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 retreived 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 <netdb.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <tcpd.h>
+#include <unistd.h>
+
+#include "util.h"
+
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_NOTICE;
+
+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. Similarily, 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 *argstr = "m:M:D:t:AnVwr";
+
+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, ...);
+
+static void
+usage()
+{
+ syslog(LOG_NOTICE,
+ "usage: %s [-ArVw] [-t timeout] [-D debuglevel] %s",
+ __progname, "[-m min_port] [-M max_port ]\n");
+ exit(EX_USAGE);
+}
+
+/*
+ * 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.
+ */
+
+ if (Use_Rdns) {
+ i = getnameinfo(
+ (struct sockaddr *) &client_sin->sin_addr,
+ sizeof(&client_sin->sin_addr), cname,
+ sizeof(cname), NULL, 0, NI_NAMEREQD);
+ if (i == -1)
+ 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 == -1)
+ 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);
+}
+
+double
+wallclock_time()
+{
+ 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()
+{
+ 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 completed (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 completed (%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 completed (%dm %ds", idelta / 60,
+ idelta % 60);
+ if (i >= len)
+ goto logit;
+ len -= i;
+ }
+ } else {
+ i = snprintf(tbuf, len, "data transfer completed (%.1fs",
+ delta);
+ if (i >= len)
+ goto logit;
+ len -= i;
+ }
+
+ if (client_data_bytes > 0) {
+ i = snprintf(&tbuf[strlen(tbuf)], len,
+ ", %d (%.1fKB/s) to server", 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 (%.1fKB/s) to client", server_data_bytes,
+ (server_data_bytes / delta) / (double)1024);
+ if (i >= len)
+ goto logit;
+ len -= i;
+ }
+ strlcat(tbuf,")", len);
+ logit:
+ syslog(LOG_INFO,"%s",tbuf);
+}
+
+/*
+ * Are we in PORT mode, PASV mode or unknown mode?
+ */
+
+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?"from client:":"server reply:"),
+ 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;
+ }
+ if (client_data_socket != -1) {
+ shutdown(client_data_socket,2);
+ close(client_data_socket);
+ client_data_socket = -1;
+ }
+ if (server_listen_socket != -1) {
+ close(server_listen_socket);
+ server_listen_socket = -1;
+ }
+ if (server_data_socket != -1) {
+ shutdown(server_data_socket,2);
+ close(server_data_socket);
+ server_data_socket = -1;
+ }
+
+ 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,"bind of server socket failed (%m)");
+ exit(EX_OSERR);
+ }
+ if (listen(server_listen_socket, 5) != 0) {
+ syslog(LOG_INFO,"listen on server socket 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,
+ "can't get client listen socket (%m)");
+ exit(EX_OSERR);
+ }
+ if (listen(client_listen_socket, 5) != 0) {
+ syslog(LOG_NOTICE,
+ "can't listen on client socket (%m)");
+ exit(EX_OSERR);
+ }
+ }
+ return(0);
+}
+
+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 = 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 = client->line_buffer;
+ }
+
+
+ /*
+ * Watch out for EPRT commands.
+ */
+
+ else if ((strncasecmp((char *)client->line_buffer,"eprt ",
+ strlen("eprt ")) == 0)) {
+
+ char *line = NULL;
+ char *q, *p;
+ char *result[3];
+ char delim;
+ struct addrinfo hints;
+ struct addrinfo *res = NULL;
+ unsigned long proto;
+
+ j = 0;
+ line = strdup(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\n",
+ 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\n",
+ 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\n",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 - Unliks 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
+ * addtions/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\n",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];
+ int byte_number;
+ u_char *tailptr, ch;
+
+ debuglog(1,"Got a PORT command\n");
+
+ tailptr = &client->line_buffer[strlen("port ")];
+ byte_number = 0;
+ values[0] = 0;
+ while ((ch = *tailptr) == ',' || isdigit(ch)) {
+ if (isdigit(ch)) {
+ values[byte_number] = values[byte_number]
+ * 10 + ch - '0';
+ if (values[byte_number] > 255) {
+ syslog(LOG_NOTICE, "%s %s %s",
+ "ERROR - byte value greater",
+ "than 255 in PORT command",
+ "- terminating session");
+ exit(EX_DATAERR);
+ }
+ } else if (ch == ',') {
+ byte_number += 1;
+ if (byte_number < 6)
+ values[byte_number] = 0;
+ else {
+ syslog(LOG_NOTICE, "%s %s",
+ "too many byte values in PORT",
+ "command - terminating session");
+ exit(EX_DATAERR);
+ }
+ }
+ tailptr += 1;
+ }
+
+ if (byte_number != 5) {
+ syslog(LOG_NOTICE, "Too few byte values in %s",
+ "PORT command - session terminated");
+ 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\n",
+ 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\n",
+ 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 = 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;
+ char tbuf[100];
+ char *sendbuf;
+
+ 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 (> 512 bytes) ftp control reply");
+ exit(EX_DATAERR);
+ }
+
+ /*
+ * Watch out for "227 Entering Passive Mode ..." replies
+ */
+
+ code = atoi((char *)server->line_buffer);
+ if (code <= 0 || code > 999) {
+ syslog(LOG_INFO,"invalid server reply code %d", code);
+ exit(EX_DATAERR);
+ }
+ if (code == 227 && !NatMode) {
+ u_char ch;
+ u_char *tailptr;
+ int byte_number;
+ unsigned int values[6];
+
+ debuglog(1, "Got a PASV reply\n");
+ debuglog(1, "{%s}\n",(char *)server->line_buffer);
+
+ tailptr = strchr((char *)server->line_buffer,'(');
+ if (tailptr == NULL) {
+ syslog(LOG_NOTICE, "malformed 227 reply");
+ exit(EX_DATAERR);
+ }
+
+ tailptr += 1; /* Move past the open-parentheses */
+ byte_number = 0;
+ values[0] = 0;
+ while ((ch = *tailptr) == ',' || isdigit(ch)) {
+ if (isdigit(ch)) {
+ values[byte_number] = values[byte_number]
+ * 10 + ch - '0';
+ if (values[byte_number] > 255) {
+ syslog(LOG_NOTICE,
+ "malformed 227 reply");
+ exit(EX_DATAERR);
+ }
+ } else if (ch == ',') {
+ byte_number += 1;
+ if (byte_number < 6) {
+ values[byte_number] = 0;
+ } else {
+ syslog(LOG_NOTICE,
+ "malformed 227 reply");
+ exit(EX_DATAERR);
+ }
+ }
+ tailptr += 1;
+ }
+
+ /*
+ * The PASV reply should be terminated by a closing
+ * parentheses.
+ */
+
+ if (ch != ')') {
+ syslog(LOG_INFO,"malformed 227 reply, junk at end");
+ exit(EX_DATAERR);
+ }
+
+ /* we need the righr number of bytes for ipv4 and port here */
+
+ if (byte_number != 5) {
+ syslog(LOG_NOTICE,
+ "malformed 227 reply, missing bytes");
+ 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\n",
+ 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\n", inet_ntoa(*iap),
+ (((u_char *)&client_listen_sa.sin_port)[0] << 8)
+ | ((u_char *)&client_listen_sa.sin_port)[1]);
+
+ 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\n",tbuf);
+
+ sendbuf = tbuf;
+ } else
+ sendbuf = 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 timeval tv;
+ long timeout_seconds = 0;
+ struct sigaction new_sa, old_sa;
+ int sval, ch, salen, flags, i;
+ int use_tcpwrapper = 0;
+ int one = 1;
+
+ while ((ch = getopt(argc, argv, argstr)) != -1) {
+ switch (ch) {
+ case 'A':
+ AnonFtpOnly = 1; /* restrict to anon usernames only */
+ break;
+ case 'w':
+ use_tcpwrapper = 1; /* do the libwrap thing */
+ break;
+ case 'V':
+ Verbose = 1;
+ break;
+ case 't':
+ timeout_seconds = atoi(optarg);
+ break;
+ case 'D':
+ Debug_Level = atoi(optarg);
+ break;
+ case 'r':
+ Use_Rdns = 1; /* look up hostnames */
+ break;
+ case 'n':
+ NatMode = 1; /* pass all passives, we're using NAT */
+ break;
+ case 'm':
+ min_port = atoi(optarg);
+ if (min_port < 0 || min_port > USHRT_MAX)
+ usage();
+ break;
+ case 'M':
+ max_port = atoi(optarg);
+ if (min_port < 0 || min_port > USHRT_MAX)
+ usage();
+ break;
+ 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 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 = NI_NUMERICHOST | NI_NUMERICSERV;
+ else
+ flags = 0;
+
+ i = getnameinfo((struct sockaddr *)&client_iob.sa,
+ sizeof(client_iob.sa), ClientName, sizeof(ClientName), NULL, 0,
+ flags);
+
+ if (i == -1) {
+ syslog (LOG_ERR, "getnameinfo failed (%m)");
+ exit(EX_OSERR);
+ }
+
+ i = getnameinfo((struct sockaddr *)&real_server_sa,
+ sizeof(real_server_sa), RealServerName, sizeof(RealServerName),
+ NULL, 0, flags);
+
+ if (i == -1) {
+ syslog (LOG_ERR, "getnameinfo failed (%m)");
+ exit(EX_OSERR);
+ }
+
+ if (use_tcpwrapper && !check_host(&client_iob.sa, &real_server_sa))
+ exit(EX_NOPERM);
+
+ client_iob.fd = 0;
+
+
+ /* Check to see if we have a timeout defined, if so,
+ * set a timeout for this select call to that value, so
+ * we may time out if don't see any data in timeout
+ * seconds.
+ */
+
+ tv.tv_sec = timeout_seconds;
+ tv.tv_usec = 0;
+
+ timeout_seconds=tv.tv_sec;
+
+
+ debuglog(1, "client is %s:%u\n", ClientName,
+ ntohs(client_iob.sa.sin_port));
+
+ debuglog(1, "target server is %s:%u\n", 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, "Can't 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.
+ */
+
+ i = getnameinfo((struct sockaddr *)&client_iob.sa,
+ sizeof(client_iob.sa), OurName, sizeof(OurName), NULL, 0, flags);
+
+ salen = sizeof(server_iob.sa);
+ getsockname(server_iob.fd, (struct sockaddr *)&server_iob.sa, &salen);
+
+ debuglog(1, "our end of socket to server is %s:%u\n", OurName,
+ ntohs(server_iob.sa.sin_port));
+
+ /* ignore sigpipe */
+
+ 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,"Can't set SO_OOBINLINE (%m) - exiting");
+ 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 (malloc failed)");
+ exit(EX_UNAVAILABLE);
+ }
+
+
+ while (client_iob.alive || server_iob.alive) {
+ fd_set fds;
+
+ /*
+ * we use a static fd_set here, any individual
+ * instance of this program can't possibly have too
+ * many open files for FD_SETSIZE - we deal here only
+ * with one ftp connection
+ */
+
+ debuglog(3,"client is %s, server is %s\n",
+ client_iob.alive ? "alive" : "dead",
+ server_iob.alive ? "alive" : "dead");
+
+ FD_ZERO(&fds);
+
+ if (client_iob.alive && telnet_getline(&client_iob,
+ &server_iob)) {
+ debuglog(3, "client line buffer is \"%s\"\n",
+ (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\"\n",
+ (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, &fds);
+ if (client_listen_socket >= 0)
+ FD_SET(client_listen_socket, &fds);
+ if (client_data_socket >= 0)
+ FD_SET(client_data_socket, &fds);
+ }
+ if (server_iob.alive) {
+ FD_SET(server_iob.fd, &fds);
+ if (server_listen_socket >= 0)
+ FD_SET(server_listen_socket, &fds);
+ if (server_data_socket >= 0)
+ FD_SET(server_data_socket, &fds);
+ }
+
+ tv.tv_sec = timeout_seconds;
+ tv.tv_usec = 0;
+
+ doselect:
+ switch (sval = select(FD_SETSIZE, &fds, NULL, NULL,
+ (tv.tv_sec == 0) ? NULL : &tv)) {
+ case 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);
+ break;
+
+ case -1:
+ if (errno == EINTR || errno == EAGAIN)
+ goto doselect;
+ syslog(LOG_NOTICE,
+ "select failed (%m) - exiting");
+ exit(EX_OSERR);
+ break;
+
+ default:
+ if (client_data_socket >= 0 &&
+ FD_ISSET(client_data_socket,&fds)) {
+ int rval;
+ debuglog(3,"xfer client to server\n");
+ 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) {
+ shutdown(client_data_socket,2);
+ close(client_data_socket);
+ client_data_socket = -1;
+ shutdown(server_data_socket,2);
+ close(server_data_socket);
+ server_data_socket = -1;
+ show_xfer_stats();
+ } else
+ client_data_bytes += rval;
+ }
+ if (server_data_socket >= 0 &&
+ FD_ISSET(server_data_socket,&fds)) {
+ int rval;
+
+ debuglog(3,"xfer server to client\n");
+ 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) {
+ shutdown(client_data_socket,2);
+ close(client_data_socket);
+ client_data_socket = -1;
+ shutdown(server_data_socket,2);
+ close(server_data_socket);
+ server_data_socket = -1;
+ show_xfer_stats();
+ } else
+ server_data_bytes += rval;
+ }
+ if (server_listen_socket >= 0 &&
+ FD_ISSET(server_listen_socket,&fds)) {
+ struct sockaddr_in listen_sa;
+
+ /*
+ * We are about to accept a
+ * connection from the server.
+ * This is a PORT or EPRT data
+ * connection.
+ */
+
+ debuglog(2,
+ "server listen socket ready\n");
+
+ if (server_data_socket >= 0) {
+ shutdown(server_data_socket,2);
+ close(server_data_socket);
+ server_data_socket = -1;
+ }
+ if (client_data_socket >= 0) {
+ shutdown(client_data_socket,2);
+ close(client_data_socket);
+ client_data_socket = -1;
+ }
+ 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;
+ 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));
+
+ debuglog(2,"setting sin_addr to %s\n",
+ inet_ntoa(client_iob.sa.sin_addr));
+
+ listen_sa.sin_port = htons(20);
+ salen = 1;
+ setsockopt(client_data_socket,
+ SOL_SOCKET, SO_REUSEADDR, &salen,
+ sizeof(salen));
+ if (bind(client_data_socket,
+ (struct sockaddr *)&listen_sa,
+ sizeof(listen_sa)) < 0) {
+ syslog(LOG_NOTICE,
+ "bind to 20 failed (%m)");
+ exit(EX_OSERR);
+ }
+
+ if (connect(client_data_socket,
+ (struct sockaddr *)
+ &client_listen_sa,
+ sizeof(client_listen_sa)) != 0) {
+ syslog(LOG_INFO,
+ "can't connect (%m)");
+ exit(EX_NOHOST);
+ }
+ client_data_bytes = 0;
+ server_data_bytes = 0;
+ xfer_start_time = wallclock_time();
+ }
+ if (client_listen_socket >= 0 &&
+ FD_ISSET(client_listen_socket,&fds)) {
+ struct sockaddr_in listen_sa;
+ int salen;
+
+ /*
+ * We are about to accept a
+ * connection from the client.
+ * This is a PASV data
+ * connection.
+ */
+
+ debuglog(2,
+ "client listen socket ready\n");
+
+ if (server_data_socket >= 0) {
+ shutdown(server_data_socket,2);
+ close(server_data_socket);
+ server_data_socket = -1;
+ }
+ if (client_data_socket >= 0) {
+ shutdown(client_data_socket,2);
+ close(client_data_socket);
+ client_data_socket = -1;
+ }
+ 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,
+ "backchannel 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();
+ }
+ if (client_iob.alive &&
+ FD_ISSET(client_iob.fd,&fds)) {
+ client_iob.data_available = 1;
+ }
+
+ if (server_iob.alive &&
+ FD_ISSET(server_iob.fd,&fds)) {
+ server_iob.data_available = 1;
+ }
+
+ }
+ }
+
+ 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;
+ }
+
+ }
+ exit(EX_OK);
+}
diff --git a/libexec/ftp-proxy/getline.c b/libexec/ftp-proxy/getline.c
new file mode 100644
index 00000000000..f1eb1deb504
--- /dev/null
+++ b/libexec/ftp-proxy/getline.c
@@ -0,0 +1,279 @@
+/*
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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
+ */
+
+/*
+ * RCS information:
+ * $Id: getline.c,v 1.1 2001/08/19 04:11:12 beck Exp $
+ */
+
+#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"
+
+/*
+ * Refill the io buffer if we KNOW that data is available
+ *
+ * Returns 1 if any new data was obtained, 0 otherwise.
+ */
+
+int
+refill_buffer(register 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) {
+ register int dst_ix, src_ix;
+ register int amount;
+ dst_ix = 0;
+ src_ix = iobp->next_byte;
+ 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)\n",
+ 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)
+ return(0);
+ 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;
+
+ }
+ 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(register struct csiob *iobp, struct csiob *telnet_passthrough)
+{
+ unsigned char ch;
+ register 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 null byte from %s - bye!\n",
+ 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)
+ return(0);
+ iobp->line_buffer = tmp;
+ }
+
+ /* +1 is for the newline */
+ clen = (ix+1) - iobp->next_byte;
+ memcpy(iobp->line_buffer, &iobp->io_buffer[iobp->next_byte],
+ clen);
+ iobp->next_byte += clen;
+ iobp->line_buffer[clen] = '\0';
+
+ return(1);
+
+ }
+
+}
diff --git a/libexec/ftp-proxy/util.c b/libexec/ftp-proxy/util.c
new file mode 100644
index 00000000000..e553b5cf3dc
--- /dev/null
+++ b/libexec/ftp-proxy/util.c
@@ -0,0 +1,322 @@
+/* $OpenBSD: util.c,v 1.1 2001/08/19 04:11:12 beck 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 <netinet/in.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 <tcpd.h>
+#include <unistd.h>
+
+#include "util.h"
+
+int Debug_Level;
+int Use_Rdns;
+
+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);
+ }
+}
+
+
+int
+get_proxy_env(int connected_fd, struct sockaddr_in *real_server_sa_ptr,
+ struct sockaddr_in *client_sa_ptr)
+{
+ struct pf_natlook natlook;
+ struct pf_natlook *natlookp;
+ char * client;
+ 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.
+ */
+
+ memset((void *)&natlook, 0, sizeof(natlook));
+ natlook.saddr = client_sa_ptr->sin_addr.s_addr;
+ natlook.daddr = 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.
+ */
+
+ client = strdup(inet_ntoa(client_sa_ptr->sin_addr));
+ if (client == NULL) {
+ errno = ENOMEM;
+ return(-1);
+ }
+
+ fd = open("/dev/pf", O_RDWR);
+ if (fd == -1) {
+ syslog(LOG_ERR, "Can't open /dev/pf (%m)");
+ exit(EX_UNAVAILABLE);
+ }
+
+ natlookp = &natlook;
+ if (ioctl(fd, DIOCNATLOOK, natlookp) == -1) {
+ syslog(LOG_INFO,
+ "pf nat lookup failed (%m), connection from %s:%hu",
+ client, 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 = natlookp->rdport;
+ real_server_sa_ptr->sin_addr.s_addr = natlookp->rdaddr;
+ 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)
+{
+ char tbuf[4096];
+ int rlen, offset, xerrno;
+ int mark, flags;
+
+ /*
+ * Are we at the OOB mark?
+ */
+
+ if (ioctl(from_fd, SIOCATMARK, &mark) < 0) {
+ xerrno = errno;
+ syslog(LOG_ERR,"can't ioctl(SIOCATMARK) socket from %s (%m)",
+ what_read);
+ errno = xerrno;
+ return(-1);
+ }
+
+ if (mark)
+ flags = MSG_OOB; /* Yes - at the OOB mark */
+ else
+ flags = 0;
+
+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, "xfer_data - 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, "xfer got %d bytes from socket\n",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\n",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.
+ */
+
+ /* by default we go up by one port until we find one */
+ if (direction != -1)
+ direction = 1;
+
+ 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) {
+ int one;
+ int fd;
+ struct sockaddr_in sa;
+
+ fd = socket(AF_INET, type, 0);
+
+ 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/libexec/ftp-proxy/util.h b/libexec/ftp-proxy/util.h
new file mode 100644
index 00000000000..3e9e33ede98
--- /dev/null
+++ b/libexec/ftp-proxy/util.h
@@ -0,0 +1,68 @@
+/* $OpenBSD: util.h,v 1.1 2001/08/19 04:11:12 beck 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;
+
+