From 25593b5e6fea76ed7c08db586924032c0810c27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Ter=C3=A4s?= Date: Sun, 7 Nov 2010 00:47:39 +0200 Subject: squark: reorganize sources to src directory --- src/squark-auth-snmp.c | 1152 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1152 insertions(+) create mode 100644 src/squark-auth-snmp.c (limited to 'src/squark-auth-snmp.c') diff --git a/src/squark-auth-snmp.c b/src/squark-auth-snmp.c new file mode 100644 index 0000000..81b846d --- /dev/null +++ b/src/squark-auth-snmp.c @@ -0,0 +1,1152 @@ +/* squark-auth-snmp.c - Squid User Authentication and Rating Kit + * An external acl helper for Squid which collects authentication + * information about an IP-address from switches via SNMP. + * + * Copyright (C) 2010 Timo Teräs + * 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 "blob.h" +#include "addr.h" +#include "authdb.h" +#include "filterdb.h" + +/* Compile time configurables */ +#define SWITCH_HASH_SIZE 128 +#define PORT_HASH_SIZE 128 +#define CACHE_TIME 120 /* seconds */ + +/* Some helpers */ +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define MAC_LEN 6 + +#define oid_const(oid) (oid), ARRAY_SIZE(oid) +#define oid_blob(b) ((oid *) (b).ptr), ((b).len / sizeof(oid)) + +/* Format specifiers for username type */ +#define FORMAT_CLIENT_IP 0x01 /* %I */ +#define FORMAT_CLIENT_MAC 0x02 /* %M */ +#define FORMAT_SWITCH_NAME 0x04 /* %N */ +#define FORMAT_SWITCH_LOCATION 0x08 /* %L */ +#define FORMAT_PORT_INDEX 0x10 /* %i */ +#define FORMAT_PORT_NAME 0x20 /* %n */ +#define FORMAT_PORT_DESCR 0x40 /* %d */ +#define FORMAT_PORT_WEBAUTH 0x80 /* %w */ + +/* Some info about the switch which we need */ +#define SWITCHF_NO_LLDP 0x01 +#define SWITCHF_BRIDGE_MIB_HAS_VLAN 0x02 + +/* IANA-AddressFamilyNumbers */ +#define IANA_AFN_OTHER 0 +#define IANA_AFN_IPV4 1 +#define IANA_AFN_IPV6 2 + +/* OIDs used by the program */ +static const oid SNMPv2_MIB_sysObjectID[] = + { SNMP_OID_MIB2, 1, 2, 0 }; +static const oid SNMPv2_MIB_sysName[] = + { SNMP_OID_MIB2, 1, 5, 0 }; +static const oid SNMPv2_MIB_sysLocation[] = + { SNMP_OID_MIB2, 1, 6, 0 }; +static const oid IF_MIB_ifDescr[] = + { SNMP_OID_MIB2, 2, 2, 1, 2 }; +static const oid IF_MIB_ifName[] = + { SNMP_OID_MIB2, 31, 1, 1, 1, 1 }; +static const oid IF_MIB_ifStackStatus[] = + { SNMP_OID_MIB2, 31, 1, 2, 1, 3 }; +static const oid IP_MIB_ipNetToPhysicalPhysAddress[] = + { SNMP_OID_MIB2, 4, 35, 1, 4 }; +static const oid BRIDGE_MIB_dot1dTpFdbPort[] = + { SNMP_OID_MIB2, 17, 4, 3, 1, 2 }; +static const oid LLDP_lldpLocSysName[] = + { 1, 0, 8802, 1, 1, 2, 1, 3, 3, 0 }; +static const oid LLDP_lldpRemManAddrIfSubtype[] = + { 1, 0, 8802, 1, 1, 2, 1, 4, 2, 1, 3 }; +static const oid HP_hpicfUsrAuthWebAuthSessionName[] = + { SNMP_OID_ENTERPRISES, 11, 2, 14, 11, 5, 1, 19, 5, 1, 1, 2 }; +static const oid HP_hpicfUsrAuthPortReauthenticate[] = + { SNMP_OID_ENTERPRISES, 11, 2, 14, 11, 5, 1, 19, 2, 1, 1, 4 }; +static const oid SEMI_MIB_hpHttpMgVersion[] = + { SNMP_OID_ENTERPRISES, 11, 2, 36, 1, 1, 2, 6, 0 }; + +/* ----------------------------------------------------------------- */ + +struct switch_info; + +static int num_queries = 0; +static int running = TRUE; +static int kick_out = FALSE; + +static struct sqdb db; +static struct authdb adb; +static struct authdb_config adbc; +static const char *snmp_community = NULL; +static const char *username_format = "%w"; +static struct switch_info *all_switches[SWITCH_HASH_SIZE]; +static struct switch_info *l3_root_dev, *l2_root_dev; +static int l3_if_ndx, l2_vlan_ndx; +static time_t current_time; +static int username_format_flags; + +static const blob_t space = BLOB_STR_INIT(" "); +static const blob_t lf = BLOB_STR_INIT("\n"); + +/* ----------------------------------------------------------------- */ + +#define BLOB_OID(objid) BLOB_BUF(objid) +#define BLOB_OID_DYN(objid,len) BLOB_PTR_LEN(objid, (len) * sizeof(oid)) + +static inline void blob_push_oid(blob_t *b, oid objid) +{ + if (b->len >= sizeof(objid)) { + *((oid*) b->ptr) = objid; + b->ptr += sizeof(oid); + b->len -= sizeof(oid); + } else { + *b = BLOB_NULL; + } +} + +static inline oid blob_pull_oid(blob_t *b) +{ + oid objid; + + if (b->len >= sizeof(objid)) { + objid = *((oid*) b->ptr); + b->ptr += sizeof(oid); + b->len -= sizeof(oid); + } else { + *b = BLOB_NULL; + objid = -1; + } + return objid; +} + +static inline void blob_push_oid_dump(blob_t *b, blob_t d) +{ + int i; + + if (b->len >= d.len * sizeof(oid)) { + for (i = 0; i < d.len; i++) { + *((oid*) b->ptr) = (unsigned char) d.ptr[i]; + b->ptr += sizeof(oid); + b->len -= sizeof(oid); + } + } else { + *b = BLOB_NULL; + } +} + +static inline void blob_pull_oid_dump(blob_t *b, blob_t d) +{ + int i; + + if (b->len >= d.len * sizeof(oid)) { + for (i = 0; i < d.len; i++) { + d.ptr[i] = (unsigned char) *((oid*) b->ptr); + b->ptr += sizeof(oid); + b->len -= sizeof(oid); + } + } else { + *b = BLOB_NULL; + } +} + +/* ----------------------------------------------------------------- */ + +void blob_push_iana_afn(blob_t *b, sockaddr_any *addr) +{ + unsigned char *ptr; + int type=0, len=0; + + switch (addr->any.sa_family) { + case AF_INET: + type = IANA_AFN_IPV4; + len = 4; + ptr = (unsigned char*) &addr->ipv4.sin_addr; + break; + } + if (type == 0 || b->len < len) { + *b = BLOB_NULL; + return; + } + blob_push_oid(b, type); + blob_push_oid(b, len); + blob_push_oid_dump(b, BLOB_PTR_LEN(ptr, len)); +} + +sockaddr_any *blob_pull_iana_afn(blob_t *b, sockaddr_any *addr) +{ + unsigned char *ptr = NULL; + int type, len; + + memset(addr, 0, sizeof(*addr)); + type = blob_pull_oid(b); + len = blob_pull_oid(b); + if (type == IANA_AFN_IPV4 && len == 4) { + addr->ipv4.sin_family = AF_INET; + ptr = (unsigned char*) &addr->ipv4.sin_addr; + } + if (ptr == NULL) { + blob_pull_skip(b, len); + return NULL; + } + blob_pull_oid_dump(b, BLOB_PTR_LEN(ptr, len)); + return addr; +} + +/* ----------------------------------------------------------------- */ + +static void safe_free(void *ptr) +{ + void **pptr = ptr; + if (*pptr != NULL) { + free(*pptr); + *pptr = NULL; + } +} + +struct cache_control { + time_t update_time; + struct auth_context * sleepers; +}; + +struct switch_port_info { + struct switch_port_info * next; + int port; + struct cache_control cache_control; + sockaddr_any link_partner; +}; + +struct switch_info { + struct switch_info * next; + sockaddr_any addr; + netsnmp_session * session; + + struct cache_control cache_control; + int flags; + int info_available; + char * system_name; + char * system_location; + char * system_version; + blob_t system_oid; + + struct switch_port_info * all_ports[PORT_HASH_SIZE]; +}; + +struct auth_context { + char * token; + sockaddr_any addr; + unsigned char mac[MAC_LEN]; + int info_available; + struct switch_info * current_switch; + struct switch_port_info *spi; + int local_port; + int lldp_port[8]; + int num_lldp_ports; + char * port_name; + char * port_descr; + char * webauth_name; + + void (*pending_operation)(struct auth_context *); + struct auth_context * next_sleeper; +}; + +static void cache_update_time(void) +{ + current_time = time(NULL); + adbc_refresh(&adbc, current_time); +} + +static int cache_refresh( + struct cache_control *cc, struct auth_context *auth, + void (*callback)(struct auth_context *auth)) +{ + int ret; + + if (cc->update_time == -1 || + cc->update_time + CACHE_TIME >= current_time) { + callback(auth); + return 0; + } + + auth->pending_operation = callback; + + ret = (cc->sleepers == NULL); + auth->next_sleeper = cc->sleepers; + cc->sleepers = auth; + + return ret; +} + +static void cache_update(struct cache_control *cc) +{ + struct auth_context *auth, *next; + + cc->update_time = current_time; + auth = cc->sleepers; + cc->sleepers = NULL; + for (; auth; auth = next) { + next = auth->next_sleeper; + auth->pending_operation(auth); + } +} + +static void cache_update_manual(struct cache_control *cc) +{ + cache_update(cc); + cc->update_time = -1; +} + +static void switch_info_free(struct switch_info *si) +{ + safe_free(&si->system_name); + safe_free(&si->system_location); + safe_free(&si->system_version); + safe_free(&si->system_oid.ptr); + si->info_available = 0; + si->flags = 0; +} + +struct switch_info *get_switch(sockaddr_any *addr) +{ + struct snmp_session config; + struct switch_info *si; + unsigned int bucket = addr_hash(addr) % ARRAY_SIZE(all_switches); + + for (si = all_switches[bucket]; si != NULL; si = si->next) + if (addr_cmp(&si->addr, addr) == 0) + return si; + + si = calloc(1, sizeof(*si)); + if (si == NULL) + return NULL; + + addr_copy(&si->addr, addr); + + snmp_sess_init(&config); + if (snmp_community != NULL) { + config.version = SNMP_VERSION_2c; + config.community = (unsigned char *) snmp_community; + config.community_len = strlen(snmp_community); + } + config.peername = (char *) addr_print(addr); + si->session = snmp_open(&config); + + si->next = all_switches[bucket]; + all_switches[bucket] = si; + + return si; +} + +struct switch_port_info *get_switch_port(struct switch_info *si, int port) +{ + unsigned int bucket = port % ARRAY_SIZE(si->all_ports); + struct switch_port_info *spi; + + if (si == NULL) + return NULL; + + for (spi = si->all_ports[bucket]; spi != NULL; spi = spi->next) + if (spi->port == port) + return spi; + + spi = calloc(1, sizeof(*spi)); + if (spi == NULL) + return NULL; + + spi->port = port; + spi->next = si->all_ports[bucket]; + si->all_ports[bucket] = spi; + + return spi; +} + +void link_switch(const char *a, int ap, const char *b, int bp) +{ + struct switch_info *sia, *sib; + struct switch_port_info *spia, *spib; + sockaddr_any addr; + + sia = get_switch(addr_parse(BLOB_STRLEN(a), &addr)); + spia = get_switch_port(sia, ap); + + sib = get_switch(addr_parse(BLOB_STRLEN(b), &addr)); + spib = get_switch_port(sib, bp); + + addr_copy(&spia->link_partner, &sib->addr); + addr_copy(&spib->link_partner, &sia->addr); + + cache_update_manual(&spia->cache_control); + cache_update_manual(&spib->cache_control); +} + +static void auth_query_switch_info(struct auth_context *auth); +static void auth_query_lldp(struct auth_context *auth, int root_query); + +static void auth_free(struct auth_context *auth) +{ + safe_free(&auth->token); + safe_free(&auth->port_name); + safe_free(&auth->port_descr); + safe_free(&auth->webauth_name); + free(auth); +} + +int resolve_ifName2ifIndex(struct switch_info *si, blob_t ifName) +{ + netsnmp_pdu *pdu, *response = NULL; + netsnmp_variable_list *vars, *lastvar = NULL; + int rc = -1; + + pdu = snmp_pdu_create(SNMP_MSG_GETBULK); + pdu->non_repeaters = 0; + pdu->max_repetitions = 10; + snmp_add_null_var(pdu, oid_const(IF_MIB_ifName)); + + do { + if (snmp_synch_response(si->session, pdu, &response) != 0) + return -1; + if (response->errstat != SNMP_ERR_NOERROR) + goto done; + + for (vars = response->variables; vars; vars = vars->next_variable) { + lastvar = vars; + + if (vars->name_length < ARRAY_SIZE(IF_MIB_ifName) || + memcmp(vars->name, IF_MIB_ifName, sizeof(IF_MIB_ifName)) != 0) + goto done; + + if (vars->type != ASN_OCTET_STR) + continue; + + if (blob_cmp(ifName, BLOB_PTR_LEN(vars->val.string, vars->val_len)) != 0) + continue; + + rc = vars->name[vars->name_length - 1]; + goto done; + } + + pdu = snmp_pdu_create(SNMP_MSG_GETBULK); + pdu->non_repeaters = 0; + pdu->max_repetitions = 10; + snmp_add_null_var(pdu, lastvar->name, lastvar->name_length); + + snmp_free_pdu(response); + response = NULL; + } while (1); + +done: + if (response) + snmp_free_pdu(response); + return rc; +} + + +static int parse_format(const char *fmt) +{ + int flags = 0; + const char *p = fmt; + + while ((p = strchr(p, '%')) != NULL) { + switch (p[1]) { + case 'I': + flags |= FORMAT_CLIENT_IP; + break; + case 'M': + flags |= FORMAT_CLIENT_MAC; + break; + case 'N': + flags |= FORMAT_SWITCH_NAME; + break; + case 'L': + flags |= FORMAT_SWITCH_LOCATION; + break; + case 'i': + flags |= FORMAT_PORT_INDEX; + break; + case 'n': + flags |= FORMAT_PORT_NAME; + break; + case 'd': + flags |= FORMAT_PORT_DESCR; + break; + case 'w': + flags |= FORMAT_PORT_WEBAUTH; + break; + } + p++; + } + return flags; +} + +static void blob_push_formatted_username( + blob_t *b, const char *fmt, struct auth_context *auth) +{ + const char *o = fmt, *p = fmt; + struct switch_info *si = auth->current_switch; + + while ((p = strchr(p, '%')) != NULL) { + blob_push(b, BLOB_PTR_LEN(o, p - o)); + switch (p[1]) { + case 'I': + blob_push(b, BLOB_STRLEN((char*) addr_print(&auth->addr))); + break; + case 'M': + blob_push_hexdump(b, BLOB_BUF(auth->mac)); + break; + case 'N': + blob_push(b, BLOB_STRLEN(si->system_name)); + break; + case 'L': + blob_push(b, BLOB_STRLEN(si->system_location)); + break; + case 'i': + blob_push_uint(b, auth->local_port, 10); + break; + case 'n': + blob_push(b, BLOB_STRLEN(auth->port_name)); + break; + case 'd': + blob_push(b, BLOB_STRLEN(auth->port_descr)); + break; + case 'w': + blob_push(b, BLOB_STRLEN(auth->webauth_name)); + break; + default: + o = p; + p++; + continue; + } + p += 2; + o = p; + } + blob_push(b, BLOB_STRLEN((char*) o)); +} + +static int auth_ok(struct auth_context *auth) +{ + return (auth->info_available & username_format_flags) == username_format_flags; +} + +static void auth_completed(struct auth_context *auth) +{ + char tmp[256]; + void *token; + struct authdb_entry entry; + blob_t b = BLOB_BUF(tmp), un; + + token = authdb_get(&adb, &auth->addr, &entry, 1); + authdb_clear_entry(&entry); + + blob_push(&b, BLOB_STRLEN(auth->token)); + if (auth_ok(auth)) { + if (token != NULL) { + un = BLOB_BUF(entry.p.login_name); + blob_push_formatted_username(&un, username_format, auth); + memcpy(entry.p.mac_address, auth->mac, MAC_LEN); + entry.p.switch_ip = auth->current_switch->addr; + entry.p.switch_port = auth->local_port; + authdb_commit_login(token, &entry, current_time, &adbc); + } + + blob_push(&b, BLOB_STR(" OK user=")); + blob_push_formatted_username(&b, username_format, auth); + blob_push(&b, BLOB_PTR_LEN("\n", 1)); + } else { + if (token != NULL) + authdb_commit_logout(token); + blob_push(&b, BLOB_STR(" ERR\n")); + } + b = blob_pushed(BLOB_BUF(tmp), b); + write(STDOUT_FILENO, b.ptr, b.len); + + auth_free(auth); + num_queries--; +} + +static void auth_talk_snmp(struct auth_context *auth, netsnmp_session *s, netsnmp_pdu *pdu, netsnmp_callback callback) +{ + if (snmp_async_send(s, pdu, callback, auth) == 0) { + snmp_free_pdu(pdu); + auth_completed(auth); + } +} + +static void cache_talk_snmp(struct cache_control *cc, netsnmp_session *s, netsnmp_pdu *pdu, netsnmp_callback callback, struct auth_context *auth) +{ + if (snmp_async_send(s, pdu, callback, auth) == 0) { + snmp_free_pdu(pdu); + cache_update(cc); + } +} + +static blob_t var_parse_type(netsnmp_variable_list **varptr, int asn_tag) +{ + netsnmp_variable_list *var = *varptr; + if (var == NULL) + return BLOB_NULL; + + *varptr = var->next_variable; + if (var->type != asn_tag) + return BLOB_NULL; + + return BLOB_PTR_LEN(var->val.string, var->val_len); +} + +static void auth_force_reauthentication(struct auth_context *auth) +{ + struct switch_info *si = auth->current_switch; + netsnmp_pdu *pdu; + oid query_oids[ARRAY_SIZE(HP_hpicfUsrAuthPortReauthenticate)+1]; + blob_t b = BLOB_BUF(query_oids); + long one = 1; + + pdu = snmp_pdu_create(SNMP_MSG_SET); + blob_push(&b, BLOB_OID(HP_hpicfUsrAuthPortReauthenticate)); + blob_push_oid(&b, auth->local_port); + b = blob_pushed(BLOB_OID(query_oids), b); + + snmp_pdu_add_variable(pdu, oid_blob(b), ASN_INTEGER, + (u_char *) &one, sizeof(one)); + + /* Send asynchornously - ignore response */ + if (snmp_send(si->session, pdu) == 0) + snmp_free_pdu(pdu); +} + +static int auth_handle_portinfo_reply(int oper, netsnmp_session *s, int reqid, netsnmp_pdu *resp, void *data) +{ + struct auth_context *auth = data; + netsnmp_variable_list *var; + + if (oper != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) + goto done; + + var = resp->variables; + if (username_format_flags & FORMAT_PORT_NAME) + auth->port_name = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR)); + if (auth->port_name) + auth->info_available |= FORMAT_PORT_NAME; + if (username_format_flags & FORMAT_PORT_DESCR) + auth->port_descr = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR)); + if (auth->port_descr) + auth->info_available |= FORMAT_PORT_DESCR; + if (username_format_flags & FORMAT_PORT_WEBAUTH) + auth->webauth_name = blob_cstr_dup(var_parse_type(&var, ASN_OCTET_STR)); + if (auth->webauth_name) + auth->info_available |= FORMAT_PORT_WEBAUTH; + +done: + if (kick_out && auth_ok(auth)) + auth_force_reauthentication(auth); + + auth_completed(auth); + return 1; +} + +static void auth_query_port_info(struct auth_context *auth) +{ + struct switch_info *si = auth->current_switch; + netsnmp_pdu *pdu; + oid query_oids[MAX_OID_LEN]; + blob_t query; + + if (auth_ok(auth)) { + auth_completed(auth); + return; + } + + pdu = snmp_pdu_create(SNMP_MSG_GET); + if (username_format_flags & FORMAT_PORT_NAME) { + query = BLOB_OID(query_oids); + blob_push(&query, BLOB_OID(IF_MIB_ifName)); + blob_push_oid(&query, auth->local_port); + query = blob_pushed(BLOB_OID(query_oids), query); + snmp_add_null_var(pdu, oid_blob(query)); + } + if (username_format_flags & FORMAT_PORT_DESCR) { + query = BLOB_OID(query_oids); + blob_push(&query, BLOB_OID(IF_MIB_ifDescr)); + blob_push_oid(&query, auth->local_port); + query = blob_pushed(BLOB_OID(query_oids), query); + snmp_add_null_var(pdu, oid_blob(query)); + } + if (username_format_flags & FORMAT_PORT_WEBAUTH) { + query = BLOB_OID(query_oids); + blob_push(&query, BLOB_OID(HP_hpicfUsrAuthWebAuthSessionName)); + blob_push_oid(&query, auth->local_port); + blob_push_oid_dump(&query, BLOB_BUF(auth->mac)); + query = blob_pushed(BLOB_OID(query_oids), query); + snmp_add_null_var(pdu, oid_blob(query)); + } + auth_talk_snmp(auth, si->session, pdu, auth_handle_portinfo_reply); +} + +static int auth_handle_lldp_reply(int oper, netsnmp_session *s, int reqid, netsnmp_pdu *resp, void *data) +{ + struct auth_context *auth = data; + struct switch_port_info *spi = auth->spi; + netsnmp_variable_list *var = resp->variables; + blob_t res; + int i; + + if (oper != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) + goto fail; + + /* print_variable(var->name, var->name_length, var); */ + + for (i = 0; i < auth->num_lldp_ports; i++) { + if (var == NULL) + goto fail; + if (var->type != ASN_INTEGER) + continue; + /* INDEX: TimeFilter, Port, Idx, Family, Addr */ + res = BLOB_OID_DYN(var->name, var->name_length); + if (blob_pull_matching(&res, BLOB_OID(LLDP_lldpRemManAddrIfSubtype)) && + blob_pull_oid(&res) == 0 && + blob_pull_oid(&res) == auth->lldp_port[i]) { + /* We have mathing LLDP neighbor */ + blob_pull_oid(&res); + blob_pull_iana_afn(&res, &spi->link_partner); + cache_update(&spi->cache_control); + return 1; + } + + var = var->next_variable; + } + auth->num_lldp_ports = 0; + for (; var; var = var->next_variable) { + if (var->type != ASN_INTEGER) + break; + /* print_variable(var->name, var->name_length, var); */ + res = BLOB_OID_DYN(var->name, var->name_length); + if (!blob_pull_matching(&res, BLOB_OID(IF_MIB_ifStackStatus))) + break; + if (blob_pull_oid(&res) != auth->local_port) + break; + auth->lldp_port[auth->num_lldp_ports++] = blob_pull_oid(&res); + if (auth->num_lldp_ports >= ARRAY_SIZE(auth->lldp_port)) + break; + } + if (auth->num_lldp_ports) { + auth_query_lldp(auth, FALSE); + return 1; + } +fail: + cache_update(&spi->cache_control); + return 1; +} + +static void auth_query_lldp(struct auth_context *auth, int root_query) +{ + struct switch_info *si = auth->current_switch; + struct switch_port_info *spi = auth->spi; + netsnmp_pdu *pdu; + oid query_oids[MAX_OID_LEN]; + blob_t query; + int i; + + /* printf("Query LLDP info for %s:%d\n", addr_print(&si->addr), spi->port); */ + + if (si->flags & SWITCHF_NO_LLDP) { + memset(&spi->link_partner, 0, sizeof(spi->link_partner)); + cache_update(&spi->cache_control); + return; + } + + if (root_query) { + auth->num_lldp_ports = 1; + auth->lldp_port[0] = auth->local_port; + } + + pdu = snmp_pdu_create(SNMP_MSG_GETBULK); + pdu->non_repeaters = auth->num_lldp_ports; + pdu->max_repetitions = 8; + + for (i = 0; i < auth->num_lldp_ports; i++) { + /* Query LLDP neighbor. lldpRemManAddrTable is INDEXed with + * [TimeFilter, LocalPort, Index, AddrSubType, Addr] */ + query = BLOB_OID(query_oids); + blob_push(&query, BLOB_OID(LLDP_lldpRemManAddrIfSubtype)); + blob_push_oid(&query, 0); + blob_push_oid(&query, auth->lldp_port[i]); + query = blob_pushed(BLOB_OID(query_oids), query); + snmp_add_null_var(pdu, oid_blob(query)); + } + + if (root_query) { + /* Query interface stacking in case this is aggregated trunk: + * IF-MIB::ifStackStatus.. */ + 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 with system_version && + si->system_version[0] == 'P' && + si->system_version[1] == '.' && + si->system_version[2] <= '1') + si->flags |= SWITCHF_BRIDGE_MIB_HAS_VLAN; + break; + } + } + cache_update(&si->cache_control); + return 1; +} + +static void auth_query_switch_info(struct auth_context *auth) +{ + struct switch_info *si = auth->current_switch; + netsnmp_pdu *pdu; + + auth->info_available &= + ~(FORMAT_SWITCH_NAME | FORMAT_SWITCH_LOCATION | + FORMAT_PORT_INDEX); + + if (!cache_refresh(&si->cache_control, auth, auth_query_fib)) + return; + + pdu = snmp_pdu_create(SNMP_MSG_GET); + snmp_add_null_var(pdu, oid_const(SNMPv2_MIB_sysName)); + snmp_add_null_var(pdu, oid_const(SNMPv2_MIB_sysLocation)); + snmp_add_null_var(pdu, oid_const(SNMPv2_MIB_sysObjectID)); + snmp_add_null_var(pdu, oid_const(SEMI_MIB_hpHttpMgVersion)); + snmp_add_null_var(pdu, oid_const(LLDP_lldpLocSysName)); + cache_talk_snmp(&si->cache_control, si->session, pdu, auth_handle_switch_info_reply, auth); +} + +static int auth_handle_arp_reply(int oper, netsnmp_session *s, int reqid, netsnmp_pdu *resp, void *data) +{ + struct auth_context *auth = data; + netsnmp_variable_list *var = resp->variables; + + if (oper == NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE && + var->type == ASN_OCTET_STR && + var->val_len == MAC_LEN) { + memcpy(auth->mac, var->val.string, MAC_LEN); + auth->info_available |= FORMAT_CLIENT_MAC; + if (!auth_ok(auth)) { + auth->current_switch = l2_root_dev; + auth_query_switch_info(auth); + return 1; + } + } + + auth_completed(auth); + return 1; +} + +void start_authentication(blob_t token, blob_t ip) +{ + struct auth_context *auth; + oid query_oids[MAX_OID_LEN]; + blob_t query; + netsnmp_pdu *pdu; + + num_queries++; + + auth = calloc(1, sizeof(*auth)); + auth->token = blob_cstr_dup(token); + if (addr_parse(ip, &auth->addr) == NULL) { + auth_completed(auth); + return; + } + auth->info_available = FORMAT_CLIENT_IP; + + /* IP-MIB::ipNetToPhysicalPhysAddress..ipv4."1.2.3.4" + * = STRING: 01:12:34:56:78:9a */ + pdu = snmp_pdu_create(SNMP_MSG_GET); + + query = BLOB_OID(query_oids); + blob_push(&query, BLOB_OID(IP_MIB_ipNetToPhysicalPhysAddress)); + blob_push_oid(&query, l3_if_ndx); + blob_push_iana_afn(&query, &auth->addr); + query = blob_pushed(BLOB_OID(query_oids), query); + snmp_add_null_var(pdu, oid_blob(query)); + + auth_talk_snmp(auth, l3_root_dev->session, pdu, auth_handle_arp_reply); +} + +static void handle_line(blob_t line) +{ + blob_t id, ipaddr; + + id = blob_pull_cspn(&line, space); + blob_pull_spn(&line, space); + ipaddr = blob_pull_cspn(&line, space); + + start_authentication(id, ipaddr); +} + +static void read_input(void) +{ + static char buffer[256]; + static blob_t left; + + blob_t b, line; + int r; + + if (blob_is_null(left)) + left = BLOB_BUF(buffer); + + r = read(STDIN_FILENO, left.ptr, left.len); + if (r < 0) + return; + if (r == 0) { + running = 0; + return; + } + left.ptr += r; + left.len -= r; + + b = blob_pushed(BLOB_BUF(buffer), left); + do { + line = blob_pull_cspn(&b, lf); + if (!blob_pull_matching(&b, lf)) + return; + + handle_line(line); + + if (b.len) { + memcpy(buffer, b.ptr, b.len); + b.ptr = buffer; + } + left = BLOB_PTR_LEN(buffer + b.len, sizeof(buffer) - b.len); + } while (b.len); +} + +void load_topology(const char *file) +{ + char a_ip[64], b_ip[64]; + int a_port, b_port; + FILE *in; + + in = fopen(file, "r"); + if (in == NULL) + return; + + while (!feof(in)) { + if (fscanf(in, "%s %d %s %d\n", + a_ip, &a_port, b_ip, &b_port) == 4) + link_switch(a_ip, a_port, b_ip, b_port); + } + fclose(in); +} + +int main(int argc, char **argv) +{ + const char *l3_root = NULL, *l3_ifname = NULL; + const char *l2_root = NULL, *l2_vlan = NULL; + struct timeval timeout; + sockaddr_any addr; + fd_set fdset; + int opt, fds, block, i; + + setenv("MIBS", "", 1); + init_snmp("squark-auth"); + + while ((opt = getopt(argc, argv, "c:r:i:R:v:f:T:K")) != -1) { + switch (opt) { + case 'c': + snmp_community = optarg; + break; + case 'r': + l3_root = optarg; + break; + case 'i': + l3_ifname = optarg; + break; + case 'R': + l2_root = optarg; + break; + case 'v': + l2_vlan = optarg; + break; + case 'f': + username_format = optarg; + break; + case 'T': + load_topology(optarg); + break; + case 'K': + kick_out = TRUE; + break; + } + } + argc -= optind; + argv += optind; + + if (l3_root == NULL || l3_ifname == NULL || l2_vlan == NULL) { + printf("Mandatory information missing\n"); + return 1; + } + + sqdb_open(&db, "/var/lib/squark/squark.db"); + authdb_open(&adb, &adbc, &db); + + if (l2_root == NULL) + l2_root = l3_root; + + l3_root_dev = get_switch(addr_parse(BLOB_STRLEN(l3_root), &addr)); + l3_if_ndx = resolve_ifName2ifIndex(l3_root_dev, BLOB_STRLEN((char *) l3_ifname)); + l2_root_dev = get_switch(addr_parse(BLOB_STRLEN(l2_root), &addr)); + l2_vlan_ndx = atoi(l2_vlan); + username_format_flags = parse_format(username_format); + + if (kick_out) + username_format_flags |= FORMAT_PORT_WEBAUTH; + + for (i = 0; i < argc; i++) { + blob_t b = BLOB_STRLEN(argv[i]); + start_authentication(b, b); + running = FALSE; + } + + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); + while (num_queries || running) { + fds = 0; + block = 1; + + FD_ZERO(&fdset); + if (running) { + FD_SET(STDIN_FILENO, &fdset); + fds = STDIN_FILENO + 1; + } + snmp_select_info(&fds, &fdset, &timeout, &block); + fds = select(fds, &fdset, NULL, NULL, block ? NULL : &timeout); + cache_update_time(); + if (fds) { + if (FD_ISSET(STDIN_FILENO, &fdset)) + read_input(); + snmp_read(&fdset); + } else + snmp_timeout(); + } + authdb_close(&adb); + sqdb_close(&db); + + return 0; +} -- cgit v1.2.3