1 /* $OpenBSD: spamdb.c,v 1.36 2018/07/26 19:33:20 mestre Exp $ */
2
3 /*
4 * Copyright (c) 2004 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 <netinet/in.h>
22 #include <arpa/inet.h>
23 #include <db.h>
24 #include <err.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30 #include <netdb.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <unistd.h>
34
35 #include "grey.h"
36
37 /* things we may add/delete from the db */
38 #define WHITE 0
39 #define TRAPHIT 1
40 #define SPAMTRAP 2
41 #define GREY 3
42
43 int dblist(DB *);
44 int dbupdate(DB *, char *, int, int);
45
46 int
dbupdate(DB * db,char * ip,int add,int type)47 dbupdate(DB *db, char *ip, int add, int type)
48 {
49 DBT dbk, dbd;
50 struct gdata gd;
51 time_t now;
52 int r;
53 struct addrinfo hints, *res;
54
55 now = time(NULL);
56 memset(&hints, 0, sizeof(hints));
57 hints.ai_family = PF_UNSPEC;
58 hints.ai_socktype = SOCK_DGRAM; /*dummy*/
59 hints.ai_flags = AI_NUMERICHOST;
60 if (add && (type == TRAPHIT || type == WHITE)) {
61 if (getaddrinfo(ip, NULL, &hints, &res) != 0) {
62 warnx("invalid ip address %s", ip);
63 goto bad;
64 }
65 freeaddrinfo(res);
66 }
67 memset(&dbk, 0, sizeof(dbk));
68 dbk.size = strlen(ip);
69 dbk.data = ip;
70 memset(&dbd, 0, sizeof(dbd));
71 if (!add) {
72 /* remove entry */
73 if (type == GREY) {
74 for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
75 r = db->seq(db, &dbk, &dbd, R_NEXT)) {
76 char *cp = memchr(dbk.data, '\n', dbk.size);
77 if (cp != NULL) {
78 size_t len = cp - (char *)dbk.data;
79 if (memcmp(ip, dbk.data, len) == 0 &&
80 ip[len] == '\0')
81 break;
82 }
83 }
84 } else {
85 r = db->get(db, &dbk, &dbd, 0);
86 if (r == -1) {
87 warn("db->get failed");
88 goto bad;
89 }
90 }
91 if (r) {
92 warnx("no entry for %s", ip);
93 goto bad;
94 } else if (db->del(db, &dbk, 0)) {
95 warn("db->del failed");
96 goto bad;
97 }
98 } else {
99 /* add or update entry */
100 r = db->get(db, &dbk, &dbd, 0);
101 if (r == -1) {
102 warn("db->get failed");
103 goto bad;
104 }
105 if (r) {
106 int i;
107
108 /* new entry */
109 memset(&gd, 0, sizeof(gd));
110 gd.first = now;
111 gd.bcount = 1;
112 switch (type) {
113 case WHITE:
114 gd.pass = now;
115 gd.expire = now + WHITEEXP;
116 break;
117 case TRAPHIT:
118 gd.expire = now + TRAPEXP;
119 gd.pcount = -1;
120 break;
121 case SPAMTRAP:
122 gd.expire = 0;
123 gd.pcount = -2;
124 /* ensure address is of the form user@host */
125 if (strchr(ip, '@') == NULL)
126 errx(-1, "not an email address: %s", ip);
127 /* ensure address is lower case*/
128 for (i = 0; ip[i] != '\0'; i++)
129 if (isupper((unsigned char)ip[i]))
130 ip[i] = tolower((unsigned char)ip[i]);
131 break;
132 default:
133 errx(-1, "unknown type %d", type);
134 }
135 memset(&dbk, 0, sizeof(dbk));
136 dbk.size = strlen(ip);
137 dbk.data = ip;
138 memset(&dbd, 0, sizeof(dbd));
139 dbd.size = sizeof(gd);
140 dbd.data = &gd;
141 r = db->put(db, &dbk, &dbd, 0);
142 if (r) {
143 warn("db->put failed");
144 goto bad;
145 }
146 } else {
147 if (gdcopyin(&dbd, &gd) == -1) {
148 /* whatever this is, it doesn't belong */
149 db->del(db, &dbk, 0);
150 goto bad;
151 }
152 gd.pcount++;
153 switch (type) {
154 case WHITE:
155 gd.pass = now;
156 gd.expire = now + WHITEEXP;
157 break;
158 case TRAPHIT:
159 gd.expire = now + TRAPEXP;
160 gd.pcount = -1;
161 break;
162 case SPAMTRAP:
163 gd.expire = 0; /* XXX */
164 gd.pcount = -2;
165 break;
166 default:
167 errx(-1, "unknown type %d", type);
168 }
169
170 memset(&dbk, 0, sizeof(dbk));
171 dbk.size = strlen(ip);
172 dbk.data = ip;
173 memset(&dbd, 0, sizeof(dbd));
174 dbd.size = sizeof(gd);
175 dbd.data = &gd;
176 r = db->put(db, &dbk, &dbd, 0);
177 if (r) {
178 warn("db->put failed");
179 goto bad;
180 }
181 }
182 }
183 return (0);
184 bad:
185 return (1);
186 }
187
188 int
print_entry(DBT * dbk,DBT * dbd)189 print_entry(DBT *dbk, DBT *dbd)
190 {
191 struct gdata gd;
192 char *a, *cp;
193
194 if ((dbk->size < 1) || gdcopyin(dbd, &gd) == -1) {
195 warnx("bogus size db entry - bad db file?");
196 return (1);
197 }
198 a = malloc(dbk->size + 1);
199 if (a == NULL)
200 err(1, "malloc");
201 memcpy(a, dbk->data, dbk->size);
202 a[dbk->size]='\0';
203 cp = strchr(a, '\n');
204 if (cp == NULL) {
205 /* this is a non-greylist entry */
206 switch (gd.pcount) {
207 case -1: /* spamtrap hit, with expiry time */
208 printf("TRAPPED|%s|%lld\n", a,
209 (long long)gd.expire);
210 break;
211 case -2: /* spamtrap address */
212 printf("SPAMTRAP|%s\n", a);
213 break;
214 default: /* whitelist */
215 printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
216 (long long)gd.first, (long long)gd.pass,
217 (long long)gd.expire, gd.bcount,
218 gd.pcount);
219 break;
220 }
221 } else {
222 char *helo, *from, *to;
223
224 /* greylist entry */
225 *cp = '\0';
226 helo = cp + 1;
227 from = strchr(helo, '\n');
228 if (from == NULL) {
229 warnx("No from part in grey key %s", a);
230 free(a);
231 return (1);
232 }
233 *from = '\0';
234 from++;
235 to = strchr(from, '\n');
236 if (to == NULL) {
237 /* probably old format - print it the
238 * with an empty HELO field instead
239 * of erroring out.
240 */
241 printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
242 a, "", helo, from, (long long)gd.first,
243 (long long)gd.pass, (long long)gd.expire,
244 gd.bcount, gd.pcount);
245
246 } else {
247 *to = '\0';
248 to++;
249 printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
250 a, helo, from, to, (long long)gd.first,
251 (long long)gd.pass, (long long)gd.expire,
252 gd.bcount, gd.pcount);
253 }
254 }
255 free(a);
256
257 return (0);
258 }
259
260 int
dblist(DB * db)261 dblist(DB *db)
262 {
263 DBT dbk, dbd;
264 int r;
265
266 /* walk db, list in text format */
267 memset(&dbk, 0, sizeof(dbk));
268 memset(&dbd, 0, sizeof(dbd));
269 for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
270 r = db->seq(db, &dbk, &dbd, R_NEXT)) {
271 if (print_entry(&dbk, &dbd) != 0) {
272 r = -1;
273 break;
274 }
275 }
276 db->close(db);
277 db = NULL;
278 return (r == -1);
279 }
280
281 int
dbshow(DB * db,char ** addrs)282 dbshow(DB *db, char **addrs)
283 {
284 DBT dbk, dbd;
285 int errors = 0;
286 char *a;
287
288 /* look up each addr */
289 while ((a = *addrs) != NULL) {
290 memset(&dbk, 0, sizeof(dbk));
291 dbk.size = strlen(a);
292 dbk.data = a;
293 memset(&dbd, 0, sizeof(dbd));
294 switch (db->get(db, &dbk, &dbd, 0)) {
295 case -1:
296 warn("db->get failed");
297 errors++;
298 goto done;
299 case 0:
300 if (print_entry(&dbk, &dbd) != 0) {
301 errors++;
302 goto done;
303 }
304 break;
305 case 1:
306 default:
307 /* not found */
308 errors++;
309 break;
310 }
311 addrs++;
312 }
313 done:
314 db->close(db);
315 db = NULL;
316 return (errors);
317 }
318
319 extern char *__progname;
320
321 static int
usage(void)322 usage(void)
323 {
324 fprintf(stderr, "usage: %s [-adGTt] [keys ...]\n", __progname);
325 exit(1);
326 /* NOTREACHED */
327 }
328
329 int
main(int argc,char ** argv)330 main(int argc, char **argv)
331 {
332 int i, ch, action = 0, type = WHITE, r = 0, c = 0;
333 HASHINFO hashinfo;
334 DB *db;
335
336 while ((ch = getopt(argc, argv, "adGtT")) != -1) {
337 switch (ch) {
338 case 'a':
339 action = 1;
340 break;
341 case 'd':
342 action = 2;
343 break;
344 case 'G':
345 type = GREY;
346 break;
347 case 't':
348 type = TRAPHIT;
349 break;
350 case 'T':
351 type = SPAMTRAP;
352 break;
353 default:
354 usage();
355 break;
356 }
357 }
358 argc -= optind;
359 argv += optind;
360 if (action == 0 && type != WHITE)
361 usage();
362
363 memset(&hashinfo, 0, sizeof(hashinfo));
364 db = dbopen(PATH_SPAMD_DB, O_EXLOCK | (action ? O_RDWR : O_RDONLY),
365 0600, DB_HASH, &hashinfo);
366 if (db == NULL) {
367 err(1, "cannot open %s for %s", PATH_SPAMD_DB,
368 action ? "writing" : "reading");
369 }
370
371 if (pledge("stdio", NULL) == -1)
372 err(1, "pledge");
373
374 switch (action) {
375 case 0:
376 if (argc)
377 return dbshow(db, argv);
378 else
379 return dblist(db);
380 case 1:
381 if (type == GREY)
382 errx(2, "cannot add GREY entries");
383 for (i=0; i<argc; i++)
384 if (argv[i][0] != '\0') {
385 c++;
386 r += dbupdate(db, argv[i], 1, type);
387 }
388 if (c == 0)
389 errx(2, "no addresses specified");
390 break;
391 case 2:
392 for (i=0; i<argc; i++)
393 if (argv[i][0] != '\0') {
394 c++;
395 r += dbupdate(db, argv[i], 0, type);
396 }
397 if (c == 0)
398 errx(2, "no addresses specified");
399 break;
400 default:
401 errx(-1, "bad action");
402 }
403 db->close(db);
404 return (r);
405 }
406