/* 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-2011 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: * - 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 "config.h" #include "blob.h" #include "addr.h" #include "authdb.h" #include "filterdb.h" #include "reporting.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_MIB 0x01 #define SWITCHF_QBRIDGE_MIB_HAS_VLAN_NUMBER 0x02 #define SWITCHF_BRIDGE_MIB_HAS_VLAN 0x04 #define SWITCHF_NO_IF_MIB_IFNAME 0x08 /* 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 Q_BRIDGE_MIB_dot1qVlanCurrentEntry_FdbId[] = { SNMP_OID_MIB2, 17, 7, 1, 4, 2, 1, 3, 0 }; static const oid Q_BRIDGE_MIB_dot1qTpFdbPort[] = { SNMP_OID_MIB2, 17, 7, 1, 2, 2, 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"); static sockaddr_any management_subnet; static uint8_t management_prefix = 0; /* ----------------------------------------------------------------- */ #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; int q_vlan_fdb_id; 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; char status_msg[64]; 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; }; void switchinfo_create_ifname(struct auth_context *auth) { char name[64]; if (auth->info_available & FORMAT_PORT_INDEX) { sprintf(name, "%d", auth->local_port); auth->port_name = strdup(name); auth->info_available |= FORMAT_PORT_NAME; } } 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 = strdup((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((char *) a), &addr)); spia = get_switch_port(sia, ap); sib = get_switch(addr_parse(BLOB_STRLEN((char *) 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], *uf_a, *uf_b; void *token; struct authdb_entry entry; blob_t b, uf; int ok = 1; token = authdb_get(&adb, &auth->addr, &entry, 1); authdb_clear_entry(&entry); if (auth_ok(auth)) { b = BLOB_BUF(tmp); blob_push(&b, BLOB_STRLEN(auth->token)); blob_push(&b, BLOB_STR(" OK user=")); uf_a = b.ptr; blob_push_formatted_username(&b, username_format, auth); uf_b = b.ptr; blob_push(&b, BLOB_PTR_LEN("\n", 1)); uf = BLOB_PTR_PTR(uf_a, uf_b); } else { ok = 0; } if (ok && uf.len <= sizeof(entry.p.login_name)) { if (token != NULL && !authdb_check_login(token, &entry, uf, current_time, &adbc)) { authdb_clear_entry(&entry); memcpy(entry.p.login_name, uf.ptr, uf.len); 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); } report_private_message(REPORT_INFO, "%s authenticated as %.*s", addr_print(&auth->addr), uf.len, uf.ptr); } else { if (token != NULL) authdb_commit_logout(token); b = BLOB_BUF(tmp); blob_push(&b, BLOB_STRLEN(auth->token)); blob_push(&b, BLOB_STR(" ERR\n")); report_private_message(REPORT_WARNING, "%s failed: %s", addr_print(&auth->addr), auth->status_msg); } b = blob_pushed(BLOB_BUF(tmp), b); if(write(STDOUT_FILENO, b.ptr, b.len) < 0) report_error("Error occurred while writing to stdout: %s", strerror(errno)); 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 int var_parse_int(netsnmp_variable_list **varptr, int default_value) { netsnmp_variable_list *var = *varptr; if (var == NULL) return default_value; *varptr = var->next_variable; if (var->type != ASN_INTEGER && var->type != ASN_GAUGE) return default_value; return *var->val.integer; } 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; if (asn_tag == ASN_INTEGER) return BLOB_PTR_LEN(var->val.integer, sizeof(*var->val.integer)); 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_need_info(struct auth_context *auth, int needed_flags) { if ((auth->info_available & needed_flags) == needed_flags) return 0; return username_format_flags & needed_flags; } 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 (auth_need_info(auth, 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; } else { auth->current_switch->flags |= SWITCHF_NO_IF_MIB_IFNAME; switchinfo_create_ifname(auth); } } if (auth_need_info(auth, 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 (auth_need_info(auth, 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; } snprintf(auth->status_msg, sizeof(auth->status_msg)-1, "required info missing: info_available=%08x", auth->info_available); report_debug("%s\n", auth->status_msg); 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 (si->flags & SWITCHF_NO_IF_MIB_IFNAME) switchinfo_create_ifname(auth); if (auth_ok(auth) || !auth_need_info(auth, FORMAT_PORT_NAME | FORMAT_PORT_DESCR | FORMAT_PORT_WEBAUTH)) { auth_completed(auth); return; } pdu = snmp_pdu_create(SNMP_MSG_GET); if (auth_need_info(auth, 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 (auth_need_info(auth, 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 (auth_need_info(auth, 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)); } snprintf(auth->status_msg, sizeof(auth->status_msg)-1, "%s: query port info (%d)", si->session->peername, auth->local_port); report_debug("%s\n", auth->status_msg); 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); report_debug("%s: lldp neighbour is %s\n", s->peername, addr_print(&spi->link_partner)); if (management_prefix != 0 && addr_prefix_cmp(&spi->link_partner, &management_subnet, management_prefix) != 0) { report_debug("%s: not matching %s/%d\n", s->peername, addr_print(&management_subnet), management_prefix); addr_invalidate(&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; if (si->flags & SWITCHF_NO_LLDP_MIB) { 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)); } snprintf(auth->status_msg, sizeof(auth->status_msg)-1, "%s: query LLDP tables (%s, base port %d)", si->session->peername, root_query ? "link" : "lacp slaves", auth->lldp_port[0]); report_debug("%s\n", auth->status_msg); 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; pdu = snmp_pdu_create(SNMP_MSG_GET); if (si->q_vlan_fdb_id < 0) { /* 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)); } else { /* Q-BRIDGE-MIB::dot1qTpFdbPort.. = INTEGER: port */ query = BLOB_OID(query_oids); blob_push(&query, BLOB_OID(Q_BRIDGE_MIB_dot1qTpFdbPort)); if (si->flags & SWITCHF_QBRIDGE_MIB_HAS_VLAN_NUMBER) blob_push_oid(&query, l2_vlan_ndx); else blob_push_oid(&query, si->q_vlan_fdb_id); 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)); } snprintf(auth->status_msg, sizeof(auth->status_msg)-1, "%s: probe FIB (%sBRIDGE-MIB)", si->session->peername, si->q_vlan_fdb_id>=0 ? "Q-" : ""); report_debug("%s\n", auth->status_msg); 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_MIB; si->q_vlan_fdb_id = var_parse_int(&var, -1); 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; si->flags |= SWITCHF_QBRIDGE_MIB_HAS_VLAN_NUMBER; 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; oid query_oids[MAX_OID_LEN]; blob_t query; netsnmp_pdu *pdu; auth->info_available &= ~(FORMAT_SWITCH_NAME | FORMAT_SWITCH_LOCATION | FORMAT_PORT_INDEX); snprintf(auth->status_msg, sizeof(auth->status_msg)-1, "%s: refresh switch information", si->session->peername); report_debug("%s\n", auth->status_msg); if (!cache_refresh(&si->cache_control, auth, auth_query_fib)) return; snprintf(auth->status_msg, sizeof(auth->status_msg)-1, "%s: query switch information", si->session->peername); report_debug("%s\n", auth->status_msg); 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)); query = BLOB_OID(query_oids); blob_push(&query, BLOB_OID(Q_BRIDGE_MIB_dot1qVlanCurrentEntry_FdbId)); blob_push_oid(&query, l2_vlan_ndx); query = blob_pushed(BLOB_OID(query_oids), query); snmp_add_null_var(pdu, oid_blob(query)); 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)); snprintf(auth->status_msg, sizeof(auth->status_msg)-1, "%s: map IP %s to MAC on VLAN %d", l3_root_dev->session->peername, addr_print(&auth->addr), l3_if_ndx); report_debug("%s\n", auth->status_msg); 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, rc = 1; reporting_init("squark-auth-snmp"); setenv("MIBS", "", 1); init_snmp("squark-auth-snmp"); while ((opt = getopt(argc, argv, "Vc:r:i:R:v:f:T:KsM:sq")) != -1) { switch (opt) { case 'V': fprintf(stderr, "squark-auth-snmp %s\n", squark_version); return 0; 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; case 'M': if (!addr_parse_prefix(BLOB_STRLEN(optarg), &management_subnet, &management_prefix)) { report_error("'%s' is not a valid network prefix\n", optarg); return 1; } break; case 's': reporting_use_syslog(1); break; case 'q': reporting_verbosity(REPORT_ALERT); break; } } argc -= optind; argv += optind; if (l3_root == NULL || l3_ifname == NULL || l2_vlan == NULL) { report_error("Mandatory information missing\n"); return 1; } if (sqdb_open(&db, squark_dbname) < 0) { report_error("%s: failed to open squarkdb\n", squark_dbname); goto err_sqdb; } if (authdb_open(&adb, &adbc, &db) < 0) { report_error("Failed to initialize authdb\n"); goto err_adb; } if (l2_root == NULL) l2_root = l3_root; l3_root_dev = get_switch(addr_parse(BLOB_STRLEN((char *) 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((char *) 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(); } rc = 0; authdb_close(&adb); err_adb: sqdb_close(&db); err_sqdb: closelog(); return rc; }