xref: /openbsd/usr.sbin/spamdb/spamdb.c (revision fc61954a)
1 /*	$OpenBSD: spamdb.c,v 1.30 2015/11/11 16:10:21 deraadt 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 
42 int	dblist(DB *);
43 int	dbupdate(DB *, char *, int, int);
44 
45 int
46 dbupdate(DB *db, char *ip, int add, int type)
47 {
48 	DBT		dbk, dbd;
49 	struct gdata	gd;
50 	time_t		now;
51 	int		r;
52 	struct addrinfo hints, *res;
53 
54 	now = time(NULL);
55 	memset(&hints, 0, sizeof(hints));
56 	hints.ai_family = PF_UNSPEC;
57 	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
58 	hints.ai_flags = AI_NUMERICHOST;
59 	if (add && (type == TRAPHIT || type == WHITE)) {
60 		if (getaddrinfo(ip, NULL, &hints, &res) != 0) {
61 			warnx("invalid ip address %s", ip);
62 			goto bad;
63 		}
64 		freeaddrinfo(res);
65 	}
66 	memset(&dbk, 0, sizeof(dbk));
67 	dbk.size = strlen(ip);
68 	dbk.data = ip;
69 	memset(&dbd, 0, sizeof(dbd));
70 	if (!add) {
71 		/* remove entry */
72 		r = db->get(db, &dbk, &dbd, 0);
73 		if (r == -1) {
74 			warn("db->get failed");
75 			goto bad;
76 		}
77 		if (r) {
78 			warnx("no entry for %s", ip);
79 			goto bad;
80 		} else if (db->del(db, &dbk, 0)) {
81 			warn("db->del failed");
82 			goto bad;
83 		}
84 	} else {
85 		/* add or update entry */
86 		r = db->get(db, &dbk, &dbd, 0);
87 		if (r == -1) {
88 			warn("db->get failed");
89 			goto bad;
90 		}
91 		if (r) {
92 			int i;
93 
94 			/* new entry */
95 			memset(&gd, 0, sizeof(gd));
96 			gd.first = now;
97 			gd.bcount = 1;
98 			switch (type) {
99 			case WHITE:
100 				gd.pass = now;
101 				gd.expire = now + WHITEEXP;
102 				break;
103 			case TRAPHIT:
104 				gd.expire = now + TRAPEXP;
105 				gd.pcount = -1;
106 				break;
107 			case SPAMTRAP:
108 				gd.expire = 0;
109 				gd.pcount = -2;
110 				/* ensure address is of the form user@host */
111 				if (strchr(ip, '@') == NULL)
112 					errx(-1, "not an email address: %s", ip);
113 				/* ensure address is lower case*/
114 				for (i = 0; ip[i] != '\0'; i++)
115 					if (isupper((unsigned char)ip[i]))
116 						ip[i] = tolower((unsigned char)ip[i]);
117 				break;
118 			default:
119 				errx(-1, "unknown type %d", type);
120 			}
121 			memset(&dbk, 0, sizeof(dbk));
122 			dbk.size = strlen(ip);
123 			dbk.data = ip;
124 			memset(&dbd, 0, sizeof(dbd));
125 			dbd.size = sizeof(gd);
126 			dbd.data = &gd;
127 			r = db->put(db, &dbk, &dbd, 0);
128 			if (r) {
129 				warn("db->put failed");
130 				goto bad;
131 			}
132 		} else {
133 			if (gdcopyin(&dbd, &gd) == -1) {
134 				/* whatever this is, it doesn't belong */
135 				db->del(db, &dbk, 0);
136 				goto bad;
137 			}
138 			gd.pcount++;
139 			switch (type) {
140 			case WHITE:
141 				gd.pass = now;
142 				gd.expire = now + WHITEEXP;
143 				break;
144 			case TRAPHIT:
145 				gd.expire = now + TRAPEXP;
146 				gd.pcount = -1;
147 				break;
148 			case SPAMTRAP:
149 				gd.expire = 0; /* XXX */
150 				gd.pcount = -2;
151 				break;
152 			default:
153 				errx(-1, "unknown type %d", type);
154 			}
155 
156 			memset(&dbk, 0, sizeof(dbk));
157 			dbk.size = strlen(ip);
158 			dbk.data = ip;
159 			memset(&dbd, 0, sizeof(dbd));
160 			dbd.size = sizeof(gd);
161 			dbd.data = &gd;
162 			r = db->put(db, &dbk, &dbd, 0);
163 			if (r) {
164 				warn("db->put failed");
165 				goto bad;
166 			}
167 		}
168 	}
169 	return (0);
170  bad:
171 	return (1);
172 }
173 
174 int
175 dblist(DB *db)
176 {
177 	DBT		dbk, dbd;
178 	struct gdata	gd;
179 	int		r;
180 
181 	/* walk db, list in text format */
182 	memset(&dbk, 0, sizeof(dbk));
183 	memset(&dbd, 0, sizeof(dbd));
184 	for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
185 	    r = db->seq(db, &dbk, &dbd, R_NEXT)) {
186 		char *a, *cp;
187 
188 		if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
189 			db->close(db);
190 			errx(1, "bogus size db entry - bad db file?");
191 		}
192 		a = malloc(dbk.size + 1);
193 		if (a == NULL)
194 			err(1, "malloc");
195 		memcpy(a, dbk.data, dbk.size);
196 		a[dbk.size]='\0';
197 		cp = strchr(a, '\n');
198 		if (cp == NULL) {
199 			/* this is a non-greylist entry */
200 			switch (gd.pcount) {
201 			case -1: /* spamtrap hit, with expiry time */
202 				printf("TRAPPED|%s|%lld\n", a,
203 				    (long long)gd.expire);
204 				break;
205 			case -2: /* spamtrap address */
206 				printf("SPAMTRAP|%s\n", a);
207 				break;
208 			default: /* whitelist */
209 				printf("WHITE|%s|||%lld|%lld|%lld|%d|%d\n", a,
210 				    (long long)gd.first, (long long)gd.pass,
211 				    (long long)gd.expire, gd.bcount,
212 				    gd.pcount);
213 				break;
214 			}
215 		} else {
216 			char *helo, *from, *to;
217 
218 			/* greylist entry */
219 			*cp = '\0';
220 			helo = cp + 1;
221 			from = strchr(helo, '\n');
222 			if (from == NULL) {
223 				warnx("No from part in grey key %s", a);
224 				free(a);
225 				goto bad;
226 			}
227 			*from = '\0';
228 			from++;
229 			to = strchr(from, '\n');
230 			if (to == NULL) {
231 				/* probably old format - print it the
232 				 * with an empty HELO field instead
233 				 * of erroring out.
234 				 */
235 				printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
236 				    a, "", helo, from, (long long)gd.first,
237 				    (long long)gd.pass, (long long)gd.expire,
238 				    gd.bcount, gd.pcount);
239 
240 			} else {
241 				*to = '\0';
242 				to++;
243 				printf("GREY|%s|%s|%s|%s|%lld|%lld|%lld|%d|%d\n",
244 				    a, helo, from, to, (long long)gd.first,
245 				    (long long)gd.pass, (long long)gd.expire,
246 				    gd.bcount, gd.pcount);
247 			}
248 		}
249 		free(a);
250 	}
251 	db->close(db);
252 	db = NULL;
253 	return (0);
254  bad:
255 	db->close(db);
256 	db = NULL;
257 	errx(1, "incorrect db format entry");
258 	/* NOTREACHED */
259 	return (1);
260 }
261 
262 extern char *__progname;
263 
264 static int
265 usage(void)
266 {
267 	fprintf(stderr, "usage: %s [[-Tt] -a keys] [[-Tt] -d keys]\n", __progname);
268 	exit(1);
269 	/* NOTREACHED */
270 }
271 
272 int
273 main(int argc, char **argv)
274 {
275 	int i, ch, action = 0, type = WHITE, r = 0, c = 0;
276 	HASHINFO	hashinfo;
277 	DB		*db;
278 
279 	while ((ch = getopt(argc, argv, "adtT")) != -1) {
280 		switch (ch) {
281 		case 'a':
282 			action = 1;
283 			break;
284 		case 'd':
285 			action = 2;
286 			break;
287 		case 't':
288 			type = TRAPHIT;
289 			break;
290 		case 'T':
291 			type = SPAMTRAP;
292 			break;
293 		default:
294 			usage();
295 			break;
296 		}
297 	}
298 	argc -= optind;
299 	argv += optind;
300 	if (action == 0 && type != WHITE)
301 		usage();
302 
303 	memset(&hashinfo, 0, sizeof(hashinfo));
304 	db = dbopen(PATH_SPAMD_DB, O_EXLOCK | (action ? O_RDWR : O_RDONLY),
305 	    0600, DB_HASH, &hashinfo);
306 	if (db == NULL) {
307 		err(1, "cannot open %s for %s", PATH_SPAMD_DB,
308 		    action ? "writing" : "reading");
309 	}
310 
311 	if (pledge("stdio rpath wpath", NULL) == -1)
312 		err(1, "pledge");
313 
314 	switch (action) {
315 	case 0:
316 		return dblist(db);
317 	case 1:
318 		for (i=0; i<argc; i++)
319 			if (argv[i][0] != '\0') {
320 				c++;
321 				r += dbupdate(db, argv[i], 1, type);
322 			}
323 		if (c == 0)
324 			errx(2, "no addresses specified");
325 		break;
326 	case 2:
327 		for (i=0; i<argc; i++)
328 			if (argv[i][0] != '\0') {
329 				c++;
330 				r += dbupdate(db, argv[i], 0, type);
331 			}
332 		if (c == 0)
333 			errx(2, "no addresses specified");
334 		break;
335 	default:
336 		errx(-1, "bad action");
337 	}
338 	db->close(db);
339 	return (r);
340 }
341