1 /* $OpenBSD: spamdb.c,v 1.36 2018/07/26 19:33:20 mestre 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 (pledge("stdio", NULL) == -1) 372 err(1, "pledge"); 373 374 switch (action) { 375 case 0: 376 if (argc) 377 return dbshow(db, argv); 378 else 379 return dblist(db); 380 case 1: 381 if (type == GREY) 382 errx(2, "cannot add GREY entries"); 383 for (i=0; i<argc; i++) 384 if (argv[i][0] != '\0') { 385 c++; 386 r += dbupdate(db, argv[i], 1, type); 387 } 388 if (c == 0) 389 errx(2, "no addresses specified"); 390 break; 391 case 2: 392 for (i=0; i<argc; i++) 393 if (argv[i][0] != '\0') { 394 c++; 395 r += dbupdate(db, argv[i], 0, type); 396 } 397 if (c == 0) 398 errx(2, "no addresses specified"); 399 break; 400 default: 401 errx(-1, "bad action"); 402 } 403 db->close(db); 404 return (r); 405 } 406