1 /* $OpenBSD: grey.c,v 1.39 2007/03/18 18:38:57 beck Exp $ */
2
3 /*
4 * Copyright (c) 2004-2006 Bob Beck. All rights reserved.
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <sys/ioctl.h>
22 #include <sys/fcntl.h>
23 #include <sys/wait.h>
24 #include <net/if.h>
25 #include <netinet/in.h>
26 #include <net/pfvar.h>
27 #include <arpa/inet.h>
28 #include <ctype.h>
29 #include <db.h>
30 #include <err.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <pwd.h>
34 #include <signal.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <syslog.h>
40 #include <time.h>
41 #include <unistd.h>
42 #include <netdb.h>
43
44 #include "grey.h"
45 #include "sync.h"
46
47 __RCSID("$MirOS: src/libexec/spamd/grey.c,v 1.4 2009/07/01 18:16:01 tg Exp $");
48
49 extern time_t passtime, greyexp, whiteexp, trapexp;
50 extern struct syslog_data sdata;
51 extern struct passwd *pw;
52 extern u_short cfg_port;
53 extern pid_t jail_pid;
54 extern FILE *trapcfg;
55 extern FILE *grey;
56 extern int debug;
57 extern int syncsend;
58
59 /* From netinet/in.h, but only _KERNEL_ gets them. */
60 #define satosin(sa) ((struct sockaddr_in *)(sa))
61 #define satosin6(sa) ((struct sockaddr_in6 *)(sa))
62 int server_lookup4(struct sockaddr_in *, struct sockaddr_in *,
63 struct sockaddr_in *);
64 int server_lookup6(struct sockaddr_in6 *, struct sockaddr_in6 *,
65 struct sockaddr_in6 *);
66
67 size_t whitecount, whitealloc;
68 size_t trapcount, trapalloc;
69 char **whitelist;
70 char **traplist;
71
72 char *traplist_name = "spamd-greytrap";
73 char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
74
75 pid_t db_pid = -1;
76 int pfdev;
77
78 struct db_change {
79 SLIST_ENTRY(db_change) entry;
80 char * key;
81 void * data;
82 size_t dsiz;
83 int act;
84 };
85
86 #define DBC_ADD 1
87 #define DBC_DEL 2
88
89 /* db pending changes list */
90 SLIST_HEAD(, db_change) db_changes = SLIST_HEAD_INITIALIZER(db_changes);
91
92 struct mail_addr {
93 SLIST_ENTRY(mail_addr) entry;
94 bool fullmatch;
95 char addr[MAX_MAIL];
96 };
97
98 /* list of suffixes that must match TO: */
99 SLIST_HEAD(, mail_addr) match_suffix = SLIST_HEAD_INITIALIZER(match_suffix);
100 char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS;
101
102 char *low_prio_mx_ip;
103 time_t startup;
104
105 static char *pargv[11]= {
106 "pfctl", "-p", "/dev/pf", "-q", "-t",
107 "spamd-white", "-T", "replace", "-f" "-", NULL
108 };
109
110 /* If the parent gets a signal, kill off the children and exit */
111 /* ARGSUSED */
112 static void
sig_term_chld(int sig)113 sig_term_chld(int sig)
114 {
115 if (db_pid != -1)
116 kill(db_pid, SIGTERM);
117 if (jail_pid != -1)
118 kill(jail_pid, SIGTERM);
119 _exit(1);
120 }
121
122 /*
123 * Greatly simplified version from spamd_setup.c - only
124 * sends one blacklist to an already open stream. Has no need
125 * to collapse cidr ranges since these are only ever single
126 * host hits.
127 */
128 int
configure_spamd(char ** addrs,int count,FILE * sdc)129 configure_spamd(char **addrs, int count, FILE *sdc)
130 {
131 int i;
132
133 fprintf(sdc, "%s;%s;", traplist_name, traplist_msg);
134 for (i = 0; i < count; i++)
135 fprintf(sdc, "%s/32;", addrs[i]);
136 fprintf(sdc, "\n");
137 if (fflush(sdc) == EOF)
138 syslog_r(LOG_DEBUG, &sdata, "configure_spamd: fflush failed (%m)");
139 return(0);
140 }
141
142
143 /* Stolen from ftp-proxy */
144 int
server_lookup(struct sockaddr * client,struct sockaddr * proxy,struct sockaddr * server)145 server_lookup(struct sockaddr *client, struct sockaddr *proxy,
146 struct sockaddr *server)
147 {
148 if (client->sa_family == AF_INET)
149 return (server_lookup4(satosin(client), satosin(proxy),
150 satosin(server)));
151
152 if (client->sa_family == AF_INET6)
153 return (server_lookup6(satosin6(client), satosin6(proxy),
154 satosin6(server)));
155
156 errno = EPROTONOSUPPORT;
157 return (-1);
158 }
159
160 int
server_lookup4(struct sockaddr_in * client,struct sockaddr_in * proxy,struct sockaddr_in * server)161 server_lookup4(struct sockaddr_in *client, struct sockaddr_in *proxy,
162 struct sockaddr_in *server)
163 {
164 struct pfioc_natlook pnl;
165
166 memset(&pnl, 0, sizeof pnl);
167 pnl.direction = PF_OUT;
168 pnl.af = AF_INET;
169 pnl.proto = IPPROTO_TCP;
170 memcpy(&pnl.saddr.v4, &client->sin_addr.s_addr, sizeof pnl.saddr.v4);
171 memcpy(&pnl.daddr.v4, &proxy->sin_addr.s_addr, sizeof pnl.daddr.v4);
172 pnl.sport = client->sin_port;
173 pnl.dport = proxy->sin_port;
174
175 if (ioctl(pfdev, DIOCNATLOOK, &pnl) == -1)
176 return (-1);
177
178 memset(server, 0, sizeof(struct sockaddr_in));
179 server->sin_len = sizeof(struct sockaddr_in);
180 server->sin_family = AF_INET;
181 memcpy(&server->sin_addr.s_addr, &pnl.rdaddr.v4,
182 sizeof server->sin_addr.s_addr);
183 server->sin_port = pnl.rdport;
184
185 return (0);
186 }
187
188 int
server_lookup6(struct sockaddr_in6 * client,struct sockaddr_in6 * proxy,struct sockaddr_in6 * server)189 server_lookup6(struct sockaddr_in6 *client, struct sockaddr_in6 *proxy,
190 struct sockaddr_in6 *server)
191 {
192 struct pfioc_natlook pnl;
193
194 memset(&pnl, 0, sizeof pnl);
195 pnl.direction = PF_OUT;
196 pnl.af = AF_INET6;
197 pnl.proto = IPPROTO_TCP;
198 memcpy(&pnl.saddr.v6, &client->sin6_addr.s6_addr, sizeof pnl.saddr.v6);
199 memcpy(&pnl.daddr.v6, &proxy->sin6_addr.s6_addr, sizeof pnl.daddr.v6);
200 pnl.sport = client->sin6_port;
201 pnl.dport = proxy->sin6_port;
202
203 if (ioctl(pfdev, DIOCNATLOOK, &pnl) == -1)
204 return (-1);
205
206 memset(server, 0, sizeof(struct sockaddr_in6));
207 server->sin6_len = sizeof(struct sockaddr_in6);
208 server->sin6_family = AF_INET6;
209 memcpy(&server->sin6_addr.s6_addr, &pnl.rdaddr.v6,
210 sizeof server->sin6_addr);
211 server->sin6_port = pnl.rdport;
212
213 return (0);
214 }
215
216 int
configure_pf(char ** addrs,int count)217 configure_pf(char **addrs, int count)
218 {
219 FILE *pf = NULL;
220 int i, pdes[2], status;
221 pid_t pid;
222 char *fdpath;
223 struct sigaction sa;
224
225 sigfillset(&sa.sa_mask);
226 sa.sa_flags = SA_RESTART;
227 sa.sa_handler = sig_term_chld;
228
229 if (debug)
230 fprintf(stderr, "configure_pf - device on fd %d\n", pfdev);
231 if (pfdev < 1 || pfdev > 63)
232 return(-1);
233 if (asprintf(&fdpath, "/dev/fd/%d", pfdev) == -1)
234 return(-1);
235 pargv[2] = fdpath;
236 if (pipe(pdes) != 0) {
237 syslog_r(LOG_INFO, &sdata, "pipe failed (%m)");
238 free(fdpath);
239 fdpath = NULL;
240 return(-1);
241 }
242 signal(SIGCHLD, SIG_DFL);
243 switch (pid = fork()) {
244 case -1:
245 syslog_r(LOG_INFO, &sdata, "fork failed (%m)");
246 free(fdpath);
247 fdpath = NULL;
248 close(pdes[0]);
249 close(pdes[1]);
250 sigaction(SIGCHLD, &sa, NULL);
251 return(-1);
252 case 0:
253 /* child */
254 close(pdes[1]);
255 if (pdes[0] != STDIN_FILENO) {
256 dup2(pdes[0], STDIN_FILENO);
257 close(pdes[0]);
258 }
259 execvp(PATH_PFCTL, pargv);
260 syslog_r(LOG_ERR, &sdata, "can't exec %s:%m", PATH_PFCTL);
261 _exit(1);
262 }
263
264 /* parent */
265 free(fdpath);
266 fdpath = NULL;
267 close(pdes[0]);
268 pf = fdopen(pdes[1], "w");
269 if (pf == NULL) {
270 syslog_r(LOG_INFO, &sdata, "fdopen failed (%m)");
271 close(pdes[1]);
272 sigaction(SIGCHLD, &sa, NULL);
273 return(-1);
274 }
275 for (i = 0; i < count; i++)
276 if (addrs[i] != NULL)
277 fprintf(pf, "%s/32\n", addrs[i]);
278 fclose(pf);
279
280 waitpid(pid, &status, 0);
281 if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
282 syslog_r(LOG_ERR, &sdata, "%s returned status %d", PATH_PFCTL,
283 WEXITSTATUS(status));
284 else if (WIFSIGNALED(status))
285 syslog_r(LOG_ERR, &sdata, "%s died on signal %d", PATH_PFCTL,
286 WTERMSIG(status));
287
288 sigaction(SIGCHLD, &sa, NULL);
289 return(0);
290 }
291
292 char *
dequotetolower(const char * addr)293 dequotetolower(const char *addr)
294 {
295 static char buf[MAX_MAIL];
296 char *cp;
297
298 if (*addr == '<');
299 addr++;
300 (void) strlcpy(buf, addr, sizeof(buf));
301 cp = strrchr(buf, '>');
302 if (cp != NULL && cp[1] == '\0')
303 *cp = '\0';
304 cp = buf;
305 while (*cp != '\0') {
306 *cp = tolower(*cp);
307 cp++;
308 }
309 return(buf);
310 }
311
312 void
readsuffixlists(void)313 readsuffixlists(void)
314 {
315 FILE *fp;
316 char *buf;
317 size_t len;
318 struct mail_addr *m;
319
320 while (!SLIST_EMPTY(&match_suffix))
321 SLIST_REMOVE_HEAD(&match_suffix, entry);
322 if ((fp = fopen(alloweddomains_file, "r")) != NULL) {
323 while ((buf = fgetln(fp, &len))) {
324 if (buf[len-1] == '\n')
325 len--;
326 if ((m = malloc(sizeof(struct mail_addr))) == NULL)
327 goto bad;
328 if ((m->fullmatch = (*buf == '='))) {
329 ++buf;
330 --len;
331 }
332 if ((len + 1) > sizeof(m->addr)) {
333 syslog_r(LOG_ERR, &sdata,
334 "line too long in %s - file ignored",
335 alloweddomains_file);
336 goto bad;
337 }
338 memcpy(m->addr, buf, len);
339 m->addr[len]='\0';
340 syslog_r(LOG_ERR, &sdata, "got suffix %s", m->addr);
341 SLIST_INSERT_HEAD(&match_suffix, m, entry);
342 }
343 }
344 return;
345 bad:
346 while (!SLIST_EMPTY(&match_suffix))
347 SLIST_REMOVE_HEAD(&match_suffix, entry);
348 }
349
350 void
freeaddrlists(void)351 freeaddrlists(void)
352 {
353 int i;
354
355 if (whitelist != NULL)
356 for (i = 0; i < whitecount; i++) {
357 free(whitelist[i]);
358 whitelist[i] = NULL;
359 }
360 whitecount = 0;
361 if (traplist != NULL) {
362 for (i = 0; i < trapcount; i++) {
363 free(traplist[i]);
364 traplist[i] = NULL;
365 }
366 }
367 trapcount = 0;
368 }
369
370 /* validate, then add to list of addrs to whitelist */
371 int
addwhiteaddr(char * addr)372 addwhiteaddr(char *addr)
373 {
374 struct addrinfo hints, *res;
375
376 memset(&hints, 0, sizeof(hints));
377 hints.ai_family = AF_INET; /*for now*/
378 hints.ai_socktype = SOCK_DGRAM; /*dummy*/
379 hints.ai_protocol = IPPROTO_UDP; /*dummy*/
380 hints.ai_flags = AI_NUMERICHOST;
381
382 if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
383 if (whitecount == whitealloc) {
384 char **tmp;
385
386 tmp = realloc(whitelist,
387 (whitealloc + 1024) * sizeof(char *));
388 if (tmp == NULL) {
389 freeaddrinfo(res);
390 return(-1);
391 }
392 whitelist = tmp;
393 whitealloc += 1024;
394 }
395 whitelist[whitecount] = strdup(addr);
396 if (whitelist[whitecount] == NULL) {
397 freeaddrinfo(res);
398 return(-1);
399 }
400 whitecount++;
401 freeaddrinfo(res);
402 } else
403 return(-1);
404 return(0);
405 }
406
407 /* validate, then add to list of addrs to traplist */
408 int
addtrapaddr(char * addr)409 addtrapaddr(char *addr)
410 {
411 struct addrinfo hints, *res;
412
413 memset(&hints, 0, sizeof(hints));
414 hints.ai_family = AF_INET; /*for now*/
415 hints.ai_socktype = SOCK_DGRAM; /*dummy*/
416 hints.ai_protocol = IPPROTO_UDP; /*dummy*/
417 hints.ai_flags = AI_NUMERICHOST;
418
419 if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
420 if (trapcount == trapalloc) {
421 char **tmp;
422
423 tmp = realloc(traplist,
424 (trapalloc + 1024) * sizeof(char *));
425 if (tmp == NULL) {
426 freeaddrinfo(res);
427 return(-1);
428 }
429 traplist = tmp;
430 trapalloc += 1024;
431 }
432 traplist[trapcount] = strdup(addr);
433 if (traplist[trapcount] == NULL) {
434 freeaddrinfo(res);
435 return(-1);
436 }
437 trapcount++;
438 freeaddrinfo(res);
439 } else
440 return(-1);
441 return(0);
442 }
443
444 static int
queue_change(char * key,char * data,size_t dsiz,int act)445 queue_change(char *key, char *data, size_t dsiz, int act)
446 {
447 struct db_change *dbc;
448
449 if ((dbc = malloc(sizeof(*dbc))) == NULL) {
450 syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
451 return(-1);
452 }
453 if ((dbc->key = strdup(key)) == NULL) {
454 syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
455 free(dbc);
456 return(-1);
457 }
458 if ((dbc->data = malloc(dsiz)) == NULL) {
459 syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
460 free(dbc->key);
461 free(dbc);
462 return(-1);
463 }
464 memcpy(dbc->data, data, dsiz);
465 dbc->dsiz = dsiz;
466 dbc->act = act;
467 syslog_r(LOG_DEBUG, &sdata,
468 "queueing %s of %s", ((act == DBC_ADD) ? "add" : "deletion"),
469 dbc->key);
470 SLIST_INSERT_HEAD(&db_changes, dbc, entry);
471 return(0);
472 }
473
474 static int
do_changes(DB * db)475 do_changes(DB *db)
476 {
477 DBT dbk, dbd;
478 struct db_change *dbc;
479 int ret = 0;
480
481 while (!SLIST_EMPTY(&db_changes)) {
482 dbc = SLIST_FIRST(&db_changes);
483 switch (dbc->act) {
484 case DBC_ADD:
485 memset(&dbk, 0, sizeof(dbk));
486 dbk.size = strlen(dbc->key);
487 dbk.data = dbc->key;
488 memset(&dbd, 0, sizeof(dbd));
489 dbd.size = dbc->dsiz;
490 dbd.data = dbc->data;
491 if (db->put(db, &dbk, &dbd, 0)) {
492 db->sync(db, 0);
493 syslog_r(LOG_ERR, &sdata,
494 "can't add %s to spamd db (%m)", dbc->key);
495 ret = -1;
496 }
497 db->sync(db, 0);
498 break;
499 case DBC_DEL:
500 memset(&dbk, 0, sizeof(dbk));
501 dbk.size = strlen(dbc->key);
502 dbk.data = dbc->key;
503 if (db->del(db, &dbk, 0)) {
504 syslog_r(LOG_ERR, &sdata,
505 "can't delete %s from spamd db (%m)",
506 dbc->key);
507 ret = -1;
508 }
509 break;
510 default:
511 syslog_r(LOG_ERR, &sdata, "Unrecognized db change");
512 ret = -1;
513 }
514 free(dbc->key);
515 dbc->key = NULL;
516 free(dbc->data);
517 dbc->data = NULL;
518 dbc->act = 0;
519 dbc->dsiz = 0;
520 SLIST_REMOVE_HEAD(&db_changes, entry);
521
522 }
523 return(ret);
524 }
525
526 int
db_notin(DB * db,char * key)527 db_notin(DB *db, char *key)
528 {
529 int i;
530 DBT dbk, dbd;
531
532 memset(&dbk, 0, sizeof(dbk));
533 dbk.size = strlen(key);
534 dbk.data = key;
535 memset(&dbd, 0,
536 sizeof(dbd));
537 i = db->get(db, &dbk, &dbd, 0);
538 if (i == -1)
539 return (-1);
540 if (i)
541 /* not in the database */
542 return (1);
543 else
544 /* it is in the database */
545 return (0);
546 }
547
548
549 int
greyscan(char * dbname)550 greyscan(char *dbname)
551 {
552 HASHINFO hashinfo;
553 DBT dbk, dbd;
554 DB *db;
555 struct gdata gd;
556 int r;
557 char *a = NULL;
558 size_t asiz = 0;
559 time_t now = time(NULL);
560
561 /* walk db, expire, and whitelist */
562 memset(&hashinfo, 0, sizeof(hashinfo));
563 db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
564 if (db == NULL) {
565 syslog_r(LOG_INFO, &sdata, "dbopen failed (%m)");
566 return(-1);
567 }
568 memset(&dbk, 0, sizeof(dbk));
569 memset(&dbd, 0, sizeof(dbd));
570 for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
571 r = db->seq(db, &dbk, &dbd, R_NEXT)) {
572 if ((dbk.size < 1) || dbd.size != sizeof(struct gdata)) {
573 syslog_r(LOG_ERR, &sdata, "bogus entry in spamd database");
574 goto bad;
575 }
576 if (asiz < dbk.size + 1) {
577 char *tmp;
578
579 tmp = realloc(a, dbk.size * 2);
580 if (tmp == NULL)
581 goto bad;
582 a = tmp;
583 asiz = dbk.size * 2;
584 }
585 memset(a, 0, asiz);
586 memcpy(a, dbk.data, dbk.size);
587 memcpy(&gd, dbd.data, sizeof(gd));
588 if (gd.expire <= now && gd.pcount != -2) {
589 /* get rid of entry */
590 if (queue_change(a, NULL, 0, DBC_DEL) == -1)
591 goto bad;
592 } else if (gd.pcount == -1) {
593 /* this is a greytrap hit */
594 if ((addtrapaddr(a) == -1) &&
595 (queue_change(a, NULL, 0, DBC_DEL) == -1))
596 goto bad;
597 } else if (gd.pcount >= 0 && gd.pass <= now) {
598 int tuple = 0;
599 char *cp;
600
601 /*
602 * add address to whitelist
603 * add an address-keyed entry to db
604 */
605 cp = strchr(a, '\n');
606 if (cp != NULL) {
607 tuple = 1;
608 *cp = '\0';
609 }
610
611 if (addwhiteaddr(a) == -1) {
612 if (cp != NULL)
613 *cp = '\n';
614 if (queue_change(a, NULL, 0, DBC_DEL) == -1)
615 goto bad;
616 }
617
618 if (tuple && db_notin(db, a)) {
619 if (cp != NULL)
620 *cp = '\0';
621 /* re-add entry, keyed only by ip */
622 gd.expire = now + whiteexp;
623 dbd.size = sizeof(gd);
624 dbd.data = &gd;
625 if (queue_change(a, (void *) &gd, sizeof(gd),
626 DBC_ADD) == -1)
627 goto bad;
628 syslog_r(LOG_DEBUG, &sdata,
629 "whitelisting %s in %s", a, dbname);
630 }
631 if (debug)
632 fprintf(stderr, "whitelisted %s\n", a);
633 }
634 }
635 (void) do_changes(db);
636 db->close(db);
637 db = NULL;
638 configure_pf(whitelist, whitecount);
639 if (configure_spamd(traplist, trapcount, trapcfg) == -1)
640 syslog_r(LOG_DEBUG, &sdata, "configure_spamd failed");
641
642 freeaddrlists();
643 free(a);
644 a = NULL;
645 asiz = 0;
646 return(0);
647 bad:
648 (void) do_changes(db);
649 db->close(db);
650 db = NULL;
651 freeaddrlists();
652 free(a);
653 a = NULL;
654 asiz = 0;
655 return(-1);
656 }
657
658 int
trapcheck(DB * db,char * to)659 trapcheck(DB *db, char *to)
660 {
661 int i, j, smatch = 0;
662 DBT dbk, dbd;
663 struct mail_addr *m;
664 char * trap;
665 size_t s;
666
667 trap = dequotetolower(to);
668 if (!SLIST_EMPTY(&match_suffix)) {
669 s = strlen(trap);
670 SLIST_FOREACH(m, &match_suffix, entry) {
671 j = m->fullmatch ? 0 : s - strlen(m->addr);
672 if ((j >= 0) && (strcasecmp(trap+j, m->addr) == 0))
673 smatch = 1;
674 }
675 if (!smatch)
676 /* no suffixes match, so trap it */
677 return (0);
678 }
679 memset(&dbk, 0, sizeof(dbk));
680 dbk.size = strlen(trap);
681 dbk.data = trap;
682 memset(&dbd, 0, sizeof(dbd));
683 i = db->get(db, &dbk, &dbd, 0);
684 if (i == -1)
685 return (-1);
686 if (i)
687 /* didn't exist - so this doesn't match a known spamtrap */
688 return (1);
689 else
690 /* To: address is a spamtrap, so add as a greytrap entry */
691 return (0);
692 }
693
694 int
twupdate(char * dbname,char * what,char * ip,char * source,char * expires)695 twupdate(char *dbname, char *what, char *ip, char *source, char *expires)
696 {
697 /* we got a TRAP or WHITE update from someone else */
698 HASHINFO hashinfo;
699 DBT dbk, dbd;
700 DB *db;
701 struct gdata gd;
702 time_t now, expire;
703 int r, spamtrap;
704
705 now = time(NULL);
706 /* expiry times have to be in the future */
707 expire = strtonum(expires, now, LLONG_MAX, NULL);
708 if (expire == 0)
709 return(-1);
710
711 if (strcmp(what, "TRAP") == 0)
712 spamtrap = 1;
713 else if (strcmp(what, "WHITE") == 0)
714 spamtrap = 0;
715 else
716 return(-1);
717
718 memset(&hashinfo, 0, sizeof(hashinfo));
719 db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
720 if (db == NULL)
721 return(-1);
722
723 memset(&dbk, 0, sizeof(dbk));
724 dbk.size = strlen(ip);
725 dbk.data = ip;
726 memset(&dbd, 0, sizeof(dbd));
727 r = db->get(db, &dbk, &dbd, 0);
728 if (r == -1)
729 goto bad;
730 if (r) {
731 /* new entry */
732 memset(&gd, 0, sizeof(gd));
733 gd.first = now;
734 gd.pcount = spamtrap ? -1 : 0;
735 gd.expire = expire;
736 memset(&dbk, 0, sizeof(dbk));
737 dbk.size = strlen(ip);
738 dbk.data = ip;
739 memset(&dbd, 0, sizeof(dbd));
740 dbd.size = sizeof(gd);
741 dbd.data = &gd;
742 r = db->put(db, &dbk, &dbd, 0);
743 db->sync(db, 0);
744 if (r)
745 goto bad;
746 if (debug)
747 fprintf(stderr, "added %s %s\n",
748 spamtrap ? "trap entry for" : "", ip);
749 syslog_r(LOG_DEBUG, &sdata,
750 "New %s from %s for %s, expires %s", what, source, ip,
751 expires);
752 } else {
753 /* existing entry */
754 if (dbd.size != sizeof(gd)) {
755 /* whatever this is, it doesn't belong */
756 db->del(db, &dbk, 0);
757 db->sync(db, 0);
758 goto bad;
759 }
760 memcpy(&gd, dbd.data, sizeof(gd));
761 if (spamtrap) {
762 gd.pcount = -1;
763 gd.bcount++;
764 } else
765 gd.pcount++;
766 memset(&dbk, 0, sizeof(dbk));
767 dbk.size = strlen(ip);
768 dbk.data = ip;
769 memset(&dbd, 0, sizeof(dbd));
770 dbd.size = sizeof(gd);
771 dbd.data = &gd;
772 r = db->put(db, &dbk, &dbd, 0);
773 db->sync(db, 0);
774 if (r)
775 goto bad;
776 if (debug)
777 fprintf(stderr, "updated %s\n", ip);
778 }
779 db->close(db);
780 return(0);
781 bad:
782 db->close(db);
783 return(-1);
784
785 }
786
787 int
greyupdate(char * dbname,char * helo,char * ip,char * from,char * to,int sync,char * cip)788 greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync,
789 char *cip)
790 {
791 HASHINFO hashinfo;
792 DBT dbk, dbd;
793 DB *db;
794 char *key = NULL;
795 char *lookup;
796 struct gdata gd;
797 time_t now, expire;
798 int r, spamtrap;
799
800 now = time(NULL);
801
802 /* open with lock, find record, update, close, unlock */
803 memset(&hashinfo, 0, sizeof(hashinfo));
804 db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
805 if (db == NULL)
806 return(-1);
807 if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1)
808 goto bad;
809 r = trapcheck(db, to);
810 switch (r) {
811 case 1:
812 /* do not trap */
813 spamtrap = 0;
814 lookup = key;
815 expire = greyexp;
816 break;
817 case 0:
818 /* trap */
819 spamtrap = 1;
820 lookup = ip;
821 expire = trapexp;
822 syslog_r(LOG_DEBUG, &sdata, "Trapping %s for tuple %s", ip,
823 key);
824 break;
825 default:
826 goto bad;
827 break;
828 }
829 memset(&dbk, 0, sizeof(dbk));
830 dbk.size = strlen(lookup);
831 dbk.data = lookup;
832 memset(&dbd, 0, sizeof(dbd));
833 r = db->get(db, &dbk, &dbd, 0);
834 if (r == -1)
835 goto bad;
836 if (r) {
837 /* new entry */
838 if (sync && low_prio_mx_ip &&
839 (strcmp(cip, low_prio_mx_ip) == 0) &&
840 ((startup + 60) < now)) {
841 /* we haven't seen a greylist entry for this tuple,
842 * and yet the connection was to a low priority MX
843 * which we know can't be hit first if the client
844 * is adhering to the RFC's - soo.. kill it!
845 */
846 spamtrap = 1;
847 lookup = ip;
848 expire = trapexp;
849 syslog_r(LOG_DEBUG, &sdata,
850 "Trapping %s for trying %s first for tuple %s",
851 ip, low_prio_mx_ip, key);
852 }
853 memset(&gd, 0, sizeof(gd));
854 gd.first = now;
855 gd.bcount = 1;
856 gd.pcount = spamtrap ? -1 : 0;
857 gd.pass = now + expire;
858 gd.expire = now + expire;
859 memset(&dbk, 0, sizeof(dbk));
860 dbk.size = strlen(lookup);
861 dbk.data = lookup;
862 memset(&dbd, 0, sizeof(dbd));
863 dbd.size = sizeof(gd);
864 dbd.data = &gd;
865 r = db->put(db, &dbk, &dbd, 0);
866 db->sync(db, 0);
867 if (r)
868 goto bad;
869 if (debug)
870 fprintf(stderr, "added %s %s\n",
871 spamtrap ? "greytrap entry for" : "", lookup);
872 } else {
873 /* existing entry */
874 if (dbd.size != sizeof(gd)) {
875 /* whatever this is, it doesn't belong */
876 db->del(db, &dbk, 0);
877 db->sync(db, 0);
878 goto bad;
879 }
880 memcpy(&gd, dbd.data, sizeof(gd));
881 gd.bcount++;
882 gd.pcount = spamtrap ? -1 : 0;
883 if (gd.first + passtime < now)
884 gd.pass = now;
885 memset(&dbk, 0, sizeof(dbk));
886 dbk.size = strlen(lookup);
887 dbk.data = lookup;
888 memset(&dbd, 0, sizeof(dbd));
889 dbd.size = sizeof(gd);
890 dbd.data = &gd;
891 r = db->put(db, &dbk, &dbd, 0);
892 db->sync(db, 0);
893 if (r)
894 goto bad;
895 if (debug)
896 fprintf(stderr, "updated %s\n", lookup);
897 }
898 free(key);
899 key = NULL;
900 db->close(db);
901 db = NULL;
902
903 /* Entry successfully update, sent out sync message */
904 if (syncsend && sync) {
905 if (spamtrap) {
906 syslog_r(LOG_DEBUG, &sdata,
907 "sync_trap %s", ip);
908 sync_trapped(now, now + expire, ip);
909 }
910 else
911 sync_update(now, helo, ip, from, to);
912 }
913 return(0);
914 bad:
915 free(key);
916 key = NULL;
917 db->close(db);
918 db = NULL;
919 return(-1);
920 }
921
922 int
twread(char * buf)923 twread(char *buf)
924 {
925 if ((strncmp(buf, "WHITE:", 6) == 0) ||
926 (strncmp(buf, "TRAP:", 5) == 0)) {
927 char **ap, *argv[5];
928 int argc = 0;
929
930 for (ap = argv;
931 ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL;) {
932 if (**ap != '\0')
933 ap++;
934 argc++;
935 }
936 *ap = NULL;
937 if (argc != 4)
938 return (-1);
939 twupdate(PATH_SPAMD_DB, argv[0], argv[1], argv[2], argv[3]);
940 return (0);
941 } else
942 return (-1);
943 }
944
945 int
greyreader(void)946 greyreader(void)
947 {
948 char cip[32], ip[32], helo[MAX_MAIL], from[MAX_MAIL], to[MAX_MAIL];
949 char *buf;
950 size_t len;
951 int state, sync;
952 struct addrinfo hints, *res;
953
954 memset(&hints, 0, sizeof(hints));
955 hints.ai_family = AF_INET; /*for now*/
956 hints.ai_socktype = SOCK_DGRAM; /*dummy*/
957 hints.ai_protocol = IPPROTO_UDP; /*dummy*/
958 hints.ai_flags = AI_NUMERICHOST;
959
960 state = 0;
961 sync = 1;
962 if (grey == NULL) {
963 syslog_r(LOG_ERR, &sdata, "No greylist pipe stream!\n");
964 exit(1);
965 }
966
967 /* grab trap suffixes */
968 readsuffixlists();
969
970 while ((buf = fgetln(grey, &len))) {
971 if (buf[len - 1] == '\n')
972 buf[len - 1] = '\0';
973 else
974 /* all valid lines end in \n */
975 continue;
976 if (strlen(buf) < 4)
977 continue;
978
979 if (strcmp(buf, "SYNC") == 0) {
980 sync = 0;
981 continue;
982 }
983
984 switch (state) {
985 case 0:
986 if (twread(buf) == 0) {
987 state = 0;
988 break;
989 }
990 if (strncmp(buf, "HE:", 3) != 0) {
991 if (strncmp(buf, "CO:", 3) == 0)
992 strlcpy(cip, buf+3, sizeof(cip));
993 state = 0;
994 break;
995 }
996 strlcpy(helo, buf+3, sizeof(helo));
997 state = 1;
998 break;
999 case 1:
1000 if (strncmp(buf, "IP:", 3) != 0)
1001 break;
1002 strlcpy(ip, buf+3, sizeof(ip));
1003 if (getaddrinfo(ip, NULL, &hints, &res) == 0) {
1004 freeaddrinfo(res);
1005 state = 2;
1006 } else
1007 state = 0;
1008 break;
1009 case 2:
1010 if (strncmp(buf, "FR:", 3) != 0) {
1011 state = 0;
1012 break;
1013 }
1014 strlcpy(from, buf+3, sizeof(from));
1015 state = 3;
1016 break;
1017 case 3:
1018 if (strncmp(buf, "TO:", 3) != 0) {
1019 state = 0;
1020 break;
1021 }
1022 strlcpy(to, buf+3, sizeof(to));
1023 if (debug)
1024 fprintf(stderr,
1025 "Got Grey HELO %s, IP %s from %s to %s\n",
1026 helo, ip, from, to);
1027 greyupdate(PATH_SPAMD_DB, helo, ip, from, to, sync, cip);
1028 sync = 1;
1029 state = 0;
1030 break;
1031 }
1032 }
1033 return (0);
1034 }
1035
1036 void
greyscanner(void)1037 greyscanner(void)
1038 {
1039 for (;;) {
1040 if (greyscan(PATH_SPAMD_DB) == -1)
1041 syslog_r(LOG_NOTICE, &sdata, "scan of %s failed",
1042 PATH_SPAMD_DB);
1043 sleep(DB_SCAN_INTERVAL);
1044 }
1045 /* NOTREACHED */
1046 }
1047
1048 static void
drop_privs(void)1049 drop_privs(void)
1050 {
1051 /*
1052 * lose root, continue as non-root user
1053 */
1054 if (pw) {
1055 setgroups(1, &pw->pw_gid);
1056 setegid(pw->pw_gid);
1057 setgid(pw->pw_gid);
1058 seteuid(pw->pw_uid);
1059 setuid(pw->pw_uid);
1060 }
1061 }
1062
1063 static void
convert_spamd_db(void)1064 convert_spamd_db(void)
1065 {
1066 char sfn[] = "/var/db/spamd.XXXXXXXXX";
1067 int r, fd = -1;
1068 DB *db1, *db2;
1069 BTREEINFO btreeinfo;
1070 HASHINFO hashinfo;
1071 DBT dbk, dbd;
1072
1073 /* try to open the db as a BTREE */
1074 memset(&btreeinfo, 0, sizeof(btreeinfo));
1075 db1 = dbopen(PATH_SPAMD_DB, O_EXLOCK|O_RDWR, 0600, DB_BTREE,
1076 &btreeinfo);
1077 if (db1 == NULL) {
1078 syslog_r(LOG_ERR, &sdata,
1079 "corrupt db in %s, remove and restart", PATH_SPAMD_DB);
1080 exit(1);
1081 }
1082
1083 if ((fd = mkstemp(sfn)) == -1) {
1084 syslog_r(LOG_ERR, &sdata,
1085 "can't convert %s: mkstemp failed (%m)", PATH_SPAMD_DB);
1086 exit(1);
1087 }
1088 memset(&hashinfo, 0, sizeof(hashinfo));
1089 db2 = dbopen(sfn, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
1090 if (db2 == NULL) {
1091 unlink(sfn);
1092 syslog_r(LOG_ERR, &sdata,
1093 "can't convert %s: can't dbopen %s (%m)", PATH_SPAMD_DB,
1094 sfn);
1095 db1->close(db1);
1096 exit(1);
1097 }
1098
1099 memset(&dbk, 0, sizeof(dbk));
1100 memset(&dbd, 0, sizeof(dbd));
1101 for (r = db1->seq(db1, &dbk, &dbd, R_FIRST); !r;
1102 r = db1->seq(db1, &dbk, &dbd, R_NEXT)) {
1103 if (db2->put(db2, &dbk, &dbd, 0)) {
1104 db2->sync(db2, 0);
1105 db2->close(db2);
1106 db1->close(db1);
1107 unlink(sfn);
1108 syslog_r(LOG_ERR, &sdata,
1109 "can't convert %s - remove and restart",
1110 PATH_SPAMD_DB);
1111 exit(1);
1112 }
1113 }
1114 db2->sync(db2, 0);
1115 db2->close(db2);
1116 db1->sync(db1, 0);
1117 db1->close(db1);
1118 rename(sfn, PATH_SPAMD_DB);
1119 close(fd);
1120 /* if we are dropping privs, chown to that user */
1121 if (pw && (chown(PATH_SPAMD_DB, pw->pw_uid, pw->pw_gid) == -1)) {
1122 syslog_r(LOG_ERR, &sdata,
1123 "chown %s failed (%m)", PATH_SPAMD_DB);
1124 exit(1);
1125 }
1126 }
1127
1128 static void
check_spamd_db(void)1129 check_spamd_db(void)
1130 {
1131 HASHINFO hashinfo;
1132 int i = -1;
1133 DB *db;
1134
1135 /* check to see if /var/db/spamd exists, if not, create it */
1136 memset(&hashinfo, 0, sizeof(hashinfo));
1137 db = dbopen(PATH_SPAMD_DB, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
1138
1139 if (db == NULL) {
1140 switch (errno) {
1141 case ENOENT:
1142 i = open(PATH_SPAMD_DB, O_RDWR|O_CREAT, 0644);
1143 if (i == -1) {
1144 syslog_r(LOG_ERR, &sdata,
1145 "create %s failed (%m)", PATH_SPAMD_DB);
1146 exit(1);
1147 }
1148 /* if we are dropping privs, chown to that user */
1149 if (pw && (fchown(i, pw->pw_uid, pw->pw_gid) == -1)) {
1150 syslog_r(LOG_ERR, &sdata,
1151 "chown %s failed (%m)", PATH_SPAMD_DB);
1152 exit(1);
1153 }
1154 close(i);
1155 drop_privs();
1156 return;
1157 break;
1158 case EFTYPE:
1159 /*
1160 * db may be old BTREE instead of HASH, attempt to
1161 * convert.
1162 */
1163 convert_spamd_db();
1164 drop_privs();
1165 return;
1166 break;
1167 default:
1168 syslog_r(LOG_ERR, &sdata, "open of %s failed (%m)",
1169 PATH_SPAMD_DB);
1170 exit(1);
1171 }
1172 }
1173 db->sync(db, 0);
1174 db->close(db);
1175 drop_privs();
1176 }
1177
1178
1179 int
greywatcher(void)1180 greywatcher(void)
1181 {
1182 struct sigaction sa;
1183
1184 check_spamd_db();
1185
1186 startup = time(NULL);
1187 db_pid = fork();
1188 switch (db_pid) {
1189 case -1:
1190 syslog_r(LOG_ERR, &sdata, "fork failed (%m)");
1191 exit(1);
1192 case 0:
1193 /*
1194 * child, talks to jailed spamd over greypipe,
1195 * updates db. has no access to pf.
1196 */
1197 close(pfdev);
1198 setproctitle("(%s update)", PATH_SPAMD_DB);
1199 greyreader();
1200 /* NOTREACHED */
1201 _exit(1);
1202 }
1203
1204
1205 fclose(grey);
1206 /*
1207 * parent, scans db periodically for changes and updates
1208 * pf whitelist table accordingly.
1209 */
1210
1211 sigfillset(&sa.sa_mask);
1212 sa.sa_flags = SA_RESTART;
1213 sa.sa_handler = sig_term_chld;
1214 sigaction(SIGTERM, &sa, NULL);
1215 sigaction(SIGHUP, &sa, NULL);
1216 sigaction(SIGCHLD, &sa, NULL);
1217 sigaction(SIGINT, &sa, NULL);
1218
1219 setproctitle("(pf <spamd-white> update)");
1220 greyscanner();
1221 /* NOTREACHED */
1222 exit(1);
1223 }
1224