1*0033a982Smestre /* $OpenBSD: spamdb.c,v 1.36 2018/07/26 19:33:20 mestre Exp $ */
284d82a5fSderaadt
31f68c1d4Sbeck /*
41f68c1d4Sbeck * Copyright (c) 2004 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 <netinet/in.h>
221f68c1d4Sbeck #include <arpa/inet.h>
231f68c1d4Sbeck #include <db.h>
241f68c1d4Sbeck #include <err.h>
251f68c1d4Sbeck #include <fcntl.h>
261f68c1d4Sbeck #include <stdio.h>
271f68c1d4Sbeck #include <stdlib.h>
281f68c1d4Sbeck #include <string.h>
291f68c1d4Sbeck #include <time.h>
3085714724Sitojun #include <netdb.h>
311e913b92Sbeck #include <ctype.h>
324020b879Sbeck #include <errno.h>
338929f197Smillert #include <unistd.h>
341f68c1d4Sbeck
351f68c1d4Sbeck #include "grey.h"
361f68c1d4Sbeck
371e913b92Sbeck /* things we may add/delete from the db */
381e913b92Sbeck #define WHITE 0
391e913b92Sbeck #define TRAPHIT 1
401e913b92Sbeck #define SPAMTRAP 2
4188a0bb7eSmillert #define GREY 3
421e913b92Sbeck
43d0872fbcSbeck int dblist(DB *);
44d0872fbcSbeck int dbupdate(DB *, char *, int, int);
455ef50bb8Sdhill
461f68c1d4Sbeck int
dbupdate(DB * db,char * ip,int add,int type)47d0872fbcSbeck dbupdate(DB *db, char *ip, int add, int type)
481f68c1d4Sbeck {
49a9073584Sbeck DBT dbk, dbd;
501f68c1d4Sbeck struct gdata gd;
511f68c1d4Sbeck time_t now;
521f68c1d4Sbeck int r;
532364f7c1Sitojun struct addrinfo hints, *res;
541f68c1d4Sbeck
551f68c1d4Sbeck now = time(NULL);
560cf15a29Sitojun memset(&hints, 0, sizeof(hints));
5785714724Sitojun hints.ai_family = PF_UNSPEC;
5885714724Sitojun hints.ai_socktype = SOCK_DGRAM; /*dummy*/
5985714724Sitojun hints.ai_flags = AI_NUMERICHOST;
60d0872fbcSbeck if (add && (type == TRAPHIT || type == WHITE)) {
612364f7c1Sitojun if (getaddrinfo(ip, NULL, &hints, &res) != 0) {
6244dd9caeSotto warnx("invalid ip address %s", ip);
631f68c1d4Sbeck goto bad;
641f68c1d4Sbeck }
652364f7c1Sitojun freeaddrinfo(res);
661e913b92Sbeck }
671f68c1d4Sbeck memset(&dbk, 0, sizeof(dbk));
681f68c1d4Sbeck dbk.size = strlen(ip);
691f68c1d4Sbeck dbk.data = ip;
701f68c1d4Sbeck memset(&dbd, 0, sizeof(dbd));
711f68c1d4Sbeck if (!add) {
721e913b92Sbeck /* remove entry */
7388a0bb7eSmillert if (type == GREY) {
7488a0bb7eSmillert for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
7588a0bb7eSmillert r = db->seq(db, &dbk, &dbd, R_NEXT)) {
7688a0bb7eSmillert char *cp = memchr(dbk.data, '\n', dbk.size);
7788a0bb7eSmillert if (cp != NULL) {
7888a0bb7eSmillert size_t len = cp - (char *)dbk.data;
7988a0bb7eSmillert if (memcmp(ip, dbk.data, len) == 0 &&
8088a0bb7eSmillert ip[len] == '\0')
8188a0bb7eSmillert break;
8288a0bb7eSmillert }
8388a0bb7eSmillert }
8488a0bb7eSmillert } else {
851f68c1d4Sbeck r = db->get(db, &dbk, &dbd, 0);
861f68c1d4Sbeck if (r == -1) {
871f68c1d4Sbeck warn("db->get failed");
881f68c1d4Sbeck goto bad;
891f68c1d4Sbeck }
9088a0bb7eSmillert }
911f68c1d4Sbeck if (r) {
921f68c1d4Sbeck warnx("no entry for %s", ip);
931f68c1d4Sbeck goto bad;
941f68c1d4Sbeck } else if (db->del(db, &dbk, 0)) {
951f68c1d4Sbeck warn("db->del failed");
961f68c1d4Sbeck goto bad;
971f68c1d4Sbeck }
981f68c1d4Sbeck } else {
991e913b92Sbeck /* add or update entry */
1001f68c1d4Sbeck r = db->get(db, &dbk, &dbd, 0);
1011f68c1d4Sbeck if (r == -1) {
1021f68c1d4Sbeck warn("db->get failed");
1031f68c1d4Sbeck goto bad;
1041f68c1d4Sbeck }
1051f68c1d4Sbeck if (r) {
1061e913b92Sbeck int i;
1071e913b92Sbeck
1081f68c1d4Sbeck /* new entry */
1091f68c1d4Sbeck memset(&gd, 0, sizeof(gd));
1101f68c1d4Sbeck gd.first = now;
1111f68c1d4Sbeck gd.bcount = 1;
1121e913b92Sbeck switch (type) {
1131e913b92Sbeck case WHITE:
1141f68c1d4Sbeck gd.pass = now;
1151f68c1d4Sbeck gd.expire = now + WHITEEXP;
1161e913b92Sbeck break;
1171e913b92Sbeck case TRAPHIT:
1181e913b92Sbeck gd.expire = now + TRAPEXP;
1191e913b92Sbeck gd.pcount = -1;
1201e913b92Sbeck break;
1211e913b92Sbeck case SPAMTRAP:
1221e913b92Sbeck gd.expire = 0;
1231e913b92Sbeck gd.pcount = -2;
124a574b422Smillert /* ensure address is of the form user@host */
125a574b422Smillert if (strchr(ip, '@') == NULL)
126a574b422Smillert errx(-1, "not an email address: %s", ip);
1271e913b92Sbeck /* ensure address is lower case*/
1281e913b92Sbeck for (i = 0; ip[i] != '\0'; i++)
1292bfe0399Sderaadt if (isupper((unsigned char)ip[i]))
1302bfe0399Sderaadt ip[i] = tolower((unsigned char)ip[i]);
1311e913b92Sbeck break;
1321e913b92Sbeck default:
1331e913b92Sbeck errx(-1, "unknown type %d", type);
1341e913b92Sbeck }
1351f68c1d4Sbeck memset(&dbk, 0, sizeof(dbk));
1361f68c1d4Sbeck dbk.size = strlen(ip);
1371f68c1d4Sbeck dbk.data = ip;
1381f68c1d4Sbeck memset(&dbd, 0, sizeof(dbd));
1391f68c1d4Sbeck dbd.size = sizeof(gd);
1401f68c1d4Sbeck dbd.data = &gd;
1411f68c1d4Sbeck r = db->put(db, &dbk, &dbd, 0);
1421f68c1d4Sbeck if (r) {
1431f68c1d4Sbeck warn("db->put failed");
1441f68c1d4Sbeck goto bad;
1451f68c1d4Sbeck }
1461f68c1d4Sbeck } else {
14751210d5cSmillert if (gdcopyin(&dbd, &gd) == -1) {
1481f68c1d4Sbeck /* whatever this is, it doesn't belong */
1491f68c1d4Sbeck db->del(db, &dbk, 0);
1501f68c1d4Sbeck goto bad;
1511f68c1d4Sbeck }
1521f68c1d4Sbeck gd.pcount++;
1531e913b92Sbeck switch (type) {
1541e913b92Sbeck case WHITE:
1551e913b92Sbeck gd.pass = now;
1561f68c1d4Sbeck gd.expire = now + WHITEEXP;
1571e913b92Sbeck break;
1581e913b92Sbeck case TRAPHIT:
1591e913b92Sbeck gd.expire = now + TRAPEXP;
1601e913b92Sbeck gd.pcount = -1;
1611e913b92Sbeck break;
1621e913b92Sbeck case SPAMTRAP:
1631e913b92Sbeck gd.expire = 0; /* XXX */
1641e913b92Sbeck gd.pcount = -2;
1651e913b92Sbeck break;
1661e913b92Sbeck default:
1671e913b92Sbeck errx(-1, "unknown type %d", type);
1681e913b92Sbeck }
1691e913b92Sbeck
1701f68c1d4Sbeck memset(&dbk, 0, sizeof(dbk));
1711f68c1d4Sbeck dbk.size = strlen(ip);
1721f68c1d4Sbeck dbk.data = ip;
1731f68c1d4Sbeck memset(&dbd, 0, sizeof(dbd));
1741f68c1d4Sbeck dbd.size = sizeof(gd);
1751f68c1d4Sbeck dbd.data = &gd;
1761f68c1d4Sbeck r = db->put(db, &dbk, &dbd, 0);
1771f68c1d4Sbeck if (r) {
1781f68c1d4Sbeck warn("db->put failed");
1791f68c1d4Sbeck goto bad;
1801f68c1d4Sbeck }
1811f68c1d4Sbeck }
1821f68c1d4Sbeck }
1831f68c1d4Sbeck return (0);
1841f68c1d4Sbeck bad:
18544dd9caeSotto return (1);
1861f68c1d4Sbeck }
1871f68c1d4Sbeck
1881f68c1d4Sbeck int
print_entry(DBT * dbk,DBT * dbd)189de9ad6b5Smillert print_entry(DBT *dbk, DBT *dbd)
1901f68c1d4Sbeck {
1911f68c1d4Sbeck struct gdata gd;
1921f68c1d4Sbeck char *a, *cp;
1931f68c1d4Sbeck
194de9ad6b5Smillert if ((dbk->size < 1) || gdcopyin(dbd, &gd) == -1) {
195de9ad6b5Smillert warnx("bogus size db entry - bad db file?");
196de9ad6b5Smillert return (1);
1971f68c1d4Sbeck }
198de9ad6b5Smillert a = malloc(dbk->size + 1);
1991f68c1d4Sbeck if (a == NULL)
2001f68c1d4Sbeck err(1, "malloc");
201de9ad6b5Smillert memcpy(a, dbk->data, dbk->size);
202de9ad6b5Smillert a[dbk->size]='\0';
2031f68c1d4Sbeck cp = strchr(a, '\n');
2041e913b92Sbeck if (cp == NULL) {
2051e913b92Sbeck /* this is a non-greylist entry */
2061e913b92Sbeck switch (gd.pcount) {
2071e913b92Sbeck case -1: /* spamtrap hit, with expiry time */
2089d683440Sotto printf("TRAPPED|%s|%lld\n", a,
2099d683440Sotto (long long)gd.expire);
2101e913b92Sbeck break;
2111e913b92Sbeck case -2: /* spamtrap address */
2121e913b92Sbeck printf("SPAMTRAP|%s\n", a);
2131e913b92Sbeck break;
2141e913b92Sbeck default: /* whitelist */
2159d683440Sotto printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
2169d683440Sotto (long long)gd.first, (long long)gd.pass,
2179d683440Sotto (long long)gd.expire, gd.bcount,
2181e913b92Sbeck gd.pcount);
2191e913b92Sbeck break;
2201e913b92Sbeck }
2211e913b92Sbeck } else {
2225b0222eaSbeck char *helo, *from, *to;
2231f68c1d4Sbeck
2241f68c1d4Sbeck /* greylist entry */
2251f68c1d4Sbeck *cp = '\0';
2265b0222eaSbeck helo = cp + 1;
2275b0222eaSbeck from = strchr(helo, '\n');
2285b0222eaSbeck if (from == NULL) {
2291f68c1d4Sbeck warnx("No from part in grey key %s", a);
230f08b6076Sbeck free(a);
231de9ad6b5Smillert return (1);
2321f68c1d4Sbeck }
2335b0222eaSbeck *from = '\0';
2345b0222eaSbeck from++;
2355b0222eaSbeck to = strchr(from, '\n');
2365b0222eaSbeck if (to == NULL) {
2375b0222eaSbeck /* probably old format - print it the
2385b0222eaSbeck * with an empty HELO field instead
2395b0222eaSbeck * of erroring out.
2405b0222eaSbeck */
2419d683440Sotto printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
2429d683440Sotto a, "", helo, from, (long long)gd.first,
2439d683440Sotto (long long)gd.pass, (long long)gd.expire,
2449d683440Sotto gd.bcount, gd.pcount);
2455b0222eaSbeck
2465b0222eaSbeck } else {
2471f68c1d4Sbeck *to = '\0';
2481f68c1d4Sbeck to++;
2499d683440Sotto printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
2509d683440Sotto a, helo, from, to, (long long)gd.first,
2519d683440Sotto (long long)gd.pass, (long long)gd.expire,
2529d683440Sotto gd.bcount, gd.pcount);
2535b0222eaSbeck }
2541f68c1d4Sbeck }
255f08b6076Sbeck free(a);
256de9ad6b5Smillert
257de9ad6b5Smillert return (0);
258de9ad6b5Smillert }
259de9ad6b5Smillert
260de9ad6b5Smillert int
dblist(DB * db)261de9ad6b5Smillert dblist(DB *db)
262de9ad6b5Smillert {
263de9ad6b5Smillert DBT dbk, dbd;
264de9ad6b5Smillert int r;
265de9ad6b5Smillert
266de9ad6b5Smillert /* walk db, list in text format */
267de9ad6b5Smillert memset(&dbk, 0, sizeof(dbk));
268de9ad6b5Smillert memset(&dbd, 0, sizeof(dbd));
269de9ad6b5Smillert for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
270de9ad6b5Smillert r = db->seq(db, &dbk, &dbd, R_NEXT)) {
271de9ad6b5Smillert if (print_entry(&dbk, &dbd) != 0) {
272de9ad6b5Smillert r = -1;
273de9ad6b5Smillert break;
274de9ad6b5Smillert }
2751f68c1d4Sbeck }
2761f68c1d4Sbeck db->close(db);
27784d82a5fSderaadt db = NULL;
278de9ad6b5Smillert return (r == -1);
279de9ad6b5Smillert }
280de9ad6b5Smillert
281de9ad6b5Smillert int
dbshow(DB * db,char ** addrs)282de9ad6b5Smillert dbshow(DB *db, char **addrs)
283de9ad6b5Smillert {
284de9ad6b5Smillert DBT dbk, dbd;
285de9ad6b5Smillert int errors = 0;
286de9ad6b5Smillert char *a;
287de9ad6b5Smillert
288de9ad6b5Smillert /* look up each addr */
289de9ad6b5Smillert while ((a = *addrs) != NULL) {
290de9ad6b5Smillert memset(&dbk, 0, sizeof(dbk));
291de9ad6b5Smillert dbk.size = strlen(a);
292de9ad6b5Smillert dbk.data = a;
293de9ad6b5Smillert memset(&dbd, 0, sizeof(dbd));
294de9ad6b5Smillert switch (db->get(db, &dbk, &dbd, 0)) {
295de9ad6b5Smillert case -1:
296de9ad6b5Smillert warn("db->get failed");
297de9ad6b5Smillert errors++;
298de9ad6b5Smillert goto done;
299de9ad6b5Smillert case 0:
300de9ad6b5Smillert if (print_entry(&dbk, &dbd) != 0) {
301de9ad6b5Smillert errors++;
302de9ad6b5Smillert goto done;
303de9ad6b5Smillert }
304de9ad6b5Smillert break;
305de9ad6b5Smillert case 1:
306de9ad6b5Smillert default:
307de9ad6b5Smillert /* not found */
308de9ad6b5Smillert errors++;
309de9ad6b5Smillert break;
310de9ad6b5Smillert }
311de9ad6b5Smillert addrs++;
312de9ad6b5Smillert }
313de9ad6b5Smillert done:
3141f68c1d4Sbeck db->close(db);
31584d82a5fSderaadt db = NULL;
316de9ad6b5Smillert return (errors);
3171f68c1d4Sbeck }
3181f68c1d4Sbeck
31944dd9caeSotto extern char *__progname;
32044dd9caeSotto
3211f68c1d4Sbeck static int
usage(void)3221f68c1d4Sbeck usage(void)
3231f68c1d4Sbeck {
324de9ad6b5Smillert fprintf(stderr, "usage: %s [-adGTt] [keys ...]\n", __progname);
32584d82a5fSderaadt exit(1);
3261d7c8e7dSdhill /* NOTREACHED */
3271f68c1d4Sbeck }
3281f68c1d4Sbeck
3291f68c1d4Sbeck int
main(int argc,char ** argv)3301f68c1d4Sbeck main(int argc, char **argv)
3311f68c1d4Sbeck {
332e1fc2954Scnst int i, ch, action = 0, type = WHITE, r = 0, c = 0;
3334020b879Sbeck HASHINFO hashinfo;
334d0872fbcSbeck DB *db;
3351f68c1d4Sbeck
33688a0bb7eSmillert while ((ch = getopt(argc, argv, "adGtT")) != -1) {
3371f68c1d4Sbeck switch (ch) {
3381f68c1d4Sbeck case 'a':
3391f68c1d4Sbeck action = 1;
3401f68c1d4Sbeck break;
3411f68c1d4Sbeck case 'd':
3421f68c1d4Sbeck action = 2;
3431f68c1d4Sbeck break;
34488a0bb7eSmillert case 'G':
34588a0bb7eSmillert type = GREY;
34688a0bb7eSmillert break;
3471e913b92Sbeck case 't':
3481e913b92Sbeck type = TRAPHIT;
3491e913b92Sbeck break;
3501e913b92Sbeck case 'T':
3511e913b92Sbeck type = SPAMTRAP;
3521e913b92Sbeck break;
3531f68c1d4Sbeck default:
3541f68c1d4Sbeck usage();
3551f68c1d4Sbeck break;
3561f68c1d4Sbeck }
3571f68c1d4Sbeck }
358d0872fbcSbeck argc -= optind;
359d0872fbcSbeck argv += optind;
36003ac6a35Smillert if (action == 0 && type != WHITE)
36103ac6a35Smillert usage();
362d0872fbcSbeck
3634020b879Sbeck memset(&hashinfo, 0, sizeof(hashinfo));
3646a594376Sotto db = dbopen(PATH_SPAMD_DB, O_EXLOCK | (action ? O_RDWR : O_RDONLY),
3656a594376Sotto 0600, DB_HASH, &hashinfo);
3664020b879Sbeck if (db == NULL) {
3676a594376Sotto err(1, "cannot open %s for %s", PATH_SPAMD_DB,
3686a594376Sotto action ? "writing" : "reading");
3694020b879Sbeck }
3701f68c1d4Sbeck
371aa29652cSmestre if (pledge("stdio", NULL) == -1)
3728a51f366Smestre err(1, "pledge");
3731a6938c5Sderaadt
3741f68c1d4Sbeck switch (action) {
3751f68c1d4Sbeck case 0:
376de9ad6b5Smillert if (argc)
377de9ad6b5Smillert return dbshow(db, argv);
378de9ad6b5Smillert else
379d0872fbcSbeck return dblist(db);
3801f68c1d4Sbeck case 1:
38188a0bb7eSmillert if (type == GREY)
38288a0bb7eSmillert errx(2, "cannot add GREY entries");
383d0872fbcSbeck for (i=0; i<argc; i++)
384e1fc2954Scnst if (argv[i][0] != '\0') {
385e1fc2954Scnst c++;
386d0872fbcSbeck r += dbupdate(db, argv[i], 1, type);
387e1fc2954Scnst }
388e1fc2954Scnst if (c == 0)
389e1fc2954Scnst errx(2, "no addresses specified");
390d0872fbcSbeck break;
3911f68c1d4Sbeck case 2:
392d0872fbcSbeck for (i=0; i<argc; i++)
393e1fc2954Scnst if (argv[i][0] != '\0') {
394e1fc2954Scnst c++;
395d0872fbcSbeck r += dbupdate(db, argv[i], 0, type);
396e1fc2954Scnst }
397e1fc2954Scnst if (c == 0)
398e1fc2954Scnst errx(2, "no addresses specified");
399d0872fbcSbeck break;
4001f68c1d4Sbeck default:
4011f68c1d4Sbeck errx(-1, "bad action");
4021f68c1d4Sbeck }
403d0872fbcSbeck db->close(db);
404d0872fbcSbeck return (r);
4051f68c1d4Sbeck }
406