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