diff options
author | Timo Teräs <timo.teras@iki.fi> | 2010-07-22 16:58:58 +0300 |
---|---|---|
committer | Timo Teräs <timo.teras@iki.fi> | 2010-07-22 16:58:58 +0300 |
commit | 17c5c7dbbf673b2e521dcc71900ae22abfbcfc8c (patch) | |
tree | a0dc438b862c59d5ab0f54f2157f25f9c7e92851 | |
download | squark-17c5c7dbbf673b2e521dcc71900ae22abfbcfc8c.tar.bz2 squark-17c5c7dbbf673b2e521dcc71900ae22abfbcfc8c.tar.xz |
squark-auth: initial commit
Basic functionality implemented.
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | squark-auth.c | 1239 |
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; +} |