/* This file is in the public domain. */ /* * Defaults. All of these are overridable on the command line. * * devpath is the path to the PF device (almost never anything but * /dev/pf). * * pfctl is the path to the pfctl executable. Usually /sbin/pfctl. * * devnull is the path to /dev/null. Usually /dev/null (duh!). * * port is the port number the daemon lists on for rdred connections. * (It always listens on 127.0.0.1 and ::1.) */ static const char *devpath = "/dev/pf"; static const char *pfctl = "/sbin/pfctl"; static const char *devnull = "/dev/null"; static int port = 2121; /* Include files. Boring. */ /* Paper over bugs in the include files */ /* depends on u_long */ #include /* depends on various things */ #include #include #include /* End bug-papering-over */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pollloop.h" /* For use in error messages. */ extern const char *__progname; /* File descriptor open on /dev/pf. */ static int pffd; /* Constants for the telnet protocol. From RFC 854. */ #define TELNET_SE 240 #define TELNET_NOP 241 #define TELNET_DM 242 #define TELNET_BRK 243 #define TELNET_IP 244 #define TELNET_AO 245 #define TELNET_AYT 246 #define TELNET_EC 247 #define TELNET_EL 248 #define TELNET_GA 249 #define TELNET_SB 250 #define TELNET_WILL 251 #define TELNET_WONT 252 #define TELNET_DO 253 #define TELNET_DONT 254 #define TELNET_IAC 255 /* Typedefs for struct types. See the definitions, below. */ typedef struct istr ISTR; typedef struct ostr OSTR; typedef struct oq OQ; typedef struct tnstate TNSTATE; typedef struct ftpstate FTPSTATE; typedef struct lq LQ; typedef struct lqe LQE; typedef struct cip CIP; typedef struct rdr RDR; /* * An RDR corresponds to a transient pf rdr rule installed for handling * data connections. These are kept in a doubly-linked list (to make * it easy to delete one of them). * * One of these corresponds to a rule * * rdr pass on IF AF proto tcp from F to TH port = TP -> RH port RP * * where * IF is the interface named on the command line * AF is "inet" if af is AF_INET and "inet6" if AF_INET6 * F is the fhost struct element * TH is the thost struct element * TP is the tport struct element * RH is the rhost struct element * RP is the rport struct element */ struct rdr { RDR *flink; RDR *blink; int af; char *fhost; char *thost; char *tport; char *rhost; char *rport; } ; /* * A CIP is a connection-in-progress (the name is an acronym). * * fd is the descriptor open on the socket. * id is the poll ID (from add_poll_fd). * (*fail)() is a callback called if the connection fails. * args: cookie, errno * (*ok)() is a callback called if the connection succeeds. * args: cookie, fd * arg is the cookie passed as the first arg to the callbacks. */ struct cip { int fd; int id; void (*fail)(void *, int); void (*ok)(void *, int); void *arg; } ; /* * An LQ is a line queue. These are used to store the lines in various * places in between their being received and their being processed. * (The actual lines are stored in LQEs.) The LQEs form a tconc * queue in q and qt. */ struct lq { LQE *q; LQE **qt; } ; /* * An LQE is an entry in an LQ. * * body is the body of the line. * len is the line's length. * flg is opaque to the LQ code. */ struct lqe { LQE *link; char *body; int len; unsigned int flg; } ; /* * A TNSTATE represents the state of the telnet protocol in one * direction. * * state is the state of the state machine processing IACs and the * octets that go with them. * line is the buffer wherein the line is accumulated. * linel is line's current length. * linea is the amount of space allocated to line (the most linel * can be before needing to realloc line). * flags is a few flag bits. * pass is the OSTR to queue things on to pass them on. * back is the OSTR to queue things on to return them to the * sender of the data being processed. * linefn is called when a complete line is accumulated. * * You can think of flags as additional state, but it's easier to store * them separately, instead of multiplying most of the state values by * the possible flag bit combinations. You could also think of them * as state for a secondary state machine driven off telnet protocol * items - data octets and other data like IAC-IP. */ struct tnstate { FTPSTATE *fs; int state; #define TNS_DATA 1 /* normal data */ #define TNS_IAC 2 /* saw IAC */ #define TNS_SB 3 /* saw IAC SB ... */ #define TNS_SBIAC 4 /* saw IAC SB ... IAC */ #define TNS_WILL 5 /* saw IAC WILL */ #define TNS_WONT 6 /* saw IAC WONT */ #define TNS_DO 7 /* saw IAC DO */ #define TNS_DONT 8 /* saw IAC DONT */ char *line; int linel; int linea; int flags; #define TNS_F_ATCR 0x00000001 /* just after CR */ #define TNS_F_ATIP 0x00000002 /* just after Telnet IP */ #define TNS_F_MARKED 0x00000004 /* IP-Synch seen */ OSTR *pass; OSTR *back; void (*linefn)(TNSTATE *); } ; /* * An OQ is a queue of data blocks waiting to be written on an OSTR. * * type is the type of the block: * DATA is normal data. * EOF represents end-of-data. * URG represents data associated with an urgent TCP send, * or as close as we can come to it, what with the BSD * bastardization of the urgent pointer into OOB data. * buf holds the data. * ptr is the current point in buf. * len is the total length of buf. * tofree is a pointer to free when this OQ is finished and is * being discarded. */ struct oq { OQ *link; int type; #define OQT_DATA 1 #define OQT_EOF 2 #define OQT_URG 3 const char *buf; int ptr; int len; char *tofree; } ; /* * An ISTR is an input stream. * * fd is the underlying file descriptor. * eof is a boolean indicating whether we've seen EOF. * id is the poll id, from add_poll_fd. * rtest is a ready-to-read test, called in addition to checking * for already-saw-EOF. * data is called when something is received. * args: cookie, buffer, length, is-urgent * length==0 indicates EOF * rderr is called on a read error. * args: cookie errno * arg is an opaque cookie passed as the first argument to the * callback functions. */ struct istr { int fd; int eof; int id; int (*rtest)(void *); void (*data)(void *, const void *, int, int); void (*rderr)(void *, int); void *arg; } ; /* * An OSTR is an output stream. * * fd is the underlying file descriptor. * q/qt are a tconc structure holding the output queue of OQs. * qlen is the number of data bytes in the output queue. * id is the poll id, from add_poll_fd. * wrerr is called on a write error. * args: cookie errno * arg is passed as the first arg to wrerr. */ struct ostr { int fd; OQ *q; OQ **qt; int qlen; int id; void (*wrerr)(void *, int); void *arg; } ; /* * An FTPSTATE encapsulates the state of an FTP session. * * state is the state of the session. (Whether a data connection * exists and whether bits are flowing over it if so are * represented in datamode, not here; as far as this state is * concerned, a data transfer is simply a long delay between a * 1xx reply to a transfer command (eg GET) and a 2xx reply.) * flags is flag bits. * tag is an ID string unique to this connection, used for * logging. * respfn is a function to be called when a response is received * from the server; it is valid only for states RESPA and RESPB. * args: ftpstate, key * ftpstate is the FTPSTATE pointer * key is the first character of the response code (as a * character, eg '2', not a number, eg 2). * datamode records the state of the data connection; see below * for more. * ccfd is the file descriptor for the control connection to the * client. * scfd is the file descriptor for the control connection to the * server. * ctl_[cs]_[io] are the input and output streams to the client * and server, for the control connection. * ctl_[cs]_tn are the telnet states for the client and server. * resplq holds the lines of an FTP control-connection response * between our receiving the first line and our receiving the * last. * [cs]clq hold lines received but not yet even looked at from the * client and server. * [cs]clq_id are the block function IDs from add_block_fn for the * code to check [cs]clq. * {ctl,data}_{,o}[cs]_ss are the various addresses for ends of * connections. ctl_ is for the control connection; data_ for * the data connection (see below for more on data connections). * The *c_ss addresses are for a connection from us to the * client, *s_ss, to the server; _?_ss are for the far end of * the connection, _o?_ss for our end. (_oc_ss values hold the * redirected addresses, not the addresses the client sees as * its peer.) * [cs]dfd are the file descriptors for data connections to the * client and server; see below for more. * data_[cs][io] are the input and output streams to the client * and server, for data connections; see below for more. * dataid is an ID used by the data connection code; see below for * more. * die_id is a block ID used to wait for control connection * shutdown in most states, or to wait for data to drain when * shutting down. * rdr is the rdr rule for the data connection, if any. * cip is a connection in progress; depending on the state, this * may be control or data. * datacount is a count of data connections. * dataib is a count of data bytes, server->client. * dataob is a count of data bytes, client->server. * datacib is a count of data bytes, server->client, for just the * current data connection. * datacob is a count of data bytes, client->server, for just the * current data connection. * * datamode values and which other fields are valid: * * NONE: * There is no data connection. * data_c_ss not valid * data_oc_ss not valid * data_os_ss not valid * data_s_ss not valid * cdfd not valid * sdfd not valid * dataid not used * rdr nil * PORT, EPRT: * We have received PORT or EPRT from the client, and constructed * and sent a suitable command to the server, but not yet received * a data connection. * data_c_ss valid * data_oc_ss not valid * data_os_ss valid * data_s_ss not valid * cdfd not valid * sdfd valid * dataid listen socket's poll * rdr nil * PORTC, EPRTC: * We have fielded the data connection from the server and have * initiated the data connection to the client, but it's not yet * completed. * data_c_ss valid * data_oc_ss valid * data_os_ss valid * data_s_ss valid * cdfd valid * sdfd valid * dataid not used * rdr nil * PASVA, EPSVA: * We have received PASV or EPSV from the client and sent it * along, but have not yet received any response. * data_c_ss not valid * data_oc_ss not valid * data_os_ss not valid * data_s_ss not valid * cdfd not valid * sdfd not valid * dataid not used * rdr nil * PASVB, EPSVB: * We have received response from the server, and constructed and * sent a response to the client, but not yet received a data * connection. * data_c_ss not valid * data_oc_ss valid * data_os_ss not valid * data_s_ss valid * cdfd not valid * sdfd valid * dataid listen socket's poll * rdr valid * PASVC, EPSVC: * We have fielded data connection from the client and have * initiated the data connection with the server, but it's not yet * completed. * data_c_ss valid * data_oc_ss valid * data_os_ss valid * data_s_ss valid * cdfd valid * sdfd valid * dataid not used * rdr valid * LIVE: * The data connection is fully up. * data_c_ss valid * data_oc_ss valid * data_os_ss valid * data_s_ss valid * cdfd valid * sdfd valid * dataid check-done block * rdr nil if via PORT/EPRT, valid if via PASV/EPSV * * Note that once the data connection goes fully live, we don't care * which direction data is flowing on it and we don't care how it was * established. In particular, a single data connection may serve for * multiple transfers if the transfer parameters are set * appropriately, so data may flow one way now and the other way * later. */ struct ftpstate { int state; #define FTPS_NONE 1 /* "impossible" transient state during startup */ #define FTPS_KILL 2 /* killed, output may need draining */ #define FTPS_DEAD 3 /* killed, output drained */ #define FTPS_CONN 4 /* opening control connection to server */ #define FTPS_RESPA 5 /* awaiting beginning of server response */ #define FTPS_RESPB 6 /* partway through a multiline server response */ #define FTPS_CMD 7 /* awaiting client command */ #define FTPS_EOF 8 /* client sent EOF */ unsigned int flags; #define FSF_EVERLIVE 0x00000001 /* control connection came up fully */ char *tag; void (*respfn)(FTPSTATE *, char); int datamode; #define DATA_NONE 1 #define DATA_PORT 2 #define DATA_PORTC 3 #define DATA_PASVA 4 #define DATA_PASVB 5 #define DATA_PASVC 6 #define DATA_EPRT 7 #define DATA_EPRTC 8 #define DATA_EPSVA 9 #define DATA_EPSVB 10 #define DATA_EPSVC 11 #define DATA_LIVE 12 int ccfd; int scfd; ISTR ctl_c_i; OSTR ctl_c_o; ISTR ctl_s_i; OSTR ctl_s_o; TNSTATE ctl_c_tn; TNSTATE ctl_s_tn; LQ resplq; LQ cclq; int cclq_id; LQ sclq; int sclq_id; struct sockaddr_storage ctl_c_ss; struct sockaddr_storage ctl_oc_ss; struct sockaddr_storage ctl_os_ss; struct sockaddr_storage ctl_s_ss; struct sockaddr_storage data_c_ss; struct sockaddr_storage data_oc_ss; struct sockaddr_storage data_os_ss; struct sockaddr_storage data_s_ss; int cdfd; int sdfd; ISTR data_c_i; OSTR data_c_o; ISTR data_s_i; OSTR data_s_o; int dataid; int die_id; RDR *rdr; CIP *cip; unsigned int datacount; unsigned long long int dataib; unsigned long long int dataob; unsigned long long int datacib; unsigned long long int datacob; } ; /* * Global variables. There aren't many of these. * * listen_[46] are the file descriptors listening for redirected * control connections on IPv4 and IPv6. * * localhost_v[46] are the loopback addresses for IPv4 and IPv6. * * rdrs is a list of RDRs. This list is walked to produce the list of * rules fed to pfctl. * * rlfd is the comm channel to the rules loader (which is responsible * for running pfctl when necessary). * * nullfd is a descriptor open onto /dev/null. * * anchor is the name of the anchor into which data-connection rdr * rules are put. * * intf is the interface name used in data-connection rdr rules. * * serial is a serial number, used for generating ID strings. * * verbose is a boolean indicating whether we should be blabby. * Verbose mode is not useful for much but debugging. */ static int listen_4; static int listen_6; static struct sockaddr_storage localhost_v4; static struct sockaddr_storage localhost_v6; static RDR *rdrs; static int rlfd; static int nullfd; static const char *anchor; static const char *intf; static unsigned int serial = 0; static int verbose; /* * Open the PF device. If we can't, cough and die. */ static void openpf(void) { pffd = open(devpath,O_RDWR,0); if (pffd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,devpath,strerror(errno)); exit(1); } } /* * Wrapper, for ioctl calls that "cannot fail" (or at least must not). */ static void Ioctl_(int fd, unsigned long int req, void *arg, const char *reqname) { int rv; rv = ioctl(fd,req,arg); if (rv < 0) { fprintf(stderr,"%s: %s ioctl: %s\n",__progname,reqname,strerror(errno)); exit(1); } } #define Ioctl(a,b,c) Ioctl_((a),(b),(c),#b) /* * Encapsulate the question "does this OSTR have nothing to send?". */ static int ostr_empty(OSTR *o) { return(!o->q); } /* * Encapsulate finding the amount of data queued in an OSTR. */ static int ostr_qlen(OSTR *o) { return(o->qlen); } /* * Clear out an ISTR, throwing away any lingering resources. Does not * free the ISTR itself. */ static void istr_kill(ISTR *i) { if (i->id != PL_NOID) remove_poll_id(i->id); i->id = PL_NOID; } /* * Clear out an OSTR, throwing away any lingering resources. Does not * free the OSTR itself. */ static void ostr_kill(OSTR *o) { OQ *q; if (o->id != PL_NOID) remove_poll_id(o->id); o->id = PL_NOID; while ((q = o->q)) { o->q = q->link; free(q->tofree); free(q); } o->qt = &o->q; } /* * Write to the rule loader process. This is passed as a write * function to fwopen. */ static int rdr_write(void *cookie __attribute__((__unused__)), const char *buf, int len) { int w; if (verbose) write(1,buf,len); w = write(rlfd,buf,len); if (w < 0) { fprintf(stderr,"%s: rule loader write: %s\n",__progname,strerror(errno)); exit(1); } if (w != len) { fprintf(stderr,"%s: rule loader write: wanted %d, did %d\n",__progname,len,w); exit(1); } return(len); } /* * Reload the rdr rules. Just generates the text form of the rules and * sends it to the rules-loader process. * * The @ generated at the end is a terminator; @ cannot appear in the * rules, making it suitable as an in-band terminator (we'd rather not * have to close and reopen the channel to the rules loader). */ static void reload_rdrs(void) { FILE *f; RDR *r; char ack; if (verbose) printf("reloading rdrs\n"); f = fwopen(0,&rdr_write); for (r=rdrs;r;r=r->flink) { fprintf(f,"rdr pass on %s ",intf); switch (r->af) { case AF_INET: fprintf(f,"inet"); break; case AF_INET6: fprintf(f,"inet6"); break; default: abort(); break; } fprintf(f," proto tcp from %s to %s port = %s -> %s port %s\n", r->fhost, r->thost, r->tport, r->rhost, r->rport ); } fclose(f); write(rlfd,"@",1); read(rlfd,&ack,1); } /* * Remove an RDR. */ static void remove_rdr(RDR *r) { if (r->blink) r->blink->flink = r->flink; else rdrs = r->flink; if (r->flink) r->flink->blink = r->blink; free(r->fhost); free(r->thost); free(r->tport); free(r->rhost); free(r->rport); reload_rdrs(); } /* * Cancel an outstanding connection-in-progress. */ static void cip_cancel(CIP *cip) { close(cip->fd); remove_poll_id(cip->id); free(cip); } /* * Destroy any data-connection setup for the FTPSTATE, closing * descriptors and cleaning up IDs and such as necessary. */ static void destroy_data(FTPSTATE *fs) { switch (fs->datamode) { default: abort(); break; case DATA_NONE: case DATA_PASVA: case DATA_EPSVA: break; case DATA_PORT: case DATA_EPRT: remove_poll_id(fs->dataid); close(fs->sdfd); break; case DATA_PASVB: case DATA_EPSVB: if (fs->rdr) remove_rdr(fs->rdr); remove_poll_id(fs->dataid); close(fs->cdfd); break; case DATA_LIVE: printf("[%s] data done, bytes get %llu put %llu\n", fs->tag,fs->datacib,fs->datacob); if (fs->rdr) remove_rdr(fs->rdr); istr_kill(&fs->data_c_i); ostr_kill(&fs->data_c_o); istr_kill(&fs->data_s_i); ostr_kill(&fs->data_s_o); close(fs->cdfd); close(fs->sdfd); remove_block_id(fs->dataid); break; case DATA_PORTC: case DATA_PASVC: case DATA_EPRTC: case DATA_EPSVC: if (fs->rdr) remove_rdr(fs->rdr); if (fs->cip) cip_cancel(fs->cip); close(fs->cdfd); close(fs->sdfd); break; } fs->datamode = DATA_NONE; } /* * Clear out a TNSTATE, throwing away any lingering resources. Does * not free the TNSTATE itself. */ static void telnet_kill(TNSTATE *s) { free(s->line); } /* * Clear out an LQ, throwing away any lingering resources. Does not * free the LQ itself. */ static void lq_kill(LQ *q) { LQE *e; while ((e = q->q)) { q->q = e->link; free(e->body); free(e); } } /* * Block function to wait for control connections to drain before * closing the FTPSTATE down entirely. Once it does, destroy the * state. */ static int death_drain(void *fsv) { FTPSTATE *fs; fs = fsv; if (fs->state != FTPS_KILL) abort(); if (ostr_empty(&fs->ctl_c_o) && ostr_empty(&fs->ctl_s_o)) { destroy_data(fs); if (fs->flags & FSF_EVERLIVE) { printf("[%s] done: conns %u, bytes get %llu, put %llu\n", fs->tag,fs->datacount,fs->dataib,fs->dataob); } remove_block_id(fs->die_id); fs->die_id = PL_NOID; fs->state = FTPS_DEAD; if (fs->ccfd >= 0) close(fs->ccfd); if (fs->scfd >= 0) close(fs->scfd); istr_kill(&fs->ctl_c_i); ostr_kill(&fs->ctl_c_o); istr_kill(&fs->ctl_s_i); ostr_kill(&fs->ctl_s_o); telnet_kill(&fs->ctl_c_tn); telnet_kill(&fs->ctl_s_tn); lq_kill(&fs->resplq); lq_kill(&fs->cclq); remove_block_id(fs->cclq_id); lq_kill(&fs->sclq); remove_block_id(fs->sclq_id); if (fs->die_id != PL_NOID) remove_block_id(fs->die_id); free(fs); return(BLOCK_LOOP); } return(BLOCK_NIL); } /* * Turn on non-blocking I/O on a file descriptor. */ static void set_nonblock(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } /* * Turn on keepalives on a socket. Ignore errors, since in case of * error the most useful thing we can do is carry on anyway. */ static void set_keepalives(int fd) { int on; on = 1; setsockopt(fd,SOL_SOCKET,SO_KEEPALIVE,&on,sizeof(on)); } /* * Close a file descriptor, but leave errno unchanged even on error. * This is used only in cases that don't care about errors, so it * doesn't pay attention to close's return, and returns void. */ static void close_noerr(int fd) { int e; e = errno; close(fd); errno = e; } /* * Return a pointer to the localhost sockaddr_storage suitable to a * given address family. */ static const struct sockaddr_storage *localhost(int af) { switch (af) { case AF_INET: return(&localhost_v4); break; case AF_INET6: return(&localhost_v6); break; default: abort(); break; } } /* * A connection-in-progress has indicated it's done. Check to see * whether it succeeded or failed, and handle each case. */ static void cip_done(int id, void *cipv) { CIP *cip; int err; socklen_t errlen; cip = cipv; if (id != cip->id) abort(); errlen = sizeof(err); remove_poll_id(cip->id); if (getsockopt(cip->fd,SOL_SOCKET,SO_ERROR,&err,&errlen) < 0) { err = errno; close(cip->fd); (*cip->fail)(cip->arg,err); } else if (err) { close(cip->fd); (*cip->fail)(cip->arg,err); } else { (*cip->ok)(cip->arg,cip->fd); } free(cip); } /* * Initiate an asynchronous connect(). fd can be -1, in which case a * SOCK_STREAM socket is created in the address family of the to * address, or it can be a descriptor already prepared. from can be a * nil pointer, in which case the local address is left up to the * kernel, or it can be an address to bind to. to is the address to * connect to. fail and ok are failure and success callbacks; their * first arg is arg. (fail's second arg is an errno; ok's, the * connected file descriptor.) * * The returned CIP may be passed to cip_cancel, provided neither of * the callbacks has happened yet. Once either callback occurs, the * CIP pointer becomes invalid and must not be touched. * * Note that this function may call fail or ok from within cip_connect * (in which case it returns a nil pointer). */ static CIP *cip_connect( int fd, const struct sockaddr_storage *from, const struct sockaddr_storage *to, void (*fail)(void *, int), void (*ok)(void *, int), void *arg ) { CIP *cip; if (fd < 0) { fd = socket(to->ss_family,SOCK_STREAM,0); if (fd < 0) { (*fail)(arg,errno); return(0); } } if (from) { if (bind(fd,(const void *)from,from->ss_len) < 0) { close_noerr(fd); (*fail)(arg,errno); return(0); } } set_nonblock(fd); if (connect(fd,(const void *)to,to->ss_len) >= 0) { (*ok)(arg,fd); return(0); } if (errno != EINPROGRESS) { close_noerr(fd); (*fail)(arg,errno); return(0); } cip = malloc(sizeof(CIP)); cip->fd = fd; cip->id = add_poll_fd(fd,&rwtest_never,&rwtest_always,0,&cip_done,cip); cip->fail = fail; cip->ok = ok; cip->arg = arg; return(cip); } /* * Kill off an FTP session. Called on fatal errors. */ static void kill_ftp(FTPSTATE *fs) { switch (fs->state) { case FTPS_CONN: if (fs->cip) cip_cancel(fs->cip); break; } switch (fs->state) { case FTPS_KILL: case FTPS_DEAD: break; default: remove_block_id(fs->die_id); fs->die_id = add_block_fn(&death_drain,fs); break; } fs->state = FTPS_KILL; } /* * Initialize an OSTR to harmless values. */ static void ostr_init(OSTR *o) { o->fd = -1; o->q = 0; o->qt = &o->q; o->qlen = 0; o->id = PL_NOID; } /* * Write test function for an OSTR. Returns true if there's anything * waiting to be written. */ static int wtest_ostr(int id __attribute__((__unused__)), void *ovp) { return(!!((OSTR *)ovp)->q); } /* * Write function for an OSTR. Writes as much of the head-of-queue * block as it can. Arguably we should walk the list and build up a * struct iovec vector for writev, but we don't bother at present. */ static void wr_ostr(int id __attribute__((__unused__)), void *ovp) { OSTR *o; OQ *q; int w; int n; int drop; int flag; int qlen; o = ovp; q = o->q; if (! q) abort(); switch (q->type) { case OQT_DATA: flag = 0; if (0) { case OQT_URG: flag = MSG_OOB; } n = q->len - q->ptr; w = send(o->fd,q->buf+q->ptr,n,flag); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } (*o->wrerr)(o->arg,errno); return; } q->ptr += w; drop = (q->ptr >= q->len); qlen = q->len; break; case OQT_EOF: shutdown(o->fd,SHUT_WR); drop = 1; break; if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } (*o->wrerr)(o->arg,errno); return; } drop = 1; qlen = 1; break; default: abort(); } if (drop) { o->q = q->link; if (! o->q) o->qt = &o->q; free(q->tofree); free(q); o->qlen -= qlen; } } /* * Set up an OSTR for use. fd is the descriptor and wrerr is a * write-error callback (arg is passed as its first arg, the second * being an errno). */ static void setup_ostr( OSTR *o, int fd, void (*wrerr)(void *, int), void *arg ) { o->fd = fd; o->q = 0; o->qt = &o->q; o->qlen = 0; o->id = add_poll_fd(fd,&rwtest_never,&wtest_ostr,0,&wr_ostr,o); o->wrerr = wrerr; o->arg = arg; } /* * Functions to queue a block of data to be written to an OSTR. They * differ in how the block of data is to be treated: copied at queue * time, merely pointed to, or taken over to be freed once written. * There are also variants which queue multiple blocks in a single * call, generate data with a printf format, queue an EOF block, and * two "marked as urgent" versions. * * In all versions that take a data-and-length, if the length is -1, * strlen() is called on the data and the result is used as the * length. */ /* * Copy at queue time. Used to, eg, send out a part of a larger buffer * which will be overwritten or freed soon. */ static void ostr_q_copy(OSTR *o, const void *data, int len) { OQ *q; if (len < 0) len = strlen(data); q = malloc(sizeof(OQ)+len); bcopy(data,q+1,len); q->type = OQT_DATA; q->buf = (void *) (q+1); q->ptr = 0; q->len = len; q->tofree = 0; q->link = 0; *o->qt = q; o->qt = &q->link; o->qlen += len; } /* * Just point to the data. Used when the data is known to remain valid * at least until the data is sent. Typically used when the data * block is a C literal string. */ static void ostr_q_point(OSTR *o, const void *data, int len) { OQ *q; if (len < 0) len = strlen(data); q = malloc(sizeof(OQ)); q->type = OQT_DATA; q->buf = data; q->ptr = 0; q->len = len; q->tofree = 0; q->link = 0; *o->qt = q; o->qt = &q->link; o->qlen += len; } /* * Take over a mallocked block, freeing it once it's been written. */ static void ostr_q_free(OSTR *o, void *data, int len) { OQ *q; if (len < 0) len = strlen(data); q = malloc(sizeof(OQ)); q->type = OQT_DATA; q->buf = data; q->ptr = 0; q->len = len; q->tofree = data; q->link = 0; *o->qt = q; o->qt = &q->link; o->qlen += len; } /* * Convenience routine, queueing multiple blocks at once. Arguments * are char * pointers; they are queued a la ostr_q_point, up to * OSQ_END, which marks the end of the arglist. This can be modified * by prefixes: * * ...,OSQ_FREE,x,... * Uses ostr_q_free instead of ostr_q_point. * ...,OSQ_COPY,x,... * Uses ostr_q_copy instead of ostr_q_point. * ...,OSQ_LEN(n),x,... * Writes n bytes, instead of strlen(x) bytes. * * OSQ_LEN() may be combined with either of the other two prefixes, in * either order. */ static char ostr_q_flg_len; static char ostr_q_flg_free; static char ostr_q_flg_copy; static char ostr_q_flg_end; static void ostr_q(OSTR *o, ...) #define OSQ_LEN(x) (&ostr_q_flg_len),(int)(x) #define OSQ_FREE (&ostr_q_flg_free) #define OSQ_COPY (&ostr_q_flg_copy) #define OSQ_END (&ostr_q_flg_end) { va_list ap; char *arg; int len; int flg_free; int flg_copy; va_start(ap,o); len = -1; flg_free = 0; flg_copy = 0; while (1) { arg = va_arg(ap,char *); if (arg == &ostr_q_flg_end) break; if (arg == &ostr_q_flg_len) { len = va_arg(ap,int); continue; } if (arg == &ostr_q_flg_free) { flg_free = 1; continue; } if (arg == &ostr_q_flg_copy) { flg_copy = 1; continue; } if (flg_copy) { ostr_q_copy(o,arg,len); } else if (flg_free) { ostr_q_free(o,arg,len); } else { ostr_q_point(o,arg,len); } len = -1; flg_free = 0; flg_copy = 0; } } /* * Queue a block generated from a printf format and arguments. */ static void ostr_q_printf(OSTR *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void ostr_q_printf(OSTR *o, const char *fmt, ...) { OQ *q; va_list ap; char *b; int n; va_start(ap,fmt); n = vasprintf(&b,fmt,ap); va_end(ap); q = malloc(sizeof(OQ)); q->type = OQT_DATA; q->buf = b; q->ptr = 0; q->len = n; q->tofree = b; q->link = 0; *o->qt = q; o->qt = &q->link; o->qlen += n; } /* * Queue an EOF-marker block. */ static void ostr_q_eof(OSTR *o) { OQ *q; q = malloc(sizeof(OQ)); q->type = OQT_EOF; q->tofree = 0; q->link = 0; *o->qt = q; o->qt = &q->link; o->qlen ++; } /* * Like ostr_q_copy, but marks the data as urgent. */ static void ostr_q_copy_urg(OSTR *o, const void *data, int len) { OQ *q; if (len < 0) len = strlen(data); q = malloc(sizeof(OQ)+len); bcopy(data,q+1,len); q->type = OQT_URG; q->buf = (void *) (q+1); q->ptr = 0; q->len = len; q->tofree = 0; q->link = 0; *o->qt = q; o->qt = &q->link; o->qlen += len; } /* * Like ostr_q_point, but marks the data as urgent. */ static void ostr_q_point_urg(OSTR *o, const void *data, int len) { OQ *q; if (len < 0) len = strlen(data); q = malloc(sizeof(OQ)); q->type = OQT_URG; q->buf = data; q->ptr = 0; q->len = len; q->tofree = 0; q->link = 0; *o->qt = q; o->qt = &q->link; o->qlen += len; } /* * Read test function for an ISTR. An ISTR is readable provided it * hasn't read EOF, and either it has no read-test function or the * read-test function returns true. */ static int rtest_istr(int id __attribute__((__unused__)), void *ivp) { ISTR *i; i = ivp; return(!i->eof && (!i->rtest || (*i->rtest)(i->arg))); } /* * Read from an ISTR. Calls the rderr callback on error; calls the * data callback for data. */ static void rd_istr(int id __attribute__((__unused__)), void *ivp) { ISTR *i; char buf[8192]; int n; int mark; i = ivp; if (ioctl(i->fd,SIOCATMARK,&mark) < 0) { fprintf(stderr,"%s: SIOCATMARK: %s\n",__progname,strerror(errno)); exit(1); } n = recv(i->fd,&buf[0],sizeof(buf),mark?MSG_OOB:0); if (n < 0) { int e; e = errno; switch (e) { case EINTR: case EWOULDBLOCK: return; break; } (*i->rderr)(i->arg,e); return; } (*i->data)(i->arg,&buf[0],n,mark); if (n == 0) i->eof = 1; } /* * Initialize an ISTR to harmless values. */ static void istr_init(ISTR *i) { i->fd = -1; i->eof = 0; i->id = PL_NOID; i->data = 0; } /* * Set up an ISTR for use. fd is the underlying descritor. rtest is * the read-test callback, or a nil pointer if none. data and rderr * are the data and read-error callbacks; arg is the first arg to the * three callbacks. */ static void setup_istr( ISTR *i, int fd, int (*rtest)(void *), void (*data)(void *, const void *, int, int), void (*rderr)(void *, int), void *arg ) { i->fd = fd; i->eof = 0; i->id = add_poll_fd(fd,&rtest_istr,&rwtest_never,&rd_istr,0,i); i->rtest = rtest; i->data = data; i->rderr = rderr; i->arg = arg; } /* * Initialize an LQ. */ static void lq_init(LQ *q) { q->q = 0; q->qt = &q->q; } /* * Queue a line on an LQ. The line is a mallocked copy of the block * given. */ static void lq_queue_copy(LQ *q, const char *body, int len, unsigned int flg) { LQE *e; e = malloc(sizeof(LQE)); e->body = malloc(len+1); e->len = len; e->flg = flg; bcopy(body,e->body,len); e->body[len] = '\0'; e->link = 0; *q->qt = e; q->qt = &e->link; } /* * Queue a line on an LQ. The line is the block given, literally (the * pointer is saved). */ static void lq_queue_point(LQ *q, char *body, int len, unsigned int flg) { LQE *e; e = malloc(sizeof(LQE)); e->body = body; e->len = len; e->flg = flg; e->link = 0; *q->qt = e; q->qt = &e->link; } /* * Pop the first line off an LQ. If there are no lines queued, the * return value is nonzero; if there was a line queued, the return * value is zero, and the data is stored through *datap, with *lenp * and *flgp receiving the length and flags values respectively. lenp * and flgp may be nil pointers, in which case those values are thrown * away; datap must not be nil. */ static int lq_pop(LQ *q, char **datap, int *lenp, unsigned int *flgp) { LQE *e; e = q->q; if (e) { *datap = e->body; if (lenp) *lenp = e->len; if (flgp) *flgp = e->flg; q->q = e->link; if (! q->q) q->qt = &q->q; free(e); return(0); } else { return(1); } } /* * Just like lq_pop except it doesn't remove the line from the LQ. */ static int lq_peek(LQ *q, char **datap, int *lenp, unsigned int *flgp) { LQE *e; e = q->q; if (e) { *datap = e->body; if (lenp) *lenp = e->len; if (flgp) *flgp = e->flg; return(0); } else { return(1); } } /* * Look up the FTPSTATE's redirected control connection in the NAT * state tables and fill in its ctl_s_ss with the result. Also fills * in ctl_oc_ss and ctl_c_ss. */ static void lookup_connection(FTPSTATE *fs) { socklen_t sslen; struct pfioc_natlook nl; sslen = sizeof(fs->ctl_oc_ss); if (getsockname(fs->ccfd,(void *)&fs->ctl_oc_ss,&sslen) < 0) { fprintf(stderr,"%s: getsockname: %s\n",__progname,strerror(errno)); kill_ftp(fs); return; } sslen = sizeof(fs->ctl_c_ss); if (getpeername(fs->ccfd,(void *)&fs->ctl_c_ss,&sslen) < 0) { fprintf(stderr,"%s: getpeername: %s\n",__progname,strerror(errno)); kill_ftp(fs); return; } if (fs->ctl_oc_ss.ss_family != fs->ctl_c_ss.ss_family) { fprintf(stderr,"%s: ends are in different AFs? (us %d, client %d)\n",__progname,fs->ctl_oc_ss.ss_family,fs->ctl_c_ss.ss_family); kill_ftp(fs); return; } fs->ctl_s_ss = fs->ctl_c_ss; switch (fs->ctl_c_ss.ss_family) { case AF_INET: nl.saddr.v4 = ((struct sockaddr_in *)&fs->ctl_c_ss)->sin_addr; nl.sport = ((struct sockaddr_in *)&fs->ctl_c_ss)->sin_port; nl.daddr.v4 = ((struct sockaddr_in *)&fs->ctl_oc_ss)->sin_addr; nl.dport = ((struct sockaddr_in *)&fs->ctl_oc_ss)->sin_port; nl.af = AF_INET; nl.proto = IPPROTO_TCP; nl.direction = PF_OUT; Ioctl(pffd,DIOCNATLOOK,&nl); ((struct sockaddr_in *)&fs->ctl_s_ss)->sin_addr = nl.rdaddr.v4; ((struct sockaddr_in *)&fs->ctl_s_ss)->sin_port = nl.rdport; break; case AF_INET6: nl.saddr.v6 = ((struct sockaddr_in6 *)&fs->ctl_c_ss)->sin6_addr; nl.sport = ((struct sockaddr_in6 *)&fs->ctl_c_ss)->sin6_port; nl.daddr.v6 = ((struct sockaddr_in6 *)&fs->ctl_oc_ss)->sin6_addr; nl.dport = ((struct sockaddr_in6 *)&fs->ctl_oc_ss)->sin6_port; nl.af = AF_INET6; nl.proto = IPPROTO_TCP; nl.direction = PF_OUT; Ioctl(pffd,DIOCNATLOOK,&nl); ((struct sockaddr_in6 *)&fs->ctl_s_ss)->sin6_addr = nl.rdaddr.v6; ((struct sockaddr_in6 *)&fs->ctl_s_ss)->sin6_port = nl.rdport; break; default: fprintf(stderr,"%s: unknown af %d\n",__progname,fs->ctl_oc_ss.ss_family); kill_ftp(fs); break; } } /* * Initialize a TNSTATE to harmless values. */ static void telnet_init(TNSTATE *s) { s->fs = 0; s->state = TNS_DATA; s->line = 0; s->linel = 0; s->linea = 0; s->flags = 0; s->pass = 0; s->back = 0; s->linefn = 0; } /* * Set up a TNSTATE for use. */ static void telnet_setup(TNSTATE *s, FTPSTATE *f, OSTR *pass, OSTR *back, void (*haveline)(TNSTATE *)) { s->fs = f; s->state = TNS_DATA; s->line = 0; s->linel = 0; s->linea = 0; s->flags = 0; s->pass = pass; s->back = back; s->linefn = haveline; } /* * Handle a received data byte for a TNSTATE. c may also be EOF, * indicating that EOF was received. This accumulates lines, calling * the TNSTATE's linefn for each one. At EOF, it calls the linefn * with the TNSTATE's linel set to -1. */ static void data_char(TNSTATE *s, int c) { if (c == EOF) { if (s->linel > 0) (*s->linefn)(s); s->linel = -1; s->flags = 0; (*s->linefn)(s); return; } if (s->linel >= s->linea) s->line = realloc(s->line,s->linea=s->linel+16); s->line[s->linel++] = c; if (c == '\r') { s->flags |= TNS_F_ATCR; } else if (s->flags & TNS_F_ATCR) { s->flags &= ~TNS_F_ATCR; switch (c) { case '\0': break; case '\n': (*s->linefn)(s); s->linel = 0; s->flags = 0; break; default: if (s->linel < 2) abort(); /* impossible CR xx */ fprintf(stderr,"%s: deleting invalid CR 0x%02x\n",__progname,c); s->linel -= 2; break; } } } /* * Process a just-read block of octets for a TNSTATE. atmark indicates * whether the bytes were received at the TCP urgent pointer mark. * * This routine handles most of the telnet protocol. */ static void telnet_process(TNSTATE *s, const void *data, int len, int atmark) { const unsigned char *dp; static void send_bytes(OSTR *o, int urg, int n, ...) { va_list ap; int i; unsigned char buf[n]; va_start(ap,n); for (i=0;i0;dp++,len--) { switch (s->state) { case TNS_DATA: if (*dp == TELNET_IAC) { s->state = TNS_IAC; } else { s->flags &= ~TNS_F_ATIP; data_char(s,*dp); } break; case TNS_IAC: if ((s->flags & TNS_F_ATIP) && (*dp == TELNET_DM) && atmark) { s->flags |= TNS_F_MARKED; } s->flags &= ~TNS_F_ATIP; switch (*dp) { case TELNET_NOP: s->state = TNS_DATA; break; case TELNET_SE: fprintf(stderr,"%s: ignoring stray Telnet SE\n",__progname); break; case TELNET_DM: break; case TELNET_BRK: fprintf(stderr,"%s: ignoring Telnet BRK\n",__progname); break; case TELNET_IP: s->flags |= TNS_F_ATIP; break; case TELNET_AO: fprintf(stderr,"%s: ignoring Telnet AO\n",__progname); break; case TELNET_AYT: fprintf(stderr,"%s: ignoring Telnet AYT\n",__progname); break; case TELNET_EC: if (s->linel > 0) s->linel --; break; case TELNET_EL: s->linel = 0; break; case TELNET_GA: fprintf(stderr,"%s: ignoring Telnet GA\n",__progname); break; case TELNET_SB: fprintf(stderr,"%s: ignoring invalid Telnet SB\n",__progname); s->state = TNS_SB; break; case TELNET_WILL: s->state = TNS_WILL; break; case TELNET_WONT: s->state = TNS_WONT; break; case TELNET_DO: s->state = TNS_DO; break; case TELNET_DONT: s->state = TNS_DONT; break; case TELNET_IAC: data_char(s,TELNET_IAC); break; } break; case TNS_SB: if (*dp == TELNET_IAC) s->state = TNS_SBIAC; break; case TNS_SBIAC: switch (*dp) { case TELNET_SE: s->state = TNS_DATA; break; case TELNET_IAC: break; default: fprintf(stderr,"%s: ignoring Telnet IAC 0x%02x during SB\n",__progname,*dp); break; } break; case TNS_WILL: fprintf(stderr,"%s: rejecting WILL 0x%02x\n",__progname,*dp); send_bytes(s->back,0,3,TELNET_IAC,TELNET_DONT,*dp); s->state = TNS_DATA; break; case TNS_WONT: s->state = TNS_DATA; break; case TNS_DO: fprintf(stderr,"%s: rejecting DO 0x%02x\n",__progname,*dp); send_bytes(s->back,0,3,TELNET_IAC,TELNET_WONT,*dp); s->state = TNS_DATA; break; case TNS_DONT: s->state = TNS_DATA; break; } } } #if 0 /* Debugging assist routine. */ static void print_data_vis(FILE *to, const void *data, int len) { const unsigned char *dp; for (dp=data;len>0;dp++,len--) { if (((*dp >= 32) && (*dp <= 126)) || ((*dp >= 160)/* && (*dp <= 255)*/)) { putc(*dp,to); } else { switch (*dp) { case '\a': fprintf(to,"\\a"); break; case '\b': fprintf(to,"\\b"); break; case '\e': fprintf(to,"\\e"); break; case '\f': fprintf(to,"\\f"); break; case '\n': fprintf(to,"\\n"); break; case '\r': fprintf(to,"\\r"); break; case '\t': fprintf(to,"\\t"); break; case '\v': fprintf(to,"\\v"); break; default: if ((len < 2) || (dp[1] < '0') || (dp[1] > '9')) { fprintf(to,"\\%o",*dp); } else { fprintf(to,"\\%03o",*dp); } break; } } } } #endif /* * Scan an IPv4 address-and-port spec (from a PORT command or a PASV * response). These take the form of a1,a2,a3,a4,p1,p2, where a1-a4 * are the octets of the address and p1 and p2 are the octets of the * port number (all MSB-first). * * Return value is nil if all went well, or a string briefly describing * the error if not. */ static const char *scan_v4_portspec(const char *s, struct sockaddr_in *sin) { int v[6]; int i; if (sscanf(s,"%d ,%d ,%d ,%d ,%d ,%d",&v[0],&v[1],&v[2],&v[3],&v[4],&v[5]) != 6) return("must have six comma-separated numbers"); for (i=0;i<6;i++) if ((v[i] < 0) || (v[i] > 255)) return("number out of 0-255 range"); bzero(sin,sizeof(*sin)); sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_addr.s_addr = htonl( (v[0] * 0x01000000) + (v[1] * 0x00010000) + (v[2] * 0x00000100) + (v[3] * 0x00000001) ); sin->sin_port = htons((v[4]*0x0100)+v[5]); return(0); } /* * Utility routine to set the port number of a sockaddr_storage, * according to its family. */ static void set_port(struct sockaddr_storage *ss, int p) { switch (ss->ss_family) { case AF_INET: ((struct sockaddr_in *)ss)->sin_port = htons(p); break; case AF_INET6: ((struct sockaddr_in6 *)ss)->sin6_port = htons(p); break; default: abort(); break; } } /* * Utility routine to get the port number of a sockaddr_storage, * according to its family. */ static int get_port(struct sockaddr_storage *ss) { switch (ss->ss_family) { case AF_INET: return(ntohs(((struct sockaddr_in *)ss)->sin_port)); break; case AF_INET6: return(ntohs(((struct sockaddr_in6 *)ss)->sin6_port)); break; default: abort(); break; } } /* * Add an rdr rule, given three sockaddrs for the three parts of it. * (The port part of from is ignored.) Returns the resulting RDR. */ static RDR *add_rdr( const struct sockaddr_storage *from, const struct sockaddr_storage *to, const struct sockaddr_storage *rdr ) { __label__ failret; RDR *r; char fhostbuf[64]; const char *fhost; char thostbuf[64]; const char *thost; char rhostbuf[64]; const char *rhost; int tport; int rport; static void fail(const char *tag) { fprintf(stderr,"%s: failed to add redirection: %s: %s\n",__progname,tag,strerror(errno)); free(r); goto failret; } if ( (from->ss_family != to->ss_family) || (from->ss_family != rdr->ss_family) ) abort(); r = malloc(sizeof(RDR)); switch (from->ss_family) { default: abort(); break; case AF_INET: fhost = inet_ntop(AF_INET,&((const struct sockaddr_in *)from)->sin_addr,&fhostbuf[0],sizeof(fhostbuf)); if (! fhost) fail("from host"); thost = inet_ntop(AF_INET,&((const struct sockaddr_in *)to)->sin_addr,&thostbuf[0],sizeof(thostbuf)); if (! thost) fail("to host"); rhost = inet_ntop(AF_INET,&((const struct sockaddr_in *)rdr)->sin_addr,&rhostbuf[0],sizeof(rhostbuf)); if (! rhost) fail("rdr host"); r->af = AF_INET; r->fhost = strdup(fhost); r->thost = strdup(thost); tport = ((const struct sockaddr_in *)to)->sin_port; r->rhost = strdup(rhost); rport = ((const struct sockaddr_in *)rdr)->sin_port; break; case AF_INET6: fhost = inet_ntop(AF_INET6,&((const struct sockaddr_in6 *)from)->sin6_addr,&fhostbuf[0],sizeof(fhostbuf)); if (! fhost) fail("from host"); thost = inet_ntop(AF_INET6,&((const struct sockaddr_in6 *)to)->sin6_addr,&thostbuf[0],sizeof(thostbuf)); if (! thost) fail("to host"); rhost = inet_ntop(AF_INET6,&((const struct sockaddr_in6 *)rdr)->sin6_addr,&rhostbuf[0],sizeof(rhostbuf)); if (! rhost) fail("rdr host"); r->af = AF_INET6; r->fhost = strdup(fhost); r->thost = strdup(thost); tport = ((const struct sockaddr_in6 *)to)->sin6_port; r->rhost = strdup(rhost); rport = ((const struct sockaddr_in6 *)rdr)->sin6_port; break; } asprintf(&r->tport,"%d",ntohs(tport)); asprintf(&r->rport,"%d",ntohs(rport)); r->flink = rdrs; r->blink = 0; if (rdrs) rdrs->blink = r; rdrs = r; reload_rdrs(); return(r); failret:; return(0); } /* * TNSTATE line callback for the control connection to the client. * Just error-checks, strips CRLF line endings, and queues for * processing next time check_cclq finds it appropriate to pay * attention to client input. */ static void client_line(TNSTATE *t) { if (t->linel < 0) { if (verbose) printf("client_line EOF\n"); lq_queue_point(&t->fs->cclq,0,0,0); return; } if ( (t->linel >= 2) && (t->line[t->linel-1] == '\n') && (t->line[t->linel-2] == '\r') ) t->linel -= 2; if (verbose) printf("client_line %.*s\n",t->linel,t->line); lq_queue_copy(&t->fs->cclq,t->line,t->linel,t->flags); } /* * TNSTATE line callback for the control connection to the server. * Just error-checks, strips CRLF line endings, and queues for * processing next time check_sclq finds it appropriate to pay * attention to server input. */ static void server_line(TNSTATE *t) { if (t->linel < 0) { if (verbose) printf("server_line EOF\n"); lq_queue_point(&t->fs->sclq,0,0,0); return; } if ( (t->linel >= 2) && (t->line[t->linel-1] == '\n') && (t->line[t->linel-2] == '\r') ) t->linel -= 2; if (verbose) printf("server_line %.*s\n",t->linel,t->line); lq_queue_copy(&t->fs->sclq,t->line,t->linel,t->flags); } /* * Check to see if (a part of) a string is all digits. */ static int alldigits(const char *s, int n) { for (;n>0;s++,n--) if ((*s < '0') || (*s > '9')) return(0); return(1); } /* * Called from respfn callbacks to just copy the response on to the * client verbatim. */ static void resp_echo(FTPSTATE *fs) { char *ql; int qll; while (! lq_pop(&fs->resplq,&ql,&qll,0)) { ostr_q(&fs->ctl_c_o,OSQ_FREE,OSQ_LEN(qll),ql,"\r\n",OSQ_END); } } /* * Called from respfn callbacks to throw away the queued server * response (presumably replacing it with a suitable proxy-generated * response instead). */ static void resp_drop(FTPSTATE *fs) { char *ql; while (! lq_pop(&fs->resplq,&ql,0,0)) free(ql); } /* * Response function used for just-copy-to-client responses when a 1xx * response has already been seen (and another 1xx is thus invalid). */ static void resp_copy_1(FTPSTATE *fs, char key) { if (key == '1') { fprintf(stderr,"%s: dropping invalid extra 1yz reply\n",__progname); resp_drop(fs); } else { resp_echo(fs); fs->state = FTPS_CMD; } } /* * Response function used for just-copy-to-client responses when no * response has yet been seen. */ static void resp_copy(FTPSTATE *fs, char key) { resp_echo(fs); if (key == '1') { fs->respfn = &resp_copy_1; } else { fs->state = FTPS_CMD; } } /* * Block function to check for input from the server on the control * connection (responses). State RESPA is used for the first line; if * the response is multi-line, state RESPB is used for the rest of the * lines. */ static int check_sclq(void *fsv) { FTPSTATE *fs; char *l; int ll; int flg; fs = fsv; switch <"state"> (fs->state) { case FTPS_RESPA: if (lq_pop(&fs->sclq,&l,&ll,&flg)) return(BLOCK_NIL); if (l == 0) { if (verbose) printf("response EOF\n"); kill_ftp(fs); break <"state">; } if (verbose) printf("response first line %.*s\n",ll,l); lq_queue_point(&fs->resplq,l,ll,0); if ((ll >= 4) && alldigits(l,3)) { switch (l[3]) { case ' ': (*fs->respfn)(fs,(unsigned char)l[0]); break <"state">; case '-': fs->state = FTPS_RESPB; break <"state">; } } if (verbose) printf("ignoring malformatted response line\n"); resp_drop(fs); break; case FTPS_RESPB: if (lq_pop(&fs->sclq,&l,&ll,&flg)) return(BLOCK_NIL); if (l == 0) { if (verbose) printf("response EOF mid-response\n"); (*fs->respfn)(fs,EOF); break <"state">; } if (verbose) printf("response non-first line %.*s\n",ll,l); lq_queue_point(&fs->resplq,l,ll,0); if ((ll >= 4) && alldigits(l,3)) { if (l[3] == ' ') { (*fs->respfn)(fs,(unsigned char)l[0]); } else { if (verbose) printf("ignoring malformatted response line\n"); if (lq_pop(&fs->resplq,&l,0,0)) abort(); free(l); } } break; case FTPS_EOF: if (lq_pop(&fs->sclq,&l,&ll,&flg)) return(BLOCK_NIL); if (l == 0) { ostr_q_eof(&fs->ctl_c_o); break <"state">; } if (verbose) printf("ignoring stray response to client EOF\n"); free(l); break; default: return(BLOCK_NIL); break; } return(BLOCK_LOOP); } /* * Read-test function for the client data connection. We don't want to * buffer more than 64K, so don't read if there's 64K or more queued * to write to the server on the data connection. */ static int data_rchk_c(void *fsv) { return(ostr_qlen(&((FTPSTATE *)fsv)->data_s_o)<65536); } /* * Data-received function for the data connection to the client. Just * echo the data to the server's data connection. */ static void data_data_c(void *fsv, const void *data, int len, int atmark __attribute__((__unused__))) { FTPSTATE *fs; fs = fsv; if (len > 0) { ostr_q_copy(&fs->data_s_o,data,len); fs->dataob += len; fs->datacob += len; } else { ostr_q_eof(&fs->data_s_o); } } /* * Read-test function for the server data connection. We don't want to * buffer more than 64K, so don't read if there's 64K or more queued * to write to the client on the data connection. */ static int data_rchk_s(void *fsv) { return(ostr_qlen(&((FTPSTATE *)fsv)->data_c_o)<65536); } /* * Data-received function for the data connection to the server. Just * echo the data to the client's data connection. */ static void data_data_s(void *fsv, const void *data, int len, int atmark __attribute__((__unused__))) { FTPSTATE *fs; fs = fsv; if (len > 0) { ostr_q_copy(&fs->data_c_o,data,len); fs->dataib += len; fs->datacib += len; } else { ostr_q_eof(&fs->data_c_o); } } /* * Read error on a data connection. We don't know whether the client's * or the server's, but we also don't care; break the data connection * and let the client and server deal with it. */ static void data_rderr(void *fsv, int err) { FTPSTATE *fs; fs = fsv; printf("[%s] data connection read error: %s\n",fs->tag,strerror(err)); destroy_data(fs); } /* * Write error on a data connection. We don't know whether the * client's or the server's, but we also don't care; break the data * connection and let the client and server deal with it. */ static void data_wrerr(void *fsv, int err) { FTPSTATE *fs; fs = fsv; printf("[%s] data connection write error: %s\n",fs->tag,strerror(err)); destroy_data(fs); } /* * Block function watching for data connection EOF. Once EOF has been * seen in both directions, and everything has drained, clean up the * data connection. */ static int check_data_done(void *fsv) { FTPSTATE *fs; fs = fsv; if ( fs->data_c_i.eof && ostr_empty(&fs->data_s_o) && fs->data_s_i.eof && ostr_empty(&fs->data_c_o) ) { destroy_data(fs); return(BLOCK_LOOP); } return(BLOCK_NIL); } /* * The data connection is now fully live. Set it all up to copy data * both ways and monitor for its being shut down. */ static void data_live(FTPSTATE *fs) { switch (fs->datamode) { case DATA_PORTC: printf("[%s] PORT data connection\n",fs->tag); break; case DATA_EPRTC: printf("[%s] EPRT data connection\n",fs->tag); break; case DATA_PASVC: printf("[%s] PASV data connection\n",fs->tag); break; case DATA_EPSVC: printf("[%s] EPSV data connection\n",fs->tag); break; default: abort(); break; } setup_istr(&fs->data_c_i,fs->cdfd,&data_rchk_c,&data_data_c,&data_rderr,fs); setup_ostr(&fs->data_c_o,fs->cdfd,&data_wrerr,fs); setup_istr(&fs->data_s_i,fs->sdfd,&data_rchk_s,&data_data_s,&data_rderr,fs); setup_ostr(&fs->data_s_o,fs->sdfd,&data_wrerr,fs); fs->dataid = add_block_fn(&check_data_done,fs); fs->datamode = DATA_LIVE; fs->datacount ++; fs->datacib = 0; fs->datacob = 0; } /* * A passed-along data connection attempt has failed. destroy_data * will shut down whatever partial pieces exist. * * We'd really like to make the data connection to us fail similarly, * but we have no support for that kind of deferred connection * establishment. */ static void data_fail(void *fsv, int err) { fprintf(stderr,"%s: data connection: %s\n",__progname,strerror(err)); /* We are called only from within cip_*, so we never want destroy_data to call cip_cancel! */ ((FTPSTATE *)fsv)->cip = 0; destroy_data(fsv); } /* * A passed-along data connection attempt has succeeded. Store the * file descriptor in the appropriate place and call data_live to * bring the connection the rest of the way up. */ static void data_ok(void *fsv, int fd) { FTPSTATE *fs; fs = fsv; switch (fs->datamode) { default: abort(); break; case DATA_PORTC: case DATA_EPRTC: fs->cdfd = fd; data_live(fs); break; case DATA_PASVC: case DATA_EPSVC: fs->sdfd = fd; data_live(fs); break; } } /* * Return a file descriptor bound to the given address; also, update * the address to match the address bound to (which can be a change * if, for example, the argument has a zero port number). */ static int bound_to(struct sockaddr_storage *a) { int fd; socklen_t sslen; struct sockaddr_storage ss; fd = socket(a->ss_family,SOCK_STREAM,0); if (fd < 0) return(-1); if (bind(fd,(void *)a,a->ss_len) < 0) { close_noerr(fd); return(-1); } sslen = sizeof(ss); if (getsockname(fd,(void *)&ss,&sslen) < 0) { close_noerr(fd); return(-1); } if ((ss.ss_family != a->ss_family) || (ss.ss_len != a->ss_len)) { fprintf(stderr,"%s: bind-vs-getsockname mismatch\n",__progname); abort(); } *a = ss; return(fd); } /* * Insert a "forged" state table entry so we can connect to the client * "from the server". Used when setting up PORT/EPRT data * connections. */ static void add_state( const struct sockaddr_storage *tgt, const struct sockaddr_storage *us, const struct sockaddr_storage *src ) { struct pfioc_state s; if ((tgt->ss_family != us->ss_family) || (us->ss_family != src->ss_family)) abort(); s.state.id = 0; strncpy(&s.state.u.ifname[0],"self",sizeof(s.state.u.ifname)); switch (us->ss_family) { case AF_INET: s.state.lan.addr.v4 = ((const struct sockaddr_in *)us)->sin_addr; s.state.lan.port = ((const struct sockaddr_in *)us)->sin_port; s.state.gwy.addr.v4 = ((const struct sockaddr_in *)tgt)->sin_addr; s.state.gwy.port = ((const struct sockaddr_in *)tgt)->sin_port; s.state.ext.addr.v4 = ((const struct sockaddr_in *)src)->sin_addr; s.state.ext.port = ((const struct sockaddr_in *)src)->sin_port; break; case AF_INET6: s.state.lan.addr.v6 = ((const struct sockaddr_in6 *)us)->sin6_addr; s.state.lan.port = ((const struct sockaddr_in6 *)us)->sin6_port; s.state.gwy.addr.v6 = ((const struct sockaddr_in6 *)tgt)->sin6_addr; s.state.gwy.port = ((const struct sockaddr_in6 *)tgt)->sin6_port; s.state.ext.addr.v6 = ((const struct sockaddr_in6 *)src)->sin6_addr; s.state.ext.port = ((const struct sockaddr_in6 *)src)->sin6_port; break; default: return; break; } s.state.src.seqlo = 0; s.state.src.seqhi = 1; s.state.src.seqdiff = 0; s.state.src.max_win = 1; s.state.src.state = TCPS_SYN_SENT; s.state.src.wscale = 0; s.state.src.mss = 0; s.state.src.scrub = 0; s.state.dst.seqlo = 0; s.state.dst.seqhi = 1; s.state.dst.seqdiff = 0; s.state.dst.max_win = 1; s.state.dst.state = TCPS_CLOSED; s.state.dst.wscale = 0; s.state.dst.mss = 0; s.state.dst.scrub = 0; /* rule, anchor, nat_rule not used */ bzero(&s.state.rt_addr,sizeof(s.state.rt_addr)); s.state.rt_kif = 0; s.state.src_node = 0; s.state.nat_src_node = 0; /* creation not used */ s.state.expire = 60; /* pfsync_time, packets, bytes not used */ s.state.creatorid = 0; s.state.af = us->ss_family; s.state.proto = IPPROTO_TCP; s.state.direction = PF_IN; s.state.log = 0; s.state.allow_opts = 0; s.state.timeout = PFTM_TCP_FIRST_PACKET; s.state.sync_flags = 0; Ioctl(pffd,DIOCADDSTATE,&s); } /* * Test whether two sockaddr_storages refer to the same host address. * Used to check that, for example, the data and control connections * are going to the same place. (This breaks the protocol as * originally defined, but in today's hostile net, FTP bounce attacks * have killed off third-party transfers.) */ static int same_host(const struct sockaddr_storage *a, const struct sockaddr_storage *b) { if (a->ss_family != b->ss_family) return(0); switch (a->ss_family) { case AF_INET: return( ((const struct sockaddr_in *)a)->sin_addr.s_addr == ((const struct sockaddr_in *)b)->sin_addr.s_addr ); break; case AF_INET6: return(!bcmp( &((const struct sockaddr_in6 *)a)->sin6_addr.s6_addr[0], &((const struct sockaddr_in6 *)b)->sin6_addr.s6_addr[0], 16)); break; default: abort(); break; } } /* * Accept a data connection. This may be from the server or the * client, depending on the datamode. Either way, set stuff up and * initiate the connection on to the other side. */ static void accept_data(int id __attribute__((__unused__)), void *fsv) { FTPSTATE *fs; int fd; socklen_t sslen; int newstate; fs = fsv; switch (fs->datamode) { default: abort(); break; case DATA_PORT: newstate = DATA_PORTC; if (0) { case DATA_EPRT: newstate = DATA_EPRTC; } sslen = sizeof(fs->data_s_ss); fd = accept(fs->sdfd,(void *)&fs->data_s_ss,&sslen); if (fd < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: data accept: %s\n",__progname,strerror(errno)); exit(1); } if (! same_host(&fs->data_s_ss,&fs->ctl_s_ss)) { fprintf(stderr,"%s: dropping data connection from wrong host\n",__progname); close(fd); return; } close(fs->sdfd); fs->sdfd = fd; fs->data_oc_ss = fs->ctl_oc_ss; set_port(&fs->data_oc_ss,0); fd = bound_to(&fs->data_oc_ss); if (fd < 0) { fprintf(stderr,"%s: data connection: %s\n",__progname,strerror(errno)); destroy_data(fsv); return; } add_state(&fs->data_s_ss,&fs->data_oc_ss,&fs->data_c_ss); remove_poll_id(fs->dataid); fs->datamode = newstate; fs->cip = 0; /* in case cip_connect -> data_fail -> destroy_data */ fs->cip = cip_connect(fd,0,&fs->data_c_ss,&data_fail,&data_ok,fs); break; case DATA_PASVB: newstate = DATA_PASVC; if (0) { case DATA_EPSVB: newstate = DATA_EPSVC; } sslen = sizeof(fs->data_c_ss); fd = accept(fs->cdfd,(void *)&fs->data_c_ss,&sslen); if (fd < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: data accept: %s\n",__progname,strerror(errno)); exit(1); } if (! same_host(&fs->data_c_ss,&fs->ctl_c_ss)) { fprintf(stderr,"%s: dropping data connection from wrong host\n",__progname); close(fd); return; } close(fs->cdfd); fs->cdfd = fd; fs->data_os_ss = fs->ctl_os_ss; set_port(&fs->data_os_ss,0); fd = bound_to(&fs->data_os_ss); if (fd < 0) { fprintf(stderr,"%s: data connection: %s\n",__progname,strerror(errno)); destroy_data(fsv); return; } if (verbose) printf("passive data from %s/%d\n", inet_ntoa(((struct sockaddr_in *)&fs->data_os_ss)->sin_addr), ntohs(((struct sockaddr_in *)&fs->data_os_ss)->sin_port)); remove_poll_id(fs->dataid); fs->datamode = newstate; fs->cip = 0; /* in case cip_connect -> data_fail -> destroy_data */ fs->cip = cip_connect(fd,0,&fs->data_s_ss,&data_fail,&data_ok,fs); break; } } /* * Field the response to a PORT command. (The only reason for paying * any attention to the response is so we can tear down what we've set * up if the server rejects the PORT command for some reason. If we * didn't care about that we could use resp_copy instead.) */ static void resp_port(FTPSTATE *fs, char key) { char *l; int ll; fs->state = FTPS_CMD; switch (key) { default: fprintf(stderr,"%s: treating bogus PORT response code `%c' as 5\n",__progname,key); ostr_q_point(&fs->ctl_c_o,"501-Server sent broken PORT response:",-1); while (lq_pop(&fs->resplq,&l,&ll,0)) { ostr_q_point(&fs->ctl_c_o,"\r\n| ",-1); ostr_q_free(&fs->ctl_c_o,l,ll); } ostr_q_point(&fs->ctl_c_o,"\r\n501 (response code is not 2, 4, or 5)\r\n",-1); destroy_data(fs); break; case '2': resp_echo(fs); break; case '4': case '5': resp_echo(fs); destroy_data(fs); break; } } /* * We've received a PORT command from the client. Set up the first * stage (move the datamode from NONE to PORT) and generate a suitable * PORT command to the server. */ static void setup_port(FTPSTATE *fs, char *l) { struct sockaddr_in sin; const char *err; unsigned int v; err = scan_v4_portspec(l,&sin); if (err) { ostr_q(&fs->ctl_c_o,"501 Syntax error in PORT command (%s)\r\n",err,OSQ_END); return; } if (fs->ctl_c_ss.ss_family != AF_INET) { ostr_q_point(&fs->ctl_c_o,"501 PORT works only over IPv4\r\n",-1); return; } if ( sin.sin_addr.s_addr != ((struct sockaddr_in *)&fs->ctl_c_ss)->sin_addr.s_addr ) { ostr_q_point(&fs->ctl_c_o,"501 Mismatch between PORT address and control connection address\r\n",-1); return; } destroy_data(fs); *(struct sockaddr_in *)&fs->data_c_ss = sin; fs->data_os_ss = fs->ctl_os_ss; set_port(&fs->data_os_ss,0); fs->sdfd = bound_to(&fs->data_os_ss); if (fs->sdfd < 0) { int e; e = errno; ostr_q(&fs->ctl_c_o,"501 Can't set up proxy PORT socket [",strerror(e),"]\r\n",OSQ_END); return; } if (listen(fs->sdfd,10) < 0) { int e; e = errno; close(fs->sdfd); ostr_q(&fs->ctl_c_o,"501 Can't set up proxy PORT socket [",strerror(e),"]\r\n",OSQ_END); return; } if (verbose) printf("Telling server PORT "); ostr_q_point(&fs->ctl_s_o,"PORT ",-1); v = ntohl(((struct sockaddr_in *)&fs->data_os_ss)->sin_addr.s_addr); if (verbose) printf("%d,%d,%d,%d",v>>24,(v>>16)&255,(v>>8)&255,v&255); ostr_q_printf(&fs->ctl_s_o,"%d,%d,%d,%d",v>>24,(v>>16)&255,(v>>8)&255,v&255); v = ntohs(((struct sockaddr_in *)&fs->data_os_ss)->sin_port); if (verbose) printf(",%d,%d\n",v>>8,v&255); ostr_q_printf(&fs->ctl_s_o,",%d,%d\r\n",v>>8,v&255); fs->state = FTPS_RESPA; fs->respfn = &resp_port; fs->datamode = DATA_PORT; fs->dataid = add_poll_fd(fs->sdfd,&rwtest_always,&rwtest_never,&accept_data,0,fs); } /* * Field the response to a PASV command. Set stuff up and generate a * suitable PASV response to the client, moving from PASVA to PASVB. */ static void resp_pasv(FTPSTATE *fs, char key) { char *l; char *at; const char *err; unsigned int v; struct sockaddr_in sin; char txt[32]; switch (key) { case '2': if (fs->datamode != DATA_PASVA) abort(); if (lq_peek(&fs->resplq,&l,0,0)) abort(); at = rindex(l,'('); if (! at) at = rindex(l,' '); if (! at) { fprintf(stderr,"%s: unparseable PASV response\n",__progname); ostr_q_point(&fs->ctl_c_o,"501 Unparseable PASV response from server\r\n",-1); destroy_data(fs); resp_drop(fs); break; } err = scan_v4_portspec(at+1,&sin); resp_drop(fs); if (err) { ostr_q(&fs->ctl_c_o,"501 Syntax error in PASV response (",err,")\r\n",OSQ_END); destroy_data(fs); break; } if ( sin.sin_addr.s_addr != ((struct sockaddr_in *)&fs->ctl_s_ss)->sin_addr.s_addr ) { ostr_q_point(&fs->ctl_c_o,"501 Mismatch between PASV response and control connection address\r\n",-1); destroy_data(fs); break; } *((struct sockaddr_in *)&fs->data_s_ss) = sin; if (verbose) printf("PASV response %s/%d\n", inet_ntoa(((struct sockaddr_in *)&fs->data_s_ss)->sin_addr), ntohs(((struct sockaddr_in *)&fs->data_s_ss)->sin_port)); destroy_data(fs); fs->data_oc_ss = localhost_v4; set_port(&fs->data_oc_ss,0); fs->cdfd = bound_to(&fs->data_oc_ss); if (fs->cdfd < 0) { ostr_q(&fs->ctl_c_o,"501 Can't set up proxy PASV socket [",strerror(errno),"]\r\n",OSQ_END); return; } if (listen(fs->cdfd,10) < 0) { close_noerr(fs->cdfd); ostr_q(&fs->ctl_c_o,"501 Can't set up proxy PASV socket [",strerror(errno),"]\r\n",OSQ_END); return; } fs->rdr = add_rdr(&fs->ctl_c_ss,&fs->data_s_ss,&fs->data_oc_ss); if (! fs->rdr) { close(fs->cdfd); ostr_q_point(&fs->ctl_c_o,"501 Can't install pf rule for proxy PASV socket\r\n",-1); return; } ostr_q_point(&fs->ctl_c_o,"227 PASV mode (",-1); v = ntohl(((struct sockaddr_in *)&fs->ctl_s_ss)->sin_addr.s_addr); sprintf(&txt[0],"%d,%d,%d,%d",v>>24,(v>>16)&255,(v>>8)&255,v&255); ostr_q_copy(&fs->ctl_c_o,&txt[0],-1); v = ntohs(sin.sin_port); sprintf(&txt[0],",%d,%d",v>>8,v&255); ostr_q_copy(&fs->ctl_c_o,&txt[0],-1); ostr_q_point(&fs->ctl_c_o,"\r\n",-1); fs->datamode = DATA_PASVB; fs->dataid = add_poll_fd(fs->cdfd,&rwtest_always,&rwtest_never,&accept_data,0,fs); fs->state = FTPS_CMD; break; default: fprintf(stderr,"%s: treating bogus PASV response code `%c' as 5\n",__progname,key); /* fall through */ case '4': case '5': resp_echo(fs); fs->state = FTPS_CMD; destroy_data(fs); break; } } /* * We've received a PASV command from the client. There isn't much to * do here, since we can't set anything up until we see the response * from the server. */ static void setup_pasv(FTPSTATE *fs, char *l) { if (fs->ctl_c_ss.ss_family != AF_INET) { ostr_q_point(&fs->ctl_c_o,"501 PASV works only over IPv4\r\n",-1); return; } while (isspace(*(unsigned char *)l)) l ++; if (*l) { ostr_q_point(&fs->ctl_c_o,"501 PASV takes no argument\r\n",-1); return; } destroy_data(fs); fs->state = FTPS_RESPA; fs->respfn = &resp_pasv; fs->datamode = DATA_PASVA; ostr_q_copy(&fs->ctl_s_o,"PASV\r\n",-1); } /* * Handle a response to an EPRT. Just like resp_port, above, except * for a few messages which change to mention EPRT instad of PORT. */ static void resp_eprt(FTPSTATE *fs, char key) { char *l; int ll; fs->state = FTPS_CMD; switch (key) { default: fprintf(stderr,"%s: treating bogus EPRT response code `%c' as 5\n",__progname,key); ostr_q_point(&fs->ctl_c_o,"501-Server sent broken EPRT response:",-1); while (lq_pop(&fs->resplq,&l,&ll,0)) { ostr_q_point(&fs->ctl_c_o,"\r\n| ",-1); ostr_q_free(&fs->ctl_c_o,l,ll); } ostr_q_point(&fs->ctl_c_o,"\r\n501 (response code is not 2, 4, or 5)\r\n",-1); destroy_data(fs); break; case '2': resp_echo(fs); break; case '4': case '5': resp_echo(fs); destroy_data(fs); break; } } /* * We received an EPRT from the client. This is very much like * setup_port, above, except that we have to parse it differently and * handle both v4 and v6. */ static void setup_eprt(FTPSTATE *fs, char *l) { __label__ ret; char d; char *end; char *s_proto; char *s_addr; char *s_port; int proto; struct sockaddr_storage ss; char txt[64]; const char *t; char *s; auto void error(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void error(const char *fmt, ...) { char *t; va_list ap; va_start(ap,fmt); vasprintf(&t,fmt,ap); va_end(ap); ostr_q(&fs->ctl_c_o,"501 Error in EPRT command (",OSQ_FREE,t,")\r\n",OSQ_END); goto ret; } auto void fail(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void fail(const char *fmt, ...) { char *t; va_list ap; va_start(ap,fmt); vasprintf(&t,fmt,ap); va_end(ap); ostr_q(&fs->ctl_c_o,OSQ_FREE,t,")\r\n",OSQ_END); goto ret; } if (*l != ' ') error("missing space after verb"); d = l[1]; if ((d < 33) || (d > 126)) error("Bad delimiter 0x%02x",(unsigned char)d); s_proto = l + 2; end = index(s_proto,d); if (! end) error("too few fields"); *end = '\0'; s_addr = end + 1; end = index(s_addr,d); if (! end) error("too few fields"); *end = '\0'; s_port = end + 1; end = index(s_addr,d); if (! end) error("too few fields"); *end = '\0'; if (end[1]) error("junk after arguments"); proto = atoi(s_proto); switch (proto) { case 1: ss.ss_family = AF_INET; ss.ss_len = sizeof(struct sockaddr_in); if (fs->ctl_c_ss.ss_family != AF_INET) { error("address family doesn't match control connection's"); } if (! inet_pton(AF_INET,s_addr,&((struct sockaddr_in *)&ss)->sin_addr)) { error("malformed IPv4 address"); } ((struct sockaddr_in *)&ss)->sin_port = htons(atoi(s_port)); break; case 2: ss.ss_family = AF_INET6; ss.ss_len = sizeof(struct sockaddr_in6); if (fs->ctl_c_ss.ss_family != AF_INET6) { error("address family doesn't match control connection's"); } if (! inet_pton(AF_INET6,s_addr,&((struct sockaddr_in6 *)&ss)->sin6_addr)) { error("malformed IPv6 address"); } ((struct sockaddr_in6 *)&ss)->sin6_port = htons(atoi(s_port)); break; default: error("unknown protocol %d",proto); break; } if (! same_host(&ss,&fs->ctl_c_ss)) { error("host specified doesn't match control connection's"); } destroy_data(fs); fs->data_c_ss = ss; fs->data_os_ss = fs->ctl_os_ss; set_port(&fs->data_os_ss,0); fs->sdfd = bound_to(&fs->data_os_ss); if (fs->sdfd < 0) { fail("501 Can't set up proxy EPRT socket [%s]",strerror(errno)); } if (listen(fs->sdfd,10) < 0) { close_noerr(fs->sdfd); fail("501 Can't set up proxy EPRT socket [%s]",strerror(errno)); } switch (ss.ss_family) { case AF_INET: t = inet_ntop(AF_INET,&((struct sockaddr_in *)&fs->data_os_ss)->sin_addr,&txt[0],sizeof(txt)); if (! t) { close_noerr(fs->sdfd); fail("501 Can't set up proxy EPRT socket [%s]",strerror(errno)); } asprintf(&s,"%d",ntohs(((struct sockaddr_in *)&fs->data_os_ss)->sin_port)); printf("Telling server EPRT |1|%s|%s|\n",t,s); ostr_q(&fs->ctl_s_o,"EPRT |1|",OSQ_COPY,t,"|",OSQ_FREE,s,"|\r\n",OSQ_END); break; case AF_INET6: t = inet_ntop(AF_INET6,&((struct sockaddr_in6 *)&fs->data_os_ss)->sin6_addr,&txt[0],sizeof(txt)); if (! t) { close_noerr(fs->sdfd); fail("501 Can't set up proxy EPRT socket [%s]",strerror(errno)); } asprintf(&s,"%d",ntohs(((struct sockaddr_in6 *)&fs->data_os_ss)->sin6_port)); printf("Telling server EPRT |2|%s|%s|\n",t,s); ostr_q(&fs->ctl_s_o,"EPRT |2|",OSQ_COPY,t,"|",OSQ_FREE,s,"|\r\n",OSQ_END); break; default: abort(); break; } fs->state = FTPS_RESPA; fs->respfn = &resp_eprt; fs->datamode = DATA_EPRT; fs->dataid = add_poll_fd(fs->sdfd,&rwtest_always,&rwtest_never,&accept_data,0,fs); ret:; } /* * Handle an EPSV response. This is very much like resp_pasv, above, * except that we have to parse it differently. */ static void resp_epsv(FTPSTATE *fs, char key) { char *l; char *at; char *end; long int v; char d; switch (key) { case '2': do <"can't-parse"> { do <"can't-setup"> { if (lq_peek(&fs->resplq,&l,0,0)) abort(); at = rindex(l,'('); if (! at) break <"can't-parse">; d = at[1]; if ((at[2] != d) || (at[3] != d)) break <"can't-parse">; end = index(at+4,d); if (! end) break <"can't-parse">; if (end[1] != ')') break <"can't-parse">; *end = '\0'; v = strtol(at+4,&end,10); if ( *end || (end == at+4) || (v < 0) || (v > 65535) ) break <"can't-parse">; resp_drop(fs); if (verbose) printf("EPSV response %d\n",(int)v); fs->data_s_ss = fs->ctl_s_ss; set_port(&fs->data_s_ss,v); fs->data_oc_ss = *localhost(fs->ctl_c_ss.ss_family); set_port(&fs->data_oc_ss,0); fs->cdfd = bound_to(&fs->data_oc_ss); if (fs->cdfd < 0) break <"can't-setup">; if (listen(fs->cdfd,10) < 0) { close_noerr(fs->cdfd); break <"can't-setup">; } destroy_data(fs); fs->rdr = add_rdr(&fs->ctl_c_ss,&fs->data_s_ss,&fs->data_oc_ss); if (! fs->rdr) { close(fs->cdfd); ostr_q_point(&fs->ctl_c_o,"501 Can't install pf rule for proxy EPSV socket\r\n",-1); return; } ostr_q_printf(&fs->ctl_c_o,"229 EPSV mode (|||%d|)\r\n",get_port(&fs->data_s_ss)); fs->datamode = DATA_EPSVB; fs->dataid = add_poll_fd(fs->cdfd,&rwtest_always,&rwtest_never,&accept_data,0,fs); fs->state = FTPS_CMD; return; } while (0); ostr_q(&fs->ctl_c_o,"501 Can't set up proxy EPSV socket [",strerror(errno),"]\r\n",OSQ_END); return; } while (0); resp_drop(fs); if (verbose) printf("bad EPSV response\n"); ostr_q_point(&fs->ctl_c_o,"501 Invalid EPSV response from server\r\n",-1); destroy_data(fs); return; break; default: printf("treating bogus EPSV response code `%c' as 5\n",key); /* fall through */ case '4': case '5': resp_echo(fs); fs->state = FTPS_CMD; destroy_data(fs); break; } } /* * We got an EPSV from the client. This is about the same as * setup_pasv, above, except that we have to handle a bunch of stuff * that PASV doesn't do, like EPSV ALL and EPSV with a family * identifier. */ static void setup_epsv(FTPSTATE *fs, char *l) { while (isspace(*(unsigned char *)l)) l ++; if (! strcasecmp(l,"all")) { ostr_q_point(&fs->ctl_s_o,"EPSV ALL\r\n",-1); fs->state = FTPS_RESPA; fs->respfn = &resp_copy; } else if (! *l) { destroy_data(fs); fs->state = FTPS_RESPA; fs->respfn = &resp_epsv; fs->datamode = DATA_EPSVA; ostr_q_point(&fs->ctl_s_o,"EPSV\r\n",-1); } else { long int v; char *ep; v = strtol(l,&ep,10); if ((l == ep) || *ep) { ostr_q_point(&fs->ctl_c_o,"501 Invalid EPSV argument\r\n",-1); return; } switch (v) { case 1: if (fs->ctl_c_ss.ss_family != AF_INET) { ostr_q_point(&fs->ctl_c_o,"522 EPSV protocol must match control connection's (1)\r\n",-1); return; } ostr_q_point(&fs->ctl_s_o,"EPSV 1\r\n",-1); break; case 2: if (fs->ctl_c_ss.ss_family != AF_INET6) { ostr_q_point(&fs->ctl_c_o,"522 EPSV protocol must match control connection's (2)\r\n",-1); return; } ostr_q_point(&fs->ctl_s_o,"EPSV 2\r\n",-1); break; default: ostr_q_point(&fs->ctl_c_o,"501 Invalid EPSV protocol number\r\n",-1); return; } destroy_data(fs); fs->state = FTPS_RESPA; fs->respfn = &resp_epsv; fs->datamode = DATA_EPSVA; } } /* * Check for client control-connection input. Do this only if we're * ready to handle another command. (The client arguably shouldn't be * sending more until it gets a response, but we have to be prepared * to handle it if it happens.) */ static int check_cclq(void *fv) { FTPSTATE *fs; char *l; int ll; int flg; char *f; fs = fv; if ((fs->state != FTPS_CMD) || lq_pop(&fs->cclq,&l,&ll,&flg)) return(BLOCK_NIL); if (l == 0) { if (verbose) printf("client cmd EOF\n"); ostr_q_eof(&fs->ctl_s_o); fs->state = FTPS_EOF; return(BLOCK_LOOP); } if (verbose) printf("client cmd%s %.*s\n",(flg&TNS_F_MARKED)?" (marked)":"",ll,l); f = 0; if ((ll >= 4) && !strncasecmp(l,"port",4)) { setup_port(fs,l+4); free(l); return(BLOCK_LOOP); } else if ((ll >= 4) && !strncasecmp(l,"pasv",4)) { setup_pasv(fs,l+4); free(l); return(BLOCK_LOOP); } else if ((ll >= 4) && !strncasecmp(l,"eprt",4)) { setup_eprt(fs,l+4); free(l); return(BLOCK_LOOP); } else if ((ll >= 4) && !strncasecmp(l,"epsv",4)) { setup_epsv(fs,l+4); free(l); return(BLOCK_LOOP); } if (flg & TNS_F_MARKED) { static const unsigned char ip_synch[] = { TELNET_IAC, TELNET_IP, TELNET_IAC, TELNET_DM }; ostr_q_point_urg(&fs->ctl_s_o,&ip_synch,sizeof(ip_synch)); } ostr_q(&fs->ctl_s_o,OSQ_LEN(ll),l,"\r\n",OSQ_END); fs->state = FTPS_RESPA; fs->respfn = &resp_copy; return(BLOCK_LOOP); } /* * Input function for the server-side control connection. Just pass it * off to telnet processing. */ static void ctl_in_s(void *fsv, const void *data, int len, int atmark) { telnet_process(&((FTPSTATE *)fsv)->ctl_s_tn,data,len,atmark); } /* * Input function for the client-side control connection. Just pass it * off to telnet processing. */ static void ctl_in_c(void *fsv, const void *data, int len, int atmark) { telnet_process(&((FTPSTATE *)fsv)->ctl_c_tn,data,len,atmark); } /* * Read error function for the control connection. We don't know which * one, but we don't care; just kill off the session. */ static void ctl_rderr(void *fsv, int err) { FTPSTATE *fs; fs = fsv; printf("[%s] control connection read error: %s\n",fs->tag,strerror(err)); kill_ftp(fs); } /* * Write error function for the control connection. We don't know * which one, but we don't care; just kill off the session. */ static void ctl_wrerr(void *fsv, int err) { FTPSTATE *fs; fs = fsv; printf("[%s] control connection write error: %s\n",fs->tag,strerror(err)); kill_ftp(fs); } /* * A passed-along control connection to the server has succeeded. * Record it, log it, and initialize state (RESPA, since the first * thing that should happen is a greeting banner "response"). */ static void ctl_connect_ok(void *fsv, int fd) { FTPSTATE *fs; socklen_t sslen; fs = fsv; sslen = sizeof(fs->ctl_os_ss); if (getsockname(fd,(void *)&fs->ctl_os_ss,&sslen) < 0) { fprintf(stderr,"%s: ctl getsockname: %s\n",__progname,strerror(errno)); kill_ftp(fs); return; } fs->flags |= FSF_EVERLIVE; fs->scfd = fd; setup_istr(&fs->ctl_s_i,fs->scfd,0,&ctl_in_s,&ctl_rderr,fs); setup_ostr(&fs->ctl_s_o,fs->scfd,&ctl_wrerr,fs); fs->state = FTPS_RESPA; printf("[%s] ",fs->tag); switch (fs->ctl_c_ss.ss_family) { case AF_INET: printf("%s/%d -> ", inet_ntoa(((struct sockaddr_in *)&fs->ctl_c_ss)->sin_addr), ntohs(((struct sockaddr_in *)&fs->ctl_c_ss)->sin_port)); printf("%s/%d -> ", inet_ntoa(((struct sockaddr_in *)&fs->ctl_oc_ss)->sin_addr), ntohs(((struct sockaddr_in *)&fs->ctl_oc_ss)->sin_port)); printf("%s/%d -> ", inet_ntoa(((struct sockaddr_in *)&fs->ctl_os_ss)->sin_addr), ntohs(((struct sockaddr_in *)&fs->ctl_os_ss)->sin_port)); printf("%s/%d\n", inet_ntoa(((struct sockaddr_in *)&fs->ctl_s_ss)->sin_addr), ntohs(((struct sockaddr_in *)&fs->ctl_s_ss)->sin_port)); break; case AF_INET6: { static void foo(const struct sockaddr_storage *ss) { char txt[64]; const char *t; t = inet_ntop(AF_INET6,&((const struct sockaddr_in6 *)ss)->sin6_addr,&txt[0],sizeof(txt)); if (t) { printf("%s/%d",t,ntohs(((const struct sockaddr_in6 *)ss)->sin6_port)); } else { printf("[%s]",strerror(errno)); } } foo(&fs->ctl_c_ss); printf(" -> "); foo(&fs->ctl_oc_ss); printf(" -> "); foo(&fs->ctl_os_ss); printf(" -> "); foo(&fs->ctl_s_ss); printf("\n"); } break; } } /* * A passed-along control connection to the server has failed. Report * it to the client as a 421 "greeting banner" (since a greeting * banner is what the client is expecting at this point). */ static void ctl_connect_fail(void *fsv, int err) { FTPSTATE *fs; fs = fsv; ostr_q(&fs->ctl_c_o,"421 Proxy connect to server: ",strerror(err),"\r\n",OSQ_END); fs->cip = 0; kill_ftp(fs); } /* * Set up the stdio descriptors. Put /dev/tty on any which aren't * open, unless /dev/tty can't be opened, in which case use /dev/null * instead. (If /dev/null can't be opened either, error out.) * * This is done only once, at startup. */ static void stdio_setup(void) { int std; std = open("/dev/tty",O_RDWR,0); if (std < 0) { std = open("/dev/null",O_RDWR,0); if (std < 0) { fprintf(stderr,"%s: /dev/null: %s\n",__progname,strerror(errno)); exit(1); } } while (std < 3) std = dup(std); close(std); nullfd = open(devnull,O_RDWR,0); if (nullfd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,devnull,strerror(errno)); exit(1); } } /* * Block function watching for control connection EOF. Once EOF has * been read in both directions, and everything has drained, kill off * the session. */ static int check_die(void *vfs) { FTPSTATE *fs; fs = vfs; if ( fs->ctl_c_i.eof && ostr_empty(&fs->ctl_s_o) && fs->ctl_s_i.eof && ostr_empty(&fs->ctl_c_o) ) { kill_ftp(fs); return(BLOCK_LOOP); } return(BLOCK_NIL); } /* * Request SO_OOBINLINE on a socket. This is done on the control * connection to the client, so we can handle urgent data properly. * (Or at least as properly as possible given the BSD braindamage of * trying to turn the urgent pointer into OOB data.) */ static void setup_oob(int fd) { int v; v = 1; setsockopt(fd,SOL_SOCKET,SO_OOBINLINE,&v,sizeof(v)); } /* * Generate a tag string, given a timestamp and a serial number. * * We do this by taking the three pieces (serial, timestamp seconds, * and timestamp microseconds) as pieces of a large number, which we * then convert to base 62. nbuf[] holds the number; it is sized to * hold 18446744073709551616000000, or 0xf42400000000000000000 - this * is 2^32 [serial] * 2^32 [now.tv_sec] * 10^6 [now.tv_usec]. tbuf * holds the text version; 62^14 and 62^15 bracket that number. */ static void gen_tag(FTPSTATE *fs) { struct timeval now; unsigned char nbuf[11]; unsigned char tbuf[15+1]; int i; static const char ch[62] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; static void muladd(unsigned long long int m, unsigned long long int a) { int i; for (i=0;i<(sizeof(nbuf)/sizeof(nbuf[0]));i++) { a += nbuf[i] * m; nbuf[i] = a & 0xff; a >>= 8; } } static int divmod(void) { int r; int i; r = 0; for (i=(sizeof(nbuf)/sizeof(nbuf[0]))-1;i>=0;i--) { r = (r << 8) + nbuf[i]; nbuf[i] = r / 62; r %= 62; } return(r); } gettimeofday(&now,0); muladd(0,serial++); muladd(1ULL<<32,now.tv_sec); muladd(1000000,now.tv_usec); for (i=(sizeof(tbuf)/sizeof(tbuf[0]))-1-1;i>=0;i--) tbuf[i] = ch[divmod()]; tbuf[(sizeof(tbuf)/sizeof(tbuf[0]))-1] = '\0'; for (i=0;tbuf[i]==ch[0];i++) ; fs->tag = strdup(&tbuf[i]); } /* * Start up a new session. This is called from the AF-specific * new-connection routines to do common stuff. Mostly, we just set up * the fields of the FTPSTATE so that if we kill the session on an * error, we don't free bogus pointers or suchlike. But we also do * the client control connection setup, look up the NAT state, and * initiate the control connection to the server. The next step is * taken when that connection completes. */ static void ftp_startup(FTPSTATE *fs, int fd) { gen_tag(fs); set_nonblock(fd); set_keepalives(fd); fs->state = FTPS_NONE; fs->flags = 0; fs->respfn = &resp_copy; fs->datamode = DATA_NONE; fs->ccfd = fd; fs->scfd = -1; istr_init(&fs->ctl_c_i); ostr_init(&fs->ctl_c_o); istr_init(&fs->ctl_s_i); ostr_init(&fs->ctl_s_o); telnet_init(&fs->ctl_c_tn); telnet_init(&fs->ctl_s_tn); lq_init(&fs->resplq); lq_init(&fs->cclq); fs->cclq_id = add_block_fn(&check_cclq,fs); lq_init(&fs->sclq); fs->sclq_id = add_block_fn(&check_sclq,fs); istr_init(&fs->data_c_i); ostr_init(&fs->data_c_o); istr_init(&fs->data_s_i); ostr_init(&fs->data_s_o); fs->die_id = PL_NOID; fs->rdr = 0; fs->datacount = 0; fs->dataib = 0; fs->dataob = 0; telnet_setup(&fs->ctl_c_tn,fs,&fs->ctl_s_o,&fs->ctl_c_o,&client_line); telnet_setup(&fs->ctl_s_tn,fs,&fs->ctl_c_o,&fs->ctl_s_o,&server_line); setup_istr(&fs->ctl_c_i,fs->ccfd,0,&ctl_in_c,&ctl_rderr,fs); setup_ostr(&fs->ctl_c_o,fs->ccfd,&ctl_wrerr,fs); setup_oob(fs->ccfd); lookup_connection(fs); if (fs->state == FTPS_KILL) return; fs->cip = cip_connect(-1,0,&fs->ctl_s_ss,&ctl_connect_fail,&ctl_connect_ok,fs); if (fs->state == FTPS_KILL) return; fs->state = FTPS_CONN; fs->die_id = add_block_fn(&check_die,fs); } /* * Set up the localhost sockaddr_storages. */ static void setup_localhosts(void) { bzero(&localhost_v4,sizeof(localhost_v4)); ((struct sockaddr_in *)&localhost_v4)->sin_family = AF_INET; ((struct sockaddr_in *)&localhost_v4)->sin_len = sizeof(struct sockaddr_in); ((struct sockaddr_in *)&localhost_v4)->sin_addr.s_addr = htonl(0x7f000001); ((struct sockaddr_in *)&localhost_v4)->sin_port = 0; bzero(&localhost_v6,sizeof(localhost_v6)); ((struct sockaddr_in6 *)&localhost_v6)->sin6_family = AF_INET6; ((struct sockaddr_in6 *)&localhost_v6)->sin6_len = sizeof(struct sockaddr_in6); ((struct sockaddr_in6 *)&localhost_v6)->sin6_addr = in6addr_loopback; ((struct sockaddr_in6 *)&localhost_v6)->sin6_port = 0; } /* * Accept an IPv4 connection. Not much to do here; the real work is * done by ftp_startup. */ static void accept_4(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { int fd; struct sockaddr_in sin; socklen_t sinlen; FTPSTATE *fs; sinlen = sizeof(sin); fd = accept(listen_4,(void *)&sin,&sinlen); if (fd < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: v4 accept: %s\n",__progname,strerror(errno)); exit(1); } fs = malloc(sizeof(FTPSTATE)); ftp_startup(fs,fd); } /* * Accept an IPv6 connection. Not much to do here; the real work is * done by ftp_startup. */ static void accept_6(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { int fd; struct sockaddr_in6 sin6; socklen_t sin6len; FTPSTATE *fs; sin6len = sizeof(sin6); fd = accept(listen_6,(void *)&sin6,&sin6len); if (fd < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: v6 accept: %s\n",__progname,strerror(errno)); exit(1); } fs = malloc(sizeof(FTPSTATE)); ftp_startup(fs,fd); } /* * Set up the listening sockets, one for v4 and one for v6. */ static void setup_listen(void) { struct sockaddr_storage ss; listen_4 = socket(AF_INET,SOCK_STREAM,0); if (listen_4 < 0) { fprintf(stderr,"%s: v4 socket: %s\n",__progname,strerror(errno)); } else { ss = localhost_v4; ((struct sockaddr_in *)&ss)->sin_port = htons(port); if (bind(listen_4,(void *)&ss,sizeof(struct sockaddr_in)) < 0) { fprintf(stderr,"%s: v4 bind: %s\n",__progname,strerror(errno)); exit(1); } if (listen(listen_4,10) < 0) { fprintf(stderr,"%s: v4 listen: %s\n",__progname,strerror(errno)); exit(1); } add_poll_fd(listen_4,&rwtest_always,&rwtest_never,&accept_4,0,0); } listen_6 = socket(AF_INET6,SOCK_STREAM,0); if (listen_6 < 0) { fprintf(stderr,"%s: v6 socket: %s\n",__progname,strerror(errno)); } else { ss = localhost_v6; ((struct sockaddr_in6 *)&ss)->sin6_port = htons(port); if (bind(listen_6,(void *)&ss,sizeof(struct sockaddr_in6)) < 0) { fprintf(stderr,"%s: v6 bind: %s\n",__progname,strerror(errno)); exit(1); } if (listen(listen_6,10) < 0) { fprintf(stderr,"%s: v6 listen: %s\n",__progname,strerror(errno)); exit(1); } add_poll_fd(listen_6,&rwtest_always,&rwtest_never,&accept_6,0,0); } } /* * The main loop of the rules-loader process. Just wait for rules from * the parent; as soon as we get anything, fork pfctl. Feed it input * until we get the @ terminator, at which point close it down, wait * for it to die, and write an ack byte back. * * Be careful to deal correctly with a terminator with more data * following it in a single read response. I don't think this can * happen at present (since the parent blocks for the ack when writing * the @), but it's easy enough to DTRT. */ static void rule_loader(int fd) { int rfill; int rptr; pid_t proc; pid_t dead; int procpipe; int p[2]; char *at; int n; char rbuf[512]; proc = -1; rfill = 0; rptr = 0; while (1) { if (rptr >= rfill) { rfill = read(fd,&rbuf[0],sizeof(rbuf)); if (rfill < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: continue; break; } fprintf(stderr,"%s: rule loader read: %s\n",__progname,strerror(errno)); exit(1); } if (rfill == 0) exit(0); rptr = 0; } if (proc < 0) { if (verbose) printf("rule_loader starting pfctl\n"); if (pipe(&p[0]) < 0) { fprintf(stderr,"%s: pfctl pipe: %s\n",__progname,strerror(errno)); exit(1); } proc = fork(); if (proc < 0) { fprintf(stderr,"%s: pfctl fork: %s\n",__progname,strerror(errno)); exit(1); } if (proc == 0) { dup2(p[0],0); close(p[0]); close(p[1]); dup2(nullfd,1); close(nullfd); execl(pfctl,pfctl,"-q","-a",anchor,"-F","nat","-f","-",(const char *)0); fprintf(stderr,"%s: can't exec %s: %s\n",__progname,pfctl,strerror(errno)); exit(1); } close(p[0]); procpipe = p[1]; } at = memchr(&rbuf[rptr],'@',rfill); if (at) { n = at - &rbuf[rptr]; if (n > 0) write(procpipe,&rbuf[rptr],n); close(procpipe); if (verbose) printf("rule_loader ending pfctl\n"); while (1) { dead = wait4(proc,0,0,0); if (dead == proc) break; if (dead < 0) { switch (errno) { case EINTR: continue; break; case ECHILD: fprintf(stderr,"%s: rule loader lost pfctl child\n",__progname); exit(1); break; default: fprintf(stderr,"%s: rule loader pfctl wait: %s\n",__progname,strerror(errno)); exit(1); break; } } } write(fd,"",1); proc = -1; rptr += n + 1; } else { write(procpipe,&rbuf[rptr],rfill); rptr = rfill; } } } /* * Start the rules loader process. */ static void start_rdrloader(void) { int p[2]; pid_t kid; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&p[0]) < 0) { fprintf(stderr,"%s: rule loader socketpair: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: rule loader fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { close(p[0]); setproctitle("rules loader helper"); rule_loader(p[1]); abort(); } close(p[1]); rlfd = p[0]; } /* * Check that the arglist stuff is satisfactory. */ static void checkargs(void) { if (!anchor || !intf) { fprintf(stderr,"%s: need to specify anchor and interface (-a and -i)\n",__progname); exit(1); } } /* * Handle the command-line arguments. */ static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-a")) { WANTARG(); anchor = av[skip]; continue; } if (!strcmp(*av,"-i")) { WANTARG(); intf = av[skip]; continue; } if (!strcmp(*av,"-p")) { WANTARG(); port = atoi(av[skip]); continue; } if (!strcmp(*av,"-v")) { verbose = 1; continue; } if (!strcmp(*av,"-pfdev")) { WANTARG(); devpath = av[skip]; continue; } if (!strcmp(*av,"-pfctl")) { WANTARG(); pfctl = av[skip]; continue; } if (!strcmp(*av,"-devnull")) { WANTARG(); devnull = av[skip]; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } /* * Block function to flush stdout, to make sure log entries don't get * hung onto unreasonably. */ static int flush_stdout(void *arg __attribute__((__unused__))) { fflush(stdout); return(BLOCK_NIL); } /* * main(). Pretty boring: just set stuff up and drop into the main * polling loop. */ int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); checkargs(); stdio_setup(); start_rdrloader(); init_polling(); openpf(); setup_localhosts(); setup_listen(); rdrs = 0; add_block_fn(&flush_stdout,0); setproctitle("running"); while (1) { pre_poll(); if (do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"poll: %s",strerror(errno)); exit(1); } post_poll(); } }