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