Bug Summary

File:src/libexec/spamd/grey.c
Warning:line 557, column 3
Null pointer passed as 1st argument to memory copy function

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.4 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name grey.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/libexec/spamd/obj -resource-dir /usr/local/llvm16/lib/clang/16 -internal-isystem /usr/local/llvm16/lib/clang/16/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/libexec/spamd/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fcf-protection=branch -fno-jump-tables -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/scan/2024-01-11-140451-98009-1 -x c /usr/src/libexec/spamd/grey.c
1/* $OpenBSD: grey.c,v 1.67 2023/03/08 04:43:06 guenther 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/wait.h>
23#include <net/if.h>
24#include <netinet/in.h>
25#include <net/pfvar.h>
26#include <ctype.h>
27#include <db.h>
28#include <errno(*__errno()).h>
29#include <fcntl.h>
30#include <pwd.h>
31#include <signal.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <syslog.h>
36#include <time.h>
37#include <unistd.h>
38#include <netdb.h>
39
40#include "grey.h"
41#include "sync.h"
42
43extern time_t passtime, greyexp, whiteexp, trapexp;
44extern struct syslog_data sdata;
45extern struct passwd *pw;
46extern u_short cfg_port;
47extern pid_t jail_pid;
48extern FILE *trapcfg;
49extern FILE *grey;
50extern int debug;
51extern int syncsend;
52extern int greyback[2];
53
54/* From netinet/in.h, but only _KERNEL_ gets them. */
55#define satosin(sa)((struct sockaddr_in *)(sa)) ((struct sockaddr_in *)(sa))
56#define satosin6(sa)((struct sockaddr_in6 *)(sa)) ((struct sockaddr_in6 *)(sa))
57
58void configure_spamd(char **, u_int, FILE *);
59int configure_pf(char **, int);
60char *dequotetolower(const char *);
61void readsuffixlists(void);
62void freeaddrlists(void);
63int addwhiteaddr(char *);
64int addtrapaddr(char *);
65int db_addrstate(DB *, char *);
66int greyscan(char *);
67int trapcheck(DB *, char *);
68int twupdate(char *, char *, char *, char *, char *);
69int twread(char *);
70int greyreader(void);
71void greyscanner(void);
72
73
74u_int whitecount, whitealloc;
75u_int trapcount, trapalloc;
76char **whitelist;
77char **traplist;
78
79char *traplist_name = "spamd-greytrap";
80char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
81
82pid_t db_pid = -1;
83int pfdev;
84
85struct db_change {
86 SLIST_ENTRY(db_change)struct { struct db_change *sle_next; } entry;
87 char * key;
88 void * data;
89 size_t dsiz;
90 int act;
91};
92
93#define DBC_ADD1 1
94#define DBC_DEL2 2
95
96/* db pending changes list */
97SLIST_HEAD(, db_change)struct { struct db_change *slh_first; } db_changes = SLIST_HEAD_INITIALIZER(db_changes){ ((void *)0) };
98
99struct mail_addr {
100 SLIST_ENTRY(mail_addr)struct { struct mail_addr *sle_next; } entry;
101 char addr[MAX_MAIL1024];
102};
103
104/* list of suffixes that must match TO: */
105SLIST_HEAD(, mail_addr)struct { struct mail_addr *slh_first; } match_suffix = SLIST_HEAD_INITIALIZER(match_suffix){ ((void *)0) };
106char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS"/etc/mail/spamd.alloweddomains";
107
108char *low_prio_mx_ip;
109time_t startup;
110
111static char *pargv[11]= {
112 "pfctl", "-p", "/dev/pf", "-q", "-t",
113 "spamd-white", "-T", "replace", "-f", "-", NULL((void *)0)
114};
115
116/* If the parent gets a signal, kill off the children and exit */
117static void
118sig_term_chld(int sig)
119{
120 if (db_pid != -1)
121 kill(db_pid, SIGTERM15);
122 if (jail_pid != -1)
123 kill(jail_pid, SIGTERM15);
124 _exit(1);
125}
126
127/*
128 * Greatly simplified version from spamd_setup.c - only
129 * sends one blacklist to an already open stream. Has no need
130 * to collapse cidr ranges since these are only ever single
131 * host hits.
132 */
133void
134configure_spamd(char **addrs, u_int count, FILE *sdc)
135{
136 u_int i;
137
138 /* XXX - doesn't support IPV6 yet */
139 fprintf(sdc, "%s;", traplist_name);
140 if (count != 0) {
141 fprintf(sdc, "%s;inet;%u", traplist_msg, count);
142 for (i = 0; i < count; i++)
143 fprintf(sdc, ";%s/32", addrs[i]);
144 }
145 fputc('\n', sdc);
146 if (fflush(sdc) == EOF(-1))
147 syslog_r(LOG_DEBUG7, &sdata, "configure_spamd: fflush failed (%m)");
148}
149
150int
151configure_pf(char **addrs, int count)
152{
153 FILE *pf = NULL((void *)0);
154 int i, pdes[2], status;
155 pid_t pid;
156 char *fdpath;
157 struct sigaction sa;
158
159 sigfillset(&sa.sa_mask);
160 sa.sa_flags = SA_RESTART0x0002;
161 sa.sa_handler__sigaction_u.__sa_handler = sig_term_chld;
162
163 if (debug)
164 fprintf(stderr(&__sF[2]), "configure_pf - device on fd %d\n", pfdev);
165
166 /* Because /dev/fd/ only contains device nodes for 0-63 */
167 if (pfdev < 1 || pfdev > 63)
168 return(-1);
169
170 if (asprintf(&fdpath, "/dev/fd/%d", pfdev) == -1)
171 return(-1);
172 pargv[2] = fdpath;
173 if (pipe(pdes) != 0) {
174 syslog_r(LOG_INFO6, &sdata, "pipe failed (%m)");
175 free(fdpath);
176 fdpath = NULL((void *)0);
177 return(-1);
178 }
179 signal(SIGCHLD20, SIG_DFL(void (*)(int))0);
180 switch (pid = fork()) {
181 case -1:
182 syslog_r(LOG_INFO6, &sdata, "fork failed (%m)");
183 free(fdpath);
184 fdpath = NULL((void *)0);
185 close(pdes[0]);
186 close(pdes[1]);
187 sigaction(SIGCHLD20, &sa, NULL((void *)0));
188 return(-1);
189 case 0:
190 /* child */
191 close(pdes[1]);
192 if (pdes[0] != STDIN_FILENO0) {
193 dup2(pdes[0], STDIN_FILENO0);
194 close(pdes[0]);
195 }
196 execvp(PATH_PFCTL"/sbin/pfctl", pargv);
197 syslog_r(LOG_ERR3, &sdata, "can't exec %s:%m", PATH_PFCTL"/sbin/pfctl");
198 _exit(1);
199 }
200
201 /* parent */
202 free(fdpath);
203 fdpath = NULL((void *)0);
204 close(pdes[0]);
205 pf = fdopen(pdes[1], "w");
206 if (pf == NULL((void *)0)) {
207 syslog_r(LOG_INFO6, &sdata, "fdopen failed (%m)");
208 close(pdes[1]);
209 sigaction(SIGCHLD20, &sa, NULL((void *)0));
210 return(-1);
211 }
212 for (i = 0; i < count; i++)
213 if (addrs[i] != NULL((void *)0))
214 fprintf(pf, "%s/32\n", addrs[i]);
215 fclose(pf);
216
217 waitpid(pid, &status, 0);
218 if (WIFEXITED(status)(((status) & 0177) == 0) && WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff) != 0)
219 syslog_r(LOG_ERR3, &sdata, "%s returned status %d", PATH_PFCTL"/sbin/pfctl",
220 WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff));
221 else if (WIFSIGNALED(status)(((status) & 0177) != 0177 && ((status) & 0177
) != 0)
)
222 syslog_r(LOG_ERR3, &sdata, "%s died on signal %d", PATH_PFCTL"/sbin/pfctl",
223 WTERMSIG(status)(((status) & 0177)));
224
225 sigaction(SIGCHLD20, &sa, NULL((void *)0));
226 return(0);
227}
228
229char *
230dequotetolower(const char *addr)
231{
232 static char buf[MAX_MAIL1024];
233 char *cp;
234
235 if (*addr == '<')
236 addr++;
237 (void) strlcpy(buf, addr, sizeof(buf));
238 cp = strrchr(buf, '>');
239 if (cp != NULL((void *)0) && cp[1] == '\0')
240 *cp = '\0';
241 cp = buf;
242 while (*cp != '\0') {
243 *cp = tolower((unsigned char)*cp);
244 cp++;
245 }
246 return(buf);
247}
248
249void
250readsuffixlists(void)
251{
252 FILE *fp;
253 char *buf;
254 size_t len;
255 struct mail_addr *m;
256
257 while (!SLIST_EMPTY(&match_suffix)(((&match_suffix)->slh_first) == ((void *)0))) {
258 m = SLIST_FIRST(&match_suffix)((&match_suffix)->slh_first);
259 SLIST_REMOVE_HEAD(&match_suffix, entry)do { (&match_suffix)->slh_first = (&match_suffix)->
slh_first->entry.sle_next; } while (0)
;
260 free(m);
261 }
262 if ((fp = fopen(alloweddomains_file, "r")) != NULL((void *)0)) {
263 while ((buf = fgetln(fp, &len))) {
264 /* strip white space-characters */
265 while (len > 0 && isspace((unsigned char)buf[len-1]))
266 len--;
267 while (len > 0 && isspace((unsigned char)*buf)) {
268 buf++;
269 len--;
270 }
271 if (len == 0)
272 continue;
273 /* jump over comments and blank lines */
274 if (*buf == '#' || *buf == '\n')
275 continue;
276 if (buf[len-1] == '\n')
277 len--;
278 if ((len + 1) > sizeof(m->addr)) {
279 syslog_r(LOG_ERR3, &sdata,
280 "line too long in %s - file ignored",
281 alloweddomains_file);
282 goto bad;
283 }
284 if ((m = malloc(sizeof(struct mail_addr))) == NULL((void *)0))
285 goto bad;
286 memcpy(m->addr, buf, len);
287 m->addr[len]='\0';
288 syslog_r(LOG_ERR3, &sdata, "got suffix %s", m->addr);
289 SLIST_INSERT_HEAD(&match_suffix, m, entry)do { (m)->entry.sle_next = (&match_suffix)->slh_first
; (&match_suffix)->slh_first = (m); } while (0)
;
290 }
291 }
292 return;
293bad:
294 while (!SLIST_EMPTY(&match_suffix)(((&match_suffix)->slh_first) == ((void *)0))) {
295 m = SLIST_FIRST(&match_suffix)((&match_suffix)->slh_first);
296 SLIST_REMOVE_HEAD(&match_suffix, entry)do { (&match_suffix)->slh_first = (&match_suffix)->
slh_first->entry.sle_next; } while (0)
;
297 free(m);
298 }
299}
300
301void
302freeaddrlists(void)
303{
304 int i;
305
306 if (whitelist != NULL((void *)0))
307 for (i = 0; i < whitecount; i++) {
308 free(whitelist[i]);
309 whitelist[i] = NULL((void *)0);
310 }
311 whitecount = 0;
312 if (traplist != NULL((void *)0)) {
313 for (i = 0; i < trapcount; i++) {
314 free(traplist[i]);
315 traplist[i] = NULL((void *)0);
316 }
317 }
318 trapcount = 0;
319}
320
321/* validate, then add to list of addrs to whitelist */
322int
323addwhiteaddr(char *addr)
324{
325 struct addrinfo hints, *res;
326 char ch;
327
328 memset(&hints, 0, sizeof(hints));
329 hints.ai_family = AF_INET2; /*for now*/
330 hints.ai_socktype = SOCK_DGRAM2; /*dummy*/
331 hints.ai_protocol = IPPROTO_UDP17; /*dummy*/
332 hints.ai_flags = AI_NUMERICHOST4;
333
334 if (getaddrinfo(addr, NULL((void *)0), &hints, &res) != 0)
335 return(-1);
336
337 /* Check spamd blacklists in main process. */
338 if (send(greyback[0], res->ai_addr, res->ai_addr->sa_len, 0) == -1) {
339 syslog_r(LOG_ERR3, &sdata, "%s: send: %m", __func__);
340 } else {
341 if (recv(greyback[0], &ch, sizeof(ch), 0) == 1) {
342 if (ch == '1') {
343 syslog_r(LOG_DEBUG7, &sdata,
344 "%s blacklisted, removing from whitelist",
345 addr);
346 freeaddrinfo(res);
347 return(-1);
348 }
349 }
350 }
351
352 if (whitecount == whitealloc) {
353 char **tmp;
354
355 tmp = reallocarray(whitelist,
356 whitealloc + 1024, sizeof(char *));
357 if (tmp == NULL((void *)0)) {
358 freeaddrinfo(res);
359 return(-1);
360 }
361 whitelist = tmp;
362 whitealloc += 1024;
363 }
364 whitelist[whitecount] = strdup(addr);
365 if (whitelist[whitecount] == NULL((void *)0)) {
366 freeaddrinfo(res);
367 return(-1);
368 }
369 whitecount++;
370 freeaddrinfo(res);
371 return(0);
372}
373
374/* validate, then add to list of addrs to traplist */
375int
376addtrapaddr(char *addr)
377{
378 struct addrinfo hints, *res;
379
380 memset(&hints, 0, sizeof(hints));
381 hints.ai_family = AF_INET2; /*for now*/
382 hints.ai_socktype = SOCK_DGRAM2; /*dummy*/
383 hints.ai_protocol = IPPROTO_UDP17; /*dummy*/
384 hints.ai_flags = AI_NUMERICHOST4;
385
386 if (getaddrinfo(addr, NULL((void *)0), &hints, &res) == 0) {
387 if (trapcount == trapalloc) {
388 char **tmp;
389
390 tmp = reallocarray(traplist,
391 trapalloc + 1024, sizeof(char *));
392 if (tmp == NULL((void *)0)) {
393 freeaddrinfo(res);
394 return(-1);
395 }
396 traplist = tmp;
397 trapalloc += 1024;
398 }
399 traplist[trapcount] = strdup(addr);
400 if (traplist[trapcount] == NULL((void *)0)) {
401 freeaddrinfo(res);
402 return(-1);
403 }
404 trapcount++;
405 freeaddrinfo(res);
406 } else
407 return(-1);
408 return(0);
409}
410
411static int
412queue_change(char *key, char *data, size_t dsiz, int act)
413{
414 struct db_change *dbc;
415
416 if ((dbc = malloc(sizeof(*dbc))) == NULL((void *)0)) {
417 syslog_r(LOG_DEBUG7, &sdata, "malloc failed (queue change)");
418 return(-1);
419 }
420 if ((dbc->key = strdup(key)) == NULL((void *)0)) {
421 syslog_r(LOG_DEBUG7, &sdata, "malloc failed (queue change)");
422 free(dbc);
423 return(-1);
424 }
425 if ((dbc->data = malloc(dsiz)) == NULL((void *)0)) {
426 syslog_r(LOG_DEBUG7, &sdata, "malloc failed (queue change)");
427 free(dbc->key);
428 free(dbc);
429 return(-1);
430 }
431 memcpy(dbc->data, data, dsiz);
432 dbc->dsiz = dsiz;
433 dbc->act = act;
434 syslog_r(LOG_DEBUG7, &sdata,
435 "queueing %s of %s", ((act == DBC_ADD1) ? "add" : "deletion"),
436 dbc->key);
437 SLIST_INSERT_HEAD(&db_changes, dbc, entry)do { (dbc)->entry.sle_next = (&db_changes)->slh_first
; (&db_changes)->slh_first = (dbc); } while (0)
;
438 return(0);
439}
440
441static int
442do_changes(DB *db)
443{
444 DBT dbk, dbd;
445 struct db_change *dbc;
446 int ret = 0;
447
448 while (!SLIST_EMPTY(&db_changes)(((&db_changes)->slh_first) == ((void *)0))) {
449 dbc = SLIST_FIRST(&db_changes)((&db_changes)->slh_first);
450 switch (dbc->act) {
451 case DBC_ADD1:
452 memset(&dbk, 0, sizeof(dbk));
453 dbk.size = strlen(dbc->key);
454 dbk.data = dbc->key;
455 memset(&dbd, 0, sizeof(dbd));
456 dbd.size = dbc->dsiz;
457 dbd.data = dbc->data;
458 if (db->put(db, &dbk, &dbd, 0)) {
459 db->sync(db, 0);
460 syslog_r(LOG_ERR3, &sdata,
461 "can't add %s to spamd db (%m)", dbc->key);
462 ret = -1;
463 }
464 db->sync(db, 0);
465 break;
466 case DBC_DEL2:
467 memset(&dbk, 0, sizeof(dbk));
468 dbk.size = strlen(dbc->key);
469 dbk.data = dbc->key;
470 if (db->del(db, &dbk, 0)) {
471 syslog_r(LOG_ERR3, &sdata,
472 "can't delete %s from spamd db (%m)",
473 dbc->key);
474 ret = -1;
475 }
476 break;
477 default:
478 syslog_r(LOG_ERR3, &sdata, "Unrecognized db change");
479 ret = -1;
480 }
481 free(dbc->key);
482 dbc->key = NULL((void *)0);
483 free(dbc->data);
484 dbc->data = NULL((void *)0);
485 dbc->act = 0;
486 dbc->dsiz = 0;
487 SLIST_REMOVE_HEAD(&db_changes, entry)do { (&db_changes)->slh_first = (&db_changes)->
slh_first->entry.sle_next; } while (0)
;
488 free(dbc);
489
490 }
491 return(ret);
492}
493
494/* -1=error, 0=notfound, 1=TRAPPED, 2=WHITE */
495int
496db_addrstate(DB *db, char *key)
497{
498 DBT dbk, dbd;
499 struct gdata gd;
500
501 memset(&dbk, 0, sizeof(dbk));
502 dbk.size = strlen(key);
503 dbk.data = key;
504 memset(&dbd, 0, sizeof(dbd));
505 switch (db->get(db, &dbk, &dbd, 0)) {
506 case 1:
507 /* not found */
508 return (0);
509 case 0:
510 if (gdcopyin(&dbd, &gd) != -1)
511 return (gd.pcount == -1 ? 1 : 2);
512 /* FALLTHROUGH */
513 default:
514 /* error */
515 return (-1);
516 }
517}
518
519
520int
521greyscan(char *dbname)
522{
523 HASHINFO hashinfo;
524 DBT dbk, dbd;
525 DB *db;
526 struct gdata gd;
527 int r;
528 char *a = NULL((void *)0);
12
'a' initialized to a null pointer value
529 size_t asiz = 0;
530 time_t now = time(NULL((void *)0));
531
532 /* walk db, expire, and whitelist */
533 memset(&hashinfo, 0, sizeof(hashinfo));
534 db = dbopen(dbname, O_EXLOCK0x0020|O_RDWR0x0002, 0600, DB_HASH, &hashinfo);
535 if (db == NULL((void *)0)) {
13
Assuming 'db' is not equal to NULL
14
Taking false branch
536 syslog_r(LOG_INFO6, &sdata, "dbopen failed (%m)");
537 return(-1);
538 }
539 memset(&dbk, 0, sizeof(dbk));
540 memset(&dbd, 0, sizeof(dbd));
541 for (r = db->seq(db, &dbk, &dbd, R_FIRST3); !r;
15
Assuming 'r' is 0
542 r = db->seq(db, &dbk, &dbd, R_NEXT7)) {
543 if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
16
Assuming field 'size' is >= 1
17
Assuming the condition is false
18
Taking false branch
544 syslog_r(LOG_ERR3, &sdata, "bogus entry in spamd database");
545 goto bad;
546 }
547 if (asiz < dbk.size + 1) {
19
Assuming the condition is false
20
Taking false branch
548 char *tmp;
549
550 tmp = reallocarray(a, dbk.size, 2);
551 if (tmp == NULL((void *)0))
552 goto bad;
553 a = tmp;
554 asiz = dbk.size * 2;
555 }
556 memset(a, 0, asiz);
557 memcpy(a, dbk.data, dbk.size);
21
Null pointer passed as 1st argument to memory copy function
558 if (gd.expire <= now && gd.pcount != -2) {
559 /* get rid of entry */
560 if (queue_change(a, NULL((void *)0), 0, DBC_DEL2) == -1)
561 goto bad;
562 } else if (gd.pcount == -1) {
563 /* this is a greytrap hit */
564 if ((addtrapaddr(a) == -1) &&
565 (queue_change(a, NULL((void *)0), 0, DBC_DEL2) == -1))
566 goto bad;
567 } else if (gd.pcount >= 0 && gd.pass <= now) {
568 int tuple = 0;
569 char *cp;
570 int state;
571
572 /*
573 * if not already TRAPPED,
574 * add address to whitelist
575 * add an address-keyed entry to db
576 */
577 cp = strchr(a, '\n');
578 if (cp != NULL((void *)0)) {
579 tuple = 1;
580 *cp = '\0';
581 }
582
583 state = db_addrstate(db, a);
584 if (state != 1 && addwhiteaddr(a) == -1) {
585 if (cp != NULL((void *)0))
586 *cp = '\n';
587 if (queue_change(a, NULL((void *)0), 0, DBC_DEL2) == -1)
588 goto bad;
589 }
590
591 if (tuple && state <= 0) {
592 if (cp != NULL((void *)0))
593 *cp = '\0';
594 /* re-add entry, keyed only by ip */
595 gd.expire = now + whiteexp;
596 dbd.size = sizeof(gd);
597 dbd.data = &gd;
598 if (queue_change(a, (void *) &gd, sizeof(gd),
599 DBC_ADD1) == -1)
600 goto bad;
601 syslog_r(LOG_DEBUG7, &sdata,
602 "whitelisting %s in %s", a, dbname);
603 }
604 if (debug)
605 fprintf(stderr(&__sF[2]), "whitelisted %s\n", a);
606 }
607 }
608 (void) do_changes(db);
609 db->close(db);
610 db = NULL((void *)0);
611 configure_pf(whitelist, whitecount);
612 configure_spamd(traplist, trapcount, trapcfg);
613
614 freeaddrlists();
615 free(a);
616 a = NULL((void *)0);
617 return(0);
618 bad:
619 (void) do_changes(db);
620 db->close(db);
621 db = NULL((void *)0);
622 freeaddrlists();
623 free(a);
624 a = NULL((void *)0);
625 return(-1);
626}
627
628int
629trapcheck(DB *db, char *to)
630{
631 int i, j, smatch = 0;
632 DBT dbk, dbd;
633 struct mail_addr *m;
634 char * trap;
635 size_t s;
636
637 trap = dequotetolower(to);
638 if (!SLIST_EMPTY(&match_suffix)(((&match_suffix)->slh_first) == ((void *)0))) {
639 s = strlen(trap);
640 SLIST_FOREACH(m, &match_suffix, entry)for((m) = ((&match_suffix)->slh_first); (m) != ((void *
)0); (m) = ((m)->entry.sle_next))
{
641 j = s - strlen(m->addr);
642 if ((j >= 0) && (strcasecmp(trap+j, m->addr) == 0))
643 smatch = 1;
644 }
645 if (!smatch)
646 /* no suffixes match, so trap it */
647 return (0);
648 }
649 memset(&dbk, 0, sizeof(dbk));
650 dbk.size = strlen(trap);
651 dbk.data = trap;
652 memset(&dbd, 0, sizeof(dbd));
653 i = db->get(db, &dbk, &dbd, 0);
654 if (i == -1)
655 return (-1);
656 if (i)
657 /* didn't exist - so this doesn't match a known spamtrap */
658 return (1);
659 else
660 /* To: address is a spamtrap, so add as a greytrap entry */
661 return (0);
662}
663
664int
665twupdate(char *dbname, char *what, char *ip, char *source, char *expires)
666{
667 /* we got a TRAP or WHITE update from someone else */
668 HASHINFO hashinfo;
669 DBT dbk, dbd;
670 DB *db;
671 struct gdata gd;
672 time_t now, expire;
673 int r, spamtrap;
674
675 now = time(NULL((void *)0));
676 /* expiry times have to be in the future */
677 expire = strtonum(expires, now,
678 sizeof(time_t) == sizeof(int) ? INT_MAX0x7fffffff : LLONG_MAX0x7fffffffffffffffLL, NULL((void *)0));
679 if (expire == 0)
680 return(-1);
681
682 if (strcmp(what, "TRAP") == 0)
683 spamtrap = 1;
684 else if (strcmp(what, "WHITE") == 0)
685 spamtrap = 0;
686 else
687 return(-1);
688
689 memset(&hashinfo, 0, sizeof(hashinfo));
690 db = dbopen(dbname, O_EXLOCK0x0020|O_RDWR0x0002, 0600, DB_HASH, &hashinfo);
691 if (db == NULL((void *)0))
692 return(-1);
693
694 memset(&dbk, 0, sizeof(dbk));
695 dbk.size = strlen(ip);
696 dbk.data = ip;
697 memset(&dbd, 0, sizeof(dbd));
698 r = db->get(db, &dbk, &dbd, 0);
699 if (r == -1)
700 goto bad;
701 if (r) {
702 /* new entry */
703 memset(&gd, 0, sizeof(gd));
704 gd.first = now;
705 gd.pcount = spamtrap ? -1 : 0;
706 gd.expire = expire;
707 memset(&dbk, 0, sizeof(dbk));
708 dbk.size = strlen(ip);
709 dbk.data = ip;
710 memset(&dbd, 0, sizeof(dbd));
711 dbd.size = sizeof(gd);
712 dbd.data = &gd;
713 r = db->put(db, &dbk, &dbd, 0);
714 db->sync(db, 0);
715 if (r)
716 goto bad;
717 if (debug)
718 fprintf(stderr(&__sF[2]), "added %s %s\n",
719 spamtrap ? "trap entry for" : "", ip);
720 syslog_r(LOG_DEBUG7, &sdata,
721 "new %s from %s for %s, expires %s", what, source, ip,
722 expires);
723 } else {
724 /* existing entry */
725 if (gdcopyin(&dbd, &gd) == -1) {
726 /* whatever this is, it doesn't belong */
727 db->del(db, &dbk, 0);
728 db->sync(db, 0);
729 goto bad;
730 }
731 if (spamtrap) {
732 gd.pcount = -1;
733 gd.bcount++;
734 } else
735 gd.pcount++;
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(&__sF[2]), "updated %s\n", ip);
748 }
749 db->close(db);
750 return(0);
751 bad:
752 db->close(db);
753 return(-1);
754
755}
756
757int
758greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync,
759 char *cip)
760{
761 HASHINFO hashinfo;
762 DBT dbk, dbd;
763 DB *db;
764 char *key = NULL((void *)0);
765 char *lookup;
766 struct gdata gd;
767 time_t now, expire;
768 int r, spamtrap;
769
770 now = time(NULL((void *)0));
771
772 /* open with lock, find record, update, close, unlock */
773 memset(&hashinfo, 0, sizeof(hashinfo));
774 db = dbopen(dbname, O_EXLOCK0x0020|O_RDWR0x0002, 0600, DB_HASH, &hashinfo);
775 if (db == NULL((void *)0))
776 return(-1);
777 if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1)
778 goto bad;
779 r = trapcheck(db, to);
780 switch (r) {
781 case 1:
782 /* do not trap */
783 spamtrap = 0;
784 lookup = key;
785 expire = greyexp;
786 break;
787 case 0:
788 /* trap */
789 spamtrap = 1;
790 lookup = ip;
791 expire = trapexp;
792 syslog_r(LOG_DEBUG7, &sdata, "Trapping %s for tuple %s", ip,
793 key);
794 break;
795 default:
796 goto bad;
797 break;
798 }
799 memset(&dbk, 0, sizeof(dbk));
800 dbk.size = strlen(lookup);
801 dbk.data = lookup;
802 memset(&dbd, 0, sizeof(dbd));
803 r = db->get(db, &dbk, &dbd, 0);
804 if (r == -1)
805 goto bad;
806 if (r) {
807 /* new entry */
808 if (sync && low_prio_mx_ip &&
809 (strcmp(cip, low_prio_mx_ip) == 0) &&
810 ((startup + 60) < now)) {
811 /* we haven't seen a greylist entry for this tuple,
812 * and yet the connection was to a low priority MX
813 * which we know can't be hit first if the client
814 * is adhering to the RFC's - soo.. kill it!
815 */
816 spamtrap = 1;
817 lookup = ip;
818 expire = trapexp;
819 syslog_r(LOG_DEBUG7, &sdata,
820 "Trapping %s for trying %s first for tuple %s",
821 ip, low_prio_mx_ip, key);
822 }
823 memset(&gd, 0, sizeof(gd));
824 gd.first = now;
825 gd.bcount = 1;
826 gd.pcount = spamtrap ? -1 : 0;
827 gd.pass = now + expire;
828 gd.expire = now + expire;
829 memset(&dbk, 0, sizeof(dbk));
830 dbk.size = strlen(lookup);
831 dbk.data = lookup;
832 memset(&dbd, 0, sizeof(dbd));
833 dbd.size = sizeof(gd);
834 dbd.data = &gd;
835 r = db->put(db, &dbk, &dbd, 0);
836 db->sync(db, 0);
837 if (r)
838 goto bad;
839 if (debug)
840 fprintf(stderr(&__sF[2]), "added %s %s\n",
841 spamtrap ? "greytrap entry for" : "", lookup);
842 syslog_r(LOG_DEBUG7, &sdata,
843 "new %sentry %s from %s to %s, helo %s",
844 spamtrap ? "greytrap " : "", ip, from, to, helo);
845 } else {
846 /* existing entry */
847 if (gdcopyin(&dbd, &gd) == -1) {
848 /* whatever this is, it doesn't belong */
849 db->del(db, &dbk, 0);
850 db->sync(db, 0);
851 goto bad;
852 }
853 gd.bcount++;
854 gd.pcount = spamtrap ? -1 : 0;
855 if (gd.first + passtime < now)
856 gd.pass = now;
857 memset(&dbk, 0, sizeof(dbk));
858 dbk.size = strlen(lookup);
859 dbk.data = lookup;
860 memset(&dbd, 0, sizeof(dbd));
861 dbd.size = sizeof(gd);
862 dbd.data = &gd;
863 r = db->put(db, &dbk, &dbd, 0);
864 db->sync(db, 0);
865 if (r)
866 goto bad;
867 if (debug)
868 fprintf(stderr(&__sF[2]), "updated %s\n", lookup);
869 }
870 free(key);
871 key = NULL((void *)0);
872 db->close(db);
873 db = NULL((void *)0);
874
875 /* Entry successfully update, sent out sync message */
876 if (syncsend && sync) {
877 if (spamtrap) {
878 syslog_r(LOG_DEBUG7, &sdata,
879 "sync_trap %s", ip);
880 sync_trapped(now, now + expire, ip);
881 }
882 else
883 sync_update(now, helo, ip, from, to);
884 }
885 return(0);
886 bad:
887 free(key);
888 key = NULL((void *)0);
889 db->close(db);
890 db = NULL((void *)0);
891 return(-1);
892}
893
894int
895twread(char *buf)
896{
897 if ((strncmp(buf, "WHITE:", 6) == 0) ||
898 (strncmp(buf, "TRAP:", 5) == 0)) {
899 char **ap, *argv[5];
900 int argc = 0;
901
902 for (ap = argv;
903 ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL((void *)0);) {
904 if (**ap != '\0')
905 ap++;
906 argc++;
907 }
908 *ap = NULL((void *)0);
909 if (argc != 4)
910 return (-1);
911 twupdate(PATH_SPAMD_DB"/var/db/spamd", argv[0], argv[1], argv[2], argv[3]);
912 return (0);
913 } else
914 return (-1);
915}
916
917int
918greyreader(void)
919{
920 char cip[32], ip[32], helo[MAX_MAIL1024], from[MAX_MAIL1024], to[MAX_MAIL1024];
921 char *buf;
922 size_t len;
923 int state, sync;
924 struct addrinfo hints, *res;
925
926 memset(&hints, 0, sizeof(hints));
927 hints.ai_family = AF_INET2; /*for now*/
928 hints.ai_socktype = SOCK_DGRAM2; /*dummy*/
929 hints.ai_protocol = IPPROTO_UDP17; /*dummy*/
930 hints.ai_flags = AI_NUMERICHOST4;
931
932 state = 0;
933 sync = 1;
934 if (grey == NULL((void *)0)) {
935 syslog_r(LOG_ERR3, &sdata, "No greylist pipe stream!\n");
936 return (-1);
937 }
938
939 /* grab trap suffixes */
940 readsuffixlists();
941
942 while ((buf = fgetln(grey, &len))) {
943 if (buf[len - 1] == '\n')
944 buf[len - 1] = '\0';
945 else
946 /* all valid lines end in \n */
947 continue;
948 if (strlen(buf) < 4)
949 continue;
950
951 if (strcmp(buf, "SYNC") == 0) {
952 sync = 0;
953 continue;
954 }
955
956 switch (state) {
957 case 0:
958 if (twread(buf) == 0) {
959 state = 0;
960 break;
961 }
962 if (strncmp(buf, "HE:", 3) != 0) {
963 if (strncmp(buf, "CO:", 3) == 0)
964 strlcpy(cip, buf+3, sizeof(cip));
965 state = 0;
966 break;
967 }
968 strlcpy(helo, buf+3, sizeof(helo));
969 state = 1;
970 break;
971 case 1:
972 if (strncmp(buf, "IP:", 3) != 0)
973 break;
974 strlcpy(ip, buf+3, sizeof(ip));
975 if (getaddrinfo(ip, NULL((void *)0), &hints, &res) == 0) {
976 freeaddrinfo(res);
977 state = 2;
978 } else
979 state = 0;
980 break;
981 case 2:
982 if (strncmp(buf, "FR:", 3) != 0) {
983 state = 0;
984 break;
985 }
986 strlcpy(from, buf+3, sizeof(from));
987 state = 3;
988 break;
989 case 3:
990 if (strncmp(buf, "TO:", 3) != 0) {
991 state = 0;
992 break;
993 }
994 strlcpy(to, buf+3, sizeof(to));
995 if (debug)
996 fprintf(stderr(&__sF[2]),
997 "Got Grey HELO %s, IP %s from %s to %s\n",
998 helo, ip, from, to);
999 greyupdate(PATH_SPAMD_DB"/var/db/spamd", helo, ip, from, to, sync, cip);
1000 sync = 1;
1001 state = 0;
1002 break;
1003 }
1004 }
1005 return (0);
1006}
1007
1008void
1009greyscanner(void)
1010{
1011 for (;;) {
10
Loop condition is true. Entering loop body
1012 if (greyscan(PATH_SPAMD_DB"/var/db/spamd") == -1)
11
Calling 'greyscan'
1013 syslog_r(LOG_NOTICE5, &sdata, "scan of %s failed",
1014 PATH_SPAMD_DB"/var/db/spamd");
1015 sleep(DB_SCAN_INTERVAL60);
1016 }
1017}
1018
1019static void
1020drop_privs(void)
1021{
1022 /*
1023 * lose root, continue as non-root user
1024 */
1025 if (setgroups(1, &pw->pw_gid) ||
1026 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
1027 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) {
1028 syslog_r(LOG_ERR3, &sdata, "failed to drop privs (%m)");
1029 exit(1);
1030 }
1031}
1032
1033void
1034check_spamd_db(void)
1035{
1036 HASHINFO hashinfo;
1037 int i = -1;
1038 DB *db;
1039
1040 /* check to see if /var/db/spamd exists, if not, create it */
1041 memset(&hashinfo, 0, sizeof(hashinfo));
1042 db = dbopen(PATH_SPAMD_DB"/var/db/spamd", O_EXLOCK0x0020|O_RDWR0x0002, 0600, DB_HASH, &hashinfo);
1043
1044 if (db == NULL((void *)0)) {
1045 switch (errno(*__errno())) {
1046 case ENOENT2:
1047 i = open(PATH_SPAMD_DB"/var/db/spamd", O_RDWR0x0002|O_CREAT0x0200, 0644);
1048 if (i == -1) {
1049 syslog_r(LOG_ERR3, &sdata,
1050 "create %s failed (%m)", PATH_SPAMD_DB"/var/db/spamd");
1051 exit(1);
1052 }
1053 /* if we are dropping privs, chown to that user */
1054 if (pw && (fchown(i, pw->pw_uid, pw->pw_gid) == -1)) {
1055 syslog_r(LOG_ERR3, &sdata,
1056 "chown %s failed (%m)", PATH_SPAMD_DB"/var/db/spamd");
1057 exit(1);
1058 }
1059 close(i);
1060 return;
1061 break;
1062 default:
1063 syslog_r(LOG_ERR3, &sdata, "open of %s failed (%m)",
1064 PATH_SPAMD_DB"/var/db/spamd");
1065 exit(1);
1066 }
1067 }
1068 db->sync(db, 0);
1069 db->close(db);
1070}
1071
1072
1073int
1074greywatcher(void)
1075{
1076 struct sigaction sa;
1077
1078 drop_privs();
1079
1080 if (unveil(PATH_SPAMD_DB"/var/db/spamd", "rw") == -1) {
1
Assuming the condition is false
2
Taking false branch
1081 syslog_r(LOG_ERR3, &sdata, "unveil failed (%m)");
1082 exit(1);
1083 }
1084 if (unveil(alloweddomains_file, "r") == -1) {
3
Assuming the condition is false
4
Taking false branch
1085 syslog_r(LOG_ERR3, &sdata, "unveil failed (%m)");
1086 exit(1);
1087 }
1088 if (unveil(PATH_PFCTL"/sbin/pfctl", "x") == -1) {
5
Assuming the condition is false
6
Taking false branch
1089 syslog_r(LOG_ERR3, &sdata, "unveil failed (%m)");
1090 exit(1);
1091 }
1092 if (pledge("stdio rpath wpath inet flock proc exec", NULL((void *)0)) == -1) {
7
Assuming the condition is false
8
Taking false branch
1093 syslog_r(LOG_ERR3, &sdata, "pledge failed (%m)");
1094 exit(1);
1095 }
1096
1097 startup = time(NULL((void *)0));
1098 db_pid = fork();
1099 switch (db_pid) {
1100 case -1:
1101 syslog_r(LOG_ERR3, &sdata, "fork failed (%m)");
1102 exit(1);
1103 case 0:
1104 /*
1105 * child, talks to jailed spamd over greypipe,
1106 * updates db. has no access to pf.
1107 */
1108 close(pfdev);
1109 setproctitle("(%s update)", PATH_SPAMD_DB"/var/db/spamd");
1110 if (greyreader() == -1) {
1111 syslog_r(LOG_ERR3, &sdata, "greyreader failed (%m)");
1112 _exit(1);
1113 }
1114 _exit(0);
1115 }
1116
1117
1118 fclose(grey);
1119 /*
1120 * parent, scans db periodically for changes and updates
1121 * pf whitelist table accordingly.
1122 */
1123
1124 sigfillset(&sa.sa_mask);
1125 sa.sa_flags = SA_RESTART0x0002;
1126 sa.sa_handler__sigaction_u.__sa_handler = sig_term_chld;
1127 sigaction(SIGTERM15, &sa, NULL((void *)0));
1128 sigaction(SIGHUP1, &sa, NULL((void *)0));
1129 sigaction(SIGCHLD20, &sa, NULL((void *)0));
1130 sigaction(SIGINT2, &sa, NULL((void *)0));
1131
1132 setproctitle("(pf <spamd-white> update)");
1133 greyscanner();
9
Calling 'greyscanner'
1134 exit(1);
1135}