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