From 29331f1c5e940499f282aea8155b89fae69f3fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Ter=C3=A4s?= Date: Sun, 22 Aug 2010 17:54:51 +0300 Subject: auth: rename to squark-auth-snmp Will implement 'captive portal' style authentication with separate DB later. --- Makefile | 4 +- squark-auth-snmp.8 | 74 ++++ squark-auth-snmp.c | 1151 ++++++++++++++++++++++++++++++++++++++++++++++++++++ squark-auth.8 | 74 ---- squark-auth.c | 1151 ---------------------------------------------------- 5 files changed, 1227 insertions(+), 1227 deletions(-) create mode 100644 squark-auth-snmp.8 create mode 100644 squark-auth-snmp.c delete mode 100644 squark-auth.8 delete mode 100644 squark-auth.c diff --git a/Makefile b/Makefile index 51d9e56..55f0d96 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TARGETS=squark-auth squark-filter squarkdb.so +TARGETS=squark-auth-snmp squark-filter squarkdb.so NETSNMP_CFLAGS:=$(shell net-snmp-config --cflags) NETSNMP_LIBS:=$(shell net-snmp-config --libs) @@ -12,7 +12,7 @@ CFLAGS=-g -I. $(NETSNMP_CFLAGS) $(LUA_CFLAGS) $(CMPH_CFLAGS) -std=gnu99 -D_GNU_S all: $(TARGETS) -squark-auth: squark-auth.o blob.o +squark-auth-snmp: squark-auth-snmp.o blob.o $(CC) -o $@ $^ $(NETSNMP_LIBS) squark-filter: squark-filter.o filterdb.o blob.o diff --git a/squark-auth-snmp.8 b/squark-auth-snmp.8 new file mode 100644 index 0000000..ca4be00 --- /dev/null +++ b/squark-auth-snmp.8 @@ -0,0 +1,74 @@ +.TH squark-auth 8 "23 July 2010" "" "Squark Documentation" + +.SH NAME +squark-auth \- Squark authentication helper for Squid + +.SH SYNOPSIS +.BI "squark-auth [" "option" "]..." + +.SH DESCRIPTION +.B squark\-auth +is an external acl helper for Squid. It maps IP-address to +credentials information collected from managed switches using SNMP. + +.SS Theory Of Operation +The code first maps received IP to a MAC using SNMP query from the +router connecting to client IP's subnet. +.PP +This is followed with MAC tracing of the MAC address. The switch +BRIDGE-MIB forwarding database is queried to detect which switch port +is active for the MAC. LLDP-MIB is then queried to receive IP-address +of the switch connected to this port. This is repeated until we end up +in "edge switch" which does not have any LLDP information available +for the port where the MAC address is assigned. +.PP +Finally, the edge switch is interrogated for detailed information of +the port and connected client. + +.SH OPTIONS +The following options are recognized: + +.IP "\fB\-c \fIsnmp\-communicty" +Allows specifying SNMP community. If specified SNMPv2c mode is assumed. +To use SNMPv3 you need to configure the required version and authentication +keys using standard net-snmp configuration files. + +.IP "\fB\-r \fIlayer3\-root\-ip" +The management IP-address of the default gateway router for the clients. +If you have multiple "routing switches", this should be the closest such +switch to the machine running squark\-auth. + +.IP "\fB\-i \fIlayer3\-root\-interface" +The router's network interface connected to the subnet containing the +clients. + +.IP "\fB\-R \fIlayer2\-root\-ip" +In case the router and the first switch are different devices and the +router does not support BRIDGE-MIB, this can be used to specify the +IP-address of the first switch. Defaults to \fIlayer3\-root\-ip\fR. + +.IP "\fB-v \fIlayer2\-vlan" +The VLAN index of the client's subnet for the layer2 switches. + +.IP "\fB-f \fIusername\-format" +Specifies the format of the username to given back for squid. The +following format specifiers are supported: +.TS Header +llw(2i). +%I Client IP address +%M Client MAC address +%N Edge switch sysName +%L Edge switch sysLocation +%i Edge switch port ifIndex +%n Edge switch port ifName +%d Edge switch port ifDescr +%w Edge switch HP ProCurve WebAuth username +.TE + +Defaults to "%w". + +.IP "\fB-T \fItopology\-file" +Load external topology information for switches not supporting LLDP. + +.SH AUTHORS +Timo Teras diff --git a/squark-auth-snmp.c b/squark-auth-snmp.c new file mode 100644 index 0000000..4b88913 --- /dev/null +++ b/squark-auth-snmp.c @@ -0,0 +1,1151 @@ +/* squark-auth.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 + * 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 +#include +#include +#include +#include + +#include +#include + +#include "blob.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 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; + +/* ----------------------------------------------------------------- */ + +#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; + } +} + +/* ----------------------------------------------------------------- */ + +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_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; +} + +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); + 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(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_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]; + blob_t b = BLOB_BUF(tmp); + + blob_push(&b, BLOB_STRLEN(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_PTR_LEN("\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_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.. */ + 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. = 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..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, 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; + } + + 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_STRLEN((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); + + if (kick_out) + username_format_flags |= FORMAT_PORT_WEBAUTH; + + for (i = 0; i < argc; i++) { + start_authentication(argv[i], argv[i]); + 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(); + } + + return 0; +} diff --git a/squark-auth.8 b/squark-auth.8 deleted file mode 100644 index ca4be00..0000000 --- a/squark-auth.8 +++ /dev/null @@ -1,74 +0,0 @@ -.TH squark-auth 8 "23 July 2010" "" "Squark Documentation" - -.SH NAME -squark-auth \- Squark authentication helper for Squid - -.SH SYNOPSIS -.BI "squark-auth [" "option" "]..." - -.SH DESCRIPTION -.B squark\-auth -is an external acl helper for Squid. It maps IP-address to -credentials information collected from managed switches using SNMP. - -.SS Theory Of Operation -The code first maps received IP to a MAC using SNMP query from the -router connecting to client IP's subnet. -.PP -This is followed with MAC tracing of the MAC address. The switch -BRIDGE-MIB forwarding database is queried to detect which switch port -is active for the MAC. LLDP-MIB is then queried to receive IP-address -of the switch connected to this port. This is repeated until we end up -in "edge switch" which does not have any LLDP information available -for the port where the MAC address is assigned. -.PP -Finally, the edge switch is interrogated for detailed information of -the port and connected client. - -.SH OPTIONS -The following options are recognized: - -.IP "\fB\-c \fIsnmp\-communicty" -Allows specifying SNMP community. If specified SNMPv2c mode is assumed. -To use SNMPv3 you need to configure the required version and authentication -keys using standard net-snmp configuration files. - -.IP "\fB\-r \fIlayer3\-root\-ip" -The management IP-address of the default gateway router for the clients. -If you have multiple "routing switches", this should be the closest such -switch to the machine running squark\-auth. - -.IP "\fB\-i \fIlayer3\-root\-interface" -The router's network interface connected to the subnet containing the -clients. - -.IP "\fB\-R \fIlayer2\-root\-ip" -In case the router and the first switch are different devices and the -router does not support BRIDGE-MIB, this can be used to specify the -IP-address of the first switch. Defaults to \fIlayer3\-root\-ip\fR. - -.IP "\fB-v \fIlayer2\-vlan" -The VLAN index of the client's subnet for the layer2 switches. - -.IP "\fB-f \fIusername\-format" -Specifies the format of the username to given back for squid. The -following format specifiers are supported: -.TS Header -llw(2i). -%I Client IP address -%M Client MAC address -%N Edge switch sysName -%L Edge switch sysLocation -%i Edge switch port ifIndex -%n Edge switch port ifName -%d Edge switch port ifDescr -%w Edge switch HP ProCurve WebAuth username -.TE - -Defaults to "%w". - -.IP "\fB-T \fItopology\-file" -Load external topology information for switches not supporting LLDP. - -.SH AUTHORS -Timo Teras diff --git a/squark-auth.c b/squark-auth.c deleted file mode 100644 index 4b88913..0000000 --- a/squark-auth.c +++ /dev/null @@ -1,1151 +0,0 @@ -/* squark-auth.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 - * 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 -#include -#include -#include -#include - -#include -#include - -#include "blob.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 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; - -/* ----------------------------------------------------------------- */ - -#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; - } -} - -/* ----------------------------------------------------------------- */ - -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_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; -} - -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); - 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(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_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]; - blob_t b = BLOB_BUF(tmp); - - blob_push(&b, BLOB_STRLEN(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_PTR_LEN("\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_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.. */ - 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. = 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..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, 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; - } - - 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_STRLEN((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); - - if (kick_out) - username_format_flags |= FORMAT_PORT_WEBAUTH; - - for (i = 0; i < argc; i++) { - start_authentication(argv[i], argv[i]); - 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(); - } - - return 0; -} -- cgit v1.2.3