1*ea9c0ebdSjoe /* $NetBSD: ftp-proxy.c,v 1.5 2025/01/07 21:15:40 joe Exp $ */ 2fff57c55Syamt /* $OpenBSD: ftp-proxy.c,v 1.15 2007/08/15 15:18:02 camield Exp $ */ 3fff57c55Syamt 4fff57c55Syamt /* 5fff57c55Syamt * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl> 6fff57c55Syamt * 7fff57c55Syamt * Permission to use, copy, modify, and distribute this software for any 8fff57c55Syamt * purpose with or without fee is hereby granted, provided that the above 9fff57c55Syamt * copyright notice and this permission notice appear in all copies. 10fff57c55Syamt * 11fff57c55Syamt * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12fff57c55Syamt * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13fff57c55Syamt * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14fff57c55Syamt * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15fff57c55Syamt * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16fff57c55Syamt * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17fff57c55Syamt * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18fff57c55Syamt */ 19fff57c55Syamt 20fff57c55Syamt #include <sys/queue.h> 21fff57c55Syamt #include <sys/types.h> 22fff57c55Syamt #include <sys/time.h> 23fff57c55Syamt #include <sys/resource.h> 24fff57c55Syamt #include <sys/socket.h> 25fff57c55Syamt 26fff57c55Syamt #include <net/if.h> 27fff57c55Syamt #include <net/pfvar.h> 28fff57c55Syamt #include <netinet/in.h> 29fff57c55Syamt #include <arpa/inet.h> 30fff57c55Syamt 31fff57c55Syamt #include <err.h> 32fff57c55Syamt #include <errno.h> 33fff57c55Syamt #include <event.h> 34fff57c55Syamt #include <fcntl.h> 35fff57c55Syamt #include <netdb.h> 36fff57c55Syamt #include <pwd.h> 37fff57c55Syamt #include <signal.h> 38fff57c55Syamt #include <stdarg.h> 39fff57c55Syamt #include <stdio.h> 40fff57c55Syamt #include <stdlib.h> 41fff57c55Syamt #include <string.h> 42fff57c55Syamt #include <syslog.h> 43fff57c55Syamt #include <unistd.h> 44fff57c55Syamt #include <vis.h> 45fff57c55Syamt 46fff57c55Syamt #include "filter.h" 47fff57c55Syamt 48fff57c55Syamt #define CONNECT_TIMEOUT 30 49fff57c55Syamt #define MIN_PORT 1024 50fff57c55Syamt #define MAX_LINE 500 51fff57c55Syamt #define MAX_LOGLINE 300 52fff57c55Syamt #define NTOP_BUFS 3 53fff57c55Syamt #define TCP_BACKLOG 10 54fff57c55Syamt 55fff57c55Syamt #define CHROOT_DIR "/var/chroot/ftp-proxy" 56fff57c55Syamt #define NOPRIV_USER "_proxy" 57fff57c55Syamt 58fff57c55Syamt /* pfctl standard NAT range. */ 59fff57c55Syamt #define PF_NAT_PROXY_PORT_LOW 50001 60fff57c55Syamt #define PF_NAT_PROXY_PORT_HIGH 65535 61fff57c55Syamt 62fff57c55Syamt #ifndef sstosa 63fff57c55Syamt #define sstosa(ss) ((struct sockaddr *)(ss)) 64fff57c55Syamt #endif /* sstosa */ 65fff57c55Syamt 66fff57c55Syamt #ifndef IPPORT_HIFIRSTAUTO 67fff57c55Syamt #define IPPORT_HIFIRSTAUTO IPPORT_ANONMIN 68fff57c55Syamt #endif /* IPPORT_HIFIRSTAUTO */ 69fff57c55Syamt 70fff57c55Syamt #ifndef IPPORT_HILASTAUTO 71fff57c55Syamt #define IPPORT_HILASTAUTO IPPORT_ANONMAX 72fff57c55Syamt #endif /* IPPORT_HILASTAUTO */ 73fff57c55Syamt 74fff57c55Syamt enum { CMD_NONE = 0, CMD_PORT, CMD_EPRT, CMD_PASV, CMD_EPSV }; 75fff57c55Syamt 76fff57c55Syamt struct session { 77fff57c55Syamt u_int32_t id; 78fff57c55Syamt struct sockaddr_storage client_ss; 79fff57c55Syamt struct sockaddr_storage proxy_ss; 80fff57c55Syamt struct sockaddr_storage server_ss; 81fff57c55Syamt struct sockaddr_storage orig_server_ss; 82fff57c55Syamt struct bufferevent *client_bufev; 83fff57c55Syamt struct bufferevent *server_bufev; 84fff57c55Syamt int client_fd; 85fff57c55Syamt int server_fd; 86fff57c55Syamt char cbuf[MAX_LINE]; 87fff57c55Syamt size_t cbuf_valid; 88fff57c55Syamt char sbuf[MAX_LINE]; 89fff57c55Syamt size_t sbuf_valid; 90fff57c55Syamt int cmd; 91fff57c55Syamt u_int16_t port; 92fff57c55Syamt u_int16_t proxy_port; 93fff57c55Syamt LIST_ENTRY(session) entry; 94fff57c55Syamt }; 95fff57c55Syamt 96fff57c55Syamt LIST_HEAD(, session) sessions = LIST_HEAD_INITIALIZER(sessions); 97fff57c55Syamt 98fff57c55Syamt void client_error(struct bufferevent *, short, void *); 99fff57c55Syamt int client_parse(struct session *s); 100fff57c55Syamt int client_parse_anon(struct session *s); 101fff57c55Syamt int client_parse_cmd(struct session *s); 102fff57c55Syamt void client_read(struct bufferevent *, void *); 103fff57c55Syamt int drop_privs(void); 104fff57c55Syamt void end_session(struct session *); 105fff57c55Syamt int exit_daemon(void); 1067027866aSroy int get_line(char *, size_t *); 107fff57c55Syamt void handle_connection(const int, short, void *); 108fff57c55Syamt void handle_signal(int, short, void *); 109fff57c55Syamt struct session * init_session(void); 110fff57c55Syamt void logmsg(int, const char *, ...); 111fff57c55Syamt u_int16_t parse_port(int); 112fff57c55Syamt u_int16_t pick_proxy_port(void); 113fff57c55Syamt void proxy_reply(int, struct sockaddr *, u_int16_t); 114fff57c55Syamt void server_error(struct bufferevent *, short, void *); 115fff57c55Syamt int server_parse(struct session *s); 116fff57c55Syamt int allow_data_connection(struct session *s); 117fff57c55Syamt void server_read(struct bufferevent *, void *); 118fff57c55Syamt const char *sock_ntop(struct sockaddr *); 119fff57c55Syamt void usage(void); 120fff57c55Syamt 121fff57c55Syamt char linebuf[MAX_LINE + 1]; 122fff57c55Syamt size_t linelen; 123fff57c55Syamt 124fff57c55Syamt char ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN]; 125fff57c55Syamt 126fff57c55Syamt struct sockaddr_storage fixed_server_ss, fixed_proxy_ss; 127fff57c55Syamt char *fixed_server, *fixed_server_port, *fixed_proxy, *listen_ip, *listen_port, 128fff57c55Syamt *qname, *tagname; 129fff57c55Syamt int anonymous_only, daemonize, id_count, ipv6_mode, loglevel, max_sessions, 130fff57c55Syamt rfc_mode, session_count, timeout, verbose; 131fff57c55Syamt extern char *__progname; 132fff57c55Syamt 13307ac07d3Srmind /* Default: PF operations. */ 13407ac07d3Srmind static const ftp_proxy_ops_t * fops = &pf_fprx_ops; 135fff57c55Syamt 136fff57c55Syamt void 137fff57c55Syamt client_error(struct bufferevent *bufev, short what, void *arg) 138fff57c55Syamt { 139fff57c55Syamt struct session *s = arg; 140fff57c55Syamt 141fff57c55Syamt if (what & EVBUFFER_EOF) 142fff57c55Syamt logmsg(LOG_INFO, "#%d client close", s->id); 143fff57c55Syamt else if (what == (EVBUFFER_ERROR | EVBUFFER_READ)) 144fff57c55Syamt logmsg(LOG_ERR, "#%d client reset connection", s->id); 145fff57c55Syamt else if (what & EVBUFFER_TIMEOUT) 146fff57c55Syamt logmsg(LOG_ERR, "#%d client timeout", s->id); 147fff57c55Syamt else if (what & EVBUFFER_WRITE) 148fff57c55Syamt logmsg(LOG_ERR, "#%d client write error: %d", s->id, what); 149fff57c55Syamt else 150fff57c55Syamt logmsg(LOG_ERR, "#%d abnormal client error: %d", s->id, what); 151fff57c55Syamt 152fff57c55Syamt end_session(s); 153fff57c55Syamt } 154fff57c55Syamt 155fff57c55Syamt int 156fff57c55Syamt client_parse(struct session *s) 157fff57c55Syamt { 158fff57c55Syamt /* Reset any previous command. */ 159fff57c55Syamt s->cmd = CMD_NONE; 160fff57c55Syamt s->port = 0; 161fff57c55Syamt 162fff57c55Syamt /* Commands we are looking for are at least 4 chars long. */ 163fff57c55Syamt if (linelen < 4) 164fff57c55Syamt return (1); 165fff57c55Syamt 166fff57c55Syamt if (linebuf[0] == 'P' || linebuf[0] == 'p' || 167fff57c55Syamt linebuf[0] == 'E' || linebuf[0] == 'e') { 168fff57c55Syamt if (!client_parse_cmd(s)) 169fff57c55Syamt return (0); 170fff57c55Syamt 171fff57c55Syamt /* 172fff57c55Syamt * Allow active mode connections immediately, instead of 173fff57c55Syamt * waiting for a positive reply from the server. Some 174fff57c55Syamt * rare servers/proxies try to probe or setup the data 175fff57c55Syamt * connection before an actual transfer request. 176fff57c55Syamt */ 177fff57c55Syamt if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) 178fff57c55Syamt return (allow_data_connection(s)); 179fff57c55Syamt } 180fff57c55Syamt 181fff57c55Syamt if (anonymous_only && (linebuf[0] == 'U' || linebuf[0] == 'u')) 182fff57c55Syamt return (client_parse_anon(s)); 183fff57c55Syamt 184fff57c55Syamt return (1); 185fff57c55Syamt } 186fff57c55Syamt 187fff57c55Syamt int 188fff57c55Syamt client_parse_anon(struct session *s) 189fff57c55Syamt { 190fff57c55Syamt if (strcasecmp("USER ftp\r\n", linebuf) != 0 && 191fff57c55Syamt strcasecmp("USER anonymous\r\n", linebuf) != 0) { 192fff57c55Syamt snprintf(linebuf, sizeof linebuf, 193fff57c55Syamt "500 Only anonymous FTP allowed\r\n"); 194fff57c55Syamt logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); 195fff57c55Syamt 196fff57c55Syamt /* Talk back to the client ourself. */ 197fff57c55Syamt linelen = strlen(linebuf); 198fff57c55Syamt bufferevent_write(s->client_bufev, linebuf, linelen); 199fff57c55Syamt 200fff57c55Syamt /* Clear buffer so it's not sent to the server. */ 201fff57c55Syamt linebuf[0] = '\0'; 202fff57c55Syamt linelen = 0; 203fff57c55Syamt } 204fff57c55Syamt 205fff57c55Syamt return (1); 206fff57c55Syamt } 207fff57c55Syamt 208fff57c55Syamt int 209fff57c55Syamt client_parse_cmd(struct session *s) 210fff57c55Syamt { 211fff57c55Syamt if (strncasecmp("PASV", linebuf, 4) == 0) 212fff57c55Syamt s->cmd = CMD_PASV; 213fff57c55Syamt else if (strncasecmp("PORT ", linebuf, 5) == 0) 214fff57c55Syamt s->cmd = CMD_PORT; 215fff57c55Syamt else if (strncasecmp("EPSV", linebuf, 4) == 0) 216fff57c55Syamt s->cmd = CMD_EPSV; 217fff57c55Syamt else if (strncasecmp("EPRT ", linebuf, 5) == 0) 218fff57c55Syamt s->cmd = CMD_EPRT; 219fff57c55Syamt else 220fff57c55Syamt return (1); 221fff57c55Syamt 222fff57c55Syamt if (ipv6_mode && (s->cmd == CMD_PASV || s->cmd == CMD_PORT)) { 223fff57c55Syamt logmsg(LOG_CRIT, "PASV and PORT not allowed with IPv6"); 224fff57c55Syamt return (0); 225fff57c55Syamt } 226fff57c55Syamt 227fff57c55Syamt if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) { 228fff57c55Syamt s->port = parse_port(s->cmd); 229fff57c55Syamt if (s->port < MIN_PORT) { 230fff57c55Syamt logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id, 231fff57c55Syamt linebuf); 232fff57c55Syamt return (0); 233fff57c55Syamt } 234fff57c55Syamt s->proxy_port = pick_proxy_port(); 235fff57c55Syamt proxy_reply(s->cmd, sstosa(&s->proxy_ss), s->proxy_port); 236fff57c55Syamt logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); 237fff57c55Syamt } 238fff57c55Syamt 239fff57c55Syamt return (1); 240fff57c55Syamt } 241fff57c55Syamt 242fff57c55Syamt void 243fff57c55Syamt client_read(struct bufferevent *bufev, void *arg) 244fff57c55Syamt { 245fff57c55Syamt struct session *s = arg; 246fff57c55Syamt size_t buf_avail, nread; 247fff57c55Syamt int n; 248fff57c55Syamt 249fff57c55Syamt do { 250fff57c55Syamt buf_avail = sizeof s->cbuf - s->cbuf_valid; 251fff57c55Syamt nread = bufferevent_read(bufev, s->cbuf + s->cbuf_valid, 252fff57c55Syamt buf_avail); 253fff57c55Syamt s->cbuf_valid += nread; 254fff57c55Syamt 2557027866aSroy while ((n = get_line(s->cbuf, &s->cbuf_valid)) > 0) { 256fff57c55Syamt logmsg(LOG_DEBUG, "#%d client: %s", s->id, linebuf); 257fff57c55Syamt if (!client_parse(s)) { 258fff57c55Syamt end_session(s); 259fff57c55Syamt return; 260fff57c55Syamt } 261fff57c55Syamt bufferevent_write(s->server_bufev, linebuf, linelen); 262fff57c55Syamt } 263fff57c55Syamt 264fff57c55Syamt if (n == -1) { 265fff57c55Syamt logmsg(LOG_ERR, "#%d client command too long or not" 266fff57c55Syamt " clean", s->id); 267fff57c55Syamt end_session(s); 268fff57c55Syamt return; 269fff57c55Syamt } 270fff57c55Syamt } while (nread == buf_avail); 271fff57c55Syamt } 272fff57c55Syamt 273fff57c55Syamt int 274fff57c55Syamt drop_privs(void) 275fff57c55Syamt { 276fff57c55Syamt struct passwd *pw; 277fff57c55Syamt 278fff57c55Syamt pw = getpwnam(NOPRIV_USER); 279fff57c55Syamt if (pw == NULL) 280fff57c55Syamt return (0); 281fff57c55Syamt 282fff57c55Syamt tzset(); 283fff57c55Syamt #ifdef __NetBSD__ 284fff57c55Syamt if (chroot(CHROOT_DIR) != 0 || chdir("/") != 0 || 285fff57c55Syamt setgroups(1, &pw->pw_gid) != 0 || 286fff57c55Syamt setgid(pw->pw_gid) != 0 || 287fff57c55Syamt setuid(pw->pw_uid) != 0) 288fff57c55Syamt return (0); 289fff57c55Syamt #else 290fff57c55Syamt if (chroot(CHROOT_DIR) != 0 || chdir("/") != 0 || 291fff57c55Syamt setgroups(1, &pw->pw_gid) != 0 || 292fff57c55Syamt setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0 || 293fff57c55Syamt setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) 294fff57c55Syamt return (0); 295fff57c55Syamt #endif /* !__NetBSD__ */ 296fff57c55Syamt 297fff57c55Syamt return (1); 298fff57c55Syamt } 299fff57c55Syamt 300fff57c55Syamt void 301fff57c55Syamt end_session(struct session *s) 302fff57c55Syamt { 303fff57c55Syamt int error; 304fff57c55Syamt 305fff57c55Syamt logmsg(LOG_INFO, "#%d ending session", s->id); 306fff57c55Syamt 307fff57c55Syamt if (s->client_fd != -1) 308fff57c55Syamt close(s->client_fd); 309fff57c55Syamt if (s->server_fd != -1) 310fff57c55Syamt close(s->server_fd); 311fff57c55Syamt 312fff57c55Syamt if (s->client_bufev) 313fff57c55Syamt bufferevent_free(s->client_bufev); 314fff57c55Syamt if (s->server_bufev) 315fff57c55Syamt bufferevent_free(s->server_bufev); 316fff57c55Syamt 317fff57c55Syamt /* Remove rulesets by commiting empty ones. */ 318fff57c55Syamt error = 0; 31907ac07d3Srmind if (fops->prepare_commit(s->id) == -1) 320fff57c55Syamt error = errno; 32107ac07d3Srmind else if (fops->do_commit() == -1) { 322fff57c55Syamt error = errno; 32307ac07d3Srmind fops->do_rollback(); 324fff57c55Syamt } 325fff57c55Syamt if (error) 326fff57c55Syamt logmsg(LOG_ERR, "#%d pf rule removal failed: %s", s->id, 327fff57c55Syamt strerror(error)); 328fff57c55Syamt 329fff57c55Syamt LIST_REMOVE(s, entry); 330fff57c55Syamt free(s); 331fff57c55Syamt session_count--; 332fff57c55Syamt } 333fff57c55Syamt 334fff57c55Syamt int 335fff57c55Syamt exit_daemon(void) 336fff57c55Syamt { 337fff57c55Syamt struct session *s, *next; 338fff57c55Syamt 339fff57c55Syamt for (s = LIST_FIRST(&sessions); s != LIST_END(&sessions); s = next) { 340fff57c55Syamt next = LIST_NEXT(s, entry); 341fff57c55Syamt end_session(s); 342fff57c55Syamt } 343fff57c55Syamt 344fff57c55Syamt if (daemonize) 345fff57c55Syamt closelog(); 346fff57c55Syamt 347fff57c55Syamt exit(0); 348fff57c55Syamt 349fff57c55Syamt /* NOTREACHED */ 350fff57c55Syamt return (-1); 351fff57c55Syamt } 352fff57c55Syamt 353fff57c55Syamt int 3547027866aSroy get_line(char *buf, size_t *valid) 355fff57c55Syamt { 356fff57c55Syamt size_t i; 357fff57c55Syamt 358fff57c55Syamt if (*valid > MAX_LINE) 359fff57c55Syamt return (-1); 360fff57c55Syamt 361fff57c55Syamt /* Copy to linebuf while searching for a newline. */ 362fff57c55Syamt for (i = 0; i < *valid; i++) { 363fff57c55Syamt linebuf[i] = buf[i]; 364fff57c55Syamt if (buf[i] == '\0') 365fff57c55Syamt return (-1); 366fff57c55Syamt if (buf[i] == '\n') 367fff57c55Syamt break; 368fff57c55Syamt } 369fff57c55Syamt 370fff57c55Syamt if (i == *valid) { 371fff57c55Syamt /* No newline found. */ 372fff57c55Syamt linebuf[0] = '\0'; 373fff57c55Syamt linelen = 0; 374fff57c55Syamt if (i < MAX_LINE) 375fff57c55Syamt return (0); 376fff57c55Syamt return (-1); 377fff57c55Syamt } 378fff57c55Syamt 379fff57c55Syamt linelen = i + 1; 380fff57c55Syamt linebuf[linelen] = '\0'; 381fff57c55Syamt *valid -= linelen; 382fff57c55Syamt 383fff57c55Syamt /* Move leftovers to the start. */ 384fff57c55Syamt if (*valid != 0) 385fff57c55Syamt bcopy(buf + linelen, buf, *valid); 386fff57c55Syamt 387fff57c55Syamt return ((int)linelen); 388fff57c55Syamt } 389fff57c55Syamt 390fff57c55Syamt void 391fff57c55Syamt handle_connection(const int listen_fd, short event, void *ev) 392fff57c55Syamt { 393fff57c55Syamt struct sockaddr_storage tmp_ss; 394fff57c55Syamt struct sockaddr *client_sa, *server_sa, *fixed_server_sa; 395fff57c55Syamt struct sockaddr *client_to_proxy_sa, *proxy_to_server_sa; 396fff57c55Syamt struct session *s; 397fff57c55Syamt socklen_t len; 398fff57c55Syamt int client_fd, fc, on; 399fff57c55Syamt 400fff57c55Syamt /* 401fff57c55Syamt * We _must_ accept the connection, otherwise libevent will keep 402fff57c55Syamt * coming back, and we will chew up all CPU. 403fff57c55Syamt */ 404fff57c55Syamt client_sa = sstosa(&tmp_ss); 405fff57c55Syamt len = sizeof(struct sockaddr_storage); 406fff57c55Syamt if ((client_fd = accept(listen_fd, client_sa, &len)) < 0) { 407fff57c55Syamt logmsg(LOG_CRIT, "accept failed: %s", strerror(errno)); 408fff57c55Syamt return; 409fff57c55Syamt } 410fff57c55Syamt 411fff57c55Syamt /* Refuse connection if the maximum is reached. */ 412fff57c55Syamt if (session_count >= max_sessions) { 413fff57c55Syamt logmsg(LOG_ERR, "client limit (%d) reached, refusing " 414fff57c55Syamt "connection from %s", max_sessions, sock_ntop(client_sa)); 415fff57c55Syamt close(client_fd); 416fff57c55Syamt return; 417fff57c55Syamt } 418fff57c55Syamt 419fff57c55Syamt /* Allocate session and copy back the info from the accept(). */ 420fff57c55Syamt s = init_session(); 421fff57c55Syamt if (s == NULL) { 422fff57c55Syamt logmsg(LOG_CRIT, "init_session failed"); 423fff57c55Syamt close(client_fd); 424fff57c55Syamt return; 425fff57c55Syamt } 426fff57c55Syamt s->client_fd = client_fd; 427fff57c55Syamt memcpy(sstosa(&s->client_ss), client_sa, client_sa->sa_len); 428fff57c55Syamt 429fff57c55Syamt /* Cast it once, and be done with it. */ 430fff57c55Syamt client_sa = sstosa(&s->client_ss); 431fff57c55Syamt server_sa = sstosa(&s->server_ss); 432fff57c55Syamt client_to_proxy_sa = sstosa(&tmp_ss); 433fff57c55Syamt proxy_to_server_sa = sstosa(&s->proxy_ss); 434fff57c55Syamt fixed_server_sa = sstosa(&fixed_server_ss); 435fff57c55Syamt 436fff57c55Syamt /* Log id/client early to ease debugging. */ 437fff57c55Syamt logmsg(LOG_DEBUG, "#%d accepted connection from %s", s->id, 438fff57c55Syamt sock_ntop(client_sa)); 439fff57c55Syamt 440fff57c55Syamt /* 441fff57c55Syamt * Find out the real server and port that the client wanted. 442fff57c55Syamt */ 443fff57c55Syamt len = sizeof(struct sockaddr_storage); 444fff57c55Syamt if ((getsockname(s->client_fd, client_to_proxy_sa, &len)) < 0) { 445fff57c55Syamt logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id, 446fff57c55Syamt strerror(errno)); 447fff57c55Syamt goto fail; 448fff57c55Syamt } 44907ac07d3Srmind if (fops->server_lookup(client_sa, client_to_proxy_sa, server_sa)) { 450fff57c55Syamt logmsg(LOG_CRIT, "#%d server lookup failed (no rdr?)", s->id); 451fff57c55Syamt goto fail; 452fff57c55Syamt } 453fff57c55Syamt if (fixed_server) { 454fff57c55Syamt memcpy(sstosa(&s->orig_server_ss), server_sa, 455fff57c55Syamt server_sa->sa_len); 456fff57c55Syamt memcpy(server_sa, fixed_server_sa, fixed_server_sa->sa_len); 457fff57c55Syamt } 458fff57c55Syamt 459fff57c55Syamt /* XXX: check we are not connecting to ourself. */ 460fff57c55Syamt 461fff57c55Syamt /* 462fff57c55Syamt * Setup socket and connect to server. 463fff57c55Syamt */ 464fff57c55Syamt if ((s->server_fd = socket(server_sa->sa_family, SOCK_STREAM, 465fff57c55Syamt IPPROTO_TCP)) < 0) { 466fff57c55Syamt logmsg(LOG_CRIT, "#%d server socket failed: %s", s->id, 467fff57c55Syamt strerror(errno)); 468fff57c55Syamt goto fail; 469fff57c55Syamt } 470fff57c55Syamt if (fixed_proxy && bind(s->server_fd, sstosa(&fixed_proxy_ss), 471fff57c55Syamt fixed_proxy_ss.ss_len) != 0) { 472fff57c55Syamt logmsg(LOG_CRIT, "#%d cannot bind fixed proxy address: %s", 473fff57c55Syamt s->id, strerror(errno)); 474fff57c55Syamt goto fail; 475fff57c55Syamt } 476fff57c55Syamt 477fff57c55Syamt /* Use non-blocking connect(), see CONNECT_TIMEOUT below. */ 478fff57c55Syamt if ((fc = fcntl(s->server_fd, F_GETFL)) == -1 || 479fff57c55Syamt fcntl(s->server_fd, F_SETFL, fc | O_NONBLOCK) == -1) { 480fff57c55Syamt logmsg(LOG_CRIT, "#%d cannot mark socket non-blocking: %s", 481fff57c55Syamt s->id, strerror(errno)); 482fff57c55Syamt goto fail; 483fff57c55Syamt } 484fff57c55Syamt if (connect(s->server_fd, server_sa, server_sa->sa_len) < 0 && 485fff57c55Syamt errno != EINPROGRESS) { 486fff57c55Syamt logmsg(LOG_CRIT, "#%d proxy cannot connect to server %s: %s", 487fff57c55Syamt s->id, sock_ntop(server_sa), strerror(errno)); 488fff57c55Syamt goto fail; 489fff57c55Syamt } 490fff57c55Syamt 491fff57c55Syamt len = sizeof(struct sockaddr_storage); 492fff57c55Syamt if ((getsockname(s->server_fd, proxy_to_server_sa, &len)) < 0) { 493fff57c55Syamt logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id, 494fff57c55Syamt strerror(errno)); 495fff57c55Syamt goto fail; 496fff57c55Syamt } 497fff57c55Syamt 498fff57c55Syamt logmsg(LOG_INFO, "#%d FTP session %d/%d started: client %s to server " 499fff57c55Syamt "%s via proxy %s ", s->id, session_count, max_sessions, 500fff57c55Syamt sock_ntop(client_sa), sock_ntop(server_sa), 501fff57c55Syamt sock_ntop(proxy_to_server_sa)); 502fff57c55Syamt 503fff57c55Syamt /* Keepalive is nice, but don't care if it fails. */ 504fff57c55Syamt on = 1; 505fff57c55Syamt setsockopt(s->client_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, 506fff57c55Syamt sizeof on); 507fff57c55Syamt setsockopt(s->server_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, 508fff57c55Syamt sizeof on); 509fff57c55Syamt 510fff57c55Syamt /* 511fff57c55Syamt * Setup buffered events. 512fff57c55Syamt */ 513fff57c55Syamt s->client_bufev = bufferevent_new(s->client_fd, &client_read, NULL, 514fff57c55Syamt &client_error, s); 515fff57c55Syamt if (s->client_bufev == NULL) { 516fff57c55Syamt logmsg(LOG_CRIT, "#%d bufferevent_new client failed", s->id); 517fff57c55Syamt goto fail; 518fff57c55Syamt } 519fff57c55Syamt bufferevent_settimeout(s->client_bufev, timeout, 0); 520fff57c55Syamt bufferevent_enable(s->client_bufev, EV_READ | EV_TIMEOUT); 521fff57c55Syamt 522fff57c55Syamt s->server_bufev = bufferevent_new(s->server_fd, &server_read, NULL, 523fff57c55Syamt &server_error, s); 524fff57c55Syamt if (s->server_bufev == NULL) { 525fff57c55Syamt logmsg(LOG_CRIT, "#%d bufferevent_new server failed", s->id); 526fff57c55Syamt goto fail; 527fff57c55Syamt } 528fff57c55Syamt bufferevent_settimeout(s->server_bufev, CONNECT_TIMEOUT, 0); 529fff57c55Syamt bufferevent_enable(s->server_bufev, EV_READ | EV_TIMEOUT); 530fff57c55Syamt 531fff57c55Syamt return; 532fff57c55Syamt 533fff57c55Syamt fail: 534fff57c55Syamt end_session(s); 535fff57c55Syamt } 536fff57c55Syamt 537fff57c55Syamt void 538fff57c55Syamt handle_signal(int sig, short event, void *arg) 539fff57c55Syamt { 540fff57c55Syamt /* 541fff57c55Syamt * Signal handler rules don't apply, libevent decouples for us. 542fff57c55Syamt */ 543fff57c55Syamt 544fff57c55Syamt logmsg(LOG_ERR, "%s exiting on signal %d", __progname, sig); 545fff57c55Syamt 546fff57c55Syamt exit_daemon(); 547fff57c55Syamt } 548fff57c55Syamt 549fff57c55Syamt struct session * 550fff57c55Syamt init_session(void) 551fff57c55Syamt { 552fff57c55Syamt struct session *s; 553fff57c55Syamt 554fff57c55Syamt s = calloc(1, sizeof(struct session)); 555fff57c55Syamt if (s == NULL) 556fff57c55Syamt return (NULL); 557fff57c55Syamt 558fff57c55Syamt s->id = id_count++; 559fff57c55Syamt s->client_fd = -1; 560fff57c55Syamt s->server_fd = -1; 561fff57c55Syamt s->cbuf[0] = '\0'; 562fff57c55Syamt s->cbuf_valid = 0; 563fff57c55Syamt s->sbuf[0] = '\0'; 564fff57c55Syamt s->sbuf_valid = 0; 565fff57c55Syamt s->client_bufev = NULL; 566fff57c55Syamt s->server_bufev = NULL; 567fff57c55Syamt s->cmd = CMD_NONE; 568fff57c55Syamt s->port = 0; 569fff57c55Syamt 570fff57c55Syamt LIST_INSERT_HEAD(&sessions, s, entry); 571fff57c55Syamt session_count++; 572fff57c55Syamt 573fff57c55Syamt return (s); 574fff57c55Syamt } 575fff57c55Syamt 576fff57c55Syamt void 577fff57c55Syamt logmsg(int pri, const char *message, ...) 578fff57c55Syamt { 579fff57c55Syamt va_list ap; 580fff57c55Syamt 581fff57c55Syamt if (pri > loglevel) 582fff57c55Syamt return; 583fff57c55Syamt 584fff57c55Syamt va_start(ap, message); 585fff57c55Syamt 586fff57c55Syamt if (daemonize) 587fff57c55Syamt /* syslog does its own vissing. */ 588fff57c55Syamt vsyslog(pri, message, ap); 589fff57c55Syamt else { 590fff57c55Syamt char buf[MAX_LOGLINE]; 591fff57c55Syamt char visbuf[2 * MAX_LOGLINE]; 592fff57c55Syamt 593fff57c55Syamt /* We don't care about truncation. */ 594fff57c55Syamt vsnprintf(buf, sizeof buf, message, ap); 595fff57c55Syamt #ifdef __NetBSD__ 596fff57c55Syamt size_t len = strlen(buf); 597fff57c55Syamt if (len > sizeof visbuf) { 598fff57c55Syamt len = sizeof visbuf; 599fff57c55Syamt } 600fff57c55Syamt strvisx(visbuf, buf, len, VIS_CSTYLE | VIS_NL); 601fff57c55Syamt #else 602fff57c55Syamt strnvis(visbuf, buf, sizeof visbuf, VIS_CSTYLE | VIS_NL); 603fff57c55Syamt #endif /* !__NetBSD__ */ 604fff57c55Syamt fprintf(stderr, "%s\n", visbuf); 605fff57c55Syamt } 606fff57c55Syamt 607fff57c55Syamt va_end(ap); 608fff57c55Syamt } 609fff57c55Syamt 610fff57c55Syamt int 611fff57c55Syamt main(int argc, char *argv[]) 612fff57c55Syamt { 613fff57c55Syamt struct rlimit rlp; 614fff57c55Syamt struct addrinfo hints, *res; 615fff57c55Syamt struct event ev, ev_sighup, ev_sigint, ev_sigterm; 616fff57c55Syamt int ch, error, listenfd, on; 617fff57c55Syamt const char *errstr = NULL; /* XXX gcc */ 618fff57c55Syamt 619fff57c55Syamt /* Defaults. */ 620fff57c55Syamt anonymous_only = 0; 621fff57c55Syamt daemonize = 1; 622fff57c55Syamt fixed_proxy = NULL; 623fff57c55Syamt fixed_server = NULL; 624fff57c55Syamt fixed_server_port = "21"; 625fff57c55Syamt ipv6_mode = 0; 626fff57c55Syamt listen_ip = NULL; 627fff57c55Syamt listen_port = "8021"; 628fff57c55Syamt loglevel = LOG_NOTICE; 629fff57c55Syamt max_sessions = 100; 630fff57c55Syamt qname = NULL; 631fff57c55Syamt rfc_mode = 0; 632fff57c55Syamt tagname = NULL; 633fff57c55Syamt timeout = 24 * 3600; 634fff57c55Syamt verbose = 0; 635fff57c55Syamt 636fff57c55Syamt /* Other initialization. */ 637fff57c55Syamt id_count = 1; 638fff57c55Syamt session_count = 0; 639fff57c55Syamt 64007ac07d3Srmind #if defined(__NetBSD__) 64107ac07d3Srmind /* Note: both for IPFilter and NPF. */ 64207ac07d3Srmind #define NBSD_OPTS "i:N:" 64307ac07d3Srmind #endif 64407ac07d3Srmind while ((ch = getopt(argc, argv, 64507ac07d3Srmind "6Aa:b:D:d" NBSD_OPTS "m:P:p:q:R:rT:t:v")) != -1) { 646fff57c55Syamt switch (ch) { 647fff57c55Syamt case '6': 648fff57c55Syamt ipv6_mode = 1; 649fff57c55Syamt break; 650fff57c55Syamt case 'A': 651fff57c55Syamt anonymous_only = 1; 652fff57c55Syamt break; 653fff57c55Syamt case 'a': 654fff57c55Syamt fixed_proxy = optarg; 655fff57c55Syamt break; 656fff57c55Syamt case 'b': 657fff57c55Syamt listen_ip = optarg; 658fff57c55Syamt break; 659fff57c55Syamt case 'D': 660fff57c55Syamt loglevel = strtonum(optarg, LOG_EMERG, LOG_DEBUG, 661fff57c55Syamt &errstr); 662fff57c55Syamt if (errstr) 663fff57c55Syamt errx(1, "loglevel %s", errstr); 664fff57c55Syamt break; 665fff57c55Syamt case 'd': 666fff57c55Syamt daemonize = 0; 667fff57c55Syamt break; 668fff57c55Syamt case 'i': 66907ac07d3Srmind #if defined(__NetBSD__) && defined(WITH_IPF) 67007ac07d3Srmind fops = &ipf_fprx_ops; 671fff57c55Syamt netif = optarg; 67207ac07d3Srmind #endif 673fff57c55Syamt break; 674fff57c55Syamt case 'm': 675fff57c55Syamt max_sessions = strtonum(optarg, 1, 500, &errstr); 676fff57c55Syamt if (errstr) 677fff57c55Syamt errx(1, "max sessions %s", errstr); 678fff57c55Syamt break; 67907ac07d3Srmind case 'N': 68007ac07d3Srmind #if defined(__NetBSD__) && defined(WITH_NPF) 68107ac07d3Srmind fops = &npf_fprx_ops; 68207ac07d3Srmind npfopts = optarg; 68307ac07d3Srmind #endif 68407ac07d3Srmind break; 685fff57c55Syamt case 'P': 686fff57c55Syamt fixed_server_port = optarg; 687fff57c55Syamt break; 688fff57c55Syamt case 'p': 689fff57c55Syamt listen_port = optarg; 690fff57c55Syamt break; 691fff57c55Syamt case 'q': 692fff57c55Syamt if (strlen(optarg) >= PF_QNAME_SIZE) 693fff57c55Syamt errx(1, "queuename too long"); 694fff57c55Syamt qname = optarg; 695fff57c55Syamt break; 696fff57c55Syamt case 'R': 697fff57c55Syamt fixed_server = optarg; 698fff57c55Syamt break; 699fff57c55Syamt case 'r': 700fff57c55Syamt rfc_mode = 1; 701fff57c55Syamt break; 702fff57c55Syamt case 'T': 703fff57c55Syamt if (strlen(optarg) >= PF_TAG_NAME_SIZE) 704fff57c55Syamt errx(1, "tagname too long"); 705fff57c55Syamt tagname = optarg; 706fff57c55Syamt break; 707fff57c55Syamt case 't': 708fff57c55Syamt timeout = strtonum(optarg, 0, 86400, &errstr); 709fff57c55Syamt if (errstr) 710fff57c55Syamt errx(1, "timeout %s", errstr); 711fff57c55Syamt break; 712fff57c55Syamt case 'v': 713fff57c55Syamt verbose++; 714fff57c55Syamt if (verbose > 2) 715fff57c55Syamt usage(); 716fff57c55Syamt break; 717fff57c55Syamt default: 718fff57c55Syamt usage(); 719fff57c55Syamt } 720fff57c55Syamt } 721fff57c55Syamt 722fff57c55Syamt if (listen_ip == NULL) 723fff57c55Syamt listen_ip = ipv6_mode ? "::1" : "127.0.0.1"; 724fff57c55Syamt 725fff57c55Syamt /* Check for root to save the user from cryptic failure messages. */ 726fff57c55Syamt if (getuid() != 0) 727fff57c55Syamt errx(1, "needs to start as root"); 728fff57c55Syamt 729fff57c55Syamt /* Raise max. open files limit to satisfy max. sessions. */ 730fff57c55Syamt rlp.rlim_cur = rlp.rlim_max = (2 * max_sessions) + 10; 731fff57c55Syamt if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) 732fff57c55Syamt err(1, "setrlimit"); 733fff57c55Syamt 734fff57c55Syamt if (fixed_proxy) { 735fff57c55Syamt memset(&hints, 0, sizeof hints); 736fff57c55Syamt hints.ai_flags = AI_NUMERICHOST; 737fff57c55Syamt hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; 738fff57c55Syamt hints.ai_socktype = SOCK_STREAM; 739fff57c55Syamt error = getaddrinfo(fixed_proxy, NULL, &hints, &res); 740fff57c55Syamt if (error) 741fff57c55Syamt errx(1, "getaddrinfo fixed proxy address failed: %s", 742fff57c55Syamt gai_strerror(error)); 743fff57c55Syamt memcpy(&fixed_proxy_ss, res->ai_addr, res->ai_addrlen); 744fff57c55Syamt logmsg(LOG_INFO, "using %s to connect to servers", 745fff57c55Syamt sock_ntop(sstosa(&fixed_proxy_ss))); 746fff57c55Syamt freeaddrinfo(res); 747fff57c55Syamt } 748fff57c55Syamt 749fff57c55Syamt if (fixed_server) { 750fff57c55Syamt memset(&hints, 0, sizeof hints); 751fff57c55Syamt hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; 752fff57c55Syamt hints.ai_socktype = SOCK_STREAM; 753fff57c55Syamt error = getaddrinfo(fixed_server, fixed_server_port, &hints, 754fff57c55Syamt &res); 755fff57c55Syamt if (error) 756fff57c55Syamt errx(1, "getaddrinfo fixed server address failed: %s", 757fff57c55Syamt gai_strerror(error)); 758fff57c55Syamt memcpy(&fixed_server_ss, res->ai_addr, res->ai_addrlen); 759fff57c55Syamt logmsg(LOG_INFO, "using fixed server %s", 760fff57c55Syamt sock_ntop(sstosa(&fixed_server_ss))); 761fff57c55Syamt freeaddrinfo(res); 762fff57c55Syamt } 763fff57c55Syamt 764fff57c55Syamt /* Setup listener. */ 765fff57c55Syamt memset(&hints, 0, sizeof hints); 766fff57c55Syamt hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE; 767fff57c55Syamt hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; 768fff57c55Syamt hints.ai_socktype = SOCK_STREAM; 769fff57c55Syamt error = getaddrinfo(listen_ip, listen_port, &hints, &res); 770fff57c55Syamt if (error) 771fff57c55Syamt errx(1, "getaddrinfo listen address failed: %s", 772fff57c55Syamt gai_strerror(error)); 773fff57c55Syamt if ((listenfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) == -1) 774fff57c55Syamt errx(1, "socket failed"); 775fff57c55Syamt on = 1; 776fff57c55Syamt if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, 777fff57c55Syamt sizeof on) != 0) 778fff57c55Syamt err(1, "setsockopt failed"); 779fff57c55Syamt if (bind(listenfd, (struct sockaddr *)res->ai_addr, 780fff57c55Syamt (socklen_t)res->ai_addrlen) != 0) 781fff57c55Syamt err(1, "bind failed"); 782fff57c55Syamt if (listen(listenfd, TCP_BACKLOG) != 0) 783fff57c55Syamt err(1, "listen failed"); 784fff57c55Syamt freeaddrinfo(res); 785fff57c55Syamt 786fff57c55Syamt /* Initialize pf. */ 78707ac07d3Srmind fops->init_filter(qname, tagname, verbose); 788fff57c55Syamt 789fff57c55Syamt if (daemonize) { 790fff57c55Syamt if (daemon(0, 0) == -1) 791fff57c55Syamt err(1, "cannot daemonize"); 792fff57c55Syamt openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); 793fff57c55Syamt } 794fff57c55Syamt 795fff57c55Syamt /* Use logmsg for output from here on. */ 796fff57c55Syamt 797fff57c55Syamt if (!drop_privs()) { 798fff57c55Syamt logmsg(LOG_ERR, "cannot drop privileges: %s", strerror(errno)); 799fff57c55Syamt exit(1); 800fff57c55Syamt } 801fff57c55Syamt 802fff57c55Syamt event_init(); 803fff57c55Syamt 804fff57c55Syamt /* Setup signal handler. */ 805fff57c55Syamt signal(SIGPIPE, SIG_IGN); 806fff57c55Syamt signal_set(&ev_sighup, SIGHUP, handle_signal, NULL); 807fff57c55Syamt signal_set(&ev_sigint, SIGINT, handle_signal, NULL); 808fff57c55Syamt signal_set(&ev_sigterm, SIGTERM, handle_signal, NULL); 809fff57c55Syamt signal_add(&ev_sighup, NULL); 810fff57c55Syamt signal_add(&ev_sigint, NULL); 811fff57c55Syamt signal_add(&ev_sigterm, NULL); 812fff57c55Syamt 813fff57c55Syamt event_set(&ev, listenfd, EV_READ | EV_PERSIST, handle_connection, &ev); 814fff57c55Syamt event_add(&ev, NULL); 815fff57c55Syamt 816fff57c55Syamt logmsg(LOG_NOTICE, "listening on %s port %s", listen_ip, listen_port); 817fff57c55Syamt 818fff57c55Syamt /* Vroom, vroom. */ 819fff57c55Syamt event_dispatch(); 820fff57c55Syamt 821fff57c55Syamt logmsg(LOG_ERR, "event_dispatch error: %s", strerror(errno)); 822fff57c55Syamt exit_daemon(); 823fff57c55Syamt 824fff57c55Syamt /* NOTREACHED */ 825fff57c55Syamt return (1); 826fff57c55Syamt } 827fff57c55Syamt 828fff57c55Syamt u_int16_t 829fff57c55Syamt parse_port(int mode) 830fff57c55Syamt { 831fff57c55Syamt unsigned int port, v[6]; 832fff57c55Syamt int n; 833fff57c55Syamt char *p; 834fff57c55Syamt 835fff57c55Syamt /* Find the last space or left-parenthesis. */ 836fff57c55Syamt for (p = linebuf + linelen; p > linebuf; p--) 837fff57c55Syamt if (*p == ' ' || *p == '(') 838fff57c55Syamt break; 839fff57c55Syamt if (p == linebuf) 840fff57c55Syamt return (0); 841fff57c55Syamt 842fff57c55Syamt switch (mode) { 843fff57c55Syamt case CMD_PORT: 844fff57c55Syamt n = sscanf(p, " %u,%u,%u,%u,%u,%u", &v[0], &v[1], &v[2], 845fff57c55Syamt &v[3], &v[4], &v[5]); 846fff57c55Syamt if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 && 847fff57c55Syamt v[3] < 256 && v[4] < 256 && v[5] < 256) 848fff57c55Syamt return ((v[4] << 8) | v[5]); 849fff57c55Syamt break; 850fff57c55Syamt case CMD_PASV: 851fff57c55Syamt n = sscanf(p, "(%u,%u,%u,%u,%u,%u)", &v[0], &v[1], &v[2], 852fff57c55Syamt &v[3], &v[4], &v[5]); 853fff57c55Syamt if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 && 854fff57c55Syamt v[3] < 256 && v[4] < 256 && v[5] < 256) 855fff57c55Syamt return ((v[4] << 8) | v[5]); 856fff57c55Syamt break; 857fff57c55Syamt case CMD_EPSV: 858fff57c55Syamt n = sscanf(p, "(|||%u|)", &port); 859fff57c55Syamt if (n == 1 && port < 65536) 860fff57c55Syamt return (port); 861fff57c55Syamt break; 862fff57c55Syamt case CMD_EPRT: 863fff57c55Syamt n = sscanf(p, " |1|%u.%u.%u.%u|%u|", &v[0], &v[1], &v[2], 864fff57c55Syamt &v[3], &port); 865fff57c55Syamt if (n == 5 && v[0] < 256 && v[1] < 256 && v[2] < 256 && 866fff57c55Syamt v[3] < 256 && port < 65536) 867fff57c55Syamt return (port); 868fff57c55Syamt n = sscanf(p, " |2|%*[a-fA-F0-9:]|%u|", &port); 869fff57c55Syamt if (n == 1 && port < 65536) 870fff57c55Syamt return (port); 871fff57c55Syamt break; 872fff57c55Syamt default: 873fff57c55Syamt return (0); 874fff57c55Syamt } 875fff57c55Syamt 876fff57c55Syamt return (0); 877fff57c55Syamt } 878fff57c55Syamt 879fff57c55Syamt u_int16_t 880fff57c55Syamt pick_proxy_port(void) 881fff57c55Syamt { 882fff57c55Syamt /* Random should be good enough for avoiding port collisions. */ 883fff57c55Syamt return (IPPORT_HIFIRSTAUTO + (arc4random() % 884fff57c55Syamt (IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO))); 885fff57c55Syamt } 886fff57c55Syamt 887fff57c55Syamt void 888fff57c55Syamt proxy_reply(int cmd, struct sockaddr *sa, u_int16_t port) 889fff57c55Syamt { 890fff57c55Syamt int i, r = -1; 891fff57c55Syamt 892fff57c55Syamt switch (cmd) { 893fff57c55Syamt case CMD_PORT: 894fff57c55Syamt r = snprintf(linebuf, sizeof linebuf, 895fff57c55Syamt "PORT %s,%u,%u\r\n", sock_ntop(sa), port / 256, 896fff57c55Syamt port % 256); 897fff57c55Syamt break; 898fff57c55Syamt case CMD_PASV: 899fff57c55Syamt r = snprintf(linebuf, sizeof linebuf, 900fff57c55Syamt "227 Entering Passive Mode (%s,%u,%u)\r\n", sock_ntop(sa), 901fff57c55Syamt port / 256, port % 256); 902fff57c55Syamt break; 903fff57c55Syamt case CMD_EPRT: 904fff57c55Syamt if (sa->sa_family == AF_INET) 905fff57c55Syamt r = snprintf(linebuf, sizeof linebuf, 906fff57c55Syamt "EPRT |1|%s|%u|\r\n", sock_ntop(sa), port); 907fff57c55Syamt else if (sa->sa_family == AF_INET6) 908fff57c55Syamt r = snprintf(linebuf, sizeof linebuf, 909fff57c55Syamt "EPRT |2|%s|%u|\r\n", sock_ntop(sa), port); 910fff57c55Syamt break; 911fff57c55Syamt case CMD_EPSV: 912fff57c55Syamt r = snprintf(linebuf, sizeof linebuf, 913fff57c55Syamt "229 Entering Extended Passive Mode (|||%u|)\r\n", port); 914fff57c55Syamt break; 915fff57c55Syamt } 916fff57c55Syamt 917fff57c55Syamt if (r < 0 || r >= sizeof linebuf) { 918fff57c55Syamt logmsg(LOG_ERR, "proxy_reply failed: %d", r); 919fff57c55Syamt linebuf[0] = '\0'; 920fff57c55Syamt linelen = 0; 921fff57c55Syamt return; 922fff57c55Syamt } 923fff57c55Syamt linelen = (size_t)r; 924fff57c55Syamt 925fff57c55Syamt if (cmd == CMD_PORT || cmd == CMD_PASV) { 926fff57c55Syamt /* Replace dots in IP address with commas. */ 927fff57c55Syamt for (i = 0; i < linelen; i++) 928fff57c55Syamt if (linebuf[i] == '.') 929fff57c55Syamt linebuf[i] = ','; 930fff57c55Syamt } 931fff57c55Syamt } 932fff57c55Syamt 933fff57c55Syamt void 934fff57c55Syamt server_error(struct bufferevent *bufev, short what, void *arg) 935fff57c55Syamt { 936fff57c55Syamt struct session *s = arg; 937fff57c55Syamt 938fff57c55Syamt if (what & EVBUFFER_EOF) 939fff57c55Syamt logmsg(LOG_INFO, "#%d server close", s->id); 940fff57c55Syamt else if (what == (EVBUFFER_ERROR | EVBUFFER_READ)) 941fff57c55Syamt logmsg(LOG_ERR, "#%d server refused connection", s->id); 942fff57c55Syamt else if (what & EVBUFFER_WRITE) 943fff57c55Syamt logmsg(LOG_ERR, "#%d server write error: %d", s->id, what); 944fff57c55Syamt else if (what & EVBUFFER_TIMEOUT) 945fff57c55Syamt logmsg(LOG_NOTICE, "#%d server timeout", s->id); 946fff57c55Syamt else 947fff57c55Syamt logmsg(LOG_ERR, "#%d abnormal server error: %d", s->id, what); 948fff57c55Syamt 949fff57c55Syamt end_session(s); 950fff57c55Syamt } 951fff57c55Syamt 952fff57c55Syamt int 953fff57c55Syamt server_parse(struct session *s) 954fff57c55Syamt { 955fff57c55Syamt if (s->cmd == CMD_NONE || linelen < 4 || linebuf[0] != '2') 956fff57c55Syamt goto out; 957fff57c55Syamt 958fff57c55Syamt if ((s->cmd == CMD_PASV && strncmp("227 ", linebuf, 4) == 0) || 959fff57c55Syamt (s->cmd == CMD_EPSV && strncmp("229 ", linebuf, 4) == 0)) 960fff57c55Syamt return (allow_data_connection(s)); 961fff57c55Syamt 962fff57c55Syamt out: 963fff57c55Syamt s->cmd = CMD_NONE; 964fff57c55Syamt s->port = 0; 965fff57c55Syamt 966fff57c55Syamt return (1); 967fff57c55Syamt } 968fff57c55Syamt 969fff57c55Syamt int 970fff57c55Syamt allow_data_connection(struct session *s) 971fff57c55Syamt { 972fff57c55Syamt struct sockaddr *client_sa, *orig_sa, *proxy_sa, *server_sa; 973fff57c55Syamt int prepared = 0; 974fff57c55Syamt 975fff57c55Syamt /* 976fff57c55Syamt * The pf rules below do quite some NAT rewriting, to keep up 977fff57c55Syamt * appearances. Points to keep in mind: 978fff57c55Syamt * 1) The client must think it's talking to the real server, 979fff57c55Syamt * for both control and data connections. Transparently. 980fff57c55Syamt * 2) The server must think that the proxy is the client. 981fff57c55Syamt * 3) Source and destination ports are rewritten to minimize 982fff57c55Syamt * port collisions, to aid security (some systems pick weak 983fff57c55Syamt * ports) or to satisfy RFC requirements (source port 20). 984fff57c55Syamt */ 985fff57c55Syamt 986fff57c55Syamt /* Cast this once, to make code below it more readable. */ 987fff57c55Syamt client_sa = sstosa(&s->client_ss); 988fff57c55Syamt server_sa = sstosa(&s->server_ss); 989fff57c55Syamt proxy_sa = sstosa(&s->proxy_ss); 990fff57c55Syamt if (fixed_server) 991fff57c55Syamt /* Fixed server: data connections must appear to come 992fff57c55Syamt from / go to the original server, not the fixed one. */ 993fff57c55Syamt orig_sa = sstosa(&s->orig_server_ss); 994fff57c55Syamt else 995fff57c55Syamt /* Server not fixed: orig_server == server. */ 996fff57c55Syamt orig_sa = sstosa(&s->server_ss); 997fff57c55Syamt 998fff57c55Syamt /* Passive modes. */ 999fff57c55Syamt if (s->cmd == CMD_PASV || s->cmd == CMD_EPSV) { 1000fff57c55Syamt s->port = parse_port(s->cmd); 1001fff57c55Syamt if (s->port < MIN_PORT) { 1002fff57c55Syamt logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id, 1003fff57c55Syamt linebuf); 1004fff57c55Syamt return (0); 1005fff57c55Syamt } 1006fff57c55Syamt s->proxy_port = pick_proxy_port(); 1007fff57c55Syamt logmsg(LOG_INFO, "#%d passive: client to server port %d" 1008fff57c55Syamt " via port %d", s->id, s->port, s->proxy_port); 1009fff57c55Syamt 101007ac07d3Srmind if (fops->prepare_commit(s->id) == -1) 1011fff57c55Syamt goto fail; 1012fff57c55Syamt prepared = 1; 1013fff57c55Syamt 1014fff57c55Syamt proxy_reply(s->cmd, orig_sa, s->proxy_port); 1015fff57c55Syamt logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); 1016fff57c55Syamt 1017fff57c55Syamt /* rdr from $client to $orig_server port $proxy_port -> $server 1018fff57c55Syamt port $port */ 101907ac07d3Srmind if (fops->add_rdr(s->id, client_sa, orig_sa, s->proxy_port, 1020fff57c55Syamt server_sa, s->port) == -1) 1021fff57c55Syamt goto fail; 1022fff57c55Syamt 1023fff57c55Syamt /* nat from $client to $server port $port -> $proxy */ 102407ac07d3Srmind if (fops->add_nat(s->id, client_sa, server_sa, s->port, 102507ac07d3Srmind proxy_sa, PF_NAT_PROXY_PORT_LOW, PF_NAT_PROXY_PORT_HIGH) 102607ac07d3Srmind == -1) 1027fff57c55Syamt goto fail; 1028fff57c55Syamt 1029fff57c55Syamt /* pass in from $client to $server port $port */ 103007ac07d3Srmind if (fops->add_filter(s->id, PF_IN, client_sa, server_sa, 1031fff57c55Syamt s->port) == -1) 1032fff57c55Syamt goto fail; 1033fff57c55Syamt 1034fff57c55Syamt /* pass out from $proxy to $server port $port */ 103507ac07d3Srmind if (fops->add_filter(s->id, PF_OUT, proxy_sa, server_sa, 1036fff57c55Syamt s->port) == -1) 1037fff57c55Syamt goto fail; 1038fff57c55Syamt } 1039fff57c55Syamt 1040fff57c55Syamt /* Active modes. */ 1041fff57c55Syamt if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) { 1042fff57c55Syamt logmsg(LOG_INFO, "#%d active: server to client port %d" 1043fff57c55Syamt " via port %d", s->id, s->port, s->proxy_port); 1044fff57c55Syamt 104507ac07d3Srmind if (fops->prepare_commit(s->id) == -1) 1046fff57c55Syamt goto fail; 1047fff57c55Syamt prepared = 1; 1048fff57c55Syamt 1049fff57c55Syamt /* rdr from $server to $proxy port $proxy_port -> $client port 1050fff57c55Syamt $port */ 105107ac07d3Srmind if (fops->add_rdr(s->id, server_sa, proxy_sa, 105207ac07d3Srmind s->proxy_port, client_sa, s->port) == -1) 1053fff57c55Syamt goto fail; 1054fff57c55Syamt 1055fff57c55Syamt /* nat from $server to $client port $port -> $orig_server port 1056fff57c55Syamt $natport */ 1057fff57c55Syamt if (rfc_mode && s->cmd == CMD_PORT) { 1058fff57c55Syamt /* Rewrite sourceport to RFC mandated 20. */ 105907ac07d3Srmind if (fops->add_nat(s->id, server_sa, client_sa, 106007ac07d3Srmind s->port, orig_sa, 20, 20) == -1) 1061fff57c55Syamt goto fail; 1062fff57c55Syamt } else { 1063fff57c55Syamt /* Let pf pick a source port from the standard range. */ 106407ac07d3Srmind if (fops->add_nat(s->id, server_sa, client_sa, 106507ac07d3Srmind s->port, orig_sa, PF_NAT_PROXY_PORT_LOW, 1066fff57c55Syamt PF_NAT_PROXY_PORT_HIGH) == -1) 1067fff57c55Syamt goto fail; 1068fff57c55Syamt } 1069fff57c55Syamt 1070fff57c55Syamt /* pass in from $server to $client port $port */ 107107ac07d3Srmind if (fops->add_filter(s->id, PF_IN, server_sa, client_sa, 107207ac07d3Srmind s->port) == -1) 1073fff57c55Syamt goto fail; 1074fff57c55Syamt 1075fff57c55Syamt /* pass out from $orig_server to $client port $port */ 107607ac07d3Srmind if (fops->add_filter(s->id, PF_OUT, orig_sa, client_sa, 107707ac07d3Srmind s->port) == -1) 1078fff57c55Syamt goto fail; 1079fff57c55Syamt } 1080fff57c55Syamt 1081fff57c55Syamt /* Commit rules if they were prepared. */ 108207ac07d3Srmind if (prepared && (fops->do_commit() == -1)) { 1083fff57c55Syamt if (errno != EBUSY) 1084fff57c55Syamt goto fail; 1085fff57c55Syamt /* One more try if busy. */ 1086fff57c55Syamt usleep(5000); 108707ac07d3Srmind if (fops->do_commit() == -1) 1088fff57c55Syamt goto fail; 1089fff57c55Syamt } 1090fff57c55Syamt 1091fff57c55Syamt s->cmd = CMD_NONE; 1092fff57c55Syamt s->port = 0; 1093fff57c55Syamt 1094fff57c55Syamt return (1); 1095fff57c55Syamt 1096fff57c55Syamt fail: 1097fff57c55Syamt logmsg(LOG_CRIT, "#%d pf operation failed: %s", s->id, strerror(errno)); 1098fff57c55Syamt if (prepared) 109907ac07d3Srmind fops->do_rollback(); 1100fff57c55Syamt return (0); 1101fff57c55Syamt } 1102fff57c55Syamt 1103fff57c55Syamt void 1104fff57c55Syamt server_read(struct bufferevent *bufev, void *arg) 1105fff57c55Syamt { 1106fff57c55Syamt struct session *s = arg; 1107fff57c55Syamt size_t buf_avail, nread; 1108fff57c55Syamt int n; 1109fff57c55Syamt 1110fff57c55Syamt bufferevent_settimeout(bufev, timeout, 0); 1111fff57c55Syamt 1112fff57c55Syamt do { 1113fff57c55Syamt buf_avail = sizeof s->sbuf - s->sbuf_valid; 1114fff57c55Syamt nread = bufferevent_read(bufev, s->sbuf + s->sbuf_valid, 1115fff57c55Syamt buf_avail); 1116fff57c55Syamt s->sbuf_valid += nread; 1117fff57c55Syamt 11187027866aSroy while ((n = get_line(s->sbuf, &s->sbuf_valid)) > 0) { 1119fff57c55Syamt logmsg(LOG_DEBUG, "#%d server: %s", s->id, linebuf); 1120fff57c55Syamt if (!server_parse(s)) { 1121fff57c55Syamt end_session(s); 1122fff57c55Syamt return; 1123fff57c55Syamt } 1124fff57c55Syamt bufferevent_write(s->client_bufev, linebuf, linelen); 1125fff57c55Syamt } 1126fff57c55Syamt 1127fff57c55Syamt if (n == -1) { 1128fff57c55Syamt logmsg(LOG_ERR, "#%d server reply too long or not" 1129fff57c55Syamt " clean", s->id); 1130fff57c55Syamt end_session(s); 1131fff57c55Syamt return; 1132fff57c55Syamt } 1133fff57c55Syamt } while (nread == buf_avail); 1134fff57c55Syamt } 1135fff57c55Syamt 1136fff57c55Syamt const char * 1137fff57c55Syamt sock_ntop(struct sockaddr *sa) 1138fff57c55Syamt { 1139fff57c55Syamt static int n = 0; 1140fff57c55Syamt 1141fff57c55Syamt /* Cycle to next buffer. */ 1142fff57c55Syamt n = (n + 1) % NTOP_BUFS; 1143fff57c55Syamt ntop_buf[n][0] = '\0'; 1144fff57c55Syamt 1145fff57c55Syamt if (sa->sa_family == AF_INET) { 1146fff57c55Syamt struct sockaddr_in *sin = (struct sockaddr_in *)sa; 1147fff57c55Syamt 1148fff57c55Syamt return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n], 1149fff57c55Syamt sizeof ntop_buf[0])); 1150fff57c55Syamt } 1151fff57c55Syamt 1152fff57c55Syamt if (sa->sa_family == AF_INET6) { 1153fff57c55Syamt struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; 1154fff57c55Syamt 1155fff57c55Syamt return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n], 1156fff57c55Syamt sizeof ntop_buf[0])); 1157fff57c55Syamt } 1158fff57c55Syamt 1159fff57c55Syamt return (NULL); 1160fff57c55Syamt } 1161fff57c55Syamt 1162fff57c55Syamt void 1163fff57c55Syamt usage(void) 1164fff57c55Syamt { 1165fff57c55Syamt fprintf(stderr, "usage: %s [-6Adrv] [-a address] [-b address]" 1166fff57c55Syamt " [-D level] [-m maxsessions]\n [-P port]" 116707ac07d3Srmind #if defined(__NetBSD__) 116807ac07d3Srmind #if defined(WITH_IPF) 1169fff57c55Syamt " [-i netif]" 117007ac07d3Srmind #endif 117107ac07d3Srmind #if defined(WITH_NPF) 117207ac07d3Srmind " [-N netif:addr:port]" 117307ac07d3Srmind #endif 117407ac07d3Srmind #endif 1175fff57c55Syamt " [-p port] [-q queue] [-R address] [-T tag] [-t timeout]\n", 1176fff57c55Syamt __progname); 1177fff57c55Syamt 1178fff57c55Syamt exit(1); 1179fff57c55Syamt } 1180