xref: /openbsd/usr.sbin/ftp-proxy/ftp-proxy.c (revision b7041c07)
1*b7041c07Sderaadt /*	$OpenBSD: ftp-proxy.c,v 1.39 2021/10/24 21:24:18 deraadt 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 <netinet/in.h>
263b53f69aScamield #include <arpa/inet.h>
2768928c43Sderaadt #include <net/if.h>
2868928c43Sderaadt #include <net/pfvar.h>
293b53f69aScamield 
303b53f69aScamield #include <err.h>
313b53f69aScamield #include <errno.h>
323b53f69aScamield #include <event.h>
333b53f69aScamield #include <fcntl.h>
343b53f69aScamield #include <netdb.h>
357307f9faSjca #include <paths.h>
363b53f69aScamield #include <pwd.h>
373b53f69aScamield #include <signal.h>
383b53f69aScamield #include <stdarg.h>
393b53f69aScamield #include <stdio.h>
403b53f69aScamield #include <stdlib.h>
413b53f69aScamield #include <string.h>
423b53f69aScamield #include <syslog.h>
433b53f69aScamield #include <unistd.h>
443b53f69aScamield #include <vis.h>
453b53f69aScamield 
463b53f69aScamield #include "filter.h"
473b53f69aScamield 
483b53f69aScamield #define CONNECT_TIMEOUT	30
493b53f69aScamield #define MIN_PORT	1024
503b53f69aScamield #define MAX_LINE	500
513b53f69aScamield #define MAX_LOGLINE	300
523b53f69aScamield #define NTOP_BUFS	3
533b53f69aScamield #define TCP_BACKLOG	10
543b53f69aScamield 
553b53f69aScamield #define CHROOT_DIR	"/var/empty"
56438e85a9Ssebastia #define NOPRIV_USER	"_ftp_proxy"
573b53f69aScamield 
583b53f69aScamield /* pfctl standard NAT range. */
593b53f69aScamield #define PF_NAT_PROXY_PORT_LOW	50001
603b53f69aScamield #define PF_NAT_PROXY_PORT_HIGH	65535
613b53f69aScamield 
623b53f69aScamield #define	sstosa(ss)	((struct sockaddr *)(ss))
633b53f69aScamield 
643b53f69aScamield enum { CMD_NONE = 0, CMD_PORT, CMD_EPRT, CMD_PASV, CMD_EPSV };
653b53f69aScamield 
663b53f69aScamield struct session {
673b53f69aScamield 	u_int32_t		 id;
683b53f69aScamield 	struct sockaddr_storage  client_ss;
693b53f69aScamield 	struct sockaddr_storage  proxy_ss;
703b53f69aScamield 	struct sockaddr_storage  server_ss;
713b53f69aScamield 	struct sockaddr_storage  orig_server_ss;
723b53f69aScamield 	struct bufferevent	*client_bufev;
733b53f69aScamield 	struct bufferevent	*server_bufev;
743b53f69aScamield 	int			 client_fd;
753b53f69aScamield 	int			 server_fd;
763b53f69aScamield 	char			 cbuf[MAX_LINE];
773b53f69aScamield 	size_t			 cbuf_valid;
783b53f69aScamield 	char			 sbuf[MAX_LINE];
793b53f69aScamield 	size_t			 sbuf_valid;
803b53f69aScamield 	int			 cmd;
8185ec68abSclaudio 	int			 client_rd;
823b53f69aScamield 	u_int16_t		 port;
833b53f69aScamield 	u_int16_t		 proxy_port;
843b53f69aScamield 	LIST_ENTRY(session)	 entry;
853b53f69aScamield };
863b53f69aScamield 
873b53f69aScamield LIST_HEAD(, session) sessions = LIST_HEAD_INITIALIZER(sessions);
883b53f69aScamield 
893b53f69aScamield void	client_error(struct bufferevent *, short, void *);
903b53f69aScamield int	client_parse(struct session *s);
913b53f69aScamield int	client_parse_anon(struct session *s);
923b53f69aScamield int	client_parse_cmd(struct session *s);
933b53f69aScamield void	client_read(struct bufferevent *, void *);
943b53f69aScamield int	drop_privs(void);
953b53f69aScamield void	end_session(struct session *);
968562b6e2Sclaudio void	exit_daemon(void);
97f9bbbf45Sfgsch int	get_line(char *, size_t *);
983b53f69aScamield void	handle_connection(const int, short, void *);
994e6f7131Scamield void	handle_signal(int, short, void *);
1003b53f69aScamield struct session * init_session(void);
1013b53f69aScamield void	logmsg(int, const char *, ...);
1023b53f69aScamield u_int16_t parse_port(int);
1033b53f69aScamield u_int16_t pick_proxy_port(void);
1043b53f69aScamield void	proxy_reply(int, struct sockaddr *, u_int16_t);
1057307f9faSjca int	rdaemon(int);
1063b53f69aScamield void	server_error(struct bufferevent *, short, void *);
1073b53f69aScamield int	server_parse(struct session *s);
1089136fc3fScamield int	allow_data_connection(struct session *s);
1093b53f69aScamield void	server_read(struct bufferevent *, void *);
1103b53f69aScamield const char *sock_ntop(struct sockaddr *);
1113b53f69aScamield void	usage(void);
1123b53f69aScamield 
1133b53f69aScamield char linebuf[MAX_LINE + 1];
1143b53f69aScamield size_t linelen;
1153b53f69aScamield 
1163b53f69aScamield char ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN];
1173b53f69aScamield 
118bc3a204fScamield struct event listen_ev, pause_accept_ev;
1193b53f69aScamield struct sockaddr_storage fixed_server_ss, fixed_proxy_ss;
1203b53f69aScamield char *fixed_server, *fixed_server_port, *fixed_proxy, *listen_ip, *listen_port,
1211a90e72aShenning     *qname, *tagname;
1220678ffdcScamield int anonymous_only, daemonize, id_count, ipv6_mode, loglevel, max_sessions,
1230678ffdcScamield     rfc_mode, session_count, timeout, verbose;
1243b53f69aScamield extern char *__progname;
1253b53f69aScamield 
1263b53f69aScamield void
client_error(struct bufferevent * bufev,short what,void * arg)1273b53f69aScamield client_error(struct bufferevent *bufev, short what, void *arg)
1283b53f69aScamield {
1293b53f69aScamield 	struct session *s = arg;
1303b53f69aScamield 
1313b53f69aScamield 	if (what & EVBUFFER_EOF)
1323b53f69aScamield 		logmsg(LOG_INFO, "#%d client close", s->id);
1333b53f69aScamield 	else if (what == (EVBUFFER_ERROR | EVBUFFER_READ))
1343b53f69aScamield 		logmsg(LOG_ERR, "#%d client reset connection", s->id);
1353b53f69aScamield 	else if (what & EVBUFFER_TIMEOUT)
1363b53f69aScamield 		logmsg(LOG_ERR, "#%d client timeout", s->id);
1373b53f69aScamield 	else if (what & EVBUFFER_WRITE)
1383b53f69aScamield 		logmsg(LOG_ERR, "#%d client write error: %d", s->id, what);
1393b53f69aScamield 	else
1403b53f69aScamield 		logmsg(LOG_ERR, "#%d abnormal client error: %d", s->id, what);
1413b53f69aScamield 
1423b53f69aScamield 	end_session(s);
1433b53f69aScamield }
1443b53f69aScamield 
1453b53f69aScamield int
client_parse(struct session * s)1463b53f69aScamield client_parse(struct session *s)
1473b53f69aScamield {
1483b53f69aScamield 	/* Reset any previous command. */
1493b53f69aScamield 	s->cmd = CMD_NONE;
1503b53f69aScamield 	s->port = 0;
1513b53f69aScamield 
1523b53f69aScamield 	/* Commands we are looking for are at least 4 chars long. */
1533b53f69aScamield 	if (linelen < 4)
1543b53f69aScamield 		return (1);
1553b53f69aScamield 
1563b53f69aScamield 	if (linebuf[0] == 'P' || linebuf[0] == 'p' ||
1579136fc3fScamield 	    linebuf[0] == 'E' || linebuf[0] == 'e') {
1589136fc3fScamield 		if (!client_parse_cmd(s))
1599136fc3fScamield 			return (0);
1609136fc3fScamield 
1619136fc3fScamield 		/*
1629136fc3fScamield 		 * Allow active mode connections immediately, instead of
1639136fc3fScamield 		 * waiting for a positive reply from the server.  Some
1649136fc3fScamield 		 * rare servers/proxies try to probe or setup the data
1659136fc3fScamield 		 * connection before an actual transfer request.
1669136fc3fScamield 		 */
1679136fc3fScamield 		if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT)
1689136fc3fScamield 			return (allow_data_connection(s));
1699136fc3fScamield 	}
1703b53f69aScamield 
1713b53f69aScamield 	if (anonymous_only && (linebuf[0] == 'U' || linebuf[0] == 'u'))
1723b53f69aScamield 		return (client_parse_anon(s));
1733b53f69aScamield 
1743b53f69aScamield 	return (1);
1753b53f69aScamield }
1763b53f69aScamield 
1773b53f69aScamield int
client_parse_anon(struct session * s)1783b53f69aScamield client_parse_anon(struct session *s)
1793b53f69aScamield {
1803b53f69aScamield 	if (strcasecmp("USER ftp\r\n", linebuf) != 0 &&
1813b53f69aScamield 	    strcasecmp("USER anonymous\r\n", linebuf) != 0) {
1824f154adaSderaadt 		snprintf(linebuf, sizeof linebuf,
1833b53f69aScamield 		    "500 Only anonymous FTP allowed\r\n");
1843b53f69aScamield 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
1853b53f69aScamield 
1863b53f69aScamield 		/* Talk back to the client ourself. */
1874f154adaSderaadt 		linelen = strlen(linebuf);
1883b53f69aScamield 		bufferevent_write(s->client_bufev, linebuf, linelen);
1893b53f69aScamield 
1903b53f69aScamield 		/* Clear buffer so it's not sent to the server. */
1913b53f69aScamield 		linebuf[0] = '\0';
1923b53f69aScamield 		linelen = 0;
1933b53f69aScamield 	}
1943b53f69aScamield 
1953b53f69aScamield 	return (1);
1963b53f69aScamield }
1973b53f69aScamield 
1983b53f69aScamield int
client_parse_cmd(struct session * s)1993b53f69aScamield client_parse_cmd(struct session *s)
2003b53f69aScamield {
2013b53f69aScamield 	if (strncasecmp("PASV", linebuf, 4) == 0)
2023b53f69aScamield 		s->cmd = CMD_PASV;
2033b53f69aScamield 	else if (strncasecmp("PORT ", linebuf, 5) == 0)
2043b53f69aScamield 		s->cmd = CMD_PORT;
2053b53f69aScamield 	else if (strncasecmp("EPSV", linebuf, 4) == 0)
2063b53f69aScamield 		s->cmd = CMD_EPSV;
2073b53f69aScamield 	else if (strncasecmp("EPRT ", linebuf, 5) == 0)
2083b53f69aScamield 		s->cmd = CMD_EPRT;
2093b53f69aScamield 	else
2103b53f69aScamield 		return (1);
2113b53f69aScamield 
2123b53f69aScamield 	if (ipv6_mode && (s->cmd == CMD_PASV || s->cmd == CMD_PORT)) {
2133b53f69aScamield 		logmsg(LOG_CRIT, "PASV and PORT not allowed with IPv6");
2143b53f69aScamield 		return (0);
2153b53f69aScamield 	}
2163b53f69aScamield 
2173b53f69aScamield 	if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) {
2183b53f69aScamield 		s->port = parse_port(s->cmd);
2193b53f69aScamield 		if (s->port < MIN_PORT) {
2203b53f69aScamield 			logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id,
2213b53f69aScamield 			    linebuf);
2223b53f69aScamield 			return (0);
2233b53f69aScamield 		}
2243b53f69aScamield 		s->proxy_port = pick_proxy_port();
2253b53f69aScamield 		proxy_reply(s->cmd, sstosa(&s->proxy_ss), s->proxy_port);
2263b53f69aScamield 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
2273b53f69aScamield 	}
2283b53f69aScamield 
2293b53f69aScamield 	return (1);
2303b53f69aScamield }
2313b53f69aScamield 
2323b53f69aScamield void
client_read(struct bufferevent * bufev,void * arg)2333b53f69aScamield client_read(struct bufferevent *bufev, void *arg)
2343b53f69aScamield {
2353b53f69aScamield 	struct session	*s = arg;
2363b53f69aScamield 	size_t		 buf_avail, read;
2373b53f69aScamield 	int		 n;
2383b53f69aScamield 
2393b53f69aScamield 	do {
2403b53f69aScamield 		buf_avail = sizeof s->cbuf - s->cbuf_valid;
2413b53f69aScamield 		read = bufferevent_read(bufev, s->cbuf + s->cbuf_valid,
2423b53f69aScamield 		    buf_avail);
2433b53f69aScamield 		s->cbuf_valid += read;
2443b53f69aScamield 
245f9bbbf45Sfgsch 		while ((n = get_line(s->cbuf, &s->cbuf_valid)) > 0) {
2463b53f69aScamield 			logmsg(LOG_DEBUG, "#%d client: %s", s->id, linebuf);
2473b53f69aScamield 			if (!client_parse(s)) {
2483b53f69aScamield 				end_session(s);
2493b53f69aScamield 				return;
2503b53f69aScamield 			}
2513b53f69aScamield 			bufferevent_write(s->server_bufev, linebuf, linelen);
2523b53f69aScamield 		}
2533b53f69aScamield 
2543b53f69aScamield 		if (n == -1) {
255f5c0a919Scamield 			logmsg(LOG_ERR, "#%d client command too long or not"
256f5c0a919Scamield 			    " clean", s->id);
2573b53f69aScamield 			end_session(s);
2583b53f69aScamield 			return;
2593b53f69aScamield 		}
2603b53f69aScamield 	} while (read == buf_avail);
2613b53f69aScamield }
2623b53f69aScamield 
2633b53f69aScamield int
drop_privs(void)2643b53f69aScamield drop_privs(void)
2653b53f69aScamield {
2663b53f69aScamield 	struct passwd *pw;
2673b53f69aScamield 
2683b53f69aScamield 	pw = getpwnam(NOPRIV_USER);
2693b53f69aScamield 	if (pw == NULL)
2703b53f69aScamield 		return (0);
2713b53f69aScamield 
2723b53f69aScamield 	tzset();
2733b53f69aScamield 	if (chroot(CHROOT_DIR) != 0 || chdir("/") != 0 ||
2743b53f69aScamield 	    setgroups(1, &pw->pw_gid) != 0 ||
2753b53f69aScamield 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0 ||
2763b53f69aScamield 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0)
2773b53f69aScamield 		return (0);
2783b53f69aScamield 
2793b53f69aScamield 	return (1);
2803b53f69aScamield }
2813b53f69aScamield 
2823b53f69aScamield void
end_session(struct session * s)2833b53f69aScamield end_session(struct session *s)
2843b53f69aScamield {
2853b53f69aScamield 	int err;
2863b53f69aScamield 
2873b53f69aScamield 	logmsg(LOG_INFO, "#%d ending session", s->id);
2883b53f69aScamield 
2896767c32eSjoel 	/* Flush output buffers. */
2906767c32eSjoel 	if (s->client_bufev && s->client_fd != -1)
2916767c32eSjoel 		evbuffer_write(s->client_bufev->output, s->client_fd);
2926767c32eSjoel 	if (s->server_bufev && s->server_fd != -1)
2936767c32eSjoel 		evbuffer_write(s->server_bufev->output, s->server_fd);
2946767c32eSjoel 
2955e170729Sbenno 	if (s->client_fd != -1)
2966afc2855Scamield 		close(s->client_fd);
2973b53f69aScamield 	if (s->server_fd != -1)
2986afc2855Scamield 		close(s->server_fd);
2993b53f69aScamield 
3002e6efe12Scamield 	if (s->client_bufev)
3012e6efe12Scamield 		bufferevent_free(s->client_bufev);
3022e6efe12Scamield 	if (s->server_bufev)
3032e6efe12Scamield 		bufferevent_free(s->server_bufev);
3042e6efe12Scamield 
305ca70fba4Smmcc 	/* Remove rulesets by committing empty ones. */
3063b53f69aScamield 	err = 0;
3073b53f69aScamield 	if (prepare_commit(s->id) == -1)
3083b53f69aScamield 		err = errno;
3093b53f69aScamield 	else if (do_commit() == -1) {
3103b53f69aScamield 		err = errno;
3113b53f69aScamield 		do_rollback();
3123b53f69aScamield 	}
3133b53f69aScamield 	if (err)
3143b53f69aScamield 		logmsg(LOG_ERR, "#%d pf rule removal failed: %s", s->id,
3153b53f69aScamield 		    strerror(err));
3163b53f69aScamield 
3173b53f69aScamield 	LIST_REMOVE(s, entry);
3183b53f69aScamield 	free(s);
3193b53f69aScamield 	session_count--;
3203b53f69aScamield }
3213b53f69aScamield 
3228562b6e2Sclaudio void
exit_daemon(void)3233b53f69aScamield exit_daemon(void)
3243b53f69aScamield {
3253b53f69aScamield 	struct session *s, *next;
3263b53f69aScamield 
327abcbcc4dSdoug 	for (s = LIST_FIRST(&sessions); s != NULL; s = next) {
3283b53f69aScamield 		next = LIST_NEXT(s, entry);
3293b53f69aScamield 		end_session(s);
3303b53f69aScamield 	}
3313b53f69aScamield 
3323b53f69aScamield 	if (daemonize)
3333b53f69aScamield 		closelog();
3343b53f69aScamield 
3353b53f69aScamield 	exit(0);
3363b53f69aScamield }
3373b53f69aScamield 
3383b53f69aScamield int
get_line(char * buf,size_t * valid)339f9bbbf45Sfgsch get_line(char *buf, size_t *valid)
3403b53f69aScamield {
3413b53f69aScamield 	size_t i;
3423b53f69aScamield 
3433b53f69aScamield 	if (*valid > MAX_LINE)
3443b53f69aScamield 		return (-1);
3453b53f69aScamield 
3463b53f69aScamield 	/* Copy to linebuf while searching for a newline. */
347f5c0a919Scamield 	for (i = 0; i < *valid; i++) {
348f5c0a919Scamield 		linebuf[i] = buf[i];
349f5c0a919Scamield 		if (buf[i] == '\0')
350f5c0a919Scamield 			return (-1);
351f5c0a919Scamield 		if (buf[i] == '\n')
3523b53f69aScamield 			break;
353f5c0a919Scamield 	}
3543b53f69aScamield 
3553b53f69aScamield 	if (i == *valid) {
3563b53f69aScamield 		/* No newline found. */
3573b53f69aScamield 		linebuf[0] = '\0';
3583b53f69aScamield 		linelen = 0;
3593b53f69aScamield 		if (i < MAX_LINE)
3603b53f69aScamield 			return (0);
3613b53f69aScamield 		return (-1);
3623b53f69aScamield 	}
3633b53f69aScamield 
3643b53f69aScamield 	linelen = i + 1;
3653b53f69aScamield 	linebuf[linelen] = '\0';
3663b53f69aScamield 	*valid -= linelen;
3673b53f69aScamield 
3683b53f69aScamield 	/* Move leftovers to the start. */
3693b53f69aScamield 	if (*valid != 0)
3703b53f69aScamield 		bcopy(buf + linelen, buf, *valid);
3713b53f69aScamield 
3723b53f69aScamield 	return ((int)linelen);
3733b53f69aScamield }
3743b53f69aScamield 
3753b53f69aScamield void
handle_connection(const int listen_fd,short event,void * arg)376bc3a204fScamield handle_connection(const int listen_fd, short event, void *arg)
3773b53f69aScamield {
3783b53f69aScamield 	struct sockaddr_storage tmp_ss;
3793b53f69aScamield 	struct sockaddr *client_sa, *server_sa, *fixed_server_sa;
38048ed0d11Smikeb 	struct sockaddr *proxy_to_server_sa;
3813b53f69aScamield 	struct session *s;
3823b53f69aScamield 	socklen_t len;
3833b53f69aScamield 	int client_fd, fc, on;
3843b53f69aScamield 
385bc3a204fScamield 	event_add(&listen_ev, NULL);
386bc3a204fScamield 
387bc3a204fScamield 	if ((event & EV_TIMEOUT))
388bc3a204fScamield 		/* accept() is no longer paused. */
389bc3a204fScamield 		return;
390bc3a204fScamield 
3913b53f69aScamield 	/*
3923b53f69aScamield 	 * We _must_ accept the connection, otherwise libevent will keep
3933b53f69aScamield 	 * coming back, and we will chew up all CPU.
3943b53f69aScamield 	 */
3953b53f69aScamield 	client_sa = sstosa(&tmp_ss);
3963b53f69aScamield 	len = sizeof(struct sockaddr_storage);
397df69c215Sderaadt 	if ((client_fd = accept(listen_fd, client_sa, &len)) == -1) {
398bc3a204fScamield 		logmsg(LOG_CRIT, "accept() failed: %s", strerror(errno));
399bc3a204fScamield 
400bc3a204fScamield 		/*
401bc3a204fScamield 		 * Pause accept if we are out of file descriptors, or
402bc3a204fScamield 		 * libevent will haunt us here too.
403bc3a204fScamield 		 */
404bc3a204fScamield 		if (errno == ENFILE || errno == EMFILE) {
405bc3a204fScamield 			struct timeval pause = { 1, 0 };
406bc3a204fScamield 
407bc3a204fScamield 			event_del(&listen_ev);
408bc3a204fScamield 			evtimer_add(&pause_accept_ev, &pause);
40962e3c252Sderaadt 		} else if (errno != EWOULDBLOCK && errno != EINTR &&
41062e3c252Sderaadt 		    errno != ECONNABORTED)
41162e3c252Sderaadt 			logmsg(LOG_CRIT, "accept() failed: %s", strerror(errno));
4123b53f69aScamield 		return;
4133b53f69aScamield 	}
4143b53f69aScamield 
4153b53f69aScamield 	/* Refuse connection if the maximum is reached. */
4163b53f69aScamield 	if (session_count >= max_sessions) {
4173b53f69aScamield 		logmsg(LOG_ERR, "client limit (%d) reached, refusing "
4183b53f69aScamield 		    "connection from %s", max_sessions, sock_ntop(client_sa));
4193b53f69aScamield 		close(client_fd);
4203b53f69aScamield 		return;
4213b53f69aScamield 	}
4223b53f69aScamield 
4233b53f69aScamield 	/* Allocate session and copy back the info from the accept(). */
4243b53f69aScamield 	s = init_session();
4253b53f69aScamield 	if (s == NULL) {
4263b53f69aScamield 		logmsg(LOG_CRIT, "init_session failed");
4273b53f69aScamield 		close(client_fd);
4283b53f69aScamield 		return;
4293b53f69aScamield 	}
4303b53f69aScamield 	s->client_fd = client_fd;
4313b53f69aScamield 	memcpy(sstosa(&s->client_ss), client_sa, client_sa->sa_len);
4323b53f69aScamield 
4333b53f69aScamield 	/* Cast it once, and be done with it. */
4343b53f69aScamield 	client_sa = sstosa(&s->client_ss);
4353b53f69aScamield 	server_sa = sstosa(&s->server_ss);
4363b53f69aScamield 	proxy_to_server_sa = sstosa(&s->proxy_ss);
4373b53f69aScamield 	fixed_server_sa = sstosa(&fixed_server_ss);
4383b53f69aScamield 
4393b53f69aScamield 	/* Log id/client early to ease debugging. */
4403b53f69aScamield 	logmsg(LOG_DEBUG, "#%d accepted connection from %s", s->id,
4413b53f69aScamield 	    sock_ntop(client_sa));
4423b53f69aScamield 
4433b53f69aScamield 	/*
4443b53f69aScamield 	 * Find out the real server and port that the client wanted.
4453b53f69aScamield 	 */
4463b53f69aScamield 	len = sizeof(struct sockaddr_storage);
447df69c215Sderaadt 	if (getsockname(s->client_fd, server_sa, &len) == -1) {
4483b53f69aScamield 		logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id,
4493b53f69aScamield 		    strerror(errno));
4503b53f69aScamield 		goto fail;
4513b53f69aScamield 	}
45248ed0d11Smikeb 	len = sizeof(s->client_rd);
45313ae2b57Smikeb 	if (getsockopt(s->client_fd, SOL_SOCKET, SO_RTABLE, &s->client_rd,
45413ae2b57Smikeb 	    &len) && errno != ENOPROTOOPT) {
45548ed0d11Smikeb 		logmsg(LOG_CRIT, "#%d getsockopt failed: %s", s->id,
45648ed0d11Smikeb 		    strerror(errno));
4573b53f69aScamield 		goto fail;
4583b53f69aScamield 	}
4593b53f69aScamield 	if (fixed_server) {
4603b53f69aScamield 		memcpy(sstosa(&s->orig_server_ss), server_sa,
4613b53f69aScamield 		    server_sa->sa_len);
4623b53f69aScamield 		memcpy(server_sa, fixed_server_sa, fixed_server_sa->sa_len);
4633b53f69aScamield 	}
4643b53f69aScamield 
4653b53f69aScamield 	/* XXX: check we are not connecting to ourself. */
4663b53f69aScamield 
4673b53f69aScamield 	/*
4683b53f69aScamield 	 * Setup socket and connect to server.
4693b53f69aScamield 	 */
4703b53f69aScamield 	if ((s->server_fd = socket(server_sa->sa_family, SOCK_STREAM,
471df69c215Sderaadt 	    IPPROTO_TCP)) == -1) {
4723b53f69aScamield 		logmsg(LOG_CRIT, "#%d server socket failed: %s", s->id,
4733b53f69aScamield 		    strerror(errno));
4743b53f69aScamield 		goto fail;
4753b53f69aScamield 	}
4763b53f69aScamield 	if (fixed_proxy && bind(s->server_fd, sstosa(&fixed_proxy_ss),
4773b53f69aScamield 	    fixed_proxy_ss.ss_len) != 0) {
4783b53f69aScamield 		logmsg(LOG_CRIT, "#%d cannot bind fixed proxy address: %s",
4793b53f69aScamield 		    s->id, strerror(errno));
4803b53f69aScamield 		goto fail;
4813b53f69aScamield 	}
4823b53f69aScamield 
4833b53f69aScamield 	/* Use non-blocking connect(), see CONNECT_TIMEOUT below. */
4843b53f69aScamield 	if ((fc = fcntl(s->server_fd, F_GETFL)) == -1 ||
4853b53f69aScamield 	    fcntl(s->server_fd, F_SETFL, fc | O_NONBLOCK) == -1) {
4863b53f69aScamield 		logmsg(LOG_CRIT, "#%d cannot mark socket non-blocking: %s",
4873b53f69aScamield 		    s->id, strerror(errno));
4883b53f69aScamield 		goto fail;
4893b53f69aScamield 	}
490df69c215Sderaadt 	if (connect(s->server_fd, server_sa, server_sa->sa_len) == -1 &&
4913b53f69aScamield 	    errno != EINPROGRESS) {
4923b53f69aScamield 		logmsg(LOG_CRIT, "#%d proxy cannot connect to server %s: %s",
4933b53f69aScamield 		    s->id, sock_ntop(server_sa), strerror(errno));
4943b53f69aScamield 		goto fail;
4953b53f69aScamield 	}
4963b53f69aScamield 
4973b53f69aScamield 	len = sizeof(struct sockaddr_storage);
498df69c215Sderaadt 	if ((getsockname(s->server_fd, proxy_to_server_sa, &len)) == -1) {
4993b53f69aScamield 		logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id,
5003b53f69aScamield 		    strerror(errno));
5013b53f69aScamield 		goto fail;
5023b53f69aScamield 	}
5033b53f69aScamield 
5043b53f69aScamield 	logmsg(LOG_INFO, "#%d FTP session %d/%d started: client %s to server "
5053b53f69aScamield 	    "%s via proxy %s", s->id, session_count, max_sessions,
5063b53f69aScamield 	    sock_ntop(client_sa), sock_ntop(server_sa),
5073b53f69aScamield 	    sock_ntop(proxy_to_server_sa));
5083b53f69aScamield 
5093b53f69aScamield 	/* Keepalive is nice, but don't care if it fails. */
5103b53f69aScamield 	on = 1;
5113b53f69aScamield 	setsockopt(s->client_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
5123b53f69aScamield 	    sizeof on);
5133b53f69aScamield 	setsockopt(s->server_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
5143b53f69aScamield 	    sizeof on);
5153b53f69aScamield 
5163b53f69aScamield 	/*
5173b53f69aScamield 	 * Setup buffered events.
5183b53f69aScamield 	 */
5199150f310Scamield 	s->client_bufev = bufferevent_new(s->client_fd, &client_read, NULL,
5209150f310Scamield 	    &client_error, s);
5213b53f69aScamield 	if (s->client_bufev == NULL) {
5223b53f69aScamield 		logmsg(LOG_CRIT, "#%d bufferevent_new client failed", s->id);
5233b53f69aScamield 		goto fail;
5243b53f69aScamield 	}
5253b53f69aScamield 	bufferevent_settimeout(s->client_bufev, timeout, 0);
5263b53f69aScamield 	bufferevent_enable(s->client_bufev, EV_READ | EV_TIMEOUT);
5273b53f69aScamield 
5289150f310Scamield 	s->server_bufev = bufferevent_new(s->server_fd, &server_read, NULL,
5299150f310Scamield 	    &server_error, s);
5303b53f69aScamield 	if (s->server_bufev == NULL) {
5313b53f69aScamield 		logmsg(LOG_CRIT, "#%d bufferevent_new server failed", s->id);
5323b53f69aScamield 		goto fail;
5333b53f69aScamield 	}
5343b53f69aScamield 	bufferevent_settimeout(s->server_bufev, CONNECT_TIMEOUT, 0);
5353b53f69aScamield 	bufferevent_enable(s->server_bufev, EV_READ | EV_TIMEOUT);
5363b53f69aScamield 
5373b53f69aScamield 	return;
5383b53f69aScamield 
5393b53f69aScamield  fail:
5403b53f69aScamield 	end_session(s);
5413b53f69aScamield }
5423b53f69aScamield 
5434e6f7131Scamield void
handle_signal(int sig,short event,void * arg)5444e6f7131Scamield handle_signal(int sig, short event, void *arg)
5454e6f7131Scamield {
5464e6f7131Scamield 	/*
5474e6f7131Scamield 	 * Signal handler rules don't apply, libevent decouples for us.
5484e6f7131Scamield 	 */
5494e6f7131Scamield 
5508562b6e2Sclaudio 	logmsg(LOG_ERR, "exiting on signal %d", sig);
5514e6f7131Scamield 
5524e6f7131Scamield 	exit_daemon();
5534e6f7131Scamield }
5544e6f7131Scamield 
5554e6f7131Scamield 
5563b53f69aScamield struct session *
init_session(void)5573b53f69aScamield init_session(void)
5583b53f69aScamield {
5593b53f69aScamield 	struct session *s;
5603b53f69aScamield 
5613b53f69aScamield 	s = calloc(1, sizeof(struct session));
5623b53f69aScamield 	if (s == NULL)
5633b53f69aScamield 		return (NULL);
5643b53f69aScamield 
5653b53f69aScamield 	s->id = id_count++;
5663b53f69aScamield 	s->client_fd = -1;
5673b53f69aScamield 	s->server_fd = -1;
5683b53f69aScamield 	s->cbuf[0] = '\0';
5693b53f69aScamield 	s->cbuf_valid = 0;
5703b53f69aScamield 	s->sbuf[0] = '\0';
5713b53f69aScamield 	s->sbuf_valid = 0;
5723b53f69aScamield 	s->client_bufev = NULL;
5733b53f69aScamield 	s->server_bufev = NULL;
5743b53f69aScamield 	s->cmd = CMD_NONE;
5753b53f69aScamield 	s->port = 0;
5763b53f69aScamield 
5773b53f69aScamield 	LIST_INSERT_HEAD(&sessions, s, entry);
5783b53f69aScamield 	session_count++;
5793b53f69aScamield 
5803b53f69aScamield 	return (s);
5813b53f69aScamield }
5823b53f69aScamield 
5833b53f69aScamield void
logmsg(int pri,const char * message,...)5843b53f69aScamield logmsg(int pri, const char *message, ...)
5853b53f69aScamield {
5863b53f69aScamield 	va_list	ap;
5873b53f69aScamield 
5883b53f69aScamield 	if (pri > loglevel)
5893b53f69aScamield 		return;
5903b53f69aScamield 
5918582d7b5Scamield 	va_start(ap, message);
5928582d7b5Scamield 
5933b53f69aScamield 	if (daemonize)
5943b53f69aScamield 		/* syslog does its own vissing. */
5953b53f69aScamield 		vsyslog(pri, message, ap);
5963b53f69aScamield 	else {
5973b53f69aScamield 		char buf[MAX_LOGLINE];
5983b53f69aScamield 		char visbuf[2 * MAX_LOGLINE];
5993b53f69aScamield 
6003b53f69aScamield 		/* We don't care about truncation. */
6013b53f69aScamield 		vsnprintf(buf, sizeof buf, message, ap);
6023b53f69aScamield 		strnvis(visbuf, buf, sizeof visbuf, VIS_CSTYLE | VIS_NL);
6033b53f69aScamield 		fprintf(stderr, "%s\n", visbuf);
6043b53f69aScamield 	}
6053b53f69aScamield 
6063b53f69aScamield 	va_end(ap);
6073b53f69aScamield }
6083b53f69aScamield 
6093b53f69aScamield int
main(int argc,char * argv[])6103b53f69aScamield main(int argc, char *argv[])
6113b53f69aScamield {
6123b53f69aScamield 	struct rlimit rlp;
6133b53f69aScamield 	struct addrinfo hints, *res;
614bc3a204fScamield 	struct event ev_sighup, ev_sigint, ev_sigterm;
6157307f9faSjca 	int ch, devnull, error, listenfd, on;
616f9429323Scamield 	const char *errstr;
6173b53f69aScamield 
6183b53f69aScamield 	/* Defaults. */
6193b53f69aScamield 	anonymous_only	= 0;
6203b53f69aScamield 	daemonize	= 1;
6213b53f69aScamield 	fixed_proxy	= NULL;
6223b53f69aScamield 	fixed_server	= NULL;
6233b53f69aScamield 	fixed_server_port = "21";
6243b53f69aScamield 	ipv6_mode	= 0;
6253b53f69aScamield 	listen_ip	= NULL;
6263b53f69aScamield 	listen_port	= "8021";
6273b53f69aScamield 	loglevel	= LOG_NOTICE;
6283b53f69aScamield 	max_sessions	= 100;
6293b53f69aScamield 	qname		= NULL;
6303b53f69aScamield 	rfc_mode	= 0;
6311a90e72aShenning 	tagname		= NULL;
6323b53f69aScamield 	timeout		= 24 * 3600;
633867a6359Scamield 	verbose		= 0;
6343b53f69aScamield 
6353b53f69aScamield 	/* Other initialization. */
636ec8c1742Sjca 	devnull		= -1;
6373b53f69aScamield 	id_count	= 1;
6383b53f69aScamield 	session_count	= 0;
6393b53f69aScamield 
6401a90e72aShenning 	while ((ch = getopt(argc, argv, "6Aa:b:D:dm:P:p:q:R:rT:t:v")) != -1) {
6413b53f69aScamield 		switch (ch) {
6423b53f69aScamield 		case '6':
6433b53f69aScamield 			ipv6_mode = 1;
6443b53f69aScamield 			break;
6453b53f69aScamield 		case 'A':
6463b53f69aScamield 			anonymous_only = 1;
6473b53f69aScamield 			break;
6483b53f69aScamield 		case 'a':
6493b53f69aScamield 			fixed_proxy = optarg;
6503b53f69aScamield 			break;
6513b53f69aScamield 		case 'b':
6523b53f69aScamield 			listen_ip = optarg;
6533b53f69aScamield 			break;
6543b53f69aScamield 		case 'D':
655f9429323Scamield 			loglevel = strtonum(optarg, LOG_EMERG, LOG_DEBUG,
656f9429323Scamield 			    &errstr);
657f9429323Scamield 			if (errstr)
658f9429323Scamield 				errx(1, "loglevel %s", errstr);
6593b53f69aScamield 			break;
6603b53f69aScamield 		case 'd':
6613b53f69aScamield 			daemonize = 0;
6623b53f69aScamield 			break;
6633b53f69aScamield 		case 'm':
664f9429323Scamield 			max_sessions = strtonum(optarg, 1, 500, &errstr);
665f9429323Scamield 			if (errstr)
666f9429323Scamield 				errx(1, "max sessions %s", errstr);
6673b53f69aScamield 			break;
6683b53f69aScamield 		case 'P':
6693b53f69aScamield 			fixed_server_port = optarg;
6703b53f69aScamield 			break;
6713b53f69aScamield 		case 'p':
6723b53f69aScamield 			listen_port = optarg;
6733b53f69aScamield 			break;
6743b53f69aScamield 		case 'q':
6753b53f69aScamield 			if (strlen(optarg) >= PF_QNAME_SIZE)
6763b53f69aScamield 				errx(1, "queuename too long");
6773b53f69aScamield 			qname = optarg;
6783b53f69aScamield 			break;
6793b53f69aScamield 		case 'R':
6803b53f69aScamield 			fixed_server = optarg;
6813b53f69aScamield 			break;
6823b53f69aScamield 		case 'r':
6833b53f69aScamield 			rfc_mode = 1;
6843b53f69aScamield 			break;
6851a90e72aShenning 		case 'T':
6861a90e72aShenning 			if (strlen(optarg) >= PF_TAG_NAME_SIZE)
6871a90e72aShenning 				errx(1, "tagname too long");
6881a90e72aShenning 			tagname = optarg;
6891a90e72aShenning 			break;
6903b53f69aScamield 		case 't':
691f9429323Scamield 			timeout = strtonum(optarg, 0, 86400, &errstr);
692f9429323Scamield 			if (errstr)
693f9429323Scamield 				errx(1, "timeout %s", errstr);
6943b53f69aScamield 			break;
695867a6359Scamield 		case 'v':
696867a6359Scamield 			verbose++;
697867a6359Scamield 			if (verbose > 2)
698867a6359Scamield 				usage();
699867a6359Scamield 			break;
7003b53f69aScamield 		default:
7013b53f69aScamield 			usage();
7023b53f69aScamield 		}
7033b53f69aScamield 	}
7043b53f69aScamield 
7053b53f69aScamield 	if (listen_ip == NULL)
7063b53f69aScamield 		listen_ip = ipv6_mode ? "::1" : "127.0.0.1";
7073b53f69aScamield 
7083b53f69aScamield 	/* Check for root to save the user from cryptic failure messages. */
7093b53f69aScamield 	if (getuid() != 0)
7103b53f69aScamield 		errx(1, "needs to start as root");
7113b53f69aScamield 
712c097a3c6Sajacoutot 	if (getpwnam(NOPRIV_USER) == NULL)
713c097a3c6Sajacoutot 		errx(1, "unknown user %s", NOPRIV_USER);
714c097a3c6Sajacoutot 
7153b53f69aScamield 	/* Raise max. open files limit to satisfy max. sessions. */
7163b53f69aScamield 	rlp.rlim_cur = rlp.rlim_max = (2 * max_sessions) + 10;
7173b53f69aScamield 	if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
7183b53f69aScamield 		err(1, "setrlimit");
7193b53f69aScamield 
7203b53f69aScamield 	if (fixed_proxy) {
7213b53f69aScamield 		memset(&hints, 0, sizeof hints);
7223b53f69aScamield 		hints.ai_flags = AI_NUMERICHOST;
7233b53f69aScamield 		hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
7243b53f69aScamield 		hints.ai_socktype = SOCK_STREAM;
7253b53f69aScamield 		error = getaddrinfo(fixed_proxy, NULL, &hints, &res);
7263b53f69aScamield 		if (error)
7273b53f69aScamield 			errx(1, "getaddrinfo fixed proxy address failed: %s",
7283b53f69aScamield 			    gai_strerror(error));
7293b53f69aScamield 		memcpy(&fixed_proxy_ss, res->ai_addr, res->ai_addrlen);
7303b53f69aScamield 		logmsg(LOG_INFO, "using %s to connect to servers",
7313b53f69aScamield 		    sock_ntop(sstosa(&fixed_proxy_ss)));
7323b53f69aScamield 		freeaddrinfo(res);
7333b53f69aScamield 	}
7343b53f69aScamield 
7353b53f69aScamield 	if (fixed_server) {
7363b53f69aScamield 		memset(&hints, 0, sizeof hints);
7373b53f69aScamield 		hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
7383b53f69aScamield 		hints.ai_socktype = SOCK_STREAM;
7393b53f69aScamield 		error = getaddrinfo(fixed_server, fixed_server_port, &hints,
7403b53f69aScamield 		    &res);
7413b53f69aScamield 		if (error)
7423b53f69aScamield 			errx(1, "getaddrinfo fixed server address failed: %s",
7433b53f69aScamield 			    gai_strerror(error));
7443b53f69aScamield 		memcpy(&fixed_server_ss, res->ai_addr, res->ai_addrlen);
7453b53f69aScamield 		logmsg(LOG_INFO, "using fixed server %s",
7463b53f69aScamield 		    sock_ntop(sstosa(&fixed_server_ss)));
7473b53f69aScamield 		freeaddrinfo(res);
7483b53f69aScamield 	}
7493b53f69aScamield 
7503b53f69aScamield 	/* Setup listener. */
7513b53f69aScamield 	memset(&hints, 0, sizeof hints);
7523b53f69aScamield 	hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
7533b53f69aScamield 	hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
7543b53f69aScamield 	hints.ai_socktype = SOCK_STREAM;
7553b53f69aScamield 	error = getaddrinfo(listen_ip, listen_port, &hints, &res);
7563b53f69aScamield 	if (error)
7573b53f69aScamield 		errx(1, "getaddrinfo listen address failed: %s",
7583b53f69aScamield 		    gai_strerror(error));
7593b53f69aScamield 	if ((listenfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
7603b53f69aScamield 		errx(1, "socket failed");
7613b53f69aScamield 	on = 1;
7623b53f69aScamield 	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on,
7633b53f69aScamield 	    sizeof on) != 0)
7643b53f69aScamield 		err(1, "setsockopt failed");
7653b53f69aScamield 	if (bind(listenfd, (struct sockaddr *)res->ai_addr,
7663b53f69aScamield 	    (socklen_t)res->ai_addrlen) != 0)
7673b53f69aScamield 	    	err(1, "bind failed");
7683b53f69aScamield 	if (listen(listenfd, TCP_BACKLOG) != 0)
7693b53f69aScamield 		err(1, "listen failed");
7703b53f69aScamield 	freeaddrinfo(res);
7713b53f69aScamield 
7723b53f69aScamield 	/* Initialize pf. */
7731a90e72aShenning 	init_filter(qname, tagname, verbose);
7743b53f69aScamield 
7753b53f69aScamield 	if (daemonize) {
776*b7041c07Sderaadt 		devnull = open(_PATH_DEVNULL, O_RDWR);
7777307f9faSjca 		if (devnull == -1)
7787307f9faSjca 			err(1, "open(%s)", _PATH_DEVNULL);
7797307f9faSjca 	}
7807307f9faSjca 
7817307f9faSjca 	if (!drop_privs())
7827307f9faSjca 		err(1, "cannot drop privileges");
7837307f9faSjca 
7847307f9faSjca 	if (daemonize) {
7857307f9faSjca 		if (rdaemon(devnull) == -1)
7863b53f69aScamield 			err(1, "cannot daemonize");
7873b53f69aScamield 		openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
7883b53f69aScamield 	}
7893b53f69aScamield 
7903b53f69aScamield 	/* Use logmsg for output from here on. */
7913b53f69aScamield 
7923b53f69aScamield 	event_init();
7934e6f7131Scamield 
7944e6f7131Scamield 	/* Setup signal handler. */
7956ba63af2Scamield 	signal(SIGPIPE, SIG_IGN);
7964e6f7131Scamield 	signal_set(&ev_sighup, SIGHUP, handle_signal, NULL);
7974e6f7131Scamield 	signal_set(&ev_sigint, SIGINT, handle_signal, NULL);
7984e6f7131Scamield 	signal_set(&ev_sigterm, SIGTERM, handle_signal, NULL);
7994e6f7131Scamield 	signal_add(&ev_sighup, NULL);
8004e6f7131Scamield 	signal_add(&ev_sigint, NULL);
8014e6f7131Scamield 	signal_add(&ev_sigterm, NULL);
8024e6f7131Scamield 
803bc3a204fScamield 	event_set(&listen_ev, listenfd, EV_READ, handle_connection, NULL);
804bc3a204fScamield 	event_add(&listen_ev, NULL);
805bc3a204fScamield 	evtimer_set(&pause_accept_ev, handle_connection, NULL);
8063b53f69aScamield 
8073b53f69aScamield 	logmsg(LOG_NOTICE, "listening on %s port %s", listen_ip, listen_port);
8083b53f69aScamield 
8093b53f69aScamield 	/*  Vroom, vroom.  */
8103b53f69aScamield 	event_dispatch();
8113b53f69aScamield 
8123b53f69aScamield 	logmsg(LOG_ERR, "event_dispatch error: %s", strerror(errno));
8133b53f69aScamield 	exit_daemon();
8143b53f69aScamield 
8153b53f69aScamield 	/* NOTREACHED */
8163b53f69aScamield 	return (1);
8173b53f69aScamield }
8183b53f69aScamield 
8193b53f69aScamield u_int16_t
parse_port(int mode)8203b53f69aScamield parse_port(int mode)
8213b53f69aScamield {
8223b53f69aScamield 	unsigned int	 port, v[6];
8233b53f69aScamield 	int		 n;
8243b53f69aScamield 	char		*p;
8253b53f69aScamield 
8263b53f69aScamield 	/* Find the last space or left-parenthesis. */
8273b53f69aScamield 	for (p = linebuf + linelen; p > linebuf; p--)
8283b53f69aScamield 		if (*p == ' ' || *p == '(')
8293b53f69aScamield 			break;
8303b53f69aScamield 	if (p == linebuf)
8313b53f69aScamield 		return (0);
8323b53f69aScamield 
8333b53f69aScamield 	switch (mode) {
8343b53f69aScamield 	case CMD_PORT:
8353b53f69aScamield 		n = sscanf(p, " %u,%u,%u,%u,%u,%u", &v[0], &v[1], &v[2],
8363b53f69aScamield 		    &v[3], &v[4], &v[5]);
8373b53f69aScamield 		if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8383b53f69aScamield 		    v[3] < 256 && v[4] < 256 && v[5] < 256)
8393b53f69aScamield 			return ((v[4] << 8) | v[5]);
8403b53f69aScamield 		break;
8413b53f69aScamield 	case CMD_PASV:
8423b53f69aScamield 		n = sscanf(p, "(%u,%u,%u,%u,%u,%u)", &v[0], &v[1], &v[2],
8433b53f69aScamield 		    &v[3], &v[4], &v[5]);
8443b53f69aScamield 		if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8453b53f69aScamield 		    v[3] < 256 && v[4] < 256 && v[5] < 256)
8463b53f69aScamield 			return ((v[4] << 8) | v[5]);
8473b53f69aScamield 		break;
8483b53f69aScamield 	case CMD_EPSV:
8493b53f69aScamield 		n = sscanf(p, "(|||%u|)", &port);
8503b53f69aScamield 		if (n == 1 && port < 65536)
8513b53f69aScamield 			return (port);
8523b53f69aScamield 		break;
8533b53f69aScamield 	case CMD_EPRT:
8543b53f69aScamield 		n = sscanf(p, " |1|%u.%u.%u.%u|%u|", &v[0], &v[1], &v[2],
8553b53f69aScamield 		    &v[3], &port);
8563b53f69aScamield 		if (n == 5 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8573b53f69aScamield 		    v[3] < 256 && port < 65536)
8583b53f69aScamield 			return (port);
8593b53f69aScamield 		n = sscanf(p, " |2|%*[a-fA-F0-9:]|%u|", &port);
8603b53f69aScamield 		if (n == 1 && port < 65536)
8613b53f69aScamield 			return (port);
8623b53f69aScamield 		break;
8633b53f69aScamield 	default:
8643b53f69aScamield 		return (0);
8653b53f69aScamield 	}
8663b53f69aScamield 
8673b53f69aScamield 	return (0);
8683b53f69aScamield }
8693b53f69aScamield 
8703b53f69aScamield u_int16_t
pick_proxy_port(void)8713b53f69aScamield pick_proxy_port(void)
8723b53f69aScamield {
8733b53f69aScamield 	/* Random should be good enough for avoiding port collisions. */
87466ad965fSdjm 	return (IPPORT_HIFIRSTAUTO +
87566ad965fSdjm 	    arc4random_uniform(IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO));
8763b53f69aScamield }
8773b53f69aScamield 
8783b53f69aScamield void
proxy_reply(int cmd,struct sockaddr * sa,u_int16_t port)8793b53f69aScamield proxy_reply(int cmd, struct sockaddr *sa, u_int16_t port)
8803b53f69aScamield {
8813b53f69aScamield 	int i, r;
8823b53f69aScamield 
8833b53f69aScamield 	switch (cmd) {
8843b53f69aScamield 	case CMD_PORT:
8853b53f69aScamield 		r = snprintf(linebuf, sizeof linebuf,
8863b53f69aScamield 		    "PORT %s,%u,%u\r\n", sock_ntop(sa), port / 256,
8873b53f69aScamield 		    port % 256);
8883b53f69aScamield 		break;
8893b53f69aScamield 	case CMD_PASV:
8903b53f69aScamield 		r = snprintf(linebuf, sizeof linebuf,
8913b53f69aScamield 		    "227 Entering Passive Mode (%s,%u,%u)\r\n", sock_ntop(sa),
8923b53f69aScamield 		        port / 256, port % 256);
8933b53f69aScamield 		break;
8943b53f69aScamield 	case CMD_EPRT:
8953b53f69aScamield 		if (sa->sa_family == AF_INET)
8963b53f69aScamield 			r = snprintf(linebuf, sizeof linebuf,
8973b53f69aScamield 			    "EPRT |1|%s|%u|\r\n", sock_ntop(sa), port);
8983b53f69aScamield 		else if (sa->sa_family == AF_INET6)
8993b53f69aScamield 			r = snprintf(linebuf, sizeof linebuf,
9003b53f69aScamield 			    "EPRT |2|%s|%u|\r\n", sock_ntop(sa), port);
9013b53f69aScamield 		break;
9023b53f69aScamield 	case CMD_EPSV:
9033b53f69aScamield 		r = snprintf(linebuf, sizeof linebuf,
9043b53f69aScamield 		    "229 Entering Extended Passive Mode (|||%u|)\r\n", port);
9053b53f69aScamield 		break;
9063b53f69aScamield 	}
9073b53f69aScamield 
908df69c215Sderaadt 	if (r == -1 || r >= sizeof linebuf) {
9093b53f69aScamield 		logmsg(LOG_ERR, "proxy_reply failed: %d", r);
9103b53f69aScamield 		linebuf[0] = '\0';
9113b53f69aScamield 		linelen = 0;
9123b53f69aScamield 		return;
9133b53f69aScamield 	}
9143b53f69aScamield 	linelen = (size_t)r;
9153b53f69aScamield 
9163b53f69aScamield 	if (cmd == CMD_PORT || cmd == CMD_PASV) {
9173b53f69aScamield 		/* Replace dots in IP address with commas. */
9183b53f69aScamield 		for (i = 0; i < linelen; i++)
9193b53f69aScamield 			if (linebuf[i] == '.')
9203b53f69aScamield 				linebuf[i] = ',';
9213b53f69aScamield 	}
9223b53f69aScamield }
9233b53f69aScamield 
9243b53f69aScamield void
server_error(struct bufferevent * bufev,short what,void * arg)9253b53f69aScamield server_error(struct bufferevent *bufev, short what, void *arg)
9263b53f69aScamield {
9273b53f69aScamield 	struct session *s = arg;
9283b53f69aScamield 
9293b53f69aScamield 	if (what & EVBUFFER_EOF)
9303b53f69aScamield 		logmsg(LOG_INFO, "#%d server close", s->id);
9313b53f69aScamield 	else if (what == (EVBUFFER_ERROR | EVBUFFER_READ))
9323b53f69aScamield 		logmsg(LOG_ERR, "#%d server refused connection", s->id);
9333b53f69aScamield 	else if (what & EVBUFFER_WRITE)
9343b53f69aScamield 		logmsg(LOG_ERR, "#%d server write error: %d", s->id, what);
9353b53f69aScamield 	else if (what & EVBUFFER_TIMEOUT)
9363b53f69aScamield 		logmsg(LOG_NOTICE, "#%d server timeout", s->id);
9373b53f69aScamield 	else
9383b53f69aScamield 		logmsg(LOG_ERR, "#%d abnormal server error: %d", s->id, what);
9393b53f69aScamield 
9403b53f69aScamield 	end_session(s);
9413b53f69aScamield }
9423b53f69aScamield 
9433b53f69aScamield int
server_parse(struct session * s)9443b53f69aScamield server_parse(struct session *s)
9453b53f69aScamield {
9463b53f69aScamield 	if (s->cmd == CMD_NONE || linelen < 4 || linebuf[0] != '2')
9473b53f69aScamield 		goto out;
9483b53f69aScamield 
9499136fc3fScamield 	if ((s->cmd == CMD_PASV && strncmp("227 ", linebuf, 4) == 0) ||
9509136fc3fScamield 	    (s->cmd == CMD_EPSV && strncmp("229 ", linebuf, 4) == 0))
9519136fc3fScamield 		return (allow_data_connection(s));
9529136fc3fScamield 
9539136fc3fScamield  out:
9549136fc3fScamield 	s->cmd = CMD_NONE;
9559136fc3fScamield 	s->port = 0;
9569136fc3fScamield 
9579136fc3fScamield 	return (1);
9589136fc3fScamield }
9599136fc3fScamield 
9609136fc3fScamield int
allow_data_connection(struct session * s)9619136fc3fScamield allow_data_connection(struct session *s)
9629136fc3fScamield {
9639136fc3fScamield 	struct sockaddr *client_sa, *orig_sa, *proxy_sa, *server_sa;
9649136fc3fScamield 	int prepared = 0;
9659136fc3fScamield 
9663b53f69aScamield 	/*
9673b53f69aScamield 	 * The pf rules below do quite some NAT rewriting, to keep up
9683b53f69aScamield 	 * appearances.  Points to keep in mind:
9693b53f69aScamield 	 * 1)  The client must think it's talking to the real server,
9703b53f69aScamield 	 *     for both control and data connections.  Transparently.
9713b53f69aScamield 	 * 2)  The server must think that the proxy is the client.
9723b53f69aScamield 	 * 3)  Source and destination ports are rewritten to minimize
9733b53f69aScamield 	 *     port collisions, to aid security (some systems pick weak
9743b53f69aScamield 	 *     ports) or to satisfy RFC requirements (source port 20).
9753b53f69aScamield 	 */
9763b53f69aScamield 
9773b53f69aScamield 	/* Cast this once, to make code below it more readable. */
9783b53f69aScamield 	client_sa = sstosa(&s->client_ss);
9793b53f69aScamield 	server_sa = sstosa(&s->server_ss);
9803b53f69aScamield 	proxy_sa = sstosa(&s->proxy_ss);
9813b53f69aScamield 	if (fixed_server)
9823b53f69aScamield 		/* Fixed server: data connections must appear to come
9833b53f69aScamield 		   from / go to the original server, not the fixed one. */
9843b53f69aScamield 		orig_sa = sstosa(&s->orig_server_ss);
9853b53f69aScamield 	else
9863b53f69aScamield 		/* Server not fixed: orig_server == server. */
9873b53f69aScamield 		orig_sa = sstosa(&s->server_ss);
9883b53f69aScamield 
9893b53f69aScamield 	/* Passive modes. */
9909136fc3fScamield 	if (s->cmd == CMD_PASV || s->cmd == CMD_EPSV) {
9913b53f69aScamield 		s->port = parse_port(s->cmd);
9923b53f69aScamield 		if (s->port < MIN_PORT) {
9933b53f69aScamield 			logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id,
9943b53f69aScamield 			    linebuf);
9953b53f69aScamield 			return (0);
9963b53f69aScamield 		}
9973b53f69aScamield 		s->proxy_port = pick_proxy_port();
9983b53f69aScamield 		logmsg(LOG_INFO, "#%d passive: client to server port %d"
9993b53f69aScamield 		    " via port %d", s->id, s->port, s->proxy_port);
10003b53f69aScamield 
10013b53f69aScamield 		if (prepare_commit(s->id) == -1)
10023b53f69aScamield 			goto fail;
10033b53f69aScamield 		prepared = 1;
10043b53f69aScamield 
10053b53f69aScamield 		proxy_reply(s->cmd, orig_sa, s->proxy_port);
10063b53f69aScamield 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
10073b53f69aScamield 
1008bb9fd9ecSclaudio 		/* pass in from $client to $orig_server port $proxy_port
1009bb9fd9ecSclaudio 		    rdr-to $server port $port */
101085ec68abSclaudio 		if (add_rdr(s->id, client_sa, s->client_rd, orig_sa,
101185ec68abSclaudio 		    s->proxy_port, server_sa, s->port, getrtable()) == -1)
10123b53f69aScamield 			goto fail;
10133b53f69aScamield 
1014bb9fd9ecSclaudio 		/* pass out from $client to $server port $port nat-to $proxy */
101585ec68abSclaudio 		if (add_nat(s->id, client_sa, getrtable(), server_sa,
101685ec68abSclaudio 		    s->port, proxy_sa, PF_NAT_PROXY_PORT_LOW,
101785ec68abSclaudio 		    PF_NAT_PROXY_PORT_HIGH) == -1)
10183b53f69aScamield 			goto fail;
10193b53f69aScamield 	}
10203b53f69aScamield 
10213b53f69aScamield 	/* Active modes. */
10229136fc3fScamield 	if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) {
10233b53f69aScamield 		logmsg(LOG_INFO, "#%d active: server to client port %d"
10243b53f69aScamield 		    " via port %d", s->id, s->port, s->proxy_port);
10253b53f69aScamield 
10263b53f69aScamield 		if (prepare_commit(s->id) == -1)
10273b53f69aScamield 			goto fail;
10283b53f69aScamield 		prepared = 1;
10293b53f69aScamield 
1030bb9fd9ecSclaudio 		/* pass in from $server to $proxy port $proxy_port
1031bb9fd9ecSclaudio 		    rdr-to $client port $port */
103285ec68abSclaudio 		if (add_rdr(s->id, server_sa, getrtable(), proxy_sa,
103385ec68abSclaudio 		    s->proxy_port, client_sa, s->port, s->client_rd) == -1)
10343b53f69aScamield 			goto fail;
10353b53f69aScamield 
1036bb9fd9ecSclaudio 		/* pass out from $server to $client port $port
1037bb9fd9ecSclaudio 		    nat-to $orig_server port $natport */
10383b53f69aScamield 		if (rfc_mode && s->cmd == CMD_PORT) {
10393b53f69aScamield 			/* Rewrite sourceport to RFC mandated 20. */
104085ec68abSclaudio 			if (add_nat(s->id, server_sa, s->client_rd, client_sa,
104185ec68abSclaudio 			    s->port, orig_sa, 20, 20) == -1)
10423b53f69aScamield 				goto fail;
10433b53f69aScamield 		} else {
10443b53f69aScamield 			/* Let pf pick a source port from the standard range. */
104585ec68abSclaudio 			if (add_nat(s->id, server_sa, s->client_rd, client_sa,
104685ec68abSclaudio 			    s->port, orig_sa, PF_NAT_PROXY_PORT_LOW,
10473b53f69aScamield 			    PF_NAT_PROXY_PORT_HIGH) == -1)
10483b53f69aScamield 			    	goto fail;
10493b53f69aScamield 		}
10503b53f69aScamield 	}
10513b53f69aScamield 
10523b53f69aScamield 	/* Commit rules if they were prepared. */
10533b53f69aScamield 	if (prepared && (do_commit() == -1)) {
10543b53f69aScamield 		if (errno != EBUSY)
10553b53f69aScamield 			goto fail;
10563b53f69aScamield 		/* One more try if busy. */
10573b53f69aScamield 		usleep(5000);
10583b53f69aScamield 		if (do_commit() == -1)
10593b53f69aScamield 			goto fail;
10603b53f69aScamield 	}
10613b53f69aScamield 
10623b53f69aScamield 	s->cmd = CMD_NONE;
10633b53f69aScamield 	s->port = 0;
10643b53f69aScamield 
10653b53f69aScamield 	return (1);
10663b53f69aScamield 
10673b53f69aScamield  fail:
10683b53f69aScamield 	logmsg(LOG_CRIT, "#%d pf operation failed: %s", s->id, strerror(errno));
10693b53f69aScamield 	if (prepared)
10703b53f69aScamield 		do_rollback();
10713b53f69aScamield 	return (0);
10723b53f69aScamield }
10733b53f69aScamield 
10743b53f69aScamield void
server_read(struct bufferevent * bufev,void * arg)10753b53f69aScamield server_read(struct bufferevent *bufev, void *arg)
10763b53f69aScamield {
10773b53f69aScamield 	struct session	*s = arg;
10783b53f69aScamield 	size_t		 buf_avail, read;
10793b53f69aScamield 	int		 n;
10803b53f69aScamield 
10813b53f69aScamield 	bufferevent_settimeout(bufev, timeout, 0);
10823b53f69aScamield 
10833b53f69aScamield 	do {
10843b53f69aScamield 		buf_avail = sizeof s->sbuf - s->sbuf_valid;
10853b53f69aScamield 		read = bufferevent_read(bufev, s->sbuf + s->sbuf_valid,
10863b53f69aScamield 		    buf_avail);
10873b53f69aScamield 		s->sbuf_valid += read;
10883b53f69aScamield 
1089f9bbbf45Sfgsch 		while ((n = get_line(s->sbuf, &s->sbuf_valid)) > 0) {
10903b53f69aScamield 			logmsg(LOG_DEBUG, "#%d server: %s", s->id, linebuf);
10913b53f69aScamield 			if (!server_parse(s)) {
10923b53f69aScamield 				end_session(s);
10933b53f69aScamield 				return;
10943b53f69aScamield 			}
10953b53f69aScamield 			bufferevent_write(s->client_bufev, linebuf, linelen);
10963b53f69aScamield 		}
10973b53f69aScamield 
10983b53f69aScamield 		if (n == -1) {
1099f5c0a919Scamield 			logmsg(LOG_ERR, "#%d server reply too long or not"
1100f5c0a919Scamield 			    " clean", s->id);
11013b53f69aScamield 			end_session(s);
11023b53f69aScamield 			return;
11033b53f69aScamield 		}
11043b53f69aScamield 	} while (read == buf_avail);
11053b53f69aScamield }
11063b53f69aScamield 
11073b53f69aScamield const char *
sock_ntop(struct sockaddr * sa)11083b53f69aScamield sock_ntop(struct sockaddr *sa)
11093b53f69aScamield {
11103b53f69aScamield 	static int n = 0;
11113b53f69aScamield 
11123b53f69aScamield 	/* Cycle to next buffer. */
11133b53f69aScamield 	n = (n + 1) % NTOP_BUFS;
11143b53f69aScamield 	ntop_buf[n][0] = '\0';
11153b53f69aScamield 
11163b53f69aScamield 	if (sa->sa_family == AF_INET) {
11173b53f69aScamield 		struct sockaddr_in *sin = (struct sockaddr_in *)sa;
11183b53f69aScamield 
11193b53f69aScamield 		return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n],
11203b53f69aScamield 		    sizeof ntop_buf[0]));
11213b53f69aScamield 	}
11223b53f69aScamield 
11233b53f69aScamield 	if (sa->sa_family == AF_INET6) {
11243b53f69aScamield 		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
11253b53f69aScamield 
11263b53f69aScamield 		return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n],
11273b53f69aScamield 		    sizeof ntop_buf[0]));
11283b53f69aScamield 	}
11293b53f69aScamield 
11303b53f69aScamield 	return (NULL);
11313b53f69aScamield }
11323b53f69aScamield 
11333b53f69aScamield void
usage(void)11343b53f69aScamield usage(void)
11353b53f69aScamield {
113665e34726Sschwarze 	fprintf(stderr, "usage: %s [-6Adrv] [-a sourceaddr] [-b address]"
11373b53f69aScamield 	    " [-D level] [-m maxsessions]\n                 [-P port]"
1138f9b24bdcShenning 	    " [-p port] [-q queue] [-R address] [-T tag]\n"
1139f9b24bdcShenning             "                 [-t timeout]\n", __progname);
11403b53f69aScamield 	exit(1);
11413b53f69aScamield }
11427307f9faSjca 
11437307f9faSjca int
rdaemon(int devnull)11447307f9faSjca rdaemon(int devnull)
11457307f9faSjca {
1146ec8c1742Sjca 	if (devnull == -1) {
1147ec8c1742Sjca 		errno = EBADF;
1148ec8c1742Sjca 		return (-1);
1149ec8c1742Sjca 	}
1150ec8c1742Sjca 	if (fcntl(devnull, F_GETFL) == -1)
1151ec8c1742Sjca 		return (-1);
11527307f9faSjca 
11537307f9faSjca 	switch (fork()) {
11547307f9faSjca 	case -1:
11557307f9faSjca 		return (-1);
11567307f9faSjca 	case 0:
11577307f9faSjca 		break;
11587307f9faSjca 	default:
11597307f9faSjca 		_exit(0);
11607307f9faSjca 	}
11617307f9faSjca 
11627307f9faSjca 	if (setsid() == -1)
11637307f9faSjca 		return (-1);
11647307f9faSjca 
11657307f9faSjca 	(void)dup2(devnull, STDIN_FILENO);
11667307f9faSjca 	(void)dup2(devnull, STDOUT_FILENO);
11677307f9faSjca 	(void)dup2(devnull, STDERR_FILENO);
11687307f9faSjca 	if (devnull > 2)
11697307f9faSjca 		(void)close(devnull);
11707307f9faSjca 
11717307f9faSjca 	return (0);
11727307f9faSjca }
1173