1 /**	$MirOS: src/usr.sbin/spamdb/spamdb.c,v 1.6 2007/03/09 13:05:10 tg Exp $ */
2 /*	$OpenBSD: spamdb.c,v 1.22 2007/02/27 16:22:11 otto Exp $	*/
3 
4 /*
5  * Copyright (c) 2004 Bob Beck.  All rights reserved.
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <netinet/in.h>
23 #include <arpa/inet.h>
24 #include <db.h>
25 #include <err.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31 #include <netdb.h>
32 #include <ctype.h>
33 #include <errno.h>
34 
35 #include "grey.h"
36 
37 __RCSID("$MirOS: src/usr.sbin/spamdb/spamdb.c,v 1.6 2007/03/09 13:05:10 tg Exp $");
38 
39 /* things we may add/delete from the db */
40 #define WHITE 0
41 #define TRAPHIT 1
42 #define SPAMTRAP 2
43 
44 int	dblist(DB *);
45 int	dbupdate(DB *, char *, int, int);
46 
47 int
dbupdate(DB * db,char * ip,int add,int type)48 dbupdate(DB *db, char *ip, int add, int type)
49 {
50 	DBT		dbk, dbd;
51 	struct gdata	gd;
52 	time_t		now;
53 	int		r;
54 	struct addrinfo hints, *res;
55 
56 	now = time(NULL);
57 	memset(&hints, 0, sizeof(hints));
58 	hints.ai_family = PF_UNSPEC;
59 	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
60 	hints.ai_flags = AI_NUMERICHOST;
61 	if (add && (type == TRAPHIT || type == WHITE)) {
62 		if (getaddrinfo(ip, NULL, &hints, &res) != 0) {
63 			warnx("invalid ip address %s", ip);
64 			goto bad;
65 		}
66 		freeaddrinfo(res);
67 	}
68 	memset(&dbk, 0, sizeof(dbk));
69 	dbk.size = strlen(ip);
70 	dbk.data = ip;
71 	memset(&dbd, 0, sizeof(dbd));
72 	if (!add) {
73 		/* remove entry */
74 		r = db->get(db, &dbk, &dbd, 0);
75 		if (r == -1) {
76 			warn("db->get failed");
77 			goto bad;
78 		}
79 		if (r) {
80 			warnx("no entry for %s", ip);
81 			goto bad;
82 		} else if (db->del(db, &dbk, 0)) {
83 			warn("db->del failed");
84 			goto bad;
85 		}
86 	} else {
87 		/* add or update entry */
88 		r = db->get(db, &dbk, &dbd, 0);
89 		if (r == -1) {
90 			warn("db->get failed");
91 			goto bad;
92 		}
93 		if (r) {
94 			int i;
95 
96 			/* new entry */
97 			memset(&gd, 0, sizeof(gd));
98 			gd.first = now;
99 			gd.bcount = 1;
100 			switch (type) {
101 			case WHITE:
102 				gd.pass = now;
103 				gd.expire = now + WHITEEXP;
104 				break;
105 			case TRAPHIT:
106 				gd.expire = now + TRAPEXP;
107 				gd.pcount = -1;
108 				break;
109 			case SPAMTRAP:
110 				gd.expire = 0;
111 				gd.pcount = -2;
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
dblist(DB * db)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|%lld\n", a, (int64_t)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 				    (int64_t)gd.first, (int64_t)gd.pass,
211 				    (int64_t)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, (int64_t)gd.first,
237 				    (int64_t)gd.pass, (int64_t)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, (int64_t)gd.first,
245 				    (int64_t)gd.pass, (int64_t)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
usage(void)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
main(int argc,char ** argv)273 main(int argc, char **argv)
274 {
275 	int i, ch, action = 0, type = WHITE, r = 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 		if (errno == EFTYPE)
308 			err(1,
309 			    "%s is old, run current spamd to convert it",
310 			    PATH_SPAMD_DB);
311 		else
312 			err(1, "cannot open %s for %s", PATH_SPAMD_DB,
313 			    action ? "writing" : "reading");
314 	}
315 
316 	switch (action) {
317 	case 0:
318 		return dblist(db);
319 	case 1:
320 		for (i=0; i<argc; i++)
321 			r += dbupdate(db, argv[i], 1, type);
322 		break;
323 	case 2:
324 		for (i=0; i<argc; i++)
325 			r += dbupdate(db, argv[i], 0, type);
326 		break;
327 	default:
328 		errx(-1, "bad action");
329 	}
330 	db->close(db);
331 	return (r);
332 }
333