1 /* $OpenBSD: spamdb.c,v 1.29 2013/11/24 01:06:19 deraadt 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 42 int dblist(DB *); 43 int dbupdate(DB *, char *, int, int); 44 45 int 46 dbupdate(DB *db, char *ip, int add, int type) 47 { 48 DBT dbk, dbd; 49 struct gdata gd; 50 time_t now; 51 int r; 52 struct addrinfo hints, *res; 53 54 now = time(NULL); 55 memset(&hints, 0, sizeof(hints)); 56 hints.ai_family = PF_UNSPEC; 57 hints.ai_socktype = SOCK_DGRAM; /*dummy*/ 58 hints.ai_flags = AI_NUMERICHOST; 59 if (add && (type == TRAPHIT || type == WHITE)) { 60 if (getaddrinfo(ip, NULL, &hints, &res) != 0) { 61 warnx("invalid ip address %s", ip); 62 goto bad; 63 } 64 freeaddrinfo(res); 65 } 66 memset(&dbk, 0, sizeof(dbk)); 67 dbk.size = strlen(ip); 68 dbk.data = ip; 69 memset(&dbd, 0, sizeof(dbd)); 70 if (!add) { 71 /* remove entry */ 72 r = db->get(db, &dbk, &dbd, 0); 73 if (r == -1) { 74 warn("db->get failed"); 75 goto bad; 76 } 77 if (r) { 78 warnx("no entry for %s", ip); 79 goto bad; 80 } else if (db->del(db, &dbk, 0)) { 81 warn("db->del failed"); 82 goto bad; 83 } 84 } else { 85 /* add or update entry */ 86 r = db->get(db, &dbk, &dbd, 0); 87 if (r == -1) { 88 warn("db->get failed"); 89 goto bad; 90 } 91 if (r) { 92 int i; 93 94 /* new entry */ 95 memset(&gd, 0, sizeof(gd)); 96 gd.first = now; 97 gd.bcount = 1; 98 switch (type) { 99 case WHITE: 100 gd.pass = now; 101 gd.expire = now + WHITEEXP; 102 break; 103 case TRAPHIT: 104 gd.expire = now + TRAPEXP; 105 gd.pcount = -1; 106 break; 107 case SPAMTRAP: 108 gd.expire = 0; 109 gd.pcount = -2; 110 /* ensure address is of the form user@host */ 111 if (strchr(ip, '@') == NULL) 112 errx(-1, "not an email address: %s", ip); 113 /* ensure address is lower case*/ 114 for (i = 0; ip[i] != '\0'; i++) 115 if (isupper((unsigned char)ip[i])) 116 ip[i] = tolower((unsigned char)ip[i]); 117 break; 118 default: 119 errx(-1, "unknown type %d", type); 120 } 121 memset(&dbk, 0, sizeof(dbk)); 122 dbk.size = strlen(ip); 123 dbk.data = ip; 124 memset(&dbd, 0, sizeof(dbd)); 125 dbd.size = sizeof(gd); 126 dbd.data = &gd; 127 r = db->put(db, &dbk, &dbd, 0); 128 if (r) { 129 warn("db->put failed"); 130 goto bad; 131 } 132 } else { 133 if (gdcopyin(&dbd, &gd) == -1) { 134 /* whatever this is, it doesn't belong */ 135 db->del(db, &dbk, 0); 136 goto bad; 137 } 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) || gdcopyin(&dbd, &gd) == -1) { 189 db->close(db); 190 errx(1, "bogus size db entry - bad db file?"); 191 } 192 a = malloc(dbk.size + 1); 193 if (a == NULL) 194 err(1, "malloc"); 195 memcpy(a, dbk.data, dbk.size); 196 a[dbk.size]='\0'; 197 cp = strchr(a, '\n'); 198 if (cp == NULL) { 199 /* this is a non-greylist entry */ 200 switch (gd.pcount) { 201 case -1: /* spamtrap hit, with expiry time */ 202 printf("TRAPPED|%s|%lld\n", a, 203 (long long)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 (long long)gd.first, (long long)gd.pass, 211 (long long)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, (long long)gd.first, 237 (long long)gd.pass, (long long)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, (long long)gd.first, 245 (long long)gd.pass, (long long)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 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 273 main(int argc, char **argv) 274 { 275 int i, ch, action = 0, type = WHITE, r = 0, c = 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 err(1, "cannot open %s for %s", PATH_SPAMD_DB, 308 action ? "writing" : "reading"); 309 } 310 311 switch (action) { 312 case 0: 313 return dblist(db); 314 case 1: 315 for (i=0; i<argc; i++) 316 if (argv[i][0] != '\0') { 317 c++; 318 r += dbupdate(db, argv[i], 1, type); 319 } 320 if (c == 0) 321 errx(2, "no addresses specified"); 322 break; 323 case 2: 324 for (i=0; i<argc; i++) 325 if (argv[i][0] != '\0') { 326 c++; 327 r += dbupdate(db, argv[i], 0, type); 328 } 329 if (c == 0) 330 errx(2, "no addresses specified"); 331 break; 332 default: 333 errx(-1, "bad action"); 334 } 335 db->close(db); 336 return (r); 337 } 338