1*5b133f3fSguenther /* $OpenBSD: grey.c,v 1.67 2023/03/08 04:43:06 guenther Exp $ */
284d82a5fSderaadt
31f68c1d4Sbeck /*
44020b879Sbeck * Copyright (c) 2004-2006 Bob Beck. All rights reserved.
51f68c1d4Sbeck *
61f68c1d4Sbeck * Permission to use, copy, modify, and distribute this software for any
71f68c1d4Sbeck * purpose with or without fee is hereby granted, provided that the above
81f68c1d4Sbeck * copyright notice and this permission notice appear in all copies.
91f68c1d4Sbeck *
101f68c1d4Sbeck * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
111f68c1d4Sbeck * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
121f68c1d4Sbeck * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
131f68c1d4Sbeck * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
141f68c1d4Sbeck * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
151f68c1d4Sbeck * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
161f68c1d4Sbeck * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
171f68c1d4Sbeck */
181f68c1d4Sbeck
191f68c1d4Sbeck #include <sys/types.h>
201f68c1d4Sbeck #include <sys/socket.h>
211f68c1d4Sbeck #include <sys/ioctl.h>
221f68c1d4Sbeck #include <sys/wait.h>
231f68c1d4Sbeck #include <net/if.h>
241f68c1d4Sbeck #include <netinet/in.h>
251f68c1d4Sbeck #include <net/pfvar.h>
261e913b92Sbeck #include <ctype.h>
271f68c1d4Sbeck #include <db.h>
281f68c1d4Sbeck #include <errno.h>
291f68c1d4Sbeck #include <fcntl.h>
301f68c1d4Sbeck #include <pwd.h>
318a6ccc17Sbeck #include <signal.h>
321f68c1d4Sbeck #include <stdio.h>
331f68c1d4Sbeck #include <stdlib.h>
341f68c1d4Sbeck #include <string.h>
351f68c1d4Sbeck #include <syslog.h>
361f68c1d4Sbeck #include <time.h>
371f68c1d4Sbeck #include <unistd.h>
38ef8fe961Sitojun #include <netdb.h>
391f68c1d4Sbeck
401f68c1d4Sbeck #include "grey.h"
4198babcadSbeck #include "sync.h"
421f68c1d4Sbeck
431e913b92Sbeck extern time_t passtime, greyexp, whiteexp, trapexp;
441f68c1d4Sbeck extern struct syslog_data sdata;
451f68c1d4Sbeck extern struct passwd *pw;
461e913b92Sbeck extern u_short cfg_port;
478a6ccc17Sbeck extern pid_t jail_pid;
481e913b92Sbeck extern FILE *trapcfg;
491f68c1d4Sbeck extern FILE *grey;
501f68c1d4Sbeck extern int debug;
5198babcadSbeck extern int syncsend;
5266baed91Smillert extern int greyback[2];
531f68c1d4Sbeck
542c79ec66Sbeck /* From netinet/in.h, but only _KERNEL_ gets them. */
552c79ec66Sbeck #define satosin(sa) ((struct sockaddr_in *)(sa))
562c79ec66Sbeck #define satosin6(sa) ((struct sockaddr_in6 *)(sa))
572c79ec66Sbeck
58694ff69bSmillert void configure_spamd(char **, u_int, FILE *);
59f66a3d85Sderaadt int configure_pf(char **, int);
60f66a3d85Sderaadt char *dequotetolower(const char *);
61f66a3d85Sderaadt void readsuffixlists(void);
62f66a3d85Sderaadt void freeaddrlists(void);
63f66a3d85Sderaadt int addwhiteaddr(char *);
64f66a3d85Sderaadt int addtrapaddr(char *);
65f66a3d85Sderaadt int db_addrstate(DB *, char *);
66f66a3d85Sderaadt int greyscan(char *);
67f66a3d85Sderaadt int trapcheck(DB *, char *);
68f66a3d85Sderaadt int twupdate(char *, char *, char *, char *, char *);
69f66a3d85Sderaadt int twread(char *);
70f66a3d85Sderaadt int greyreader(void);
71f66a3d85Sderaadt void greyscanner(void);
72f66a3d85Sderaadt
73f66a3d85Sderaadt
74694ff69bSmillert u_int whitecount, whitealloc;
75694ff69bSmillert u_int trapcount, trapalloc;
761f68c1d4Sbeck char **whitelist;
771e913b92Sbeck char **traplist;
781e913b92Sbeck
791e913b92Sbeck char *traplist_name = "spamd-greytrap";
801e913b92Sbeck char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
811e913b92Sbeck
828a6ccc17Sbeck pid_t db_pid = -1;
831f68c1d4Sbeck int pfdev;
841f68c1d4Sbeck
854020b879Sbeck struct db_change {
864020b879Sbeck SLIST_ENTRY(db_change) entry;
874020b879Sbeck char * key;
884020b879Sbeck void * data;
894020b879Sbeck size_t dsiz;
904020b879Sbeck int act;
914020b879Sbeck };
924020b879Sbeck
934020b879Sbeck #define DBC_ADD 1
944020b879Sbeck #define DBC_DEL 2
954020b879Sbeck
964020b879Sbeck /* db pending changes list */
974020b879Sbeck SLIST_HEAD(, db_change) db_changes = SLIST_HEAD_INITIALIZER(db_changes);
984020b879Sbeck
992abcd8eaSbeck struct mail_addr {
1002abcd8eaSbeck SLIST_ENTRY(mail_addr) entry;
1012abcd8eaSbeck char addr[MAX_MAIL];
1022abcd8eaSbeck };
1032abcd8eaSbeck
1042abcd8eaSbeck /* list of suffixes that must match TO: */
1052abcd8eaSbeck SLIST_HEAD(, mail_addr) match_suffix = SLIST_HEAD_INITIALIZER(match_suffix);
1062abcd8eaSbeck char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS;
1072abcd8eaSbeck
1082c79ec66Sbeck char *low_prio_mx_ip;
109ee505fa1Sbeck time_t startup;
1102c79ec66Sbeck
11184d82a5fSderaadt static char *pargv[11]= {
11284d82a5fSderaadt "pfctl", "-p", "/dev/pf", "-q", "-t",
113dc7e4988Sderaadt "spamd-white", "-T", "replace", "-f", "-", NULL
11484d82a5fSderaadt };
11584d82a5fSderaadt
1168a6ccc17Sbeck /* If the parent gets a signal, kill off the children and exit */
1178a6ccc17Sbeck static void
sig_term_chld(int sig)1188a6ccc17Sbeck sig_term_chld(int sig)
1198a6ccc17Sbeck {
1208a6ccc17Sbeck if (db_pid != -1)
1218a6ccc17Sbeck kill(db_pid, SIGTERM);
1228a6ccc17Sbeck if (jail_pid != -1)
1238a6ccc17Sbeck kill(jail_pid, SIGTERM);
1248a6ccc17Sbeck _exit(1);
1258a6ccc17Sbeck }
1268a6ccc17Sbeck
1271e913b92Sbeck /*
1281e913b92Sbeck * Greatly simplified version from spamd_setup.c - only
1291e913b92Sbeck * sends one blacklist to an already open stream. Has no need
1301e913b92Sbeck * to collapse cidr ranges since these are only ever single
1311e913b92Sbeck * host hits.
1321e913b92Sbeck */
133b8f31536Sray void
configure_spamd(char ** addrs,u_int count,FILE * sdc)134694ff69bSmillert configure_spamd(char **addrs, u_int count, FILE *sdc)
1351e913b92Sbeck {
136694ff69bSmillert u_int i;
1371e913b92Sbeck
138694ff69bSmillert /* XXX - doesn't support IPV6 yet */
139e25a0afeSbeck fprintf(sdc, "%s;", traplist_name);
140e25a0afeSbeck if (count != 0) {
141694ff69bSmillert fprintf(sdc, "%s;inet;%u", traplist_msg, count);
1421e913b92Sbeck for (i = 0; i < count; i++)
143694ff69bSmillert fprintf(sdc, ";%s/32", addrs[i]);
144e25a0afeSbeck }
145694ff69bSmillert fputc('\n', sdc);
1464020b879Sbeck if (fflush(sdc) == EOF)
1474020b879Sbeck syslog_r(LOG_DEBUG, &sdata, "configure_spamd: fflush failed (%m)");
1481e913b92Sbeck }
1491e913b92Sbeck
1501f68c1d4Sbeck int
configure_pf(char ** addrs,int count)1511f68c1d4Sbeck configure_pf(char **addrs, int count)
1521f68c1d4Sbeck {
1531f68c1d4Sbeck FILE *pf = NULL;
154e6ec2729Sotto int i, pdes[2], status;
1551f68c1d4Sbeck pid_t pid;
1561f68c1d4Sbeck char *fdpath;
1573ac6234cSmoritz struct sigaction sa;
1583ac6234cSmoritz
1593ac6234cSmoritz sigfillset(&sa.sa_mask);
1603ac6234cSmoritz sa.sa_flags = SA_RESTART;
1613ac6234cSmoritz sa.sa_handler = sig_term_chld;
1621f68c1d4Sbeck
1631f68c1d4Sbeck if (debug)
1641f68c1d4Sbeck fprintf(stderr, "configure_pf - device on fd %d\n", pfdev);
1653c8544c9Sderaadt
1663c8544c9Sderaadt /* Because /dev/fd/ only contains device nodes for 0-63 */
1671f68c1d4Sbeck if (pfdev < 1 || pfdev > 63)
1681f68c1d4Sbeck return(-1);
1693c8544c9Sderaadt
1701f68c1d4Sbeck if (asprintf(&fdpath, "/dev/fd/%d", pfdev) == -1)
1711f68c1d4Sbeck return(-1);
17284d82a5fSderaadt pargv[2] = fdpath;
1731f68c1d4Sbeck if (pipe(pdes) != 0) {
1741f68c1d4Sbeck syslog_r(LOG_INFO, &sdata, "pipe failed (%m)");
1751f68c1d4Sbeck free(fdpath);
1767066e321Sbeck fdpath = NULL;
1771f68c1d4Sbeck return(-1);
1781f68c1d4Sbeck }
1798a6ccc17Sbeck signal(SIGCHLD, SIG_DFL);
1801f68c1d4Sbeck switch (pid = fork()) {
1811f68c1d4Sbeck case -1:
1821f68c1d4Sbeck syslog_r(LOG_INFO, &sdata, "fork failed (%m)");
1831f68c1d4Sbeck free(fdpath);
1847066e321Sbeck fdpath = NULL;
18584d82a5fSderaadt close(pdes[0]);
18684d82a5fSderaadt close(pdes[1]);
1873ac6234cSmoritz sigaction(SIGCHLD, &sa, NULL);
1881f68c1d4Sbeck return(-1);
1891f68c1d4Sbeck case 0:
1901f68c1d4Sbeck /* child */
1911f68c1d4Sbeck close(pdes[1]);
1921f68c1d4Sbeck if (pdes[0] != STDIN_FILENO) {
1931f68c1d4Sbeck dup2(pdes[0], STDIN_FILENO);
1941f68c1d4Sbeck close(pdes[0]);
1951f68c1d4Sbeck }
19684d82a5fSderaadt execvp(PATH_PFCTL, pargv);
1971f68c1d4Sbeck syslog_r(LOG_ERR, &sdata, "can't exec %s:%m", PATH_PFCTL);
1981f68c1d4Sbeck _exit(1);
1991f68c1d4Sbeck }
2001f68c1d4Sbeck
2011f68c1d4Sbeck /* parent */
2021f68c1d4Sbeck free(fdpath);
2037066e321Sbeck fdpath = NULL;
2041f68c1d4Sbeck close(pdes[0]);
2051f68c1d4Sbeck pf = fdopen(pdes[1], "w");
2061f68c1d4Sbeck if (pf == NULL) {
2071f68c1d4Sbeck syslog_r(LOG_INFO, &sdata, "fdopen failed (%m)");
2087066e321Sbeck close(pdes[1]);
2093ac6234cSmoritz sigaction(SIGCHLD, &sa, NULL);
2101f68c1d4Sbeck return(-1);
2111f68c1d4Sbeck }
2121f68c1d4Sbeck for (i = 0; i < count; i++)
21308225fe9Sbeck if (addrs[i] != NULL)
2141f68c1d4Sbeck fprintf(pf, "%s/32\n", addrs[i]);
2151f68c1d4Sbeck fclose(pf);
216e6ec2729Sotto
217e6ec2729Sotto waitpid(pid, &status, 0);
218e6ec2729Sotto if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
219e6ec2729Sotto syslog_r(LOG_ERR, &sdata, "%s returned status %d", PATH_PFCTL,
220e6ec2729Sotto WEXITSTATUS(status));
221e6ec2729Sotto else if (WIFSIGNALED(status))
222e6ec2729Sotto syslog_r(LOG_ERR, &sdata, "%s died on signal %d", PATH_PFCTL,
223e6ec2729Sotto WTERMSIG(status));
224e6ec2729Sotto
2253ac6234cSmoritz sigaction(SIGCHLD, &sa, NULL);
2261f68c1d4Sbeck return(0);
2271f68c1d4Sbeck }
2281f68c1d4Sbeck
2292abcd8eaSbeck char *
dequotetolower(const char * addr)2302abcd8eaSbeck dequotetolower(const char *addr)
2312abcd8eaSbeck {
2322abcd8eaSbeck static char buf[MAX_MAIL];
23361a23f49Sbeck char *cp;
2342abcd8eaSbeck
23531ffa88fSjsg if (*addr == '<')
2362abcd8eaSbeck addr++;
23761a23f49Sbeck (void) strlcpy(buf, addr, sizeof(buf));
23861a23f49Sbeck cp = strrchr(buf, '>');
23961a23f49Sbeck if (cp != NULL && cp[1] == '\0')
24061a23f49Sbeck *cp = '\0';
24161a23f49Sbeck cp = buf;
24261a23f49Sbeck while (*cp != '\0') {
2434207a9b6Sderaadt *cp = tolower((unsigned char)*cp);
24461a23f49Sbeck cp++;
24561a23f49Sbeck }
2462abcd8eaSbeck return(buf);
2472abcd8eaSbeck }
2482abcd8eaSbeck
2492abcd8eaSbeck void
readsuffixlists(void)2502abcd8eaSbeck readsuffixlists(void)
2512abcd8eaSbeck {
2522abcd8eaSbeck FILE *fp;
2532abcd8eaSbeck char *buf;
2542abcd8eaSbeck size_t len;
2552abcd8eaSbeck struct mail_addr *m;
2562abcd8eaSbeck
257f576ce10Sbeck while (!SLIST_EMPTY(&match_suffix)) {
258f576ce10Sbeck m = SLIST_FIRST(&match_suffix);
2592abcd8eaSbeck SLIST_REMOVE_HEAD(&match_suffix, entry);
260f576ce10Sbeck free(m);
261f576ce10Sbeck }
2622abcd8eaSbeck if ((fp = fopen(alloweddomains_file, "r")) != NULL) {
2632abcd8eaSbeck while ((buf = fgetln(fp, &len))) {
26400ddf0caSbeck /* strip white space-characters */
2654207a9b6Sderaadt while (len > 0 && isspace((unsigned char)buf[len-1]))
26600ddf0caSbeck len--;
2674207a9b6Sderaadt while (len > 0 && isspace((unsigned char)*buf)) {
26800ddf0caSbeck buf++;
26900ddf0caSbeck len--;
27000ddf0caSbeck }
27100ddf0caSbeck if (len == 0)
27200ddf0caSbeck continue;
27300ddf0caSbeck /* jump over comments and blank lines */
27400ddf0caSbeck if (*buf == '#' || *buf == '\n')
27500ddf0caSbeck continue;
2762abcd8eaSbeck if (buf[len-1] == '\n')
2772abcd8eaSbeck len--;
2782abcd8eaSbeck if ((len + 1) > sizeof(m->addr)) {
2792abcd8eaSbeck syslog_r(LOG_ERR, &sdata,
2802abcd8eaSbeck "line too long in %s - file ignored",
2812abcd8eaSbeck alloweddomains_file);
2822abcd8eaSbeck goto bad;
2832abcd8eaSbeck }
28434fa0e41Sderaadt if ((m = malloc(sizeof(struct mail_addr))) == NULL)
28534fa0e41Sderaadt goto bad;
2862abcd8eaSbeck memcpy(m->addr, buf, len);
2872abcd8eaSbeck m->addr[len]='\0';
2882abcd8eaSbeck syslog_r(LOG_ERR, &sdata, "got suffix %s", m->addr);
2892abcd8eaSbeck SLIST_INSERT_HEAD(&match_suffix, m, entry);
2902abcd8eaSbeck }
2912abcd8eaSbeck }
2922abcd8eaSbeck return;
2932abcd8eaSbeck bad:
294f576ce10Sbeck while (!SLIST_EMPTY(&match_suffix)) {
295f576ce10Sbeck m = SLIST_FIRST(&match_suffix);
2962abcd8eaSbeck SLIST_REMOVE_HEAD(&match_suffix, entry);
297f576ce10Sbeck free(m);
298f576ce10Sbeck }
2992abcd8eaSbeck }
3002abcd8eaSbeck
30108225fe9Sbeck void
freeaddrlists(void)3021e913b92Sbeck freeaddrlists(void)
30308225fe9Sbeck {
30408225fe9Sbeck int i;
30508225fe9Sbeck
30608225fe9Sbeck if (whitelist != NULL)
30708225fe9Sbeck for (i = 0; i < whitecount; i++) {
30808225fe9Sbeck free(whitelist[i]);
30908225fe9Sbeck whitelist[i] = NULL;
31008225fe9Sbeck }
31108225fe9Sbeck whitecount = 0;
3121e913b92Sbeck if (traplist != NULL) {
3131e913b92Sbeck for (i = 0; i < trapcount; i++) {
3141e913b92Sbeck free(traplist[i]);
3151e913b92Sbeck traplist[i] = NULL;
3161e913b92Sbeck }
3171e913b92Sbeck }
3181e913b92Sbeck trapcount = 0;
31908225fe9Sbeck }
32008225fe9Sbeck
3211f68c1d4Sbeck /* validate, then add to list of addrs to whitelist */
3221f68c1d4Sbeck int
addwhiteaddr(char * addr)3231f68c1d4Sbeck addwhiteaddr(char *addr)
3241f68c1d4Sbeck {
325ef8fe961Sitojun struct addrinfo hints, *res;
32666baed91Smillert char ch;
3271f68c1d4Sbeck
328ef8fe961Sitojun memset(&hints, 0, sizeof(hints));
32994c946baSitojun hints.ai_family = AF_INET; /*for now*/
330ef8fe961Sitojun hints.ai_socktype = SOCK_DGRAM; /*dummy*/
331ef8fe961Sitojun hints.ai_protocol = IPPROTO_UDP; /*dummy*/
332ef8fe961Sitojun hints.ai_flags = AI_NUMERICHOST;
333ef8fe961Sitojun
33466baed91Smillert if (getaddrinfo(addr, NULL, &hints, &res) != 0)
33566baed91Smillert return(-1);
33666baed91Smillert
33766baed91Smillert /* Check spamd blacklists in main process. */
33866baed91Smillert if (send(greyback[0], res->ai_addr, res->ai_addr->sa_len, 0) == -1) {
33966baed91Smillert syslog_r(LOG_ERR, &sdata, "%s: send: %m", __func__);
34066baed91Smillert } else {
34166baed91Smillert if (recv(greyback[0], &ch, sizeof(ch), 0) == 1) {
34266baed91Smillert if (ch == '1') {
34366baed91Smillert syslog_r(LOG_DEBUG, &sdata,
34466baed91Smillert "%s blacklisted, removing from whitelist",
34566baed91Smillert addr);
34666baed91Smillert freeaddrinfo(res);
34766baed91Smillert return(-1);
34866baed91Smillert }
34966baed91Smillert }
35066baed91Smillert }
35166baed91Smillert
3521f68c1d4Sbeck if (whitecount == whitealloc) {
3531f68c1d4Sbeck char **tmp;
3541f68c1d4Sbeck
355915dd247Sderaadt tmp = reallocarray(whitelist,
356915dd247Sderaadt whitealloc + 1024, sizeof(char *));
357053e71a7Sderaadt if (tmp == NULL) {
358053e71a7Sderaadt freeaddrinfo(res);
3591f68c1d4Sbeck return(-1);
360053e71a7Sderaadt }
3611f68c1d4Sbeck whitelist = tmp;
3621f68c1d4Sbeck whitealloc += 1024;
3631f68c1d4Sbeck }
3641f68c1d4Sbeck whitelist[whitecount] = strdup(addr);
365053e71a7Sderaadt if (whitelist[whitecount] == NULL) {
366053e71a7Sderaadt freeaddrinfo(res);
3671f68c1d4Sbeck return(-1);
368053e71a7Sderaadt }
3691f68c1d4Sbeck whitecount++;
370ef8fe961Sitojun freeaddrinfo(res);
3711f68c1d4Sbeck return(0);
3721f68c1d4Sbeck }
3731f68c1d4Sbeck
3741e913b92Sbeck /* validate, then add to list of addrs to traplist */
3751e913b92Sbeck int
addtrapaddr(char * addr)3761e913b92Sbeck addtrapaddr(char *addr)
3771e913b92Sbeck {
3781e913b92Sbeck struct addrinfo hints, *res;
3791e913b92Sbeck
3801e913b92Sbeck memset(&hints, 0, sizeof(hints));
3811e913b92Sbeck hints.ai_family = AF_INET; /*for now*/
3821e913b92Sbeck hints.ai_socktype = SOCK_DGRAM; /*dummy*/
3831e913b92Sbeck hints.ai_protocol = IPPROTO_UDP; /*dummy*/
3841e913b92Sbeck hints.ai_flags = AI_NUMERICHOST;
3851e913b92Sbeck
3861e913b92Sbeck if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
3871e913b92Sbeck if (trapcount == trapalloc) {
3881e913b92Sbeck char **tmp;
3891e913b92Sbeck
390915dd247Sderaadt tmp = reallocarray(traplist,
391915dd247Sderaadt trapalloc + 1024, sizeof(char *));
3921e913b92Sbeck if (tmp == NULL) {
3931e913b92Sbeck freeaddrinfo(res);
3941e913b92Sbeck return(-1);
3951e913b92Sbeck }
3961e913b92Sbeck traplist = tmp;
3971e913b92Sbeck trapalloc += 1024;
3981e913b92Sbeck }
3991e913b92Sbeck traplist[trapcount] = strdup(addr);
4001e913b92Sbeck if (traplist[trapcount] == NULL) {
4011e913b92Sbeck freeaddrinfo(res);
4021e913b92Sbeck return(-1);
4031e913b92Sbeck }
4041e913b92Sbeck trapcount++;
4051e913b92Sbeck freeaddrinfo(res);
4061e913b92Sbeck } else
4071e913b92Sbeck return(-1);
4081e913b92Sbeck return(0);
4091e913b92Sbeck }
4101e913b92Sbeck
4114020b879Sbeck static int
queue_change(char * key,char * data,size_t dsiz,int act)41220a5c6fdSderaadt queue_change(char *key, char *data, size_t dsiz, int act)
41320a5c6fdSderaadt {
4144020b879Sbeck struct db_change *dbc;
4154020b879Sbeck
4164020b879Sbeck if ((dbc = malloc(sizeof(*dbc))) == NULL) {
4174020b879Sbeck syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
4184020b879Sbeck return(-1);
4194020b879Sbeck }
4204020b879Sbeck if ((dbc->key = strdup(key)) == NULL) {
4214020b879Sbeck syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
4224020b879Sbeck free(dbc);
4234020b879Sbeck return(-1);
4244020b879Sbeck }
4254020b879Sbeck if ((dbc->data = malloc(dsiz)) == NULL) {
4264020b879Sbeck syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
4274020b879Sbeck free(dbc->key);
4284020b879Sbeck free(dbc);
4294020b879Sbeck return(-1);
4304020b879Sbeck }
4314020b879Sbeck memcpy(dbc->data, data, dsiz);
4324020b879Sbeck dbc->dsiz = dsiz;
4334020b879Sbeck dbc->act = act;
4344020b879Sbeck syslog_r(LOG_DEBUG, &sdata,
4354020b879Sbeck "queueing %s of %s", ((act == DBC_ADD) ? "add" : "deletion"),
4364020b879Sbeck dbc->key);
4374020b879Sbeck SLIST_INSERT_HEAD(&db_changes, dbc, entry);
4384020b879Sbeck return(0);
4394020b879Sbeck }
4404020b879Sbeck
4414020b879Sbeck static int
do_changes(DB * db)44220a5c6fdSderaadt do_changes(DB *db)
44320a5c6fdSderaadt {
4444020b879Sbeck DBT dbk, dbd;
4454020b879Sbeck struct db_change *dbc;
4464020b879Sbeck int ret = 0;
4474020b879Sbeck
4484020b879Sbeck while (!SLIST_EMPTY(&db_changes)) {
4494020b879Sbeck dbc = SLIST_FIRST(&db_changes);
4504020b879Sbeck switch (dbc->act) {
4514020b879Sbeck case DBC_ADD:
4524020b879Sbeck memset(&dbk, 0, sizeof(dbk));
4534020b879Sbeck dbk.size = strlen(dbc->key);
4544020b879Sbeck dbk.data = dbc->key;
4554020b879Sbeck memset(&dbd, 0, sizeof(dbd));
4564020b879Sbeck dbd.size = dbc->dsiz;
4574020b879Sbeck dbd.data = dbc->data;
4584020b879Sbeck if (db->put(db, &dbk, &dbd, 0)) {
4594020b879Sbeck db->sync(db, 0);
4604020b879Sbeck syslog_r(LOG_ERR, &sdata,
4614020b879Sbeck "can't add %s to spamd db (%m)", dbc->key);
4624020b879Sbeck ret = -1;
4634020b879Sbeck }
4644020b879Sbeck db->sync(db, 0);
4654020b879Sbeck break;
4664020b879Sbeck case DBC_DEL:
4674020b879Sbeck memset(&dbk, 0, sizeof(dbk));
4684020b879Sbeck dbk.size = strlen(dbc->key);
4694020b879Sbeck dbk.data = dbc->key;
4704020b879Sbeck if (db->del(db, &dbk, 0)) {
4714020b879Sbeck syslog_r(LOG_ERR, &sdata,
4724020b879Sbeck "can't delete %s from spamd db (%m)",
4734020b879Sbeck dbc->key);
4744020b879Sbeck ret = -1;
4754020b879Sbeck }
4764020b879Sbeck break;
4774020b879Sbeck default:
4784020b879Sbeck syslog_r(LOG_ERR, &sdata, "Unrecognized db change");
4794020b879Sbeck ret = -1;
4804020b879Sbeck }
4814020b879Sbeck free(dbc->key);
4824020b879Sbeck dbc->key = NULL;
4834020b879Sbeck free(dbc->data);
4844020b879Sbeck dbc->data = NULL;
4854020b879Sbeck dbc->act = 0;
4864020b879Sbeck dbc->dsiz = 0;
4874020b879Sbeck SLIST_REMOVE_HEAD(&db_changes, entry);
488f576ce10Sbeck free(dbc);
4894020b879Sbeck
4904020b879Sbeck }
4914020b879Sbeck return(ret);
4924020b879Sbeck }
4934020b879Sbeck
494a6397563Sstephan /* -1=error, 0=notfound, 1=TRAPPED, 2=WHITE */
4951f68c1d4Sbeck int
db_addrstate(DB * db,char * key)496a6397563Sstephan db_addrstate(DB *db, char *key)
49705b7f62bSbeck {
49805b7f62bSbeck DBT dbk, dbd;
499a6397563Sstephan struct gdata gd;
50005b7f62bSbeck
50105b7f62bSbeck memset(&dbk, 0, sizeof(dbk));
50205b7f62bSbeck dbk.size = strlen(key);
50305b7f62bSbeck dbk.data = key;
50400ddf0caSbeck memset(&dbd, 0, sizeof(dbd));
50551210d5cSmillert switch (db->get(db, &dbk, &dbd, 0)) {
50651210d5cSmillert case 1:
50751210d5cSmillert /* not found */
50805b7f62bSbeck return (0);
50951210d5cSmillert case 0:
51051210d5cSmillert if (gdcopyin(&dbd, &gd) != -1)
51151210d5cSmillert return (gd.pcount == -1 ? 1 : 2);
51251210d5cSmillert /* FALLTHROUGH */
51351210d5cSmillert default:
51451210d5cSmillert /* error */
51551210d5cSmillert return (-1);
51651210d5cSmillert }
51705b7f62bSbeck }
51805b7f62bSbeck
51905b7f62bSbeck
52005b7f62bSbeck int
greyscan(char * dbname)5211f68c1d4Sbeck greyscan(char *dbname)
5221f68c1d4Sbeck {
5234020b879Sbeck HASHINFO hashinfo;
5247066e321Sbeck DBT dbk, dbd;
5257066e321Sbeck DB *db;
5261f68c1d4Sbeck struct gdata gd;
5271f68c1d4Sbeck int r;
5281e913b92Sbeck char *a = NULL;
5291e913b92Sbeck size_t asiz = 0;
5307066e321Sbeck time_t now = time(NULL);
5311f68c1d4Sbeck
5321f68c1d4Sbeck /* walk db, expire, and whitelist */
5334020b879Sbeck memset(&hashinfo, 0, sizeof(hashinfo));
5344020b879Sbeck db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
5351f68c1d4Sbeck if (db == NULL) {
5361f68c1d4Sbeck syslog_r(LOG_INFO, &sdata, "dbopen failed (%m)");
5371f68c1d4Sbeck return(-1);
5381f68c1d4Sbeck }
5391f68c1d4Sbeck memset(&dbk, 0, sizeof(dbk));
5401f68c1d4Sbeck memset(&dbd, 0, sizeof(dbd));
5411f68c1d4Sbeck for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
5421f68c1d4Sbeck r = db->seq(db, &dbk, &dbd, R_NEXT)) {
54351210d5cSmillert if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
5444020b879Sbeck syslog_r(LOG_ERR, &sdata, "bogus entry in spamd database");
5451e913b92Sbeck goto bad;
5461f68c1d4Sbeck }
5471e913b92Sbeck if (asiz < dbk.size + 1) {
5481e913b92Sbeck char *tmp;
5491e913b92Sbeck
550915dd247Sderaadt tmp = reallocarray(a, dbk.size, 2);
5511e913b92Sbeck if (tmp == NULL)
5521e913b92Sbeck goto bad;
5531e913b92Sbeck a = tmp;
5541e913b92Sbeck asiz = dbk.size * 2;
5551e913b92Sbeck }
5561e913b92Sbeck memset(a, 0, asiz);
5571e913b92Sbeck memcpy(a, dbk.data, dbk.size);
5581e913b92Sbeck if (gd.expire <= now && gd.pcount != -2) {
5591f68c1d4Sbeck /* get rid of entry */
5604020b879Sbeck if (queue_change(a, NULL, 0, DBC_DEL) == -1)
5611e913b92Sbeck goto bad;
5621e913b92Sbeck } else if (gd.pcount == -1) {
5631e913b92Sbeck /* this is a greytrap hit */
5641e913b92Sbeck if ((addtrapaddr(a) == -1) &&
5654020b879Sbeck (queue_change(a, NULL, 0, DBC_DEL) == -1))
5661e913b92Sbeck goto bad;
5671e913b92Sbeck } else if (gd.pcount >= 0 && gd.pass <= now) {
5681f68c1d4Sbeck int tuple = 0;
5691f68c1d4Sbeck char *cp;
570a6397563Sstephan int state;
5711f68c1d4Sbeck
5721f68c1d4Sbeck /*
573a6397563Sstephan * if not already TRAPPED,
5741f68c1d4Sbeck * add address to whitelist
5751f68c1d4Sbeck * add an address-keyed entry to db
5761f68c1d4Sbeck */
5771f68c1d4Sbeck cp = strchr(a, '\n');
5781f68c1d4Sbeck if (cp != NULL) {
5791f68c1d4Sbeck tuple = 1;
5801f68c1d4Sbeck *cp = '\0';
5811f68c1d4Sbeck }
5824020b879Sbeck
583a6397563Sstephan state = db_addrstate(db, a);
584a6397563Sstephan if (state != 1 && addwhiteaddr(a) == -1) {
5854020b879Sbeck if (cp != NULL)
5864020b879Sbeck *cp = '\n';
5874020b879Sbeck if (queue_change(a, NULL, 0, DBC_DEL) == -1)
5881f68c1d4Sbeck goto bad;
589a2836901Sbeck }
5904020b879Sbeck
591a6397563Sstephan if (tuple && state <= 0) {
5924020b879Sbeck if (cp != NULL)
5934020b879Sbeck *cp = '\0';
5941f68c1d4Sbeck /* re-add entry, keyed only by ip */
5957066e321Sbeck gd.expire = now + whiteexp;
5961f68c1d4Sbeck dbd.size = sizeof(gd);
5971f68c1d4Sbeck dbd.data = &gd;
5984020b879Sbeck if (queue_change(a, (void *) &gd, sizeof(gd),
5994020b879Sbeck DBC_ADD) == -1)
6001f68c1d4Sbeck goto bad;
6011f68c1d4Sbeck syslog_r(LOG_DEBUG, &sdata,
6021f68c1d4Sbeck "whitelisting %s in %s", a, dbname);
6031f68c1d4Sbeck }
6041f68c1d4Sbeck if (debug)
6051f68c1d4Sbeck fprintf(stderr, "whitelisted %s\n", a);
6061f68c1d4Sbeck }
6071f68c1d4Sbeck }
6084020b879Sbeck (void) do_changes(db);
6090199266dSbeck db->close(db);
6100199266dSbeck db = NULL;
6111f68c1d4Sbeck configure_pf(whitelist, whitecount);
612b8f31536Sray configure_spamd(traplist, trapcount, trapcfg);
6131e913b92Sbeck
6141e913b92Sbeck freeaddrlists();
6151e913b92Sbeck free(a);
6161e913b92Sbeck a = NULL;
6171f68c1d4Sbeck return(0);
6181f68c1d4Sbeck bad:
6194020b879Sbeck (void) do_changes(db);
6201f68c1d4Sbeck db->close(db);
62184d82a5fSderaadt db = NULL;
6221e913b92Sbeck freeaddrlists();
6231e913b92Sbeck free(a);
6241e913b92Sbeck a = NULL;
6251f68c1d4Sbeck return(-1);
6261f68c1d4Sbeck }
6271f68c1d4Sbeck
6281f68c1d4Sbeck int
trapcheck(DB * db,char * to)6292abcd8eaSbeck trapcheck(DB *db, char *to)
6302abcd8eaSbeck {
6312abcd8eaSbeck int i, j, smatch = 0;
6322abcd8eaSbeck DBT dbk, dbd;
6332abcd8eaSbeck struct mail_addr *m;
6342abcd8eaSbeck char * trap;
6352abcd8eaSbeck size_t s;
6362abcd8eaSbeck
6372abcd8eaSbeck trap = dequotetolower(to);
6382abcd8eaSbeck if (!SLIST_EMPTY(&match_suffix)) {
6392abcd8eaSbeck s = strlen(trap);
6402abcd8eaSbeck SLIST_FOREACH(m, &match_suffix, entry) {
6412abcd8eaSbeck j = s - strlen(m->addr);
6422abcd8eaSbeck if ((j >= 0) && (strcasecmp(trap+j, m->addr) == 0))
6432abcd8eaSbeck smatch = 1;
6442abcd8eaSbeck }
6452abcd8eaSbeck if (!smatch)
6462abcd8eaSbeck /* no suffixes match, so trap it */
6472abcd8eaSbeck return (0);
6482abcd8eaSbeck }
6492abcd8eaSbeck memset(&dbk, 0, sizeof(dbk));
6502abcd8eaSbeck dbk.size = strlen(trap);
6512abcd8eaSbeck dbk.data = trap;
6522abcd8eaSbeck memset(&dbd, 0, sizeof(dbd));
6532abcd8eaSbeck i = db->get(db, &dbk, &dbd, 0);
6542abcd8eaSbeck if (i == -1)
6552abcd8eaSbeck return (-1);
6562abcd8eaSbeck if (i)
6572abcd8eaSbeck /* didn't exist - so this doesn't match a known spamtrap */
6582abcd8eaSbeck return (1);
6592abcd8eaSbeck else
6602abcd8eaSbeck /* To: address is a spamtrap, so add as a greytrap entry */
6612abcd8eaSbeck return (0);
6622abcd8eaSbeck }
6632abcd8eaSbeck
6642abcd8eaSbeck int
twupdate(char * dbname,char * what,char * ip,char * source,char * expires)66520a5c6fdSderaadt twupdate(char *dbname, char *what, char *ip, char *source, char *expires)
66620a5c6fdSderaadt {
66798babcadSbeck /* we got a TRAP or WHITE update from someone else */
66898babcadSbeck HASHINFO hashinfo;
66998babcadSbeck DBT dbk, dbd;
67098babcadSbeck DB *db;
67198babcadSbeck struct gdata gd;
67298babcadSbeck time_t now, expire;
67398babcadSbeck int r, spamtrap;
67498babcadSbeck
67598babcadSbeck now = time(NULL);
67698babcadSbeck /* expiry times have to be in the future */
677f66a3d85Sderaadt expire = strtonum(expires, now,
678f66a3d85Sderaadt sizeof(time_t) == sizeof(int) ? INT_MAX : LLONG_MAX, NULL);
67998babcadSbeck if (expire == 0)
68098babcadSbeck return(-1);
68198babcadSbeck
68298babcadSbeck if (strcmp(what, "TRAP") == 0)
68398babcadSbeck spamtrap = 1;
68498babcadSbeck else if (strcmp(what, "WHITE") == 0)
68598babcadSbeck spamtrap = 0;
68698babcadSbeck else
68798babcadSbeck return(-1);
68898babcadSbeck
68998babcadSbeck memset(&hashinfo, 0, sizeof(hashinfo));
69098babcadSbeck db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
69198babcadSbeck if (db == NULL)
69298babcadSbeck return(-1);
69398babcadSbeck
69498babcadSbeck memset(&dbk, 0, sizeof(dbk));
69598babcadSbeck dbk.size = strlen(ip);
69698babcadSbeck dbk.data = ip;
69798babcadSbeck memset(&dbd, 0, sizeof(dbd));
69898babcadSbeck r = db->get(db, &dbk, &dbd, 0);
69998babcadSbeck if (r == -1)
70098babcadSbeck goto bad;
70198babcadSbeck if (r) {
70298babcadSbeck /* new entry */
70398babcadSbeck memset(&gd, 0, sizeof(gd));
70498babcadSbeck gd.first = now;
70598babcadSbeck gd.pcount = spamtrap ? -1 : 0;
70698babcadSbeck gd.expire = expire;
70798babcadSbeck memset(&dbk, 0, sizeof(dbk));
70898babcadSbeck dbk.size = strlen(ip);
70998babcadSbeck dbk.data = ip;
71098babcadSbeck memset(&dbd, 0, sizeof(dbd));
71198babcadSbeck dbd.size = sizeof(gd);
71298babcadSbeck dbd.data = &gd;
71398babcadSbeck r = db->put(db, &dbk, &dbd, 0);
71498babcadSbeck db->sync(db, 0);
71598babcadSbeck if (r)
71698babcadSbeck goto bad;
71798babcadSbeck if (debug)
71898babcadSbeck fprintf(stderr, "added %s %s\n",
71998babcadSbeck spamtrap ? "trap entry for" : "", ip);
7202c79ec66Sbeck syslog_r(LOG_DEBUG, &sdata,
721c0289727Sreyk "new %s from %s for %s, expires %s", what, source, ip,
7222c79ec66Sbeck expires);
72398babcadSbeck } else {
72498babcadSbeck /* existing entry */
72551210d5cSmillert if (gdcopyin(&dbd, &gd) == -1) {
72698babcadSbeck /* whatever this is, it doesn't belong */
72798babcadSbeck db->del(db, &dbk, 0);
72898babcadSbeck db->sync(db, 0);
72998babcadSbeck goto bad;
73098babcadSbeck }
73198babcadSbeck if (spamtrap) {
73298babcadSbeck gd.pcount = -1;
73398babcadSbeck gd.bcount++;
73498babcadSbeck } else
73598babcadSbeck gd.pcount++;
73698babcadSbeck memset(&dbk, 0, sizeof(dbk));
73798babcadSbeck dbk.size = strlen(ip);
73898babcadSbeck dbk.data = ip;
73998babcadSbeck memset(&dbd, 0, sizeof(dbd));
74098babcadSbeck dbd.size = sizeof(gd);
74198babcadSbeck dbd.data = &gd;
74298babcadSbeck r = db->put(db, &dbk, &dbd, 0);
74398babcadSbeck db->sync(db, 0);
74498babcadSbeck if (r)
74598babcadSbeck goto bad;
74698babcadSbeck if (debug)
74798babcadSbeck fprintf(stderr, "updated %s\n", ip);
74898babcadSbeck }
74998babcadSbeck db->close(db);
75098babcadSbeck return(0);
75198babcadSbeck bad:
75298babcadSbeck db->close(db);
75398babcadSbeck return(-1);
75498babcadSbeck
75598babcadSbeck }
75698babcadSbeck
75798babcadSbeck int
greyupdate(char * dbname,char * helo,char * ip,char * from,char * to,int sync,char * cip)7582c79ec66Sbeck greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync,
7592c79ec66Sbeck char *cip)
7601f68c1d4Sbeck {
7614020b879Sbeck HASHINFO hashinfo;
7627066e321Sbeck DBT dbk, dbd;
7637066e321Sbeck DB *db;
7641f68c1d4Sbeck char *key = NULL;
7651e913b92Sbeck char *lookup;
7661f68c1d4Sbeck struct gdata gd;
7671e913b92Sbeck time_t now, expire;
7682abcd8eaSbeck int r, spamtrap;
7691f68c1d4Sbeck
77084d82a5fSderaadt now = time(NULL);
77184d82a5fSderaadt
7721f68c1d4Sbeck /* open with lock, find record, update, close, unlock */
7734020b879Sbeck memset(&hashinfo, 0, sizeof(hashinfo));
7744020b879Sbeck db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
7751f68c1d4Sbeck if (db == NULL)
7761f68c1d4Sbeck return(-1);
7775b0222eaSbeck if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1)
7781f68c1d4Sbeck goto bad;
7792abcd8eaSbeck r = trapcheck(db, to);
7802abcd8eaSbeck switch (r) {
7812abcd8eaSbeck case 1:
7822abcd8eaSbeck /* do not trap */
7831e913b92Sbeck spamtrap = 0;
7841e913b92Sbeck lookup = key;
7851e913b92Sbeck expire = greyexp;
7862abcd8eaSbeck break;
7872abcd8eaSbeck case 0:
7882abcd8eaSbeck /* trap */
7891e913b92Sbeck spamtrap = 1;
7901e913b92Sbeck lookup = ip;
7911e913b92Sbeck expire = trapexp;
7924b6843aaSbeck syslog_r(LOG_DEBUG, &sdata, "Trapping %s for tuple %s", ip,
7934b6843aaSbeck key);
7942abcd8eaSbeck break;
7952abcd8eaSbeck default:
7962abcd8eaSbeck goto bad;
7972abcd8eaSbeck break;
7981e913b92Sbeck }
7991e913b92Sbeck memset(&dbk, 0, sizeof(dbk));
8001e913b92Sbeck dbk.size = strlen(lookup);
8011e913b92Sbeck dbk.data = lookup;
8021f68c1d4Sbeck memset(&dbd, 0, sizeof(dbd));
8031f68c1d4Sbeck r = db->get(db, &dbk, &dbd, 0);
8041f68c1d4Sbeck if (r == -1)
8051f68c1d4Sbeck goto bad;
8061f68c1d4Sbeck if (r) {
8071f68c1d4Sbeck /* new entry */
808ee505fa1Sbeck if (sync && low_prio_mx_ip &&
809ee505fa1Sbeck (strcmp(cip, low_prio_mx_ip) == 0) &&
810ee505fa1Sbeck ((startup + 60) < now)) {
8112c79ec66Sbeck /* we haven't seen a greylist entry for this tuple,
8122c79ec66Sbeck * and yet the connection was to a low priority MX
8132c79ec66Sbeck * which we know can't be hit first if the client
8142c79ec66Sbeck * is adhering to the RFC's - soo.. kill it!
8152c79ec66Sbeck */
8162c79ec66Sbeck spamtrap = 1;
8172c79ec66Sbeck lookup = ip;
8182c79ec66Sbeck expire = trapexp;
8192c79ec66Sbeck syslog_r(LOG_DEBUG, &sdata,
8202c79ec66Sbeck "Trapping %s for trying %s first for tuple %s",
8212c79ec66Sbeck ip, low_prio_mx_ip, key);
8222c79ec66Sbeck }
8231f68c1d4Sbeck memset(&gd, 0, sizeof(gd));
8241f68c1d4Sbeck gd.first = now;
8251f68c1d4Sbeck gd.bcount = 1;
8261e913b92Sbeck gd.pcount = spamtrap ? -1 : 0;
8271e913b92Sbeck gd.pass = now + expire;
8281e913b92Sbeck gd.expire = now + expire;
8291f68c1d4Sbeck memset(&dbk, 0, sizeof(dbk));
8301e913b92Sbeck dbk.size = strlen(lookup);
8311e913b92Sbeck dbk.data = lookup;
8321f68c1d4Sbeck memset(&dbd, 0, sizeof(dbd));
8331f68c1d4Sbeck dbd.size = sizeof(gd);
8341f68c1d4Sbeck dbd.data = &gd;
8351f68c1d4Sbeck r = db->put(db, &dbk, &dbd, 0);
836a2836901Sbeck db->sync(db, 0);
8371f68c1d4Sbeck if (r)
8381f68c1d4Sbeck goto bad;
8391f68c1d4Sbeck if (debug)
8401e913b92Sbeck fprintf(stderr, "added %s %s\n",
8411e913b92Sbeck spamtrap ? "greytrap entry for" : "", lookup);
842c0289727Sreyk syslog_r(LOG_DEBUG, &sdata,
843c0289727Sreyk "new %sentry %s from %s to %s, helo %s",
844c0289727Sreyk spamtrap ? "greytrap " : "", ip, from, to, helo);
8451f68c1d4Sbeck } else {
8461f68c1d4Sbeck /* existing entry */
84751210d5cSmillert if (gdcopyin(&dbd, &gd) == -1) {
8481f68c1d4Sbeck /* whatever this is, it doesn't belong */
8491f68c1d4Sbeck db->del(db, &dbk, 0);
850a2836901Sbeck db->sync(db, 0);
8511f68c1d4Sbeck goto bad;
8521f68c1d4Sbeck }
8531f68c1d4Sbeck gd.bcount++;
8541e913b92Sbeck gd.pcount = spamtrap ? -1 : 0;
8557066e321Sbeck if (gd.first + passtime < now)
8561f68c1d4Sbeck gd.pass = now;
8571f68c1d4Sbeck memset(&dbk, 0, sizeof(dbk));
8581e913b92Sbeck dbk.size = strlen(lookup);
8591e913b92Sbeck dbk.data = lookup;
8601f68c1d4Sbeck memset(&dbd, 0, sizeof(dbd));
8611f68c1d4Sbeck dbd.size = sizeof(gd);
8621f68c1d4Sbeck dbd.data = &gd;
8631f68c1d4Sbeck r = db->put(db, &dbk, &dbd, 0);
864a2836901Sbeck db->sync(db, 0);
8651f68c1d4Sbeck if (r)
8661f68c1d4Sbeck goto bad;
8671f68c1d4Sbeck if (debug)
8681e913b92Sbeck fprintf(stderr, "updated %s\n", lookup);
8691f68c1d4Sbeck }
8701f68c1d4Sbeck free(key);
8717066e321Sbeck key = NULL;
87284d82a5fSderaadt db->close(db);
87384d82a5fSderaadt db = NULL;
87498babcadSbeck
87598babcadSbeck /* Entry successfully update, sent out sync message */
87698babcadSbeck if (syncsend && sync) {
8772c79ec66Sbeck if (spamtrap) {
8782c79ec66Sbeck syslog_r(LOG_DEBUG, &sdata,
8792c79ec66Sbeck "sync_trap %s", ip);
88098babcadSbeck sync_trapped(now, now + expire, ip);
8812c79ec66Sbeck }
88298babcadSbeck else
88398babcadSbeck sync_update(now, helo, ip, from, to);
88498babcadSbeck }
8851f68c1d4Sbeck return(0);
8861f68c1d4Sbeck bad:
8871f68c1d4Sbeck free(key);
8887066e321Sbeck key = NULL;
8891f68c1d4Sbeck db->close(db);
89084d82a5fSderaadt db = NULL;
8911f68c1d4Sbeck return(-1);
8921f68c1d4Sbeck }
8931f68c1d4Sbeck
8941f68c1d4Sbeck int
twread(char * buf)89520a5c6fdSderaadt twread(char *buf)
89620a5c6fdSderaadt {
89798babcadSbeck if ((strncmp(buf, "WHITE:", 6) == 0) ||
89898babcadSbeck (strncmp(buf, "TRAP:", 5) == 0)) {
89998babcadSbeck char **ap, *argv[5];
90098babcadSbeck int argc = 0;
90120a5c6fdSderaadt
90220a5c6fdSderaadt for (ap = argv;
90320a5c6fdSderaadt ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL;) {
90498babcadSbeck if (**ap != '\0')
90598babcadSbeck ap++;
90698babcadSbeck argc++;
90798babcadSbeck }
90898babcadSbeck *ap = NULL;
90998babcadSbeck if (argc != 4)
91098babcadSbeck return (-1);
91198babcadSbeck twupdate(PATH_SPAMD_DB, argv[0], argv[1], argv[2], argv[3]);
91220a5c6fdSderaadt return (0);
91398babcadSbeck } else
91420a5c6fdSderaadt return (-1);
91598babcadSbeck }
91698babcadSbeck
91798babcadSbeck int
greyreader(void)9181f68c1d4Sbeck greyreader(void)
9191f68c1d4Sbeck {
9202c79ec66Sbeck char cip[32], ip[32], helo[MAX_MAIL], from[MAX_MAIL], to[MAX_MAIL];
9212c79ec66Sbeck char *buf;
9221f68c1d4Sbeck size_t len;
92398babcadSbeck int state, sync;
924ef8fe961Sitojun struct addrinfo hints, *res;
925ef8fe961Sitojun
926ef8fe961Sitojun memset(&hints, 0, sizeof(hints));
92794c946baSitojun hints.ai_family = AF_INET; /*for now*/
928ef8fe961Sitojun hints.ai_socktype = SOCK_DGRAM; /*dummy*/
929ef8fe961Sitojun hints.ai_protocol = IPPROTO_UDP; /*dummy*/
930ef8fe961Sitojun hints.ai_flags = AI_NUMERICHOST;
9311f68c1d4Sbeck
9321f68c1d4Sbeck state = 0;
93398babcadSbeck sync = 1;
9348a6ccc17Sbeck if (grey == NULL) {
9358a6ccc17Sbeck syslog_r(LOG_ERR, &sdata, "No greylist pipe stream!\n");
93651210d5cSmillert return (-1);
9378a6ccc17Sbeck }
9382abcd8eaSbeck
9392abcd8eaSbeck /* grab trap suffixes */
9402abcd8eaSbeck readsuffixlists();
9412abcd8eaSbeck
9421f68c1d4Sbeck while ((buf = fgetln(grey, &len))) {
9431f68c1d4Sbeck if (buf[len - 1] == '\n')
9441f68c1d4Sbeck buf[len - 1] = '\0';
9451f68c1d4Sbeck else
9461f68c1d4Sbeck /* all valid lines end in \n */
9471f68c1d4Sbeck continue;
9481f68c1d4Sbeck if (strlen(buf) < 4)
9491f68c1d4Sbeck continue;
9501f68c1d4Sbeck
95198babcadSbeck if (strcmp(buf, "SYNC") == 0) {
95298babcadSbeck sync = 0;
95398babcadSbeck continue;
95498babcadSbeck }
95598babcadSbeck
9561f68c1d4Sbeck switch (state) {
9571f68c1d4Sbeck case 0:
95898babcadSbeck if (twread(buf) == 0) {
95998babcadSbeck state = 0;
96098babcadSbeck break;
96198babcadSbeck }
9625b0222eaSbeck if (strncmp(buf, "HE:", 3) != 0) {
9632c79ec66Sbeck if (strncmp(buf, "CO:", 3) == 0)
9642c79ec66Sbeck strlcpy(cip, buf+3, sizeof(cip));
9655b0222eaSbeck state = 0;
9665b0222eaSbeck break;
9675b0222eaSbeck }
9685b0222eaSbeck strlcpy(helo, buf+3, sizeof(helo));
9695b0222eaSbeck state = 1;
9705b0222eaSbeck break;
9715b0222eaSbeck case 1:
9721f68c1d4Sbeck if (strncmp(buf, "IP:", 3) != 0)
9731f68c1d4Sbeck break;
9741f68c1d4Sbeck strlcpy(ip, buf+3, sizeof(ip));
975ef8fe961Sitojun if (getaddrinfo(ip, NULL, &hints, &res) == 0) {
976ef8fe961Sitojun freeaddrinfo(res);
9775b0222eaSbeck state = 2;
978ef8fe961Sitojun } else
9791f68c1d4Sbeck state = 0;
9801f68c1d4Sbeck break;
9815b0222eaSbeck case 2:
9821f68c1d4Sbeck if (strncmp(buf, "FR:", 3) != 0) {
9831f68c1d4Sbeck state = 0;
9841f68c1d4Sbeck break;
9851f68c1d4Sbeck }
9861f68c1d4Sbeck strlcpy(from, buf+3, sizeof(from));
9875b0222eaSbeck state = 3;
9881f68c1d4Sbeck break;
9895b0222eaSbeck case 3:
9901f68c1d4Sbeck if (strncmp(buf, "TO:", 3) != 0) {
9911f68c1d4Sbeck state = 0;
9921f68c1d4Sbeck break;
9931f68c1d4Sbeck }
9941f68c1d4Sbeck strlcpy(to, buf+3, sizeof(to));
9951f68c1d4Sbeck if (debug)
9961f68c1d4Sbeck fprintf(stderr,
9975b0222eaSbeck "Got Grey HELO %s, IP %s from %s to %s\n",
9985b0222eaSbeck helo, ip, from, to);
9992c79ec66Sbeck greyupdate(PATH_SPAMD_DB, helo, ip, from, to, sync, cip);
10000af273bdSbeck sync = 1;
10011f68c1d4Sbeck state = 0;
10021f68c1d4Sbeck break;
10031f68c1d4Sbeck }
10041f68c1d4Sbeck }
10051f68c1d4Sbeck return (0);
10061f68c1d4Sbeck }
10071f68c1d4Sbeck
10081f68c1d4Sbeck void
greyscanner(void)10091f68c1d4Sbeck greyscanner(void)
10101f68c1d4Sbeck {
10111f68c1d4Sbeck for (;;) {
10124020b879Sbeck if (greyscan(PATH_SPAMD_DB) == -1)
10137066e321Sbeck syslog_r(LOG_NOTICE, &sdata, "scan of %s failed",
10147066e321Sbeck PATH_SPAMD_DB);
10154020b879Sbeck sleep(DB_SCAN_INTERVAL);
10161f68c1d4Sbeck }
10171f68c1d4Sbeck }
10181f68c1d4Sbeck
10194020b879Sbeck static void
drop_privs(void)10204020b879Sbeck drop_privs(void)
10211f68c1d4Sbeck {
10221f68c1d4Sbeck /*
10231f68c1d4Sbeck * lose root, continue as non-root user
10241f68c1d4Sbeck */
1025747158c7Smestre if (setgroups(1, &pw->pw_gid) ||
1026747158c7Smestre setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
1027747158c7Smestre setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) {
1028747158c7Smestre syslog_r(LOG_ERR, &sdata, "failed to drop privs (%m)");
1029747158c7Smestre exit(1);
10301f68c1d4Sbeck }
10314020b879Sbeck }
10324020b879Sbeck
10331cdc3374Sbeck void
check_spamd_db(void)10344020b879Sbeck check_spamd_db(void)
10354020b879Sbeck {
10364020b879Sbeck HASHINFO hashinfo;
10374020b879Sbeck int i = -1;
10384020b879Sbeck DB *db;
10394020b879Sbeck
10404020b879Sbeck /* check to see if /var/db/spamd exists, if not, create it */
10414020b879Sbeck memset(&hashinfo, 0, sizeof(hashinfo));
10424020b879Sbeck db = dbopen(PATH_SPAMD_DB, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
10434020b879Sbeck
10444020b879Sbeck if (db == NULL) {
10454020b879Sbeck switch (errno) {
10464020b879Sbeck case ENOENT:
10474020b879Sbeck i = open(PATH_SPAMD_DB, O_RDWR|O_CREAT, 0644);
10484020b879Sbeck if (i == -1) {
10494020b879Sbeck syslog_r(LOG_ERR, &sdata,
10504020b879Sbeck "create %s failed (%m)", PATH_SPAMD_DB);
10514020b879Sbeck exit(1);
10524020b879Sbeck }
10534020b879Sbeck /* if we are dropping privs, chown to that user */
10544020b879Sbeck if (pw && (fchown(i, pw->pw_uid, pw->pw_gid) == -1)) {
10554020b879Sbeck syslog_r(LOG_ERR, &sdata,
10564020b879Sbeck "chown %s failed (%m)", PATH_SPAMD_DB);
10574020b879Sbeck exit(1);
10584020b879Sbeck }
10594020b879Sbeck close(i);
10604020b879Sbeck return;
10614020b879Sbeck break;
10624020b879Sbeck default:
10634020b879Sbeck syslog_r(LOG_ERR, &sdata, "open of %s failed (%m)",
10644020b879Sbeck PATH_SPAMD_DB);
10654020b879Sbeck exit(1);
10664020b879Sbeck }
10674020b879Sbeck }
10684020b879Sbeck db->sync(db, 0);
10694020b879Sbeck db->close(db);
10704020b879Sbeck }
10714020b879Sbeck
10724020b879Sbeck
10734020b879Sbeck int
greywatcher(void)10744020b879Sbeck greywatcher(void)
10754020b879Sbeck {
10764020b879Sbeck struct sigaction sa;
10774020b879Sbeck
10781cdc3374Sbeck drop_privs();
10791f68c1d4Sbeck
1080800fd717Smestre if (unveil(PATH_SPAMD_DB, "rw") == -1) {
1081800fd717Smestre syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1082800fd717Smestre exit(1);
1083800fd717Smestre }
1084800fd717Smestre if (unveil(alloweddomains_file, "r") == -1) {
1085800fd717Smestre syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1086800fd717Smestre exit(1);
1087800fd717Smestre }
1088800fd717Smestre if (unveil(PATH_PFCTL, "x") == -1) {
1089800fd717Smestre syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1090800fd717Smestre exit(1);
1091800fd717Smestre }
10922d7c0bc3Sbeck if (pledge("stdio rpath wpath inet flock proc exec", NULL) == -1) {
10932d7c0bc3Sbeck syslog_r(LOG_ERR, &sdata, "pledge failed (%m)");
10942d7c0bc3Sbeck exit(1);
10952d7c0bc3Sbeck }
10962d7c0bc3Sbeck
1097ee505fa1Sbeck startup = time(NULL);
10988a6ccc17Sbeck db_pid = fork();
10998a6ccc17Sbeck switch (db_pid) {
11007066e321Sbeck case -1:
11018a6ccc17Sbeck syslog_r(LOG_ERR, &sdata, "fork failed (%m)");
11028a6ccc17Sbeck exit(1);
11037066e321Sbeck case 0:
11041f68c1d4Sbeck /*
11051f68c1d4Sbeck * child, talks to jailed spamd over greypipe,
11061f68c1d4Sbeck * updates db. has no access to pf.
11071f68c1d4Sbeck */
11081f68c1d4Sbeck close(pfdev);
11091f68c1d4Sbeck setproctitle("(%s update)", PATH_SPAMD_DB);
111051210d5cSmillert if (greyreader() == -1) {
111129c90403Sphessler syslog_r(LOG_ERR, &sdata, "greyreader failed (%m)");
11127066e321Sbeck _exit(1);
11137066e321Sbeck }
111451210d5cSmillert _exit(0);
111551210d5cSmillert }
11164020b879Sbeck
11172c79ec66Sbeck
11182c79ec66Sbeck fclose(grey);
11191f68c1d4Sbeck /*
11201f68c1d4Sbeck * parent, scans db periodically for changes and updates
11211f68c1d4Sbeck * pf whitelist table accordingly.
11221f68c1d4Sbeck */
11232c79ec66Sbeck
11243ac6234cSmoritz sigfillset(&sa.sa_mask);
11253ac6234cSmoritz sa.sa_flags = SA_RESTART;
11263ac6234cSmoritz sa.sa_handler = sig_term_chld;
11273ac6234cSmoritz sigaction(SIGTERM, &sa, NULL);
11283ac6234cSmoritz sigaction(SIGHUP, &sa, NULL);
11293ac6234cSmoritz sigaction(SIGCHLD, &sa, NULL);
11303ac6234cSmoritz sigaction(SIGINT, &sa, NULL);
11318a6ccc17Sbeck
11321f68c1d4Sbeck setproctitle("(pf <spamd-white> update)");
11331f68c1d4Sbeck greyscanner();
11347066e321Sbeck exit(1);
11351f68c1d4Sbeck }
1136