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