diff options
author | Bob Beck <beck@cvs.openbsd.org> | 2001-08-19 04:11:13 +0000 |
---|---|---|
committer | Bob Beck <beck@cvs.openbsd.org> | 2001-08-19 04:11:13 +0000 |
commit | 39a444a369d31d9b46d53363b2c4048743e38516 (patch) | |
tree | f4084ba7c88e380c0194129f6ecc17ea4927bf16 | |
parent | 2227a0f057f47fc26716273fd678ccec599a6b7b (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/Makefile | 14 | ||||
-rw-r--r-- | libexec/ftp-proxy/ftp-proxy.8 | 154 | ||||
-rw-r--r-- | libexec/ftp-proxy/ftp-proxy.c | 1320 | ||||
-rw-r--r-- | libexec/ftp-proxy/getline.c | 279 | ||||
-rw-r--r-- | libexec/ftp-proxy/util.c | 322 | ||||
-rw-r--r-- | libexec/ftp-proxy/util.h | 68 |
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; + + |