/* This file is in the public domain. */ #define CLIENTS_PER_PROCESS 64 #define MIN_LISTENERS 1 #define MAX_LISTENERS 10 #define MAXILINELEN 65536 #define REAL_SMTP_SERVER "backend-mx.example.net" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkip.h" #include "validaddr.h" extern const char *__progname; /* * The protocols between the various processes are relatively simple. * * Listeners can be in any of these states: * * LS_LISTENING * The listener is accepting new connections and managing * those it already has. * * LS_FULL * LS_STOPPING * LS_DYING * The listener is managing existing connections but not * listening for new connections. * * When the parent forks a new listener it comes up in state * LS_LISTENING. * * When an LS_LISTENING listener reaches CLIENTS_PER_PROCESS clients, * it switches to LS_FULL and sends the parent an LM_FULL byte. * * When an LS_FULL listener loses a client, it switches to LS_LISTENING * and sends the parent an LM_LISTENING byte. * * When the parent wants to kill off a listener, it sends it an LM_STOP * byte. The listener then switches from LS_LISTENING or LS_FULL (as * the case may be) to LS_STOPPING. * * When the parent dies, the listener notices the connection to the * parent break and switches to LS_DYING. This state is just like * LS_STOPPING except that the listener does not try to interact * further with the parent. * * When an LS_STOPPING listener has no clients, it sends the parent an * LM_DYING byte and exits; LS_DYING is the same except that the * listener doesn't send anything before exiting. * * When an LS_LISTENING listener sees a new connection, it accepts it * and starts speaking SMTP to it. When it gets a valid RCPT, it does * DNSBL checks (see below), and if they pass, opens a connection to * the real SMTP server, does initial protocol with it, and (except * for filtering invalid RCPTs) transparently passes the rest of the * SMTP protocol between the two. If it gets an error trying to talk * to the real SMTP server, it starts 4xxing all SMTP protocol * commands. * * The parent tries to keep no fewer than MIN_LISTENERS and no more * than MAX_LISTENERS listeners in state LS_LISTENING. If there are * fewer, it forks a new one; if there are more, it tells one to stop. * * DNSBL lookups are also supported (actually, filtering based on * source IP address; it's designed for DNSBLs, but could actually * filter based on any criterion you care to code). Since the SMTP * workers cannot afford to block, and (while possible) it's * relatively difficult to do DNS lookups without blocking, this is * handled by separate processes. There is a DNSL overseer, which has * a number of DNSL worker children. When an SMTP worker wants to do * a DNSL lookup, it sends the necessary information to the DNSL * overseer, along with a pipe to send the response back through; the * DNSL overseer chooses a non-busy worker process and sends the * request to it. The worker process sends its response back and then * tells the overseer that it's available to handle requests again. * * The DNSL-lookup parent process is separate from the listener parent * so that the listener parent can be killed to shut down operation * without killing off the DNSL-lookup parent. * * Many of the pipes between the DNSL pieces are duplicated. This is * primarily because AF_LOCAL SOCK_DGRAM sockets do not, it appears, * wake up a poll waiting for read when their peer disconnects. So we * have a SOCK_DGRAM socket for data and a SOCK_STREAM socket for * death detection. This applies to the pipe between the connection * handlers and the DNSL parent and to the pipes between the DNSL * parent and the DNSL workers. * * DNSL requests pass through the overseer rather than going directly * to the DNSL workers. This does not have to be so; we could instead * oave the DNSL workers pick up the requests directly. But in that * case the DNSL workers would have to tell the overseer when they're * busy, so the overseer can know when to fork a new one and when to * kill off an existing one. It seems slightly cleaner to me to have * the overseer handle requests than to have the workers constantly * reporting their status, though it's admittedly a close call. * * When compiled with DOSTATS defined, the protocol between the * listeners and the parent becomes significantly chattier. In this * case, every connection accept(), every SMTP command, every address * checked, and every DATA body handled, each of these things produces * a message to the parent, for statistics-keeping. (The messages are * LM_ACCEPT, LM_ADDR_OK, LM_ADDR_BAD, LM_ADDR_UNK, LM_BODY, and one * message per SMTP command in the smtpcmds[] array below.) Of * course, these are not sent when the listener is in state LS_DYING. * Addresses tested with XRT are not counted towards any of the * LM_ADDR_* counts. Message bodies not completed (eg, client drops * the connection before the dot) are not counted. Statistics are * printed as part of the response to SIGUSR1. * * The SMTP listener imposes comparatively few checks. It does check * all command lines to ensure they don't contain octets in the 0-31 * or 128-159 ranges, and it imposes a limit of 64K on each SMTP * command line. (It imposes neither of these restrictions on * DATA-phase data.) It requires that commands be syntactic in a few * of the most rudimentary ways. A line that attempts to exceed the * 64K limit produces an abrupt and ungraceful dropping of the * connection; command lines with forbidden octets produce 500 syntax * error replies. * * VRFY and EXPN are implemented, but as dummy routines, the effect of * which is that they are completely disabled. * * Source routes in RCPT commands have all but the part after the colon * silently disacarded before being checked or passed along. * * The state machine for a CLIENT looks like: * * startup: * 220->client, CS_GREET * CS_GREET: read from client * get HELO: 250->client, CS_HELO * get RSET: 250->client, CS_GREET * get QUIT: 221->client, CS_DEAD * get other: error->client * CS_HELO: read from client * get MAIL: 250->client, CS_MAIL * get HELO: 250->client, CS_HELO * get RSET: 250->client, CS_HELO * get QUIT: 221->client, CS_DEAD * get other: error->client * CS_MAIL: read from client * get RCPT, DNSL status unknown: request DNSL, CS_DNSL * get RCPT, bad address: 550->client, CS_MAIL * get RCPT, good address: start server conn, CS_SCONN * get HELO: 250->client, CS_HELO * get RSET: 250->client, CS_HELO * get QUIT: 221->client, CS_DEAD * get other: error->client * CS_DNSL: wait for DNSL worker response * set dnsl_status, CS_MAIL, call dnsl_done_reply * CS_SCONN: wait for server connect * success: CS_SGREET * failure: 451->client, CS_CLOSING * CS_SGREET: read from server * get 2xx: HELO->server, CS_SHELO * get other: close server, 451->client, CS_CLOSING * CS_SHELO: read from server * get 2xx: MAIL->server, CS_SMAIL * get other: close server, 451->client, CS_CLOSING * CS_SMAIL: read from server * get 2xx: RCPT->server, CS_SRCPT * get 4xx: close server, 451->client, CS_TEMPFAIL * get 5xx: close server, 550->client, CS_HARDFAIL * get other: close server, 451->client, CS_TEMPFAIL * CS_SRCPT: read from server * echo->client, CS_RCPT * CS_RCPT: read from client * get RCPT, bogus address: 550->client, CS_RCPT * get RCPT, good address: echo->server, CS_SRCPT * get DATA: echo->server, CS_SDATA * get HELO: 250->client, QUIT->server, CS_SQUIT * get RSET: 250->client, QUIT->server, CS_SQUIT * get QUIT: 221->client, QUIT->server, CS_SQEXIT * get other: error->client * CS_SDATA: read from server * get 3xx: echo->client, CS_DATASTART * get other: echo->client, CS_RCPT * CS_DATASTART: flush client output, switch to CS_DATA when done * CS_DATA: copy data client->server; on dot, CS_SDOT * CS_SDOT: read from server * echo->client, QUIT->server, CS_SQUIT * CS_SQUIT: read from server * close server, CS_HELO * CS_SQEXIT: read from server * close server, CS_DEAD * CS_CLOSING: read from client * get QUIT: 221->client, CS_DEAD * get other: 421->client * CS_TEMPFAIL: read from client * get RCPT: 450->client, CS_TEMPFAIL * get HELO: 250->client, CS_HELO * get RSET: 250->client, CS_HELO * get QUIT: 221->client, CS_DEAD * get other: error->client * CS_HARDFAIL: read from client * get RCPT: 550->client, CS_TEMPFAIL * get HELO: 250->client, CS_HELO * get RSET: 250->client, CS_HELO * get QUIT: 221->client, CS_DEAD * get other: error->client * * Some SMTP commands are valid in any "read from client" state: * * get NOOP: 250->client * get VRFY: 553->client * * Also, EHLO counts as HELO. */ /* * There are multiple distinct namespaces here; I use different numbers * for all of them to get slightly better error checking - if, for * example, I use LS_DEAD where I mean CS_DEAD, this sort of numbering * will help catch it. */ /* Listener State */ /* These are used both by the parent, to record a listener's state in the structure for that listener, and by the listener, in ownstate. */ #define LS_LISTENING 1 #define LS_FULL 2 #define LS_STOPPING 3 #define LS_DYING 4 /* LS_DEAD is not truly a listener state; it is used to mark a listener data structure corresponding to a listener that's died. */ #define LS_DEAD 5 /* Client State */ #define CS_GREET 6 /* sent greeting, expecting HELO */ #define CS_HELO 7 /* post-HELO, want MAIL */ #define CS_MAIL 8 /* post-MAIL, want RCPT(s), no server connection */ #define CS_DNSL 9 /* got RCPT, waiting for DNSL check */ #define CS_SCONN 10 /* waiting for connect() to server */ #define CS_SGREET 11 /* awaiting server greeting */ #define CS_SHELO 12 /* awaiting response to HELO */ #define CS_SMAIL 13 /* awaiting response to MAIL */ #define CS_SRCPT 14 /* awaiting response to RCPT */ #define CS_RCPT 15 /* server connection, want RCPT(s)/DATA */ #define CS_SDATA 16 /* awaiting response to DATA */ #define CS_DATASTART 17 /* flushing response to DATA */ #define CS_DATA 18 /* receiving data, sending to server */ #define CS_SDOT 19 /* awaiting response to closing dot */ #define CS_SQUIT 20 /* awaiting response to QUIT, ->HELO */ #define CS_SQEXIT 21 /* awaiting response to QUIT, ->DEAD */ #define CS_CLOSING 22 /* always sending soft-fails to client */ #define CS_TEMPFAIL 23 /* tempfailing all recipients */ #define CS_HARDFAIL 24 /* hardfailing all recipients */ #define CS_DYING 25 /* flushing response to QUIT */ #define CS_DEAD 26 /* transient state during client shutdown */ /* Listener Message */ #define LM_FULL 27 #define LM_LISTENING 28 #define LM_STOP 29 #define LM_DYING 30 /* Statistics Message */ #define SM_FIRST_STATS (SM_ACCEPT) #define SM_ACCEPT 31 #define SM_ADDR_OK 32 #define SM_ADDR_BAD 33 #define SM_ADDR_UNK 34 #define SM_DNSL_OK 35 #define SM_DNSL_BAD 36 #define SM_DNSL_FAIL 37 #define SM_BODY 38 #define SM__CMD 39 #define SM_CMD_HELO (SM__CMD+CMD_HELO) #define SM_CMD_EHLO (SM__CMD+CMD_EHLO) #define SM_CMD_MAIL (SM__CMD+CMD_MAIL) #define SM_CMD_RCPT (SM__CMD+CMD_RCPT) #define SM_CMD_DATA (SM__CMD+CMD_DATA) #define SM_CMD_RSET (SM__CMD+CMD_RSET) #define SM_CMD_NOOP (SM__CMD+CMD_NOOP) #define SM_CMD_QUIT (SM__CMD+CMD_QUIT) #define SM_CMD_VRFY (SM__CMD+CMD_VRFY) #define SM_CMD_XRT (SM__CMD+CMD_XRT) #define SM_CMD_POST (SM__CMD+CMD_POST) #define SM_LAST_STATS (SM_CMD_POST) #define STATS_N (SM_LAST_STATS+1-SM_FIRST_STATS) /* Switch case pseudo-values. The are designed so you can write things like switch (...) { case DIGITS: ... } */ #define DIGITS '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9' #define UCLETTERS 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z' #define LCLETTERS 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z' /* * Data structures. * * LSOCK is a listening socket. There is one of these for each port on * which the shim accepts conenctions. They are set up by the parent * and inherited by each SMTP worker forked; it's the SMTP workers * that actually use them. * * LISTENER is used by the parent to record information about an SMTP * worker process. (These processes were originally thought of as * listeners for incoming SMTP connections, which is whence the name.) * * CLIENT is used by the SMTP workers to represent an SMTP client * connection and the stuff attached thereto. * * IOQ is an input/output queue; it contains both queued blocks of data * to be written and an input data buffer. * * OB is an output block of data - an IOQ uses these to record output * data. * * DNSLPROC is used by the DNSL overseer to represent a DNSL worker. */ typedef struct lsock LSOCK; typedef struct listener LISTENER; typedef struct client CLIENT; typedef struct ioq IOQ; typedef struct ob OB; typedef struct dnslproc DNSLPROC; struct dnslproc { DNSLPROC *link; unsigned int flags; #define DPF_DEAD 0x00000001 /* either exited or sick; transient */ #define DPF_BUSY 0x00000002 /* busy handling a request */ int datafd; int datapx; int deathfd; int deathpx; pid_t kid; } ; struct ob { OB *link; const char *data; char *tofree; int len; int ptr; } ; struct ioq { int fd; OB *q; OB **qt; char ibuf[8192]; int ibfill; int ibptr; } ; struct client { CLIENT *link; int state; /* one of the CS_* values */ /* lbuf/lalloc/llen form an input line buffer, for input from wherever input is being read from - the client or the server. lalloc is the amount of memory allocated; llen is the amount used. */ unsigned char *lbuf; int lalloc; int llen; int termstate; /* state machine for data termination */ int px; /* poll index */ time_t timeout; /* time at which timeout expires */ struct sockaddr *peeraddr; /* peer's address */ int peeralen; /* length of peeraddr */ char *peertext; /* text form of peeraddr */ /* Values from the SMTP dialogue. helo_as is from the HELO, env_from from the MAIL; each is nil if that hasn't happened yet. rcpt_* record a RCPT address while the shim holds onto it (which is only until it's been dealt with - eg during starting up a backend connection); they are nil if no RCPT is hanging fire. */ char *helo_as; char *env_from; char *rcpt_lp; char *rcpt_dom; int dnsl_status; /* DNSL status of this peer IP */ #define DNSLSTAT_UNK 1 /* don't yet know */ #define DNSLSTAT_OK 2 /* we like it */ #define DNSLSTAT_BAD 3 /* we don't like it */ #define DNSLSTAT_FAIL 4 /* we can't tell */ char *dnsl_msg; /* long message if BAD or FAIL */ char *dnsl_tag; /* log-entry tag if BAD or FAIL */ int dnsl_fd; /* fd to DNSL worker while in CS_DNSL */ int anyvalid; /* any RCPTs accepted yet? */ int baddata; /* just following a rejected DATA? */ int unrecog; /* unrecognized-command count */ /* When doing I/O, the state is looked at and something is chosen to add to the poll() list. checkfn is set to a function to call if the poll succeeds on that fd. If this is I/O to the client or server, ioq is set to point to either cq or sq (below) and iostr is set to a string for use in log messages, indicating what it's talking to. */ void (*checkfn)(CLIENT *); IOQ *ioq; const char *iostr; char *xid; /* transaction ID, nil if not yet set */ IOQ cq; /* I/O to client */ IOQ sq; /* I/O to backend server */ } ; struct lsock { LSOCK *link; int fd; int px; } ; struct listener { LISTENER *link; pid_t proc; int pipe; int state; int px; } ; /* myhostname holds my hostname, per gethostname(); it's used various places, wherever this machine's hostname is needed. */ static char myhostname[MAXHOSTNAMELEN]; /* listeners is where the parent keeps its list of the SMTP workers. */ static LISTENER *listeners; /* accs is a list of sockets to accept SMTP connections on. As remarked above, this is set up by the parent at startup and inherited by the SMTP workers, which are the processes that actually use it. */ static LSOCK *accs; /* dnslprocs is where the DNSL overseer keeps the list of DNSL workers. */ static DNSLPROC *dnslprocs; /* dnslfree is a pointer to a non-busy DNSLPROC, to save unnecessary list walks in some places. */ static DNSLPROC *dnslfree; /* This records a low-water mark for the number of free DNSL workers. See dnsl_timeout() for more. */ static int dnsl_minfree; /* The pipe between the DNSL oveseer, on one end, and the parent and SMTP workers, on the other. Two sockets (one for data and one for death), as sketched above. */ static int dnsl_data; static int dnsl_death; /* Next time at which we want to call dnsl_timeout(). See dnsl_timeout() for more. */ static unsigned long int dnsl_timer; /* List of fds to poll. Used by most processes. pfds is the list itself; apfds is the size of the memory allocated to pfds, with npfds the current number of pollfds set. */ static struct pollfd *pfds = 0; static int apfds = 0; static int npfds; /* Flag to notice SIGUSR1. */ static volatile int got_usr1; /* In an SMTP worker, the pipe between it and the parent. */ static int parentpipe; /* Poll index for parentpipe. */ static int pppx; /* An SMTP worker's own state (LS_*). */ static int ownstate; /* The state value corresponding to our current process title. */ static int titlestate; /* A listener's list of clients. */ static CLIENT *clients; /* See xid() for what this is. */ static unsigned int xid_serial = 0; /* List of SMTP commands we recognize. XRT is a private extension. POST is not really an SMTP command; we recognize it in order to do special handling for attempts to talk to us through open Web proxies. */ static const char *smtpcmds[] = { "HELO", #define CMD_HELO 0 "EHLO", #define CMD_EHLO 1 "MAIL", #define CMD_MAIL 2 "RCPT", #define CMD_RCPT 3 "DATA", #define CMD_DATA 4 "RSET", #define CMD_RSET 5 "NOOP", #define CMD_NOOP 6 "QUIT", #define CMD_QUIT 7 "VRFY", #define CMD_VRFY 8 "XRT", #define CMD_XRT 9 "POST", #define CMD_POST 10 0 }; #define CMD__N 11 #ifdef DOSTATS static time_t starttt; static char starttext[256]; static unsigned int counters[STATS_N]; #endif #if SM_LAST_STATS != SM__CMD+CMD__N-1 #error Statistics table disagrees with commands table #endif /* * We really should use CMSG_SPACE, CMSG_LEN, and CMSG_DATA. But we * want this same code to run on both a 1.4T sparc and a 1.6.1 i386, * and 1.4T is before the ABI change for SCM_RIGHTS. * * However, on i386, __cmsg_alignbytes() is 3, so the old way produces * the same data layout as the new. So we use the old way, but we * have these defines against the possibility of changing this in the * future. */ #ifdef OLD_CMSG_INTERFACE #define CMSPACE(x) (sizeof(struct cmsghdr)+(x)) #define CMLEN(x) (sizeof(struct cmsghdr)+(x)) #define CMSKIP(x) (sizeof(struct cmsghdr)) #else #define CMSPACE(x) (CMSG_SPACE((x))) #define CMLEN(x) (CMSG_LEN((x))) /* It's gross to have to do this, but it's forced upon us by the botched design of the the CMSG_* interface. This code is not as portable as I'd like - it depends on using a pointer past the end of an object, on some architectures - but I believe it's the least horrible of the available alternatives. */ #define CMSKIP(x) ((char *)CMSG_DATA((x))-(char *)(x)) #endif /* * It's not possible to do proper const poisoning in a program that * uses writev() without either extreme gyrations like mallocking * copies of every const string, or some kind of const-stripper. (The * abstractly correct way to handle this is to have one struct iovec * variant with non-const iov_base, for read, and one with const * iov_base, for write. That's not possible for compatability reasons * if nothing else, and in any case it isn't how the interfaces are * currently designed.) * * We, of course, go the const-stripper route. Here it is. It would * probably be possible to do better than this, but I don't know of * any way without either compiling deconst() separately (without * -Wcast-qual or at least without -Werror) or doing deconst() in * assembly. */ static void *deconst(const void *cvp) { const void *a; void * const *b; a = &cvp; b = a; return(*b); } /* * When something happens that can be tolerated if it happens only * occasionally but should be a fatal error if it happens too often, * we use RATELIMIT(). This keeps a counter; each time the event * happens, it's incremented by "inc", and it's decremented by "dec" * per second, with a clamp to prevent it from going below 0. If it * ever reaches "max", we exit. */ #define RATELIMIT(inc,dec,max) \ do { static int c = 0; static time_t t = 0; ratelimit(&c,&t,(inc),(dec),(max)); } while (0) static void ratelimit(int *cp, time_t *tp, int inc, int dec, int max) { time_t now; if (*tp == 0) { time(tp); *cp = inc; return; } time(&now); if (now != *tp) { inc -= dec * (now - *tp); *tp = now; } inc += *cp; if (inc >= max) exit(1); if (inc < 0) inc = 0; *cp = inc; } /* * Initialize a new IOQ. */ static void ioq_init(IOQ *q, int fd) { q->fd = fd; q->q = 0; q->qt = &q->q; q->ibfill = 0; q->ibptr = 0; } /* * Queue a block of data for output. There are three variants of this, * depending on the exact semantics: * * ioq_oq_point Queues a pointer to the data; the data block * must remain valid until the write completes. * Nothing in particular is done when the block is * fully written. * * ioq_oq_copy A copy is made of the data; the data pointer * passed in need not remain valid once the * queueing function returns. The copy is freed * once it is completely written. * * ioq_oq_free Queues a pointer to the data, like * ioq_oq_point, but once the write is complete, * frees the data. This is semantically like * ioq_oq_copy followed immediately by free, * except it doesn't actually bother making a * copy. */ static void ioq_oq_point(IOQ *q, const void *data, int len) { OB *b; if (len < 0) len = strlen(data); if (len < 1) return; b = malloc(sizeof(OB)); b->data = data; b->tofree = 0; b->len = len; b->ptr = 0; b->link = 0; *q->qt = b; q->qt = &b->link; } static void ioq_oq_copy(IOQ *q, const void *data, int len) { OB *b; if (len < 0) len = strlen(data); if (len < 1) return; b = malloc(sizeof(OB)+len); bcopy(data,b+1,len); b->data = (void *) (b+1); b->tofree = 0; b->len = len; b->ptr = 0; b->link = 0; *q->qt = b; q->qt = &b->link; } static void ioq_oq_free(IOQ *q, void *data, int len) { OB *b; if (len < 0) len = strlen(data); if (len < 1) { free(data); return; } b = malloc(sizeof(OB)); b->data = data; b->tofree = data; b->len = len; b->ptr = 0; b->link = 0; *q->qt = b; q->qt = &b->link; } /* * Flush all queued output blocks. This disposes of the blocks as if * they were successfully written, but does not actually write * anything anywhere. It's used when a connection is being torn down * in circumstances under which we don't care about whether queued * data gets flushed (such as after a write error). */ static void ioq_flush(IOQ *q) { OB *b; while (q->q) { b = q->q; q->q = b->link; if (b->tofree) free(b->tofree); free(b); } q->qt = &q->q; } /* * Try to write queued data for a client. Which connection the write * is attempted on depends on the client's ioq pointer. * * This accumulates multiple data blocks into a vector of struct * iovecs, finally calling writev to perform the write. It tries the * write as soon as either it's run out of data blocks or it's * accumulated min(kern.iov_max,64) blocks. If it actually writes * anything, it then flushes that many bytes, including handling any * completely-written blocks. */ static int try_write(CLIENT *c) { static int maxiov = 0; static struct iovec *iov = 0; static int aiov = 0; int niov; OB *b; int w; IOQ *q; if (maxiov == 0) { int mib[2]; size_t vallen; mib[0] = CTL_KERN; mib[1] = KERN_IOV_MAX; vallen = sizeof(maxiov); if (sysctl(&mib[0],2,&maxiov,&vallen,0,0) < 0) { fprintf(stderr,"%s: sysctl kern.iov_max: %s\n",__progname,strerror(errno)); exit(1); } if (maxiov > 64) maxiov = 64; } niov = 0; q = c->ioq; b = q->q; while (b && (niov < maxiov)) { if (niov >= aiov) iov = realloc(iov,(aiov=niov+8)*sizeof(*iov)); iov[niov].iov_base = deconst(b->data+b->ptr); iov[niov].iov_len = b->len - b->ptr; niov ++; b = b->link; } w = writev(q->fd,iov,niov); if (w < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return(0); } printf("[%d] write error to %s: %s\n",(int)getpid(),c->iostr,strerror(errno)); return(1); } while ((b=q->q) && (w >= b->len-b->ptr)) { w -= b->len - b->ptr; q->q = b->link; if (b->tofree) free(b->tofree); free(b); } if (! b) q->qt = &q->q; if (w) { if (! b) { fprintf(stderr,"%s: write overrun (leftover=%d) to %s\n",__progname,w,c->iostr); return(1); } b->ptr += w; } return(0); } /* * Try to fill an IOQ's input buffer. */ static int ioq_read(IOQ *q) { int r; r = read(q->fd,&q->ibuf[0],sizeof(q->ibuf)); if (r < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return(0); } return(errno); } if (r == 0) return(-1); q->ibfill = r; q->ibptr = 0; return(0); } /* * Send our SMTP greeting banner to a CLIENT's client connection. */ static void send_greeting(CLIENT *c) { ioq_oq_point(&c->cq,"220 ",-1); ioq_oq_point(&c->cq,&myhostname[0],-1); ioq_oq_point(&c->cq," SMTP listener ready.\r\n",-1); } /* * Set a file descriptor nonblocking. Or rather, try to; we ignore * errors from the set attempt. (F_GETFL cannot fail for any reason * that won't make the F_SETFL fail equally hard, so we don't need to * check for F_GETFL failures.) */ static void set_nonblock(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } /* * Record that a CLIENT has had something happen, so its inactivity * timeout should be updated. */ static void fresh_timeout(CLIENT *c) { time(&c->timeout); c->timeout += 3600; } /* * Send a message to our parent. Note that all messages are exactly * one byte long. We deliberately ignore write errors here; the only * plausible error is EPIPE, which can happen if the parent dies just * as we do something that makes us want to write to it - but if the * parent is gone, doing nothing is the right thing. */ static void tellparent(char msg) { write(parentpipe,&msg,1); } /* * To avoid scattering DOSTATS #if lines throughout the code, we define * STATS_RECORD and use that. */ #ifdef DOSTATS #define STATS_RECORD(msg) do { if (ownstate != LS_DYING) tellparent(msg); } while (0) #else #define STATS_RECORD(msg) /* nothing */ #endif /* * Accept a new connection on the given LSOCK. If we get EINTR or * EWOULDBLOCK, ignore it (if there's a real connection, we'll pick it * up next time around the main loop); other errors get reported, and * if they occur too fast, we die. If we succeed, set up a new * CLIENT, send it the greeting banner, and it then just gets tossed * into the pot. */ static void accept_acc(LSOCK *a) { int new; struct sockaddr_storage ss; socklen_t sslen; CLIENT *c; char hnbuf[NI_MAXHOST]; char pnbuf[NI_MAXSERV]; sslen = sizeof(ss); new = accept(a->fd,(struct sockaddr *)&ss,&sslen); if (new < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: accept: %s\n",__progname,strerror(errno)); RATELIMIT(1,1,60); } STATS_RECORD(SM_ACCEPT); set_nonblock(new); c = malloc(sizeof(CLIENT)); c->state = CS_GREET; c->lbuf = 0; c->lalloc = 0; c->llen = 0; c->termstate = 0; fresh_timeout(c); ioq_init(&c->cq,new); ioq_init(&c->sq,-1); c->peeraddr = malloc(sslen); bcopy(&ss,c->peeraddr,sslen); c->peeralen = sslen; if (getnameinfo((const void *)&ss,sslen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { asprintf(&c->peertext,"[getnameinfo failed; %s]",strerror(errno)); } else { asprintf(&c->peertext,"%s/%s",&hnbuf[0],&pnbuf[0]); } c->helo_as = 0; c->env_from = 0; c->rcpt_lp = 0; c->rcpt_dom = 0; c->dnsl_status = DNSLSTAT_UNK; c->dnsl_msg = 0; c->dnsl_tag = 0; c->dnsl_fd = -1; c->anyvalid = 0; c->baddata = 0; c->unrecog = 0; c->xid = 0; send_greeting(c); c->link = clients; clients = c; } /* * Add a file descriptor to the poll vector, showing interest in the * given event(s). */ static int add_pfd(int fd, short int ev) { if (npfds >= apfds) pfds = realloc(pfds,(apfds=npfds+8)*sizeof(struct pollfd)); pfds[npfds] = ((struct pollfd){.fd=fd,.events=ev,.revents=0}); return(npfds++); } /* * Append some data to a client's input line. */ static int iline_append(CLIENT *c, const void *data, int len) { if (c->llen+len > MAXILINELEN) return(1); if (c->llen+len > c->lalloc) c->lbuf = realloc(c->lbuf,c->lalloc=c->llen+len); bcopy(data,c->lbuf+c->llen,len); c->llen += len; return(0); } /* * Make a mallocked NUL-terminated string from a pointer-and-length * block of data. */ static char *blk_to_malloc_str(const void *data, int len) { char *t; t = malloc(len+1); if (len) bcopy(data,t,len); t[len] = '\0'; return(t); } /* * Look up our server, returning the struct addrinfo * chain of * addresses for it. If called multiple times, we either return the * same chain (if CACHE_SERVER is defined) or look it up afresh each * time (if not). This does mean that if CACHE_SERVER is not defined, * the resulting chain remains valid only until the next time * lookup_server() is called. * * This is done synchronously; all other interaction locks up while the * lookup is happening (this is why CACHE_SERVER exists). * * If the lookup fails, (*fail)() is called with a printf-style * argument list. It may throw out; if it returns, lookup_server() * returns nil. */ static struct addrinfo *lookup_server(void (*fail)(const char *, ...)) { struct addrinfo hints; static struct addrinfo *ai0 = 0; int e; if (ai0) #ifdef CACHE_SERVER return(ai0); #else freeaddrinfo(ai0); #endif hints.ai_flags = 0; hints.ai_family = PF_INET/*PF_UNSPEC*/; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(REAL_SMTP_SERVER,"smtp",&hints,&ai0); if (e) { (*fail)("can't getaddrinfo for %s/smtp: %s",REAL_SMTP_SERVER,gai_strerror(e)); return(0); } if (! ai0) { (*fail)("%s/smtp: successful lookup but no addresses?",REAL_SMTP_SERVER); return(0); } return(ai0); } /* * Start opening a connection to the server for the given client. * Loops over the list of server addresses, trying to connect to each * one, until it gets success, gets EINPROGRESS, or runs out of * addresses. In the first two cases, it leavse the client in state * SCONN or SGREET and returns 0; in the last case, it leaves the * client state unchanged and returns 1. */ static int open_server_conn(CLIENT *c) { struct addrinfo *ai0; struct addrinfo *ai; int e; int s; static void fail(const char *fmt, ...) { va_list ap; printf("[%d] ",(int)getpid()); va_start(ap,fmt); vprintf(fmt,ap); va_end(ap); printf("\n"); } ai0 = lookup_server(&fail); for (ai=ai0;ai;ai=ai->ai_next) { s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { printf("[%d] socket (af %d): %s\n",(int)getpid(),ai->ai_family,strerror(errno)); continue; } set_nonblock(s); if (connect(s,ai->ai_addr,ai->ai_addrlen) < 0) { char hnbuf[NI_MAXHOST]; char pnbuf[NI_MAXSERV]; if (errno == EINPROGRESS) { c->sq.fd = s; c->state = CS_SCONN; return(0); } e = errno; if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { printf("[%d] %s/smtp: connect failed [%s], can't get numeric hostname info [%s]\n",(int)getpid(),REAL_SMTP_SERVER,strerror(e),strerror(errno)); } else { if (ai0->ai_next) { printf("[%d] connect %s [%s/%s]: %s\n",(int)getpid(),REAL_SMTP_SERVER,&hnbuf[0],&pnbuf[0],strerror(e)); } else { printf("[%d] connect %s/%s: %s\n",(int)getpid(),&hnbuf[0],&pnbuf[0],strerror(e)); } } close(s); continue; } c->sq.fd = s; c->state = CS_SGREET; return(0); } printf("[%d] can't connect to %s\n",(int)getpid(),REAL_SMTP_SERVER); return(1); } /* * Send an RCPT command to a client's server connection. The * local-part and domain are given as NUL-terminated C strings. */ static void send_rcpt(CLIENT *c, char *lp, char *dom) { ioq_oq_point(&c->sq,"RCPT To:<",-1); ioq_oq_free(&c->sq,lp,-1); if (dom) { ioq_oq_point(&c->sq,"@",-1); ioq_oq_free(&c->sq,dom,-1); } ioq_oq_point(&c->sq,">\r\n",-1); } /* * Pick apart the forward-path from an RCPT command. Returns 0 if all * went well, nonzero on error. On success, stores the local-part and * domain through *lp and *dom. */ static int parse_path(const char *buf, int len, char **lp, char **dom) { int x; int lpbeg; int lpend; int dombeg; int domend; static char get(int i) { return(((i < 0) || (i >= len)) ? '\0' : buf[i]); } static int skip_domain(int x) { if (get(x) == '[') { while (1) { x ++; switch (get(x)) { case '\\': x ++; break; case ']': goto out1; /* break 2, grrr */ break; case '\0': return(-1); break; } } out1:; x ++; } else { /* This is more lenient than 2822's grammar calls for; this code accepts zero-length labels, dots at the beginning or end of the string, and dashes at the beginning or end of labels. */ while (1) { switch (get(x)) { case '-': case '.': case DIGITS: case UCLETTERS: case LCLETTERS: x ++; break; default: goto out2; /* break 2, grrr */ break; } } out2:; } return(x); } /* This routine does not quite parse an RFC2821 "Path"; it parses the part of a path between the < and >. */ /* Ugh. There is no way to skip over a source route without parsing most of it, if only because domains can contain arbitrary characters in their source-text form (via domain -> address-literal -> general-address-literal -> dcontent -> quoted-pair). */ /* Fortunately, whether by chance or design, the grammar is relatively parser-friendly. In particular, minimal lookahead is called for. */ x = 0; /* First, skip any a-d-l: portion. */ if (get(x) == '@') { while (1) { x ++; x = skip_domain(x); if (x < 0) return(1); switch (get(x++)) { case ',': break; case ':': goto out3; /* break 2, grrr */ break; default: return(1); break; } } out3:; } /* What's left must be a mailbox - or an error. */ lpbeg = x; if (get(x) == '"') { /* quoted-string local-part */ while (1) { x ++; switch (get(x)) { case '\\': x ++; break; case '"': goto out4; /* break 2, grrr */ break; case '\0': return(1); break; } } out4:; x ++; } else { /* dot-string local-part */ /* This code is more lenient than 2822's grammar. In particular, 2822 requires that dots not be first or last, that no two dots be adjacent internally, and imposes some constraints on where - can appear. */ while (1) { switch (get(x)) { case '.': case DIGITS: case UCLETTERS: case LCLETTERS: case '!': case '#': case '$': case '%': case '&': case '\'': case '*': case '+': case '-': case '/': case '=': case '?': case '^': case '_': case '`': case '{': case '|': case '}': case '~': x ++; break; default: goto out5; /* break 2, grrr */ break; } } out5:; } lpend = x; if (get(x) != '@') return(1); x ++; dombeg = x; x = skip_domain(x); if (x < 0) return(1); domend = x; if (x != len) return(1); *lp = blk_to_malloc_str(buf+lpbeg,lpend-lpbeg); *dom = blk_to_malloc_str(buf+dombeg,domend-dombeg); return(0); } /* * Do common cleanup for RSET and similar commands. Most places that * call this do additional state-epsecific cleanup. */ static void do_rset(CLIENT *c) { free(c->env_from); c->env_from = 0; } /* * Process a HELO/EHLO. (Since we support no ESMTP extensions, our * response is the same for EHLO as for HELO.) */ static void do_helo(CLIENT *c, const char *arg, int arglen) { free(c->helo_as); c->helo_as = blk_to_malloc_str(arg,arglen); ioq_oq_point(&c->cq,"250 ",-1); ioq_oq_point(&c->cq,&myhostname[0],-1); ioq_oq_point(&c->cq,"\r\n",-1); do_rset(c); } /* * Handle RCPT. Perform rudimentary syntax checks and notice the * bare-postmaster special case, then hand the hard work off to * parse_path. */ static int do_rcpt(CLIENT *c, const char *arg, int arglen) { char *lp; char *dom; const char *abeg; int alen; static void syntax(void) { ioq_oq_point(&c->cq,"500 Syntax: RCPT To:\r\n",-1); printf("[%d] %s (%.*s): 500 Syntax: RCPT To:\n",(int)getpid(),c->iostr,c->llen,c->lbuf); } if ( (arglen < 5) || (arg[arglen-1] != '>') || strncasecmp(arg,"to:",3) ) { syntax(); return(1); } abeg = &arg[3]; alen = arglen - 4; if (*abeg != '<') { syntax(); return(1); } abeg ++; alen --; if ((alen == 10) && !strncasecmp(abeg,"postmaster",10)) { lp = strdup("postmaster"); dom = 0; } else { if (parse_path(abeg,alen,&lp,&dom)) { syntax(); return(1); } } c->rcpt_lp = lp; c->rcpt_dom = dom; return(0); } /* * Handle MAIL. Perform rudimentary syntax checks, then remember the * sender for passing on to the backend server if/when we open a * connection to it. We don't syntax-check the argument further; we * leave that up to the backend. (If we don't ever talk to the * backend, we don't care if it's invalid, and if we do, syntactically * invalid envelope senders are rare enough we don't mind the extra * cost of bothering the backend.) */ static int do_mail(CLIENT *c, const char *arg, int arglen) { const char *abeg; int alen; static void syntax(void) { ioq_oq_point(&c->cq,"500 Syntax: MAIL From:\r\n",-1); printf("[%d] %s (%.*s): 500 Syntax: MAIL From:\n",(int)getpid(),c->iostr,c->llen,c->lbuf); } if ( (arglen < 7) || (arg[arglen-1] != '>') || strncasecmp(arg,"from:",5) ) { syntax(); return(1); } abeg = &arg[5]; alen = arglen - 6; if (*abeg != '<') { syntax(); return(1); } abeg ++; alen --; free(c->env_from); c->env_from = blk_to_malloc_str(abeg,alen); c->state = CS_MAIL; return(0); } /* * Return the xid string for a client. This consists of a base-62 * rendition of a large number formed from * - a per-SMTP-worker serial number * - the SMTP worker's process ID * - a timestamp, in seconds and microseconds */ static char *xid(CLIENT *c) { if (! c->xid) { struct timeval tv; pid_t me; int z; unsigned char nbuf[14]; char tbuf[20]; int tx; int dr; static void ninc(unsigned long long int val, unsigned long long int mul) { int i; for (i=0;i>= 8; } } gettimeofday(&tv,0); me = getpid(); bzero(&nbuf[0],sizeof(nbuf)); ninc(xid_serial++,0x100000000ULL); ninc(me,30000); ninc(tv.tv_sec,0x100000000ULL); ninc(tv.tv_usec,1000000); for (z=sizeof(nbuf);(z>0)&&!nbuf[z-1];z--) ; tx = sizeof(tbuf); tbuf[--tx] = '\0'; while (z > 0) { int i; dr = 0; for (i=z-1;i>=0;i--) { dr = (dr * 256) + nbuf[i]; nbuf[i] = dr / 62; dr %= 62; } if (tx < 1) abort(); tbuf[--tx] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[dr]; if (! nbuf[z-1]) z --; } c->xid = strdup(tbuf+tx); } return(c->xid); } /* * Do common things that must be done when a DNSL query completes. */ static void dnsl_over(CLIENT *c) { close(c->dnsl_fd); c->dnsl_fd = -1; c->state = CS_MAIL; } /* * A DNSL query has completed and the CLIENT's state has been updated * correspondingly; take the appropriate actions (logging, sending a * response for BAD or FAIL, and initiating a server connection for * OK). */ static void dnsl_done_reply(CLIENT *c) { switch (c->dnsl_status) { case DNSLSTAT_OK: printf("[%d] RCPT accepted %s%s%s from %s efrom %s xid %s\n",(int)getpid(),c->rcpt_lp,c->rcpt_dom?"@":"",c->rcpt_dom?:"",c->peertext,c->env_from,xid(c)); if (! open_server_conn(c)) return; ioq_oq_point(&c->cq,"451 Backend error",-1); ioq_oq_point(&c->cq,"\r\n",-1); break; case DNSLSTAT_BAD: printf("[%d] RCPT rejected %s%s%s from %s efrom %s xid %s (%s)\n",(int)getpid(),c->rcpt_lp,c->rcpt_dom?"@":"",c->rcpt_dom?:"",c->peertext,c->env_from,xid(c),c->dnsl_tag); ioq_oq_point(&c->cq,"550 ",-1); ioq_oq_point(&c->cq,c->dnsl_msg,-1); ioq_oq_point(&c->cq,"\r\n",-1); break; case DNSLSTAT_FAIL: printf("[%d] RCPT tempfail %s%s%s from %s efrom %s xid %s (%s)\n",(int)getpid(),c->rcpt_lp,c->rcpt_dom?"@":"",c->rcpt_dom?:"",c->peertext,c->env_from,xid(c),c->dnsl_tag); ioq_oq_point(&c->cq,"451 ",-1); ioq_oq_point(&c->cq,c->dnsl_msg,-1); ioq_oq_point(&c->cq,"\r\n",-1); break; default: abort(); break; } } /* * Common routine called when a DNSL lookup fails for some reason that * does not allow us to tell whether the address is listed. */ static void dnsl_fail(CLIENT *c, const char *msg, const char *tag) { c->dnsl_status = DNSLSTAT_FAIL; c->dnsl_msg = strdup(msg); c->dnsl_tag = strdup(tag); dnsl_over(c); dnsl_done_reply(c); } /* * Called upon getting a "this IP is OK" response from a DNSL lookup. */ static void dnsl_accept(CLIENT *c) { c->dnsl_status = DNSLSTAT_OK; dnsl_over(c); dnsl_done_reply(c); } /* * Called to request a DNSL lookup for CLIENT's client address. We * create a pipe for the DNSL worker to respond to us through, then * compose a query and send it off to the overseer to pass along to a * worker. We then shift to CS_DNSL and wait for the worker to send * us a response. */ static void request_dnslookup(CLIENT *c) { int p[2]; int n; struct iovec iov; struct msghdr mh; struct cmsghdr cmh; char ctlbuf[CMSPACE(sizeof(int))]; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&p[0]) < 0) { printf("[%d] client DNSL socketpair: %s\n",(int)getpid(),strerror(errno)); dnsl_fail(c,"Internal error","socketpair"); return; } iov.iov_base = c->peeraddr; iov.iov_len = c->peeralen; cmh.cmsg_len = CMLEN(sizeof(int)); cmh.cmsg_level = SOL_SOCKET; cmh.cmsg_type = SCM_RIGHTS; bcopy(&cmh,&ctlbuf[0],sizeof(struct cmsghdr)); bcopy(&p[1],&ctlbuf[CMSKIP(&cmh)],sizeof(int)); mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = &ctlbuf[0]; mh.msg_controllen = sizeof(ctlbuf); n = sendmsg(dnsl_data,&mh,0); if (n < 0) { printf("[%d] client DNSL sendmsg: %s\n",(int)getpid(),strerror(errno)); dnsl_fail(c,"Internal error","sendmsg"); close(p[0]); close(p[1]); return; } close(p[1]); c->dnsl_fd = p[0]; c->state = CS_DNSL; } /* * Handle an SMTP command from a client. First we perform a gross * syntax check, checking that no C0 or C1 control characters appear * in the command (arguably, we should enforce the restriction to * ASCII as well), then extract the command verb and search our table * of commands for it. If it's not there, we return an error, and, if * the client is issuing too many unrecognized commands without * recognized commands (like ratware which blats a whole transaction * at us and ignores all return codes), summarily drop the connection. * * Otherwise, update the statistics counter for this command. Then * check to see if it's a command with no arguments that was given an * argument, or one with an argument that was given none, and return * an error in those cases. Then check for a few commands (like NOOP) * that are valid and do the same thing in all states, and handle * them. Else, do a big switch on the current state and handle things * appropriately for the state. */ static void smtp_command(CLIENT *c) { int i; int cl; int al; int cmd; static int smtpcmdlens[sizeof(smtpcmds)/sizeof(smtpcmds[0])] = { -1 }; if (smtpcmdlens[0] < 0) for (i=0;smtpcmds[i];i++) smtpcmdlens[i] = strlen(smtpcmds[i]); for (i=0;illen;i++) { if ((c->lbuf[i]&0x7f) < 32) { ioq_oq_point(&c->cq,"500 Syntax error: invalid character\r\n",-1); printf("[%d] %s (octet 0x%02x): 500 Syntax error: invalid character\n",(int)getpid(),c->iostr,c->lbuf[i]); return; } } for (i=0;(illen)&&(c->lbuf[i]!=' ');i++) ; cl = i; for (;(illen)&&(c->lbuf[i]==' ');i++) ; al = c->llen - i; for (cmd=0;smtpcmds[cmd];cmd++) if ((cl == smtpcmdlens[cmd]) && !strncasecmp(c->lbuf,smtpcmds[cmd],cl)) break; if (! smtpcmds[cmd]) { c->unrecog ++; if (c->unrecog > (c->baddata?5:25)) { ioq_oq_point(&c->cq,"500-Command `",-1); ioq_oq_copy(&c->cq,c->lbuf,cl); ioq_oq_point(&c->cq,"' not recognized\r\n",-1); ioq_oq_point(&c->cq,"500-Come back when you learn SMTP\r\n",-1); ioq_oq_point(&c->cq,"500 Goodbye.\r\n",-1); if (c->baddata) { printf("[%d] %s: 500 Bad DATA unrecognized stream\n",(int)getpid(),c->iostr); } else { printf("[%d] %s (%.*s): 500 Come back when you learn SMTP\n",(int)getpid(),c->iostr,cl,c->lbuf); } c->state = CS_DYING; } else { ioq_oq_point(&c->cq,"500 Command `",-1); ioq_oq_copy(&c->cq,c->lbuf,cl); ioq_oq_point(&c->cq,"' not recognized\r\n",-1); if (! c->baddata) { printf("[%d] %s (%.*s): 500 Command unrecognized\n",(int)getpid(),c->iostr,cl,c->lbuf); } } return; } else { if (c->unrecog > 10) c->unrecog -= 10; else c->unrecog = 0; c->baddata = 0; } STATS_RECORD(SM__CMD+cmd); /* The reason the control structure here is upside down relative to what you might conventionally expect (which would be a switch out to command handlers here, with the command handlers checking the state) is that this way the code matches the state machine description more closely. */ switch (cmd) { case CMD_HELO: case CMD_EHLO: case CMD_MAIL: case CMD_RCPT: case CMD_VRFY: case CMD_XRT: if (i >= c->llen) { ioq_oq_point(&c->cq,"501 Syntax error: ",-1); ioq_oq_copy(&c->cq,c->lbuf,cl); ioq_oq_point(&c->cq," requires an argument\r\n",-1); printf("[%d] %s (%.*s): 501 Requires an argument\n",(int)getpid(),c->iostr,cl,c->lbuf); return; } break; case CMD_DATA: case CMD_RSET: case CMD_NOOP: case CMD_QUIT: if (i < c->llen) { ioq_oq_point(&c->cq,"501 Syntax error: ",-1); ioq_oq_copy(&c->cq,c->lbuf,cl); ioq_oq_point(&c->cq," takes no argument\r\n",-1); printf("[%d] %s (%.*s): 501 Takes no argument\n",(int)getpid(),c->iostr,c->llen,c->lbuf); return; } break; } switch (cmd) { case CMD_NOOP: ioq_oq_point(&c->cq,"250 Nothing done.\r\n",-1); return; break; case CMD_VRFY: ioq_oq_point(&c->cq,"553 VRFY disabled to you.\r\n",-1); printf("[%d] %s (%.*s): 553 VRFY disabled\n",(int)getpid(),c->iostr,cl,c->lbuf); return; break; case CMD_XRT: ioq_oq_point(&c->cq,"050 Testing...\r\n",-1); if (! do_rcpt(c,c->lbuf+i,al)) { switch (valid_address(c->rcpt_lp,c->rcpt_dom)) { case VA_GOOD: ioq_oq_point(&c->cq,"250 Valid recipient\r\n",-1); break; case VA_BAD: ioq_oq_point(&c->cq,"250 Not a valid recipient\r\n",-1); break; case VA_UNKNOWN: ioq_oq_point(&c->cq,"250 Can't determine recipient validity\r\n",-1); break; default: abort(); break; } free(c->rcpt_lp); free(c->rcpt_dom); c->rcpt_lp = 0; c->rcpt_dom = 0; } return; break; case CMD_POST: ioq_oq_point(&c->cq,"500 Command `",-1); ioq_oq_copy(&c->cq,c->lbuf,cl); ioq_oq_point(&c->cq,"' recognized as a Web-proxy-abuse signature\r\n",-1); printf("[%d] %s (%.*s): 500 tried to POST\n",(int)getpid(),c->iostr,c->llen,c->lbuf); c->state = CS_DYING; return; break; } #define SEND250() simpleresp(0,"250 OK") switch (c->state) { static void simpleresp(int logit, const char *str) { ioq_oq_point(&c->cq,str,-1); ioq_oq_point(&c->cq,"\r\n",-1); if (logit) printf("[%d] %s (%.*s): %s\n",(int)getpid(),c->iostr,c->llen,c->lbuf,str); } case CS_GREET: switch (cmd) { case CMD_HELO: case CMD_EHLO: do_helo(c,c->lbuf+i,al); c->state = CS_HELO; break; case CMD_RSET: SEND250(); break; case CMD_QUIT: simpleresp(0,"221 OK"); c->state = CS_DYING; break; default: simpleresp(1,"503 Must HELO/EHLO first"); break; } break; case CS_HELO: free(c->xid); c->xid = 0; switch (cmd) { case CMD_HELO: case CMD_EHLO: do_helo(c,c->lbuf+i,al); break; case CMD_MAIL: if (! do_mail(c,c->lbuf+i,al)) SEND250(); break; case CMD_RSET: SEND250(); break; case CMD_QUIT: simpleresp(0,"221 OK"); c->state = CS_DYING; break; default: simpleresp(1,"503 Must MAIL first"); break; } break; case CS_MAIL: switch (cmd) { case CMD_HELO: case CMD_EHLO: do_helo(c,c->lbuf+i,al); c->state = CS_HELO; break; case CMD_MAIL: simpleresp(1,"503 Already did MAIL"); break; case CMD_RCPT: if (! do_rcpt(c,c->lbuf+i,al)) { switch (valid_address(c->rcpt_lp,c->rcpt_dom)) { case VA_GOOD: STATS_RECORD(SM_ADDR_OK); switch (c->dnsl_status) { case DNSLSTAT_UNK: request_dnslookup(c); return; break; case DNSLSTAT_OK: case DNSLSTAT_BAD: case DNSLSTAT_FAIL: dnsl_done_reply(c); return; break; default: abort(); break; } break; case VA_BAD: STATS_RECORD(SM_ADDR_BAD); simpleresp(0,"550 No such user, or relaying refused"); break; case VA_UNKNOWN: STATS_RECORD(SM_ADDR_UNK); simpleresp(1,"450 Can't check address validity"); break; default: abort(); break; } free(c->rcpt_lp); free(c->rcpt_dom); c->rcpt_lp = 0; c->rcpt_dom = 0; } break; case CMD_DATA: simpleresp(1,"503 Need an accepted RCPT first"); c->baddata = 1; break; case CMD_RSET: do_rset(c); SEND250(); c->state = CS_HELO; break; case CMD_QUIT: simpleresp(0,"221 OK"); c->state = CS_DYING; break; default: abort(); break; } break; case CS_RCPT: switch (cmd) { case CMD_HELO: case CMD_EHLO: do_helo(c,c->lbuf+i,al); ioq_oq_point(&c->sq,"QUIT\r\n",-1); c->state = CS_SQUIT; break; case CMD_MAIL: simpleresp(1,"503 Already did MAIL"); break; case CMD_RCPT: if (! do_rcpt(c,c->lbuf+i,al)) { switch (valid_address(c->rcpt_lp,c->rcpt_dom)) { case VA_GOOD: STATS_RECORD(SM_ADDR_OK); switch (c->dnsl_status) { case DNSLSTAT_OK: printf("[%d] RCPT accepted %s%s%s from %s efrom %s xid %s\n",(int)getpid(),c->rcpt_lp,c->rcpt_dom?"@":"",c->rcpt_dom?:"",c->peertext,c->env_from,xid(c)); send_rcpt(c,c->rcpt_lp,c->rcpt_dom); c->rcpt_lp = 0; c->rcpt_dom = 0; c->state = CS_SRCPT; break; default: abort(); break; } break; case VA_BAD: STATS_RECORD(SM_ADDR_BAD); simpleresp(0,"550 No such user, or relaying refused"); break; case VA_UNKNOWN: STATS_RECORD(SM_ADDR_UNK); simpleresp(1,"450 Can't check address validity"); break; default: abort(); break; } free(c->rcpt_lp); free(c->rcpt_dom); c->rcpt_lp = 0; c->rcpt_dom = 0; } break; case CMD_DATA: if (c->anyvalid) { ioq_oq_point(&c->sq,"DATA\r\n",-1); c->state = CS_SDATA; } else { simpleresp(1,"503 Need an accepted RCPT first"); c->baddata = 1; } break; case CMD_RSET: do_rset(c); SEND250(); ioq_oq_point(&c->sq,"QUIT\r\n",-1); c->state = CS_SQUIT; break; case CMD_QUIT: simpleresp(0,"221 OK"); ioq_oq_point(&c->sq,"QUIT\r\n",-1); c->state = CS_SQEXIT; break; default: abort(); break; } break; case CS_CLOSING: switch (cmd) { case CMD_QUIT: simpleresp(0,"221 OK"); c->state = CS_DYING; break; default: simpleresp(0,"451 Backend error"); break; } break; case CS_TEMPFAIL: switch (cmd) { case CMD_HELO: case CMD_EHLO: do_helo(c,c->lbuf+i,al); c->state = CS_HELO; break; case CMD_MAIL: simpleresp(1,"503 Already did MAIL"); break; case CMD_RCPT: case CMD_DATA: simpleresp(0,"450 Backend error"); break; case CMD_RSET: do_rset(c); SEND250(); c->state = CS_HELO; break; case CMD_QUIT: simpleresp(0,"221 OK"); c->state = CS_DYING; break; default: abort(); break; } break; case CS_HARDFAIL: switch (cmd) { case CMD_HELO: case CMD_EHLO: do_helo(c,c->lbuf+i,al); c->state = CS_HELO; break; case CMD_MAIL: simpleresp(0,"503 Already did MAIL"); break; case CMD_RCPT: case CMD_DATA: simpleresp(0,"550 Mail not accepted from this sender"); break; case CMD_RSET: do_rset(c); SEND250(); c->state = CS_HELO; break; case CMD_QUIT: simpleresp(0,"221 OK"); c->state = CS_DYING; break; default: abort(); break; } break; default: abort(); break; } #undef SEND250 } /* * Called upon seeing a backend server failure. */ static void server_fail(CLIENT *c, int newstate) { close(c->sq.fd); ioq_flush(&c->sq); ioq_init(&c->sq,-1); free(c->rcpt_lp); c->rcpt_lp = 0; free(c->rcpt_dom); c->rcpt_dom = 0; c->state = newstate; } /* * Called when we've accumulated a reply from the backend server. Just * check the state and reply code and either continue or error out. */ static void server_reply(CLIENT *c) { switch (c->state) { case CS_SGREET: if ((c->llen >= 1) && (c->lbuf[0] == '2')) { ioq_oq_point(&c->sq,"HELO ",-1); ioq_oq_point(&c->sq,&myhostname[0],-1); ioq_oq_point(&c->sq,"\r\n",-1); c->state = CS_SHELO; } else { ioq_oq_point(&c->cq,"451 Backend error\r\n",-1); server_fail(c,CS_CLOSING); } break; case CS_SHELO: if ((c->llen >= 1) && (c->lbuf[0] == '2')) { ioq_oq_point(&c->sq,"MAIL From:<",-1); ioq_oq_point(&c->sq,c->env_from,-1); ioq_oq_point(&c->sq,">\r\n",-1); c->state = CS_SMAIL; } else { ioq_oq_point(&c->cq,"451 Backend error\r\n",-1); server_fail(c,CS_CLOSING); } break; case CS_SMAIL: if ((c->llen >= 1) && (c->lbuf[0] == '2')) { send_rcpt(c,c->rcpt_lp,c->rcpt_dom); c->rcpt_lp = 0; c->rcpt_dom = 0; c->state = CS_SRCPT; } else { int s; if ((c->llen >= 1) && (c->lbuf[0] == '5')) { s = CS_HARDFAIL; ioq_oq_point(&c->cq,"550",-1); } else { s = CS_TEMPFAIL; ioq_oq_point(&c->cq,"451",-1); } ioq_oq_point(&c->cq," Backend error\r\n",-1); printf("[%d] %s backend rejected MAIL From:<%s> with %.*s\n",(int)getpid(),c->iostr,c->env_from,c->llen,c->lbuf); server_fail(c,s); } break; case CS_SRCPT: if ((c->llen >= 1) && (c->lbuf[0] == '2')) c->anyvalid = 1; c->state = CS_RCPT; if (0) { case CS_SDATA: c->state = ((c->llen >= 1) && (c->lbuf[0] == '3')) ? CS_DATASTART : CS_RCPT; } ioq_oq_copy(&c->cq,c->lbuf,c->llen); ioq_oq_point(&c->cq,"\r\n",2); break; case CS_SDOT: ioq_oq_copy(&c->cq,c->lbuf,c->llen); ioq_oq_point(&c->cq,"\r\n",2); ioq_oq_copy(&c->sq,"QUIT\r\n",-1); c->state = CS_SQUIT; break; case CS_SQUIT: close(c->sq.fd); ioq_flush(&c->sq); ioq_init(&c->sq,-1); c->state = CS_HELO; break; case CS_SQEXIT: close(c->sq.fd); ioq_flush(&c->sq); ioq_init(&c->sq,-1); c->state = CS_DYING; break; default: abort(); break; } } /* * Process an input line. Where it's input from depends on the state. */ static void client_process_line(CLIENT *c) { if ((c->llen < 2) || (c->lbuf[c->llen-1] != '\n') || (c->lbuf[c->llen-2] != '\r')) abort(); c->llen -= 2; switch (c->state) { case CS_GREET: case CS_HELO: case CS_MAIL: case CS_RCPT: case CS_CLOSING: case CS_TEMPFAIL: case CS_HARDFAIL: smtp_command(c); break; case CS_SGREET: case CS_SHELO: case CS_SMAIL: case CS_SRCPT: case CS_SDATA: case CS_SDOT: case CS_SQUIT: case CS_SQEXIT: server_reply(c); break; default: fprintf(stderr,"%s: client_process_line: state %d\n",__progname,c->state); c->state = CS_DEAD; break; } c->llen = 0; } /* * Accept "input" that's already in the client's read buffer. Crank * through the terminator state machine appropriate to the client's * state, and when done, either switch state (for DATA) or handle an * input line (for others). */ static void check_buffered_in(CLIENT *c) { int i; IOQ *q; int ts; q = c->ioq; switch (c->state) { case CS_DATA: ts = c->termstate; for (i=q->ibptr;iibfill;i++) { switch (ts) { case 0: /* after CRLF */ switch (q->ibuf[i]) { case '.': ts = 1; break; case '\r': ts = 4; break; default: ts = 3; break; } break; case 1: /* after CRLF dot */ ts = (q->ibuf[i] == '\r') ? 2 : 3; break; case 2: /* after CRLF dot CR */ if (q->ibuf[i] == '\n') { i ++; ioq_oq_copy(&c->sq,&q->ibuf[q->ibptr],i-q->ibptr); q->ibptr = i; c->state = CS_SDOT; c->termstate = 0; STATS_RECORD(SM_BODY); return; } ts = 4; break; case 3: /* middle of line, after nothing special */ if (q->ibuf[i] == '\r') ts = 4; break; case 4: /* after CR */ switch (q->ibuf[i]) { case '\n': ts = 0; break; case '\r': break; default: ts = 3; break; } break; default: abort(); break; } } c->termstate = ts; ioq_oq_copy(&c->sq,&q->ibuf[q->ibptr],i-q->ibptr); q->ibptr = i; break; default: ts = c->termstate; for (i=q->ibptr;iibfill;i++) { if (ts) { if (q->ibuf[i] == '\n') { i ++; if (iline_append(c,&q->ibuf[q->ibptr],i-q->ibptr)) { c->state = CS_DEAD; return; } q->ibptr = i; c->termstate = 0; client_process_line(c); return; } ts = 0; } else { if (q->ibuf[i] == '\r') ts = 1; } } if (iline_append(c,&q->ibuf[q->ibptr],i-q->ibptr)) { c->state = CS_DEAD; return; } c->termstate = ts; break; } } /* * The client's input fd - whichever one that is - has become readable. * Read input. (Don't bother actually processing it; let * check_buffered_in handle that next time around the loop.) */ static void check_poll_in(CLIENT *c) { int e; if (pfds[c->px].revents) { e = ioq_read(c->ioq); if (e < 0) { /* EOF */ c->state = CS_DEAD; } else if (e) { /* error; ECONNRESET isn't worth mentioning, but otherwise... */ if (errno != ECONNRESET) { fprintf(stderr,"%s: read error [%s]: %s\n",__progname,c->peertext,strerror(errno)); } c->state = CS_DEAD; } else { fresh_timeout(c); } } } /* * The client's fd shows writeable. Try to write. On failure, mark * the client dead. */ static void check_poll_out(CLIENT *c) { if (pfds[c->px].revents && try_write(c)) c->state = CS_DEAD; } /* * A client's backend server connect() has completed. Check whether it * worked or not and handle it, either way. */ static void check_poll_conn(CLIENT *c) { if (pfds[c->px].revents) { int e; socklen_t elen; elen = sizeof(e); if (getsockopt(c->sq.fd,SOL_SOCKET,SO_ERROR,&e,&elen) < 0) { fprintf(stderr,"%s: can't getsockopt(SO_ERROR): %s\n",__progname,strerror(errno)); e = 1; } else if (e) { fprintf(stderr,"%s: connect to server: %s\n",__progname,strerror(e)); } if (e) { close(c->sq.fd); c->sq.fd = -1; ioq_oq_point(&c->cq,"451 Backend error\r\n",-1); c->state = CS_CLOSING; } else { c->state = CS_SGREET; } } } /* * A client's DNSL response descriptor is readable. See what the DNSL * lookup produced. */ static void check_dnsl_done(CLIENT *c) { if (pfds[c->px].revents) { int resplen; int n; int rcode; int llen; int slen; unsigned char *resp; n = recv(c->dnsl_fd,&resplen,sizeof(int),MSG_WAITALL); if (n < 0) { printf("[%d] DNSL response length recv: %s\n",(int)getpid(),strerror(errno)); dnsl_fail(c,"Internal error","length recv"); return; } if (n != sizeof(int)) { printf("[%d] DNSL response length recv: got %d, wanted %d\n",(int)getpid(),n,(int)sizeof(int)); dnsl_fail(c,"Internal error","incomplete length"); return; } if ((resplen < 2*sizeof(int)) || (resplen > 8192)) { printf("[%d] insane DNSL response length %d\n",(int)getpid,resplen); dnsl_fail(c,"Internal error","insane length"); return; } resplen -= sizeof(int); resp = malloc(resplen); n = recv(c->dnsl_fd,resp,resplen,MSG_WAITALL); if (n < 0) { printf("[%d] DNSL response body recv: %s\n",(int)getpid(),strerror(errno)); dnsl_fail(c,"Internal error","body recv"); free(resp); return; } if (n != resplen) { printf("[%d] DNSL response body recv: got %d, wanted %d\n",(int)getpid(),n,resplen); dnsl_fail(c,"Internal error","incomplete body"); free(resp); return; } bcopy(resp,&rcode,sizeof(int)); switch (rcode) { const char *kindstr; case DNSLSTAT_OK: if (resplen != sizeof(int)) { printf("[%d] DNSL response body length wrong for OK (is %d, wanted %d)\n",(int)getpid(),resplen,2*(int)sizeof(int)); dnsl_fail(c,"Internal error","body length wrong"); free(resp); return; } dnsl_accept(c); break; case DNSLSTAT_BAD: kindstr = "BAD"; if (0) { case DNSLSTAT_FAIL: kindstr = "FAIL"; } bcopy(resp+sizeof(int),&llen,sizeof(int)); bcopy(resp+(2*sizeof(int)),&slen,sizeof(int)); if ( (llen > resplen) || (slen > resplen) || (resplen != (3*sizeof(int))+llen+slen) ) { printf("[%d] DNSL response body length wrong for %s (is %d, wanted %d)\n",(int)getpid(),kindstr,resplen,(3*(int)sizeof(int))+llen+slen); dnsl_fail(c,"Internal error","body length wrong"); free(resp); return; } c->dnsl_status = rcode; c->dnsl_msg = malloc(llen+1); bcopy(resp+(3*sizeof(int)),c->dnsl_msg,llen); c->dnsl_msg[llen] = '\0'; c->dnsl_tag = malloc(slen+1); bcopy(resp+(3*sizeof(int))+llen,c->dnsl_tag,slen); c->dnsl_tag[slen] = '\0'; dnsl_over(c); dnsl_done_reply(c); break; default: printf("[%d] DNSL response type bad (%d)\n",(int)getpid(),rcode); dnsl_fail(c,"Internal error","bad rcode"); free(resp); return; break; } free(resp); } } /* * Create a Received: header and send it to the client's server * connection (thereby inserting it before the data the client sends). */ static void generate_received(CLIENT *c) { int e; int i; time_t nowtt; struct tm *nowtm; char *t; char hnbuf[NI_MAXHOST]; char tbuf[64]; e = getnameinfo(c->peeraddr,c->peeralen,&hnbuf[0],NI_MAXHOST,0,0,NI_NUMERICHOST); if (e) { ioq_oq_point(&c->sq,"Received: from UNKNOWN (getnameinfo error: ",-1); ioq_oq_copy(&c->sq,strerror(e),-1); ioq_oq_point(&c->sq,", sockaddr",-1); for (i=0;ipeeralen;i++) { sprintf(&tbuf[0]," %02x",((const unsigned char *)c->peeraddr)[i]); ioq_oq_copy(&c->sq,&tbuf[0],3); } ioq_oq_point(&c->sq,")\r\n\t",-1); } else { ioq_oq_point(&c->sq,"Received: from ",-1); ioq_oq_copy(&c->sq,&hnbuf[0],-1); ioq_oq_point(&c->sq," ",-1); } ioq_oq_point(&c->sq,"(helo: ",-1); t = malloc(strlen(c->helo_as)); i = 0; while (1) { int ch; ch = ((const unsigned char *)c->helo_as)[i]; if (! ch) break; if ((ch < 32) || (ch > 126) || (ch == '(') || (ch == ')')) ch = '?'; t[i] = ch; i ++; } ioq_oq_copy(&c->sq,t,i); ioq_oq_point(&c->sq,")\r\n",-1); ioq_oq_point(&c->sq,"\tby ",-1); ioq_oq_point(&c->sq,&myhostname[0],-1); ioq_oq_point(&c->sq," with SMTP id ",-1); ioq_oq_point(&c->sq,xid(c),-1); ioq_oq_point(&c->sq,";\r\n\t",-1); time(&nowtt); nowtm = localtime(&nowtt); sprintf(&tbuf[0],"%02d %.3s %04d %02d:%02d:%02d %c%02ld%02ld (%s, ", nowtm->tm_mday, "JanFebMarAprMayJunJulAugSepOctNovDec"+(nowtm->tm_mon*3), nowtm->tm_year + 1900, nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec, (nowtm->tm_gmtoff<0)?'-':'+', labs(nowtm->tm_gmtoff)/3600, (labs(nowtm->tm_gmtoff)/60)%60, nowtm->tm_zone ); ioq_oq_copy(&c->sq,&tbuf[0],-1); nowtm = gmtime(&nowtt); sprintf(&tbuf[0],"%02d:%02d:%02d UTC)", nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec ); ioq_oq_copy(&c->sq,&tbuf[0],-1); ioq_oq_point(&c->sq,"\r\n",-1); } /* * Close all LSOCKs. Used by SMTP worksers when the parent dies, to * release port 25. */ static void close_accs(void) { LSOCK *a; for (a=accs;a;a=a->link) close(a->fd); } /* * Read from the parent pipe in an SMTP worker. */ static void parent_read(void) { unsigned char c; int r; r = read(parentpipe,&c,1); switch (r) { case 0: ownstate = LS_DYING; close(parentpipe); close_accs(); break; case 1: switch (c) { case LM_STOP: ownstate = LS_STOPPING; break; default: fprintf(stderr,"%s: unknown parent pipe command %02x\n",__progname,(unsigned char)c); exit(1); break; } break; default: if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: parent pipe read: %s\n",__progname,strerror(errno)); exit(1); } fprintf(stderr,"%s: parent pipe read %d?""?""?\n",__progname,r); exit(1); break; } } /* * Dump out an SMTP worker's internal state to stdout (whence it goes * into the log). */ static void dump_listener_state(void) { int pid; LSOCK *a; CLIENT *c; time_t now; time(&now); pid = getpid(); printf("[%d] state = ",pid); switch (ownstate) { case LS_LISTENING: printf("LISTENING"); break; case LS_FULL: printf("FULL"); break; case LS_STOPPING: printf("STOPPING"); break; case LS_DYING: printf("DYING"); break; default: printf("%d?",ownstate); break; } printf("\n"); if (ownstate != LS_DYING) { for (a=accs;a;a=a->link) { printf("[%d] listening fd %d\n",pid,a->fd); } } for (c=clients;c;c=c->link) { char hnbuf[NI_MAXHOST]; char pnbuf[NI_MAXSERV]; printf("[%d] client ",pid); if (getnameinfo(c->peeraddr,c->peeralen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { printf("[getnameinfo failed: %s] ",strerror(errno)); } else { printf("%s/%s ",&hnbuf[0],&pnbuf[0]); } printf("state="); switch (c->state) { case CS_GREET: printf("GREET"); break; case CS_HELO: printf("HELO"); break; case CS_MAIL: printf("MAIL"); break; case CS_SCONN: printf("SCONN"); break; case CS_SGREET: printf("SGREET"); break; case CS_SHELO: printf("SHELO"); break; case CS_SMAIL: printf("SMAIL"); break; case CS_SRCPT: printf("SRCPT"); break; case CS_RCPT: printf("RCPT"); break; case CS_SDATA: printf("SDATA"); break; case CS_DATASTART: printf("DATASTART"); break; case CS_DATA: printf("DATA"); break; case CS_SDOT: printf("SDOT"); break; case CS_SQUIT: printf("SQUIT"); break; case CS_SQEXIT: printf("SQEXIT"); break; case CS_CLOSING: printf("CLOSING"); break; case CS_TEMPFAIL: printf("TEMPFAIL"); break; case CS_HARDFAIL: printf("HARDFAIL"); break; case CS_DYING: printf("DYING"); break; case CS_DEAD: printf("DEAD"); break; default: printf("%d?",c->state); break; } if (c->helo_as) printf(" helo=%s",c->helo_as); if (c->env_from) printf(" from=%s",c->env_from); printf(" cq.fd=%d",c->cq.fd); if (c->sq.fd >= 0) printf(" sq.fd=%d",c->sq.fd); printf(" timeout=%d",(int)(c->timeout-now)); printf(" anyvalid=%d\n",c->anyvalid); } } /* * The SMTP worker main loop. This is called repeatedly by an SMTP * worker. Nothing particularly notable here. Update our process * title if it's changed. Walk the list of clients, checking each * one's state and adding the appropriate stuff to the poll vector. * Possibly shift state based on client count. Do the poll and handle * any I/O it indicates is possible. */ static void listener_tick(void) { LSOCK *a; int n; int nclients; CLIENT *c; CLIENT **cp; IOQ *q; int timeout; time_t now; unsigned long int tmo; if (titlestate != ownstate) { switch (ownstate) { case LS_LISTENING: setproctitle("SMTP worker (listening)"); break; case LS_FULL: setproctitle("SMTP worker (full)"); break; case LS_STOPPING: setproctitle("SMTP worker (stopping)"); break; case LS_DYING: setproctitle("SMTP worker (dying)"); break; default: setproctitle("SMTP worker (%d?)",ownstate); break; } titlestate = ownstate; } fflush(0); if (got_usr1) { got_usr1 = 0; dump_listener_state(); return; } timeout = INFTIM; time(&now); npfds = 0; if (ownstate != LS_DYING) pppx = add_pfd(parentpipe,POLLIN|POLLRDNORM); nclients = 0; tmo = 0; for (cp=&clients;(c=*cp);) { if (c->timeout && (c->timeout < now)) c->state = CS_DEAD; switch (c->state) { case CS_GREET: case CS_HELO: case CS_MAIL: case CS_RCPT: case CS_CLOSING: case CS_TEMPFAIL: case CS_HARDFAIL: q = &c->cq; break; case CS_SGREET: case CS_SHELO: case CS_SMAIL: case CS_SRCPT: case CS_SDATA: case CS_SDOT: case CS_SQUIT: case CS_SQEXIT: q = &c->sq; break; case CS_SCONN: q = 0; c->checkfn = &check_poll_conn; c->px = add_pfd(c->sq.fd,POLLOUT|POLLWRNORM); break; case CS_DNSL: q = 0; c->checkfn = &check_dnsl_done; c->px = add_pfd(c->dnsl_fd,POLLIN|POLLRDNORM); break; case CS_DATASTART: if (c->cq.q) { q = &c->cq; break; } c->state = CS_DATA; generate_received(c); /* fall through */ case CS_DATA: q = c->sq.q ? &c->sq : &c->cq; break; case CS_DYING: if (c->cq.q) { q = &c->cq; break; } if (0) { default: fprintf(stderr,"%s: listener_tick: state %d\n",__progname,c->state); } /* fall through */ case CS_DEAD: *cp = c->link; close(c->cq.fd); if (c->sq.fd >= 0) close(c->sq.fd); ioq_flush(&c->cq); ioq_flush(&c->sq); free(c->peeraddr); free(c->peertext); free(c->helo_as); free(c->env_from); free(c->dnsl_msg); free(c->dnsl_tag); if (c->dnsl_fd >= 0) close(c->dnsl_fd); free(c->lbuf); free(c->rcpt_lp); free(c->rcpt_dom); free(c->xid); free(c); continue; break; } nclients ++; if (q) { c->ioq = q; c->iostr = (q == &c->cq) ? c->peertext : REAL_SMTP_SERVER; if (q->q) { c->checkfn = &check_poll_out; c->px = add_pfd(q->fd,POLLOUT|POLLWRNORM); } else if (q->ibptr < q->ibfill) { c->checkfn = &check_buffered_in; timeout = 0; } else { c->checkfn = &check_poll_in; c->px = add_pfd(q->fd,POLLIN|POLLRDNORM); if (c->timeout) { tmo = (c->timeout + 1 - now) * 1000; if ((timeout == INFTIM) || (tmo < timeout)) timeout = tmo; } } } cp = &c->link; } switch (ownstate) { case LS_LISTENING: if (nclients >= CLIENTS_PER_PROCESS) { ownstate = LS_FULL; tellparent(LM_FULL); } break; case LS_FULL: if (nclients < CLIENTS_PER_PROCESS) { ownstate = LS_LISTENING; tellparent(LM_LISTENING); } break; case LS_STOPPING: if (nclients < 1) { tellparent(LM_DYING); exit(0); } break; case LS_DYING: if (nclients < 1) exit(0); break; default: abort(); break; } if (ownstate == LS_LISTENING) { for (a=accs;a;a=a->link) a->px = add_pfd(a->fd,POLLIN|POLLRDNORM); } n = poll(pfds,npfds,timeout); if (n < 0) { if (errno == EINTR) return; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } /* Do clients before accs - accept_acc can add a new client, and if so, it won't have a checkfn set up yet! Similarly do accs before parentpipe, since reading from parentpipe can change ownstate. */ for (c=clients;c;c=c->link) (*c->checkfn)(c); if (ownstate == LS_LISTENING) for (a=accs;a;a=a->link) if (pfds[a->px].revents) accept_acc(a); if ((ownstate != LS_DYING) && pfds[pppx].revents) parent_read(); } /* * Signal-handler for SIGUSR1. Don't try to do anythign here; just set * a flag which is noticed in the main loop. */ static void handle_usr1(int sig __attribute__((__unused__))) { got_usr1 = 1; } /* * Set up SIGUSR1 handling. */ static void setup_usr1(void) { struct sigaction sa; got_usr1 = 0; sa.sa_handler = &handle_usr1; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGUSR1,&sa,0); } /* * Called by the parent to fork a new SMTP worker. */ static void make_new_listener(void) { pid_t kid; int p[2]; LISTENER *l; if (socketpair(AF_LOCAL,SOCK_STREAM,0,p) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } set_nonblock(p[0]); set_nonblock(p[1]); kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { while (listeners) { l = listeners; listeners = l->link; close(l->pipe); free(l); } close(p[0]); parentpipe = p[1]; ownstate = LS_LISTENING; titlestate = LS_DEAD; while (1) listener_tick(); } close(p[1]); l = malloc(sizeof(LISTENER)); l->proc = kid; l->pipe = p[0]; l->state = LS_LISTENING; l->link = listeners; listeners = l; printf("[%d] forked new listener %d\n",(int)getpid(),(int)kid); } /* * Called by the parent to tell an SMTP worker to go away. Just find a * LISTENING worker and send it an LM_STOP message. */ static void stop_a_listener(void) { LISTENER *l; for (l=listeners;l;l=l->link) { if (l->state == LS_LISTENING) { char c; printf("[%d] stopping listener %d\n",(int)getpid(),(int)l->proc); c = LM_STOP; if (write(l->pipe,&c,1) < 0) { printf("[%d] write error to listener %d: %s\n",(int)getpid(),(int)l->proc,strerror(errno)); l->state = LS_DEAD; } else { l->state = LS_STOPPING; } return; } } } /* * Parent read from an SMTP worker. Track state changes and accumulate * stats. */ static void listener_read(LISTENER *l) { unsigned char rbuf[64]; int r; int i; r = read(l->pipe,&rbuf[0],sizeof(rbuf)); if (r == 0) { fprintf(stderr,"%s: listener %d: EOF\n",__progname,(int)l->proc); l->state = LS_DEAD; return; } if (r < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return; break; } fprintf(stderr,"%s: listener %d: read: %s\n",__progname,(int)l->proc,strerror(errno)); return; } for (i=0;iproc,0xff&(int)rbuf[i]); l->state = LS_DEAD; return; break; case LM_FULL: l->state = LS_FULL; break; case LM_LISTENING: l->state = LS_LISTENING; break; case LM_DYING: l->state = LS_DEAD; return; break; #ifdef DOSTATS case SM_FIRST_STATS ... SM_LAST_STATS: counters[rbuf[i]-SM_FIRST_STATS] ++; break; #endif } } } /* * Dump out the parent's state to stdout, which ends up putting it in * the log. */ static void dump_parent_state(void) { int pid; LISTENER *l; time_t now; time(&now); pid = getpid(); for (l=listeners;l;l=l->link) { printf("[%d] listener proc=%d pipe=%d state=",pid,(int)l->proc,l->pipe); switch (l->state) { case LS_LISTENING: printf("LISTENING"); break; case LS_FULL: printf("FULL"); break; case LS_STOPPING: printf("STOPPING"); break; case LS_DYING: printf("DYING"); break; case LS_DEAD: printf("DEAD"); break; default: printf("%d?",l->state); break; } printf("\n"); } #ifdef DOSTATS printf("[%d] Statistics:\n",pid); printf("[%d] Connections accepted: %u\n",pid,counters[SM_ACCEPT-SM_FIRST_STATS]); printf("[%d] Recipients accepted: %u\n",pid,counters[SM_ADDR_OK-SM_FIRST_STATS]); printf("[%d] Recipients rejected: %u\n",pid,counters[SM_ADDR_BAD-SM_FIRST_STATS]); printf("[%d] Recipients uncheckable: %u\n",pid,counters[SM_ADDR_UNK-SM_FIRST_STATS]); printf("[%d] DNSL lookups passed: %u\n",pid,counters[SM_DNSL_OK-SM_FIRST_STATS]); printf("[%d] DNSL lookups rejected: %u\n",pid,counters[SM_DNSL_BAD-SM_FIRST_STATS]); printf("[%d] DNSL lookups failed: %u\n",pid,counters[SM_DNSL_FAIL-SM_FIRST_STATS]); printf("[%d] Message bodies: %u\n",pid,counters[SM_BODY-SM_FIRST_STATS]); { int i; for (i=0;i= 86400) { printf("%lud",up/86400); up %= 86400; } if (up >= 3600) { printf("%luh",up/3600); up %= 3600; } if (up >= 60) { printf("%lum",up/60); up %= 60; } if (up) printf("%lus",up); printf(")\n"); } #endif } #ifdef DOSTATS /* * Called by the parent to receive DNSL stats messages. */ static void dnsl_stats(void) { unsigned char status; int r; r = recv(dnsl_data,&status,1,0); if (r < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return; break; } printf("[%d] recv stats from DNSL: %s\n",(int)getpid(),strerror(errno)); exit(1); } if (r == 0) { printf("[%d] recv EOF from DNSL\n",(int)getpid()); exit(1); } switch (status) { case SM_DNSL_OK: case SM_DNSL_BAD: case SM_DNSL_FAIL: counters[status-SM_FIRST_STATS] ++; break; default: printf("[%d] got bad stats byte 0x%02x from DNSL overseer\n",(int)getpid(),status); break; } } #endif /* * The parent process's main loop. Check for SIGUSR1, reap dead * children, build a list of SMTP workers, do the poll...nothing * particularly noteworthy. */ static void parent_tick(void) { LISTENER *l; LISTENER **lp; int lcount; void (*timeoutfn)(void); int n; #ifdef DOSTATS int dnslpx; #endif fflush(0); if (got_usr1) { got_usr1 = 0; dump_parent_state(); return; } while (1) { pid_t dead; int status; dead = wait3(&status,WNOHANG,0); if (dead <= 0) break; if (WIFEXITED(status)) { fprintf(stderr,"[%d] child %d: exit %d\n",(int)getpid(),(int)dead,WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { fprintf(stderr,"[%d] child %d: %s%s\n",(int)getpid(),(int)dead,strsignal(WTERMSIG(status)),WCOREDUMP(status)?" (core dumped)":""); } else { fprintf(stderr,"[%d] child %d: undecodable status %#x\n",(int)getpid(),(int)dead,status); } } npfds = 0; #ifdef DOSTATS dnslpx = add_pfd(dnsl_data,POLLIN|POLLRDNORM); #endif lcount = 0; for (lp=&listeners;(l=*lp);) { switch (l->state) { case LS_LISTENING: lcount ++; break; case LS_DEAD: close(l->pipe); *lp = l->link; free(l); continue; break; } l->px = add_pfd(l->pipe,POLLIN|POLLRDNORM); lp = &l->link; } timeoutfn = 0; if (lcount < MIN_LISTENERS) { timeoutfn = &make_new_listener; } else if (lcount > MAX_LISTENERS) { timeoutfn = &stop_a_listener; } n = poll(pfds,npfds,timeoutfn?0:3600*1000); if (n < 0) { if (errno == EINTR) return; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } if (n == 0) { if (timeoutfn) (*timeoutfn)(); return; } #ifdef DOSTATS if (pfds[dnslpx].revents) dnsl_stats(); #endif for (l=listeners;l;l=l->link) if (pfds[l->px].revents) listener_read(l); } /* * Setup the LSOCKs. */ static void setup_accs(void) { struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; int s; int v; LSOCK *ls; hints.ai_flags = AI_PASSIVE; hints.ai_family = PF_INET/*PF_UNSPEC*/; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; e = getaddrinfo(0,"smtp",&hints,&ai0); if (e) { fprintf(stderr,"%s: can't getaddrinfo for smtp: %s\n",__progname,gai_strerror(e)); exit(1); } if (! ai0) { fprintf(stderr,"%s: successful smtp lookup but no addresses?\n",__progname); exit(1); } for (ai=ai0;ai;ai=ai->ai_next) { s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { fprintf(stderr,"%s: socket (af %d): %s\n",__progname,ai->ai_family,strerror(errno)); continue; } v = 1; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&v,sizeof(v)); /* ignore errors */ if (bind(s,ai->ai_addr,ai->ai_addrlen) < 0) { fprintf(stderr,"%s: bind (af %d): %s\n",__progname,ai->ai_family,strerror(errno)); close(s); continue; } set_nonblock(s); listen(s,10); ls = malloc(sizeof(LSOCK)); ls->fd = s; ls->link = accs; accs = ls; } if (! accs) { fprintf(stderr,"%s: no listening sockets\n",__progname); exit(1); } } /* * Initialize the statistics counters. */ static void stats_init(void) { #ifdef DOSTATS int i; struct tm *tm; time(&starttt); tm = localtime(&starttt); strftime(&starttext[0],sizeof(starttext),"%Y-%m-%d %H:%M:%S",tm); for (i=0;i 2) { if (!dnslfree || (dnslfree->flags & (DPF_BUSY|DPF_DEAD))) abort(); printf("[%d] dnsl_timeout: killing %d\n",(int)getpid(),(int)dnslfree->kid); dnslfree->flags |= DPF_DEAD; } dnsl_minfree = 50; } /* * Called by the DNSL overseer to fork a new DNSL worker. Called * whenever we have no free DNSL workers. */ static int new_dnslproc(void) { pid_t kid; DNSLPROC *p; int dp[2]; int sp[2]; if (socketpair(AF_LOCAL,SOCK_DGRAM,0,&dp[0]) < 0) { fprintf(stderr,"%s: new DNSL dgram socketpair: %s\n",__progname,strerror(errno)); exit(1); } if (socketpair(AF_LOCAL,SOCK_STREAM,0,&sp[0]) < 0) { fprintf(stderr,"%s: new DNSL stream socketpair: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: new DNSL fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { while (dnslprocs) { p = dnslprocs; dnslprocs = p->link; close(p->datafd); close(p->deathfd); free(p); } close(dp[0]); close(sp[0]); close(dnsl_data); close(dnsl_death); dnsl_data = dp[1]; dnsl_death = sp[1]; return(1); } printf("[%d] forked DNSL process %d\n",(int)getpid(),(int)kid); close(dp[1]); close(sp[1]); p = malloc(sizeof(DNSLPROC)); p->link = dnslprocs; dnslprocs = p; p->flags = 0; p->datafd = dp[0]; p->deathfd = sp[0]; p->kid = kid; return(0); } /* * Called to find a free DNSL worker. This is not normally called, * because dnslfree is normally kept pointing to a free DNSL worker. * This is called only if we find that dnslfree is unsuitable for some * reason, such as sendmsg() failing when called to send the request. */ static DNSLPROC *find_free_dnsproc(void) { DNSLPROC *p; for (p=dnslprocs;p;p=p->link) if (! (p->flags & (DPF_BUSY|DPF_DEAD))) return(p); printf("[%d] no free DNSL handlers\n",(int)getpid()); exit(1); } /* * Called in the overseer to handle a DNSL request from an SMTP worker. * We just receive it, do some minimal error checking, then pass it * along to a DNSL worker to process. */ static void dnsl_request(void) { struct sockaddr_storage ss; int len; int fd; int sent; struct iovec iov; struct msghdr mh; struct cmsghdr cmh; char ctlbuf[CMSPACE(sizeof(int))]; iov.iov_base = &ss; iov.iov_len = sizeof(ss); mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = &ctlbuf[0]; mh.msg_controllen = sizeof(ctlbuf); len = recvmsg(dnsl_data,&mh,0); if (len < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } printf("[%d] DNSL request recvmsg: %s\n",(int)getpid(),strerror(errno)); exit(1); } if (len == 0) { printf("[%d] DNSL empty request\n",(int)getpid()); exit(0); } if (!dnslfree || (dnslfree->flags & (DPF_BUSY|DPF_DEAD))) abort(); if (mh.msg_controllen == 0) { printf("[%d] request without control data\n",(int)getpid()); } else if (mh.msg_controllen != CMSPACE(sizeof(int))) { printf("[%d] request controllen %d (wanted %d)\n",(int)getpid(),mh.msg_controllen,(int)CMSPACE(sizeof(int))); } else { bcopy(&ctlbuf[0],&cmh,sizeof(struct cmsghdr)); if (cmh.cmsg_len != CMLEN(sizeof(int))) { printf("[%d] cmsg_len %d (wanted %d)\n",(int)getpid(),cmh.cmsg_len,(int)CMLEN(sizeof(int))); } else if (cmh.cmsg_level != SOL_SOCKET) { printf("[%d] cmsg_level %d (wanted %d)\n",(int)getpid(),cmh.cmsg_level,(int)SOL_SOCKET); } else if (cmh.cmsg_type != SCM_RIGHTS) { printf("[%d] cmsg_type %d (wanted %d)\n",(int)getpid(),cmh.cmsg_type,(int)SCM_RIGHTS); } else { bcopy(&ctlbuf[CMSKIP(&cmh)],&fd,sizeof(int)); while (1) { iov.iov_base = &ss; iov.iov_len = len; sent = sendmsg(dnslfree->datafd,&mh,0); if (sent == len) break; if (sent >= 0) { printf("[%d] DNSL request sendmesg to %d sent %d, wanted %d\n",(int)getpid(),(int)dnslfree->kid,sent,len); dnslfree->flags |= DPF_DEAD; break; } if (sent < 0) { printf("[%d] DNSL request sendmsg to %d: %s\n",(int)getpid(),(int)dnslfree->kid,strerror(errno)); dnslfree->flags |= DPF_DEAD; dnslfree = find_free_dnsproc(); } } dnslfree->flags |= DPF_BUSY; close(fd); } } } /* * Called in the overseer to read from a DNSL worker. This is how the * overseer finds out that a worker is done with a request. Also, in * a DOSTATS build, the byte is a statistics message and is passed on * to the parent for statistics-keeping. Some dead DNSL worker * detection is done here, but it's not reliable (which is why the * death pipes between workers and the overseer exist). */ static void dnsl_read(DNSLPROC *p) { unsigned char status; int n; n = recv(p->datafd,&status,1,0); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } printf("[%d] DNSL child recv: %s\n",(int)getpid(),strerror(errno)); p->flags |= DPF_DEAD; return; } if (n == 0) { p->flags |= DPF_DEAD; return; } p->flags &= ~DPF_BUSY; #ifdef DOSTATS send(dnsl_data,&status,1,0); #endif } /* * The DNSL overseer main loop. Nothing especially noteworthy here: we * just reap dead children, build the poll list, poll, and handle * possible I/O. (Note that I/O possible on the death pipes always * indicates a dead peer, since they are not used for I/O.) */ static int dnsl_tick(void) { struct timeval now; DNSLPROC *p; DNSLPROC **pp; int nfree; int datapx; int deathpx; int prv; int wstat; pid_t kid; while (1) { kid = wait3(&wstat,WNOHANG,0); if (kid <= 0) break; if (WIFEXITED(wstat)) { if (WEXITSTATUS(wstat) != 0) { printf("[%d] DNSL child %d exited %d\n",(int)getpid(),(int)kid,WEXITSTATUS(wstat)); } } else if (WIFSIGNALED(wstat)) { printf("[%d] DNSL child %d died on %d (%s)%s\n",(int)getpid(),(int)kid,WTERMSIG(wstat),strsignal(WTERMSIG(wstat)),WCOREDUMP(wstat)?" (core dumped)":""); } else { printf("[%d] DNSL child %d cryptic wait status %#x\n",(int)getpid(),(int)kid,wstat); } } fflush(0); npfds = 0; nfree = 0; datapx = add_pfd(dnsl_data,POLLIN|POLLRDNORM); deathpx = add_pfd(dnsl_death,POLLIN|POLLRDNORM); pp = &dnslprocs; dnslfree = 0; while ((p = *pp)) { if (p->flags & DPF_DEAD) { *pp = p->link; close(p->datafd); close(p->deathfd); free(p); } else { pp = &p->link; if (! (p->flags & DPF_BUSY)) { nfree ++; dnslfree = p; } p->datapx = add_pfd(p->datafd,POLLIN|POLLRDNORM); p->deathpx = add_pfd(p->deathfd,POLLIN|POLLRDNORM); } } gettimeofday(&now,0); if (now.tv_sec >= dnsl_timer) { dnsl_timeout(); return(0); } if (nfree < 1) return(new_dnslproc()); if (nfree < dnsl_minfree) dnsl_minfree = nfree; prv = poll(pfds,npfds,((dnsl_timer-now.tv_sec)*1000)+100-(now.tv_usec/1000)); if (prv < 0) { if (errno == EINTR) return(0); printf("[%d] DNSL poll: %s\n",(int)getpid(),strerror(errno)); exit(1); } if (prv == 0) return(0); if (pfds[deathpx].revents & (POLLIN|POLLRDNORM)) { printf("[%d] DNSL parent dying\n",(int)getpid()); exit(0); } if (pfds[datapx].revents & (POLLIN|POLLRDNORM)) { dnsl_request(); return(0); } for (p=dnslprocs;p;p=p->link) { if (p->flags & DPF_DEAD) continue; if (pfds[p->deathpx].revents & (POLLIN|POLLRDNORM)) { printf("[%d] DNSL child %d died\n",(int)getpid(),(int)p->kid); p->flags |= DPF_DEAD; continue; } if (pfds[p->datapx].revents & (POLLIN|POLLRDNORM)) dnsl_read(p); } return(0); } /* * Initialize DNSL overseer state. */ static void dnsl_init(void) { struct timeval tv; dnslprocs = 0; dnsl_minfree = 0; gettimeofday(&tv,0); dnsl_timer = tv.tv_sec; } /* * A DNSL worker's main loop. The only thing of note here is how the * response is constructed with the resp_* functions. * * We don't yet support DNSL checks for address families other than * AF_INET, though most of the infrastructure is in place. */ static int dnslkid_tick(void) { struct pollfd pfd[2]; struct sockaddr_storage ss; int len; int fd; int niov; int resplen; struct iovec iov[8]; unsigned char statcode; int ni; int ival[4]; struct msghdr mh; struct cmsghdr cmh; char ctlbuf[CMSPACE(sizeof(int))]; static struct iovec *resp_iov(void) { if (niov >= sizeof(iov)/sizeof(iov[0])) abort(); return(&iov[niov++]); } static void resp_add_int(int iv) { struct iovec *v; if (ni >= sizeof(ival)/sizeof(ival[0])) abort(); v = resp_iov(); v->iov_base = &ival[ni]; v->iov_len = sizeof(int); ival[ni++] = iv; resplen += sizeof(int); } static void resp_add_block(const void *data, int len) { struct iovec *v; v = resp_iov(); v->iov_base = deconst(data); v->iov_len = len; resplen += len; } static void resp_init(void) { niov = 0; resplen = 0; ni = 0; resp_add_block(&resplen,sizeof(int)); } static void resp_send(void) { int w; w = writev(fd,&iov[0],niov); if (w < 0) { printf("[%d] error writing reply data: %s\n",(int)getpid(),strerror(errno)); exit(0); } if (w != resplen) { printf("[%d] error writing reply data: sent %d, wanted %d\n",(int)getpid(),w,resplen); exit(0); } } auto void fail(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void fail(const char *fmt, ...) { va_list ap; char *t; int tl; resp_add_int(DNSLSTAT_FAIL); statcode = SM_DNSL_FAIL; va_start(ap,fmt); tl = vasprintf(&t,fmt,ap); va_end(ap); resp_add_int(tl); resp_add_int(tl); resp_add_block(t,tl); resp_add_block(t,tl); resp_send(); free(t); } pfd[0].fd = dnsl_data; pfd[0].events = POLLIN | POLLRDNORM; pfd[1].fd = dnsl_death; pfd[1].events = POLLIN | POLLRDNORM; ni = poll(&pfd[0],2,INFTIM); if (pfd[1].revents) { printf("[%d] DNSL child exiting\n",(int)getpid()); exit(0); } iov[0].iov_base = &ss; iov[0].iov_len = sizeof(ss); mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov[0]; mh.msg_iovlen = 1; mh.msg_control = &ctlbuf[0]; mh.msg_controllen = sizeof(ctlbuf); len = recvmsg(dnsl_data,&mh,0); if (len < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(0); } printf("[%d] DNSL child recvmsg: %s\n",(int)getpid(),strerror(errno)); exit(1); } if (len == 0) { printf("[%d] DNSL child empty request\n",(int)getpid()); exit(0); } if (mh.msg_controllen == 0) { printf("[%d] request without control data\n",(int)getpid()); } else if (mh.msg_controllen != CMSPACE(sizeof(int))) { printf("[%d] request controllen %d (wanted %d)\n",(int)getpid(),mh.msg_controllen,(int)CMSPACE(sizeof(int))); } else { bcopy(&ctlbuf[0],&cmh,sizeof(struct cmsghdr)); if (cmh.cmsg_len != CMLEN(sizeof(int))) { printf("[%d] cmsg_len %d (wanted %d)\n",(int)getpid(),cmh.cmsg_len,(int)CMLEN(sizeof(int))); } else if (cmh.cmsg_level != SOL_SOCKET) { printf("[%d] cmsg_level %d (wanted %d)\n",(int)getpid(),cmh.cmsg_level,(int)SOL_SOCKET); } else if (cmh.cmsg_type != SCM_RIGHTS) { printf("[%d] cmsg_type %d (wanted %d)\n",(int)getpid(),cmh.cmsg_type,(int)SCM_RIGHTS); } else { bcopy(&ctlbuf[CMSKIP(&cmh)],&fd,sizeof(int)); resp_init(); if (ss.ss_len != len) { printf("[%d] ss_len %d, len %d\n",(int)getpid(),ss.ss_len,len); fail("Internal error (ss_len %d, len %d)",ss.ss_len,len); } else { switch (ss.ss_family) { case AF_INET: { int status; const char *lmsg; const char *smsg; int ll; int sl; check_ipv4(&((struct sockaddr_in *)&ss)->sin_addr,&status,&lmsg,&smsg); switch (status) { case IPSTAT_OK: resp_add_int(DNSLSTAT_OK); statcode = SM_DNSL_OK; break; case IPSTAT_BAD: resp_add_int(DNSLSTAT_BAD); statcode = SM_DNSL_BAD; if (0) { case IPSTAT_FAIL: resp_add_int(DNSLSTAT_FAIL); statcode = SM_DNSL_FAIL; } ll = strlen(lmsg); sl = strlen(smsg); resp_add_int(ll); resp_add_int(sl); resp_add_block(lmsg,ll); resp_add_block(smsg,sl); break; default: abort(); break; } resp_send(); } break; default: printf("[%d] ss_family %d\n",(int)getpid,ss.ss_family); fail("Internal error (ss_family %d)",ss.ss_family); break; } } close(fd); send(dnsl_data,&statcode,1,0); } } return(0); } /* * Called in the parent to fork the DNSL overseer. */ static int run_dnsl(void) { int dp[2]; int sp[2]; pid_t kid; if (socketpair(AF_LOCAL,SOCK_DGRAM,0,&dp[0]) < 0) { fprintf(stderr,"%s: DNSL dgram socketpair: %s\n",__progname,strerror(errno)); exit(1); } if (socketpair(AF_LOCAL,SOCK_STREAM,0,&sp[0]) < 0) { fprintf(stderr,"%s: DNSL stream socketpair: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: DNSL fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { close(dp[0]); close(sp[0]); dnsl_data = dp[1]; dnsl_death = sp[1]; fcntl(dnsl_data,F_SETFL,fcntl(dnsl_data,F_GETFL,0)|O_NONBLOCK); return(1); } close(dp[1]); dnsl_data = dp[0]; close(sp[1]); dnsl_death = sp[0]; return(0); } /* * main(). Fairly boring. The only thing of note here is that the * main loops for the parent, the DNSL overseer, and the DNSL workers * are all here (the SMTP workers' aren't, but that's a historical * artifact). This is an attempt to keep the stack clean, largely for * aesthetics. */ int main(void); int main(void) { setup_usr1(); ignore_pipe(); stats_init(); gethostname(&myhostname[0],sizeof(myhostname)); printf("[%d] startup\n",(int)getpid()); if (run_dnsl()) { dnsl_init(); setproctitle("DNSL overseer"); while (! dnsl_tick()) ; setproctitle("DNSL worker"); while (! dnslkid_tick()) ; exit(0); } listeners = 0; setup_accs(); setproctitle("parent"); while (1) parent_tick(); }