summaryrefslogtreecommitdiffstats
path: root/src/squark-auth-snmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/squark-auth-snmp.c')
-rw-r--r--src/squark-auth-snmp.c1152
1 files changed, 1152 insertions, 0 deletions
diff --git a/src/squark-auth-snmp.c b/src/squark-auth-snmp.c
new file mode 100644
index 0000000..81b846d
--- /dev/null
+++ b/src/squark-auth-snmp.c
@@ -0,0 +1,1152 @@
+/* squark-auth-snmp.c - Squid User Authentication and Rating Kit
+ * An external acl helper for Squid which collects authentication
+ * information about an 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
+ * - poll lldpStatsRemTablesLastChangeTime when doing switch update
+ * to figure out if lldp info is valid or not
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+
+#include "blob.h"
+#include "addr.h"
+#include "authdb.h"
+#include "filterdb.h"
+
+/* Compile time configurables */
+#define SWITCH_HASH_SIZE 128
+#define PORT_HASH_SIZE 128
+#define CACHE_TIME 120 /* 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 HP_hpicfUsrAuthPortReauthenticate[] =
+ { SNMP_OID_ENTERPRISES, 11, 2, 14, 11, 5, 1, 19, 2, 1, 1, 4 };
+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;
+static int running = TRUE;
+static int kick_out = FALSE;
+
+static struct sqdb db;
+static struct authdb adb;
+static struct authdb_config adbc;
+static const char *snmp_community = NULL;
+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;
+
+static const blob_t space = BLOB_STR_INIT(" ");
+static const blob_t lf = BLOB_STR_INIT("\n");
+
+/* ----------------------------------------------------------------- */
+
+#define BLOB_OID(objid) BLOB_BUF(objid)
+#define BLOB_OID_DYN(objid,len) BLOB_PTR_LEN(objid, (len) * sizeof(oid))
+
+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;
+ }
+}
+
+/* ----------------------------------------------------------------- */
+
+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_PTR_LEN(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_PTR_LEN(ptr, len));
+ return addr;
+}
+
+/* ----------------------------------------------------------------- */
+
+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);
+ adbc_refresh(&adbc, current_time);
+}
+
+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);
+ si->info_available = 0;
+ si->flags = 0;
+}
+
+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);
+ if (snmp_community != NULL) {
+ 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(BLOB_STRLEN(a), &addr));
+ spia = get_switch_port(sia, ap);
+
+ sib = get_switch(addr_parse(BLOB_STRLEN(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_PTR_LEN(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_PTR_LEN(o, p - o));
+ switch (p[1]) {
+ case 'I':
+ blob_push(b, BLOB_STRLEN((char*) addr_print(&auth->addr)));
+ break;
+ case 'M':
+ blob_push_hexdump(b, BLOB_BUF(auth->mac));
+ break;
+ case 'N':
+ blob_push(b, BLOB_STRLEN(si->system_name));
+ break;
+ case 'L':
+ blob_push(b, BLOB_STRLEN(si->system_location));
+ break;
+ case 'i':
+ blob_push_uint(b, auth->local_port, 10);
+ break;
+ case 'n':
+ blob_push(b, BLOB_STRLEN(auth->port_name));
+ break;
+ case 'd':
+ blob_push(b, BLOB_STRLEN(auth->port_descr));
+ break;
+ case 'w':
+ blob_push(b, BLOB_STRLEN(auth->webauth_name));
+ break;
+ default:
+ o = p;
+ p++;
+ continue;
+ }
+ p += 2;
+ o = p;
+ }
+ blob_push(b, BLOB_STRLEN((char*) 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];
+ void *token;
+ struct authdb_entry entry;
+ blob_t b = BLOB_BUF(tmp), un;
+
+ token = authdb_get(&adb, &auth->addr, &entry, 1);
+ authdb_clear_entry(&entry);
+
+ blob_push(&b, BLOB_STRLEN(auth->token));
+ if (auth_ok(auth)) {
+ if (token != NULL) {
+ un = BLOB_BUF(entry.p.login_name);
+ blob_push_formatted_username(&un, username_format, auth);
+ memcpy(entry.p.mac_address, auth->mac, MAC_LEN);
+ entry.p.switch_ip = auth->current_switch->addr;
+ entry.p.switch_port = auth->local_port;
+ authdb_commit_login(token, &entry, current_time, &adbc);
+ }
+
+ blob_push(&b, BLOB_STR(" OK user="));
+ blob_push_formatted_username(&b, username_format, auth);
+ blob_push(&b, BLOB_PTR_LEN("\n", 1));
+ } else {
+ if (token != NULL)
+ authdb_commit_logout(token);
+ 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_PTR_LEN(var->val.string, var->val_len);
+}
+
+static void auth_force_reauthentication(struct auth_context *auth)
+{
+ struct switch_info *si = auth->current_switch;
+ netsnmp_pdu *pdu;
+ oid query_oids[ARRAY_SIZE(HP_hpicfUsrAuthPortReauthenticate)+1];
+ blob_t b = BLOB_BUF(query_oids);
+ long one = 1;
+
+ pdu = snmp_pdu_create(SNMP_MSG_SET);
+ blob_push(&b, BLOB_OID(HP_hpicfUsrAuthPortReauthenticate));
+ blob_push_oid(&b, auth->local_port);
+ b = blob_pushed(BLOB_OID(query_oids), b);
+
+ snmp_pdu_add_variable(pdu, oid_blob(b), ASN_INTEGER,
+ (u_char *) &one, sizeof(one));
+
+ /* Send asynchornously - ignore response */
+ if (snmp_send(si->session, pdu) == 0)
+ snmp_free_pdu(pdu);
+}
+
+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:
+ if (kick_out && auth_ok(auth))
+ auth_force_reauthentication(auth);
+
+ 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 with <P1.18 is useless
+ * P1.19-P1.20 has BRIDGE-MIB bug
+ * P2.x+ seem to work more or less */
+ if (si->system_version &&
+ si->system_version[0] == 'P' &&
+ si->system_version[1] == '.' &&
+ si->system_version[2] <= '1')
+ 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(blob_t token, blob_t 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 = blob_cstr_dup(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);
+}
+
+static void handle_line(blob_t line)
+{
+ blob_t id, ipaddr;
+
+ id = blob_pull_cspn(&line, space);
+ blob_pull_spn(&line, space);
+ ipaddr = blob_pull_cspn(&line, space);
+
+ start_authentication(id, ipaddr);
+}
+
+static void read_input(void)
+{
+ static char buffer[256];
+ static blob_t left;
+
+ blob_t b, line;
+ int r;
+
+ if (blob_is_null(left))
+ left = BLOB_BUF(buffer);
+
+ r = read(STDIN_FILENO, left.ptr, left.len);
+ if (r < 0)
+ return;
+ if (r == 0) {
+ running = 0;
+ return;
+ }
+ left.ptr += r;
+ left.len -= r;
+
+ b = blob_pushed(BLOB_BUF(buffer), left);
+ do {
+ line = blob_pull_cspn(&b, lf);
+ if (!blob_pull_matching(&b, lf))
+ return;
+
+ handle_line(line);
+
+ if (b.len) {
+ memcpy(buffer, b.ptr, b.len);
+ b.ptr = buffer;
+ }
+ left = BLOB_PTR_LEN(buffer + b.len, sizeof(buffer) - b.len);
+ } while (b.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, i;
+
+ setenv("MIBS", "", 1);
+ init_snmp("squark-auth");
+
+ while ((opt = getopt(argc, argv, "c:r:i:R:v:f:T:K")) != -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;
+ case 'K':
+ kick_out = TRUE;
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (l3_root == NULL || l3_ifname == NULL || l2_vlan == NULL) {
+ printf("Mandatory information missing\n");
+ return 1;
+ }
+
+ sqdb_open(&db, "/var/lib/squark/squark.db");
+ authdb_open(&adb, &adbc, &db);
+
+ if (l2_root == NULL)
+ l2_root = l3_root;
+
+ 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(BLOB_STRLEN(l2_root), &addr));
+ l2_vlan_ndx = atoi(l2_vlan);
+ username_format_flags = parse_format(username_format);
+
+ if (kick_out)
+ username_format_flags |= FORMAT_PORT_WEBAUTH;
+
+ for (i = 0; i < argc; i++) {
+ blob_t b = BLOB_STRLEN(argv[i]);
+ start_authentication(b, b);
+ running = FALSE;
+ }
+
+ 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();
+ }
+ authdb_close(&adb);
+ sqdb_close(&db);
+
+ return 0;
+}