xref: /freebsd/contrib/pf/ftp-proxy/ftp-proxy.c (revision bbde5c07)
1e0bfbfceSBjoern A. Zeeb /*	$OpenBSD: ftp-proxy.c,v 1.19 2008/06/13 07:25:26 claudio Exp $ */
213b9f610SMax Laier 
313b9f610SMax Laier /*
45ee7cd21SMax Laier  * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl>
513b9f610SMax Laier  *
65ee7cd21SMax Laier  * Permission to use, copy, modify, and distribute this software for any
75ee7cd21SMax Laier  * purpose with or without fee is hereby granted, provided that the above
85ee7cd21SMax Laier  * copyright notice and this permission notice appear in all copies.
913b9f610SMax Laier  *
105ee7cd21SMax Laier  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
115ee7cd21SMax Laier  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
125ee7cd21SMax Laier  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
135ee7cd21SMax Laier  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
145ee7cd21SMax Laier  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
155ee7cd21SMax Laier  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
165ee7cd21SMax Laier  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1713b9f610SMax Laier  */
1813b9f610SMax Laier 
195ee7cd21SMax Laier #include <sys/queue.h>
205ee7cd21SMax Laier #include <sys/types.h>
2113b9f610SMax Laier #include <sys/time.h>
225ee7cd21SMax Laier #include <sys/resource.h>
2313b9f610SMax Laier #include <sys/socket.h>
2413b9f610SMax Laier 
2513b9f610SMax Laier #include <net/if.h>
265ee7cd21SMax Laier #include <net/pfvar.h>
2713b9f610SMax Laier #include <netinet/in.h>
2813b9f610SMax Laier #include <arpa/inet.h>
2913b9f610SMax Laier 
305ee7cd21SMax Laier #include <err.h>
3113b9f610SMax Laier #include <errno.h>
325ee7cd21SMax Laier #include <event.h>
335ee7cd21SMax Laier #include <fcntl.h>
3413b9f610SMax Laier #include <netdb.h>
3513b9f610SMax Laier #include <pwd.h>
3613b9f610SMax Laier #include <signal.h>
3713b9f610SMax Laier #include <stdarg.h>
3813b9f610SMax Laier #include <stdio.h>
3913b9f610SMax Laier #include <stdlib.h>
4013b9f610SMax Laier #include <string.h>
4113b9f610SMax Laier #include <syslog.h>
4213b9f610SMax Laier #include <unistd.h>
435ee7cd21SMax Laier #include <vis.h>
4413b9f610SMax Laier 
455ee7cd21SMax Laier #include "filter.h"
4613b9f610SMax Laier 
475ee7cd21SMax Laier #define CONNECT_TIMEOUT	30
485ee7cd21SMax Laier #define MIN_PORT	1024
495ee7cd21SMax Laier #define MAX_LINE	500
505ee7cd21SMax Laier #define MAX_LOGLINE	300
515ee7cd21SMax Laier #define NTOP_BUFS	3
525ee7cd21SMax Laier #define TCP_BACKLOG	10
5313b9f610SMax Laier 
545ee7cd21SMax Laier #define CHROOT_DIR	"/var/empty"
555ee7cd21SMax Laier #define NOPRIV_USER	"proxy"
5613b9f610SMax Laier 
575ee7cd21SMax Laier /* pfctl standard NAT range. */
585ee7cd21SMax Laier #define PF_NAT_PROXY_PORT_LOW	50001
595ee7cd21SMax Laier #define PF_NAT_PROXY_PORT_HIGH	65535
6013b9f610SMax Laier 
61e0bfbfceSBjoern A. Zeeb #ifndef LIST_END
62e0bfbfceSBjoern A. Zeeb #define LIST_END(a)     NULL
63e0bfbfceSBjoern A. Zeeb #endif
64e0bfbfceSBjoern A. Zeeb 
65e0bfbfceSBjoern A. Zeeb #ifndef getrtable
66e0bfbfceSBjoern A. Zeeb #define getrtable(a)    0
67e0bfbfceSBjoern A. Zeeb #endif
68e0bfbfceSBjoern A. Zeeb 
695ee7cd21SMax Laier #define	sstosa(ss)	((struct sockaddr *)(ss))
7013b9f610SMax Laier 
715ee7cd21SMax Laier enum { CMD_NONE = 0, CMD_PORT, CMD_EPRT, CMD_PASV, CMD_EPSV };
7213b9f610SMax Laier 
735ee7cd21SMax Laier struct session {
745ee7cd21SMax Laier 	u_int32_t		 id;
755ee7cd21SMax Laier 	struct sockaddr_storage  client_ss;
765ee7cd21SMax Laier 	struct sockaddr_storage  proxy_ss;
775ee7cd21SMax Laier 	struct sockaddr_storage  server_ss;
785ee7cd21SMax Laier 	struct sockaddr_storage  orig_server_ss;
795ee7cd21SMax Laier 	struct bufferevent	*client_bufev;
805ee7cd21SMax Laier 	struct bufferevent	*server_bufev;
815ee7cd21SMax Laier 	int			 client_fd;
825ee7cd21SMax Laier 	int			 server_fd;
835ee7cd21SMax Laier 	char			 cbuf[MAX_LINE];
845ee7cd21SMax Laier 	size_t			 cbuf_valid;
855ee7cd21SMax Laier 	char			 sbuf[MAX_LINE];
865ee7cd21SMax Laier 	size_t			 sbuf_valid;
875ee7cd21SMax Laier 	int			 cmd;
885ee7cd21SMax Laier 	u_int16_t		 port;
895ee7cd21SMax Laier 	u_int16_t		 proxy_port;
905ee7cd21SMax Laier 	LIST_ENTRY(session)	 entry;
915ee7cd21SMax Laier };
9213b9f610SMax Laier 
935ee7cd21SMax Laier LIST_HEAD(, session) sessions = LIST_HEAD_INITIALIZER(sessions);
9413b9f610SMax Laier 
955ee7cd21SMax Laier void	client_error(struct bufferevent *, short, void *);
965ee7cd21SMax Laier int	client_parse(struct session *s);
975ee7cd21SMax Laier int	client_parse_anon(struct session *s);
985ee7cd21SMax Laier int	client_parse_cmd(struct session *s);
995ee7cd21SMax Laier void	client_read(struct bufferevent *, void *);
1005ee7cd21SMax Laier int	drop_privs(void);
1015ee7cd21SMax Laier void	end_session(struct session *);
102e0bfbfceSBjoern A. Zeeb void	exit_daemon(void);
103053a8868SBaptiste Daroussin int	get_line(char *, size_t *);
1045ee7cd21SMax Laier void	handle_connection(const int, short, void *);
1055ee7cd21SMax Laier void	handle_signal(int, short, void *);
1065ee7cd21SMax Laier struct session * init_session(void);
1075ee7cd21SMax Laier void	logmsg(int, const char *, ...);
1085ee7cd21SMax Laier u_int16_t parse_port(int);
1095ee7cd21SMax Laier u_int16_t pick_proxy_port(void);
1105ee7cd21SMax Laier void	proxy_reply(int, struct sockaddr *, u_int16_t);
1115ee7cd21SMax Laier void	server_error(struct bufferevent *, short, void *);
1125ee7cd21SMax Laier int	server_parse(struct session *s);
113e0bfbfceSBjoern A. Zeeb int	allow_data_connection(struct session *s);
1145ee7cd21SMax Laier void	server_read(struct bufferevent *, void *);
1155ee7cd21SMax Laier const char *sock_ntop(struct sockaddr *);
1165ee7cd21SMax Laier void	usage(void);
11713b9f610SMax Laier 
1185ee7cd21SMax Laier char linebuf[MAX_LINE + 1];
1195ee7cd21SMax Laier size_t linelen;
12013b9f610SMax Laier 
1215ee7cd21SMax Laier char ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN];
12213b9f610SMax Laier 
1235ee7cd21SMax Laier struct sockaddr_storage fixed_server_ss, fixed_proxy_ss;
124e0bfbfceSBjoern A. Zeeb const char *fixed_server, *fixed_server_port, *fixed_proxy, *listen_ip, *listen_port,
125e0bfbfceSBjoern A. Zeeb     *qname, *tagname;
1265ee7cd21SMax Laier int anonymous_only, daemonize, id_count, ipv6_mode, loglevel, max_sessions,
1275ee7cd21SMax Laier     rfc_mode, session_count, timeout, verbose;
12813b9f610SMax Laier extern char *__progname;
12913b9f610SMax Laier 
1305ee7cd21SMax Laier void
client_error(struct bufferevent * bufev __unused,short what,void * arg)131e0bfbfceSBjoern A. Zeeb client_error(struct bufferevent *bufev __unused, short what, void *arg)
13213b9f610SMax Laier {
1335ee7cd21SMax Laier 	struct session *s = arg;
1345ee7cd21SMax Laier 
1355ee7cd21SMax Laier 	if (what & EVBUFFER_EOF)
1365ee7cd21SMax Laier 		logmsg(LOG_INFO, "#%d client close", s->id);
1375ee7cd21SMax Laier 	else if (what == (EVBUFFER_ERROR | EVBUFFER_READ))
1385ee7cd21SMax Laier 		logmsg(LOG_ERR, "#%d client reset connection", s->id);
1395ee7cd21SMax Laier 	else if (what & EVBUFFER_TIMEOUT)
1405ee7cd21SMax Laier 		logmsg(LOG_ERR, "#%d client timeout", s->id);
1415ee7cd21SMax Laier 	else if (what & EVBUFFER_WRITE)
1425ee7cd21SMax Laier 		logmsg(LOG_ERR, "#%d client write error: %d", s->id, what);
1435ee7cd21SMax Laier 	else
1445ee7cd21SMax Laier 		logmsg(LOG_ERR, "#%d abnormal client error: %d", s->id, what);
1455ee7cd21SMax Laier 
1465ee7cd21SMax Laier 	end_session(s);
14713b9f610SMax Laier }
14813b9f610SMax Laier 
1495ee7cd21SMax Laier int
client_parse(struct session * s)1505ee7cd21SMax Laier client_parse(struct session *s)
15113b9f610SMax Laier {
1525ee7cd21SMax Laier 	/* Reset any previous command. */
1535ee7cd21SMax Laier 	s->cmd = CMD_NONE;
1545ee7cd21SMax Laier 	s->port = 0;
1555ee7cd21SMax Laier 
1565ee7cd21SMax Laier 	/* Commands we are looking for are at least 4 chars long. */
1575ee7cd21SMax Laier 	if (linelen < 4)
1585ee7cd21SMax Laier 		return (1);
1595ee7cd21SMax Laier 
1605ee7cd21SMax Laier 	if (linebuf[0] == 'P' || linebuf[0] == 'p' ||
161e0bfbfceSBjoern A. Zeeb 	    linebuf[0] == 'E' || linebuf[0] == 'e') {
162e0bfbfceSBjoern A. Zeeb 		if (!client_parse_cmd(s))
163e0bfbfceSBjoern A. Zeeb 			return (0);
164e0bfbfceSBjoern A. Zeeb 
165e0bfbfceSBjoern A. Zeeb 		/*
166e0bfbfceSBjoern A. Zeeb 		 * Allow active mode connections immediately, instead of
167e0bfbfceSBjoern A. Zeeb 		 * waiting for a positive reply from the server.  Some
168e0bfbfceSBjoern A. Zeeb 		 * rare servers/proxies try to probe or setup the data
169e0bfbfceSBjoern A. Zeeb 		 * connection before an actual transfer request.
170e0bfbfceSBjoern A. Zeeb 		 */
171e0bfbfceSBjoern A. Zeeb 		if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT)
172e0bfbfceSBjoern A. Zeeb 			return (allow_data_connection(s));
173e0bfbfceSBjoern A. Zeeb 	}
1745ee7cd21SMax Laier 
1755ee7cd21SMax Laier 	if (anonymous_only && (linebuf[0] == 'U' || linebuf[0] == 'u'))
1765ee7cd21SMax Laier 		return (client_parse_anon(s));
1775ee7cd21SMax Laier 
1785ee7cd21SMax Laier 	return (1);
17913b9f610SMax Laier }
18013b9f610SMax Laier 
1815ee7cd21SMax Laier int
client_parse_anon(struct session * s)1825ee7cd21SMax Laier client_parse_anon(struct session *s)
18313b9f610SMax Laier {
1845ee7cd21SMax Laier 	if (strcasecmp("USER ftp\r\n", linebuf) != 0 &&
1855ee7cd21SMax Laier 	    strcasecmp("USER anonymous\r\n", linebuf) != 0) {
1865ee7cd21SMax Laier 		snprintf(linebuf, sizeof linebuf,
1875ee7cd21SMax Laier 		    "500 Only anonymous FTP allowed\r\n");
1885ee7cd21SMax Laier 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
1895ee7cd21SMax Laier 
1905ee7cd21SMax Laier 		/* Talk back to the client ourself. */
1915ee7cd21SMax Laier 		linelen = strlen(linebuf);
1925ee7cd21SMax Laier 		bufferevent_write(s->client_bufev, linebuf, linelen);
1935ee7cd21SMax Laier 
1945ee7cd21SMax Laier 		/* Clear buffer so it's not sent to the server. */
1955ee7cd21SMax Laier 		linebuf[0] = '\0';
1965ee7cd21SMax Laier 		linelen = 0;
19713b9f610SMax Laier 	}
19813b9f610SMax Laier 
1995ee7cd21SMax Laier 	return (1);
2005ee7cd21SMax Laier }
2015ee7cd21SMax Laier 
2025ee7cd21SMax Laier int
client_parse_cmd(struct session * s)2035ee7cd21SMax Laier client_parse_cmd(struct session *s)
2045ee7cd21SMax Laier {
2055ee7cd21SMax Laier 	if (strncasecmp("PASV", linebuf, 4) == 0)
2065ee7cd21SMax Laier 		s->cmd = CMD_PASV;
2075ee7cd21SMax Laier 	else if (strncasecmp("PORT ", linebuf, 5) == 0)
2085ee7cd21SMax Laier 		s->cmd = CMD_PORT;
2095ee7cd21SMax Laier 	else if (strncasecmp("EPSV", linebuf, 4) == 0)
2105ee7cd21SMax Laier 		s->cmd = CMD_EPSV;
2115ee7cd21SMax Laier 	else if (strncasecmp("EPRT ", linebuf, 5) == 0)
2125ee7cd21SMax Laier 		s->cmd = CMD_EPRT;
2135ee7cd21SMax Laier 	else
2145ee7cd21SMax Laier 		return (1);
2155ee7cd21SMax Laier 
2165ee7cd21SMax Laier 	if (ipv6_mode && (s->cmd == CMD_PASV || s->cmd == CMD_PORT)) {
2175ee7cd21SMax Laier 		logmsg(LOG_CRIT, "PASV and PORT not allowed with IPv6");
2185ee7cd21SMax Laier 		return (0);
2195ee7cd21SMax Laier 	}
2205ee7cd21SMax Laier 
2215ee7cd21SMax Laier 	if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) {
2225ee7cd21SMax Laier 		s->port = parse_port(s->cmd);
2235ee7cd21SMax Laier 		if (s->port < MIN_PORT) {
2245ee7cd21SMax Laier 			logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id,
2255ee7cd21SMax Laier 			    linebuf);
2265ee7cd21SMax Laier 			return (0);
2275ee7cd21SMax Laier 		}
2285ee7cd21SMax Laier 		s->proxy_port = pick_proxy_port();
2295ee7cd21SMax Laier 		proxy_reply(s->cmd, sstosa(&s->proxy_ss), s->proxy_port);
2305ee7cd21SMax Laier 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
2315ee7cd21SMax Laier 	}
2325ee7cd21SMax Laier 
2335ee7cd21SMax Laier 	return (1);
2345ee7cd21SMax Laier }
2355ee7cd21SMax Laier 
2365ee7cd21SMax Laier void
client_read(struct bufferevent * bufev,void * arg)2375ee7cd21SMax Laier client_read(struct bufferevent *bufev, void *arg)
2385ee7cd21SMax Laier {
2395ee7cd21SMax Laier 	struct session	*s = arg;
240e0bfbfceSBjoern A. Zeeb 	size_t		 buf_avail, clientread;
2415ee7cd21SMax Laier 	int		 n;
2425ee7cd21SMax Laier 
2435ee7cd21SMax Laier 	do {
2445ee7cd21SMax Laier 		buf_avail = sizeof s->cbuf - s->cbuf_valid;
245e0bfbfceSBjoern A. Zeeb 		clientread = bufferevent_read(bufev, s->cbuf + s->cbuf_valid,
2465ee7cd21SMax Laier 		    buf_avail);
247e0bfbfceSBjoern A. Zeeb 		s->cbuf_valid += clientread;
2485ee7cd21SMax Laier 
249053a8868SBaptiste Daroussin 		while ((n = get_line(s->cbuf, &s->cbuf_valid)) > 0) {
2505ee7cd21SMax Laier 			logmsg(LOG_DEBUG, "#%d client: %s", s->id, linebuf);
2515ee7cd21SMax Laier 			if (!client_parse(s)) {
2525ee7cd21SMax Laier 				end_session(s);
2535ee7cd21SMax Laier 				return;
2545ee7cd21SMax Laier 			}
2555ee7cd21SMax Laier 			bufferevent_write(s->server_bufev, linebuf, linelen);
2565ee7cd21SMax Laier 		}
2575ee7cd21SMax Laier 
2585ee7cd21SMax Laier 		if (n == -1) {
2595ee7cd21SMax Laier 			logmsg(LOG_ERR, "#%d client command too long or not"
2605ee7cd21SMax Laier 			    " clean", s->id);
2615ee7cd21SMax Laier 			end_session(s);
2625ee7cd21SMax Laier 			return;
2635ee7cd21SMax Laier 		}
264e0bfbfceSBjoern A. Zeeb 	} while (clientread == buf_avail);
2655ee7cd21SMax Laier }
2665ee7cd21SMax Laier 
2675ee7cd21SMax Laier int
drop_privs(void)26813b9f610SMax Laier drop_privs(void)
26913b9f610SMax Laier {
27013b9f610SMax Laier 	struct passwd *pw;
27113b9f610SMax Laier 
2725ee7cd21SMax Laier 	pw = getpwnam(NOPRIV_USER);
2735ee7cd21SMax Laier 	if (pw == NULL)
27413b9f610SMax Laier 		return (0);
2755ee7cd21SMax Laier 
2765ee7cd21SMax Laier 	tzset();
2775ee7cd21SMax Laier 	if (chroot(CHROOT_DIR) != 0 || chdir("/") != 0 ||
2785ee7cd21SMax Laier 	    setgroups(1, &pw->pw_gid) != 0 ||
2795ee7cd21SMax Laier 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0 ||
2805ee7cd21SMax Laier 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0)
2815ee7cd21SMax Laier 		return (0);
2825ee7cd21SMax Laier 
28313b9f610SMax Laier 	return (1);
28413b9f610SMax Laier }
28513b9f610SMax Laier 
28613b9f610SMax Laier void
end_session(struct session * s)2875ee7cd21SMax Laier end_session(struct session *s)
28813b9f610SMax Laier {
289e0bfbfceSBjoern A. Zeeb 	int serr;
29013b9f610SMax Laier 
2915ee7cd21SMax Laier 	logmsg(LOG_INFO, "#%d ending session", s->id);
29213b9f610SMax Laier 
293e0bfbfceSBjoern A. Zeeb 	/* Flush output buffers. */
294e0bfbfceSBjoern A. Zeeb 	if (s->client_bufev && s->client_fd != -1)
295e0bfbfceSBjoern A. Zeeb 		evbuffer_write(s->client_bufev->output, s->client_fd);
296e0bfbfceSBjoern A. Zeeb 	if (s->server_bufev && s->server_fd != -1)
297e0bfbfceSBjoern A. Zeeb 		evbuffer_write(s->server_bufev->output, s->server_fd);
298e0bfbfceSBjoern A. Zeeb 
2995ee7cd21SMax Laier 	if (s->client_fd != -1)
3005ee7cd21SMax Laier 		close(s->client_fd);
3015ee7cd21SMax Laier 	if (s->server_fd != -1)
3025ee7cd21SMax Laier 		close(s->server_fd);
3035ee7cd21SMax Laier 
3045ee7cd21SMax Laier 	if (s->client_bufev)
3055ee7cd21SMax Laier 		bufferevent_free(s->client_bufev);
3065ee7cd21SMax Laier 	if (s->server_bufev)
3075ee7cd21SMax Laier 		bufferevent_free(s->server_bufev);
3085ee7cd21SMax Laier 
3095ee7cd21SMax Laier 	/* Remove rulesets by commiting empty ones. */
310e0bfbfceSBjoern A. Zeeb 	serr = 0;
3115ee7cd21SMax Laier 	if (prepare_commit(s->id) == -1)
312e0bfbfceSBjoern A. Zeeb 		serr = errno;
3135ee7cd21SMax Laier 	else if (do_commit() == -1) {
314e0bfbfceSBjoern A. Zeeb 		serr = errno;
3155ee7cd21SMax Laier 		do_rollback();
31613b9f610SMax Laier 	}
317e0bfbfceSBjoern A. Zeeb 	if (serr)
3185ee7cd21SMax Laier 		logmsg(LOG_ERR, "#%d pf rule removal failed: %s", s->id,
319e0bfbfceSBjoern A. Zeeb 		    strerror(serr));
3205ee7cd21SMax Laier 
3215ee7cd21SMax Laier 	LIST_REMOVE(s, entry);
3225ee7cd21SMax Laier 	free(s);
3235ee7cd21SMax Laier 	session_count--;
32413b9f610SMax Laier }
32513b9f610SMax Laier 
326e0bfbfceSBjoern A. Zeeb void
exit_daemon(void)3275ee7cd21SMax Laier exit_daemon(void)
32813b9f610SMax Laier {
3295ee7cd21SMax Laier 	struct session *s, *next;
33013b9f610SMax Laier 
3315ee7cd21SMax Laier 	for (s = LIST_FIRST(&sessions); s != LIST_END(&sessions); s = next) {
3325ee7cd21SMax Laier 		next = LIST_NEXT(s, entry);
3335ee7cd21SMax Laier 		end_session(s);
33413b9f610SMax Laier 	}
33513b9f610SMax Laier 
3365ee7cd21SMax Laier 	if (daemonize)
3375ee7cd21SMax Laier 		closelog();
33813b9f610SMax Laier 
3395ee7cd21SMax Laier 	exit(0);
34013b9f610SMax Laier }
34113b9f610SMax Laier 
3425ee7cd21SMax Laier int
get_line(char * buf,size_t * valid)343053a8868SBaptiste Daroussin get_line(char *buf, size_t *valid)
3445ee7cd21SMax Laier {
3455ee7cd21SMax Laier 	size_t i;
3465ee7cd21SMax Laier 
3475ee7cd21SMax Laier 	if (*valid > MAX_LINE)
3485ee7cd21SMax Laier 		return (-1);
3495ee7cd21SMax Laier 
3505ee7cd21SMax Laier 	/* Copy to linebuf while searching for a newline. */
3515ee7cd21SMax Laier 	for (i = 0; i < *valid; i++) {
3525ee7cd21SMax Laier 		linebuf[i] = buf[i];
3535ee7cd21SMax Laier 		if (buf[i] == '\0')
3545ee7cd21SMax Laier 			return (-1);
3555ee7cd21SMax Laier 		if (buf[i] == '\n')
3565ee7cd21SMax Laier 			break;
35713b9f610SMax Laier 	}
3585ee7cd21SMax Laier 
3595ee7cd21SMax Laier 	if (i == *valid) {
3605ee7cd21SMax Laier 		/* No newline found. */
3615ee7cd21SMax Laier 		linebuf[0] = '\0';
3625ee7cd21SMax Laier 		linelen = 0;
3635ee7cd21SMax Laier 		if (i < MAX_LINE)
36413b9f610SMax Laier 			return (0);
3655ee7cd21SMax Laier 		return (-1);
36613b9f610SMax Laier 	}
36713b9f610SMax Laier 
3685ee7cd21SMax Laier 	linelen = i + 1;
3695ee7cd21SMax Laier 	linebuf[linelen] = '\0';
3705ee7cd21SMax Laier 	*valid -= linelen;
37113b9f610SMax Laier 
3725ee7cd21SMax Laier 	/* Move leftovers to the start. */
3735ee7cd21SMax Laier 	if (*valid != 0)
3745ee7cd21SMax Laier 		bcopy(buf + linelen, buf, *valid);
37513b9f610SMax Laier 
3765ee7cd21SMax Laier 	return ((int)linelen);
37713b9f610SMax Laier }
37813b9f610SMax Laier 
37913b9f610SMax Laier void
handle_connection(const int listen_fd,short event __unused,void * ev __unused)380e0bfbfceSBjoern A. Zeeb handle_connection(const int listen_fd, short event __unused, void *ev __unused)
38113b9f610SMax Laier {
3825ee7cd21SMax Laier 	struct sockaddr_storage tmp_ss;
3835ee7cd21SMax Laier 	struct sockaddr *client_sa, *server_sa, *fixed_server_sa;
3845ee7cd21SMax Laier 	struct sockaddr *client_to_proxy_sa, *proxy_to_server_sa;
3855ee7cd21SMax Laier 	struct session *s;
3865ee7cd21SMax Laier 	socklen_t len;
3875ee7cd21SMax Laier 	int client_fd, fc, on;
38813b9f610SMax Laier 
38913b9f610SMax Laier 	/*
3905ee7cd21SMax Laier 	 * We _must_ accept the connection, otherwise libevent will keep
3915ee7cd21SMax Laier 	 * coming back, and we will chew up all CPU.
39213b9f610SMax Laier 	 */
3935ee7cd21SMax Laier 	client_sa = sstosa(&tmp_ss);
3945ee7cd21SMax Laier 	len = sizeof(struct sockaddr_storage);
3955ee7cd21SMax Laier 	if ((client_fd = accept(listen_fd, client_sa, &len)) < 0) {
3965ee7cd21SMax Laier 		logmsg(LOG_CRIT, "accept failed: %s", strerror(errno));
3975ee7cd21SMax Laier 		return;
39813b9f610SMax Laier 	}
39913b9f610SMax Laier 
4005ee7cd21SMax Laier 	/* Refuse connection if the maximum is reached. */
4015ee7cd21SMax Laier 	if (session_count >= max_sessions) {
4025ee7cd21SMax Laier 		logmsg(LOG_ERR, "client limit (%d) reached, refusing "
4035ee7cd21SMax Laier 		    "connection from %s", max_sessions, sock_ntop(client_sa));
4045ee7cd21SMax Laier 		close(client_fd);
4055ee7cd21SMax Laier 		return;
4065ee7cd21SMax Laier 	}
40713b9f610SMax Laier 
4085ee7cd21SMax Laier 	/* Allocate session and copy back the info from the accept(). */
4095ee7cd21SMax Laier 	s = init_session();
4105ee7cd21SMax Laier 	if (s == NULL) {
4115ee7cd21SMax Laier 		logmsg(LOG_CRIT, "init_session failed");
4125ee7cd21SMax Laier 		close(client_fd);
4135ee7cd21SMax Laier 		return;
4145ee7cd21SMax Laier 	}
4155ee7cd21SMax Laier 	s->client_fd = client_fd;
4165ee7cd21SMax Laier 	memcpy(sstosa(&s->client_ss), client_sa, client_sa->sa_len);
41713b9f610SMax Laier 
4185ee7cd21SMax Laier 	/* Cast it once, and be done with it. */
4195ee7cd21SMax Laier 	client_sa = sstosa(&s->client_ss);
4205ee7cd21SMax Laier 	server_sa = sstosa(&s->server_ss);
4215ee7cd21SMax Laier 	client_to_proxy_sa = sstosa(&tmp_ss);
4225ee7cd21SMax Laier 	proxy_to_server_sa = sstosa(&s->proxy_ss);
4235ee7cd21SMax Laier 	fixed_server_sa = sstosa(&fixed_server_ss);
4245ee7cd21SMax Laier 
4255ee7cd21SMax Laier 	/* Log id/client early to ease debugging. */
4265ee7cd21SMax Laier 	logmsg(LOG_DEBUG, "#%d accepted connection from %s", s->id,
4275ee7cd21SMax Laier 	    sock_ntop(client_sa));
42813b9f610SMax Laier 
42913b9f610SMax Laier 	/*
4305ee7cd21SMax Laier 	 * Find out the real server and port that the client wanted.
43113b9f610SMax Laier 	 */
4325ee7cd21SMax Laier 	len = sizeof(struct sockaddr_storage);
4335ee7cd21SMax Laier 	if ((getsockname(s->client_fd, client_to_proxy_sa, &len)) < 0) {
4345ee7cd21SMax Laier 		logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id,
4355ee7cd21SMax Laier 		    strerror(errno));
4365ee7cd21SMax Laier 		goto fail;
43713b9f610SMax Laier 	}
4385ee7cd21SMax Laier 	if (server_lookup(client_sa, client_to_proxy_sa, server_sa) != 0) {
4395ee7cd21SMax Laier 	    	logmsg(LOG_CRIT, "#%d server lookup failed (no rdr?)", s->id);
4405ee7cd21SMax Laier 		goto fail;
4415ee7cd21SMax Laier 	}
4425ee7cd21SMax Laier 	if (fixed_server) {
4435ee7cd21SMax Laier 		memcpy(sstosa(&s->orig_server_ss), server_sa,
4445ee7cd21SMax Laier 		    server_sa->sa_len);
4455ee7cd21SMax Laier 		memcpy(server_sa, fixed_server_sa, fixed_server_sa->sa_len);
4465ee7cd21SMax Laier 	}
4475ee7cd21SMax Laier 
4485ee7cd21SMax Laier 	/* XXX: check we are not connecting to ourself. */
44913b9f610SMax Laier 
45013b9f610SMax Laier 	/*
4515ee7cd21SMax Laier 	 * Setup socket and connect to server.
45213b9f610SMax Laier 	 */
4535ee7cd21SMax Laier 	if ((s->server_fd = socket(server_sa->sa_family, SOCK_STREAM,
4545ee7cd21SMax Laier 	    IPPROTO_TCP)) < 0) {
4555ee7cd21SMax Laier 		logmsg(LOG_CRIT, "#%d server socket failed: %s", s->id,
4565ee7cd21SMax Laier 		    strerror(errno));
4575ee7cd21SMax Laier 		goto fail;
4585ee7cd21SMax Laier 	}
4595ee7cd21SMax Laier 	if (fixed_proxy && bind(s->server_fd, sstosa(&fixed_proxy_ss),
4605ee7cd21SMax Laier 	    fixed_proxy_ss.ss_len) != 0) {
4615ee7cd21SMax Laier 		logmsg(LOG_CRIT, "#%d cannot bind fixed proxy address: %s",
4625ee7cd21SMax Laier 		    s->id, strerror(errno));
4635ee7cd21SMax Laier 		goto fail;
46413b9f610SMax Laier 	}
46513b9f610SMax Laier 
4665ee7cd21SMax Laier 	/* Use non-blocking connect(), see CONNECT_TIMEOUT below. */
4675ee7cd21SMax Laier 	if ((fc = fcntl(s->server_fd, F_GETFL)) == -1 ||
4685ee7cd21SMax Laier 	    fcntl(s->server_fd, F_SETFL, fc | O_NONBLOCK) == -1) {
4695ee7cd21SMax Laier 		logmsg(LOG_CRIT, "#%d cannot mark socket non-blocking: %s",
4705ee7cd21SMax Laier 		    s->id, strerror(errno));
4715ee7cd21SMax Laier 		goto fail;
47213b9f610SMax Laier 	}
4735ee7cd21SMax Laier 	if (connect(s->server_fd, server_sa, server_sa->sa_len) < 0 &&
4745ee7cd21SMax Laier 	    errno != EINPROGRESS) {
4755ee7cd21SMax Laier 		logmsg(LOG_CRIT, "#%d proxy cannot connect to server %s: %s",
4765ee7cd21SMax Laier 		    s->id, sock_ntop(server_sa), strerror(errno));
4775ee7cd21SMax Laier 		goto fail;
47813b9f610SMax Laier 	}
47913b9f610SMax Laier 
4805ee7cd21SMax Laier 	len = sizeof(struct sockaddr_storage);
4815ee7cd21SMax Laier 	if ((getsockname(s->server_fd, proxy_to_server_sa, &len)) < 0) {
4825ee7cd21SMax Laier 		logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id,
4835ee7cd21SMax Laier 		    strerror(errno));
4845ee7cd21SMax Laier 		goto fail;
4855ee7cd21SMax Laier 	}
48613b9f610SMax Laier 
4875ee7cd21SMax Laier 	logmsg(LOG_INFO, "#%d FTP session %d/%d started: client %s to server "
4885ee7cd21SMax Laier 	    "%s via proxy %s ", s->id, session_count, max_sessions,
4895ee7cd21SMax Laier 	    sock_ntop(client_sa), sock_ntop(server_sa),
4905ee7cd21SMax Laier 	    sock_ntop(proxy_to_server_sa));
4915ee7cd21SMax Laier 
4925ee7cd21SMax Laier 	/* Keepalive is nice, but don't care if it fails. */
4935ee7cd21SMax Laier 	on = 1;
4945ee7cd21SMax Laier 	setsockopt(s->client_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
4955ee7cd21SMax Laier 	    sizeof on);
4965ee7cd21SMax Laier 	setsockopt(s->server_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
4975ee7cd21SMax Laier 	    sizeof on);
49813b9f610SMax Laier 
49913b9f610SMax Laier 	/*
5005ee7cd21SMax Laier 	 * Setup buffered events.
50113b9f610SMax Laier 	 */
5025ee7cd21SMax Laier 	s->client_bufev = bufferevent_new(s->client_fd, &client_read, NULL,
5035ee7cd21SMax Laier 	    &client_error, s);
5045ee7cd21SMax Laier 	if (s->client_bufev == NULL) {
5055ee7cd21SMax Laier 		logmsg(LOG_CRIT, "#%d bufferevent_new client failed", s->id);
5065ee7cd21SMax Laier 		goto fail;
50713b9f610SMax Laier 	}
5085ee7cd21SMax Laier 	bufferevent_settimeout(s->client_bufev, timeout, 0);
5095ee7cd21SMax Laier 	bufferevent_enable(s->client_bufev, EV_READ | EV_TIMEOUT);
5105ee7cd21SMax Laier 
5115ee7cd21SMax Laier 	s->server_bufev = bufferevent_new(s->server_fd, &server_read, NULL,
5125ee7cd21SMax Laier 	    &server_error, s);
5135ee7cd21SMax Laier 	if (s->server_bufev == NULL) {
5145ee7cd21SMax Laier 		logmsg(LOG_CRIT, "#%d bufferevent_new server failed", s->id);
5155ee7cd21SMax Laier 		goto fail;
5165ee7cd21SMax Laier 	}
5175ee7cd21SMax Laier 	bufferevent_settimeout(s->server_bufev, CONNECT_TIMEOUT, 0);
5185ee7cd21SMax Laier 	bufferevent_enable(s->server_bufev, EV_READ | EV_TIMEOUT);
5195ee7cd21SMax Laier 
5205ee7cd21SMax Laier 	return;
5215ee7cd21SMax Laier 
5225ee7cd21SMax Laier  fail:
5235ee7cd21SMax Laier 	end_session(s);
52413b9f610SMax Laier }
52513b9f610SMax Laier 
52613b9f610SMax Laier void
handle_signal(int sig,short event __unused,void * arg __unused)527e0bfbfceSBjoern A. Zeeb handle_signal(int sig, short event __unused, void *arg __unused)
52813b9f610SMax Laier {
52913b9f610SMax Laier 	/*
5305ee7cd21SMax Laier 	 * Signal handler rules don't apply, libevent decouples for us.
53113b9f610SMax Laier 	 */
5325ee7cd21SMax Laier 
533e0bfbfceSBjoern A. Zeeb 	logmsg(LOG_ERR, "exiting on signal %d", sig);
5345ee7cd21SMax Laier 
5355ee7cd21SMax Laier 	exit_daemon();
53613b9f610SMax Laier }
53713b9f610SMax Laier 
53813b9f610SMax Laier 
5395ee7cd21SMax Laier struct session *
init_session(void)5405ee7cd21SMax Laier init_session(void)
5415ee7cd21SMax Laier {
5425ee7cd21SMax Laier 	struct session *s;
54313b9f610SMax Laier 
5445ee7cd21SMax Laier 	s = calloc(1, sizeof(struct session));
5455ee7cd21SMax Laier 	if (s == NULL)
5465ee7cd21SMax Laier 		return (NULL);
54713b9f610SMax Laier 
5485ee7cd21SMax Laier 	s->id = id_count++;
5495ee7cd21SMax Laier 	s->client_fd = -1;
5505ee7cd21SMax Laier 	s->server_fd = -1;
5515ee7cd21SMax Laier 	s->cbuf[0] = '\0';
5525ee7cd21SMax Laier 	s->cbuf_valid = 0;
5535ee7cd21SMax Laier 	s->sbuf[0] = '\0';
5545ee7cd21SMax Laier 	s->sbuf_valid = 0;
5555ee7cd21SMax Laier 	s->client_bufev = NULL;
5565ee7cd21SMax Laier 	s->server_bufev = NULL;
5575ee7cd21SMax Laier 	s->cmd = CMD_NONE;
5585ee7cd21SMax Laier 	s->port = 0;
55913b9f610SMax Laier 
5605ee7cd21SMax Laier 	LIST_INSERT_HEAD(&sessions, s, entry);
5615ee7cd21SMax Laier 	session_count++;
5625ee7cd21SMax Laier 
5635ee7cd21SMax Laier 	return (s);
56413b9f610SMax Laier }
56513b9f610SMax Laier 
5665ee7cd21SMax Laier void
logmsg(int pri,const char * message,...)5675ee7cd21SMax Laier logmsg(int pri, const char *message, ...)
5685ee7cd21SMax Laier {
5695ee7cd21SMax Laier 	va_list	ap;
57013b9f610SMax Laier 
5715ee7cd21SMax Laier 	if (pri > loglevel)
5725ee7cd21SMax Laier 		return;
57313b9f610SMax Laier 
5745ee7cd21SMax Laier 	va_start(ap, message);
57513b9f610SMax Laier 
5765ee7cd21SMax Laier 	if (daemonize)
5775ee7cd21SMax Laier 		/* syslog does its own vissing. */
5785ee7cd21SMax Laier 		vsyslog(pri, message, ap);
5795ee7cd21SMax Laier 	else {
5805ee7cd21SMax Laier 		char buf[MAX_LOGLINE];
5815ee7cd21SMax Laier 		char visbuf[2 * MAX_LOGLINE];
58213b9f610SMax Laier 
5835ee7cd21SMax Laier 		/* We don't care about truncation. */
5845ee7cd21SMax Laier 		vsnprintf(buf, sizeof buf, message, ap);
5855ee7cd21SMax Laier #ifdef __FreeBSD__
586e0bfbfceSBjoern A. Zeeb 		strvis(visbuf, buf, VIS_CSTYLE | VIS_NL);
5875ee7cd21SMax Laier #else
5885ee7cd21SMax Laier 		strnvis(visbuf, buf, sizeof visbuf, VIS_CSTYLE | VIS_NL);
5895ee7cd21SMax Laier #endif
5905ee7cd21SMax Laier 		fprintf(stderr, "%s\n", visbuf);
59113b9f610SMax Laier 	}
59213b9f610SMax Laier 
5935ee7cd21SMax Laier 	va_end(ap);
59413b9f610SMax Laier }
59513b9f610SMax Laier 
59613b9f610SMax Laier int
main(int argc,char * argv[])59713b9f610SMax Laier main(int argc, char *argv[])
59813b9f610SMax Laier {
5995ee7cd21SMax Laier 	struct rlimit rlp;
6005ee7cd21SMax Laier 	struct addrinfo hints, *res;
6015ee7cd21SMax Laier 	struct event ev, ev_sighup, ev_sigint, ev_sigterm;
6025ee7cd21SMax Laier 	int ch, error, listenfd, on;
6035ee7cd21SMax Laier 	const char *errstr;
60413b9f610SMax Laier 
6055ee7cd21SMax Laier 	/* Defaults. */
6065ee7cd21SMax Laier 	anonymous_only	= 0;
6075ee7cd21SMax Laier 	daemonize	= 1;
6085ee7cd21SMax Laier 	fixed_proxy	= NULL;
6095ee7cd21SMax Laier 	fixed_server	= NULL;
6105ee7cd21SMax Laier 	fixed_server_port = "21";
6115ee7cd21SMax Laier 	ipv6_mode	= 0;
6125ee7cd21SMax Laier 	listen_ip	= NULL;
6135ee7cd21SMax Laier 	listen_port	= "8021";
6145ee7cd21SMax Laier 	loglevel	= LOG_NOTICE;
6155ee7cd21SMax Laier 	max_sessions	= 100;
6165ee7cd21SMax Laier 	qname		= NULL;
6175ee7cd21SMax Laier 	rfc_mode	= 0;
618e0bfbfceSBjoern A. Zeeb 	tagname		= NULL;
6195ee7cd21SMax Laier 	timeout		= 24 * 3600;
6205ee7cd21SMax Laier 	verbose		= 0;
6215ee7cd21SMax Laier 
6225ee7cd21SMax Laier 	/* Other initialization. */
6235ee7cd21SMax Laier 	id_count	= 1;
6245ee7cd21SMax Laier 	session_count	= 0;
6255ee7cd21SMax Laier 
626e0bfbfceSBjoern A. Zeeb 	while ((ch = getopt(argc, argv, "6Aa:b:D:dm:P:p:q:R:rT:t:v")) != -1) {
62713b9f610SMax Laier 		switch (ch) {
6285ee7cd21SMax Laier 		case '6':
6295ee7cd21SMax Laier 			ipv6_mode = 1;
63022ac3eadSMax Laier 			break;
63113b9f610SMax Laier 		case 'A':
6325ee7cd21SMax Laier 			anonymous_only = 1;
6335ee7cd21SMax Laier 			break;
6345ee7cd21SMax Laier 		case 'a':
6355ee7cd21SMax Laier 			fixed_proxy = optarg;
6365ee7cd21SMax Laier 			break;
6375ee7cd21SMax Laier 		case 'b':
6385ee7cd21SMax Laier 			listen_ip = optarg;
63913b9f610SMax Laier 			break;
64013b9f610SMax Laier 		case 'D':
6415ee7cd21SMax Laier 			loglevel = strtonum(optarg, LOG_EMERG, LOG_DEBUG,
6425ee7cd21SMax Laier 			    &errstr);
6435ee7cd21SMax Laier 			if (errstr)
6445ee7cd21SMax Laier 				errx(1, "loglevel %s", errstr);
64513b9f610SMax Laier 			break;
6465ee7cd21SMax Laier 		case 'd':
6475ee7cd21SMax Laier 			daemonize = 0;
64813b9f610SMax Laier 			break;
64913b9f610SMax Laier 		case 'm':
6505ee7cd21SMax Laier 			max_sessions = strtonum(optarg, 1, 500, &errstr);
6515ee7cd21SMax Laier 			if (errstr)
6525ee7cd21SMax Laier 				errx(1, "max sessions %s", errstr);
65313b9f610SMax Laier 			break;
6545ee7cd21SMax Laier 		case 'P':
6555ee7cd21SMax Laier 			fixed_server_port = optarg;
65613b9f610SMax Laier 			break;
6575ee7cd21SMax Laier 		case 'p':
6585ee7cd21SMax Laier 			listen_port = optarg;
6595ee7cd21SMax Laier 			break;
6605ee7cd21SMax Laier 		case 'q':
6615ee7cd21SMax Laier 			if (strlen(optarg) >= PF_QNAME_SIZE)
6625ee7cd21SMax Laier 				errx(1, "queuename too long");
6635ee7cd21SMax Laier 			qname = optarg;
6645ee7cd21SMax Laier 			break;
6655ee7cd21SMax Laier 		case 'R':
6665ee7cd21SMax Laier 			fixed_server = optarg;
66713b9f610SMax Laier 			break;
66813b9f610SMax Laier 		case 'r':
6695ee7cd21SMax Laier 			rfc_mode = 1;
6700baf7c86SMax Laier 			break;
671e0bfbfceSBjoern A. Zeeb 		case 'T':
672e0bfbfceSBjoern A. Zeeb 			if (strlen(optarg) >= PF_TAG_NAME_SIZE)
673e0bfbfceSBjoern A. Zeeb 				errx(1, "tagname too long");
674e0bfbfceSBjoern A. Zeeb 			tagname = optarg;
675e0bfbfceSBjoern A. Zeeb 			break;
67613b9f610SMax Laier 		case 't':
6775ee7cd21SMax Laier 			timeout = strtonum(optarg, 0, 86400, &errstr);
6785ee7cd21SMax Laier 			if (errstr)
6795ee7cd21SMax Laier 				errx(1, "timeout %s", errstr);
6805ee7cd21SMax Laier 			break;
6815ee7cd21SMax Laier 		case 'v':
6825ee7cd21SMax Laier 			verbose++;
6835ee7cd21SMax Laier 			if (verbose > 2)
68413b9f610SMax Laier 				usage();
68513b9f610SMax Laier 			break;
68613b9f610SMax Laier 		default:
68713b9f610SMax Laier 			usage();
6885ee7cd21SMax Laier 		}
6895ee7cd21SMax Laier 	}
6905ee7cd21SMax Laier 
6915ee7cd21SMax Laier 	if (listen_ip == NULL)
6925ee7cd21SMax Laier 		listen_ip = ipv6_mode ? "::1" : "127.0.0.1";
6935ee7cd21SMax Laier 
6945ee7cd21SMax Laier 	/* Check for root to save the user from cryptic failure messages. */
6955ee7cd21SMax Laier 	if (getuid() != 0)
6965ee7cd21SMax Laier 		errx(1, "needs to start as root");
6975ee7cd21SMax Laier 
6985ee7cd21SMax Laier 	/* Raise max. open files limit to satisfy max. sessions. */
6995ee7cd21SMax Laier 	rlp.rlim_cur = rlp.rlim_max = (2 * max_sessions) + 10;
7005ee7cd21SMax Laier 	if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
7015ee7cd21SMax Laier 		err(1, "setrlimit");
7025ee7cd21SMax Laier 
7035ee7cd21SMax Laier 	if (fixed_proxy) {
7045ee7cd21SMax Laier 		memset(&hints, 0, sizeof hints);
7055ee7cd21SMax Laier 		hints.ai_flags = AI_NUMERICHOST;
7065ee7cd21SMax Laier 		hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
7075ee7cd21SMax Laier 		hints.ai_socktype = SOCK_STREAM;
7085ee7cd21SMax Laier 		error = getaddrinfo(fixed_proxy, NULL, &hints, &res);
7095ee7cd21SMax Laier 		if (error)
7105ee7cd21SMax Laier 			errx(1, "getaddrinfo fixed proxy address failed: %s",
7115ee7cd21SMax Laier 			    gai_strerror(error));
7125ee7cd21SMax Laier 		memcpy(&fixed_proxy_ss, res->ai_addr, res->ai_addrlen);
7135ee7cd21SMax Laier 		logmsg(LOG_INFO, "using %s to connect to servers",
7145ee7cd21SMax Laier 		    sock_ntop(sstosa(&fixed_proxy_ss)));
7155ee7cd21SMax Laier 		freeaddrinfo(res);
7165ee7cd21SMax Laier 	}
7175ee7cd21SMax Laier 
7185ee7cd21SMax Laier 	if (fixed_server) {
7195ee7cd21SMax Laier 		memset(&hints, 0, sizeof hints);
7205ee7cd21SMax Laier 		hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
7215ee7cd21SMax Laier 		hints.ai_socktype = SOCK_STREAM;
7225ee7cd21SMax Laier 		error = getaddrinfo(fixed_server, fixed_server_port, &hints,
7235ee7cd21SMax Laier 		    &res);
7245ee7cd21SMax Laier 		if (error)
7255ee7cd21SMax Laier 			errx(1, "getaddrinfo fixed server address failed: %s",
7265ee7cd21SMax Laier 			    gai_strerror(error));
7275ee7cd21SMax Laier 		memcpy(&fixed_server_ss, res->ai_addr, res->ai_addrlen);
7285ee7cd21SMax Laier 		logmsg(LOG_INFO, "using fixed server %s",
7295ee7cd21SMax Laier 		    sock_ntop(sstosa(&fixed_server_ss)));
7305ee7cd21SMax Laier 		freeaddrinfo(res);
7315ee7cd21SMax Laier 	}
7325ee7cd21SMax Laier 
7335ee7cd21SMax Laier 	/* Setup listener. */
7345ee7cd21SMax Laier 	memset(&hints, 0, sizeof hints);
7355ee7cd21SMax Laier 	hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
7365ee7cd21SMax Laier 	hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET;
7375ee7cd21SMax Laier 	hints.ai_socktype = SOCK_STREAM;
7385ee7cd21SMax Laier 	error = getaddrinfo(listen_ip, listen_port, &hints, &res);
7395ee7cd21SMax Laier 	if (error)
7405ee7cd21SMax Laier 		errx(1, "getaddrinfo listen address failed: %s",
7415ee7cd21SMax Laier 		    gai_strerror(error));
7425ee7cd21SMax Laier 	if ((listenfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
7435ee7cd21SMax Laier 		errx(1, "socket failed");
7445ee7cd21SMax Laier 	on = 1;
7455ee7cd21SMax Laier 	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on,
7465ee7cd21SMax Laier 	    sizeof on) != 0)
7475ee7cd21SMax Laier 		err(1, "setsockopt failed");
7485ee7cd21SMax Laier 	if (bind(listenfd, (struct sockaddr *)res->ai_addr,
7495ee7cd21SMax Laier 	    (socklen_t)res->ai_addrlen) != 0)
7505ee7cd21SMax Laier 	    	err(1, "bind failed");
7515ee7cd21SMax Laier 	if (listen(listenfd, TCP_BACKLOG) != 0)
7525ee7cd21SMax Laier 		err(1, "listen failed");
7535ee7cd21SMax Laier 	freeaddrinfo(res);
7545ee7cd21SMax Laier 
7555ee7cd21SMax Laier 	/* Initialize pf. */
756e0bfbfceSBjoern A. Zeeb 	init_filter(qname, tagname, verbose);
7575ee7cd21SMax Laier 
7585ee7cd21SMax Laier 	if (daemonize) {
7595ee7cd21SMax Laier 		if (daemon(0, 0) == -1)
7605ee7cd21SMax Laier 			err(1, "cannot daemonize");
7615ee7cd21SMax Laier 		openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
7625ee7cd21SMax Laier 	}
7635ee7cd21SMax Laier 
7645ee7cd21SMax Laier 	/* Use logmsg for output from here on. */
7655ee7cd21SMax Laier 
7665ee7cd21SMax Laier 	if (!drop_privs()) {
7675ee7cd21SMax Laier 		logmsg(LOG_ERR, "cannot drop privileges: %s", strerror(errno));
7685ee7cd21SMax Laier 		exit(1);
7695ee7cd21SMax Laier 	}
7705ee7cd21SMax Laier 
7715ee7cd21SMax Laier 	event_init();
7725ee7cd21SMax Laier 
7735ee7cd21SMax Laier 	/* Setup signal handler. */
7745ee7cd21SMax Laier 	signal(SIGPIPE, SIG_IGN);
7755ee7cd21SMax Laier 	signal_set(&ev_sighup, SIGHUP, handle_signal, NULL);
7765ee7cd21SMax Laier 	signal_set(&ev_sigint, SIGINT, handle_signal, NULL);
7775ee7cd21SMax Laier 	signal_set(&ev_sigterm, SIGTERM, handle_signal, NULL);
7785ee7cd21SMax Laier 	signal_add(&ev_sighup, NULL);
7795ee7cd21SMax Laier 	signal_add(&ev_sigint, NULL);
7805ee7cd21SMax Laier 	signal_add(&ev_sigterm, NULL);
7815ee7cd21SMax Laier 
7825ee7cd21SMax Laier 	event_set(&ev, listenfd, EV_READ | EV_PERSIST, handle_connection, &ev);
7835ee7cd21SMax Laier 	event_add(&ev, NULL);
7845ee7cd21SMax Laier 
7855ee7cd21SMax Laier 	logmsg(LOG_NOTICE, "listening on %s port %s", listen_ip, listen_port);
7865ee7cd21SMax Laier 
7875ee7cd21SMax Laier 	/*  Vroom, vroom.  */
7885ee7cd21SMax Laier 	event_dispatch();
7895ee7cd21SMax Laier 
7905ee7cd21SMax Laier 	logmsg(LOG_ERR, "event_dispatch error: %s", strerror(errno));
7915ee7cd21SMax Laier 	exit_daemon();
7925ee7cd21SMax Laier 
79313b9f610SMax Laier 	/* NOTREACHED */
7945ee7cd21SMax Laier 	return (1);
7955ee7cd21SMax Laier }
7965ee7cd21SMax Laier 
7975ee7cd21SMax Laier u_int16_t
parse_port(int mode)7985ee7cd21SMax Laier parse_port(int mode)
7995ee7cd21SMax Laier {
8005ee7cd21SMax Laier 	unsigned int	 port, v[6];
8015ee7cd21SMax Laier 	int		 n;
8025ee7cd21SMax Laier 	char		*p;
8035ee7cd21SMax Laier 
8045ee7cd21SMax Laier 	/* Find the last space or left-parenthesis. */
8055ee7cd21SMax Laier 	for (p = linebuf + linelen; p > linebuf; p--)
8065ee7cd21SMax Laier 		if (*p == ' ' || *p == '(')
8075ee7cd21SMax Laier 			break;
8085ee7cd21SMax Laier 	if (p == linebuf)
8095ee7cd21SMax Laier 		return (0);
8105ee7cd21SMax Laier 
8115ee7cd21SMax Laier 	switch (mode) {
8125ee7cd21SMax Laier 	case CMD_PORT:
8135ee7cd21SMax Laier 		n = sscanf(p, " %u,%u,%u,%u,%u,%u", &v[0], &v[1], &v[2],
8145ee7cd21SMax Laier 		    &v[3], &v[4], &v[5]);
8155ee7cd21SMax Laier 		if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8165ee7cd21SMax Laier 		    v[3] < 256 && v[4] < 256 && v[5] < 256)
8175ee7cd21SMax Laier 			return ((v[4] << 8) | v[5]);
8185ee7cd21SMax Laier 		break;
8195ee7cd21SMax Laier 	case CMD_PASV:
8205ee7cd21SMax Laier 		n = sscanf(p, "(%u,%u,%u,%u,%u,%u)", &v[0], &v[1], &v[2],
8215ee7cd21SMax Laier 		    &v[3], &v[4], &v[5]);
8225ee7cd21SMax Laier 		if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8235ee7cd21SMax Laier 		    v[3] < 256 && v[4] < 256 && v[5] < 256)
8245ee7cd21SMax Laier 			return ((v[4] << 8) | v[5]);
8255ee7cd21SMax Laier 		break;
8265ee7cd21SMax Laier 	case CMD_EPSV:
8275ee7cd21SMax Laier 		n = sscanf(p, "(|||%u|)", &port);
8285ee7cd21SMax Laier 		if (n == 1 && port < 65536)
8295ee7cd21SMax Laier 			return (port);
8305ee7cd21SMax Laier 		break;
8315ee7cd21SMax Laier 	case CMD_EPRT:
8325ee7cd21SMax Laier 		n = sscanf(p, " |1|%u.%u.%u.%u|%u|", &v[0], &v[1], &v[2],
8335ee7cd21SMax Laier 		    &v[3], &port);
8345ee7cd21SMax Laier 		if (n == 5 && v[0] < 256 && v[1] < 256 && v[2] < 256 &&
8355ee7cd21SMax Laier 		    v[3] < 256 && port < 65536)
8365ee7cd21SMax Laier 			return (port);
8375ee7cd21SMax Laier 		n = sscanf(p, " |2|%*[a-fA-F0-9:]|%u|", &port);
8385ee7cd21SMax Laier 		if (n == 1 && port < 65536)
8395ee7cd21SMax Laier 			return (port);
8405ee7cd21SMax Laier 		break;
8415ee7cd21SMax Laier 	default:
8425ee7cd21SMax Laier 		return (0);
8435ee7cd21SMax Laier 	}
8445ee7cd21SMax Laier 
8455ee7cd21SMax Laier 	return (0);
8465ee7cd21SMax Laier }
8475ee7cd21SMax Laier 
8485ee7cd21SMax Laier u_int16_t
pick_proxy_port(void)8495ee7cd21SMax Laier pick_proxy_port(void)
8505ee7cd21SMax Laier {
8515ee7cd21SMax Laier 	/* Random should be good enough for avoiding port collisions. */
852e0bfbfceSBjoern A. Zeeb 	return (IPPORT_HIFIRSTAUTO +
853e0bfbfceSBjoern A. Zeeb 	    arc4random_uniform(IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO));
8545ee7cd21SMax Laier }
8555ee7cd21SMax Laier 
8565ee7cd21SMax Laier void
proxy_reply(int cmd,struct sockaddr * sa,u_int16_t port)8575ee7cd21SMax Laier proxy_reply(int cmd, struct sockaddr *sa, u_int16_t port)
8585ee7cd21SMax Laier {
859e0bfbfceSBjoern A. Zeeb 	u_int i;
860e0bfbfceSBjoern A. Zeeb 	int r = 0;
8615ee7cd21SMax Laier 
8625ee7cd21SMax Laier 	switch (cmd) {
8635ee7cd21SMax Laier 	case CMD_PORT:
8645ee7cd21SMax Laier 		r = snprintf(linebuf, sizeof linebuf,
8655ee7cd21SMax Laier 		    "PORT %s,%u,%u\r\n", sock_ntop(sa), port / 256,
8665ee7cd21SMax Laier 		    port % 256);
8675ee7cd21SMax Laier 		break;
8685ee7cd21SMax Laier 	case CMD_PASV:
8695ee7cd21SMax Laier 		r = snprintf(linebuf, sizeof linebuf,
8705ee7cd21SMax Laier 		    "227 Entering Passive Mode (%s,%u,%u)\r\n", sock_ntop(sa),
8715ee7cd21SMax Laier 		        port / 256, port % 256);
8725ee7cd21SMax Laier 		break;
8735ee7cd21SMax Laier 	case CMD_EPRT:
8745ee7cd21SMax Laier 		if (sa->sa_family == AF_INET)
8755ee7cd21SMax Laier 			r = snprintf(linebuf, sizeof linebuf,
8765ee7cd21SMax Laier 			    "EPRT |1|%s|%u|\r\n", sock_ntop(sa), port);
8775ee7cd21SMax Laier 		else if (sa->sa_family == AF_INET6)
8785ee7cd21SMax Laier 			r = snprintf(linebuf, sizeof linebuf,
8795ee7cd21SMax Laier 			    "EPRT |2|%s|%u|\r\n", sock_ntop(sa), port);
8805ee7cd21SMax Laier 		break;
8815ee7cd21SMax Laier 	case CMD_EPSV:
8825ee7cd21SMax Laier 		r = snprintf(linebuf, sizeof linebuf,
8835ee7cd21SMax Laier 		    "229 Entering Extended Passive Mode (|||%u|)\r\n", port);
8845ee7cd21SMax Laier 		break;
8855ee7cd21SMax Laier 	}
8865ee7cd21SMax Laier 
887e0bfbfceSBjoern A. Zeeb 	if (r < 0 || ((u_int)r) >= sizeof linebuf) {
8885ee7cd21SMax Laier 		logmsg(LOG_ERR, "proxy_reply failed: %d", r);
8895ee7cd21SMax Laier 		linebuf[0] = '\0';
8905ee7cd21SMax Laier 		linelen = 0;
8915ee7cd21SMax Laier 		return;
8925ee7cd21SMax Laier 	}
8935ee7cd21SMax Laier 	linelen = (size_t)r;
8945ee7cd21SMax Laier 
8955ee7cd21SMax Laier 	if (cmd == CMD_PORT || cmd == CMD_PASV) {
8965ee7cd21SMax Laier 		/* Replace dots in IP address with commas. */
8975ee7cd21SMax Laier 		for (i = 0; i < linelen; i++)
8985ee7cd21SMax Laier 			if (linebuf[i] == '.')
8995ee7cd21SMax Laier 				linebuf[i] = ',';
90013b9f610SMax Laier 	}
90113b9f610SMax Laier }
90213b9f610SMax Laier 
9035ee7cd21SMax Laier void
server_error(struct bufferevent * bufev __unused,short what,void * arg)904e0bfbfceSBjoern A. Zeeb server_error(struct bufferevent *bufev __unused, short what, void *arg)
9055ee7cd21SMax Laier {
9065ee7cd21SMax Laier 	struct session *s = arg;
90713b9f610SMax Laier 
9085ee7cd21SMax Laier 	if (what & EVBUFFER_EOF)
9095ee7cd21SMax Laier 		logmsg(LOG_INFO, "#%d server close", s->id);
9105ee7cd21SMax Laier 	else if (what == (EVBUFFER_ERROR | EVBUFFER_READ))
9115ee7cd21SMax Laier 		logmsg(LOG_ERR, "#%d server refused connection", s->id);
9125ee7cd21SMax Laier 	else if (what & EVBUFFER_WRITE)
9135ee7cd21SMax Laier 		logmsg(LOG_ERR, "#%d server write error: %d", s->id, what);
9145ee7cd21SMax Laier 	else if (what & EVBUFFER_TIMEOUT)
9155ee7cd21SMax Laier 		logmsg(LOG_NOTICE, "#%d server timeout", s->id);
91613b9f610SMax Laier 	else
9175ee7cd21SMax Laier 		logmsg(LOG_ERR, "#%d abnormal server error: %d", s->id, what);
91813b9f610SMax Laier 
9195ee7cd21SMax Laier 	end_session(s);
92013b9f610SMax Laier }
92113b9f610SMax Laier 
9225ee7cd21SMax Laier int
server_parse(struct session * s)9235ee7cd21SMax Laier server_parse(struct session *s)
9245ee7cd21SMax Laier {
9255ee7cd21SMax Laier 	if (s->cmd == CMD_NONE || linelen < 4 || linebuf[0] != '2')
9265ee7cd21SMax Laier 		goto out;
92713b9f610SMax Laier 
928e0bfbfceSBjoern A. Zeeb 	if ((s->cmd == CMD_PASV && strncmp("227 ", linebuf, 4) == 0) ||
929e0bfbfceSBjoern A. Zeeb 	    (s->cmd == CMD_EPSV && strncmp("229 ", linebuf, 4) == 0))
930e0bfbfceSBjoern A. Zeeb 		return (allow_data_connection(s));
931e0bfbfceSBjoern A. Zeeb 
932e0bfbfceSBjoern A. Zeeb  out:
933e0bfbfceSBjoern A. Zeeb 	s->cmd = CMD_NONE;
934e0bfbfceSBjoern A. Zeeb 	s->port = 0;
935e0bfbfceSBjoern A. Zeeb 
936e0bfbfceSBjoern A. Zeeb 	return (1);
937e0bfbfceSBjoern A. Zeeb }
938e0bfbfceSBjoern A. Zeeb 
939e0bfbfceSBjoern A. Zeeb int
allow_data_connection(struct session * s)940e0bfbfceSBjoern A. Zeeb allow_data_connection(struct session *s)
941e0bfbfceSBjoern A. Zeeb {
942e0bfbfceSBjoern A. Zeeb 	struct sockaddr *client_sa, *orig_sa, *proxy_sa, *server_sa;
943e0bfbfceSBjoern A. Zeeb 	int prepared = 0;
944e0bfbfceSBjoern A. Zeeb 
94513b9f610SMax Laier 	/*
9465ee7cd21SMax Laier 	 * The pf rules below do quite some NAT rewriting, to keep up
9475ee7cd21SMax Laier 	 * appearances.  Points to keep in mind:
9485ee7cd21SMax Laier 	 * 1)  The client must think it's talking to the real server,
9495ee7cd21SMax Laier 	 *     for both control and data connections.  Transparently.
9505ee7cd21SMax Laier 	 * 2)  The server must think that the proxy is the client.
9515ee7cd21SMax Laier 	 * 3)  Source and destination ports are rewritten to minimize
9525ee7cd21SMax Laier 	 *     port collisions, to aid security (some systems pick weak
9535ee7cd21SMax Laier 	 *     ports) or to satisfy RFC requirements (source port 20).
95413b9f610SMax Laier 	 */
95513b9f610SMax Laier 
9565ee7cd21SMax Laier 	/* Cast this once, to make code below it more readable. */
9575ee7cd21SMax Laier 	client_sa = sstosa(&s->client_ss);
9585ee7cd21SMax Laier 	server_sa = sstosa(&s->server_ss);
9595ee7cd21SMax Laier 	proxy_sa = sstosa(&s->proxy_ss);
9605ee7cd21SMax Laier 	if (fixed_server)
9615ee7cd21SMax Laier 		/* Fixed server: data connections must appear to come
9625ee7cd21SMax Laier 		   from / go to the original server, not the fixed one. */
9635ee7cd21SMax Laier 		orig_sa = sstosa(&s->orig_server_ss);
9645ee7cd21SMax Laier 	else
9655ee7cd21SMax Laier 		/* Server not fixed: orig_server == server. */
9665ee7cd21SMax Laier 		orig_sa = sstosa(&s->server_ss);
96713b9f610SMax Laier 
9685ee7cd21SMax Laier 	/* Passive modes. */
969e0bfbfceSBjoern A. Zeeb 	if (s->cmd == CMD_PASV || s->cmd == CMD_EPSV) {
9705ee7cd21SMax Laier 		s->port = parse_port(s->cmd);
9715ee7cd21SMax Laier 		if (s->port < MIN_PORT) {
9725ee7cd21SMax Laier 			logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id,
9735ee7cd21SMax Laier 			    linebuf);
9745ee7cd21SMax Laier 			return (0);
9755ee7cd21SMax Laier 		}
9765ee7cd21SMax Laier 		s->proxy_port = pick_proxy_port();
9775ee7cd21SMax Laier 		logmsg(LOG_INFO, "#%d passive: client to server port %d"
9785ee7cd21SMax Laier 		    " via port %d", s->id, s->port, s->proxy_port);
9795ee7cd21SMax Laier 
9805ee7cd21SMax Laier 		if (prepare_commit(s->id) == -1)
9815ee7cd21SMax Laier 			goto fail;
9825ee7cd21SMax Laier 		prepared = 1;
9835ee7cd21SMax Laier 
9845ee7cd21SMax Laier 		proxy_reply(s->cmd, orig_sa, s->proxy_port);
9855ee7cd21SMax Laier 		logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf);
9865ee7cd21SMax Laier 
9875ee7cd21SMax Laier 		/* rdr from $client to $orig_server port $proxy_port -> $server
9885ee7cd21SMax Laier 		    port $port */
9895ee7cd21SMax Laier 		if (add_rdr(s->id, client_sa, orig_sa, s->proxy_port,
9905ee7cd21SMax Laier 		    server_sa, s->port) == -1)
9915ee7cd21SMax Laier 			goto fail;
9925ee7cd21SMax Laier 
9935ee7cd21SMax Laier 		/* nat from $client to $server port $port -> $proxy */
9945ee7cd21SMax Laier 		if (add_nat(s->id, client_sa, server_sa, s->port, proxy_sa,
9955ee7cd21SMax Laier 		    PF_NAT_PROXY_PORT_LOW, PF_NAT_PROXY_PORT_HIGH) == -1)
9965ee7cd21SMax Laier 			goto fail;
9975ee7cd21SMax Laier 
9985ee7cd21SMax Laier 		/* pass in from $client to $server port $port */
9995ee7cd21SMax Laier 		if (add_filter(s->id, PF_IN, client_sa, server_sa,
10005ee7cd21SMax Laier 		    s->port) == -1)
10015ee7cd21SMax Laier 			goto fail;
10025ee7cd21SMax Laier 
10035ee7cd21SMax Laier 		/* pass out from $proxy to $server port $port */
10045ee7cd21SMax Laier 		if (add_filter(s->id, PF_OUT, proxy_sa, server_sa,
10055ee7cd21SMax Laier 		    s->port) == -1)
10065ee7cd21SMax Laier 			goto fail;
100713b9f610SMax Laier 	}
100813b9f610SMax Laier 
10095ee7cd21SMax Laier 	/* Active modes. */
1010e0bfbfceSBjoern A. Zeeb 	if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) {
10115ee7cd21SMax Laier 		logmsg(LOG_INFO, "#%d active: server to client port %d"
10125ee7cd21SMax Laier 		    " via port %d", s->id, s->port, s->proxy_port);
101313b9f610SMax Laier 
10145ee7cd21SMax Laier 		if (prepare_commit(s->id) == -1)
10155ee7cd21SMax Laier 			goto fail;
10165ee7cd21SMax Laier 		prepared = 1;
101713b9f610SMax Laier 
10185ee7cd21SMax Laier 		/* rdr from $server to $proxy port $proxy_port -> $client port
10195ee7cd21SMax Laier 		    $port */
10205ee7cd21SMax Laier 		if (add_rdr(s->id, server_sa, proxy_sa, s->proxy_port,
10215ee7cd21SMax Laier 		    client_sa, s->port) == -1)
10225ee7cd21SMax Laier 			goto fail;
102313b9f610SMax Laier 
10245ee7cd21SMax Laier 		/* nat from $server to $client port $port -> $orig_server port
10255ee7cd21SMax Laier 		    $natport */
10265ee7cd21SMax Laier 		if (rfc_mode && s->cmd == CMD_PORT) {
10275ee7cd21SMax Laier 			/* Rewrite sourceport to RFC mandated 20. */
10285ee7cd21SMax Laier 			if (add_nat(s->id, server_sa, client_sa, s->port,
10295ee7cd21SMax Laier 			    orig_sa, 20, 20) == -1)
10305ee7cd21SMax Laier 				goto fail;
103113b9f610SMax Laier 		} else {
10325ee7cd21SMax Laier 			/* Let pf pick a source port from the standard range. */
10335ee7cd21SMax Laier 			if (add_nat(s->id, server_sa, client_sa, s->port,
10345ee7cd21SMax Laier 			    orig_sa, PF_NAT_PROXY_PORT_LOW,
10355ee7cd21SMax Laier 			    PF_NAT_PROXY_PORT_HIGH) == -1)
10365ee7cd21SMax Laier 			    	goto fail;
103713b9f610SMax Laier 		}
103813b9f610SMax Laier 
10395ee7cd21SMax Laier 		/* pass in from $server to $client port $port */
10405ee7cd21SMax Laier 		if (add_filter(s->id, PF_IN, server_sa, client_sa, s->port) ==
10415ee7cd21SMax Laier 		    -1)
10425ee7cd21SMax Laier 			goto fail;
104313b9f610SMax Laier 
10445ee7cd21SMax Laier 		/* pass out from $orig_server to $client port $port */
10455ee7cd21SMax Laier 		if (add_filter(s->id, PF_OUT, orig_sa, client_sa, s->port) ==
10465ee7cd21SMax Laier 		    -1)
10475ee7cd21SMax Laier 			goto fail;
10485ee7cd21SMax Laier 	}
10495ee7cd21SMax Laier 
10505ee7cd21SMax Laier 	/* Commit rules if they were prepared. */
10515ee7cd21SMax Laier 	if (prepared && (do_commit() == -1)) {
10525ee7cd21SMax Laier 		if (errno != EBUSY)
10535ee7cd21SMax Laier 			goto fail;
10545ee7cd21SMax Laier 		/* One more try if busy. */
10555ee7cd21SMax Laier 		usleep(5000);
10565ee7cd21SMax Laier 		if (do_commit() == -1)
10575ee7cd21SMax Laier 			goto fail;
10585ee7cd21SMax Laier 	}
10595ee7cd21SMax Laier 
10605ee7cd21SMax Laier 	s->cmd = CMD_NONE;
10615ee7cd21SMax Laier 	s->port = 0;
10625ee7cd21SMax Laier 
10635ee7cd21SMax Laier 	return (1);
10645ee7cd21SMax Laier 
10655ee7cd21SMax Laier  fail:
10665ee7cd21SMax Laier 	logmsg(LOG_CRIT, "#%d pf operation failed: %s", s->id, strerror(errno));
10675ee7cd21SMax Laier 	if (prepared)
10685ee7cd21SMax Laier 		do_rollback();
10695ee7cd21SMax Laier 	return (0);
10705ee7cd21SMax Laier }
10715ee7cd21SMax Laier 
10725ee7cd21SMax Laier void
server_read(struct bufferevent * bufev,void * arg)10735ee7cd21SMax Laier server_read(struct bufferevent *bufev, void *arg)
10745ee7cd21SMax Laier {
10755ee7cd21SMax Laier 	struct session	*s = arg;
1076e0bfbfceSBjoern A. Zeeb 	size_t		 buf_avail, srvread;
10775ee7cd21SMax Laier 	int		 n;
10785ee7cd21SMax Laier 
10795ee7cd21SMax Laier 	bufferevent_settimeout(bufev, timeout, 0);
10805ee7cd21SMax Laier 
10815ee7cd21SMax Laier 	do {
10825ee7cd21SMax Laier 		buf_avail = sizeof s->sbuf - s->sbuf_valid;
1083e0bfbfceSBjoern A. Zeeb 		srvread = bufferevent_read(bufev, s->sbuf + s->sbuf_valid,
10845ee7cd21SMax Laier 		    buf_avail);
1085e0bfbfceSBjoern A. Zeeb 		s->sbuf_valid += srvread;
10865ee7cd21SMax Laier 
1087053a8868SBaptiste Daroussin 		while ((n = get_line(s->sbuf, &s->sbuf_valid)) > 0) {
10885ee7cd21SMax Laier 			logmsg(LOG_DEBUG, "#%d server: %s", s->id, linebuf);
10895ee7cd21SMax Laier 			if (!server_parse(s)) {
10905ee7cd21SMax Laier 				end_session(s);
10915ee7cd21SMax Laier 				return;
10925ee7cd21SMax Laier 			}
10935ee7cd21SMax Laier 			bufferevent_write(s->client_bufev, linebuf, linelen);
10945ee7cd21SMax Laier 		}
10955ee7cd21SMax Laier 
10965ee7cd21SMax Laier 		if (n == -1) {
10975ee7cd21SMax Laier 			logmsg(LOG_ERR, "#%d server reply too long or not"
10985ee7cd21SMax Laier 			    " clean", s->id);
10995ee7cd21SMax Laier 			end_session(s);
11005ee7cd21SMax Laier 			return;
11015ee7cd21SMax Laier 		}
1102e0bfbfceSBjoern A. Zeeb 	} while (srvread == buf_avail);
11035ee7cd21SMax Laier }
11045ee7cd21SMax Laier 
11055ee7cd21SMax Laier const char *
sock_ntop(struct sockaddr * sa)11065ee7cd21SMax Laier sock_ntop(struct sockaddr *sa)
11075ee7cd21SMax Laier {
11085ee7cd21SMax Laier 	static int n = 0;
11095ee7cd21SMax Laier 
11105ee7cd21SMax Laier 	/* Cycle to next buffer. */
11115ee7cd21SMax Laier 	n = (n + 1) % NTOP_BUFS;
11125ee7cd21SMax Laier 	ntop_buf[n][0] = '\0';
11135ee7cd21SMax Laier 
11145ee7cd21SMax Laier 	if (sa->sa_family == AF_INET) {
11155ee7cd21SMax Laier 		struct sockaddr_in *sin = (struct sockaddr_in *)sa;
11165ee7cd21SMax Laier 
11175ee7cd21SMax Laier 		return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n],
11185ee7cd21SMax Laier 		    sizeof ntop_buf[0]));
11195ee7cd21SMax Laier 	}
11205ee7cd21SMax Laier 
11215ee7cd21SMax Laier 	if (sa->sa_family == AF_INET6) {
11225ee7cd21SMax Laier 		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
11235ee7cd21SMax Laier 
11245ee7cd21SMax Laier 		return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n],
11255ee7cd21SMax Laier 		    sizeof ntop_buf[0]));
11265ee7cd21SMax Laier 	}
11275ee7cd21SMax Laier 
11285ee7cd21SMax Laier 	return (NULL);
11295ee7cd21SMax Laier }
11305ee7cd21SMax Laier 
11315ee7cd21SMax Laier void
usage(void)11325ee7cd21SMax Laier usage(void)
11335ee7cd21SMax Laier {
11345ee7cd21SMax Laier 	fprintf(stderr, "usage: %s [-6Adrv] [-a address] [-b address]"
11355ee7cd21SMax Laier 	    " [-D level] [-m maxsessions]\n                 [-P port]"
1136e0bfbfceSBjoern A. Zeeb 	    " [-p port] [-q queue] [-R address] [-T tag]\n"
1137e0bfbfceSBjoern A. Zeeb             "                 [-t timeout]\n", __progname);
11385ee7cd21SMax Laier 	exit(1);
113913b9f610SMax Laier }
1140