summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimo Teräs <timo.teras@iki.fi>2010-08-27 17:05:18 +0300
committerTimo Teräs <timo.teras@iki.fi>2010-08-27 17:05:18 +0300
commitb8944ab71ccdc9951c6b74ef8ed8686d0329f99c (patch)
treea44327faefdfb99bf61d1249c044bfef66419267
parent29331f1c5e940499f282aea8155b89fae69f3fb8 (diff)
downloadsquark-b8944ab71ccdc9951c6b74ef8ed8686d0329f99c.tar.bz2
squark-b8944ab71ccdc9951c6b74ef8ed8686d0329f99c.tar.xz
authdb: implement basics
Implement a shared memory based authentication cache. It's a simple local cache indexed by IP-address, and keeps track of that IP's auth info such as username, allowed categories and timeouts. This provides basis for captive portal, per-user definable category restrictions and implementation of soft blocks (block which can be overridden by user by clicking a button on the blocked page).
-rw-r--r--Makefile6
-rw-r--r--addr.c53
-rw-r--r--addr.h31
-rw-r--r--authdb.c223
-rw-r--r--authdb.h50
-rw-r--r--filterdb.h4
-rw-r--r--squark-auth-snmp.c69
-rw-r--r--squark-filter.c67
8 files changed, 420 insertions, 83 deletions
diff --git a/Makefile b/Makefile
index 55f0d96..09c58d4 100644
--- a/Makefile
+++ b/Makefile
@@ -12,11 +12,11 @@ CFLAGS=-g -I. $(NETSNMP_CFLAGS) $(LUA_CFLAGS) $(CMPH_CFLAGS) -std=gnu99 -D_GNU_S
all: $(TARGETS)
-squark-auth-snmp: squark-auth-snmp.o blob.o
+squark-auth-snmp: squark-auth-snmp.o blob.o addr.o
$(CC) -o $@ $^ $(NETSNMP_LIBS)
-squark-filter: squark-filter.o filterdb.o blob.o
- $(CC) -o $@ $^ $(CMPH_LIBS)
+squark-filter: squark-filter.o filterdb.o authdb.o blob.o addr.o
+ $(CC) -o $@ $^ $(CMPH_LIBS) -lrt
squarkdb.so: lua-squarkdb.o filterdb.o blob.o
$(CC) -shared -o $@ $^ $(LUA_LIBS) $(CMPH_LIBS)
diff --git a/addr.c b/addr.c
new file mode 100644
index 0000000..1dddaff
--- /dev/null
+++ b/addr.c
@@ -0,0 +1,53 @@
+#include <string.h>
+
+#include "addr.h"
+
+int addr_len(const sockaddr_any *addr)
+{
+ switch (addr->any.sa_family) {
+ case AF_INET:
+ return sizeof(struct sockaddr_in);
+ default:
+ return 0;
+ }
+}
+
+sockaddr_any *addr_parse(blob_t b, sockaddr_any *addr)
+{
+ memset(addr, 0, sizeof(*addr));
+ addr->ipv4.sin_family = AF_INET;
+ addr->ipv4.sin_addr.s_addr = blob_inet_addr(b);
+ if (addr->ipv4.sin_addr.s_addr == -1)
+ return NULL;
+ return addr;
+}
+
+unsigned long addr_hash(const sockaddr_any *addr)
+{
+ switch (addr->any.sa_family) {
+ case AF_INET:
+ return htonl(addr->ipv4.sin_addr.s_addr);
+ default:
+ return 0;
+ }
+}
+
+const char *addr_print(const sockaddr_any *addr)
+{
+ switch (addr->any.sa_family) {
+ case AF_INET:
+ return inet_ntoa(addr->ipv4.sin_addr);
+ default:
+ return "unknown";
+ }
+}
+
+blob_t addr_get_hostaddr_blob(const sockaddr_any *addr)
+{
+ switch (addr->any.sa_family) {
+ case AF_INET:
+ return BLOB_BUF(&addr->ipv4.sin_addr);
+ default:
+ return BLOB_NULL;
+ }
+}
diff --git a/addr.h b/addr.h
new file mode 100644
index 0000000..00e1833
--- /dev/null
+++ b/addr.h
@@ -0,0 +1,31 @@
+#ifndef ADDR_H
+#define ADDR_H
+
+#include <arpa/inet.h>
+#include "blob.h"
+
+typedef union {
+ struct sockaddr any;
+ struct sockaddr_in ipv4;
+} sockaddr_any;
+
+int addr_len(const sockaddr_any *addr);
+sockaddr_any *addr_parse(blob_t text, sockaddr_any *addr);
+unsigned long addr_hash(const sockaddr_any *addr);
+const char *addr_print(const sockaddr_any *addr);
+blob_t addr_get_hostaddr_blob(const sockaddr_any *addr);
+
+static inline void addr_copy(sockaddr_any *dst, const sockaddr_any *src)
+{
+ memcpy(dst, src, addr_len(src));
+}
+
+static inline int addr_cmp(const sockaddr_any *a, const sockaddr_any *b)
+{
+ if (a->any.sa_family != b->any.sa_family)
+ return -1;
+ return memcmp(a, b, addr_len(a));
+}
+
+#endif
+
diff --git a/authdb.c b/authdb.c
new file mode 100644
index 0000000..452c94e
--- /dev/null
+++ b/authdb.c
@@ -0,0 +1,223 @@
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <sched.h>
+#include <fcntl.h>
+
+#include "authdb.h"
+#include "addr.h"
+#include "blob.h"
+
+#define AUTHDB_IP_PER_ME 256
+#define AUTHDB_LOGOFF_PERIOD (15*60) /* 15 mins */
+
+static struct authdb_map_entry *authdb_me_open(sockaddr_any *addr, int create)
+{
+ int oflag, fd;
+ char name[64];
+ blob_t b = BLOB_BUF(name);
+ void *base;
+ struct authdb_map_entry *me;
+
+ 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, 0600);
+ if (fd < 0)
+ return NULL;
+
+ if (create &&
+ ftruncate(fd, sizeof(struct authdb_entry[AUTHDB_IP_PER_ME])) < 0) {
+ close(fd);
+ return NULL;
+ }
+
+ base = mmap(NULL, sizeof(struct authdb_entry[AUTHDB_IP_PER_ME]),
+ 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, sizeof(struct authdb_entry[AUTHDB_IP_PER_ME]));
+ 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, sizeof(struct authdb_entry[AUTHDB_IP_PER_ME]));
+ free(me);
+}
+
+int authdb_open(struct authdb *adb)
+{
+ memset(adb, 0, sizeof(*adb));
+ return 0;
+}
+
+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(n);
+ }
+ }
+}
+
+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;
+ 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 = b.ptr[0];
+ b.ptr[0] = 0x00;
+ hash = b.ptr[1] + b.ptr[2] + b.ptr[3];
+
+ 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(&me->entries[e], entry, sizeof(struct authdb_entry));
+ if (entry->u.checksum == 0 && entry->u.login_time == 0)
+ return &me->entries[e];
+ if (entry->u.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->u.checksum;
+
+ entry->u.checksum = authdb_entry_checksum(entry);
+ if (mme->u.checksum != checksum)
+ return 0;
+
+ mme->u.checksum = entry->u.checksum;
+ 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->u.login_time + AUTHDB_LOGOFF_PERIOD)
+ return 0;
+
+ /* and that no one clobbered the entry */
+ if (mme->u.checksum != e->u.checksum)
+ return 0;
+
+ /* refresh last activity */
+ mme->u.login_time = now;
+
+ return 1;
+}
+
+void authdb_clear_entry(struct authdb_entry *entry)
+{
+ memset(&entry->p, 0, sizeof(entry->p));
+ entry->u.login_time = 0;
+ entry->u.override_time = 0;
+}
+
+void authdb_commit_login(void *token, struct authdb_entry *e, time_t now)
+{
+ /* fixme read stuff from config files */
+ e->u.login_time = now;
+
+ authdb_set(token, e);
+}
diff --git a/authdb.h b/authdb.h
new file mode 100644
index 0000000..5a3f3c4
--- /dev/null
+++ b/authdb.h
@@ -0,0 +1,50 @@
+#ifndef AUTHDB_H
+#define AUTHDB_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include "blob.h"
+#include "addr.h"
+
+#define AUTHDB_IP_HASH_SIZE 64
+
+struct authdb_map_entry;
+
+struct authdb {
+ struct authdb_map_entry *hash_bucket[AUTHDB_IP_HASH_SIZE];
+};
+
+struct authdb_entry {
+ struct {
+ char login_name[44];
+ char mac_address[6];
+ uint16_t switch_port;
+ sockaddr_any switch_ip;
+ uint64_t block_categories;
+ uint64_t hard_block_categories;
+ } p;
+
+ struct {
+ uint32_t login_time;
+ uint32_t override_time;
+ uint32_t checksum;
+ } u;
+};
+
+struct authdb_map_entry {
+ struct authdb_map_entry *next;
+ sockaddr_any baseaddr;
+ struct authdb_entry * entries;
+};
+
+int authdb_open(struct authdb *adb);
+void authdb_close(struct authdb *adb);
+
+void *authdb_get(struct authdb *adb, sockaddr_any *addr, struct authdb_entry *entry, int create);
+
+void authdb_clear_entry(struct authdb_entry *entry);
+int authdb_set(void *token, struct authdb_entry *entry);
+int authdb_check_login(void *token, struct authdb_entry *e, blob_t username, time_t now);
+void authdb_commit_login(void *token, struct authdb_entry *e, time_t now);
+
+#endif
diff --git a/filterdb.h b/filterdb.h
index 68c1a2a..2d16572 100644
--- a/filterdb.h
+++ b/filterdb.h
@@ -1,5 +1,5 @@
-#ifndef SQUARKDB_H
-#define SQUARKDB_H
+#ifndef FILTERDB_H
+#define FILTERDB_H
#include <stddef.h>
#include <stdint.h>
diff --git a/squark-auth-snmp.c b/squark-auth-snmp.c
index 4b88913..f386ab3 100644
--- a/squark-auth-snmp.c
+++ b/squark-auth-snmp.c
@@ -22,12 +22,12 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
-#include <arpa/inet.h>
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include "blob.h"
+#include "addr.h"
/* Compile time configurables */
#define SWITCH_HASH_SIZE 128
@@ -167,43 +167,6 @@ static inline void blob_pull_oid_dump(blob_t *b, blob_t d)
/* ----------------------------------------------------------------- */
-typedef union {
- struct sockaddr any;
- struct sockaddr_in ipv4;
-} sockaddr_any;
-
-int addr_len(const sockaddr_any *addr)
-{
- switch (addr->any.sa_family) {
- case AF_INET:
- return sizeof(struct sockaddr_in);
- default:
- return 0;
- }
-}
-
-void addr_copy(sockaddr_any *dst, const sockaddr_any *src)
-{
- memcpy(dst, src, addr_len(src));
-}
-
-int addr_cmp(const sockaddr_any *a, const sockaddr_any *b)
-{
- if (a->any.sa_family != b->any.sa_family)
- return -1;
- return memcmp(a, b, addr_len(a));
-}
-
-sockaddr_any *addr_parse(const char *str, sockaddr_any *addr)
-{
- memset(addr, 0, sizeof(*addr));
- addr->ipv4.sin_family = AF_INET;
- addr->ipv4.sin_addr.s_addr = inet_addr(str);
- if (addr->ipv4.sin_addr.s_addr == -1)
- return NULL;
- return addr;
-}
-
void blob_push_iana_afn(blob_t *b, sockaddr_any *addr)
{
unsigned char *ptr;
@@ -245,26 +208,6 @@ sockaddr_any *blob_pull_iana_afn(blob_t *b, sockaddr_any *addr)
return addr;
}
-unsigned long addr_hash(const sockaddr_any *addr)
-{
- switch (addr->any.sa_family) {
- case AF_INET:
- return htonl(addr->ipv4.sin_addr.s_addr);
- default:
- return 0;
- }
-}
-
-const char *addr_print(const sockaddr_any *addr)
-{
- switch (addr->any.sa_family) {
- case AF_INET:
- return inet_ntoa(addr->ipv4.sin_addr);
- default:
- return "unknown";
- }
-}
-
/* ----------------------------------------------------------------- */
static void safe_free(void *ptr)
@@ -435,10 +378,10 @@ void link_switch(const char *a, int ap, const char *b, int bp)
struct switch_port_info *spia, *spib;
sockaddr_any addr;
- sia = get_switch(addr_parse(a, &addr));
+ sia = get_switch(addr_parse(BLOB_STRLEN(a), &addr));
spia = get_switch_port(sia, ap);
- sib = get_switch(addr_parse(b, &addr));
+ sib = get_switch(addr_parse(BLOB_STRLEN(b), &addr));
spib = get_switch_port(sib, bp);
addr_copy(&spia->link_partner, &sib->addr);
@@ -994,7 +937,7 @@ void start_authentication(const char *token, const char *ip)
auth = calloc(1, sizeof(*auth));
auth->token = strdup(token);
- if (addr_parse(ip, &auth->addr) == NULL) {
+ if (addr_parse(BLOB_STRLEN(ip), &auth->addr) == NULL) {
auth_completed(auth);
return;
}
@@ -1112,9 +1055,9 @@ int main(int argc, char **argv)
if (l2_root == NULL)
l2_root = l3_root;
- l3_root_dev = get_switch(addr_parse(l3_root, &addr));
+ l3_root_dev = get_switch(addr_parse(BLOB_STRLEN(l3_root), &addr));
l3_if_ndx = resolve_ifName2ifIndex(l3_root_dev, BLOB_STRLEN((char *) l3_ifname));
- l2_root_dev = get_switch(addr_parse(l2_root, &addr));
+ l2_root_dev = get_switch(addr_parse(BLOB_STRLEN(l2_root), &addr));
l2_vlan_ndx = atoi(l2_vlan);
username_format_flags = parse_format(username_format);
diff --git a/squark-filter.c b/squark-filter.c
index 588eb73..7a005e1 100644
--- a/squark-filter.c
+++ b/squark-filter.c
@@ -10,6 +10,7 @@
* by the Free Software Foundation. See http://www.gnu.org/ for details.
*/
+#include <time.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
@@ -17,14 +18,20 @@
#include <cmph.h>
-#include "filterdb.h"
#include "blob.h"
+#include "addr.h"
+#include "filterdb.h"
+#include "authdb.h"
static int running = 1;
static uint64_t banned_categories = 0;
+static const blob_t dash = BLOB_STR_INIT("-");
static const blob_t space = BLOB_STR_INIT(" ");
+static const blob_t slash = BLOB_STR_INIT("/");
static const blob_t lf = BLOB_STR_INIT("\n");
-static blob_t redirect_page;
+static blob_t redirect_banned_page, redirect_login_page;
+static struct authdb adb;
+static time_t now;
struct url_info {
blob_t protocol;
@@ -321,7 +328,7 @@ static void send_ok(blob_t tag)
write(STDOUT_FILENO, b.ptr, b.len);
}
-static void send_redirect(struct sqdb *db, blob_t tag, blob_t url, int categ, blob_t username)
+static void send_redirect(blob_t redirect_page, blob_t tag, blob_t url, blob_t categ, blob_t username)
{
static char buffer[8*1024];
blob_t b = BLOB_BUF(buffer);
@@ -330,7 +337,7 @@ static void send_redirect(struct sqdb *db, blob_t tag, blob_t url, int categ, bl
blob_push(&b, BLOB_STR(" 302:"));
blob_push(&b, redirect_page);
blob_push(&b, BLOB_STR("?REASON="));
- blob_push_urlencode(&b, get_category_name(db, categ));
+ blob_push_urlencode(&b, categ);
blob_push(&b, BLOB_STR("&USER="));
blob_push_urlencode(&b, username);
blob_push(&b, BLOB_STR("&DENIEDURL="));
@@ -346,9 +353,12 @@ static void read_input(struct sqdb *db)
static char buffer[8 * 1024];
static blob_t left;
- blob_t b, line, id, url, username;
+ blob_t b, line, id, ipaddr, url, username;
struct url_info nfo;
- int r, category;
+ int r, category, auth_ok;
+ sockaddr_any addr;
+ struct authdb_entry entry;
+ void *token;
if (blob_is_null(left))
left = BLOB_BUF(buffer);
@@ -363,6 +373,8 @@ static void read_input(struct sqdb *db)
left.ptr += r;
left.len -= r;
+ now = time(NULL);
+
b = blob_pushed(BLOB_BUF(buffer), left);
do {
line = blob_pull_cspn(&b, lf);
@@ -373,16 +385,16 @@ static void read_input(struct sqdb *db)
blob_pull_spn(&line, space);
url = blob_pull_cspn(&line, space);
blob_pull_spn(&line, space);
- blob_pull_cspn(&line, space); /* client addr / fqdn */
+ ipaddr = blob_pull_cspn(&line, slash); /* client addr */
+ blob_pull_cspn(&line, space); /* fqdn */
blob_pull_spn(&line, space);
username = blob_pull_cspn(&line, space);
/* http method */
/* urlgroup */
/* myaddr=xxx myport=xxx etc */
- if (!blob_is_null(url)) {
- if (blob_is_null(username))
- username = BLOB_STR("-");
+ if (!blob_is_null(url) &&
+ addr_parse(ipaddr, &addr)) {
/* valid request, handle it */
if (url_parse(url, &nfo)) {
url_print(&nfo);
@@ -390,9 +402,29 @@ static void read_input(struct sqdb *db)
} else
category = 0;
- if ((1ULL << category) & banned_categories)
- send_redirect(db, id, url, category, username);
- else
+ token = authdb_get(&adb, &addr, &entry, 1);
+ if (authdb_check_login(token, &entry, username, now)) {
+ auth_ok = 1;
+ username = BLOB_STRLEN(entry.p.login_name);
+ } else if (blob_cmp(username, dash) != 0 ||
+ blob_is_null(redirect_login_page)) {
+ auth_ok = 1;
+ authdb_clear_entry(&entry);
+ entry.p.block_categories = banned_categories;
+ memcpy(entry.p.login_name, username.ptr, username.len);
+ authdb_commit_login(token, &entry, now);
+ } else {
+ auth_ok = 0;
+ }
+
+ if (!auth_ok) {
+ send_redirect(redirect_login_page, id, url, BLOB_STR("auth"), username);
+ } else if (((1ULL << category) & entry.p.block_categories) &&
+ (entry.u.override_time < now ||
+ entry.u.override_time + 15*60 > now ||
+ ((1ULL << category) & entry.p.hard_block_categories))) {
+ send_redirect(redirect_banned_page, id, url, get_category_name(db, category), username);
+ } else
send_ok(id);
}
@@ -421,12 +453,16 @@ int main(int argc, char **argv)
struct sqdb db;
int opt;
+ authdb_open(&adb);
sqdb_open(&db, "/var/lib/squark/squark.db");
- while ((opt = getopt(argc, argv, "r:b:")) != -1) {
+ while ((opt = getopt(argc, argv, "r:b:c:")) != -1) {
switch (opt) {
case 'r':
- redirect_page = BLOB_STRLEN(optarg);
+ redirect_banned_page = BLOB_STRLEN(optarg);
+ break;
+ case 'c':
+ redirect_login_page = BLOB_STRLEN(optarg);
break;
case 'b':
ban_category(&db, BLOB_STRLEN(optarg));
@@ -438,4 +474,5 @@ int main(int argc, char **argv)
read_input(&db);
sqdb_close(&db);
+ authdb_close(&adb);
}