summaryrefslogtreecommitdiffstats
path: root/src/authdb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/authdb.c')
-rw-r--r--src/authdb.c364
1 files changed, 364 insertions, 0 deletions
diff --git a/src/authdb.c b/src/authdb.c
new file mode 100644
index 0000000..e6e71c4
--- /dev/null
+++ b/src/authdb.c
@@ -0,0 +1,364 @@
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <sched.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <time.h>
+#include <grp.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_LOGOFF_PERIOD (15*60) /* 15 mins */
+#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[0] = 0x00;
+
+ hash = b.ptr[1] + b.ptr[2] + b.ptr[3];
+ 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_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 + AUTHDB_LOGOFF_PERIOD)
+ return 0;
+
+ /* and that no one clobbered the entry */
+ if (mme->checksum != e->checksum)
+ return 0;
+
+ /* refresh last activity */
+ 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;
+}
+
+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("/etc/squark/filter.conf", &st) != 0)
+ return -1;
+
+ if (cfg->last_change == st.st_ctime)
+ return 0;
+
+ /* check timestamp */
+
+ in = fopen("/etc/squark/filter.conf", "r");
+ if (in == NULL)
+ return -1;
+
+ cfg->block_categories = 0;
+ cfg->hard_block_categories = 0;
+ 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);
+ }
+ }
+ cfg->block_categories |= cfg->hard_block_categories;
+
+ fclose(in);
+}