xref: /openbsd/usr.sbin/ftp-proxy/ftp-proxy.c (revision 66ad965f)
1*66ad965fSdjm /*	$OpenBSD: ftp-proxy.c,v 1.17 2008/04/13 00:22:17 djm Exp $ */
28bfc93fdScamield 
33b53f69aScamield /*
43b53f69aScamield  * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl>
53b53f69aScamield  *
63b53f69aScamield  * Permission to use, copy, modify, and distribute this software for any
73b53f69aScamield  * purpose with or without fee is hereby granted, provided that the above
83b53f69aScamield  * copyright notice and this permission notice appear in all copies.
93b53f69aScamield  *
103b53f69aScamield  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
113b53f69aScamield  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
123b53f69aScamield  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
133b53f69aScamield  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
143b53f69aScamield  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
153b53f69aScamield  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
163b53f69aScamield  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
173b53f69aScamield  */
183b53f69aScamield 
193b53f69aScamield #include <sys/queue.h>
203b53f69aScamield #include <sys/types.h>
213b53f69aScamield #include <sys/time.h>
223b53f69aScamield #include <sys/resource.h>
233b53f69aScamield #include <sys/socket.h>
243b53f69aScamield 
253b53f69aScamield #include <net/if.h>
263b53f69aScamield #include <net/pfvar.h>
273b53f69aScamield #include <netinet/in.h>
283b53f69aScamield #include <arpa/inet.h>
293b53f69aScamield 
303b53f69aScamield #include <err.h>
313b53f69aScamield #include <errno.h>
323b53f69aScamield #include <event.h>
333b53f69aScamield #include <fcntl.h>
343b53f69aScamield #include <netdb.h>
353b53f69aScamield #include <pwd.h>
363b53f69aScamield #include <signal.h>
373b53f69aScamield #include <stdarg.h>
383b53f69aScamield #include <stdio.h>
393b53f69aScamield #include <stdlib.h>
403b53f69aScamield #include <string.h>
413b53f69aScamield #include <syslog.h>
423b53f69aScamield #include <unistd.h>
433b53f69aScamield #include <vis.h>
443b53f69aScamield 
453b53f69aScamield #include "filter.h"
463b53f69aScamield 
473b53f69aScamield #define CONNECT_TIMEOUT	30
483b53f69aScamield #define MIN_PORT	1024
493b53f69aScamield #define MAX_LINE	500
503b53f69aScamield #define MAX_LOGLINE	300
513b53f69aScamield #define NTOP_BUFS	3
523b53f69aScamield #define TCP_BACKLOG	10
533b53f69aScamield 
543b53f69aScamield #define CHROOT_DIR	"/var/empty"
553b53f69aScamield #define NOPRIV_USER	"proxy"
563b53f69aScamield 
573b53f69aScamield /* pfctl standard NAT range. */
583b53f69aScamield #define PF_NAT_PROXY_PORT_LOW	50001
593b53f69aScamield #define PF_NAT_PROXY_PORT_HIGH	65535
603b53f69aScamield 
613b53f69aScamield #define	sstosa(ss)	((struct sockaddr *)(ss))
623b53f69aScamield 
633b53f69aScamield enum { CMD_NONE = 0, CMD_PORT, CMD_EPRT, CMD_PASV, CMD_EPSV };
643b53f69aScamield 
653b53f69aScamield struct session {
663b53f69aScamield 	u_int32_t		 id;
673b53f69aScamield 	struct sockaddr_storage  client_ss;
683b53f69aScamield 	struct sockaddr_storage  proxy_ss;
693b53f69aScamield 	struct sockaddr_storage  server_ss;
703b53f69aScamield 	struct sockaddr_storage  orig_server_ss;
713b53f69aScamield 	struct bufferevent	*client_bufev;
723b53f69aScamield 	struct bufferevent	*server_bufev;
733b53f69aScamield 	int			 client_fd;
743b53f69aScamield 	int			 server_fd;
753b53f69aScamield 	char			 cbuf[MAX_LINE];
763b53f69aScamield 	size_t			 cbuf_valid;
773b53f69aScamield 	char			 sbuf[MAX_LINE];
783b53f69aScamield 	size_t			 sbuf_valid;
793b53f69aScamield 	int			 cmd;
803b53f69aScamield 	u_int16_t		 port;
813b53f69aScamield 	u_int16_t		 proxy_port;
823b53f69aScamield 	LIST_ENTRY(session)	 entry;
833b53f69aScamield };
843b53f69aScamield 
853b53f69aScamield LIST_HEAD(, session) sessions = LIST_HEAD_INITIALIZER(sessions);
863b53f69aScamield 
873b53f69aScamield void	client_error(struct bufferevent *, short, void *);
883b53f69aScamield int	client_parse(struct session *s);
893b53f69aScamield int	client_parse_anon(struct session *s);
903b53f69aScamield int	client_parse_cmd(struct session *s);
913b53f69aScamield void	client_read(struct bufferevent *, void *);
923b53f69aScamield int	drop_privs(void);
933b53f69aScamield void	end_session(struct session *);
943b53f69aScamield int	exit_daemon(void);
953b53f69aScamield int	getline(char *, size_t *);
963b53f69aScamield void	handle_connection(const int, short, void *);
974e6f7131Scamield void	handle_signal(int, short, void *);
983b53f69aScamield struct session * init_session(void);
993b53f69aScamield void	logmsg(int, const char *, ...);
1003b53f69aScamield u_int16_t parse_port(int);
1013b53f69aScamield u_int16_t pick_proxy_port(void);
1023b53f69aScamield void	proxy_reply(int, struct sockaddr *, u_int16_t);
1033b53f69aScamield void	server_error(struct bufferevent *, short, void *);
1043b53f69aScamield int	server_parse(struct session *s);
1059136fc3fScamield int	allow_data_connection(struct session *s);
1063b53f69aScamield void	server_read(struct bufferevent *, void *);
1073b53f69aScamield const char *sock_ntop(struct sockaddr *);
1083b53f69aScamield void	usage(void);
1093b53f69aScamield 
1103b53f69aScamield char linebuf[MAX_LINE + 1];
1113b53f69aScamield size_t linelen;
1123b53f69aScamield 
1133b53f69aScamield char ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN];
1143b53f69aScamield 
1153b53f69aScamield struct sockaddr_storage fixed_server_ss, fixed_proxy_ss;
1163b53f69aScamield char *fixed_server, *fixed_server_port, *fixed_proxy, *listen_ip, *listen_port,
1171a90e72aShenning     *qname, *tagname;
1180678ffdcScamield int anonymous_only, daemonize, id_count, ipv6_mode, loglevel, max_sessions,
1190678ffdcScamield     rfc_mode, session_count, timeout, verbose;
1203b53f69aScamield extern char *__progname;
1213b53f69aScamield 
1223b53f69aScamield void
1233b53f69aScamield client_error(struct bufferevent *bufev, short what, void *arg)
1243b53f69aScamield {
1253b53f69aScamield 	struct session *s = arg;
1263b53f69aScamield 
1273b53f69aScamield 	if (what & EVBUFFER_EOF)
1283b53f69aScamield 		logmsg(LOG_INFO, "#%d client close", s->id);
1293b53f69aScamield 	else if (what == (EVBUFFER_ERROR | EVBUFFER_READ))
1303b53f69aScamield 		logmsg(LOG_ERR, "#%d client reset connection", s->id);
1313b53f69aScamield 	else if (what & EVBUFFER_TIMEOUT)
1323b53f69aScamield 		logmsg(LOG_ERR, "#%d client timeout", s->id);
1333b53f69aScamield 	else if (what & EVBUFFER_WRITE)
1343b53f69aScamield 		logmsg(LOG_ERR, "#%d client write error: %d", s->id, what);
1353b53f69aScamield 	else
1363b53f69aScamield 		logmsg(LOG_ERR, "#%d abnormal client error: %d", s->id, what);
1373b53f69aScamield 
1383b53f69aScamield 	end_session(s);
1393b53f69aScamield }
1403b53f69aScamield 
1413b53f69aScamield int
1423b53f69aScamield client_parse(struct session *s)
1433b53f69aScamield {
1443b53f69aScamield 	/* Reset any previous command. */
1453b53f69aScamield 	s->cmd = CMD_NONE;
1463b53f69aScamield 	s->port = 0;
1473b53f69aScamield 
1483b53f69aScamield 	/* Commands we are looking for are at least 4 chars long. */
1493b53f69aScamield 	if (linelen < 4)
1503b53f69aScamield 		return (1);
1513b53f69aScamield 
1523b53f69aScamield 	if (linebuf[0] == 'P' || linebuf[0] == 'p' ||
1539136fc3fScamield 	    linebuf[0] == 'E' || linebuf[0] == 'e') {
1549136fc3fScamield 		if (!client_parse_cmd(s))
1559136fc3fScamield 			return (0);
1569136fc3fScamield 
1579136fc3fScamield 		/*
1589136fc3fScamield 		 * Allow active mode connections immediately, instead of
1599136fc3fScamield 		 * waiting for a positive reply from the server.  Some
1609136fc3fScamield 		 * rare servers/proxies try to probe or setup the data
1619136fc3fScamield 		 * connection before an actual transfer request.
1629136fc3fScamield 		 */
1639136fc3fScamield 		if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT)
1649136fc3fScamield 			return (allow_data_connection(s));
1659136fc3fScamield 	}
1663b53f69aScamield 
1673b53f69aScamield 	if (anonymous_only && (linebuf[0] == 'U' || linebuf[0] == 'u'))
1683b53f69aScamield 		return (client_parse_anon(s));
1693b53f69aScamield 
1703b53f69aScamield 	return (1);
1713b53f69aScamield }
1723b53f69aScamield 
1733b53f69aScamield int
1743b53f69aScamield client_parse_anon(struct session *s)
1753b53f69aScamield {
1763b53f69aScamield 	if (strcasecmp("USER ftp\r\n", linebuf) != 0 &&
1773b53f69aScamield 	    strcasecmp("USER anonymous\r\n", linebuf) != 0) {
1784f154adaSderaadt 		snprintf(linebuf, sizeof linebuf,
1793b53f69aScamield 		    "500 Only anonymous FTP allowed\r\n");
1803b53f69aScamield 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
1813b53f69aScamield 
1823b53f69aScamield 		/* Talk back to the client ourself. */
1834f154adaSderaadt 		linelen = strlen(linebuf);
1843b53f69aScamield 		bufferevent_write(s->client_bufev, linebuf, linelen);
1853b53f69aScamield 
1863b53f69aScamield 		/* Clear buffer so it's not sent to the server. */
1873b53f69aScamield 		linebuf[0] = '\0';
1883b53f69aScamield 		linelen = 0;
1893b53f69aScamield 	}
1903b53f69aScamield 
1913b53f69aScamield 	return (1);
1923b53f69aScamield }
1933b53f69aScamield 
1943b53f69aScamield int
1953b53f69aScamield client_parse_cmd(struct session *s)
1963b53f69aScamield {
1973b53f69aScamield 	if (strncasecmp("PASV", linebuf, 4) == 0)
1983b53f69aScamield 		s->cmd = CMD_PASV;
1993b53f69aScamield 	else if (strncasecmp("PORT ", linebuf, 5) == 0)
2003b53f69aScamield 		s->cmd = CMD_PORT;
2013b53f69aScamield 	else if (strncasecmp("EPSV", linebuf, 4) == 0)
2023b53f69aScamield 		s->cmd = CMD_EPSV;
2033b53f69aScamield 	else if (strncasecmp("EPRT ", linebuf, 5) == 0)
2043b53f69aScamield 		s->cmd = CMD_EPRT;
2053b53f69aScamield 	else
2063b53f69aScamield 		return (1);
2073b53f69aScamield 
2083b53f69aScamield 	if (ipv6_mode && (s->cmd == CMD_PASV || s->cmd == CMD_PORT)) {
2093b53f69aScamield 		logmsg(LOG_CRIT, "PASV and PORT not allowed with IPv6");
2103b53f69aScamield 		return (0);
2113b53f69aScamield 	}
2123b53f69aScamield 
2133b53f69aScamield 	if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) {
2143b53f69aScamield 		s->port = parse_port(s->cmd);
2153b53f69aScamield 		if (s->port < MIN_PORT) {
2163b53f69aScamield 			logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id,
2173b53f69aScamield 			    linebuf);
2183b53f69aScamield 			return (0);
2193b53f69aScamield 		}
2203b53f69aScamield 		s->proxy_port = pick_proxy_port();
2213b53f69aScamield 		proxy_reply(s->cmd, sstosa(&s->proxy_ss), s->proxy_port);
2223b53f69aScamield 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
2233b53f69aScamield 	}
2243b53f69aScamield 
2253b53f69aScamield 	return (1);
2263b53f69aScamield }
2273b53f69aScamield 
2283b53f69aScamield void
2293b53f69aScamield client_read(struct bufferevent *bufev, void *arg)
2303b53f69aScamield {
2313b53f69aScamield 	struct session	*s = arg;
2323b53f69aScamield 	size_t		 buf_avail, read;
2333b53f69aScamield 	int		 n;
2343b53f69aScamield 
2353b53f69aScamield 	do {
2363b53f69aScamield 		buf_avail = sizeof s->cbuf - s->cbuf_valid;
2373b53f69aScamield 		read = bufferevent_read(bufev, s->cbuf + s->cbuf_valid,
2383b53f69aScamield 		    buf_avail);
2393b53f69aScamield 		s->cbuf_valid += read;
2403b53f69aScamield 
2413b53f69aScamield 		while ((n = getline(s->cbuf, &s->cbuf_valid)) > 0) {
2423b53f69aScamield 			logmsg(LOG_DEBUG, "#%d client: %s", s->id, linebuf);
2433b53f69aScamield 			if (!client_parse(s)) {
2443b53f69aScamield 				end_session(s);
2453b53f69aScamield 				return;
2463b53f69aScamield 			}
2473b53f69aScamield 			bufferevent_write(s->server_bufev, linebuf, linelen);
2483b53f69aScamield 		}
2493b53f69aScamield 
2503b53f69aScamield 		if (n == -1) {
251f5c0a919Scamield 			logmsg(LOG_ERR, "#%d client command too long or not"
252f5c0a919Scamield 			    " clean", s->id);
2533b53f69aScamield 			end_session(s);
2543b53f69aScamield 			return;
2553b53f69aScamield 		}
2563b53f69aScamield 	} while (read == buf_avail);
2573b53f69aScamield }
2583b53f69aScamield 
2593b53f69aScamield int
2603b53f69aScamield drop_privs(void)
2613b53f69aScamield {
2623b53f69aScamield 	struct passwd *pw;
2633b53f69aScamield 
2643b53f69aScamield 	pw = getpwnam(NOPRIV_USER);
2653b53f69aScamield 	if (pw == NULL)
2663b53f69aScamield 		return (0);
2673b53f69aScamield 
2683b53f69aScamield 	tzset();
2693b53f69aScamield 	if (chroot(CHROOT_DIR) != 0 || chdir("/") != 0 ||
2703b53f69aScamield 	    setgroups(1, &pw->pw_gid) != 0 ||
2713b53f69aScamield 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0 ||
2723b53f69aScamield 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0)
2733b53f69aScamield 		return (0);
2743b53f69aScamield 
2753b53f69aScamield 	return (1);
2763b53f69aScamield }
2773b53f69aScamield 
2783b53f69aScamield void
2793b53f69aScamield end_session(struct session *s)
2803b53f69aScamield {
2813b53f69aScamield 	int err;
2823b53f69aScamield 
2833b53f69aScamield 	logmsg(LOG_INFO, "#%d ending session", s->id);
2843b53f69aScamield 
2853b53f69aScamield 	if (s->client_fd != -1)
2866afc2855Scamield 		close(s->client_fd);
2873b53f69aScamield 	if (s->server_fd != -1)
2886afc2855Scamield 		close(s->server_fd);
2893b53f69aScamield 
2902e6efe12Scamield 	if (s->client_bufev)
2912e6efe12Scamield 		bufferevent_free(s->client_bufev);
2922e6efe12Scamield 	if (s->server_bufev)
2932e6efe12Scamield 		bufferevent_free(s->server_bufev);
2942e6efe12Scamield 
2953b53f69aScamield 	/* Remove rulesets by commiting empty ones. */
2963b53f69aScamield 	err = 0;
2973b53f69aScamield 	if (prepare_commit(s->id) == -1)
2983b53f69aScamield 		err = errno;
2993b53f69aScamield 	else if (do_commit() == -1) {
3003b53f69aScamield 		err = errno;
3013b53f69aScamield 		do_rollback();
3023b53f69aScamield 	}
3033b53f69aScamield 	if (err)
3043b53f69aScamield 		logmsg(LOG_ERR, "#%d pf rule removal failed: %s", s->id,
3053b53f69aScamield 		    strerror(err));
3063b53f69aScamield 
3073b53f69aScamield 	LIST_REMOVE(s, entry);
3083b53f69aScamield 	free(s);
3093b53f69aScamield 	session_count--;
3103b53f69aScamield }
3113b53f69aScamield 
3123b53f69aScamield int
3133b53f69aScamield exit_daemon(void)
3143b53f69aScamield {
3153b53f69aScamield 	struct session *s, *next;
3163b53f69aScamield 
3173b53f69aScamield 	for (s = LIST_FIRST(&sessions); s != LIST_END(&sessions); s = next) {
3183b53f69aScamield 		next = LIST_NEXT(s, entry);
3193b53f69aScamield 		end_session(s);
3203b53f69aScamield 	}
3213b53f69aScamield 
3223b53f69aScamield 	if (daemonize)
3233b53f69aScamield 		closelog();
3243b53f69aScamield 
3253b53f69aScamield 	exit(0);
3263b53f69aScamield 
3273b53f69aScamield 	/* NOTREACHED */
3283b53f69aScamield 	return (-1);
3293b53f69aScamield }
3303b53f69aScamield 
3313b53f69aScamield int
3323b53f69aScamield getline(char *buf, size_t *valid)
3333b53f69aScamield {
3343b53f69aScamield 	size_t i;
3353b53f69aScamield 
3363b53f69aScamield 	if (*valid > MAX_LINE)
3373b53f69aScamield 		return (-1);
3383b53f69aScamield 
3393b53f69aScamield 	/* Copy to linebuf while searching for a newline. */
340f5c0a919Scamield 	for (i = 0; i < *valid; i++) {
341f5c0a919Scamield 		linebuf[i] = buf[i];
342f5c0a919Scamield 		if (buf[i] == '\0')
343f5c0a919Scamield 			return (-1);
344f5c0a919Scamield 		if (buf[i] == '\n')
3453b53f69aScamield 			break;
346f5c0a919Scamield 	}
3473b53f69aScamield 
3483b53f69aScamield 	if (i == *valid) {
3493b53f69aScamield 		/* No newline found. */
3503b53f69aScamield 		linebuf[0] = '\0';
3513b53f69aScamield 		linelen = 0;
3523b53f69aScamield 		if (i < MAX_LINE)
3533b53f69aScamield 			return (0);
3543b53f69aScamield 		return (-1);
3553b53f69aScamield 	}
3563b53f69aScamield 
3573b53f69aScamield 	linelen = i + 1;
3583b53f69aScamield 	linebuf[linelen] = '\0';
3593b53f69aScamield 	*valid -= linelen;
3603b53f69aScamield 
3613b53f69aScamield 	/* Move leftovers to the start. */
3623b53f69aScamield 	if (*valid != 0)
3633b53f69aScamield 		bcopy(buf + linelen, buf, *valid);
3643b53f69aScamield 
3653b53f69aScamield 	return ((int)linelen);
3663b53f69aScamield }
3673b53f69aScamield 
3683b53f69aScamield void
3693b53f69aScamield handle_connection(const int listen_fd, short event, void *ev)
3703b53f69aScamield {
3713b53f69aScamield 	struct sockaddr_storage tmp_ss;
3723b53f69aScamield 	struct sockaddr *client_sa, *server_sa, *fixed_server_sa;
3733b53f69aScamield 	struct sockaddr *client_to_proxy_sa, *proxy_to_server_sa;
3743b53f69aScamield 	struct session *s;
3753b53f69aScamield 	socklen_t len;
3763b53f69aScamield 	int client_fd, fc, on;
3773b53f69aScamield 
3783b53f69aScamield 	/*
3793b53f69aScamield 	 * We _must_ accept the connection, otherwise libevent will keep
3803b53f69aScamield 	 * coming back, and we will chew up all CPU.
3813b53f69aScamield 	 */
3823b53f69aScamield 	client_sa = sstosa(&tmp_ss);
3833b53f69aScamield 	len = sizeof(struct sockaddr_storage);
3843b53f69aScamield 	if ((client_fd = accept(listen_fd, client_sa, &len)) < 0) {
3853b53f69aScamield 		logmsg(LOG_CRIT, "accept failed: %s", strerror(errno));
3863b53f69aScamield 		return;
3873b53f69aScamield 	}
3883b53f69aScamield 
3893b53f69aScamield 	/* Refuse connection if the maximum is reached. */
3903b53f69aScamield 	if (session_count >= max_sessions) {
3913b53f69aScamield 		logmsg(LOG_ERR, "client limit (%d) reached, refusing "
3923b53f69aScamield 		    "connection from %s", max_sessions, sock_ntop(client_sa));
3933b53f69aScamield 		close(client_fd);
3943b53f69aScamield 		return;
3953b53f69aScamield 	}
3963b53f69aScamield 
3973b53f69aScamield 	/* Allocate session and copy back the info from the accept(). */
3983b53f69aScamield 	s = init_session();
3993b53f69aScamield 	if (s == NULL) {
4003b53f69aScamield 		logmsg(LOG_CRIT, "init_session failed");
4013b53f69aScamield 		close(client_fd);
4023b53f69aScamield 		return;
4033b53f69aScamield 	}
4043b53f69aScamield 	s->client_fd = client_fd;
4053b53f69aScamield 	memcpy(sstosa(&s->client_ss), client_sa, client_sa->sa_len);
4063b53f69aScamield 
4073b53f69aScamield 	/* Cast it once, and be done with it. */
4083b53f69aScamield 	client_sa = sstosa(&s->client_ss);
4093b53f69aScamield 	server_sa = sstosa(&s->server_ss);
4103b53f69aScamield 	client_to_proxy_sa = sstosa(&tmp_ss);
4113b53f69aScamield 	proxy_to_server_sa = sstosa(&s->proxy_ss);
4123b53f69aScamield 	fixed_server_sa = sstosa(&fixed_server_ss);
4133b53f69aScamield 
4143b53f69aScamield 	/* Log id/client early to ease debugging. */
4153b53f69aScamield 	logmsg(LOG_DEBUG, "#%d accepted connection from %s", s->id,
4163b53f69aScamield 	    sock_ntop(client_sa));
4173b53f69aScamield 
4183b53f69aScamield 	/*
4193b53f69aScamield 	 * Find out the real server and port that the client wanted.
4203b53f69aScamield 	 */
4213b53f69aScamield 	len = sizeof(struct sockaddr_storage);
4223b53f69aScamield 	if ((getsockname(s->client_fd, client_to_proxy_sa, &len)) < 0) {
4233b53f69aScamield 		logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id,
4243b53f69aScamield 		    strerror(errno));
4253b53f69aScamield 		goto fail;
4263b53f69aScamield 	}
4273b53f69aScamield 	if (server_lookup(client_sa, client_to_proxy_sa, server_sa) != 0) {
4283b53f69aScamield 	    	logmsg(LOG_CRIT, "#%d server lookup failed (no rdr?)", s->id);
4293b53f69aScamield 		goto fail;
4303b53f69aScamield 	}
4313b53f69aScamield 	if (fixed_server) {
4323b53f69aScamield 		memcpy(sstosa(&s->orig_server_ss), server_sa,
4333b53f69aScamield 		    server_sa->sa_len);
4343b53f69aScamield 		memcpy(server_sa, fixed_server_sa, fixed_server_sa->sa_len);
4353b53f69aScamield 	}
4363b53f69aScamield 
4373b53f69aScamield 	/* XXX: check we are not connecting to ourself. */
4383b53f69aScamield 
4393b53f69aScamield 	/*
4403b53f69aScamield 	 * Setup socket and connect to server.
4413b53f69aScamield 	 */
4423b53f69aScamield 	if ((s->server_fd = socket(server_sa->sa_family, SOCK_STREAM,
4433b53f69aScamield 	    IPPROTO_TCP)) < 0) {
4443b53f69aScamield 		logmsg(LOG_CRIT, "#%d server socket failed: %s", s->id,
4453b53f69aScamield 		    strerror(errno));
4463b53f69aScamield 		goto fail;
4473b53f69aScamield 	}
4483b53f69aScamield 	if (fixed_proxy && bind(s->server_fd, sstosa(&fixed_proxy_ss),
4493b53f69aScamield 	    fixed_proxy_ss.ss_len) != 0) {
4503b53f69aScamield 		logmsg(LOG_CRIT, "#%d cannot bind fixed proxy address: %s",
4513b53f69aScamield 		    s->id, strerror(errno));
4523b53f69aScamield 		goto fail;
4533b53f69aScamield 	}
4543b53f69aScamield 
4553b53f69aScamield 	/* Use non-blocking connect(), see CONNECT_TIMEOUT below. */
4563b53f69aScamield 	if ((fc = fcntl(s->server_fd, F_GETFL)) == -1 ||
4573b53f69aScamield 	    fcntl(s->server_fd, F_SETFL, fc | O_NONBLOCK) == -1) {
4583b53f69aScamield 		logmsg(LOG_CRIT, "#%d cannot mark socket non-blocking: %s",
4593b53f69aScamield 		    s->id, strerror(errno));
4603b53f69aScamield 		goto fail;
4613b53f69aScamield 	}
4623b53f69aScamield 	if (connect(s->server_fd, server_sa, server_sa->sa_len) < 0 &&
4633b53f69aScamield 	    errno != EINPROGRESS) {
4643b53f69aScamield 		logmsg(LOG_CRIT, "#%d proxy cannot connect to server %s: %s",
4653b53f69aScamield 		    s->id, sock_ntop(server_sa), strerror(errno));
4663b53f69aScamield 		goto fail;
4673b53f69aScamield 	}
4683b53f69aScamield 
4693b53f69aScamield 	len = sizeof(struct sockaddr_storage);
4703b53f69aScamield 	if ((getsockname(s->server_fd, proxy_to_server_sa, &len)) < 0) {
4713b53f69aScamield 		logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id,
4723b53f69aScamield 		    strerror(errno));
4733b53f69aScamield 		goto fail;
4743b53f69aScamield 	}
4753b53f69aScamield 
4763b53f69aScamield 	logmsg(LOG_INFO, "#%d FTP session %d/%d started: client %s to server "
4773b53f69aScamield 	    "%s via proxy %s ", s->id, session_count, max_sessions,
4783b53f69aScamield 	    sock_ntop(client_sa), sock_ntop(server_sa),
4793b53f69aScamield 	    sock_ntop(proxy_to_server_sa));
4803b53f69aScamield 
4813b53f69aScamield 	/* Keepalive is nice, but don't care if it fails. */
4823b53f69aScamield 	on = 1;
4833b53f69aScamield 	setsockopt(s->client_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
4843b53f69aScamield 	    sizeof on);
4853b53f69aScamield 	setsockopt(s->server_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
4863b53f69aScamield 	    sizeof on);
4873b53f69aScamield 
4883b53f69aScamield 	/*
4893b53f69aScamield 	 * Setup buffered events.
4903b53f69aScamield 	 */
4919150f310Scamield 	s->client_bufev = bufferevent_new(s->client_fd, &client_read, NULL,
4929150f310Scamield 	    &client_error, s);
4933b53f69aScamield 	if (s->client_bufev == NULL) {
4943b53f69aScamield 		logmsg(LOG_CRIT, "#%d bufferevent_new client failed", s->id);
4953b53f69aScamield 		goto fail;
4963b53f69aScamield 	}
4973b53f69aScamield 	bufferevent_settimeout(s->client_bufev, timeout, 0);
4983b53f69aScamield 	bufferevent_enable(s->client_bufev, EV_READ | EV_TIMEOUT);
4993b53f69aScamield 
5009150f310Scamield 	s->server_bufev = bufferevent_new(s->server_fd, &server_read, NULL,
5019150f310Scamield 	    &server_error, s);
5023b53f69aScamield 	if (s->server_bufev == NULL) {
5033b53f69aScamield 		logmsg(LOG_CRIT, "#%d bufferevent_new server failed", s->id);
5043b53f69aScamield 		goto fail;
5053b53f69aScamield 	}
5063b53f69aScamield 	bufferevent_settimeout(s->server_bufev, CONNECT_TIMEOUT, 0);
5073b53f69aScamield 	bufferevent_enable(s->server_bufev, EV_READ | EV_TIMEOUT);
5083b53f69aScamield 
5093b53f69aScamield 	return;
5103b53f69aScamield 
5113b53f69aScamield  fail:
5123b53f69aScamield 	end_session(s);
5133b53f69aScamield }
5143b53f69aScamield 
5154e6f7131Scamield void
5164e6f7131Scamield handle_signal(int sig, short event, void *arg)
5174e6f7131Scamield {
5184e6f7131Scamield 	/*
5194e6f7131Scamield 	 * Signal handler rules don't apply, libevent decouples for us.
5204e6f7131Scamield 	 */
5214e6f7131Scamield 
5224e6f7131Scamield 	logmsg(LOG_ERR, "%s exiting on signal %d", __progname, sig);
5234e6f7131Scamield 
5244e6f7131Scamield 	exit_daemon();
5254e6f7131Scamield }
5264e6f7131Scamield 
5274e6f7131Scamield 
5283b53f69aScamield struct session *
5293b53f69aScamield init_session(void)
5303b53f69aScamield {
5313b53f69aScamield 	struct session *s;
5323b53f69aScamield 
5333b53f69aScamield 	s = calloc(1, sizeof(struct session));
5343b53f69aScamield 	if (s == NULL)
5353b53f69aScamield 		return (NULL);
5363b53f69aScamield 
5373b53f69aScamield 	s->id = id_count++;
5383b53f69aScamield 	s->client_fd = -1;
5393b53f69aScamield 	s->server_fd = -1;
5403b53f69aScamield 	s->cbuf[0] = '\0';
5413b53f69aScamield 	s->cbuf_valid = 0;
5423b53f69aScamield 	s->sbuf[0] = '\0';
5433b53f69aScamield 	s->sbuf_valid = 0;
5443b53f69aScamield 	s->client_bufev = NULL;
5453b53f69aScamield 	s->server_bufev = NULL;
5463b53f69aScamield 	s->cmd = CMD_NONE;
5473b53f69aScamield 	s->port = 0;
5483b53f69aScamield 
5493b53f69aScamield 	LIST_INSERT_HEAD(&sessions, s, entry);
5503b53f69aScamield 	session_count++;
5513b53f69aScamield 
5523b53f69aScamield 	return (s);
5533b53f69aScamield }
5543b53f69aScamield 
5553b53f69aScamield void
5563b53f69aScamield logmsg(int pri, const char *message, ...)
5573b53f69aScamield {
5583b53f69aScamield 	va_list	ap;
5593b53f69aScamield 
5603b53f69aScamield 	if (pri > loglevel)
5613b53f69aScamield 		return;
5623b53f69aScamield 
5638582d7b5Scamield 	va_start(ap, message);
5648582d7b5Scamield 
5653b53f69aScamield 	if (daemonize)
5663b53f69aScamield 		/* syslog does its own vissing. */
5673b53f69aScamield 		vsyslog(pri, message, ap);
5683b53f69aScamield 	else {
5693b53f69aScamield 		char buf[MAX_LOGLINE];
5703b53f69aScamield 		char visbuf[2 * MAX_LOGLINE];
5713b53f69aScamield 
5723b53f69aScamield 		/* We don't care about truncation. */
5733b53f69aScamield 		vsnprintf(buf, sizeof buf, message, ap);
5743b53f69aScamield 		strnvis(visbuf, buf, sizeof visbuf, VIS_CSTYLE | VIS_NL);
5753b53f69aScamield 		fprintf(stderr, "%s\n", visbuf);
5763b53f69aScamield 	}
5773b53f69aScamield 
5783b53f69aScamield 	va_end(ap);
5793b53f69aScamield }
5803b53f69aScamield 
5813b53f69aScamield int
5823b53f69aScamield main(int argc, char *argv[])
5833b53f69aScamield {
5843b53f69aScamield 	struct rlimit rlp;
5853b53f69aScamield 	struct addrinfo hints, *res;
5864e6f7131Scamield 	struct event ev, ev_sighup, ev_sigint, ev_sigterm;
5873b53f69aScamield 	int ch, error, listenfd, on;
588f9429323Scamield 	const char *errstr;
5893b53f69aScamield 
5903b53f69aScamield 	/* Defaults. */
5913b53f69aScamield 	anonymous_only	= 0;
5923b53f69aScamield 	daemonize	= 1;
5933b53f69aScamield 	fixed_proxy	= NULL;
5943b53f69aScamield 	fixed_server	= NULL;
5953b53f69aScamield 	fixed_server_port = "21";
5963b53f69aScamield 	ipv6_mode	= 0;
5973b53f69aScamield 	listen_ip	= NULL;
5983b53f69aScamield 	listen_port	= "8021";
5993b53f69aScamield 	loglevel	= LOG_NOTICE;
6003b53f69aScamield 	max_sessions	= 100;
6013b53f69aScamield 	qname		= NULL;
6023b53f69aScamield 	rfc_mode	= 0;
6031a90e72aShenning 	tagname		= NULL;
6043b53f69aScamield 	timeout		= 24 * 3600;
605867a6359Scamield 	verbose		= 0;
6063b53f69aScamield 
6073b53f69aScamield 	/* Other initialization. */
6083b53f69aScamield 	id_count	= 1;
6093b53f69aScamield 	session_count	= 0;
6103b53f69aScamield 
6111a90e72aShenning 	while ((ch = getopt(argc, argv, "6Aa:b:D:dm:P:p:q:R:rT:t:v")) != -1) {
6123b53f69aScamield 		switch (ch) {
6133b53f69aScamield 		case '6':
6143b53f69aScamield 			ipv6_mode = 1;
6153b53f69aScamield 			break;
6163b53f69aScamield 		case 'A':
6173b53f69aScamield 			anonymous_only = 1;
6183b53f69aScamield 			break;
6193b53f69aScamield 		case 'a':
6203b53f69aScamield 			fixed_proxy = optarg;
6213b53f69aScamield 			break;
6223b53f69aScamield 		case 'b':
6233b53f69aScamield 			listen_ip = optarg;
6243b53f69aScamield 			break;
6253b53f69aScamield 		case 'D':
626f9429323Scamield 			loglevel = strtonum(optarg, LOG_EMERG, LOG_DEBUG,
627f9429323Scamield 			    &errstr);
628f9429323Scamield 			if (errstr)
629f9429323Scamield 				errx(1, "loglevel %s", errstr);
6303b53f69aScamield 			break;
6313b53f69aScamield 		case 'd':
6323b53f69aScamield 			daemonize = 0;
6333b53f69aScamield 			break;
6343b53f69aScamield 		case 'm':
635f9429323Scamield 			max_sessions = strtonum(optarg, 1, 500, &errstr);
636f9429323Scamield 			if (errstr)
637f9429323Scamield 				errx(1, "max sessions %s", errstr);
6383b53f69aScamield 			break;
6393b53f69aScamield 		case 'P':
6403b53f69aScamield 			fixed_server_port = optarg;
6413b53f69aScamield 			break;
6423b53f69aScamield 		case 'p':
6433b53f69aScamield 			listen_port = optarg;
6443b53f69aScamield 			break;
6453b53f69aScamield 		case 'q':
6463b53f69aScamield 			if (strlen(optarg) >= PF_QNAME_SIZE)
6473b53f69aScamield 				errx(1, "queuename too long");
6483b53f69aScamield 			qname = optarg;
6493b53f69aScamield 			break;
6503b53f69aScamield 		case 'R':
6513b53f69aScamield 			fixed_server = optarg;
6523b53f69aScamield 			break;
6533b53f69aScamield 		case 'r':
6543b53f69aScamield 			rfc_mode = 1;
6553b53f69aScamield 			break;
6561a90e72aShenning 		case 'T':
6571a90e72aShenning 			if (strlen(optarg) >= PF_TAG_NAME_SIZE)
6581a90e72aShenning 				errx(1, "tagname too long");
6591a90e72aShenning 			tagname = optarg;
6601a90e72aShenning 			break;
6613b53f69aScamield 		case 't':
662f9429323Scamield 			timeout = strtonum(optarg, 0, 86400, &errstr);
663f9429323Scamield 			if (errstr)
664f9429323Scamield 				errx(1, "timeout %s", errstr);
6653b53f69aScamield 			break;
666867a6359Scamield 		case 'v':
667867a6359Scamield 			verbose++;
668867a6359Scamield 			if (verbose > 2)
669867a6359Scamield 				usage();
670867a6359Scamield 			break;
6713b53f69aScamield 		default:
6723b53f69aScamield 			usage();
6733b53f69aScamield 		}
6743b53f69aScamield 	}
6753b53f69aScamield 
6763b53f69aScamield 	if (listen_ip == NULL)
6773b53f69aScamield 		listen_ip = ipv6_mode ? "::1" : "127.0.0.1";
6783b53f69aScamield 
6793b53f69aScamield 	/* Check for root to save the user from cryptic failure messages. */
6803b53f69aScamield 	if (getuid() != 0)
6813b53f69aScamield 		errx(1, "needs to start as root");
6823b53f69aScamield 
6833b53f69aScamield 	/* Raise max. open files limit to satisfy max. sessions. */
6843b53f69aScamield 	rlp.rlim_cur = rlp.rlim_max = (2 * max_sessions) + 10;
6853b53f69aScamield 	if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
6863b53f69aScamield 		err(1, "setrlimit");
6873b53f69aScamield 
6883b53f69aScamield 	if (fixed_proxy) {
6893b53f69aScamield 		memset(&hints, 0, sizeof hints);
6903b53f69aScamield 		hints.ai_flags = AI_NUMERICHOST;
6913b53f69aScamield 		hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
6923b53f69aScamield 		hints.ai_socktype = SOCK_STREAM;
6933b53f69aScamield 		error = getaddrinfo(fixed_proxy, NULL, &hints, &res);
6943b53f69aScamield 		if (error)
6953b53f69aScamield 			errx(1, "getaddrinfo fixed proxy address failed: %s",
6963b53f69aScamield 			    gai_strerror(error));
6973b53f69aScamield 		memcpy(&fixed_proxy_ss, res->ai_addr, res->ai_addrlen);
6983b53f69aScamield 		logmsg(LOG_INFO, "using %s to connect to servers",
6993b53f69aScamield 		    sock_ntop(sstosa(&fixed_proxy_ss)));
7003b53f69aScamield 		freeaddrinfo(res);
7013b53f69aScamield 	}
7023b53f69aScamield 
7033b53f69aScamield 	if (fixed_server) {
7043b53f69aScamield 		memset(&hints, 0, sizeof hints);
7053b53f69aScamield 		hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
7063b53f69aScamield 		hints.ai_socktype = SOCK_STREAM;
7073b53f69aScamield 		error = getaddrinfo(fixed_server, fixed_server_port, &hints,
7083b53f69aScamield 		    &res);
7093b53f69aScamield 		if (error)
7103b53f69aScamield 			errx(1, "getaddrinfo fixed server address failed: %s",
7113b53f69aScamield 			    gai_strerror(error));
7123b53f69aScamield 		memcpy(&fixed_server_ss, res->ai_addr, res->ai_addrlen);
7133b53f69aScamield 		logmsg(LOG_INFO, "using fixed server %s",
7143b53f69aScamield 		    sock_ntop(sstosa(&fixed_server_ss)));
7153b53f69aScamield 		freeaddrinfo(res);
7163b53f69aScamield 	}
7173b53f69aScamield 
7183b53f69aScamield 	/* Setup listener. */
7193b53f69aScamield 	memset(&hints, 0, sizeof hints);
7203b53f69aScamield 	hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
7213b53f69aScamield 	hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
7223b53f69aScamield 	hints.ai_socktype = SOCK_STREAM;
7233b53f69aScamield 	error = getaddrinfo(listen_ip, listen_port, &hints, &res);
7243b53f69aScamield 	if (error)
7253b53f69aScamield 		errx(1, "getaddrinfo listen address failed: %s",
7263b53f69aScamield 		    gai_strerror(error));
7273b53f69aScamield 	if ((listenfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
7283b53f69aScamield 		errx(1, "socket failed");
7293b53f69aScamield 	on = 1;
7303b53f69aScamield 	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on,
7313b53f69aScamield 	    sizeof on) != 0)
7323b53f69aScamield 		err(1, "setsockopt failed");
7333b53f69aScamield 	if (bind(listenfd, (struct sockaddr *)res->ai_addr,
7343b53f69aScamield 	    (socklen_t)res->ai_addrlen) != 0)
7353b53f69aScamield 	    	err(1, "bind failed");
7363b53f69aScamield 	if (listen(listenfd, TCP_BACKLOG) != 0)
7373b53f69aScamield 		err(1, "listen failed");
7383b53f69aScamield 	freeaddrinfo(res);
7393b53f69aScamield 
7403b53f69aScamield 	/* Initialize pf. */
7411a90e72aShenning 	init_filter(qname, tagname, verbose);
7423b53f69aScamield 
7433b53f69aScamield 	if (daemonize) {
7443b53f69aScamield 		if (daemon(0, 0) == -1)
7453b53f69aScamield 			err(1, "cannot daemonize");
7463b53f69aScamield 		openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
7473b53f69aScamield 	}
7483b53f69aScamield 
7493b53f69aScamield 	/* Use logmsg for output from here on. */
7503b53f69aScamield 
7513b53f69aScamield 	if (!drop_privs()) {
7523b53f69aScamield 		logmsg(LOG_ERR, "cannot drop privileges: %s", strerror(errno));
7533b53f69aScamield 		exit(1);
7543b53f69aScamield 	}
7553b53f69aScamield 
7563b53f69aScamield 	event_init();
7574e6f7131Scamield 
7584e6f7131Scamield 	/* Setup signal handler. */
7596ba63af2Scamield 	signal(SIGPIPE, SIG_IGN);
7604e6f7131Scamield 	signal_set(&ev_sighup, SIGHUP, handle_signal, NULL);
7614e6f7131Scamield 	signal_set(&ev_sigint, SIGINT, handle_signal, NULL);
7624e6f7131Scamield 	signal_set(&ev_sigterm, SIGTERM, handle_signal, NULL);
7634e6f7131Scamield 	signal_add(&ev_sighup, NULL);
7644e6f7131Scamield 	signal_add(&ev_sigint, NULL);
7654e6f7131Scamield 	signal_add(&ev_sigterm, NULL);
7664e6f7131Scamield 
7673b53f69aScamield 	event_set(&ev, listenfd, EV_READ | EV_PERSIST, handle_connection, &ev);
7683b53f69aScamield 	event_add(&ev, NULL);
7693b53f69aScamield 
7703b53f69aScamield 	logmsg(LOG_NOTICE, "listening on %s port %s", listen_ip, listen_port);
7713b53f69aScamield 
7723b53f69aScamield 	/*  Vroom, vroom.  */
7733b53f69aScamield 	event_dispatch();
7743b53f69aScamield 
7753b53f69aScamield 	logmsg(LOG_ERR, "event_dispatch error: %s", strerror(errno));
7763b53f69aScamield 	exit_daemon();
7773b53f69aScamield 
7783b53f69aScamield 	/* NOTREACHED */
7793b53f69aScamield 	return (1);
7803b53f69aScamield }
7813b53f69aScamield 
7823b53f69aScamield u_int16_t
7833b53f69aScamield parse_port(int mode)
7843b53f69aScamield {
7853b53f69aScamield 	unsigned int	 port, v[6];
7863b53f69aScamield 	int		 n;
7873b53f69aScamield 	char		*p;
7883b53f69aScamield 
7893b53f69aScamield 	/* Find the last space or left-parenthesis. */
7903b53f69aScamield 	for (p = linebuf + linelen; p > linebuf; p--)
7913b53f69aScamield 		if (*p == ' ' || *p == '(')
7923b53f69aScamield 			break;
7933b53f69aScamield 	if (p == linebuf)
7943b53f69aScamield 		return (0);
7953b53f69aScamield 
7963b53f69aScamield 	switch (mode) {
7973b53f69aScamield 	case CMD_PORT:
7983b53f69aScamield 		n = sscanf(p, " %u,%u,%u,%u,%u,%u", &v[0], &v[1], &v[2],
7993b53f69aScamield 		    &v[3], &v[4], &v[5]);
8003b53f69aScamield 		if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8013b53f69aScamield 		    v[3] < 256 && v[4] < 256 && v[5] < 256)
8023b53f69aScamield 			return ((v[4] << 8) | v[5]);
8033b53f69aScamield 		break;
8043b53f69aScamield 	case CMD_PASV:
8053b53f69aScamield 		n = sscanf(p, "(%u,%u,%u,%u,%u,%u)", &v[0], &v[1], &v[2],
8063b53f69aScamield 		    &v[3], &v[4], &v[5]);
8073b53f69aScamield 		if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8083b53f69aScamield 		    v[3] < 256 && v[4] < 256 && v[5] < 256)
8093b53f69aScamield 			return ((v[4] << 8) | v[5]);
8103b53f69aScamield 		break;
8113b53f69aScamield 	case CMD_EPSV:
8123b53f69aScamield 		n = sscanf(p, "(|||%u|)", &port);
8133b53f69aScamield 		if (n == 1 && port < 65536)
8143b53f69aScamield 			return (port);
8153b53f69aScamield 		break;
8163b53f69aScamield 	case CMD_EPRT:
8173b53f69aScamield 		n = sscanf(p, " |1|%u.%u.%u.%u|%u|", &v[0], &v[1], &v[2],
8183b53f69aScamield 		    &v[3], &port);
8193b53f69aScamield 		if (n == 5 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8203b53f69aScamield 		    v[3] < 256 && port < 65536)
8213b53f69aScamield 			return (port);
8223b53f69aScamield 		n = sscanf(p, " |2|%*[a-fA-F0-9:]|%u|", &port);
8233b53f69aScamield 		if (n == 1 && port < 65536)
8243b53f69aScamield 			return (port);
8253b53f69aScamield 		break;
8263b53f69aScamield 	default:
8273b53f69aScamield 		return (0);
8283b53f69aScamield 	}
8293b53f69aScamield 
8303b53f69aScamield 	return (0);
8313b53f69aScamield }
8323b53f69aScamield 
8333b53f69aScamield u_int16_t
8343b53f69aScamield pick_proxy_port(void)
8353b53f69aScamield {
8363b53f69aScamield 	/* Random should be good enough for avoiding port collisions. */
837*66ad965fSdjm 	return (IPPORT_HIFIRSTAUTO +
838*66ad965fSdjm 	    arc4random_uniform(IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO));
8393b53f69aScamield }
8403b53f69aScamield 
8413b53f69aScamield void
8423b53f69aScamield proxy_reply(int cmd, struct sockaddr *sa, u_int16_t port)
8433b53f69aScamield {
8443b53f69aScamield 	int i, r;
8453b53f69aScamield 
8463b53f69aScamield 	switch (cmd) {
8473b53f69aScamield 	case CMD_PORT:
8483b53f69aScamield 		r = snprintf(linebuf, sizeof linebuf,
8493b53f69aScamield 		    "PORT %s,%u,%u\r\n", sock_ntop(sa), port / 256,
8503b53f69aScamield 		    port % 256);
8513b53f69aScamield 		break;
8523b53f69aScamield 	case CMD_PASV:
8533b53f69aScamield 		r = snprintf(linebuf, sizeof linebuf,
8543b53f69aScamield 		    "227 Entering Passive Mode (%s,%u,%u)\r\n", sock_ntop(sa),
8553b53f69aScamield 		        port / 256, port % 256);
8563b53f69aScamield 		break;
8573b53f69aScamield 	case CMD_EPRT:
8583b53f69aScamield 		if (sa->sa_family == AF_INET)
8593b53f69aScamield 			r = snprintf(linebuf, sizeof linebuf,
8603b53f69aScamield 			    "EPRT |1|%s|%u|\r\n", sock_ntop(sa), port);
8613b53f69aScamield 		else if (sa->sa_family == AF_INET6)
8623b53f69aScamield 			r = snprintf(linebuf, sizeof linebuf,
8633b53f69aScamield 			    "EPRT |2|%s|%u|\r\n", sock_ntop(sa), port);
8643b53f69aScamield 		break;
8653b53f69aScamield 	case CMD_EPSV:
8663b53f69aScamield 		r = snprintf(linebuf, sizeof linebuf,
8673b53f69aScamield 		    "229 Entering Extended Passive Mode (|||%u|)\r\n", port);
8683b53f69aScamield 		break;
8693b53f69aScamield 	}
8703b53f69aScamield 
8713b53f69aScamield 	if (r < 0 || r >= sizeof linebuf) {
8723b53f69aScamield 		logmsg(LOG_ERR, "proxy_reply failed: %d", r);
8733b53f69aScamield 		linebuf[0] = '\0';
8743b53f69aScamield 		linelen = 0;
8753b53f69aScamield 		return;
8763b53f69aScamield 	}
8773b53f69aScamield 	linelen = (size_t)r;
8783b53f69aScamield 
8793b53f69aScamield 	if (cmd == CMD_PORT || cmd == CMD_PASV) {
8803b53f69aScamield 		/* Replace dots in IP address with commas. */
8813b53f69aScamield 		for (i = 0; i < linelen; i++)
8823b53f69aScamield 			if (linebuf[i] == '.')
8833b53f69aScamield 				linebuf[i] = ',';
8843b53f69aScamield 	}
8853b53f69aScamield }
8863b53f69aScamield 
8873b53f69aScamield void
8883b53f69aScamield server_error(struct bufferevent *bufev, short what, void *arg)
8893b53f69aScamield {
8903b53f69aScamield 	struct session *s = arg;
8913b53f69aScamield 
8923b53f69aScamield 	if (what & EVBUFFER_EOF)
8933b53f69aScamield 		logmsg(LOG_INFO, "#%d server close", s->id);
8943b53f69aScamield 	else if (what == (EVBUFFER_ERROR | EVBUFFER_READ))
8953b53f69aScamield 		logmsg(LOG_ERR, "#%d server refused connection", s->id);
8963b53f69aScamield 	else if (what & EVBUFFER_WRITE)
8973b53f69aScamield 		logmsg(LOG_ERR, "#%d server write error: %d", s->id, what);
8983b53f69aScamield 	else if (what & EVBUFFER_TIMEOUT)
8993b53f69aScamield 		logmsg(LOG_NOTICE, "#%d server timeout", s->id);
9003b53f69aScamield 	else
9013b53f69aScamield 		logmsg(LOG_ERR, "#%d abnormal server error: %d", s->id, what);
9023b53f69aScamield 
9033b53f69aScamield 	end_session(s);
9043b53f69aScamield }
9053b53f69aScamield 
9063b53f69aScamield int
9073b53f69aScamield server_parse(struct session *s)
9083b53f69aScamield {
9093b53f69aScamield 	if (s->cmd == CMD_NONE || linelen < 4 || linebuf[0] != '2')
9103b53f69aScamield 		goto out;
9113b53f69aScamield 
9129136fc3fScamield 	if ((s->cmd == CMD_PASV && strncmp("227 ", linebuf, 4) == 0) ||
9139136fc3fScamield 	    (s->cmd == CMD_EPSV && strncmp("229 ", linebuf, 4) == 0))
9149136fc3fScamield 		return (allow_data_connection(s));
9159136fc3fScamield 
9169136fc3fScamield  out:
9179136fc3fScamield 	s->cmd = CMD_NONE;
9189136fc3fScamield 	s->port = 0;
9199136fc3fScamield 
9209136fc3fScamield 	return (1);
9219136fc3fScamield }
9229136fc3fScamield 
9239136fc3fScamield int
9249136fc3fScamield allow_data_connection(struct session *s)
9259136fc3fScamield {
9269136fc3fScamield 	struct sockaddr *client_sa, *orig_sa, *proxy_sa, *server_sa;
9279136fc3fScamield 	int prepared = 0;
9289136fc3fScamield 
9293b53f69aScamield 	/*
9303b53f69aScamield 	 * The pf rules below do quite some NAT rewriting, to keep up
9313b53f69aScamield 	 * appearances.  Points to keep in mind:
9323b53f69aScamield 	 * 1)  The client must think it's talking to the real server,
9333b53f69aScamield 	 *     for both control and data connections.  Transparently.
9343b53f69aScamield 	 * 2)  The server must think that the proxy is the client.
9353b53f69aScamield 	 * 3)  Source and destination ports are rewritten to minimize
9363b53f69aScamield 	 *     port collisions, to aid security (some systems pick weak
9373b53f69aScamield 	 *     ports) or to satisfy RFC requirements (source port 20).
9383b53f69aScamield 	 */
9393b53f69aScamield 
9403b53f69aScamield 	/* Cast this once, to make code below it more readable. */
9413b53f69aScamield 	client_sa = sstosa(&s->client_ss);
9423b53f69aScamield 	server_sa = sstosa(&s->server_ss);
9433b53f69aScamield 	proxy_sa = sstosa(&s->proxy_ss);
9443b53f69aScamield 	if (fixed_server)
9453b53f69aScamield 		/* Fixed server: data connections must appear to come
9463b53f69aScamield 		   from / go to the original server, not the fixed one. */
9473b53f69aScamield 		orig_sa = sstosa(&s->orig_server_ss);
9483b53f69aScamield 	else
9493b53f69aScamield 		/* Server not fixed: orig_server == server. */
9503b53f69aScamield 		orig_sa = sstosa(&s->server_ss);
9513b53f69aScamield 
9523b53f69aScamield 	/* Passive modes. */
9539136fc3fScamield 	if (s->cmd == CMD_PASV || s->cmd == CMD_EPSV) {
9543b53f69aScamield 		s->port = parse_port(s->cmd);
9553b53f69aScamield 		if (s->port < MIN_PORT) {
9563b53f69aScamield 			logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id,
9573b53f69aScamield 			    linebuf);
9583b53f69aScamield 			return (0);
9593b53f69aScamield 		}
9603b53f69aScamield 		s->proxy_port = pick_proxy_port();
9613b53f69aScamield 		logmsg(LOG_INFO, "#%d passive: client to server port %d"
9623b53f69aScamield 		    " via port %d", s->id, s->port, s->proxy_port);
9633b53f69aScamield 
9643b53f69aScamield 		if (prepare_commit(s->id) == -1)
9653b53f69aScamield 			goto fail;
9663b53f69aScamield 		prepared = 1;
9673b53f69aScamield 
9683b53f69aScamield 		proxy_reply(s->cmd, orig_sa, s->proxy_port);
9693b53f69aScamield 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
9703b53f69aScamield 
9713b53f69aScamield 		/* rdr from $client to $orig_server port $proxy_port -> $server
9723b53f69aScamield 		    port $port */
9733b53f69aScamield 		if (add_rdr(s->id, client_sa, orig_sa, s->proxy_port,
9743b53f69aScamield 		    server_sa, s->port) == -1)
9753b53f69aScamield 			goto fail;
9763b53f69aScamield 
9773b53f69aScamield 		/* nat from $client to $server port $port -> $proxy */
9783b53f69aScamield 		if (add_nat(s->id, client_sa, server_sa, s->port, proxy_sa,
9793b53f69aScamield 		    PF_NAT_PROXY_PORT_LOW, PF_NAT_PROXY_PORT_HIGH) == -1)
9803b53f69aScamield 			goto fail;
9813b53f69aScamield 
9823b53f69aScamield 		/* pass in from $client to $server port $port */
9833b53f69aScamield 		if (add_filter(s->id, PF_IN, client_sa, server_sa,
9843b53f69aScamield 		    s->port) == -1)
9853b53f69aScamield 			goto fail;
9863b53f69aScamield 
9873b53f69aScamield 		/* pass out from $proxy to $server port $port */
9883b53f69aScamield 		if (add_filter(s->id, PF_OUT, proxy_sa, server_sa,
9893b53f69aScamield 		    s->port) == -1)
9903b53f69aScamield 			goto fail;
9913b53f69aScamield 	}
9923b53f69aScamield 
9933b53f69aScamield 	/* Active modes. */
9949136fc3fScamield 	if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) {
9953b53f69aScamield 		logmsg(LOG_INFO, "#%d active: server to client port %d"
9963b53f69aScamield 		    " via port %d", s->id, s->port, s->proxy_port);
9973b53f69aScamield 
9983b53f69aScamield 		if (prepare_commit(s->id) == -1)
9993b53f69aScamield 			goto fail;
10003b53f69aScamield 		prepared = 1;
10013b53f69aScamield 
10023b53f69aScamield 		/* rdr from $server to $proxy port $proxy_port -> $client port
10033b53f69aScamield 		    $port */
10043b53f69aScamield 		if (add_rdr(s->id, server_sa, proxy_sa, s->proxy_port,
10053b53f69aScamield 		    client_sa, s->port) == -1)
10063b53f69aScamield 			goto fail;
10073b53f69aScamield 
10083b53f69aScamield 		/* nat from $server to $client port $port -> $orig_server port
10093b53f69aScamield 		    $natport */
10103b53f69aScamield 		if (rfc_mode && s->cmd == CMD_PORT) {
10113b53f69aScamield 			/* Rewrite sourceport to RFC mandated 20. */
10123b53f69aScamield 			if (add_nat(s->id, server_sa, client_sa, s->port,
10133b53f69aScamield 			    orig_sa, 20, 20) == -1)
10143b53f69aScamield 				goto fail;
10153b53f69aScamield 		} else {
10163b53f69aScamield 			/* Let pf pick a source port from the standard range. */
10173b53f69aScamield 			if (add_nat(s->id, server_sa, client_sa, s->port,
10183b53f69aScamield 			    orig_sa, PF_NAT_PROXY_PORT_LOW,
10193b53f69aScamield 			    PF_NAT_PROXY_PORT_HIGH) == -1)
10203b53f69aScamield 			    	goto fail;
10213b53f69aScamield 		}
10223b53f69aScamield 
10233b53f69aScamield 		/* pass in from $server to $client port $port */
10243b53f69aScamield 		if (add_filter(s->id, PF_IN, server_sa, client_sa, s->port) ==
10253b53f69aScamield 		    -1)
10263b53f69aScamield 			goto fail;
10273b53f69aScamield 
10283b53f69aScamield 		/* pass out from $orig_server to $client port $port */
10293b53f69aScamield 		if (add_filter(s->id, PF_OUT, orig_sa, client_sa, s->port) ==
10303b53f69aScamield 		    -1)
10313b53f69aScamield 			goto fail;
10323b53f69aScamield 	}
10333b53f69aScamield 
10343b53f69aScamield 	/* Commit rules if they were prepared. */
10353b53f69aScamield 	if (prepared && (do_commit() == -1)) {
10363b53f69aScamield 		if (errno != EBUSY)
10373b53f69aScamield 			goto fail;
10383b53f69aScamield 		/* One more try if busy. */
10393b53f69aScamield 		usleep(5000);
10403b53f69aScamield 		if (do_commit() == -1)
10413b53f69aScamield 			goto fail;
10423b53f69aScamield 	}
10433b53f69aScamield 
10443b53f69aScamield 	s->cmd = CMD_NONE;
10453b53f69aScamield 	s->port = 0;
10463b53f69aScamield 
10473b53f69aScamield 	return (1);
10483b53f69aScamield 
10493b53f69aScamield  fail:
10503b53f69aScamield 	logmsg(LOG_CRIT, "#%d pf operation failed: %s", s->id, strerror(errno));
10513b53f69aScamield 	if (prepared)
10523b53f69aScamield 		do_rollback();
10533b53f69aScamield 	return (0);
10543b53f69aScamield }
10553b53f69aScamield 
10563b53f69aScamield void
10573b53f69aScamield server_read(struct bufferevent *bufev, void *arg)
10583b53f69aScamield {
10593b53f69aScamield 	struct session	*s = arg;
10603b53f69aScamield 	size_t		 buf_avail, read;
10613b53f69aScamield 	int		 n;
10623b53f69aScamield 
10633b53f69aScamield 	bufferevent_settimeout(bufev, timeout, 0);
10643b53f69aScamield 
10653b53f69aScamield 	do {
10663b53f69aScamield 		buf_avail = sizeof s->sbuf - s->sbuf_valid;
10673b53f69aScamield 		read = bufferevent_read(bufev, s->sbuf + s->sbuf_valid,
10683b53f69aScamield 		    buf_avail);
10693b53f69aScamield 		s->sbuf_valid += read;
10703b53f69aScamield 
10713b53f69aScamield 		while ((n = getline(s->sbuf, &s->sbuf_valid)) > 0) {
10723b53f69aScamield 			logmsg(LOG_DEBUG, "#%d server: %s", s->id, linebuf);
10733b53f69aScamield 			if (!server_parse(s)) {
10743b53f69aScamield 				end_session(s);
10753b53f69aScamield 				return;
10763b53f69aScamield 			}
10773b53f69aScamield 			bufferevent_write(s->client_bufev, linebuf, linelen);
10783b53f69aScamield 		}
10793b53f69aScamield 
10803b53f69aScamield 		if (n == -1) {
1081f5c0a919Scamield 			logmsg(LOG_ERR, "#%d server reply too long or not"
1082f5c0a919Scamield 			    " clean", s->id);
10833b53f69aScamield 			end_session(s);
10843b53f69aScamield 			return;
10853b53f69aScamield 		}
10863b53f69aScamield 	} while (read == buf_avail);
10873b53f69aScamield }
10883b53f69aScamield 
10893b53f69aScamield const char *
10903b53f69aScamield sock_ntop(struct sockaddr *sa)
10913b53f69aScamield {
10923b53f69aScamield 	static int n = 0;
10933b53f69aScamield 
10943b53f69aScamield 	/* Cycle to next buffer. */
10953b53f69aScamield 	n = (n + 1) % NTOP_BUFS;
10963b53f69aScamield 	ntop_buf[n][0] = '\0';
10973b53f69aScamield 
10983b53f69aScamield 	if (sa->sa_family == AF_INET) {
10993b53f69aScamield 		struct sockaddr_in *sin = (struct sockaddr_in *)sa;
11003b53f69aScamield 
11013b53f69aScamield 		return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n],
11023b53f69aScamield 		    sizeof ntop_buf[0]));
11033b53f69aScamield 	}
11043b53f69aScamield 
11053b53f69aScamield 	if (sa->sa_family == AF_INET6) {
11063b53f69aScamield 		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
11073b53f69aScamield 
11083b53f69aScamield 		return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n],
11093b53f69aScamield 		    sizeof ntop_buf[0]));
11103b53f69aScamield 	}
11113b53f69aScamield 
11123b53f69aScamield 	return (NULL);
11133b53f69aScamield }
11143b53f69aScamield 
11153b53f69aScamield void
11163b53f69aScamield usage(void)
11173b53f69aScamield {
1118867a6359Scamield 	fprintf(stderr, "usage: %s [-6Adrv] [-a address] [-b address]"
11193b53f69aScamield 	    " [-D level] [-m maxsessions]\n                 [-P port]"
1120f9b24bdcShenning 	    " [-p port] [-q queue] [-R address] [-T tag]\n"
1121f9b24bdcShenning             "                 [-t timeout]\n", __progname);
11223b53f69aScamield 	exit(1);
11233b53f69aScamield }
1124