/*- * Copyright (c) 2004-2007 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * IEEE 802.11 MAC ACL support. * * When this module is loaded the sender address of each received * frame is passed to the iac_check method and the module indicates * if the frame should be accepted or rejected. If the policy is * set to ACL_POLICY_OPEN then all frames are accepted w/o checking * the address. Otherwise, the address is looked up in the database * and if found the frame is either accepted (ACL_POLICY_ALLOW) * or rejected (ACL_POLICY_DENT). */ #include #include #include #include #include #include #include #include #include #include #include #include enum { ACL_POLICY_OPEN = 0, /* open, don't check ACL's */ ACL_POLICY_ALLOW = 1, /* allow traffic from MAC */ ACL_POLICY_DENY = 2, /* deny traffic from MAC */ }; #define ACL_HASHSIZE 32 struct acl { TAILQ_ENTRY(acl) acl_list; LIST_ENTRY(acl) acl_hash; u_int8_t acl_macaddr[IEEE80211_ADDR_LEN]; }; struct aclstate { acl_lock_t as_lock; int as_policy; int as_nacls; TAILQ_HEAD(, acl) as_list; /* list of all ACL's */ LIST_HEAD(, acl) as_hash[ACL_HASHSIZE]; struct ieee80211com *as_ic; }; /* simple hash is enough for variation of macaddr */ #define ACL_HASH(addr) \ (((const u_int8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE) MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl"); static int acl_free_all(struct ieee80211com *); static int acl_attach(struct ieee80211com *ic) { struct aclstate *as; MALLOC(as, struct aclstate *, sizeof(struct aclstate), M_80211_ACL, M_NOWAIT | M_ZERO); if (as == NULL) return 0; ACL_LOCK_INIT(as, "acl"); TAILQ_INIT(&as->as_list); as->as_policy = ACL_POLICY_OPEN; as->as_ic = ic; ic->ic_as = as; return 1; } static void acl_detach(struct ieee80211com *ic) { struct aclstate *as = ic->ic_as; acl_free_all(ic); ic->ic_as = NULL; ACL_LOCK_DESTROY(as); FREE(as, M_DEVBUF); } static __inline struct acl * _find_acl(struct aclstate *as, const u_int8_t *macaddr) { struct acl *acl; int hash; hash = ACL_HASH(macaddr); LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) { if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr)) return acl; } return NULL; } static void _acl_free(struct aclstate *as, struct acl *acl) { ACL_LOCK_ASSERT(as); TAILQ_REMOVE(&as->as_list, acl, acl_list); LIST_REMOVE(acl, acl_hash); FREE(acl, M_80211_ACL); as->as_nacls--; } static int acl_check(struct ieee80211com *ic, const u_int8_t mac[IEEE80211_ADDR_LEN]) { struct aclstate *as = ic->ic_as; switch (as->as_policy) { case ACL_POLICY_OPEN: return 1; case ACL_POLICY_ALLOW: return _find_acl(as, mac) != NULL; case ACL_POLICY_DENY: return _find_acl(as, mac) == NULL; } return 0; /* should not happen */ } static int acl_add(struct ieee80211com *ic, const u_int8_t mac[IEEE80211_ADDR_LEN]) { struct aclstate *as = ic->ic_as; struct acl *acl, *new; int hash; MALLOC(new, struct acl *, sizeof(struct acl), M_80211_ACL, M_NOWAIT | M_ZERO); if (new == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: add %s failed, no memory\n", ether_sprintf(mac)); /* XXX statistic */ return ENOMEM; } ACL_LOCK(as); hash = ACL_HASH(mac); LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) { if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) { ACL_UNLOCK(as); FREE(new, M_80211_ACL); IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: add %s failed, already present\n", ether_sprintf(mac)); return EEXIST; } } IEEE80211_ADDR_COPY(new->acl_macaddr, mac); TAILQ_INSERT_TAIL(&as->as_list, new, acl_list); LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash); as->as_nacls++; ACL_UNLOCK(as); IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: add %s\n", ether_sprintf(mac)); return 0; } static int acl_remove(struct ieee80211com *ic, const u_int8_t mac[IEEE80211_ADDR_LEN]) { struct aclstate *as = ic->ic_as; struct acl *acl; ACL_LOCK(as); acl = _find_acl(as, mac); if (acl != NULL) _acl_free(as, acl); ACL_UNLOCK(as); IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: remove %s%s\n", ether_sprintf(mac), acl == NULL ? ", not present" : ""); return (acl == NULL ? ENOENT : 0); } static int acl_free_all(struct ieee80211com *ic) { struct aclstate *as = ic->ic_as; struct acl *acl; IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: %s\n", "free all"); ACL_LOCK(as); while ((acl = TAILQ_FIRST(&as->as_list)) != NULL) _acl_free(as, acl); ACL_UNLOCK(as); return 0; } static int acl_setpolicy(struct ieee80211com *ic, int policy) { struct aclstate *as = ic->ic_as; IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: set policy to %u\n", policy); switch (policy) { case IEEE80211_MACCMD_POLICY_OPEN: as->as_policy = ACL_POLICY_OPEN; break; case IEEE80211_MACCMD_POLICY_ALLOW: as->as_policy = ACL_POLICY_ALLOW; break; case IEEE80211_MACCMD_POLICY_DENY: as->as_policy = ACL_POLICY_DENY; break; default: return EINVAL; } return 0; } static int acl_getpolicy(struct ieee80211com *ic) { struct aclstate *as = ic->ic_as; return as->as_policy; } static int acl_setioctl(struct ieee80211com *ic, struct ieee80211req *ireq) { return EINVAL; } static int acl_getioctl(struct ieee80211com *ic, struct ieee80211req *ireq) { struct aclstate *as = ic->ic_as; struct acl *acl; struct ieee80211req_maclist *ap; int error, space, i; switch (ireq->i_val) { case IEEE80211_MACCMD_POLICY: ireq->i_val = as->as_policy; return 0; case IEEE80211_MACCMD_LIST: space = as->as_nacls * IEEE80211_ADDR_LEN; if (ireq->i_len == 0) { ireq->i_len = space; /* return required space */ return 0; /* NB: must not error */ } MALLOC(ap, struct ieee80211req_maclist *, space, M_TEMP, M_NOWAIT); if (ap == NULL) return ENOMEM; i = 0; ACL_LOCK(as); TAILQ_FOREACH(acl, &as->as_list, acl_list) { IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr); i++; } ACL_UNLOCK(as); if (ireq->i_len >= space) { error = copyout(ap, ireq->i_data, space); ireq->i_len = space; } else error = copyout(ap, ireq->i_data, ireq->i_len); FREE(ap, M_TEMP); return error; } return EINVAL; } static const struct ieee80211_aclator mac = { .iac_name = "mac", .iac_attach = acl_attach, .iac_detach = acl_detach, .iac_check = acl_check, .iac_add = acl_add, .iac_remove = acl_remove, .iac_flush = acl_free_all, .iac_setpolicy = acl_setpolicy, .iac_getpolicy = acl_getpolicy, .iac_setioctl = acl_setioctl, .iac_getioctl = acl_getioctl, }; /* * Module glue. */ static int wlan_acl_modevent(module_t mod, int type, void *unused) { switch (type) { case MOD_LOAD: if (bootverbose) printf("wlan: <802.11 MAC ACL support>\n"); ieee80211_aclator_register(&mac); return 0; case MOD_UNLOAD: ieee80211_aclator_unregister(&mac); return 0; } return EINVAL; } static moduledata_t wlan_acl_mod = { "wlan_acl", wlan_acl_modevent, 0 }; DECLARE_MODULE(wlan_acl, wlan_acl_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST); MODULE_VERSION(wlan_acl, 1); MODULE_DEPEND(wlan_acl, wlan, 1, 1, 1);