/* * Copyright (c) 2003-2008 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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 AUTHOR 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. */ /* * Lookup table for spam checker statistics per IPv4/IPv6 address. */ #include #include #include #include #include #include #include #include #include #include #include #include "mailblockd.h" #define TBL_FIELD_SEP " \t" /* Initialize a table. */ void TBL_Init(TBL *t, const char *path, u_int nBuckets) { int i; if ((t->path = strdup(path)) == NULL) { MBD_Fatal("Out of memory"); } t->nbuckets = nBuckets; t->buckets = Malloc(nBuckets*sizeof(TBL_Bucket)); t->count = 0; for (i = 0; i < nBuckets; i++) SLIST_INIT(&t->buckets[i].ents); } /* Release the resources allocated by a table. */ void TBL_Destroy(TBL *t) { TBL_Entry *ent, *nent; int i; for (i = 0; i < t->nbuckets; i++) { for (ent = SLIST_FIRST(&t->buckets[i].ents); ent != SLIST_END(&t->buckets[i].ents); ent = nent) { nent = SLIST_NEXT(ent, bents); free(ent->key); free(ent); } } free(t->buckets); free(t->path); free(t); } #if 0 int TBL_Rehash(TBL **t, u_int nbuckets) { char path[FILENAME_MAX]; char tempPath[16]; int fd; Debug("Rehashing: %d -> %d buckets", (*t)->nbuckets, nbuckets); Strlcpy(path, (*t)->path, sizeof(path)); Strlcpy(tempPath, "/tmp/mbdXXXXXX", sizeof(tempPath)); if ((fd = mkstemp(tempPath)) == -1) { MBD_SetError("mkstemp: %s", strerror(errno)); return (-1); } if (TBL_SaveFD(*t, fd) == -1) { close(fd); return (-1); } close(fd); TBL_Destroy(*t); if ((*t = TBL_Load(tempPath, nbuckets)) == NULL) { return (-1); } unlink(tempPath); return (0); } #endif /* Insert a new table entry. */ TBL_Entry * TBL_Insert(TBL *ta, u_int h, const char *key) { TBL_Entry *ent; if (ta->nbuckets == 0) { MBD_SetError("Zero-sized table"); return (NULL); } if ((ent = malloc(sizeof(TBL_Entry))) == NULL) { goto outofmem; } if ((ent->key = strdup(key)) == NULL) { free(ent); goto outofmem; } SLIST_INSERT_HEAD(&ta->buckets[h].ents, ent, bents); ta->buckets[h].nents++; ta->count++; return (ent); outofmem: MBD_SetError("Out of memory"); return (NULL); } void TBL_DeleteEntry(TBL *ta, u_int h, TBL_Entry *ent) { SLIST_REMOVE(&ta->buckets[h].ents, ent, tbl_entry, bents); free(ent->key); free(ent); if (ta->count > 0) { ta->count--; } else { syslog(LOG_ERR, "Bogus table count!"); } } int TBL_Delete(TBL *ta, const char *key) { TBL_Entry *ent; u_int h = TBL_Hash(ta,key); if ((ent = TBL_LookupHash(ta, key, h)) == NULL) { MBD_SetError("Element not found in table: %s", key); return (-1); } TBL_DeleteEntry(ta, h, ent); return (0); } static int ParseLine(TBL *ta, char *s) { TBL_Entry *nent; char *key, *p; u_int h; int i; if ((key = strsep(&s, TBL_FIELD_SEP)) == NULL || key[0] == '\0') { MBD_SetError("Bad key"); return (-1); } if ((nent = malloc(sizeof(TBL_Entry))) == NULL) { goto outofmem; } if ((nent->key = strdup(key)) == NULL) { free(nent); goto outofmem; } /* Read the count */ if ((p = strsep(&s, TBL_FIELD_SEP)) == NULL) { nent->count = 1; } else { if ((nent->count = (unsigned int)strtoul(p, NULL, 10)) < 1) nent->count = 1; } /* Read the average score (redundant but saves us some CPU) */ if ((p = strsep(&s, TBL_FIELD_SEP)) == NULL) { nent->scoreAvg = 0.0; } else { nent->scoreAvg = (unsigned int)strtoul(p, NULL, 10); } /* Read the total score */ if ((p = strsep(&s, TBL_FIELD_SEP)) == NULL) { nent->scoreTot = 0.0; } else { nent->scoreTot = (unsigned int)strtoul(p, NULL, 10); } /* Read the timestamp */ if ((p = strsep(&s, TBL_FIELD_SEP)) == NULL) { nent->stamp = 0; } else { nent->stamp = strtol(p, NULL, 10); } /* Insert into the table */ h = TBL_Hash(ta, nent->key); SLIST_INSERT_HEAD(&ta->buckets[h].ents, nent, bents); ta->buckets[h].nents++; ta->count++; return (0); fail: free(nent->key); free(nent); return (-1); outofmem: MBD_SetError("Out of memory"); return (-1); } /* * Load a table from the given file. Leading spacing characters and characters * between '#' and end of line are ignored. */ TBL * TBL_Load(const char *path, u_int nBuckets) { TBL *ta; char *data, *p, *eol; size_t len, nRead; ssize_t rv; u_int nLines; int fd; if ((fd = open(path, O_RDONLY)) == -1) { MBD_SetError("%s: %s", path, strerror(errno)); return (NULL); } len = (size_t)lseek(fd, 0, SEEK_END); (void)lseek(fd, 0, SEEK_SET); if (len == 0) { ta = Malloc(sizeof(TBL)); TBL_Init(ta, path, (nBuckets == -1) ? 0 : nBuckets); close(fd); return (ta); } if ((data = malloc(len+1)) == NULL) { MBD_SetError("Out of memory for data"); close(fd); return (NULL); } for (nRead = 0; nRead < len; ) { rv = read(fd, &data[nRead], len-nRead); if (rv == -1) { if (errno == EINTR) { continue; } goto fail_read; } else if (rv == 0) { goto fail_read; } nRead += rv; } close(fd); data[len] = '\0'; ta = Malloc(sizeof(TBL)); if (nBuckets == -1) { /* Auto-size */ nLines = 0; for (p = &data[0]; *p != '\0'; p++) { if (*p == '\n') nLines++; } TBL_Init(ta, path, nLines); } else { TBL_Init(ta, path, nBuckets); } nLines = 0; for (p = &data[0]; *p != '\0'; p++) { while (isspace(*p)) { p++; } if (*p == '#') { while (*(++p) != '\n') ;; } for (eol = p; *eol != '\n' && *eol != '\0'; eol++) { if (*eol == '#') { for (eol--; isspace(*eol); eol--) ;; eol++; break; } } *eol = '\0'; if (p < eol) { char *sp; for (sp = &eol[-1]; isspace(*sp); sp--) ;; sp[1] = '\0'; if (ParseLine(ta, p) == -1) { Debug("%s:%u: %s", path, nLines, MBD_GetError()); syslog(LOG_ERR, "%s:%u: %s", path, nLines, MBD_GetError()); } } p = eol; nLines++; } free(data); return (ta); fail_read: free(data); close(fd); return (NULL); } /* Save the contents of the given table to the named file. */ int TBL_Save(TBL *t, const char *path, mode_t mode) { TBL_Entry *ent; int fd, rv; if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0600)) == -1) { MBD_SetError("%s: %s", path, strerror(errno)); return (-1); } rv = TBL_SaveFD(t, fd); close(fd); return (rv); } /* Save the contents of the given table to an open fd. */ int TBL_SaveFD(TBL *ta, int fd) { char buf[80]; TBL_Entry *ent; size_t nWrote, len; ssize_t rv; u_int i; for (i = 0; i < ta->nbuckets; i++) { SLIST_FOREACH(ent, &ta->buckets[i].ents, bents) { snprintf(buf, sizeof(buf), "%s %ld %f %f %ld\n", ent->key, ent->count, ent->scoreAvg, ent->scoreTot, ent->stamp); len = strlen(buf); for (nWrote = 0; nWrote < len; ) { rv = write(fd, &buf[nWrote], len-nWrote); if (rv == -1) { if (errno == EINTR) { continue; } else { goto fail_write; } } else if (rv == 0) { goto fail_write; } nWrote += rv; } } } return (0); fail_write: MBD_SetError("Write error"); return (-1); }