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