xref: /openbsd/usr.sbin/spamdb/spamdb.c (revision 73471bf0)
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
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
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
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
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
322 usage(void)
323 {
324 	fprintf(stderr, "usage: %s [-adGTt] [keys ...]\n", __progname);
325 	exit(1);
326 	/* NOTREACHED */
327 }
328 
329 int
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