Edit

IABSD.fr/src/libexec/spamd/spamd.c

Branch :

  • Show log

    Commit

  • Author : millert
    Date : 2026-04-21 14:44:29
    Hash : d6ec741d
    Message : Fix handing of multi-line blacklist error strings in spamd.conf When appending the blacklist error string, spamd splits the message on a newline and continues the message on a new line. There was a bug where the current pointer was incremented too far, which resulted in the message being truncated at the newline instead of continued. For very long blacklist messages (around 8K) in spamd.conf, this could result in heap corruption. However, this is very unlikely in practice. OK jsg@ Reported by and fix from Dhiraj Mishra

  • libexec/spamd/spamd.c
  • /*	$OpenBSD: spamd.c,v 1.165 2026/04/21 14:44:29 millert Exp $	*/
    
    /*
     * Copyright (c) 2015 Henning Brauer <henning@openbsd.org>
     * Copyright (c) 2002-2007 Bob Beck.  All rights reserved.
     * Copyright (c) 2002 Theo de Raadt.  All rights reserved.
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/sysctl.h>
    #include <sys/resource.h>
    #include <sys/signal.h>
    #include <sys/stat.h>
    
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <err.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <poll.h>
    #include <pwd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <syslog.h>
    #include <unistd.h>
    #include <tls.h>
    
    #include <netdb.h>
    
    #include "sdl.h"
    #include "grey.h"
    #include "sync.h"
    
    struct con {
    	struct pollfd *pfd;
    	int state;
    	int laststate;
    	int af;
    	int il;
    	struct sockaddr_storage ss;
    	void *ia;
    	char addr[32];
    	char caddr[32];
    	char helo[MAX_MAIL], mail[MAX_MAIL], rcpt[MAX_MAIL];
    	struct sdlist **blacklists;
    	struct tls *cctx;
    
    	/*
    	 * we will do stuttering by changing these to time_t's of
    	 * now + n, and only advancing when the time is in the past/now
    	 */
    	time_t r;
    	time_t w;
    	time_t s;
    
    	char ibuf[8192];
    	char *ip;
    	char rend[5];	/* any chars in here causes input termination */
    
    	char *obuf;
    	char *lists;
    	size_t osize;
    	char *op;
    	int ol;
    	int data_lines;
    	int data_body;
    	int stutter;
    	int badcmd;
    	int sr;
    	int tlsaction;
    } *con;
    
    #define	SPAMD_TLS_ACT_NONE		0
    #define	SPAMD_TLS_ACT_READ_POLLIN	1
    #define	SPAMD_TLS_ACT_READ_POLLOUT	2
    #define	SPAMD_TLS_ACT_WRITE_POLLIN	3
    #define	SPAMD_TLS_ACT_WRITE_POLLOUT	4
    
    #define	SPAMD_USER			"_spamd"
    
    void     usage(void);
    char    *grow_obuf(struct con *, int);
    int      parse_configline(char *);
    void     parse_configs(void);
    void     do_config(void);
    int      append_error_string (struct con *, size_t, char *, int, void *);
    void     doreply(struct con *);
    void     setlog(char *, size_t, char *);
    void     initcon(struct con *, int, struct sockaddr *);
    void     closecon(struct con *);
    int      match(const char *, const char *);
    void     nextstate(struct con *);
    void     handler(struct con *);
    void     handlew(struct con *, int one);
    char    *loglists(struct con *);
    void     getcaddr(struct con *);
    void     gethelo(char *, size_t, char *);
    int      read_configline(FILE *);
    void	 spamd_tls_init(void);
    void	 check_spamd_db(void);
    void	 blackcheck(int);
    
    char hostname[HOST_NAME_MAX+1];
    struct syslog_data sdata = SYSLOG_DATA_INIT;
    char *nreply = "450";
    char *spamd = "spamd IP-based SPAM blocker";
    int greyback[2];
    int greypipe[2];
    int trappipe[2];
    FILE *grey;
    FILE *trapcfg;
    time_t passtime = PASSTIME;
    time_t greyexp = GREYEXP;
    time_t whiteexp = WHITEEXP;
    time_t trapexp = TRAPEXP;
    struct passwd *pw;
    pid_t jail_pid = -1;
    u_short cfg_port;
    u_short sync_port;
    struct tls_config *tlscfg;
    struct tls *tlsctx;
    char 	*tlskeyfile = NULL;
    char 	*tlscertfile = NULL;
    
    extern struct sdlist *blacklists;
    extern int pfdev;
    extern char *low_prio_mx_ip;
    
    time_t slowdowntill;
    
    int conffd = -1;
    int trapfd = -1;
    char *cb;
    size_t cbs, cbu;
    
    time_t t;
    
    #define MAXCON 800
    int maxfiles;
    int maxcon = MAXCON;
    int maxblack = MAXCON;
    int blackcount;
    int clients;
    int debug;
    int greylist = 1;
    int grey_stutter = 10;
    int verbose;
    int stutter = 1;
    int window;
    int syncrecv;
    int syncsend;
    #define MAXTIME 400
    
    #define MAXIMUM(a,b) (((a)>(b))?(a):(b))
    
    void
    usage(void)
    {
    	extern char *__progname;
    
    	fprintf(stderr,
    	    "usage: %s [-45bdv] [-B maxblack] [-C file] [-c maxcon] "
    	    "[-G passtime:greyexp:whiteexp]\n"
    	    "\t[-h hostname] [-K file] [-l address] [-M address] [-n name]\n"
    	    "\t[-p port] [-S secs] [-s secs] "
    	    "[-w window] [-Y synctarget] [-y synclisten]\n",
    	    __progname);
    
    	exit(1);
    }
    
    char *
    grow_obuf(struct con *cp, int off)
    {
    	char *tmp;
    
    	tmp = realloc(cp->obuf, cp->osize + 8192);
    	if (tmp == NULL) {
    		free(cp->obuf);
    		cp->obuf = NULL;
    		cp->osize = 0;
    		return (NULL);
    	} else {
    		cp->osize += 8192;
    		cp->obuf = tmp;
    		return (cp->obuf + off);
    	}
    }
    
    int
    parse_configline(char *line)
    {
    	char *cp, prev, *name, *msg, *tmp;
    	char **v4 = NULL, **v6 = NULL;
    	const char *errstr;
    	u_int nv4 = 0, nv6 = 0;
    	int mdone = 0;
    	sa_family_t af;
    
    	name = line;
    
    	for (cp = name; *cp && *cp != ';'; cp++)
    		;
    	if (*cp != ';')
    		goto parse_error;
    	*cp++ = '\0';
    	if (!*cp) {
    		sdl_del(name);
    		return (0);
    	}
    	msg = cp;
    	if (*cp++ != '"')
    		goto parse_error;
    	prev = '\0';
    	for (; !mdone; cp++) {
    		switch (*cp) {
    		case '\\':
    			if (!prev)
    				prev = *cp;
    			else
    				prev = '\0';
    			break;
    		case '"':
    			if (prev != '\\') {
    				cp++;
    				if (*cp == ';') {
    					mdone = 1;
    					*cp = '\0';
    				} else {
    					if (debug > 0)
    						printf("bad message: %s\n", msg);
    					goto parse_error;
    				}
    			}
    			break;
    		case '\0':
    			if (debug > 0)
    				printf("bad message: %s\n", msg);
    			goto parse_error;
    		default:
    			prev = '\0';
    			break;
    		}
    	}
    
    	while ((tmp = strsep(&cp, ";")) != NULL) {
    		char **av;
    		u_int au, ac;
    
    		if (*tmp == '\0')
    			continue;
    
    		if (strncmp(tmp, "inet", 4) != 0)
    			goto parse_error;
    		switch (tmp[4]) {
    		case '\0':
    			af = AF_INET;
    			break;
    		case '6':
    			if (tmp[5] == '\0') {
    				af = AF_INET6;
    				break;
    			}
    			/* FALLTHROUGH */
    		default:
    			if (debug > 0)
    				printf("unsupported address family: %s\n", tmp);
    			goto parse_error;
    		}
    
    		tmp = strsep(&cp, ";");
    		if (tmp == NULL) {
    			if (debug > 0)
    				printf("missing address count\n");
    			goto parse_error;
    		}
    		ac = strtonum(tmp, 0, UINT_MAX, &errstr);
    		if (errstr != NULL) {
    			if (debug > 0)
    				printf("count \"%s\" is %s\n", tmp, errstr);
    			goto parse_error;
    		}
    
    		av = reallocarray(NULL, ac, sizeof(char *));
    		for (au = 0; au < ac; au++) {
    			tmp = strsep(&cp, ";");
    			if (tmp == NULL) {
    				if (debug > 0)
    					printf("expected %u addrs, got %u\n",
    					    ac, au + 1);
    				free(av);
    				goto parse_error;
    			}
    			if (*tmp == '\0')
    				continue;
    			av[au] = tmp;
    		}
    		if (af == AF_INET) {
    			if (v4 != NULL) {
    				if (debug > 0)
    					printf("duplicate inet\n");
    				goto parse_error;
    			}
    			v4 = av;
    			nv4 = ac;
    		} else {
    			if (v6 != NULL) {
    				if (debug > 0)
    					printf("duplicate inet6\n");
    				goto parse_error;
    			}
    			v6 = av;
    			nv6 = ac;
    		}
    	}
    	if (nv4 == 0 && nv6 == 0) {
    		if (debug > 0)
    			printf("no addresses\n");
    		goto parse_error;
    	}
    	sdl_add(name, msg, v4, nv4, v6, nv6);
    	free(v4);
    	free(v6);
    	return (0);
    
    parse_error:
    	if (debug > 0)
    		printf("bogus config line - need 'tag;message;af;count;a/m;a/m;a/m...'\n");
    	free(v4);
    	free(v6);
    	return (-1);
    }
    
    void
    parse_configs(void)
    {
    	char *start, *end;
    	size_t i;
    
    	/* We always leave an extra byte for the NUL. */
    	cb[cbu++] = '\0';
    
    	start = cb;
    	end = start;
    	for (i = 0; i < cbu; i++) {
    		if (*end == '\n') {
    			*end = '\0';
    			if (end > start + 1)
    				parse_configline(start);
    			start = ++end;
    		} else
    			++end;
    	}
    	if (end > start + 1)
    		parse_configline(start);
    }
    
    void
    do_config(void)
    {
    	int n;
    
    	if (debug > 0)
    		printf("got configuration connection\n");
    
    	/* Leave an extra byte for the terminating NUL. */
    	if (cbu + 1 >= cbs) {
    		char *tmp;
    
    		tmp = realloc(cb, cbs + (1024 * 1024));
    		if (tmp == NULL) {
    			if (debug > 0)
    				warn("realloc");
    			goto configdone;
    		}
    		cbs += 1024 * 1024;
    		cb = tmp;
    	}
    
    	n = read(conffd, cb + cbu, cbs - cbu);
    	if (debug > 0)
    		printf("read %d config bytes\n", n);
    	if (n == 0) {
    		if (cbu != 0)
    			parse_configs();
    		goto configdone;
    	} else if (n == -1) {
    		if (debug > 0)
    			warn("read");
    		goto configdone;
    	} else
    		cbu += n;
    	return;
    
    configdone:
    	free(cb);
    	cb = NULL;
    	cbs = 0;
    	cbu = 0;
    	close(conffd);
    	conffd = -1;
    	slowdowntill = 0;
    }
    
    int
    read_configline(FILE *config)
    {
    	char *buf;
    	size_t len;
    
    	if ((buf = fgetln(config, &len))) {
    		if (buf[len - 1] == '\n')
    			buf[len - 1] = '\0';
    		else
    			return (-1);	/* all valid lines end in \n */
    		parse_configline(buf);
    	} else {
    		syslog_r(LOG_DEBUG, &sdata, "read_configline: fgetln (%m)");
    		return (-1);
    	}
    	return (0);
    }
    
    void
    spamd_tls_init(void)
    {
    	if (tlskeyfile == NULL && tlscertfile == NULL)
    		return;
    	if (tlskeyfile == NULL || tlscertfile == NULL)
    		errx(1, "need key and certificate for TLS");
    
    	if ((tlscfg = tls_config_new()) == NULL)
    		errx(1, "failed to get tls config");
    	if ((tlsctx = tls_server()) == NULL)
    		errx(1, "failed to get tls server");
    
    	if (tls_config_set_protocols(tlscfg, TLS_PROTOCOLS_ALL) != 0)
    		errx(1, "failed to set tls protocols");
    
    	/* might need user-specified ciphers, tls_config_set_ciphers */
    	if (tls_config_set_ciphers(tlscfg, "all") != 0)
    		errx(1, "failed to set tls ciphers");
    
    	if (tls_config_set_cert_file(tlscfg, tlscertfile) == -1)
    		errx(1, "unable to set TLS certificate file %s", tlscertfile);
    	if (tls_config_set_key_file(tlscfg, tlskeyfile) == -1)
    		errx(1, "unable to set TLS key file %s", tlskeyfile);
    	if (tls_configure(tlsctx, tlscfg) != 0)
    		errx(1, "failed to configure TLS - %s", tls_error(tlsctx));
    
    	/* set hostname to cert's CN unless explicitly given? */
    }
    
    int
    append_error_string(struct con *cp, size_t off, char *fmt, int af, void *ia)
    {
    	char sav = '\0';
    	static size_t lastcont = 0;
    	char *c = cp->obuf + off;
    	char *s = fmt;
    	size_t len = cp->osize - off;
    	size_t i = 0;
    
    	if (off == 0)
    		lastcont = 0;
    
    	if (lastcont != 0)
    		cp->obuf[lastcont] = '-';
    	snprintf(c, len, "%s ", nreply);
    	i += strlen(c);
    	lastcont = off + i - 1;
    	if (*s == '"')
    		s++;
    	while (*s) {
    		/*
    		 * Make sure we at minimum, have room to add a
    		 * format code (4 bytes), a v6 address(39 bytes),
    		 * a byte saved in sav and the NUL terminator.
    		 */
    		if (len <= i + 46) {
    			c = grow_obuf(cp, off);
    			if (c == NULL)
    				return (-1);
    			len = cp->osize - off;
    		}
    
    		if (c[i-1] == '\n') {
    			if (lastcont != 0)
    				cp->obuf[lastcont] = '-';
    			snprintf(c + i, len - i, "%s ", nreply);
    			i += strlen(c + i);
    			lastcont = off + i - 1;
    		}
    
    		switch (*s) {
    		case '\\':
    		case '%':
    			if (!sav)
    				sav = *s;
    			else {
    				c[i++] = sav;
    				sav = '\0';
    				c[i] = '\0';
    			}
    			break;
    		case '"':
    		case 'A':
    		case 'n':
    			if (*(s+1) == '\0') {
    				break;
    			}
    			if (sav == '\\' && *s == 'n') {
    				c[i++] = '\n';
    				sav = '\0';
    				c[i] = '\0';
    				break;
    			} else if (sav == '\\' && *s == '"') {
    				c[i++] = '"';
    				sav = '\0';
    				c[i] = '\0';
    				break;
    			} else if (sav == '%' && *s == 'A') {
    				inet_ntop(af, ia, c + i, (len - i));
    				i += strlen(c + i);
    				sav = '\0';
    				break;
    			}
    			/* FALLTHROUGH */
    		default:
    			if (sav)
    				c[i++] = sav;
    			c[i++] = *s;
    			sav = '\0';
    			c[i] = '\0';
    			break;
    		}
    		s++;
    	}
    	return (i);
    }
    
    char *
    loglists(struct con *cp)
    {
    	static char matchlists[80];
    	struct sdlist **matches;
    	int s = sizeof(matchlists) - 4;
    
    	matchlists[0] = '\0';
    	matches = cp->blacklists;
    	if (matches == NULL)
    		return (NULL);
    	for (; *matches; matches++) {
    
    		/* don't report an insane amount of lists in the logs.
    		 * just truncate and indicate with ...
    		 */
    		if (strlen(matchlists) + strlen(matches[0]->tag) + 1 >= s)
    			strlcat(matchlists, " ...", sizeof(matchlists));
    		else {
    			strlcat(matchlists, " ", s);
    			strlcat(matchlists, matches[0]->tag, s);
    		}
    	}
    	return matchlists;
    }
    
    void
    doreply(struct con *cp)
    {
    	struct sdlist **matches;
    	int off = 0;
    
    	matches = cp->blacklists;
    	if (matches == NULL)
    		goto nomatch;
    	for (; *matches; matches++) {
    		int used = 0;
    		int left = cp->osize - off;
    
    		used = append_error_string(cp, off, matches[0]->string,
    		    cp->af, cp->ia);
    		if (used == -1)
    			goto bad;
    		off += used;
    		left -= used;
    		if (cp->obuf[off - 1] != '\n') {
    			if (left < 1) {
    				if (grow_obuf(cp, off) == NULL)
    					goto bad;
    			}
    			cp->obuf[off++] = '\n';
    			cp->obuf[off] = '\0';
    		}
    	}
    	return;
    nomatch:
    	/* No match. give generic reply */
    	free(cp->obuf);
    	if (cp->blacklists != NULL)
    		cp->osize = asprintf(&cp->obuf,
    		    "%s-Sorry %s\n"
    		    "%s-You are trying to send mail from an address "
    		    "listed by one\n"
    		    "%s or more IP-based registries as being a SPAM source.\n",
    		    nreply, cp->addr, nreply, nreply);
    	else
    		cp->osize = asprintf(&cp->obuf,
    		    "451 Temporary failure, please try again later.\r\n");
    	if (cp->osize == -1)
    		cp->obuf = NULL;
    	cp->osize++; /* size includes the NUL (also changes -1 to 0) */
    	return;
    bad:
    	if (cp->obuf != NULL) {
    		free(cp->obuf);
    		cp->obuf = NULL;
    		cp->osize = 0;
    	}
    }
    
    void
    setlog(char *p, size_t len, char *f)
    {
    	char *s;
    
    	s = strsep(&f, ":");
    	if (!f)
    		return;
    	while (*f == ' ' || *f == '\t')
    		f++;
    	s = strsep(&f, " \t");
    	if (s == NULL)
    		return;
    	strlcpy(p, s, len);
    	s = strsep(&p, " \t\n\r");
    	if (s == NULL)
    		return;
    	s = strsep(&p, " \t\n\r");
    	if (s)
    		*s = '\0';
    }
    
    /*
     * Get address client connected to, by doing a getsockname call.
     * Must not be used with a NAT'ed connection (use divert-to instead of rdr-to).
     */
    void
    getcaddr(struct con *cp)
    {
    	struct sockaddr_storage original_destination;
    	struct sockaddr *odp = (struct sockaddr *) &original_destination;
    	socklen_t len = sizeof(struct sockaddr_storage);
    	int error;
    
    	cp->caddr[0] = '\0';
    	if (getsockname(cp->pfd->fd, odp, &len) == -1)
    		return;
    	error = getnameinfo(odp, odp->sa_len, cp->caddr, sizeof(cp->caddr),
    	    NULL, 0, NI_NUMERICHOST);
    	if (error)
    		cp->caddr[0] = '\0';
    }
    
    void
    gethelo(char *p, size_t len, char *f)
    {
    	char *s;
    
    	/* skip HELO/EHLO */
    	f+=4;
    	/* skip whitespace */
    	while (*f == ' ' || *f == '\t')
    		f++;
    	s = strsep(&f, " \t");
    	if (s == NULL)
    		return;
    	strlcpy(p, s, len);
    	s = strsep(&p, " \t\n\r");
    	if (s == NULL)
    		return;
    	s = strsep(&p, " \t\n\r");
    	if (s)
    		*s = '\0';
    }
    
    void
    initcon(struct con *cp, int fd, struct sockaddr *sa)
    {
    	struct pollfd *pfd = cp->pfd;
    	char ctimebuf[26];
    	time_t tt;
    	int error;
    
    	if (sa->sa_family != AF_INET)
    		errx(1, "not supported yet");
    
    	time(&tt);
    	free(cp->obuf);
    	free(cp->blacklists);
    	free(cp->lists);
    	memset(cp, 0, sizeof(*cp));
    	if (grow_obuf(cp, 0) == NULL)
    		err(1, "malloc");
    	cp->pfd = pfd;
    	cp->pfd->fd = fd;
    	memcpy(&cp->ss, sa, sa->sa_len);
    	cp->af = sa->sa_family;
    	cp->ia = &((struct sockaddr_in *)&cp->ss)->sin_addr;
    	cp->blacklists = sdl_lookup(blacklists, cp->af, cp->ia);
    	cp->stutter = (greylist && !grey_stutter && cp->blacklists == NULL) ?
    	    0 : stutter;
    	error = getnameinfo(sa, sa->sa_len, cp->addr, sizeof(cp->addr), NULL, 0,
    	    NI_NUMERICHOST);
    	if (error)
    		strlcpy(cp->addr, "<unknown>", sizeof(cp->addr));
    	memset(ctimebuf, 0, sizeof(ctimebuf));
    	ctime_r(&t, ctimebuf);
    	ctimebuf[sizeof(ctimebuf) - 2] = '\0'; /* nuke newline */
    	snprintf(cp->obuf, cp->osize, "220 %s ESMTP %s; %s\r\n",
    	    hostname, spamd, ctimebuf);
    	cp->op = cp->obuf;
    	cp->ol = strlen(cp->op);
    	cp->w = tt + cp->stutter;
    	cp->s = tt;
    	strlcpy(cp->rend, "\n", sizeof cp->rend);
    	clients++;
    	if (cp->blacklists != NULL) {
    		blackcount++;
    		if (greylist && blackcount > maxblack)
    			cp->stutter = 0;
    		cp->lists = strdup(loglists(cp));
    		if (cp->lists == NULL)
    			err(1, "malloc");
    	}
    	else
    		cp->lists = NULL;
    }
    
    void
    closecon(struct con *cp)
    {
    	time_t tt;
    
    	if (cp->cctx) {
    		tls_close(cp->cctx);
    		tls_free(cp->cctx);
    		cp->cctx = NULL;
    	}
    	close(cp->pfd->fd);
    	cp->pfd->fd = -1;
    
    	slowdowntill = 0;
    
    	time(&tt);
    	syslog_r(LOG_INFO, &sdata, "%s: disconnected after %lld seconds.%s%s",
    	    cp->addr, (long long)(tt - cp->s),
    	    ((cp->lists == NULL) ? "" : " lists:"),
    	    ((cp->lists == NULL) ? "": cp->lists));
    	if (debug > 0)
    		printf("%s connected for %lld seconds.\n", cp->addr,
    		    (long long)(tt - cp->s));
    	free(cp->lists);
    	cp->lists = NULL;
    	if (cp->blacklists != NULL) {
    		blackcount--;
    		free(cp->blacklists);
    		cp->blacklists = NULL;
    	}
    	if (cp->obuf != NULL) {
    		free(cp->obuf);
    		cp->obuf = NULL;
    		cp->osize = 0;
    	}
    	clients--;
    }
    
    int
    match(const char *s1, const char *s2)
    {
    	return (strncasecmp(s1, s2, strlen(s2)) == 0);
    }
    
    void
    nextstate(struct con *cp)
    {
    	if (match(cp->ibuf, "QUIT") && cp->state < 99) {
    		snprintf(cp->obuf, cp->osize, "221 %s\r\n", hostname);
    		cp->op = cp->obuf;
    		cp->ol = strlen(cp->op);
    		cp->w = t + cp->stutter;
    		cp->laststate = cp->state;
    		cp->state = 99;
    		return;
    	}
    
    	if (match(cp->ibuf, "RSET") && cp->state > 2 && cp->state < 50) {
    		snprintf(cp->obuf, cp->osize,
    		    "250 Ok to start over.\r\n");
    		cp->op = cp->obuf;
    		cp->ol = strlen(cp->op);
    		cp->w = t + cp->stutter;
    		cp->laststate = cp->state;
    		cp->state = 2;
    		return;
    	}
    	switch (cp->state) {
    	case 0:
    	tlsinitdone:
    		/* banner sent; wait for input */
    		cp->ip = cp->ibuf;
    		cp->il = sizeof(cp->ibuf) - 1;
    		cp->laststate = cp->state;
    		cp->state = 1;
    		cp->r = t;
    		break;
    	case 1:
    		/* received input: parse, and select next state */
    		if (match(cp->ibuf, "HELO") ||
    		    match(cp->ibuf, "EHLO")) {
    			int nextstate = 2;
    			cp->helo[0] = '\0';
    			gethelo(cp->helo, sizeof cp->helo, cp->ibuf);
    			if (cp->helo[0] == '\0') {
    				nextstate = 0;
    				snprintf(cp->obuf, cp->osize,
    				    "501 helo requires domain name.\r\n");
    			} else {
    				if (cp->cctx == NULL && tlsctx != NULL &&
    				    cp->blacklists == NULL &&
    				    match(cp->ibuf, "EHLO")) {
    					snprintf(cp->obuf, cp->osize,
    					    "250-%s\r\n"
    					    "250-8BITMIME\r\n"
    					    "250-SMTPUTF8\r\n"
    					    "250 STARTTLS\r\n",
    					    hostname);
    					nextstate = 7;
    				} else {
    					snprintf(cp->obuf, cp->osize,
    					    "250 Hello, spam sender. Pleased "
    					    "to be wasting your time.\r\n");
    				}
    			}
    			cp->op = cp->obuf;
    			cp->ol = strlen(cp->op);
    			cp->laststate = cp->state;
    			cp->state = nextstate;
    			cp->w = t + cp->stutter;
    			break;
    		}
    		goto mail;
    	case 2:
    		/* sent 250 Hello, wait for input */
    		cp->ip = cp->ibuf;
    		cp->il = sizeof(cp->ibuf) - 1;
    		cp->laststate = cp->state;
    		cp->state = 3;
    		cp->r = t;
    		break;
    	case 3:
    	mail:
    		if (match(cp->ibuf, "MAIL")) {
    			setlog(cp->mail, sizeof cp->mail, cp->ibuf);
    			snprintf(cp->obuf, cp->osize,
    			    "250 You are about to try to deliver spam. "
    			    "Your time will be spent, for nothing.\r\n");
    			cp->op = cp->obuf;
    			cp->ol = strlen(cp->op);
    			cp->laststate = cp->state;
    			cp->state = 4;
    			cp->w = t + cp->stutter;
    			break;
    		}
    		goto rcpt;
    	case 4:
    		/* sent 250 Sender ok */
    		cp->ip = cp->ibuf;
    		cp->il = sizeof(cp->ibuf) - 1;
    		cp->laststate = cp->state;
    		cp->state = 5;
    		cp->r = t;
    		break;
    	case 5:
    	rcpt:
    		if (match(cp->ibuf, "RCPT")) {
    			setlog(cp->rcpt, sizeof(cp->rcpt), cp->ibuf);
    			snprintf(cp->obuf, cp->osize,
    			    "250 This is hurting you more than it is "
    			    "hurting me.\r\n");
    			cp->op = cp->obuf;
    			cp->ol = strlen(cp->op);
    			cp->laststate = cp->state;
    			cp->state = 6;
    			cp->w = t + cp->stutter;
    
    			if (cp->mail[0] && cp->rcpt[0]) {
    				if (verbose)
    					syslog_r(LOG_INFO, &sdata,
    					    "(%s) %s: %s -> %s",
    					    cp->blacklists ? "BLACK" : "GREY",
    					    cp->addr, cp->mail,
    					    cp->rcpt);
    				if (debug)
    					fprintf(stderr, "(%s) %s: %s -> %s\n",
    					    cp->blacklists ? "BLACK" : "GREY",
    					    cp->addr, cp->mail, cp->rcpt);
    				if (greylist && cp->blacklists == NULL) {
    					/* send this info to the greylister */
    					getcaddr(cp);
    					fprintf(grey,
    					    "CO:%s\nHE:%s\nIP:%s\nFR:%s\nTO:%s\n",
    					    cp->caddr, cp->helo, cp->addr,
    					    cp->mail, cp->rcpt);
    					fflush(grey);
    				}
    			}
    			break;
    		}
    		goto spam;
    	case 6:
    		/* sent 250 blah */
    		cp->ip = cp->ibuf;
    		cp->il = sizeof(cp->ibuf) - 1;
    		cp->laststate = cp->state;
    		cp->state = 5;
    		cp->r = t;
    		break;
    	case 7:
    		/* sent 250 STARTTLS, wait for input */
    		cp->ip = cp->ibuf;
    		cp->il = sizeof(cp->ibuf) - 1;
    		cp->laststate = cp->state;
    		cp->state = 8;
    		cp->r = t;
    		break;
    	case 8:
    		if (tlsctx != NULL && cp->blacklists == NULL &&
    		    cp->cctx == NULL && match(cp->ibuf, "STARTTLS")) {
    			snprintf(cp->obuf, cp->osize,
    			    "220 glad you want to burn more CPU cycles on "
    			    "your spam\r\n");
    			cp->op = cp->obuf;
    			cp->ol = strlen(cp->op);
    			cp->laststate = cp->state;
    			cp->state = 9;
    			cp->w = t + cp->stutter;
    			break;
    		}
    		goto mail;
    	case 9:
    		if (tls_accept_socket(tlsctx, &cp->cctx, cp->pfd->fd) == -1) {
    			snprintf(cp->obuf, cp->osize,
    			    "500 STARTTLS failed\r\n");
    			cp->op = cp->obuf;
    			cp->ol = strlen(cp->op);
    			cp->laststate = cp->state;
    			cp->state = 98;
    			goto done;
    		}
    		goto tlsinitdone;
    
    	case 50:
    	spam:
    		if (match(cp->ibuf, "DATA")) {
    			snprintf(cp->obuf, cp->osize,
    			    "354 Enter spam, end with \".\" on a line by "
    			    "itself\r\n");
    			cp->state = 60;
    			if (window && setsockopt(cp->pfd->fd, SOL_SOCKET,
    			    SO_RCVBUF, &window, sizeof(window)) == -1) {
    				syslog_r(LOG_DEBUG, &sdata,"setsockopt: %m");
    				/* don't fail if this doesn't work. */
    			}
    			cp->ip = cp->ibuf;
    			cp->il = sizeof(cp->ibuf) - 1;
    			cp->op = cp->obuf;
    			cp->ol = strlen(cp->op);
    			cp->w = t + cp->stutter;
    			if (greylist && cp->blacklists == NULL) {
    				cp->laststate = cp->state;
    				cp->state = 98;
    				goto done;
    			}
    		} else {
    			if (match(cp->ibuf, "NOOP"))
    				snprintf(cp->obuf, cp->osize,
    				    "250 2.0.0 OK I did nothing\r\n");
    			else {
    				snprintf(cp->obuf, cp->osize,
    				    "500 5.5.1 Command unrecognized\r\n");
    				cp->badcmd++;
    				if (cp->badcmd > 20) {
    					cp->laststate = cp->state;
    					cp->state = 98;
    					goto done;
    				}
    			}
    			cp->state = cp->laststate;
    			cp->ip = cp->ibuf;
    			cp->il = sizeof(cp->ibuf) - 1;
    			cp->op = cp->obuf;
    			cp->ol = strlen(cp->op);
    			cp->w = t + cp->stutter;
    		}
    		break;
    	case 60:
    		/* sent 354 blah */
    		cp->ip = cp->ibuf;
    		cp->il = sizeof(cp->ibuf) - 1;
    		cp->laststate = cp->state;
    		cp->state = 70;
    		cp->r = t;
    		break;
    	case 70: {
    		char *p, *q;
    
    		for (p = q = cp->ibuf; q <= cp->ip; ++q)
    			if (*q == '\n' || q == cp->ip) {
    				*q = 0;
    				if (q > p && q[-1] == '\r')
    					q[-1] = 0;
    				if (!strcmp(p, ".") ||
    				    (cp->data_body && ++cp->data_lines >= 10)) {
    					cp->laststate = cp->state;
    					cp->state = 98;
    					goto done;
    				}
    				if (!cp->data_body && !*p)
    					cp->data_body = 1;
    				if (verbose && cp->data_body && *p)
    					syslog_r(LOG_DEBUG, &sdata, "%s: "
    					    "Body: %s", cp->addr, p);
    				else if (verbose && (match(p, "FROM:") ||
    				    match(p, "TO:") || match(p, "SUBJECT:")))
    					syslog_r(LOG_INFO, &sdata, "%s: %s",
    					    cp->addr, p);
    				p = ++q;
    			}
    		cp->ip = cp->ibuf;
    		cp->il = sizeof(cp->ibuf) - 1;
    		cp->r = t;
    		break;
    	}
    	case 98:
    	done:
    		doreply(cp);
    		cp->op = cp->obuf;
    		cp->ol = strlen(cp->op);
    		cp->w = t + cp->stutter;
    		cp->laststate = cp->state;
    		cp->state = 99;
    		break;
    	case 99:
    		closecon(cp);
    		break;
    	default:
    		errx(1, "illegal state %d", cp->state);
    		break;
    	}
    }
    
    void
    handler(struct con *cp)
    {
    	int end = 0;
    	ssize_t n;
    
    	if (cp->r || cp->tlsaction != SPAMD_TLS_ACT_NONE) {
    		if (cp->cctx) {
    			cp->tlsaction = SPAMD_TLS_ACT_NONE;
    			n = tls_read(cp->cctx, cp->ip, cp->il);
    			if (n == TLS_WANT_POLLIN)
    				cp->tlsaction = SPAMD_TLS_ACT_READ_POLLIN;
    			if (n == TLS_WANT_POLLOUT)
    				cp->tlsaction = SPAMD_TLS_ACT_READ_POLLOUT;
    			if (cp->tlsaction != SPAMD_TLS_ACT_NONE)
    				return;
    		} else
    			n = read(cp->pfd->fd, cp->ip, cp->il);
    
    		if (n == 0)
    			closecon(cp);
    		else if (n == -1) {
    			if (errno == EAGAIN)
    				return;
    			if (debug > 0)
    				warn("read");
    			closecon(cp);
    		} else {
    			cp->ip[n] = '\0';
    			if (cp->rend[0])
    				if (strpbrk(cp->ip, cp->rend))
    					end = 1;
    			cp->ip += n;
    			cp->il -= n;
    		}
    	}
    	if (end || cp->il == 0) {
    		while (cp->ip > cp->ibuf &&
    		    (cp->ip[-1] == '\r' || cp->ip[-1] == '\n'))
    			cp->ip--;
    		*cp->ip = '\0';
    		cp->r = 0;
    		nextstate(cp);
    	}
    }
    
    void
    handlew(struct con *cp, int one)
    {
    	ssize_t n;
    
    	/* kill stutter on greylisted connections after initial delay */
    	if (cp->stutter && greylist && cp->blacklists == NULL &&
    	    (t - cp->s) > grey_stutter)
    		cp->stutter=0;
    
    	if (cp->w || cp->tlsaction != SPAMD_TLS_ACT_NONE) {
    		if (*cp->op == '\n' && !cp->sr) {
    			/* insert \r before \n */
    			if (cp->cctx) {
    				cp->tlsaction = SPAMD_TLS_ACT_NONE;
    				n = tls_write(cp->cctx, "\r", 1);
    				if (n == TLS_WANT_POLLIN)
    					cp->tlsaction =
    					    SPAMD_TLS_ACT_WRITE_POLLIN;
    				if (n == TLS_WANT_POLLOUT)
    					cp->tlsaction =
    					    SPAMD_TLS_ACT_WRITE_POLLOUT;
    				if (cp->tlsaction != SPAMD_TLS_ACT_NONE)
    					return;
    			} else
    				n = write(cp->pfd->fd, "\r", 1);
    
    			if (n == 0) {
    				closecon(cp);
    				goto handled;
    			} else if (n == -1) {
    				if (errno == EAGAIN)
    					return;
    				if (debug > 0 && errno != EPIPE)
    					warn("write");
    				closecon(cp);
    				goto handled;
    			}
    		}
    		if (*cp->op == '\r')
    			cp->sr = 1;
    		else
    			cp->sr = 0;
    		if (cp->cctx) {
    			cp->tlsaction = SPAMD_TLS_ACT_NONE;
    			n = tls_write(cp->cctx, cp->op, cp->ol);
    			if (n == TLS_WANT_POLLIN)
    				cp->tlsaction = SPAMD_TLS_ACT_WRITE_POLLIN;
    			if (n == TLS_WANT_POLLOUT)
    				cp->tlsaction = SPAMD_TLS_ACT_WRITE_POLLOUT;
    			if (cp->tlsaction != SPAMD_TLS_ACT_NONE)
    				return;
    		} else
    			n = write(cp->pfd->fd, cp->op,
    			   (one && cp->stutter) ? 1 : cp->ol);
    
    		if (n == 0)
    			closecon(cp);
    		else if (n == -1) {
    			if (errno == EAGAIN)
    				return;
    			if (debug > 0 && errno != EPIPE)
    				warn("write");
    			closecon(cp);
    		} else {
    			cp->op += n;
    			cp->ol -= n;
    		}
    	}
    handled:
    	cp->w = t + cp->stutter;
    	if (cp->ol == 0) {
    		cp->w = 0;
    		nextstate(cp);
    	}
    }
    
    static int
    get_maxfiles(void)
    {
    	int mib[2], maxfiles;
    	size_t len;
    
    	mib[0] = CTL_KERN;
    	mib[1] = KERN_MAXFILES;
    	len = sizeof(maxfiles);
    	if (sysctl(mib, 2, &maxfiles, &len, NULL, 0) == -1)
    		return(MAXCON);
    	if ((maxfiles - 200) < 10)
    		errx(1, "kern.maxfiles is only %d, can not continue\n",
    		    maxfiles);
    	else
    		return(maxfiles - 200);
    }
    
    /* Symbolic indexes for pfd[] below */
    #define PFD_SMTPLISTEN	0
    #define PFD_CONFLISTEN	1
    #define PFD_SYNCFD	2
    #define PFD_CONFFD	3
    #define PFD_TRAPFD	4
    #define PFD_GREYBACK	5
    #define PFD_FIRSTCON	6
    
    int
    main(int argc, char *argv[])
    {
    	struct pollfd *pfd;
    	struct sockaddr_in sin;
    	struct sockaddr_in lin;
    	int ch, smtplisten, conflisten, syncfd = -1, i, one = 1;
    	u_short port;
    	long long passt, greyt, whitet;
    	struct servent *ent;
    	struct rlimit rlp;
    	char *bind_address = NULL;
    	const char *errstr;
    	char *sync_iface = NULL;
    	char *sync_baddr = NULL;
    	struct addrinfo hints, *res;
    	char *addr;
    	char portstr[6];
    	int error;
    
    	tzset();
    	openlog_r("spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
    
    	if ((ent = getservbyname("spamd", "tcp")) == NULL)
    		errx(1, "Can't find service \"spamd\" in /etc/services");
    	port = ntohs(ent->s_port);
    	if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL)
    		errx(1, "Can't find service \"spamd-cfg\" in /etc/services");
    	cfg_port = ntohs(ent->s_port);
    	if ((ent = getservbyname("spamd-sync", "udp")) == NULL)
    		errx(1, "Can't find service \"spamd-sync\" in /etc/services");
    	sync_port = ntohs(ent->s_port);
    
    	if (gethostname(hostname, sizeof hostname) == -1)
    		err(1, "gethostname");
    	maxfiles = get_maxfiles();
    	if (maxcon > maxfiles)
    		maxcon = maxfiles;
    	if (maxblack > maxfiles)
    		maxblack = maxfiles;
    	while ((ch =
    	    getopt(argc, argv, "45l:c:B:p:bdG:h:s:S:M:n:vw:y:Y:C:K:")) != -1) {
    		switch (ch) {
    		case '4':
    			nreply = "450";
    			break;
    		case '5':
    			nreply = "550";
    			break;
    		case 'l':
    			bind_address = optarg;
    			break;
    		case 'B':
    			maxblack = strtonum(optarg, 0, INT_MAX, &errstr);
    			if (errstr)
    				errx(1, "-B %s: %s", optarg, errstr);
    			break;
    		case 'c':
    			maxcon = strtonum(optarg, 1, maxfiles, &errstr);
    			if (errstr) {
    				fprintf(stderr, "-c %s: %s\n", optarg, errstr);
    				usage();
    			}
    			break;
    		case 'p':
    			port = strtonum(optarg, 1, USHRT_MAX, &errstr);
    			if (errstr)
    				errx(1, "-p %s: %s", optarg, errstr);
    			break;
    		case 'd':
    			debug = 1;
    			break;
    		case 'b':
    			greylist = 0;
    			break;
    		case 'G':
    			if (sscanf(optarg, "%lld:%lld:%lld", &passt, &greyt,
    			    &whitet) != 3)
    				usage();
    			passtime = passt;
    			greyexp = greyt;
    			whiteexp = whitet;
    			/* convert to seconds from minutes */
    			passtime *= 60;
    			/* convert to seconds from hours */
    			whiteexp *= (60 * 60);
    			/* convert to seconds from hours */
    			greyexp *= (60 * 60);
    			break;
    		case 'h':
    			memset(hostname, 0, sizeof(hostname));
    			if (strlcpy(hostname, optarg, sizeof(hostname)) >=
    			    sizeof(hostname))
    				errx(1, "-h arg too long");
    			break;
    		case 's':
    			stutter = strtonum(optarg, 0, 10, &errstr);
    			if (errstr)
    				usage();
    			break;
    		case 'S':
    			grey_stutter = strtonum(optarg, 0, 90, &errstr);
    			if (errstr)
    				usage();
    			break;
    		case 'M':
    			low_prio_mx_ip = optarg;
    			break;
    		case 'n':
    			spamd = optarg;
    			break;
    		case 'v':
    			verbose = 1;
    			break;
    		case 'w':
    			window = strtonum(optarg, 1, INT_MAX, &errstr);
    			if (errstr)
    				errx(1, "-w %s: %s", optarg, errstr);
    			break;
    		case 'Y':
    			if (sync_addhost(optarg, sync_port) != 0)
    				sync_iface = optarg;
    			syncsend++;
    			break;
    		case 'y':
    			sync_baddr = optarg;
    			syncrecv++;
    			break;
    		case 'C':
    			tlscertfile = optarg;
    			break;
    		case 'K':
    			tlskeyfile = optarg;
    			break;
    		default:
    			usage();
    			break;
    		}
    	}
    
    	setproctitle("[priv]%s%s",
    	    greylist ? " (greylist)" : "",
    	    (syncrecv || syncsend) ? " (sync)" : "");
    
    	if (syncsend || syncrecv) {
    		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
    		if (syncfd == -1)
    			err(1, "sync init");
    	}
    
    	if (geteuid())
    		errx(1, "need root privileges");
    
    	if ((pw = getpwnam(SPAMD_USER)) == NULL)
    		errx(1, "no such user %s", SPAMD_USER);
    
    	if (!greylist) {
    		maxblack = maxcon;
    	} else if (maxblack > maxcon)
    		usage();
    
    	spamd_tls_init();
    
    	rlp.rlim_cur = rlp.rlim_max = maxcon + 15;
    	if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
    		err(1, "setrlimit");
    
    	pfd = reallocarray(NULL, PFD_FIRSTCON + maxcon, sizeof(*pfd));
    	if (pfd == NULL)
    		err(1, "reallocarray");
    
    	con = calloc(maxcon, sizeof(*con));
    	if (con == NULL)
    		err(1, "calloc");
    
    	con->obuf = malloc(8192);
    
    	if (con->obuf == NULL)
    		err(1, "malloc");
    	con->osize = 8192;
    
    	for (i = 0; i < maxcon; i++) {
    		con[i].pfd = &pfd[PFD_FIRSTCON + i];
    		con[i].pfd->fd = -1;
    	}
    
    	signal(SIGPIPE, SIG_IGN);
    
    	smtplisten = socket(AF_INET, SOCK_STREAM, 0);
    	if (smtplisten == -1)
    		err(1, "socket");
    
    	if (setsockopt(smtplisten, SOL_SOCKET, SO_REUSEADDR, &one,
    	    sizeof(one)) == -1)
    		return (-1);
    
    	conflisten = socket(AF_INET, SOCK_STREAM, 0);
    	if (conflisten == -1)
    		err(1, "socket");
    
    	if (setsockopt(conflisten, SOL_SOCKET, SO_REUSEADDR, &one,
    	    sizeof(one)) == -1)
    		return (-1);
    
    	memset(&hints, 0, sizeof(hints));
    	hints.ai_family = AF_INET;
    	addr = bind_address;
    	snprintf(portstr, sizeof(portstr), "%hu", port);
    
    	if ((error = getaddrinfo(addr, portstr, &hints, &res)) != 0) {
    		errx(1, "getaddrinfo: %s", gai_strerror(error));
    	}
    
    	if (bind(smtplisten, res->ai_addr, res->ai_addrlen) == -1) {
    		freeaddrinfo(res);
    		err(1, "bind");
    	}
    	freeaddrinfo(res);
    
    	memset(&lin, 0, sizeof sin);
    	lin.sin_len = sizeof(sin);
    	lin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    	lin.sin_family = AF_INET;
    	lin.sin_port = htons(cfg_port);
    
    	if (bind(conflisten, (struct sockaddr *)&lin, sizeof lin) == -1)
    		err(1, "bind local");
    
    	if (debug == 0) {
    		if (daemon(1, 1) == -1)
    			err(1, "daemon");
    	}
    
    	if (greylist) {
    		pfdev = open("/dev/pf", O_RDWR);
    		if (pfdev == -1) {
    			syslog_r(LOG_ERR, &sdata, "open /dev/pf: %m");
    			exit(1);
    		}
    
    		check_spamd_db();
    
    		maxblack = (maxblack >= maxcon) ? maxcon - 100 : maxblack;
    		if (maxblack < 0)
    			maxblack = 0;
    
    		/* open pipe to talk to greylister */
    		if (socketpair(AF_UNIX, SOCK_DGRAM, 0, greyback) == -1) {
    			syslog(LOG_ERR, "socketpair (%m)");
    			exit(1);
    		}
    		if (pipe(greypipe) == -1) {
    			syslog(LOG_ERR, "pipe (%m)");
    			exit(1);
    		}
    		/* open pipe to receive spamtrap configs */
    		if (pipe(trappipe) == -1) {
    			syslog(LOG_ERR, "pipe (%m)");
    			exit(1);
    		}
    		jail_pid = fork();
    		switch (jail_pid) {
    		case -1:
    			syslog(LOG_ERR, "fork (%m)");
    			exit(1);
    		case 0:
    			/* child - continue */
    			signal(SIGPIPE, SIG_IGN);
    			grey = fdopen(greypipe[1], "w");
    			if (grey == NULL) {
    				syslog(LOG_ERR, "fdopen (%m)");
    				_exit(1);
    			}
    			close(greyback[0]);
    			close(greypipe[0]);
    			trapfd = trappipe[0];
    			trapcfg = fdopen(trappipe[0], "r");
    			if (trapcfg == NULL) {
    				syslog(LOG_ERR, "fdopen (%m)");
    				_exit(1);
    			}
    			close(trappipe[1]);
    
    			if (setgroups(1, &pw->pw_gid) ||
    			    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
    			    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
    				err(1, "failed to drop privs");
    
    			goto jail;
    		}
    		/* parent - run greylister */
    		close(greyback[1]);
    		grey = fdopen(greypipe[0], "r");
    		if (grey == NULL) {
    			syslog(LOG_ERR, "fdopen (%m)");
    			exit(1);
    		}
    		close(greypipe[1]);
    		trapcfg = fdopen(trappipe[1], "w");
    		if (trapcfg == NULL) {
    			syslog(LOG_ERR, "fdopen (%m)");
    			exit(1);
    		}
    		close(trappipe[0]);
    		return (greywatcher());
    	}
    
    jail:
    	if (pledge("stdio inet", NULL) == -1)
    		err(1, "pledge");
    
    	if (listen(smtplisten, 10) == -1)
    		err(1, "listen");
    
    	if (listen(conflisten, 10) == -1)
    		err(1, "listen");
    
    	if (debug != 0)
    		printf("listening for incoming connections.\n");
    	syslog_r(LOG_WARNING, &sdata, "listening for incoming connections.");
    
    	/* We always check for trap and sync events if configured. */
    	if (trapfd != -1) {
    		pfd[PFD_TRAPFD].fd = trapfd;
    		pfd[PFD_TRAPFD].events = POLLIN;
    	} else {
    		pfd[PFD_TRAPFD].fd = -1;
    		pfd[PFD_TRAPFD].events = 0;
    	}
    	if (syncrecv) {
    		pfd[PFD_SYNCFD].fd = syncfd;
    		pfd[PFD_SYNCFD].events = POLLIN;
    	} else {
    		pfd[PFD_SYNCFD].fd = -1;
    		pfd[PFD_SYNCFD].events = 0;
    	}
    	if (greylist) {
    		pfd[PFD_GREYBACK].fd = greyback[1];
    		pfd[PFD_GREYBACK].events = POLLIN;
    	} else {
    		pfd[PFD_GREYBACK].fd = -1;
    		pfd[PFD_GREYBACK].events = 0;
    	}
    
    	/* events and pfd entries for con[] are filled in below. */
    	pfd[PFD_SMTPLISTEN].fd = smtplisten;
    	pfd[PFD_CONFLISTEN].fd = conflisten;
    
    	while (1) {
    		int numcon = 0, n, timeout, writers;
    
    		time(&t);
    
    		writers = 0;
    		for (i = 0; i < maxcon; i++) {
    			if (con[i].pfd->fd == -1)
    				continue;
    			con[i].pfd->events = 0;
    			if (con[i].r) {
    				if (con[i].r + MAXTIME <= t) {
    					closecon(&con[i]);
    					continue;
    				}
    				con[i].pfd->events |= POLLIN;
    			}
    			if (con[i].w) {
    				if (con[i].w + MAXTIME <= t) {
    					closecon(&con[i]);
    					continue;
    				}
    				if (con[i].w <= t)
    					con[i].pfd->events |= POLLOUT;
    				writers = 1;
    			}
    			if (con[i].tlsaction == SPAMD_TLS_ACT_READ_POLLIN ||
    			    con[i].tlsaction == SPAMD_TLS_ACT_WRITE_POLLIN)
    				con[i].pfd->events = POLLIN;
    			if (con[i].tlsaction == SPAMD_TLS_ACT_READ_POLLOUT ||
    			    con[i].tlsaction == SPAMD_TLS_ACT_WRITE_POLLOUT)
    				con[i].pfd->events = POLLOUT;
    			if (i + 1 > numcon)
    				numcon = i + 1;
    		}
    		pfd[PFD_SMTPLISTEN].events = 0;
    		pfd[PFD_CONFLISTEN].events = 0;
    		pfd[PFD_CONFFD].events = 0;
    		pfd[PFD_CONFFD].fd = conffd;
    		if (slowdowntill == 0) {
    			pfd[PFD_SMTPLISTEN].events = POLLIN;
    
    			/* only one active config conn at a time */
    			if (conffd == -1)
    				pfd[PFD_CONFLISTEN].events = POLLIN;
    			else
    				pfd[PFD_CONFFD].events = POLLIN;
    		}
    
    		/* If we are not listening, wake up at least once a second */
    		if (writers == 0 && slowdowntill == 0)
    			timeout = INFTIM;
    		else
    			timeout = 1000;
    
    		n = poll(pfd, PFD_FIRSTCON + numcon, timeout);
    		if (n == -1) {
    			if (errno != EINTR)
    				err(1, "poll");
    			continue;
    		}
    
    		/* Check if we can speed up accept() calls */
    		if (slowdowntill && slowdowntill > t)
    			slowdowntill = 0;
    
    		for (i = 0; i < maxcon; i++) {
    			if (con[i].pfd->fd == -1)
    				continue;
    			if (pfd[PFD_FIRSTCON + i].revents & POLLHUP) {
    				closecon(&con[i]);
    				continue;
    			}
    			if (pfd[PFD_FIRSTCON + i].revents & POLLIN) {
    				if (con[i].tlsaction ==
    				    SPAMD_TLS_ACT_WRITE_POLLIN)
    					handlew(&con[i], clients + 5 < maxcon);
    				else
    					handler(&con[i]);
    			}
    			if (con[i].pfd->fd != -1 &&
    			    (pfd[PFD_FIRSTCON + i].revents & POLLOUT)) {
    				if (con[i].tlsaction ==
    				    SPAMD_TLS_ACT_READ_POLLOUT)
    					handler(&con[i]);
    				else
    					handlew(&con[i], clients + 5 < maxcon);
    			}
    		}
    		if (pfd[PFD_SMTPLISTEN].revents & (POLLIN|POLLHUP)) {
    			socklen_t sinlen;
    			int s2;
    
    			sinlen = sizeof(sin);
    			s2 = accept4(smtplisten, (struct sockaddr *)&sin, &sinlen,
    			    SOCK_NONBLOCK);
    			if (s2 == -1) {
    				switch (errno) {
    				case EINTR:
    				case ECONNABORTED:
    					break;
    				case EMFILE:
    				case ENFILE:
    					slowdowntill = time(NULL) + 1;
    					break;
    				default:
    					errx(1, "accept");
    				}
    			} else {
    				/* Check if we hit the chosen fd limit */
    				for (i = 0; i < maxcon; i++)
    					if (con[i].pfd->fd == -1)
    						break;
    				if (i == maxcon) {
    					close(s2);
    					slowdowntill = 0;
    				} else {
    					initcon(&con[i], s2,
    					    (struct sockaddr *)&sin);
    					syslog_r(LOG_INFO, &sdata,
    					    "%s: connected (%d/%d)%s%s",
    					    con[i].addr, clients, blackcount,
    					    ((con[i].lists == NULL) ? "" :
    					    ", lists:"),
    					    ((con[i].lists == NULL) ? "":
    					    con[i].lists));
    				}
    			}
    		}
    		if (pfd[PFD_CONFLISTEN].revents & (POLLIN|POLLHUP)) {
    			socklen_t sinlen;
    
    			sinlen = sizeof(lin);
    			conffd = accept(conflisten, (struct sockaddr *)&lin,
    			    &sinlen);
    			if (conffd == -1) {
    				switch (errno) {
    				case EINTR:
    				case ECONNABORTED:
    					break;
    				case EMFILE:
    				case ENFILE:
    					slowdowntill = time(NULL) + 1;
    					break;
    				default:
    					errx(1, "accept");
    				}
    			} else if (ntohs(lin.sin_port) >= IPPORT_RESERVED) {
    				close(conffd);
    				conffd = -1;
    				slowdowntill = 0;
    			}
    		} else if (pfd[PFD_CONFFD].revents & (POLLIN|POLLHUP))
    			do_config();
    		if (pfd[PFD_TRAPFD].revents & (POLLIN|POLLHUP))
    			read_configline(trapcfg);
    		if (pfd[PFD_SYNCFD].revents & (POLLIN|POLLHUP))
    			sync_recv();
    		if (pfd[PFD_GREYBACK].revents & (POLLIN|POLLHUP))
    			blackcheck(greyback[1]);
    	}
    	exit(1);
    }
    
    void
    blackcheck(int fd)
    {
    	struct sockaddr_storage ss;
    	ssize_t nread;
    	void *ia;
    	char ch;
    
    	/* Read sockaddr from greylister and look it up in the blacklists. */
    	nread = recv(fd, &ss, sizeof(ss), 0);
    	if (nread == -1) {
    		syslog(LOG_ERR, "%s: recv: %m", __func__);
    		return;
    	}
    	if (nread != sizeof(struct sockaddr_in) &&
    	    nread != sizeof(struct sockaddr_in6)) {
    		syslog(LOG_ERR, "%s: invalid size %zd", __func__, nread);
    		return;
    	}
    	if (ss.ss_family == AF_INET) {
    		ia = &((struct sockaddr_in *)&ss)->sin_addr;
    	} else if (ss.ss_family == AF_INET6) {
    		ia = &((struct sockaddr_in6 *)&ss)->sin6_addr;
    	} else {
    		syslog(LOG_ERR, "%s: bad family %d", __func__, ss.ss_family);
    		return;
    	}
    	ch = sdl_check(blacklists, ss.ss_family, ia) ? '1' : '0';
    
    	/* Send '1' for match or '0' for no match. */
    	if (send(fd, &ch, sizeof(ch), 0) == -1) {
    		syslog(LOG_ERR, "%s: send: %m", __func__);
    		return;
    	}
    }