summaryrefslogtreecommitdiff
path: root/libexec/ftp-proxy/ftp-proxy.c
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 /libexec/ftp-proxy/ftp-proxy.c
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
Diffstat (limited to 'libexec/ftp-proxy/ftp-proxy.c')
-rw-r--r--libexec/ftp-proxy/ftp-proxy.c1320
1 files changed, 1320 insertions, 0 deletions
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);
+}