GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/spamdb/spamdb.c Lines: 0 217 0.0 %
Date: 2017-11-13 Branches: 0 121 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: spamdb.c,v 1.34 2017/10/29 19:11:34 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
#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 (action == 0) {
372
		if (pledge("stdio rpath flock cpath wpath", NULL) == -1)
373
			err(1, "pledge");
374
	} else {
375
		if (pledge("stdio rpath wpath flock cpath", NULL) == -1)
376
			err(1, "pledge");
377
	}
378
379
	switch (action) {
380
	case 0:
381
		if (argc)
382
			return dbshow(db, argv);
383
		else
384
			return dblist(db);
385
	case 1:
386
		if (type == GREY)
387
			errx(2, "cannot add GREY entries");
388
		for (i=0; i<argc; i++)
389
			if (argv[i][0] != '\0') {
390
				c++;
391
				r += dbupdate(db, argv[i], 1, type);
392
			}
393
		if (c == 0)
394
			errx(2, "no addresses specified");
395
		break;
396
	case 2:
397
		for (i=0; i<argc; i++)
398
			if (argv[i][0] != '\0') {
399
				c++;
400
				r += dbupdate(db, argv[i], 0, type);
401
			}
402
		if (c == 0)
403
			errx(2, "no addresses specified");
404
		break;
405
	default:
406
		errx(-1, "bad action");
407
	}
408
	db->close(db);
409
	return (r);
410
}