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