summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimo Teräs <timo.teras@iki.fi>2010-07-22 16:58:58 +0300
committerTimo Teräs <timo.teras@iki.fi>2010-07-22 16:58:58 +0300
commit17c5c7dbbf673b2e521dcc71900ae22abfbcfc8c (patch)
treea0dc438b862c59d5ab0f54f2157f25f9c7e92851
downloadsquark-17c5c7dbbf673b2e521dcc71900ae22abfbcfc8c.tar.bz2
squark-17c5c7dbbf673b2e521dcc71900ae22abfbcfc8c.tar.xz
squark-auth: initial commit
Basic functionality implemented.
-rw-r--r--Makefile14
-rw-r--r--squark-auth.c1239
2 files changed, 1253 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..01d7400
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+CC=gcc
+OBJS1=squark-auth.o
+TARGETS=squark-auth
+CFLAGS=-g -I. $(shell net-snmp-config --cflags) -std=gnu99 -Wall
+BUILDLIBS=$(shell net-snmp-config --libs)
+
+all: $(TARGETS)
+
+squark-auth: $(OBJS1)
+ $(CC) -g -o $@ $(OBJS1) $(BUILDLIBS) -nopie
+
+clean:
+ rm $(OBJS1) $(TARGETS)
+
diff --git a/squark-auth.c b/squark-auth.c
new file mode 100644
index 0000000..84240c9
--- /dev/null
+++ b/squark-auth.c
@@ -0,0 +1,1239 @@
+/* squark-auth.c - Squid User Authentication and Rating Kit
+ * An external acl helper for Squid which collects authentication
+ * information for IP-address from switches via SNMP.
+ *
+ * Copyright (C) 2010 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation. See http://www.gnu.org/ for details.
+ */
+
+/* TODO:
+ * - implement Q-BRIDGE-MIB query
+ * - map vlan names to vlan index
+ * - print some usage information
+ */
+
+#include <fcntl.h>
+#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>
+
+/* Compile time configurables */
+#define SWITCH_HASH_SIZE 128
+#define PORT_HASH_SIZE 128
+#define CACHE_TIME 30 /* seconds */
+
+/* Some helpers */
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define MAC_LEN 6
+
+#define oid_const(oid) (oid), ARRAY_SIZE(oid)
+#define oid_blob(b) ((oid *) (b).ptr), ((b).len / sizeof(oid))
+
+/* Format specifiers for username type */
+#define FORMAT_CLIENT_IP 0x01 /* %I */
+#define FORMAT_CLIENT_MAC 0x02 /* %M */
+#define FORMAT_SWITCH_NAME 0x04 /* %N */
+#define FORMAT_SWITCH_LOCATION 0x08 /* %L */
+#define FORMAT_PORT_INDEX 0x10 /* %i */
+#define FORMAT_PORT_NAME 0x20 /* %n */
+#define FORMAT_PORT_DESCR 0x40 /* %d */
+#define FORMAT_PORT_WEBAUTH 0x80 /* %w */
+
+/* Some info about the switch which we need */
+#define SWITCHF_NO_LLDP 0x01
+#define SWITCHF_BRIDGE_MIB_HAS_VLAN 0x02
+
+/* IANA-AddressFamilyNumbers */
+#define IANA_AFN_OTHER 0
+#define IANA_AFN_IPV4 1
+#define IANA_AFN_IPV6 2
+
+/* OIDs used by the program */
+static const oid SNMPv2_MIB_sysObjectID[] =
+ { SNMP_OID_MIB2, 1, 2, 0 };
+static const oid SNMPv2_MIB_sysName[] =
+ { SNMP_OID_MIB2, 1, 5, 0 };
+static const oid SNMPv2_MIB_sysLocation[] =
+ { SNMP_OID_MIB2, 1, 6, 0 };
+static const oid IF_MIB_ifDescr[] =
+ { SNMP_OID_MIB2, 2, 2, 1, 2 };
+static const oid IF_MIB_ifName[] =
+ { SNMP_OID_MIB2, 31, 1, 1, 1, 1 };
+static const oid IF_MIB_ifStackStatus[] =
+ { SNMP_OID_MIB2, 31, 1, 2, 1, 3 };
+static const oid IP_MIB_ipNetToPhysicalPhysAddress[] =
+ { SNMP_OID_MIB2, 4, 35, 1, 4 };
+static const oid BRIDGE_MIB_dot1dTpFdbPort[] =
+ { SNMP_OID_MIB2, 17, 4, 3, 1, 2 };
+static const oid LLDP_lldpLocSysName[] =
+ { 1, 0, 8802, 1, 1, 2, 1, 3, 3, 0 };
+static const oid LLDP_lldpRemManAddrIfSubtype[] =
+ { 1, 0, 8802, 1, 1, 2, 1, 4, 2, 1, 3 };
+static const oid HP_hpicfUsrAuthWebAuthSessionName[] =
+ { SNMP_OID_ENTERPRISES, 11, 2, 14, 11, 5, 1, 19, 5, 1, 1, 2 };
+static const oid SEMI_MIB_hpHttpMgVersion[] =
+ { SNMP_OID_ENTERPRISES, 11, 2, 36, 1, 1, 2, 6, 0 };
+
+/* ----------------------------------------------------------------- */
+
+struct switch_info;
+
+static int num_queries = 0, running = TRUE;
+
+static const char *snmp_community = "public";
+static const char *username_format = "%w";
+static struct switch_info *all_switches[SWITCH_HASH_SIZE];
+static struct switch_info *l3_root_dev, *l2_root_dev;
+static int l3_if_ndx, l2_vlan_ndx;
+static time_t current_time;
+static int username_format_flags;
+
+/* ----------------------------------------------------------------- */
+
+typedef struct blob {
+ char *ptr;
+ unsigned int len;
+} blob_t;
+
+#define blob_dyn(ptr,len) (blob_t){(void*)(ptr), (len)}
+#define blob_buf(buf) (blob_t){(void*)(buf), sizeof(buf)}
+#define blob_oid(objid) blob_buf(objid)
+#define blob_oid_dyn(objid,len) blob_dyn(objid, (len) * sizeof(oid))
+#define blob_str(str) (blob_t){(char*)(str), strlen(str)}
+
+const blob_t BLOB_NULL = { NULL, 0 };
+
+static inline int blob_is_null(blob_t b)
+{
+ return b.ptr == NULL;
+}
+
+static char *blob_cstr_dup(blob_t b)
+{
+ char *p;
+
+ if (blob_is_null(b))
+ return NULL;
+
+ p = malloc(b.len+1);
+ if (p != NULL) {
+ memcpy(p, b.ptr, b.len);
+ p[b.len] = 0;
+ }
+ return p;
+}
+
+static blob_t blob_dup(blob_t b)
+{
+ blob_t p;
+
+ if (blob_is_null(b))
+ return BLOB_NULL;
+
+ p.ptr = malloc(b.len);
+ if (p.ptr != NULL) {
+ memcpy(p.ptr, b.ptr, b.len);
+ p.len = b.len;
+ } else {
+ p.len = 0;
+ }
+ return p;
+}
+
+static int blob_cmp(blob_t a, blob_t b)
+{
+ if (a.len != b.len)
+ return a.len - b.len;
+ return memcmp(a.ptr, b.ptr, a.len);
+}
+
+static blob_t blob_pushed(blob_t buffer, blob_t left)
+{
+ if (buffer.ptr + buffer.len != left.ptr + left.len)
+ return BLOB_NULL;
+ return blob_dyn(buffer.ptr, left.ptr - buffer.ptr);
+}
+
+static inline void blob_push(blob_t *b, blob_t d)
+{
+ if (b->len >= d.len) {
+ memcpy(b->ptr, d.ptr, d.len);
+ b->ptr += d.len;
+ b->len -= d.len;
+ } else {
+ *b = BLOB_NULL;
+ }
+}
+
+static void blob_push_int_str(blob_t *b, int val)
+{
+ int l;
+
+ l = snprintf(b->ptr, b->len, "%d", val);
+ b->ptr += l;
+ b->len -= l;
+}
+
+static void blob_push_hexdump(blob_t *to, blob_t binary)
+{
+ static const char *xd = "0123456789abcdef";
+ char *d;
+ int i;
+
+ if (blob_is_null(*to))
+ return;
+
+ if (to->len < binary.len * 2) {
+ *to = BLOB_NULL;
+ return;
+ }
+
+ for (i = 0, d = to->ptr; i < binary.len; i++) {
+ *(d++) = xd[(binary.ptr[i] >> 4) & 0xf];
+ *(d++) = xd[binary.ptr[i] & 0xf];
+ }
+ to->ptr = d;
+ to->len -= binary.len * 2;
+}
+
+static inline blob_t blob_pull(blob_t *b, int len)
+{
+ blob_t r;
+
+ if (b->len >= len) {
+ r = blob_dyn(b->ptr, len);
+ b->ptr += len;
+ b->len -= len;
+ return r;
+ }
+ *b = BLOB_NULL;
+ return BLOB_NULL;
+}
+
+static inline void blob_pull_skip(blob_t *b, int len)
+{
+ if (b->len >= len) {
+ b->ptr += len;
+ b->len -= len;
+ } else {
+ *b = BLOB_NULL;
+ }
+}
+
+static inline int blob_pull_matching(blob_t *b, blob_t e)
+{
+ if (b->len < e.len)
+ return 0;
+ if (memcmp(b->ptr, e.ptr, e.len) != 0)
+ return 0;
+ b->ptr += e.len;
+ b->len -= e.len;
+ return 1;
+}
+
+static inline void blob_push_oid(blob_t *b, oid objid)
+{
+ if (b->len >= sizeof(objid)) {
+ *((oid*) b->ptr) = objid;
+ b->ptr += sizeof(oid);
+ b->len -= sizeof(oid);
+ } else {
+ *b = BLOB_NULL;
+ }
+}
+
+static inline oid blob_pull_oid(blob_t *b)
+{
+ oid objid;
+
+ if (b->len >= sizeof(objid)) {
+ objid = *((oid*) b->ptr);
+ b->ptr += sizeof(oid);
+ b->len -= sizeof(oid);
+ } else {
+ *b = BLOB_NULL;
+ objid = -1;
+ }
+ return objid;
+}
+
+static inline void blob_push_oid_dump(blob_t *b, blob_t d)
+{
+ int i;
+
+ if (b->len >= d.len * sizeof(oid)) {
+ for (i = 0; i < d.len; i++) {
+ *((oid*) b->ptr) = (unsigned char) d.ptr[i];
+ b->ptr += sizeof(oid);
+ b->len -= sizeof(oid);
+ }
+ } else {
+ *b = BLOB_NULL;
+ }
+}
+
+static inline void blob_pull_oid_dump(blob_t *b, blob_t d)
+{
+ int i;
+
+ if (b->len >= d.len * sizeof(oid)) {
+ for (i = 0; i < d.len; i++) {
+ d.ptr[i] = (unsigned char) *((oid*) b->ptr);
+ b->ptr += sizeof(oid);
+ b->len -= sizeof(oid);
+ }
+ } else {
+ *b = BLOB_NULL;
+ }
+}
+
+/* ----------------------------------------------------------------- */
+
+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;
+ int type=0, len=0;
+
+ switch (addr->any.sa_family) {
+ case AF_INET:
+ type = IANA_AFN_IPV4;
+ len = 4;
+ ptr = (unsigned char*) &addr->ipv4.sin_addr;
+ break;
+ }
+ if (type == 0 || b->len < len) {
+ *b = BLOB_NULL;
+ return;
+ }
+ blob_push_oid(b, type);
+ blob_push_oid(b, len);
+ blob_push_oid_dump(b, blob_dyn(ptr, len));
+}
+
+sockaddr_any *blob_pull_iana_afn(blob_t *b, sockaddr_any *addr)
+{
+ unsigned char *ptr = NULL;
+ int type, len;
+
+ memset(addr, 0, sizeof(*addr));
+ type = blob_pull_oid(b);
+ len = blob_pull_oid(b);
+ if (type == IANA_AFN_IPV4 && len == 4) {
+ addr->ipv4.sin_family = AF_INET;
+ ptr = (unsigned char*) &addr->ipv4.sin_addr;
+ }
+ if (ptr == NULL) {
+ blob_pull_skip(b, len);
+ return NULL;
+ }
+ blob_pull_oid_dump(b, blob_dyn(ptr, len));
+ 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)
+{
+ void **pptr = ptr;
+ if (*pptr != NULL) {
+ free(*pptr);
+ *pptr = NULL;
+ }
+}
+
+struct cache_control {
+ time_t update_time;
+ struct auth_context * sleepers;
+};
+
+struct switch_port_info {
+ struct switch_port_info * next;
+ int port;
+ struct cache_control cache_control;
+ sockaddr_any link_partner;
+};
+
+struct switch_info {
+ struct switch_info * next;
+ sockaddr_any addr;
+ netsnmp_session * session;
+
+ struct cache_control cache_control;
+ int flags;
+ int info_available;
+ char * system_name;
+ char * system_location;
+ char * system_version;
+ blob_t system_oid;
+
+ struct switch_port_info * all_ports[PORT_HASH_SIZE];
+};
+
+struct auth_context {
+ char * token;
+ sockaddr_any addr;
+ unsigned char mac[MAC_LEN];
+ int info_available;
+ struct switch_info * current_switch;
+ struct switch_port_info *spi;
+ int local_port;
+ int lldp_port[8];
+ int num_lldp_ports;
+ char * port_name;
+ char * port_descr;
+ char * webauth_name;
+
+ void (*pending_operation)(struct auth_context *);
+ struct auth_context * next_sleeper;
+};
+
+static void cache_update_time(void)
+{
+ current_time = time(NULL);
+}
+
+static int cache_refresh(
+ struct cache_control *cc, struct auth_context *auth,
+ void (*callback)(struct auth_context *auth))
+{
+ int ret;
+
+ if (cc->update_time == -1 ||
+ cc->update_time + CACHE_TIME >= current_time) {
+ callback(auth);
+ return 0;
+ }
+
+ auth->pending_operation = callback;
+
+ ret = (cc->sleepers == NULL);
+ auth->next_sleeper = cc->sleepers;
+ cc->sleepers = auth;
+
+ return ret;
+}
+
+static void cache_update(struct cache_control *cc)
+{
+ struct auth_context *auth, *next;
+
+ cc->update_time = current_time;
+ auth = cc->sleepers;
+ cc->sleepers = NULL;
+ for (; auth; auth = next) {
+ next = auth->next_sleeper;
+ auth->pending_operation(auth);
+ }
+}
+
+static void cache_update_manual(struct cache_control *cc)
+{
+ cache_update(cc);
+ cc->update_time = -1;
+}
+
+static void switch_info_free(struct switch_info *si)
+{
+ safe_free(&si->system_name);
+ safe_free(&si->system_location);
+ safe_free(&si->system_version);
+ safe_free(&si->system_oid.ptr);
+}
+
+struct switch_info *get_switch(sockaddr_any *addr)
+{
+ struct snmp_session config;
+ struct switch_info *si;
+ unsigned int bucket = addr_hash(addr) % ARRAY_SIZE(all_switches);
+
+ for (si = all_switches[bucket]; si != NULL; si = si->next)
+ if (addr_cmp(&si->addr, addr) == 0)
+ return si;
+
+ si = calloc(1, sizeof(*si));
+ if (si == NULL)
+ return NULL;
+
+ addr_copy(&si->addr, addr);
+
+ snmp_sess_init(&config);
+ config.version = SNMP_VERSION_2c;
+ config.community = (unsigned char *) snmp_community;
+ config.community_len = strlen(snmp_community);
+ config.peername = (char *) addr_print(addr);
+ si->session = snmp_open(&config);
+
+ si->next = all_switches[bucket];
+ all_switches[bucket] = si;
+
+ return si;
+}
+
+struct switch_port_info *get_switch_port(struct switch_info *si, int port)
+{
+ unsigned int bucket = port % ARRAY_SIZE(si->all_ports);
+ struct switch_port_info *spi;
+
+ if (si == NULL)
+ return NULL;
+
+ for (spi = si->all_ports[bucket]; spi != NULL; spi = spi->next)
+ if (spi->port == port)
+ return spi;
+
+ spi = calloc(1, sizeof(*spi));
+ if (spi == NULL)
+ return NULL;
+
+ spi->port = port;
+ spi->next = si->all_ports[bucket];
+ si->all_ports[bucket] = spi;
+
+ return spi;
+}
+
+void link_switch(const char *a, int ap, const char *b, int bp)
+{
+ struct switch_info *sia, *sib;
+ struct switch_port_info *spia, *spib;
+ sockaddr_any addr;
+
+ sia = get_switch(addr_parse(a, &addr));
+ spia = get_switch_port(sia, ap);
+
+ sib = get_switch(addr_parse(b, &addr));
+ spib = get_switch_port(sib, bp);
+
+ addr_copy(&spia->link_partner, &sib->addr);
+ addr_copy(&spib->link_partner, &sia->addr);
+
+ cache_update_manual(&spia->cache_control);
+ cache_update_manual(&spib->cache_control);
+}
+
+static void auth_query_switch_info(struct auth_context *auth);
+static void auth_query_lldp(struct auth_context *auth, int root_query);
+
+static void auth_free(struct auth_context *auth)
+{
+ safe_free(&auth->token);
+ safe_free(&auth->port_name);
+ safe_free(&auth->port_descr);
+ safe_free(&auth->webauth_name);
+ free(auth);
+}
+
+int resolve_ifName2ifIndex(struct switch_info *si, blob_t ifName)
+{
+ netsnmp_pdu *pdu, *response = NULL;
+ netsnmp_variable_list *vars, *lastvar = NULL;
+ int rc = -1;
+
+ pdu = snmp_pdu_create(SNMP_MSG_GETBULK);
+ pdu->non_repeaters = 0;
+ pdu->max_repetitions = 10;
+ snmp_add_null_var(pdu, oid_const(IF_MIB_ifName));
+
+ do {
+ if (snmp_synch_response(si->session, pdu, &response) != 0)
+ return -1;
+ if (response->errstat != SNMP_ERR_NOERROR)
+ goto done;
+
+ for (vars = response->variables; vars; vars = vars->next_variable) {
+ lastvar = vars;
+
+ if (vars->name_length < ARRAY_SIZE(IF_MIB_ifName) ||
+ memcmp(vars->name, IF_MIB_ifName, sizeof(IF_MIB_ifName)) != 0)
+ goto done;
+
+ if (vars->type != ASN_OCTET_STR)
+ continue;
+
+ if (blob_cmp(ifName, blob_dyn(vars->val.string, vars->val_len)) != 0)
+ continue;
+
+ rc = vars->name[vars->name_length - 1];
+ goto done;
+ }
+
+ pdu = snmp_pdu_create(SNMP_MSG_GETBULK);
+ pdu->non_repeaters = 0;
+ pdu->max_repetitions = 10;
+ snmp_add_null_var(pdu, lastvar->name, lastvar->name_length);
+
+ snmp_free_pdu(response);
+ response = NULL;
+ } while (1);
+
+done:
+ if (response)
+ snmp_free_pdu(response);
+ return rc;
+}
+
+
+static int parse_format(const char *fmt)
+{
+ int flags = 0;
+ const char *p = fmt;
+
+ while ((p = strchr(p, '%')) != NULL) {
+ switch (p[1]) {
+ case 'I':
+ flags |= FORMAT_CLIENT_IP;
+ break;
+ case 'M':
+ flags |= FORMAT_CLIENT_MAC;
+ break;
+ case 'N':
+ flags |= FORMAT_SWITCH_NAME;
+ break;
+ case 'L':
+ flags |= FORMAT_SWITCH_LOCATION;
+ break;
+ case 'i':
+ flags |= FORMAT_PORT_INDEX;
+ break;
+ case 'n':
+ flags |= FORMAT_PORT_NAME;
+ break;
+ case 'd':
+ flags |= FORMAT_PORT_DESCR;
+ break;
+ case 'w':
+ flags |= FORMAT_PORT_WEBAUTH;
+ break;
+ }
+ p++;
+ }
+ return flags;
+}
+
+static void blob_push_formatted_username(
+ blob_t *b, const char *fmt, struct auth_context *auth)
+{
+ const char *o = fmt, *p = fmt;
+ struct switch_info *si = auth->current_switch;
+
+ while ((p = strchr(p, '%')) != NULL) {
+ blob_push(b, blob_dyn(o, p - o));
+ switch (p[1]) {
+ case 'I':
+ blob_push(b, blob_str(addr_print(&auth->addr)));
+ break;
+ case 'M':
+ blob_push_hexdump(b, blob_buf(auth->mac));
+ break;
+ case 'N':
+ blob_push(b, blob_str(si->system_name));
+ break;
+ case 'L':
+ blob_push(b, blob_str(si->system_location));
+ break;
+ case 'i':
+ blob_push_int_str(b, auth->local_port);
+ break;
+ case 'n':
+ blob_push(b, blob_str(auth->port_name));
+ break;
+ case 'd':
+ blob_push(b, blob_str(auth->port_descr));
+ break;
+ case 'w':
+ blob_push(b, blob_str(auth->webauth_name));
+ break;
+ default:
+ o = p;
+ p++;
+ continue;
+ }
+ p += 2;
+ o = p;
+ }
+ blob_push(b, blob_str(o));
+}
+
+static int auth_ok(struct auth_context *auth)
+{
+ return (auth->info_available & username_format_flags) == username_format_flags;
+}
+
+static void auth_completed(struct auth_context *auth)
+{
+ char tmp[256];
+ blob_t b = blob_buf(tmp);
+
+ blob_push(&b, blob_str(auth->token));
+ if (auth_ok(auth)) {
+ blob_push(&b, blob_str(" OK user="));
+ blob_push_formatted_username(&b, username_format, auth);
+ blob_push(&b, blob_dyn("\n", 1));
+ } else {
+ blob_push(&b, blob_str(" ERR\n"));
+ }
+ b = blob_pushed(blob_buf(tmp), b);
+ write(STDOUT_FILENO, b.ptr, b.len);
+
+ auth_free(auth);
+ num_queries--;
+}
+
+static void auth_talk_snmp(struct auth_context *auth, netsnmp_session *s, netsnmp_pdu *pdu, netsnmp_callback callback)
+{
+ if (snmp_async_send(s, pdu, callback, auth) == 0) {
+ snmp_free_pdu(pdu);
+ auth_completed(auth);
+ }
+}
+
+static void cache_talk_snmp(struct cache_control *cc, netsnmp_session *s, netsnmp_pdu *pdu, netsnmp_callback callback, struct auth_context *auth)
+{
+ if (snmp_async_send(s, pdu, callback, auth) == 0) {
+ snmp_free_pdu(pdu);
+ cache_update(cc);
+ }
+}
+
+static blob_t var_parse_type(netsnmp_variable_list **varptr, int asn_tag)
+{
+ netsnmp_variable_list *var = *varptr;
+ if (var == NULL)
+ return BLOB_NULL;
+
+ *varptr = var->next_variable;
+ if (var->type != asn_tag)
+ return BLOB_NULL;
+
+ return blob_dyn(var->val.string, var->val_len);
+}
+
+static int auth_handle_portinfo_reply(int oper, netsnmp_session *s, int reqid, netsnmp_pdu *resp, void *data)
+{
+ struct auth_context *auth = data;
+ netsnmp_variable_list *var;
+
+ if (oper != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE)
+ goto done;
+
+ var = resp->variables;
+ if (username_format_flags & FORMAT_PORT_NAME)
+ auth->port_name = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR));
+ if (auth->port_name)
+ auth->info_available |= FORMAT_PORT_NAME;
+ if (username_format_flags & FORMAT_PORT_DESCR)
+ auth->port_descr = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR));
+ if (auth->port_descr)
+ auth->info_available |= FORMAT_PORT_DESCR;
+ if (username_format_flags & FORMAT_PORT_WEBAUTH)
+ auth->webauth_name = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR));
+ if (auth->webauth_name)
+ auth->info_available |= FORMAT_PORT_WEBAUTH;
+
+done:
+ auth_completed(auth);
+ return 1;
+}
+
+static void auth_query_port_info(struct auth_context *auth)
+{
+ struct switch_info *si = auth->current_switch;
+ netsnmp_pdu *pdu;
+ oid query_oids[MAX_OID_LEN];
+ blob_t query;
+
+ if (auth_ok(auth)) {
+ auth_completed(auth);
+ return;
+ }
+
+ pdu = snmp_pdu_create(SNMP_MSG_GET);
+ if (username_format_flags & FORMAT_PORT_NAME) {
+ query = blob_oid(query_oids);
+ blob_push(&query, blob_oid(IF_MIB_ifName));
+ blob_push_oid(&query, auth->local_port);
+ query = blob_pushed(blob_oid(query_oids), query);
+ snmp_add_null_var(pdu, oid_blob(query));
+ }
+ if (username_format_flags & FORMAT_PORT_DESCR) {
+ query = blob_oid(query_oids);
+ blob_push(&query, blob_oid(IF_MIB_ifDescr));
+ blob_push_oid(&query, auth->local_port);
+ query = blob_pushed(blob_oid(query_oids), query);
+ snmp_add_null_var(pdu, oid_blob(query));
+ }
+ if (username_format_flags & FORMAT_PORT_WEBAUTH) {
+ query = blob_oid(query_oids);
+ blob_push(&query, blob_oid(HP_hpicfUsrAuthWebAuthSessionName));
+ blob_push_oid(&query, auth->local_port);
+ blob_push_oid_dump(&query, blob_buf(auth->mac));
+ query = blob_pushed(blob_oid(query_oids), query);
+ snmp_add_null_var(pdu, oid_blob(query));
+ }
+ auth_talk_snmp(auth, si->session, pdu, auth_handle_portinfo_reply);
+}
+
+static int auth_handle_lldp_reply(int oper, netsnmp_session *s, int reqid, netsnmp_pdu *resp, void *data)
+{
+ struct auth_context *auth = data;
+ struct switch_port_info *spi = auth->spi;
+ netsnmp_variable_list *var = resp->variables;
+ blob_t res;
+ int i;
+
+ if (oper != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE)
+ goto fail;
+
+ /* print_variable(var->name, var->name_length, var); */
+
+ for (i = 0; i < auth->num_lldp_ports; i++) {
+ if (var == NULL)
+ goto fail;
+ if (var->type != ASN_INTEGER)
+ continue;
+ /* INDEX: TimeFilter, Port, Idx, Family, Addr */
+ res = blob_oid_dyn(var->name, var->name_length);
+ if (blob_pull_matching(&res, blob_oid(LLDP_lldpRemManAddrIfSubtype)) &&
+ blob_pull_oid(&res) == 0 &&
+ blob_pull_oid(&res) == auth->lldp_port[i]) {
+ /* We have mathing LLDP neighbor */
+ blob_pull_oid(&res);
+ blob_pull_iana_afn(&res, &spi->link_partner);
+ cache_update(&spi->cache_control);
+ return 1;
+ }
+
+ var = var->next_variable;
+ }
+ auth->num_lldp_ports = 0;
+ for (; var; var = var->next_variable) {
+ if (var->type != ASN_INTEGER)
+ break;
+ /* print_variable(var->name, var->name_length, var); */
+ res = blob_oid_dyn(var->name, var->name_length);
+ if (!blob_pull_matching(&res, blob_oid(IF_MIB_ifStackStatus)))
+ break;
+ if (blob_pull_oid(&res) != auth->local_port)
+ break;
+ auth->lldp_port[auth->num_lldp_ports++] = blob_pull_oid(&res);
+ if (auth->num_lldp_ports >= ARRAY_SIZE(auth->lldp_port))
+ break;
+ }
+ if (auth->num_lldp_ports) {
+ auth_query_lldp(auth, FALSE);
+ return 1;
+ }
+fail:
+ cache_update(&spi->cache_control);
+ return 1;
+}
+
+static void auth_query_lldp(struct auth_context *auth, int root_query)
+{
+ struct switch_info *si = auth->current_switch;
+ struct switch_port_info *spi = auth->spi;
+ netsnmp_pdu *pdu;
+ oid query_oids[MAX_OID_LEN];
+ blob_t query;
+ int i;
+
+ /* printf("Query LLDP info for %s:%d\n", addr_print(&si->addr), spi->port); */
+
+ if (si->flags & SWITCHF_NO_LLDP) {
+ memset(&spi->link_partner, 0, sizeof(spi->link_partner));
+ cache_update(&spi->cache_control);
+ return;
+ }
+
+ if (root_query) {
+ auth->num_lldp_ports = 1;
+ auth->lldp_port[0] = auth->local_port;
+ }
+
+ pdu = snmp_pdu_create(SNMP_MSG_GETBULK);
+ pdu->non_repeaters = auth->num_lldp_ports;
+ pdu->max_repetitions = 8;
+
+ for (i = 0; i < auth->num_lldp_ports; i++) {
+ /* Query LLDP neighbor. lldpRemManAddrTable is INDEXed with
+ * [TimeFilter, LocalPort, Index, AddrSubType, Addr] */
+ query = blob_oid(query_oids);
+ blob_push(&query, blob_oid(LLDP_lldpRemManAddrIfSubtype));
+ blob_push_oid(&query, 0);
+ blob_push_oid(&query, auth->lldp_port[i]);
+ query = blob_pushed(blob_oid(query_oids), query);
+ snmp_add_null_var(pdu, oid_blob(query));
+ }
+
+ if (root_query) {
+ /* Query interface stacking in case this is aggregated trunk:
+ * IF-MIB::ifStackStatus.<ifUpperIndex>.<ifLowerIndex> */
+ query = blob_oid(query_oids);
+ blob_push(&query, blob_oid(IF_MIB_ifStackStatus));
+ blob_push_oid(&query, auth->local_port);
+ query = blob_pushed(blob_oid(query_oids), query);
+ snmp_add_null_var(pdu, oid_blob(query));
+ }
+
+ cache_talk_snmp(&spi->cache_control, si->session, pdu, auth_handle_lldp_reply, auth);
+}
+
+static void auth_check_spi(struct auth_context *auth)
+{
+ struct switch_port_info *spi = auth->spi;
+
+ if (addr_len(&spi->link_partner) != 0) {
+ auth->current_switch = get_switch(&spi->link_partner);
+ auth_query_switch_info(auth);
+ } else {
+ auth_query_port_info(auth);
+ }
+}
+
+static int auth_handle_fib_reply(int oper, netsnmp_session *s, int reqid, netsnmp_pdu *resp, void *data)
+{
+ struct auth_context *auth = data;
+ struct switch_info *si = auth->current_switch;
+ struct switch_port_info *spi;
+ netsnmp_variable_list *var;
+
+ if (oper != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE)
+ goto failed;
+
+ var = resp->variables;
+ /* print_variable(var->name, var->name_length, var); */
+
+ if (var->type != ASN_INTEGER)
+ goto failed;
+
+ auth->local_port = *var->val.integer;
+ auth->info_available |= FORMAT_PORT_INDEX;
+ auth->spi = spi = get_switch_port(si, auth->local_port);
+ if (cache_refresh(&spi->cache_control, auth, auth_check_spi))
+ auth_query_lldp(auth, TRUE);
+ return 1;
+
+ /* No further info available */
+failed:
+ auth_completed(auth);
+ return 1;
+}
+
+static void auth_query_fib(struct auth_context *auth)
+{
+ oid query_oids[MAX_OID_LEN];
+ blob_t query;
+ struct switch_info *si = auth->current_switch;
+ netsnmp_pdu *pdu;
+
+ auth->info_available |= si->info_available;
+
+ /* printf("Probing switch %s\n", addr_print(&si->addr)); */
+
+ pdu = snmp_pdu_create(SNMP_MSG_GET);
+
+ /* FIXME: Implement Q-BRIDGE-MIB query too. */
+
+ /* BRIDGE-MIB::dot1dTpFdbPort.<MAC> = INTEGER: port */
+ query = blob_oid(query_oids);
+ blob_push(&query, blob_oid(BRIDGE_MIB_dot1dTpFdbPort));
+ if (si->flags & SWITCHF_BRIDGE_MIB_HAS_VLAN)
+ blob_push_oid(&query, l2_vlan_ndx);
+ blob_push_oid_dump(&query, blob_buf(auth->mac));
+ query = blob_pushed(blob_oid(query_oids), query);
+ snmp_add_null_var(pdu, oid_blob(query));
+
+ auth_talk_snmp(auth, si->session, pdu, auth_handle_fib_reply);
+}
+
+static int auth_handle_switch_info_reply(int oper, netsnmp_session *s, int reqid, netsnmp_pdu *resp, void *data)
+{
+ static const oid HP_ICF_OID_hpEtherSwitch[] =
+ { SNMP_OID_ENTERPRISES, 11, 2, 3, 7, 11 };
+ struct auth_context *auth = data;
+ struct switch_info *si = auth->current_switch;
+ netsnmp_variable_list *var;
+ blob_t b;
+
+ switch_info_free(si);
+ var = resp->variables;
+ si->system_name = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR));
+ si->system_location = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR));
+ si->system_oid = blob_dup(var_parse_type(&var, ASN_OBJECT_ID));
+ si->system_version = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR));
+ if (blob_is_null(var_parse_type(&var, ASN_OCTET_STR)))
+ si->flags |= SWITCHF_NO_LLDP;
+ if (si->system_name)
+ si->info_available |= FORMAT_SWITCH_NAME;
+ if (si->system_location)
+ si->info_available |= FORMAT_SWITCH_LOCATION;
+ b = si->system_oid;
+ if (blob_pull_matching(&b, blob_oid(HP_ICF_OID_hpEtherSwitch))) {
+ /* Hewlett-Packard ProCurve Switches */
+ switch (blob_pull_oid(&b)) {
+ case 104: /* 1810G-24 */
+ si->flags |= SWITCHF_BRIDGE_MIB_HAS_VLAN;
+ break;
+ }
+ }
+ cache_update(&si->cache_control);
+ return 1;
+}
+
+static void auth_query_switch_info(struct auth_context *auth)
+{
+ struct switch_info *si = auth->current_switch;
+ netsnmp_pdu *pdu;
+
+ auth->info_available &=
+ ~(FORMAT_SWITCH_NAME | FORMAT_SWITCH_LOCATION |
+ FORMAT_PORT_INDEX);
+
+ if (!cache_refresh(&si->cache_control, auth, auth_query_fib))
+ return;
+
+ pdu = snmp_pdu_create(SNMP_MSG_GET);
+ snmp_add_null_var(pdu, oid_const(SNMPv2_MIB_sysName));
+ snmp_add_null_var(pdu, oid_const(SNMPv2_MIB_sysLocation));
+ snmp_add_null_var(pdu, oid_const(SNMPv2_MIB_sysObjectID));
+ snmp_add_null_var(pdu, oid_const(SEMI_MIB_hpHttpMgVersion));
+ snmp_add_null_var(pdu, oid_const(LLDP_lldpLocSysName));
+ cache_talk_snmp(&si->cache_control, si->session, pdu, auth_handle_switch_info_reply, auth);
+}
+
+static int auth_handle_arp_reply(int oper, netsnmp_session *s, int reqid, netsnmp_pdu *resp, void *data)
+{
+ struct auth_context *auth = data;
+ netsnmp_variable_list *var = resp->variables;
+
+ if (oper == NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE &&
+ var->type == ASN_OCTET_STR &&
+ var->val_len == MAC_LEN) {
+ memcpy(auth->mac, var->val.string, MAC_LEN);
+ auth->info_available |= FORMAT_CLIENT_MAC;
+ if (!auth_ok(auth)) {
+ auth->current_switch = l2_root_dev;
+ auth_query_switch_info(auth);
+ return 1;
+ }
+ }
+
+ auth_completed(auth);
+ return 1;
+}
+
+void start_authentication(const char *token, const char *ip)
+{
+ struct auth_context *auth;
+ oid query_oids[MAX_OID_LEN];
+ blob_t query;
+ netsnmp_pdu *pdu;
+
+ num_queries++;
+
+ auth = calloc(1, sizeof(*auth));
+ auth->token = strdup(token);
+ if (addr_parse(ip, &auth->addr) == NULL) {
+ auth_completed(auth);
+ return;
+ }
+ auth->info_available = FORMAT_CLIENT_IP;
+
+ /* IP-MIB::ipNetToPhysicalPhysAddress.<ifIndex>.ipv4."1.2.3.4"
+ * = STRING: 01:12:34:56:78:9a */
+ pdu = snmp_pdu_create(SNMP_MSG_GET);
+
+ query = blob_oid(query_oids);
+ blob_push(&query, blob_oid(IP_MIB_ipNetToPhysicalPhysAddress));
+ blob_push_oid(&query, l3_if_ndx);
+ blob_push_iana_afn(&query, &auth->addr);
+ query = blob_pushed(blob_oid(query_oids), query);
+ snmp_add_null_var(pdu, oid_blob(query));
+
+ auth_talk_snmp(auth, l3_root_dev->session, pdu, auth_handle_arp_reply);
+}
+
+void read_input(void)
+{
+ static char buffer[256];
+ static int len = 0;
+ char token[32], ip[256], *p;
+ int r;
+
+ r = read(STDIN_FILENO, &buffer[len], sizeof(buffer) - len);
+ if (r < 0)
+ return;
+ if (r == 0) {
+ running = FALSE;
+ return;
+ }
+
+ len += r;
+ do {
+ p = strchr(buffer, '\n');
+ if (p == NULL)
+ return;
+
+ *p = 0;
+ if (sscanf(buffer, "%s %s", token, ip) == 2)
+ start_authentication(token, ip);
+ len -= (p - buffer) + 1;
+ memcpy(buffer, p + 1, len);
+ } while (len);
+}
+
+void load_topology(const char *file)
+{
+ char a_ip[64], b_ip[64];
+ int a_port, b_port;
+ FILE *in;
+
+ in = fopen(file, "r");
+ if (in == NULL)
+ return;
+
+ while (!feof(in)) {
+ if (fscanf(in, "%s %d %s %d\n",
+ a_ip, &a_port, b_ip, &b_port) == 4)
+ link_switch(a_ip, a_port, b_ip, b_port);
+ }
+ fclose(in);
+}
+
+int main(int argc, char **argv)
+{
+ const char *l3_root = NULL, *l3_ifname = NULL;
+ const char *l2_root = NULL, *l2_vlan = NULL;
+ struct timeval timeout;
+ sockaddr_any addr;
+ fd_set fdset;
+ int opt, fds, block;
+
+ while ((opt = getopt(argc, argv, "c:r:i:R:v:f:T:")) != -1) {
+ switch (opt) {
+ case 'c':
+ snmp_community = optarg;
+ break;
+ case 'r':
+ l3_root = optarg;
+ break;
+ case 'i':
+ l3_ifname = optarg;
+ break;
+ case 'R':
+ l2_root = optarg;
+ break;
+ case 'v':
+ l2_vlan = optarg;
+ break;
+ case 'f':
+ username_format = optarg;
+ break;
+ case 'T':
+ load_topology(optarg);
+ break;
+ }
+ }
+
+ if (l3_root == NULL || l3_ifname == NULL || l2_vlan == NULL) {
+ printf("Mandatory information missing\n");
+ return 1;
+ }
+
+ if (l2_root == NULL)
+ l2_root = l3_root;
+
+ l3_root_dev = get_switch(addr_parse(l3_root, &addr));
+ l3_if_ndx = resolve_ifName2ifIndex(l3_root_dev, blob_str((char *) l3_ifname));
+ l2_root_dev = get_switch(addr_parse(l2_root, &addr));
+ l2_vlan_ndx = atoi(l2_vlan);
+ username_format_flags = parse_format(username_format);
+
+ fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
+ while (num_queries || running) {
+ fds = 0;
+ block = 1;
+
+ FD_ZERO(&fdset);
+ if (running) {
+ FD_SET(STDIN_FILENO, &fdset);
+ fds = STDIN_FILENO + 1;
+ }
+ snmp_select_info(&fds, &fdset, &timeout, &block);
+ fds = select(fds, &fdset, NULL, NULL, block ? NULL : &timeout);
+ cache_update_time();
+ if (fds) {
+ if (FD_ISSET(STDIN_FILENO, &fdset))
+ read_input();
+ snmp_read(&fdset);
+ } else
+ snmp_timeout();
+ }
+
+ return 0;
+}