#include #include #include #include #include #include #include #include #include #include "config.h" #include "authdb.h" #include "filterdb.h" #include "addr.h" #include "blob.h" #define ALIGN(s,a) (((s) + a - 1) & ~(a - 1)) #define AUTHDB_IP_PER_ME 256 #define AUTHDB_SHM_SIZE ALIGN(sizeof(struct authdb_entry[AUTHDB_IP_PER_ME]), 4096) static struct authdb_map_entry *authdb_me_open(sockaddr_any *addr, int create) { int oflag, fd; char name[64], buf[256]; blob_t b = BLOB_BUF(name); void *base; struct authdb_map_entry *me; struct group grp, *res; blob_push(&b, BLOB_STR("squark-auth-")); blob_push_hexdump(&b, addr_get_hostaddr_blob(addr)); blob_push_byte(&b, 0); oflag = O_RDWR; if (create) oflag |= O_CREAT; fd = shm_open(name, oflag, 0660); if (fd < 0) return NULL; if (ftruncate(fd, AUTHDB_SHM_SIZE) < 0) { close(fd); return NULL; } getgrnam_r("squark", &grp, buf, sizeof(buf), &res); if (res != NULL) { fchown(fd, -1, res->gr_gid); fchmod(fd, 0660); } base = mmap(NULL, AUTHDB_SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); if (base == MAP_FAILED) return NULL; me = malloc(sizeof(*me)); if (me == NULL) { munmap(base, AUTHDB_SHM_SIZE); return NULL; } me->next = NULL; me->baseaddr = *addr; me->entries = base; return me; } static void authdb_me_free(struct authdb_map_entry *me) { munmap(me->entries, AUTHDB_SHM_SIZE); free(me); } int authdb_open(struct authdb *adb, struct authdb_config *cfg, struct sqdb *db) { memset(adb, 0, sizeof(*adb)); memset(cfg, 0, sizeof(*cfg)); cfg->db = db; return adbc_refresh(cfg, time(NULL)); } void authdb_close(struct authdb *adb) { struct authdb_map_entry *c, *n; int i; for (i = 0; i < ARRAY_SIZE(adb->hash_bucket); i++) { for (c = adb->hash_bucket[i]; c != NULL; c = n) { n = c->next; authdb_me_free(c); } } } static unsigned int MurmurHash2(const void * key, int len, unsigned int seed) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const unsigned int m = 0x5bd1e995; const int r = 24; unsigned int h = seed ^ len; const unsigned char * data = (const unsigned char *)key; while(len >= 4) { unsigned int k = *(unsigned int *)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; } h ^= h >> 13; h *= m; h ^= h >> 15; return h; } static uint32_t authdb_entry_checksum(struct authdb_entry *entry) { return MurmurHash2(&entry->p, sizeof(entry->p), 0); } void *authdb_get(struct authdb *adb, sockaddr_any *addr, struct authdb_entry *entry, int create) { struct authdb_map_entry *me; unsigned int hash, e, i; sockaddr_any baseaddr; blob_t b; baseaddr = *addr; b = addr_get_hostaddr_blob(&baseaddr); if (b.len < 4) return NULL; e = (unsigned char) b.ptr[0]; b.ptr[3] = 0x00; hash = b.ptr[0] + b.ptr[1] + b.ptr[2]; hash %= ARRAY_SIZE(adb->hash_bucket); for (me = adb->hash_bucket[hash]; me != NULL; me = me->next) { if (addr_cmp(&baseaddr, &me->baseaddr) == 0) break; } if (me == NULL) { me = authdb_me_open(&baseaddr, create); if (me == NULL) return NULL; me->next = adb->hash_bucket[hash]; adb->hash_bucket[hash] = me; } for (i = 0; i < 3; i++) { memcpy(entry, &me->entries[e], sizeof(struct authdb_entry)); if (entry->checksum == 0 && entry->p.login_time == 0) return &me->entries[e]; if (entry->checksum == authdb_entry_checksum(entry)) return &me->entries[e]; sched_yield(); } authdb_clear_entry(entry); return &me->entries[e]; } int authdb_set(void *token, struct authdb_entry *entry) { struct authdb_entry *mme = token; uint32_t checksum = entry->checksum; entry->checksum = authdb_entry_checksum(entry); if (mme->checksum != checksum) return 0; mme->checksum = ~0; memcpy(mme, entry, sizeof(*entry)); return 1; } int authdb_check_login(void *token, struct authdb_entry *e, blob_t username, time_t now, struct authdb_config *adbc) { struct authdb_entry *mme = token; /* check username */ if (!blob_is_null(username) && blob_cmp(username, BLOB_STRLEN(e->p.login_name)) != 0) return 0; /* and dates */ if (now > e->last_activity_time + adbc->logout_timeout) return 0; /* and that no one clobbered the entry */ if (mme->checksum != e->checksum) return 0; /* refresh last activity -- avoid writes to page so * caches don't get invalidated too often */ if (now > mme->last_activity_time + 2) mme->last_activity_time = now; return 1; } void authdb_clear_entry(struct authdb_entry *entry) { uint32_t checksum = entry->checksum; memset(entry, 0, sizeof(*entry)); entry->checksum = checksum; } void authdb_commit_login(void *token, struct authdb_entry *e, time_t now, struct authdb_config *cfg) { e->p.block_categories = cfg->block_categories; e->p.hard_block_categories = cfg->hard_block_categories; e->p.login_time = now; e->last_activity_time = now; e->override_time = 0; authdb_set(token, e); } void authdb_commit_logout(void *token) { memset(token, 0, sizeof(struct authdb_entry)); } void authdb_commit_override(void *token, struct authdb_entry *e, time_t now) { struct authdb_entry *mme = token; mme->override_time = now; } static blob_t read_word(FILE *in, int *lineno, blob_t b) { int ch, i, comment = 0; blob_t r; ch = fgetc(in); while (1) { if (ch == EOF) return BLOB_NULL; if (ch == '#') comment = 1; if (!comment && !isspace(ch)) break; if (ch == '\n') { (*lineno)++; comment = 0; } ch = fgetc(in); } r.ptr = b.ptr; r.len = 0; for (i = 0; i < b.len-1 && !isspace(ch); i++, r.len++) { r.ptr[i] = ch; ch = fgetc(in); if (ch == EOF) break; if (ch == '\n') (*lineno)++; } return r; } static int find_category_id(struct sqdb *db, blob_t cat) { uint32_t size, *ptr; int i; ptr = sqdb_section_get(db, SQDB_SECTION_CATEGORIES, &size); if (ptr == NULL) return -1; size /= sizeof(uint32_t); for (i = 0; i < size; i++) if (blob_cmp(cat, sqdb_get_string_literal(db, ptr[i])) == 0) return i; return -1; } static inline uint64_t to_category(struct sqdb *db, blob_t c) { int category; category = find_category_id(db, c); if (category >= 0) return 1ULL << category; fprintf(stderr, "WARNING: unknown category '%.*s'\n", c.len, c.ptr); return 0; } static int is_true(blob_t p) { if (blob_icmp(p, BLOB_STR("yes")) == 0 || blob_icmp(p, BLOB_STR("true")) == 0 || blob_icmp(p, BLOB_STR("1")) == 0) return 1; return 0; } int adbc_refresh(struct authdb_config *cfg, time_t now) { FILE *in; int lineno = 1; char word1[64], word2[64]; blob_t b, p; struct stat st; if (cfg->last_check != 0 && cfg->last_check + 2*60 > now) return 0; if (stat(squark_config, &st) != 0) return -1; cfg->last_check = now; if (cfg->last_change == st.st_ctime) return 0; in = fopen(squark_config, "r"); if (in == NULL) return -1; cfg->last_change = st.st_ctime; cfg->block_categories = 0; cfg->hard_block_categories = 0; cfg->logout_timeout = DEFAULT_LOGOUT_TIMEOUT; cfg->require_auth = 1; while (1) { b = read_word(in, &lineno, BLOB_BUF(word1)); if (blob_is_null(b)) break; p = read_word(in, &lineno, BLOB_BUF(word2)); if (blob_cmp(b, BLOB_STR("redirect_path")) == 0) { cfg->redirect_url_base = blob_dup(p); } else if (blob_cmp(b, BLOB_STR("forbid")) == 0) { cfg->hard_block_categories |= to_category(cfg->db, p); } else if (blob_cmp(b, BLOB_STR("warn")) == 0) { cfg->block_categories |= to_category(cfg->db, p); } else if (blob_cmp(b, BLOB_STR("logout_timeout")) == 0) { cfg->logout_timeout = blob_pull_uint(&p, 10); } else if (blob_cmp(b, BLOB_STR("require_auth")) == 0) { cfg->require_auth = is_true(p); } } cfg->block_categories |= cfg->hard_block_categories; fclose(in); return 1; }