xref: /openbsd-src/libexec/spamd/spamd.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: spamd.c,v 1.108 2010/01/14 00:44:12 beck Exp $	*/
2 
3 /*
4  * Copyright (c) 2002-2007 Bob Beck.  All rights reserved.
5  * Copyright (c) 2002 Theo de Raadt.  All rights reserved.
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/param.h>
21 #include <sys/file.h>
22 #include <sys/wait.h>
23 #include <sys/socket.h>
24 #include <sys/sysctl.h>
25 #include <sys/resource.h>
26 
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 
30 #include <err.h>
31 #include <errno.h>
32 #include <getopt.h>
33 #include <pwd.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <syslog.h>
38 #include <unistd.h>
39 
40 #include <netdb.h>
41 
42 #include "sdl.h"
43 #include "grey.h"
44 #include "sync.h"
45 
46 extern int server_lookup(struct sockaddr *, struct sockaddr *,
47     struct sockaddr *);
48 
49 struct con {
50 	int fd;
51 	int state;
52 	int laststate;
53 	int af;
54 	struct sockaddr_storage ss;
55 	void *ia;
56 	char addr[32];
57 	char caddr[32];
58 	char helo[MAX_MAIL], mail[MAX_MAIL], rcpt[MAX_MAIL];
59 	struct sdlist **blacklists;
60 
61 	/*
62 	 * we will do stuttering by changing these to time_t's of
63 	 * now + n, and only advancing when the time is in the past/now
64 	 */
65 	time_t r;
66 	time_t w;
67 	time_t s;
68 
69 	char ibuf[8192];
70 	char *ip;
71 	int il;
72 	char rend[5];	/* any chars in here causes input termination */
73 
74 	char *obuf;
75 	char *lists;
76 	size_t osize;
77 	char *op;
78 	int ol;
79 	int data_lines;
80 	int data_body;
81 	int stutter;
82 	int badcmd;
83 	int sr;
84 } *con;
85 
86 void     usage(void);
87 char    *grow_obuf(struct con *, int);
88 int      parse_configline(char *);
89 void     parse_configs(void);
90 void     do_config(void);
91 int      append_error_string (struct con *, size_t, char *, int, void *);
92 void     build_reply(struct  con *);
93 void     doreply(struct con *);
94 void     setlog(char *, size_t, char *);
95 void     initcon(struct con *, int, struct sockaddr *);
96 void     closecon(struct con *);
97 int      match(const char *, const char *);
98 void     nextstate(struct con *);
99 void     handler(struct con *);
100 void     handlew(struct con *, int one);
101 
102 char hostname[MAXHOSTNAMELEN];
103 struct syslog_data sdata = SYSLOG_DATA_INIT;
104 char *nreply = "450";
105 char *spamd = "spamd IP-based SPAM blocker";
106 int greypipe[2];
107 int trappipe[2];
108 FILE *grey;
109 FILE *trapcfg;
110 time_t passtime = PASSTIME;
111 time_t greyexp = GREYEXP;
112 time_t whiteexp = WHITEEXP;
113 time_t trapexp = TRAPEXP;
114 struct passwd *pw;
115 pid_t jail_pid = -1;
116 u_short cfg_port;
117 u_short sync_port;
118 
119 extern struct sdlist *blacklists;
120 extern int pfdev;
121 extern char *low_prio_mx_ip;
122 
123 int conffd = -1;
124 int trapfd = -1;
125 char *cb;
126 size_t cbs, cbu;
127 
128 time_t t;
129 
130 #define MAXCON 800
131 int maxfiles;
132 int maxcon = MAXCON;
133 int maxblack = MAXCON;
134 int blackcount;
135 int clients;
136 int debug;
137 int greylist = 1;
138 int grey_stutter = 10;
139 int verbose;
140 int stutter = 1;
141 int window;
142 int syncrecv;
143 int syncsend;
144 #define MAXTIME 400
145 
146 void
147 usage(void)
148 {
149 	extern char *__progname;
150 
151 	fprintf(stderr,
152 	    "usage: %s [-45bdv] [-B maxblack] [-c maxcon] "
153 	    "[-G passtime:greyexp:whiteexp]\n"
154 	    "\t[-h hostname] [-l address] [-M address] [-n name] [-p port]\n"
155 	    "\t[-S secs] [-s secs] "
156 	    "[-w window] [-Y synctarget] [-y synclisten]\n",
157 	    __progname);
158 
159 	exit(1);
160 }
161 
162 char *
163 grow_obuf(struct con *cp, int off)
164 {
165 	char *tmp;
166 
167 	tmp = realloc(cp->obuf, cp->osize + 8192);
168 	if (tmp == NULL) {
169 		free(cp->obuf);
170 		cp->obuf = NULL;
171 		cp->osize = 0;
172 		return (NULL);
173 	} else {
174 		cp->osize += 8192;
175 		cp->obuf = tmp;
176 		return (cp->obuf + off);
177 	}
178 }
179 
180 int
181 parse_configline(char *line)
182 {
183 	char *cp, prev, *name, *msg;
184 	static char **av = NULL;
185 	static size_t ac = 0;
186 	size_t au = 0;
187 	int mdone = 0;
188 
189 	name = line;
190 
191 	for (cp = name; *cp && *cp != ';'; cp++)
192 		;
193 	if (*cp != ';')
194 		goto parse_error;
195 	*cp++ = '\0';
196 	if (!*cp) {
197 		sdl_del(name);
198 		return (0);
199 	}
200 	msg = cp;
201 	if (*cp++ != '"')
202 		goto parse_error;
203 	prev = '\0';
204 	for (; !mdone; cp++) {
205 		switch (*cp) {
206 		case '\\':
207 			if (!prev)
208 				prev = *cp;
209 			else
210 				prev = '\0';
211 			break;
212 		case '"':
213 			if (prev != '\\') {
214 				cp++;
215 				if (*cp == ';') {
216 					mdone = 1;
217 					*cp = '\0';
218 				} else
219 					goto parse_error;
220 			}
221 			break;
222 		case '\0':
223 			goto parse_error;
224 		default:
225 			prev = '\0';
226 			break;
227 		}
228 	}
229 
230 	do {
231 		if (ac == au) {
232 			char **tmp;
233 
234 			tmp = realloc(av, (ac + 2048) * sizeof(char *));
235 			if (tmp == NULL) {
236 				free(av);
237 				av = NULL;
238 				ac = 0;
239 				return (-1);
240 			}
241 			av = tmp;
242 			ac += 2048;
243 		}
244 	} while ((av[au++] = strsep(&cp, ";")) != NULL);
245 
246 	/* toss empty last entry to allow for trailing ; */
247 	while (au > 0 && (av[au - 1] == NULL || av[au - 1][0] == '\0'))
248 		au--;
249 
250 	if (au < 1)
251 		goto parse_error;
252 	else
253 		sdl_add(name, msg, av, au);
254 	return (0);
255 
256 parse_error:
257 	if (debug > 0)
258 		printf("bogus config line - need 'tag;message;a/m;a/m;a/m...'\n");
259 	return (-1);
260 }
261 
262 void
263 parse_configs(void)
264 {
265 	char *start, *end;
266 	int i;
267 
268 	if (cbu == cbs) {
269 		char *tmp;
270 
271 		tmp = realloc(cb, cbs + 8192);
272 		if (tmp == NULL) {
273 			if (debug > 0)
274 				perror("malloc()");
275 			free(cb);
276 			cb = NULL;
277 			cbs = cbu = 0;
278 			return;
279 		}
280 		cbs += 8192;
281 		cb = tmp;
282 	}
283 	cb[cbu++] = '\0';
284 
285 	start = cb;
286 	end = start;
287 	for (i = 0; i < cbu; i++) {
288 		if (*end == '\n') {
289 			*end = '\0';
290 			if (end > start + 1)
291 				parse_configline(start);
292 			start = ++end;
293 		} else
294 			++end;
295 	}
296 	if (end > start + 1)
297 		parse_configline(start);
298 }
299 
300 void
301 do_config(void)
302 {
303 	int n;
304 
305 	if (debug > 0)
306 		printf("got configuration connection\n");
307 
308 	if (cbu == cbs) {
309 		char *tmp;
310 
311 		tmp = realloc(cb, cbs + 8192);
312 		if (tmp == NULL) {
313 			if (debug > 0)
314 				perror("malloc()");
315 			free(cb);
316 			cb = NULL;
317 			cbs = 0;
318 			goto configdone;
319 		}
320 		cbs += 8192;
321 		cb = tmp;
322 	}
323 
324 	n = read(conffd, cb + cbu, cbs - cbu);
325 	if (debug > 0)
326 		printf("read %d config bytes\n", n);
327 	if (n == 0) {
328 		parse_configs();
329 		goto configdone;
330 	} else if (n == -1) {
331 		if (debug > 0)
332 			perror("read()");
333 		goto configdone;
334 	} else
335 		cbu += n;
336 	return;
337 
338 configdone:
339 	cbu = 0;
340 	close(conffd);
341 	conffd = -1;
342 }
343 
344 int
345 read_configline(FILE *config)
346 {
347 	char *buf;
348 	size_t len;
349 
350 	if ((buf = fgetln(config, &len))) {
351 		if (buf[len - 1] == '\n')
352 			buf[len - 1] = '\0';
353 		else
354 			return (-1);	/* all valid lines end in \n */
355 		parse_configline(buf);
356 	} else {
357 		syslog_r(LOG_DEBUG, &sdata, "read_configline: fgetln (%m)");
358 		return (-1);
359 	}
360 	return (0);
361 }
362 
363 int
364 append_error_string(struct con *cp, size_t off, char *fmt, int af, void *ia)
365 {
366 	char sav = '\0';
367 	static int lastcont = 0;
368 	char *c = cp->obuf + off;
369 	char *s = fmt;
370 	size_t len = cp->osize - off;
371 	int i = 0;
372 
373 	if (off == 0)
374 		lastcont = 0;
375 
376 	if (lastcont != 0)
377 		cp->obuf[lastcont] = '-';
378 	snprintf(c, len, "%s ", nreply);
379 	i += strlen(c);
380 	lastcont = off + i - 1;
381 	if (*s == '"')
382 		s++;
383 	while (*s) {
384 		/*
385 		 * Make sure we at minimum, have room to add a
386 		 * format code (4 bytes), and a v6 address(39 bytes)
387 		 * and a byte saved in sav.
388 		 */
389 		if (i >= len - 46) {
390 			c = grow_obuf(cp, off);
391 			if (c == NULL)
392 				return (-1);
393 			len = cp->osize - (off + i);
394 		}
395 
396 		if (c[i-1] == '\n') {
397 			if (lastcont != 0)
398 				cp->obuf[lastcont] = '-';
399 			snprintf(c + i, len, "%s ", nreply);
400 			i += strlen(c);
401 			lastcont = off + i - 1;
402 		}
403 
404 		switch (*s) {
405 		case '\\':
406 		case '%':
407 			if (!sav)
408 				sav = *s;
409 			else {
410 				c[i++] = sav;
411 				sav = '\0';
412 				c[i] = '\0';
413 			}
414 			break;
415 		case '"':
416 		case 'A':
417 		case 'n':
418 			if (*(s+1) == '\0') {
419 				break;
420 			}
421 			if (sav == '\\' && *s == 'n') {
422 				c[i++] = '\n';
423 				sav = '\0';
424 				c[i] = '\0';
425 				break;
426 			} else if (sav == '\\' && *s == '"') {
427 				c[i++] = '"';
428 				sav = '\0';
429 				c[i] = '\0';
430 				break;
431 			} else if (sav == '%' && *s == 'A') {
432 				inet_ntop(af, ia, c + i, (len - i));
433 				i += strlen(c + i);
434 				sav = '\0';
435 				break;
436 			}
437 			/* FALLTHROUGH */
438 		default:
439 			if (sav)
440 				c[i++] = sav;
441 			c[i++] = *s;
442 			sav = '\0';
443 			c[i] = '\0';
444 			break;
445 		}
446 		s++;
447 	}
448 	return (i);
449 }
450 
451 char *
452 loglists(struct con *cp)
453 {
454 	static char matchlists[80];
455 	struct sdlist **matches;
456 	int s = sizeof(matchlists) - 4;
457 
458 	matchlists[0] = '\0';
459 	matches = cp->blacklists;
460 	if (matches == NULL)
461 		return (NULL);
462 	for (; *matches; matches++) {
463 
464 		/* don't report an insane amount of lists in the logs.
465 		 * just truncate and indicate with ...
466 		 */
467 		if (strlen(matchlists) + strlen(matches[0]->tag) + 1 >= s)
468 			strlcat(matchlists, " ...", sizeof(matchlists));
469 		else {
470 			strlcat(matchlists, " ", s);
471 			strlcat(matchlists, matches[0]->tag, s);
472 		}
473 	}
474 	return matchlists;
475 }
476 
477 void
478 build_reply(struct con *cp)
479 {
480 	struct sdlist **matches;
481 	int off = 0;
482 
483 	matches = cp->blacklists;
484 	if (matches == NULL)
485 		goto nomatch;
486 	for (; *matches; matches++) {
487 		int used = 0;
488 		char *c = cp->obuf + off;
489 		int left = cp->osize - off;
490 
491 		used = append_error_string(cp, off, matches[0]->string,
492 		    cp->af, cp->ia);
493 		if (used == -1)
494 			goto bad;
495 		off += used;
496 		left -= used;
497 		if (cp->obuf[off - 1] != '\n') {
498 			if (left < 1) {
499 				c = grow_obuf(cp, off);
500 				if (c == NULL)
501 					goto bad;
502 			}
503 			cp->obuf[off++] = '\n';
504 			cp->obuf[off] = '\0';
505 		}
506 	}
507 	return;
508 nomatch:
509 	/* No match. give generic reply */
510 	free(cp->obuf);
511 	cp->obuf = NULL;
512 	cp->osize = 0;
513 	if (cp->blacklists != NULL)
514 		asprintf(&cp->obuf,
515 		    "%s-Sorry %s\n"
516 		    "%s-You are trying to send mail from an address "
517 		    "listed by one\n"
518 		    "%s or more IP-based registries as being a SPAM source.\n",
519 		    nreply, cp->addr, nreply, nreply);
520 	else
521 		asprintf(&cp->obuf,
522 		    "451 Temporary failure, please try again later.\r\n");
523 	if (cp->obuf != NULL)
524 		cp->osize = strlen(cp->obuf) + 1;
525 	else
526 		cp->osize = 0;
527 	return;
528 bad:
529 	if (cp->obuf != NULL) {
530 		free(cp->obuf);
531 		cp->obuf = NULL;
532 		cp->osize = 0;
533 	}
534 }
535 
536 void
537 doreply(struct con *cp)
538 {
539 	build_reply(cp);
540 }
541 
542 void
543 setlog(char *p, size_t len, char *f)
544 {
545 	char *s;
546 
547 	s = strsep(&f, ":");
548 	if (!f)
549 		return;
550 	while (*f == ' ' || *f == '\t')
551 		f++;
552 	s = strsep(&f, " \t");
553 	if (s == NULL)
554 		return;
555 	strlcpy(p, s, len);
556 	s = strsep(&p, " \t\n\r");
557 	if (s == NULL)
558 		return;
559 	s = strsep(&p, " \t\n\r");
560 	if (s)
561 		*s = '\0';
562 }
563 
564 /*
565  * Get address client connected to, by doing a DIOCNATLOOK call.
566  * Uses server_lookup code from ftp-proxy.
567  */
568 void
569 getcaddr(struct con *cp) {
570 	struct sockaddr_storage spamd_end;
571 	struct sockaddr *sep = (struct sockaddr *) &spamd_end;
572 	struct sockaddr_storage original_destination;
573 	struct sockaddr *odp = (struct sockaddr *) &original_destination;
574 	socklen_t len = sizeof(struct sockaddr_storage);
575 	int error;
576 
577 	cp->caddr[0] = '\0';
578 	if (getsockname(cp->fd, sep, &len) == -1)
579 		return;
580 	if (server_lookup((struct sockaddr *)&cp->ss, sep, odp) != 0)
581 		return;
582 	error = getnameinfo(odp, odp->sa_len, cp->caddr, sizeof(cp->caddr),
583 	    NULL, 0, NI_NUMERICHOST);
584 	if (error)
585 		cp->caddr[0] = '\0';
586 }
587 
588 
589 void
590 gethelo(char *p, size_t len, char *f)
591 {
592 	char *s;
593 
594 	/* skip HELO/EHLO */
595 	f+=4;
596 	/* skip whitespace */
597 	while (*f == ' ' || *f == '\t')
598 		f++;
599 	s = strsep(&f, " \t");
600 	if (s == NULL)
601 		return;
602 	strlcpy(p, s, len);
603 	s = strsep(&p, " \t\n\r");
604 	if (s == NULL)
605 		return;
606 	s = strsep(&p, " \t\n\r");
607 	if (s)
608 		*s = '\0';
609 }
610 
611 void
612 initcon(struct con *cp, int fd, struct sockaddr *sa)
613 {
614 	socklen_t len = sa->sa_len;
615 	time_t tt;
616 	char *tmp;
617 	int error;
618 
619 	time(&tt);
620 	free(cp->obuf);
621 	cp->obuf = NULL;
622 	cp->osize = 0;
623 	free(cp->blacklists);
624 	cp->blacklists = NULL;
625 	free(cp->lists);
626 	cp->lists = NULL;
627 	bzero(cp, sizeof(struct con));
628 	if (grow_obuf(cp, 0) == NULL)
629 		err(1, "malloc");
630 	cp->fd = fd;
631 	if (len > sizeof(cp->ss))
632 		errx(1, "sockaddr size");
633 	if (sa->sa_family != AF_INET)
634 		errx(1, "not supported yet");
635 	memcpy(&cp->ss, sa, sa->sa_len);
636 	cp->af = sa->sa_family;
637 	cp->ia = &((struct sockaddr_in *)&cp->ss)->sin_addr;
638 	cp->blacklists = sdl_lookup(blacklists, cp->af, cp->ia);
639 	cp->stutter = (greylist && !grey_stutter && cp->blacklists == NULL) ?
640 	    0 : stutter;
641 	error = getnameinfo(sa, sa->sa_len, cp->addr, sizeof(cp->addr), NULL, 0,
642 	    NI_NUMERICHOST);
643 	if (error)
644 		errx(1, "%s", gai_strerror(error));
645 	tmp = strdup(ctime(&t));
646 	if (tmp == NULL)
647 		err(1, "malloc");
648 	tmp[strlen(tmp) - 1] = '\0'; /* nuke newline */
649 	snprintf(cp->obuf, cp->osize, "220 %s ESMTP %s; %s\r\n",
650 	    hostname, spamd, tmp);
651 	free(tmp);
652 	cp->op = cp->obuf;
653 	cp->ol = strlen(cp->op);
654 	cp->w = tt + cp->stutter;
655 	cp->s = tt;
656 	strlcpy(cp->rend, "\n", sizeof cp->rend);
657 	clients++;
658 	if (cp->blacklists != NULL) {
659 		blackcount++;
660 		if (greylist && blackcount > maxblack)
661 			cp->stutter = 0;
662 		cp->lists = strdup(loglists(cp));
663 	}
664 	else
665 		cp->lists = NULL;
666 }
667 
668 void
669 closecon(struct con *cp)
670 {
671 	time_t tt;
672 
673 	time(&tt);
674 	syslog_r(LOG_INFO, &sdata, "%s: disconnected after %ld seconds.%s%s",
675 	    cp->addr, (long)(tt - cp->s),
676 	    ((cp->lists == NULL) ? "" : " lists:"),
677 	    ((cp->lists == NULL) ? "": cp->lists));
678 	if (debug > 0)
679 		printf("%s connected for %ld seconds.\n", cp->addr,
680 		    (long)(tt - cp->s));
681 	if (cp->lists != NULL) {
682 		free(cp->lists);
683 		cp->lists = NULL;
684 	}
685 	if (cp->blacklists != NULL) {
686 		blackcount--;
687 		free(cp->blacklists);
688 		cp->blacklists = NULL;
689 	}
690 	if (cp->obuf != NULL) {
691 		free(cp->obuf);
692 		cp->obuf = NULL;
693 		cp->osize = 0;
694 	}
695 	close(cp->fd);
696 	clients--;
697 	cp->fd = -1;
698 }
699 
700 int
701 match(const char *s1, const char *s2)
702 {
703 	return (strncasecmp(s1, s2, strlen(s2)) == 0);
704 }
705 
706 void
707 nextstate(struct con *cp)
708 {
709 	if (match(cp->ibuf, "QUIT") && cp->state < 99) {
710 		snprintf(cp->obuf, cp->osize, "221 %s\r\n", hostname);
711 		cp->op = cp->obuf;
712 		cp->ol = strlen(cp->op);
713 		cp->w = t + cp->stutter;
714 		cp->laststate = cp->state;
715 		cp->state = 99;
716 		return;
717 	}
718 
719 	if (match(cp->ibuf, "RSET") && cp->state > 2 && cp->state < 50) {
720 		snprintf(cp->obuf, cp->osize,
721 		    "250 Ok to start over.\r\n");
722 		cp->op = cp->obuf;
723 		cp->ol = strlen(cp->op);
724 		cp->w = t + cp->stutter;
725 		cp->laststate = cp->state;
726 		cp->state = 2;
727 		return;
728 	}
729 	switch (cp->state) {
730 	case 0:
731 		/* banner sent; wait for input */
732 		cp->ip = cp->ibuf;
733 		cp->il = sizeof(cp->ibuf) - 1;
734 		cp->laststate = cp->state;
735 		cp->state = 1;
736 		cp->r = t;
737 		break;
738 	case 1:
739 		/* received input: parse, and select next state */
740 		if (match(cp->ibuf, "HELO") ||
741 		    match(cp->ibuf, "EHLO")) {
742 			int nextstate = 2;
743 			cp->helo[0] = '\0';
744 			gethelo(cp->helo, sizeof cp->helo, cp->ibuf);
745 			if (cp->helo[0] == '\0') {
746 				nextstate = 0;
747 				snprintf(cp->obuf, cp->osize,
748 				    "501 helo requires domain name.\r\n");
749 			} else {
750 				snprintf(cp->obuf, cp->osize,
751 				    "250 Hello, spam sender. "
752 				    "Pleased to be wasting your time.\r\n");
753 			}
754 			cp->op = cp->obuf;
755 			cp->ol = strlen(cp->op);
756 			cp->laststate = cp->state;
757 			cp->state = nextstate;
758 			cp->w = t + cp->stutter;
759 			break;
760 		}
761 		goto mail;
762 	case 2:
763 		/* sent 250 Hello, wait for input */
764 		cp->ip = cp->ibuf;
765 		cp->il = sizeof(cp->ibuf) - 1;
766 		cp->laststate = cp->state;
767 		cp->state = 3;
768 		cp->r = t;
769 		break;
770 	case 3:
771 	mail:
772 		if (match(cp->ibuf, "MAIL")) {
773 			setlog(cp->mail, sizeof cp->mail, cp->ibuf);
774 			snprintf(cp->obuf, cp->osize,
775 			    "250 You are about to try to deliver spam. "
776 			    "Your time will be spent, for nothing.\r\n");
777 			cp->op = cp->obuf;
778 			cp->ol = strlen(cp->op);
779 			cp->laststate = cp->state;
780 			cp->state = 4;
781 			cp->w = t + cp->stutter;
782 			break;
783 		}
784 		goto rcpt;
785 	case 4:
786 		/* sent 250 Sender ok */
787 		cp->ip = cp->ibuf;
788 		cp->il = sizeof(cp->ibuf) - 1;
789 		cp->laststate = cp->state;
790 		cp->state = 5;
791 		cp->r = t;
792 		break;
793 	case 5:
794 	rcpt:
795 		if (match(cp->ibuf, "RCPT")) {
796 			setlog(cp->rcpt, sizeof(cp->rcpt), cp->ibuf);
797 			snprintf(cp->obuf, cp->osize,
798 			    "250 This is hurting you more than it is "
799 			    "hurting me.\r\n");
800 			cp->op = cp->obuf;
801 			cp->ol = strlen(cp->op);
802 			cp->laststate = cp->state;
803 			cp->state = 6;
804 			cp->w = t + cp->stutter;
805 			if (cp->mail[0] && cp->rcpt[0]) {
806 				if (verbose)
807 					syslog_r(LOG_INFO, &sdata,
808 					    "(%s) %s: %s -> %s",
809 					    cp->blacklists ? "BLACK" : "GREY",
810 					    cp->addr, cp->mail,
811 					    cp->rcpt);
812 				if (debug)
813 					fprintf(stderr, "(%s) %s: %s -> %s\n",
814 					    cp->blacklists ? "BLACK" : "GREY",
815 					    cp->addr, cp->mail, cp->rcpt);
816 				if (greylist && cp->blacklists == NULL) {
817 					/* send this info to the greylister */
818 					getcaddr(cp);
819 					fprintf(grey,
820 					    "CO:%s\nHE:%s\nIP:%s\nFR:%s\nTO:%s\n",
821 					    cp->caddr, cp->helo, cp->addr,
822 					    cp->mail, cp->rcpt);
823 					fflush(grey);
824 				}
825 			}
826 			break;
827 		}
828 		goto spam;
829 	case 6:
830 		/* sent 250 blah */
831 		cp->ip = cp->ibuf;
832 		cp->il = sizeof(cp->ibuf) - 1;
833 		cp->laststate = cp->state;
834 		cp->state = 5;
835 		cp->r = t;
836 		break;
837 
838 	case 50:
839 	spam:
840 		if (match(cp->ibuf, "DATA")) {
841 			snprintf(cp->obuf, cp->osize,
842 			    "354 Enter spam, end with \".\" on a line by "
843 			    "itself\r\n");
844 			cp->state = 60;
845 			if (window && setsockopt(cp->fd, SOL_SOCKET, SO_RCVBUF,
846 			    &window, sizeof(window)) == -1) {
847 				syslog_r(LOG_DEBUG, &sdata,"setsockopt: %m");
848 				/* don't fail if this doesn't work. */
849 			}
850 			cp->ip = cp->ibuf;
851 			cp->il = sizeof(cp->ibuf) - 1;
852 			cp->op = cp->obuf;
853 			cp->ol = strlen(cp->op);
854 			cp->w = t + cp->stutter;
855 			if (greylist && cp->blacklists == NULL) {
856 				cp->laststate = cp->state;
857 				cp->state = 98;
858 				goto done;
859 			}
860 		} else {
861 			if (match(cp->ibuf, "NOOP"))
862 				snprintf(cp->obuf, cp->osize,
863 				    "250 2.0.0 OK I did nothing\r\n");
864 			else {
865                         	snprintf(cp->obuf, cp->osize,
866 				    "500 5.5.1 Command unrecognized\r\n");
867 				cp->badcmd++;
868 				if (cp->badcmd > 20) {
869 					cp->laststate = cp->state;
870 					cp->state = 98;
871 					goto done;
872 				}
873 			}
874 			cp->state = cp->laststate;
875 			cp->ip = cp->ibuf;
876 			cp->il = sizeof(cp->ibuf) - 1;
877 			cp->op = cp->obuf;
878 			cp->ol = strlen(cp->op);
879 			cp->w = t + cp->stutter;
880 		}
881 		break;
882 	case 60:
883 		/* sent 354 blah */
884 		cp->ip = cp->ibuf;
885 		cp->il = sizeof(cp->ibuf) - 1;
886 		cp->laststate = cp->state;
887 		cp->state = 70;
888 		cp->r = t;
889 		break;
890 	case 70: {
891 		char *p, *q;
892 
893 		for (p = q = cp->ibuf; q <= cp->ip; ++q)
894 			if (*q == '\n' || q == cp->ip) {
895 				*q = 0;
896 				if (q > p && q[-1] == '\r')
897 					q[-1] = 0;
898 				if (!strcmp(p, ".") ||
899 				    (cp->data_body && ++cp->data_lines >= 10)) {
900 					cp->laststate = cp->state;
901 					cp->state = 98;
902 					goto done;
903 				}
904 				if (!cp->data_body && !*p)
905 					cp->data_body = 1;
906 				if (verbose && cp->data_body && *p)
907 					syslog_r(LOG_DEBUG, &sdata, "%s: "
908 					    "Body: %s", cp->addr, p);
909 				else if (verbose && (match(p, "FROM:") ||
910 				    match(p, "TO:") || match(p, "SUBJECT:")))
911 					syslog_r(LOG_INFO, &sdata, "%s: %s",
912 					    cp->addr, p);
913 				p = ++q;
914 			}
915 		cp->ip = cp->ibuf;
916 		cp->il = sizeof(cp->ibuf) - 1;
917 		cp->r = t;
918 		break;
919 	}
920 	case 98:
921 	done:
922 		doreply(cp);
923 		cp->op = cp->obuf;
924 		cp->ol = strlen(cp->op);
925 		cp->w = t + cp->stutter;
926 		cp->laststate = cp->state;
927 		cp->state = 99;
928 		break;
929 	case 99:
930 		closecon(cp);
931 		break;
932 	default:
933 		errx(1, "illegal state %d", cp->state);
934 		break;
935 	}
936 }
937 
938 void
939 handler(struct con *cp)
940 {
941 	int end = 0;
942 	int n;
943 
944 	if (cp->r) {
945 		n = read(cp->fd, cp->ip, cp->il);
946 		if (n == 0)
947 			closecon(cp);
948 		else if (n == -1) {
949 			if (debug > 0)
950 				perror("read()");
951 			closecon(cp);
952 		} else {
953 			cp->ip[n] = '\0';
954 			if (cp->rend[0])
955 				if (strpbrk(cp->ip, cp->rend))
956 					end = 1;
957 			cp->ip += n;
958 			cp->il -= n;
959 		}
960 	}
961 	if (end || cp->il == 0) {
962 		while (cp->ip > cp->ibuf &&
963 		    (cp->ip[-1] == '\r' || cp->ip[-1] == '\n'))
964 			cp->ip--;
965 		*cp->ip = '\0';
966 		cp->r = 0;
967 		nextstate(cp);
968 	}
969 }
970 
971 void
972 handlew(struct con *cp, int one)
973 {
974 	int n;
975 
976 	/* kill stutter on greylisted connections after initial delay */
977 	if (cp->stutter && greylist && cp->blacklists == NULL &&
978 	    (t - cp->s) > grey_stutter)
979 		cp->stutter=0;
980 
981 	if (cp->w) {
982 		if (*cp->op == '\n' && !cp->sr) {
983 			/* insert \r before \n */
984 			n = write(cp->fd, "\r", 1);
985 			if (n == 0) {
986 				closecon(cp);
987 				goto handled;
988 			} else if (n == -1) {
989 				if (debug > 0 && errno != EPIPE)
990 					perror("write()");
991 				closecon(cp);
992 				goto handled;
993 			}
994 		}
995 		if (*cp->op == '\r')
996 			cp->sr = 1;
997 		else
998 			cp->sr = 0;
999 		n = write(cp->fd, cp->op, (one && cp->stutter) ? 1 : cp->ol);
1000 		if (n == 0)
1001 			closecon(cp);
1002 		else if (n == -1) {
1003 			if (debug > 0 && errno != EPIPE)
1004 				perror("write()");
1005 			closecon(cp);
1006 		} else {
1007 			cp->op += n;
1008 			cp->ol -= n;
1009 		}
1010 	}
1011 handled:
1012 	cp->w = t + cp->stutter;
1013 	if (cp->ol == 0) {
1014 		cp->w = 0;
1015 		nextstate(cp);
1016 	}
1017 }
1018 
1019 static int
1020 get_maxfiles(void)
1021 {
1022 	int mib[2], maxfiles;
1023 	size_t len;
1024 
1025 	mib[0] = CTL_KERN;
1026 	mib[1] = KERN_MAXFILES;
1027 	len = sizeof(maxfiles);
1028 	if (sysctl(mib, 2, &maxfiles, &len, NULL, 0) == -1)
1029 		return(MAXCON);
1030 	if ((maxfiles - 200) < 10)
1031 		errx(1, "kern.maxfiles is only %d, can not continue\n",
1032 		    maxfiles);
1033 	else
1034 		return(maxfiles - 200);
1035 }
1036 
1037 int
1038 main(int argc, char *argv[])
1039 {
1040 	fd_set *fdsr = NULL, *fdsw = NULL;
1041 	struct sockaddr_in sin;
1042 	struct sockaddr_in lin;
1043 	int ch, s, s2, conflisten = 0, syncfd = 0, i, omax = 0, one = 1;
1044 	socklen_t sinlen;
1045 	u_short port;
1046 	struct servent *ent;
1047 	struct rlimit rlp;
1048 	char *bind_address = NULL;
1049 	const char *errstr;
1050 	char *sync_iface = NULL;
1051 	char *sync_baddr = NULL;
1052 
1053 	tzset();
1054 	openlog_r("spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
1055 
1056 	if ((ent = getservbyname("spamd", "tcp")) == NULL)
1057 		errx(1, "Can't find service \"spamd\" in /etc/services");
1058 	port = ntohs(ent->s_port);
1059 	if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL)
1060 		errx(1, "Can't find service \"spamd-cfg\" in /etc/services");
1061 	cfg_port = ntohs(ent->s_port);
1062 	if ((ent = getservbyname("spamd-sync", "udp")) == NULL)
1063 		errx(1, "Can't find service \"spamd-sync\" in /etc/services");
1064 	sync_port = ntohs(ent->s_port);
1065 
1066 	if (gethostname(hostname, sizeof hostname) == -1)
1067 		err(1, "gethostname");
1068 	maxfiles = get_maxfiles();
1069 	if (maxcon > maxfiles)
1070 		maxcon = maxfiles;
1071 	if (maxblack > maxfiles)
1072 		maxblack = maxfiles;
1073 	while ((ch =
1074 	    getopt(argc, argv, "45l:c:B:p:bdG:h:s:S:M:n:vw:y:Y:")) != -1) {
1075 		switch (ch) {
1076 		case '4':
1077 			nreply = "450";
1078 			break;
1079 		case '5':
1080 			nreply = "550";
1081 			break;
1082 		case 'l':
1083 			bind_address = optarg;
1084 			break;
1085 		case 'B':
1086 			i = atoi(optarg);
1087 			maxblack = i;
1088 			break;
1089 		case 'c':
1090 			i = atoi(optarg);
1091 			if (i > maxfiles) {
1092 				fprintf(stderr,
1093 				    "%d > system max of %d connections\n",
1094 				    i, maxfiles);
1095 				usage();
1096 			}
1097 			maxcon = i;
1098 			break;
1099 		case 'p':
1100 			i = atoi(optarg);
1101 			port = i;
1102 			break;
1103 		case 'd':
1104 			debug = 1;
1105 			break;
1106 		case 'b':
1107 			greylist = 0;
1108 			break;
1109 		case 'G':
1110 			if (sscanf(optarg, "%d:%d:%d", &passtime, &greyexp,
1111 			    &whiteexp) != 3)
1112 				usage();
1113 			/* convert to seconds from minutes */
1114 			passtime *= 60;
1115 			/* convert to seconds from hours */
1116 			whiteexp *= (60 * 60);
1117 			/* convert to seconds from hours */
1118 			greyexp *= (60 * 60);
1119 			break;
1120 		case 'h':
1121 			bzero(&hostname, sizeof(hostname));
1122 			if (strlcpy(hostname, optarg, sizeof(hostname)) >=
1123 			    sizeof(hostname))
1124 				errx(1, "-h arg too long");
1125 			break;
1126 		case 's':
1127 			i = strtonum(optarg, 0, 10, &errstr);
1128 			if (errstr)
1129 				usage();
1130 			stutter = i;
1131 			break;
1132 		case 'S':
1133 			i = strtonum(optarg, 0, 90, &errstr);
1134 			if (errstr)
1135 				usage();
1136 			grey_stutter = i;
1137 			break;
1138 		case 'M':
1139 			low_prio_mx_ip = optarg;
1140 			break;
1141 		case 'n':
1142 			spamd = optarg;
1143 			break;
1144 		case 'v':
1145 			verbose = 1;
1146 			break;
1147 		case 'w':
1148 			window = atoi(optarg);
1149 			if (window <= 0)
1150 				usage();
1151 			break;
1152 		case 'Y':
1153 			if (sync_addhost(optarg, sync_port) != 0)
1154 				sync_iface = optarg;
1155 			syncsend++;
1156 			break;
1157 		case 'y':
1158 			sync_baddr = optarg;
1159 			syncrecv++;
1160 			break;
1161 		default:
1162 			usage();
1163 			break;
1164 		}
1165 	}
1166 
1167 	setproctitle("[priv]%s%s",
1168 	    greylist ? " (greylist)" : "",
1169 	    (syncrecv || syncsend) ? " (sync)" : "");
1170 
1171 	if (!greylist)
1172 		maxblack = maxcon;
1173 	else if (maxblack > maxcon)
1174 		usage();
1175 
1176 	rlp.rlim_cur = rlp.rlim_max = maxcon + 15;
1177 	if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
1178 		err(1, "setrlimit");
1179 
1180 	con = calloc(maxcon, sizeof(*con));
1181 	if (con == NULL)
1182 		err(1, "calloc");
1183 
1184 	con->obuf = malloc(8192);
1185 
1186 	if (con->obuf == NULL)
1187 		err(1, "malloc");
1188 	con->osize = 8192;
1189 
1190 	for (i = 0; i < maxcon; i++)
1191 		con[i].fd = -1;
1192 
1193 	signal(SIGPIPE, SIG_IGN);
1194 
1195 	s = socket(AF_INET, SOCK_STREAM, 0);
1196 	if (s == -1)
1197 		err(1, "socket");
1198 
1199 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one,
1200 	    sizeof(one)) == -1)
1201 		return (-1);
1202 
1203 	conflisten = socket(AF_INET, SOCK_STREAM, 0);
1204 	if (conflisten == -1)
1205 		err(1, "socket");
1206 
1207 	if (setsockopt(conflisten, SOL_SOCKET, SO_REUSEADDR, &one,
1208 	    sizeof(one)) == -1)
1209 		return (-1);
1210 
1211 	memset(&sin, 0, sizeof sin);
1212 	sin.sin_len = sizeof(sin);
1213 	if (bind_address) {
1214 		if (inet_pton(AF_INET, bind_address, &sin.sin_addr) != 1)
1215 			err(1, "inet_pton");
1216 	} else
1217 		sin.sin_addr.s_addr = htonl(INADDR_ANY);
1218 	sin.sin_family = AF_INET;
1219 	sin.sin_port = htons(port);
1220 
1221 	if (bind(s, (struct sockaddr *)&sin, sizeof sin) == -1)
1222 		err(1, "bind");
1223 
1224 	memset(&lin, 0, sizeof sin);
1225 	lin.sin_len = sizeof(sin);
1226 	lin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1227 	lin.sin_family = AF_INET;
1228 	lin.sin_port = htons(cfg_port);
1229 
1230 	if (bind(conflisten, (struct sockaddr *)&lin, sizeof lin) == -1)
1231 		err(1, "bind local");
1232 
1233 	if (syncsend || syncrecv) {
1234 		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
1235 		if (syncfd == -1)
1236 			err(1, "sync init");
1237 	}
1238 
1239 	if ((pw = getpwnam("_spamd")) == NULL)
1240 		errx(1, "no such user _spamd");
1241 
1242 	if (debug == 0) {
1243 		if (daemon(1, 1) == -1)
1244 			err(1, "daemon");
1245 	}
1246 
1247 	if (greylist) {
1248 		pfdev = open("/dev/pf", O_RDWR);
1249 		if (pfdev == -1) {
1250 			syslog_r(LOG_ERR, &sdata, "open /dev/pf: %m");
1251 			exit(1);
1252 		}
1253 
1254 		maxblack = (maxblack >= maxcon) ? maxcon - 100 : maxblack;
1255 		if (maxblack < 0)
1256 			maxblack = 0;
1257 
1258 		/* open pipe to talk to greylister */
1259 		if (pipe(greypipe) == -1) {
1260 			syslog(LOG_ERR, "pipe (%m)");
1261 			exit(1);
1262 		}
1263 		/* open pipe to recieve spamtrap configs */
1264 		if (pipe(trappipe) == -1) {
1265 			syslog(LOG_ERR, "pipe (%m)");
1266 			exit(1);
1267 		}
1268 		jail_pid = fork();
1269 		switch (jail_pid) {
1270 		case -1:
1271 			syslog(LOG_ERR, "fork (%m)");
1272 			exit(1);
1273 		case 0:
1274 			/* child - continue */
1275 			signal(SIGPIPE, SIG_IGN);
1276 			grey = fdopen(greypipe[1], "w");
1277 			if (grey == NULL) {
1278 				syslog(LOG_ERR, "fdopen (%m)");
1279 				_exit(1);
1280 			}
1281 			close(greypipe[0]);
1282 			trapfd = trappipe[0];
1283 			trapcfg = fdopen(trappipe[0], "r");
1284 			if (trapcfg == NULL) {
1285 				syslog(LOG_ERR, "fdopen (%m)");
1286 				_exit(1);
1287 			}
1288 			close(trappipe[1]);
1289 			goto jail;
1290 		}
1291 		/* parent - run greylister */
1292 		grey = fdopen(greypipe[0], "r");
1293 		if (grey == NULL) {
1294 			syslog(LOG_ERR, "fdopen (%m)");
1295 			exit(1);
1296 		}
1297 		close(greypipe[1]);
1298 		trapcfg = fdopen(trappipe[1], "w");
1299 		if (trapcfg == NULL) {
1300 			syslog(LOG_ERR, "fdopen (%m)");
1301 			exit(1);
1302 		}
1303 		close(trappipe[0]);
1304 		return (greywatcher());
1305 		/* NOTREACHED */
1306 	}
1307 
1308 jail:
1309 	if (chroot("/var/empty") == -1 || chdir("/") == -1) {
1310 		syslog(LOG_ERR, "cannot chdir to /var/empty.");
1311 		exit(1);
1312 	}
1313 
1314 	if (pw)
1315 		if (setgroups(1, &pw->pw_gid) ||
1316 		    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
1317 		    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
1318 			err(1, "failed to drop privs");
1319 
1320 	if (listen(s, 10) == -1)
1321 		err(1, "listen");
1322 
1323 	if (listen(conflisten, 10) == -1)
1324 		err(1, "listen");
1325 
1326 	if (debug != 0)
1327 		printf("listening for incoming connections.\n");
1328 	syslog_r(LOG_WARNING, &sdata, "listening for incoming connections.");
1329 
1330 	while (1) {
1331 		struct timeval tv, *tvp;
1332 		int max, n;
1333 		int writers;
1334 
1335 		max = MAX(s, conflisten);
1336 		if (syncrecv)
1337 			max = MAX(max, syncfd);
1338 		max = MAX(max, conffd);
1339 		max = MAX(max, trapfd);
1340 
1341 		time(&t);
1342 		for (i = 0; i < maxcon; i++)
1343 			if (con[i].fd != -1)
1344 				max = MAX(max, con[i].fd);
1345 
1346 		if (max > omax) {
1347 			free(fdsr);
1348 			fdsr = NULL;
1349 			free(fdsw);
1350 			fdsw = NULL;
1351 			fdsr = (fd_set *)calloc(howmany(max+1, NFDBITS),
1352 			    sizeof(fd_mask));
1353 			if (fdsr == NULL)
1354 				err(1, "calloc");
1355 			fdsw = (fd_set *)calloc(howmany(max+1, NFDBITS),
1356 			    sizeof(fd_mask));
1357 			if (fdsw == NULL)
1358 				err(1, "calloc");
1359 			omax = max;
1360 		} else {
1361 			memset(fdsr, 0, howmany(max+1, NFDBITS) *
1362 			    sizeof(fd_mask));
1363 			memset(fdsw, 0, howmany(max+1, NFDBITS) *
1364 			    sizeof(fd_mask));
1365 		}
1366 
1367 		writers = 0;
1368 		for (i = 0; i < maxcon; i++) {
1369 			if (con[i].fd != -1 && con[i].r) {
1370 				if (con[i].r + MAXTIME <= t) {
1371 					closecon(&con[i]);
1372 					continue;
1373 				}
1374 				FD_SET(con[i].fd, fdsr);
1375 			}
1376 			if (con[i].fd != -1 && con[i].w) {
1377 				if (con[i].w + MAXTIME <= t) {
1378 					closecon(&con[i]);
1379 					continue;
1380 				}
1381 				if (con[i].w <= t)
1382 					FD_SET(con[i].fd, fdsw);
1383 				writers = 1;
1384 			}
1385 		}
1386 		FD_SET(s, fdsr);
1387 
1388 		/* only one active config conn at a time */
1389 		if (conffd == -1)
1390 			FD_SET(conflisten, fdsr);
1391 		else
1392 			FD_SET(conffd, fdsr);
1393 		if (trapfd != -1)
1394 			FD_SET(trapfd, fdsr);
1395 		if (syncrecv)
1396 			FD_SET(syncfd, fdsr);
1397 
1398 		if (writers == 0) {
1399 			tvp = NULL;
1400 		} else {
1401 			tv.tv_sec = 1;
1402 			tv.tv_usec = 0;
1403 			tvp = &tv;
1404 		}
1405 
1406 		n = select(max+1, fdsr, fdsw, NULL, tvp);
1407 		if (n == -1) {
1408 			if (errno != EINTR)
1409 				err(1, "select");
1410 			continue;
1411 		}
1412 		if (n == 0)
1413 			continue;
1414 
1415 		for (i = 0; i < maxcon; i++) {
1416 			if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsr))
1417 				handler(&con[i]);
1418 			if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsw))
1419 				handlew(&con[i], clients + 5 < maxcon);
1420 		}
1421 		if (FD_ISSET(s, fdsr)) {
1422 			sinlen = sizeof(sin);
1423 			s2 = accept(s, (struct sockaddr *)&sin, &sinlen);
1424 			if (s2 == -1)
1425 				/* accept failed, they may try again */
1426 				continue;
1427 			for (i = 0; i < maxcon; i++)
1428 				if (con[i].fd == -1)
1429 					break;
1430 			if (i == maxcon)
1431 				close(s2);
1432 			else {
1433 				initcon(&con[i], s2, (struct sockaddr *)&sin);
1434 				syslog_r(LOG_INFO, &sdata,
1435 				    "%s: connected (%d/%d)%s%s",
1436 				    con[i].addr, clients, blackcount,
1437 				    ((con[i].lists == NULL) ? "" :
1438 				    ", lists:"),
1439 				    ((con[i].lists == NULL) ? "":
1440 				    con[i].lists));
1441 			}
1442 		}
1443 		if (FD_ISSET(conflisten, fdsr)) {
1444 			sinlen = sizeof(lin);
1445 			conffd = accept(conflisten, (struct sockaddr *)&lin,
1446 			    &sinlen);
1447 			if (conffd == -1)
1448 				/* accept failed, they may try again */
1449 				continue;
1450 			else if (ntohs(lin.sin_port) >= IPPORT_RESERVED) {
1451 				close(conffd);
1452 				conffd = -1;
1453 			}
1454 		}
1455 		if (conffd != -1 && FD_ISSET(conffd, fdsr))
1456 			do_config();
1457 		if (trapfd != -1 && FD_ISSET(trapfd, fdsr))
1458 			read_configline(trapcfg);
1459 		if (syncrecv && FD_ISSET(syncfd, fdsr))
1460 			sync_recv();
1461 	}
1462 	exit(1);
1463 }
1464