/* $Id: relaydb.c,v 1.15 2003/12/18 12:17:00 dhartmei Exp $ */ /* * Copyright (c) 2003 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ static const char rcsid[] = "$Id: relaydb.c,v 1.15 2003/12/18 12:17:00 dhartmei Exp $"; #include #include #include #include #include #include #include #include struct data { int white; int black; time_t mtime; }; struct data_old { int white; int black; }; extern char *__progname; const int bufsiz = 1024; const int factor = 3; int debug = 0; int action = 0; int reverse = 0; int traverse = 1; int use_v4 = 1; int use_v6 = 1; BTREEINFO btreeinfo; DB *db; DBT dbk, dbd; int read_data(const DBT *, struct data *); int address_valid_ipv4(const char *); int address_valid_ipv6(const char *); int address_private(const char *); int check(const char *); void read_headers(void); void import_file(const char *); void usage(void); int read_data(const DBT *dbd, struct data *d) { if (dbd->size == sizeof(*d)) memcpy(d, dbd->data, sizeof(*d)); else if (dbd->size == sizeof(struct data_old)) { struct data_old o; memcpy(&o, dbd->data, sizeof(o)); d->white = o.white; d->black = o.black; d->mtime = 0; } else return (1); return (0); } int address_valid_v4(const char *a) { if (!*a) return (0); while (*a) if ((*a >= '0' && *a <= '9') || *a == '.') a++; else return (0); return (1); } int address_valid_v6(const char *a) { if (!*a) return (0); while (*a) if ((*a >= '0' && *a <= '9') || (*a >= 'a' && *a <= 'f') || (*a >= 'A' && *a <= 'F') || *a == ':') a++; else return (0); return (1); } int address_private(const char *a) { if (!strcmp(a, "::1")) return (1); if (!strncmp(a, "127.", 4) || !strncmp(a, "10.", 3) || !strncmp(a, "172.16.", 7) || !strncmp(a, "192.168.", 8)) return (1); return (0); } int check(const char *address) { int r; struct data d; if (!strncmp(address, "IPv6:", 5)) address += 5; if (!((use_v4 && address_valid_v4(address)) || (use_v6 && address_valid_v6(address)))) { if (debug) printf("invalid address '%s'\n", address); return (1); } else if (address_private(address)) return (0); if (debug) printf("checking %s\n", address); memset(&dbk, 0, sizeof(dbk)); dbk.size = strlen(address); dbk.data = (char *)address; memset(&dbd, 0, sizeof(dbd)); r = db->get(db, &dbk, &dbd, 0); if (r < 0) { fprintf(stderr, "db->get() %s\n", strerror(errno)); return (1); } if (r) { if (debug) printf(" not found, inserting new host %s %s 1\n", address, (action == 'b' ? "black" : "white")); memset(&d, 0, sizeof(d)); if (action == 'b') d.black = 1; else d.white = 1; d.mtime = time(NULL); memset(&dbk, 0, sizeof(dbk)); dbk.size = strlen(address); dbk.data = (char *)address; memset(&dbd, 0, sizeof(dbd)); dbd.size = sizeof(d); dbd.data = &d; r = db->put(db, &dbk, &dbd, 0); if (r) fprintf(stderr, "db->put() %s\n", strerror(errno)); return (1); } else { if (read_data(&dbd, &d)) { fprintf(stderr, "db->get() invalid data\n"); return (1); } if (debug) { printf(" found, "); if (action == 'b') printf("white %d black %d -> %d\n", d.white, d.black, d.black + 1); else printf("white %d -> %d black %d\n", d.white, d.white + 1, d.black); } if (action == 'b') { if (reverse) { if (d.black > 0) d.black--; } else d.black++; } else { if (reverse) { if (d.white > 0) d.white--; } else d.white++; } d.mtime = time(NULL); memset(&dbk, 0, sizeof(dbk)); dbk.size = strlen(address); dbk.data = (char *)address; memset(&dbd, 0, sizeof(dbd)); dbd.size = sizeof(d); dbd.data = &d; r = db->put(db, &dbk, &dbd, 0); if (r) { fprintf(stderr, "db->put() %s\n", strerror(errno)); return (1); } if (!traverse || d.black >= factor * d.white) { if (debug) printf("ignoring further headers\n"); return (1); } else { if (debug) printf("checking next header\n"); return (0); } } } void read_headers() { char buf[bufsiz], c; int pos = 0; int hdr = 1; while (hdr && fread(&c, 1, 1, stdin) > 0) { if (pos == bufsiz || c == '\n') { buf[pos] = 0; if (!pos) hdr = 0; pos = 0; if (hdr) { if (!strncmp(buf, "Received:", 9)) { char *b, *e; b = strchr(buf, '['); e = strchr(buf, ']'); if (b != NULL && e != NULL && b < e) { *e = 0; if (check(b + 1)) break; } } } } else buf[pos++] = c; } } time_t parse_syslog_time(const char *s) { const char *names[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; char mon[4]; time_t t = time(NULL); struct tm tm; memcpy(&tm, localtime(&t), sizeof(tm)); if (sscanf(s, "%3s %d %d:%d:%d", mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 5) return (0); for (tm.tm_mon = 0; names[tm.tm_mon]; ++tm.tm_mon) if (!strcmp(names[tm.tm_mon], mon)) break; if (names[tm.tm_mon] == NULL) return (0); return (mktime(&tm)); } void parse_syslog(const char *filename) { FILE *f; char buf[bufsiz], address[128], c; int pos = 0, r; struct data d; unsigned count = 0; if (debug) printf("reading syslog %s\n", filename); f = fopen(filename, "r"); if (f == NULL) { fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno)); return; } while (fread(&c, 1, 1, f) > 0) if (pos == bufsiz || c == '\n') { char *p; time_t mtime; buf[pos] = 0; pos = 0; p = strstr(buf, ": connected ("); if (p == NULL || strstr(buf, " spamd[") == NULL) continue; *p = 0; p = strrchr(buf, ':'); if (p == NULL || p[1] != ' ') continue; strlcpy(address, p + 2, sizeof(address)); if (!((use_v4 && address_valid_v4(address)) || (use_v6 && address_valid_v6(address))) || address_private(address)) continue; if (!(mtime = parse_syslog_time(buf))) continue; memset(&dbk, 0, sizeof(dbk)); dbk.size = strlen(address); dbk.data = address; memset(&dbd, 0, sizeof(dbd)); r = db->get(db, &dbk, &dbd, 0); if (r < 0) { fprintf(stderr, "db->get() %s\n", strerror(errno)); goto done; } if (r) continue; if (read_data(&dbd, &d)) { fprintf(stderr, "db->get() invalid data\n"); goto done; } if (d.mtime >= mtime) continue; if (debug) printf("touching %lu %s\n", (unsigned long)mtime, address); d.mtime = mtime; memset(&dbk, 0, sizeof(dbk)); dbk.size = strlen(address); dbk.data = address; memset(&dbd, 0, sizeof(dbd)); dbd.size = sizeof(d); dbd.data = &d; r = db->put(db, &dbk, &dbd, 0); if (r) { fprintf(stderr, "db->put() %s\n", strerror(errno)); goto done; } count++; } else buf[pos++] = c; done: fclose(f); printf("%u entries touched\n", count); } void import_file(const char *filename) { FILE *f; char buf[bufsiz], address[128], c; int pos = 0, r; struct data d; unsigned count = 0; if (debug) printf("importing %s\n", filename); memset(&d, 0, sizeof(d)); f = fopen(filename, "r"); if (f == NULL) { fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno)); return; } while (fread(&c, 1, 1, f) > 0) if (pos == bufsiz || c == '\n') { unsigned long mtime; buf[pos] = 0; pos = 0; r = sscanf(buf, "%127s %d %d %lu", address, &d.white, &d.black, &mtime); if (r == 4) d.mtime = mtime; else if (r == 3) d.mtime = time(NULL); else { fprintf(stderr, "sscanf() invalid input '%s'\n", buf); fclose(f); return; } if (!((use_v4 && address_valid_v4(address)) || (use_v6 && address_valid_v6(address))) || address_private(address)) continue; if (debug) printf("adding %s %d %d %lu\n", address, d.white, d.black, (unsigned long)d.mtime); memset(&dbk, 0, sizeof(dbk)); dbk.size = strlen(address); dbk.data = address; memset(&dbd, 0, sizeof(dbd)); dbd.size = sizeof(d); dbd.data = &d; r = db->put(db, &dbk, &dbd, 0); if (r) { fprintf(stderr, "db->put() %s\n", strerror(errno)); fclose(f); return; } count++; } else buf[pos++] = c; fclose(f); printf("%u entries imported\n", count); } void usage() { fprintf(stderr, "usage: %s [-46bdlnrvw] " "[-BW [+-]num] [-m [+-]days]\n\t[-f filename] " "[-i filename] [-t filename]\n", __progname); exit(1); } int main(int argc, char *argv[]) { int list = 0, delete = 0; const char *filename = NULL, *import = NULL, *syslog = NULL; time_t mtime = 0; int mtime_op = 0; int black = -1, white = -1; int black_op = 0, white_op = 0; int ch; unsigned count = 0; while ((ch = getopt(argc, argv, "46bB:df:i:lm:nrt:vwW:")) != -1) { switch (ch) { case '4': use_v4 = 1; use_v6 = 0; break; case '6': use_v6 = 1; use_v4 = 0; break; case 'b': case 'w': action = ch; break; case 'B': if (*optarg == '+') { black_op = 1; optarg++; } else if (*optarg == '-') { black_op = -1; optarg++; } black = atol(optarg); break; case 'W': if (*optarg == '+') { white_op = 1; optarg++; } else if (*optarg == '-') { white_op = -1; optarg++; } white = atol(optarg); break; case 'd': delete = 1; break; case 'f': filename = optarg; break; case 'i': import = optarg; break; case 'l': list = 1; break; case 'm': if (*optarg == '+') { mtime_op = 1; optarg++; } else if (*optarg == '-') { mtime_op = -1; optarg++; } mtime = time(NULL) - atol(optarg) * 60 * 60 * 24; break; case 'n': traverse = 0; break; case 'r': reverse = 1; break; case 't': syslog = optarg; break; case 'v': debug++; break; default: usage(); } } if (!list && !delete && !action && import == NULL && syslog == NULL) usage(); if (delete && !action && !mtime && black == -1 && white == -1) { fprintf(stderr, "to delete all entries, delete the file\n"); return (1); } if (filename == NULL) { const char *home = getenv("HOME"); const char *file = "/.relaydb"; int len; char *fn; if (home == NULL) { fprintf(stderr, "-f not specified and $HOME undefined\n"); return (1); } len = strlen(home)+strlen(file)+1; fn = (char *)malloc(len); if (fn == NULL) { fprintf(stderr, "malloc: %s\n", strerror(errno)); return (1); } strlcpy(fn, home, len); strlcat(fn, file, len); filename = fn; } memset(&btreeinfo, 0, sizeof(btreeinfo)); db = dbopen(filename, O_CREAT|O_EXLOCK|(list ? O_RDONLY : O_RDWR), 0600, DB_BTREE, &btreeinfo); if (db == NULL) { fprintf(stderr, "dbopen: %s: %s\n", filename, strerror(errno)); return (1); } if (syslog != NULL) parse_syslog(syslog); else if (import != NULL) import_file(import); else if (list || delete) { int r; struct data d; char a[128]; memset(&dbk, 0, sizeof(dbk)); memset(&dbd, 0, sizeof(dbd)); for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r; r = db->seq(db, &dbk, &dbd, R_NEXT)) { if (dbk.size < 1 || dbk.size >= sizeof(a) || read_data(&dbd, &d)) { fprintf(stderr, "db->seq() invalid data\n"); if (db->close(db)) fprintf(stderr, "db->close() %s\n", strerror(errno)); return (1); } if (black != -1) if ((black_op == 0 && d.black != black) || (black_op == 1 && d.black < black) || (black_op == -1 && d.black > black)) continue; if (white != -1) if ((white_op == 0 && d.white != white) || (white_op == 1 && d.white < white) || (white_op == -1 && d.white > white)) continue; if (action == 'b' && d.black <= factor * d.white) continue; if (action == 'w' && d.black > factor * d.white) continue; if (mtime) if ((mtime_op == 0 && (d.mtime < mtime - 60 * 60 * 24 || d.mtime > mtime)) || (mtime_op == 1 && d.mtime > mtime - 60 * 60 * 24) || (mtime_op == -1 && d.mtime < mtime)) continue; memcpy(a, dbk.data, dbk.size); a[dbk.size] = 0; if (!((use_v4 && address_valid_v4(a)) || (use_v6 && address_valid_v6(a)))) continue; if (list) { if (debug) printf("%s %d %d %lu\n", a, d.white, d.black, (unsigned long)d.mtime); else printf("%s\n", a); } else { if (debug) printf("deleting %s\n", a); if (db->del(db, &dbk, 0)) { fprintf(stderr, "db->del() %s\n", strerror(errno)); db->sync(db, 0); db->close(db); return (1); } count++; } } } else { if (debug) printf("reading mail headers, considering the mail " "%sspam\n", (action == 'b' ? "" : "not ")); read_headers(); } if (delete && !list) printf("%u entries deleted\n", count); if (!list && db->sync(db, 0)) fprintf(stderr, "db->sync() %s\n", strerror(errno)); if (db->close(db)) fprintf(stderr, "db->close() %s\n", strerror(errno)); return (0); }