1138568Ssam/*-
2178354Ssam * Copyright (c) 2004-2008 Sam Leffler, Errno Consulting
3138568Ssam * All rights reserved.
4138568Ssam *
5138568Ssam * Redistribution and use in source and binary forms, with or without
6138568Ssam * modification, are permitted provided that the following conditions
7138568Ssam * are met:
8138568Ssam * 1. Redistributions of source code must retain the above copyright
9138568Ssam *    notice, this list of conditions and the following disclaimer.
10138568Ssam * 2. Redistributions in binary form must reproduce the above copyright
11138568Ssam *    notice, this list of conditions and the following disclaimer in the
12138568Ssam *    documentation and/or other materials provided with the distribution.
13138568Ssam *
14138568Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15138568Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16138568Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17138568Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18138568Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19138568Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20138568Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21138568Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22138568Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23138568Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24138568Ssam */
25138568Ssam
26138568Ssam#include <sys/cdefs.h>
27138568Ssam__FBSDID("$FreeBSD$");
28138568Ssam
29138568Ssam/*
30138568Ssam * IEEE 802.11 MAC ACL support.
31138568Ssam *
32178354Ssam * When this module is loaded the sender address of each auth mgt
33138568Ssam * frame is passed to the iac_check method and the module indicates
34138568Ssam * if the frame should be accepted or rejected.  If the policy is
35138568Ssam * set to ACL_POLICY_OPEN then all frames are accepted w/o checking
36138568Ssam * the address.  Otherwise, the address is looked up in the database
37138568Ssam * and if found the frame is either accepted (ACL_POLICY_ALLOW)
38138568Ssam * or rejected (ACL_POLICY_DENT).
39138568Ssam */
40178354Ssam#include "opt_wlan.h"
41178354Ssam
42138568Ssam#include <sys/param.h>
43138568Ssam#include <sys/kernel.h>
44138568Ssam#include <sys/systm.h>
45138568Ssam#include <sys/mbuf.h>
46138568Ssam#include <sys/module.h>
47138568Ssam#include <sys/queue.h>
48138568Ssam
49138568Ssam#include <sys/socket.h>
50138568Ssam
51138568Ssam#include <net/if.h>
52138568Ssam#include <net/if_media.h>
53138568Ssam#include <net/ethernet.h>
54138568Ssam#include <net/route.h>
55138568Ssam
56138568Ssam#include <net80211/ieee80211_var.h>
57138568Ssam
58138568Ssamenum {
59138568Ssam	ACL_POLICY_OPEN		= 0,	/* open, don't check ACL's */
60138568Ssam	ACL_POLICY_ALLOW	= 1,	/* allow traffic from MAC */
61138568Ssam	ACL_POLICY_DENY		= 2,	/* deny traffic from MAC */
62178354Ssam	/*
63178354Ssam	 * NB: ACL_POLICY_RADIUS must be the same value as
64178354Ssam	 *     IEEE80211_MACCMD_POLICY_RADIUS because of the way
65178354Ssam	 *     acl_getpolicy() works.
66178354Ssam	 */
67178354Ssam	ACL_POLICY_RADIUS	= 7,	/* defer to RADIUS ACL server */
68138568Ssam};
69138568Ssam
70138568Ssam#define	ACL_HASHSIZE	32
71138568Ssam
72138568Ssamstruct acl {
73138568Ssam	TAILQ_ENTRY(acl)	acl_list;
74138568Ssam	LIST_ENTRY(acl)		acl_hash;
75170530Ssam	uint8_t			acl_macaddr[IEEE80211_ADDR_LEN];
76138568Ssam};
77138568Ssamstruct aclstate {
78138568Ssam	acl_lock_t		as_lock;
79138568Ssam	int			as_policy;
80223145Skevlo	uint32_t		as_nacls;
81138568Ssam	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
82138568Ssam	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
83178354Ssam	struct ieee80211vap	*as_vap;
84138568Ssam};
85138568Ssam
86138568Ssam/* simple hash is enough for variation of macaddr */
87138568Ssam#define	ACL_HASH(addr)	\
88170530Ssam	(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
89138568Ssam
90227293Sedstatic MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
91138568Ssam
92178354Ssamstatic	int acl_free_all(struct ieee80211vap *);
93138568Ssam
94178354Ssam/* number of references from net80211 layer */
95178354Ssamstatic	int nrefs = 0;
96178354Ssam
97138568Ssamstatic int
98178354Ssamacl_attach(struct ieee80211vap *vap)
99138568Ssam{
100138568Ssam	struct aclstate *as;
101138568Ssam
102186302Ssam	as = (struct aclstate *) malloc(sizeof(struct aclstate),
103149028Ssam		M_80211_ACL, M_NOWAIT | M_ZERO);
104138568Ssam	if (as == NULL)
105138568Ssam		return 0;
106138568Ssam	ACL_LOCK_INIT(as, "acl");
107138568Ssam	TAILQ_INIT(&as->as_list);
108138568Ssam	as->as_policy = ACL_POLICY_OPEN;
109178354Ssam	as->as_vap = vap;
110178354Ssam	vap->iv_as = as;
111178354Ssam	nrefs++;			/* NB: we assume caller locking */
112138568Ssam	return 1;
113138568Ssam}
114138568Ssam
115138568Ssamstatic void
116178354Ssamacl_detach(struct ieee80211vap *vap)
117138568Ssam{
118178354Ssam	struct aclstate *as = vap->iv_as;
119138568Ssam
120178354Ssam	KASSERT(nrefs > 0, ("imbalanced attach/detach"));
121178354Ssam	nrefs--;			/* NB: we assume caller locking */
122178354Ssam
123178354Ssam	acl_free_all(vap);
124178354Ssam	vap->iv_as = NULL;
125138568Ssam	ACL_LOCK_DESTROY(as);
126186302Ssam	free(as, M_80211_ACL);
127138568Ssam}
128138568Ssam
129139503Ssamstatic __inline struct acl *
130170530Ssam_find_acl(struct aclstate *as, const uint8_t *macaddr)
131138568Ssam{
132138568Ssam	struct acl *acl;
133138568Ssam	int hash;
134138568Ssam
135138568Ssam	hash = ACL_HASH(macaddr);
136138568Ssam	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
137138568Ssam		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
138138568Ssam			return acl;
139138568Ssam	}
140138568Ssam	return NULL;
141138568Ssam}
142138568Ssam
143138568Ssamstatic void
144138568Ssam_acl_free(struct aclstate *as, struct acl *acl)
145138568Ssam{
146138568Ssam	ACL_LOCK_ASSERT(as);
147138568Ssam
148138568Ssam	TAILQ_REMOVE(&as->as_list, acl, acl_list);
149138568Ssam	LIST_REMOVE(acl, acl_hash);
150186302Ssam	free(acl, M_80211_ACL);
151149028Ssam	as->as_nacls--;
152138568Ssam}
153138568Ssam
154138568Ssamstatic int
155228622Sbschmidtacl_check(struct ieee80211vap *vap, const struct ieee80211_frame *wh)
156138568Ssam{
157178354Ssam	struct aclstate *as = vap->iv_as;
158138568Ssam
159138568Ssam	switch (as->as_policy) {
160138568Ssam	case ACL_POLICY_OPEN:
161178354Ssam	case ACL_POLICY_RADIUS:
162138568Ssam		return 1;
163138568Ssam	case ACL_POLICY_ALLOW:
164228622Sbschmidt		return _find_acl(as, wh->i_addr2) != NULL;
165138568Ssam	case ACL_POLICY_DENY:
166228622Sbschmidt		return _find_acl(as, wh->i_addr2) == NULL;
167138568Ssam	}
168138568Ssam	return 0;		/* should not happen */
169138568Ssam}
170138568Ssam
171138568Ssamstatic int
172178354Ssamacl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
173138568Ssam{
174178354Ssam	struct aclstate *as = vap->iv_as;
175138568Ssam	struct acl *acl, *new;
176138568Ssam	int hash;
177138568Ssam
178186302Ssam	new = (struct acl *) malloc(sizeof(struct acl), M_80211_ACL, M_NOWAIT | M_ZERO);
179138568Ssam	if (new == NULL) {
180178354Ssam		IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
181138568Ssam			"ACL: add %s failed, no memory\n", ether_sprintf(mac));
182138568Ssam		/* XXX statistic */
183138568Ssam		return ENOMEM;
184138568Ssam	}
185138568Ssam
186138568Ssam	ACL_LOCK(as);
187138568Ssam	hash = ACL_HASH(mac);
188138568Ssam	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
189138568Ssam		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
190138568Ssam			ACL_UNLOCK(as);
191186302Ssam			free(new, M_80211_ACL);
192178354Ssam			IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
193138568Ssam				"ACL: add %s failed, already present\n",
194138568Ssam				ether_sprintf(mac));
195138568Ssam			return EEXIST;
196138568Ssam		}
197138568Ssam	}
198138568Ssam	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
199138568Ssam	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
200138568Ssam	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
201149028Ssam	as->as_nacls++;
202138568Ssam	ACL_UNLOCK(as);
203138568Ssam
204178354Ssam	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
205138568Ssam		"ACL: add %s\n", ether_sprintf(mac));
206138568Ssam	return 0;
207138568Ssam}
208138568Ssam
209138568Ssamstatic int
210178354Ssamacl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
211138568Ssam{
212178354Ssam	struct aclstate *as = vap->iv_as;
213138568Ssam	struct acl *acl;
214138568Ssam
215138568Ssam	ACL_LOCK(as);
216138568Ssam	acl = _find_acl(as, mac);
217138568Ssam	if (acl != NULL)
218138568Ssam		_acl_free(as, acl);
219138568Ssam	ACL_UNLOCK(as);
220138568Ssam
221178354Ssam	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
222138568Ssam		"ACL: remove %s%s\n", ether_sprintf(mac),
223138568Ssam		acl == NULL ? ", not present" : "");
224138568Ssam
225138568Ssam	return (acl == NULL ? ENOENT : 0);
226138568Ssam}
227138568Ssam
228138568Ssamstatic int
229178354Ssamacl_free_all(struct ieee80211vap *vap)
230138568Ssam{
231178354Ssam	struct aclstate *as = vap->iv_as;
232138568Ssam	struct acl *acl;
233138568Ssam
234178354Ssam	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
235138568Ssam
236138568Ssam	ACL_LOCK(as);
237138568Ssam	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
238138568Ssam		_acl_free(as, acl);
239138568Ssam	ACL_UNLOCK(as);
240138568Ssam
241138568Ssam	return 0;
242138568Ssam}
243138568Ssam
244138568Ssamstatic int
245178354Ssamacl_setpolicy(struct ieee80211vap *vap, int policy)
246138568Ssam{
247178354Ssam	struct aclstate *as = vap->iv_as;
248138568Ssam
249178354Ssam	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
250138568Ssam		"ACL: set policy to %u\n", policy);
251138568Ssam
252138568Ssam	switch (policy) {
253138568Ssam	case IEEE80211_MACCMD_POLICY_OPEN:
254138568Ssam		as->as_policy = ACL_POLICY_OPEN;
255138568Ssam		break;
256138568Ssam	case IEEE80211_MACCMD_POLICY_ALLOW:
257138568Ssam		as->as_policy = ACL_POLICY_ALLOW;
258138568Ssam		break;
259138568Ssam	case IEEE80211_MACCMD_POLICY_DENY:
260138568Ssam		as->as_policy = ACL_POLICY_DENY;
261138568Ssam		break;
262178354Ssam	case IEEE80211_MACCMD_POLICY_RADIUS:
263178354Ssam		as->as_policy = ACL_POLICY_RADIUS;
264178354Ssam		break;
265138568Ssam	default:
266138568Ssam		return EINVAL;
267138568Ssam	}
268138568Ssam	return 0;
269138568Ssam}
270138568Ssam
271138568Ssamstatic int
272178354Ssamacl_getpolicy(struct ieee80211vap *vap)
273138568Ssam{
274178354Ssam	struct aclstate *as = vap->iv_as;
275138568Ssam
276138568Ssam	return as->as_policy;
277138568Ssam}
278138568Ssam
279149028Ssamstatic int
280178354Ssamacl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
281149028Ssam{
282149028Ssam
283149028Ssam	return EINVAL;
284149028Ssam}
285149028Ssam
286149028Ssamstatic int
287178354Ssamacl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
288149028Ssam{
289178354Ssam	struct aclstate *as = vap->iv_as;
290149028Ssam	struct acl *acl;
291149028Ssam	struct ieee80211req_maclist *ap;
292223145Skevlo	int error;
293223145Skevlo	uint32_t i, space;
294149028Ssam
295149028Ssam	switch (ireq->i_val) {
296149028Ssam	case IEEE80211_MACCMD_POLICY:
297149028Ssam		ireq->i_val = as->as_policy;
298149028Ssam		return 0;
299149028Ssam	case IEEE80211_MACCMD_LIST:
300149028Ssam		space = as->as_nacls * IEEE80211_ADDR_LEN;
301149028Ssam		if (ireq->i_len == 0) {
302149028Ssam			ireq->i_len = space;	/* return required space */
303149028Ssam			return 0;		/* NB: must not error */
304149028Ssam		}
305186302Ssam		ap = (struct ieee80211req_maclist *) malloc(space,
306178354Ssam		    M_TEMP, M_NOWAIT);
307149028Ssam		if (ap == NULL)
308149028Ssam			return ENOMEM;
309149028Ssam		i = 0;
310149028Ssam		ACL_LOCK(as);
311149028Ssam		TAILQ_FOREACH(acl, &as->as_list, acl_list) {
312149028Ssam			IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr);
313149028Ssam			i++;
314149028Ssam		}
315149028Ssam		ACL_UNLOCK(as);
316149028Ssam		if (ireq->i_len >= space) {
317149028Ssam			error = copyout(ap, ireq->i_data, space);
318149028Ssam			ireq->i_len = space;
319149028Ssam		} else
320149028Ssam			error = copyout(ap, ireq->i_data, ireq->i_len);
321186302Ssam		free(ap, M_TEMP);
322149028Ssam		return error;
323149028Ssam	}
324149028Ssam	return EINVAL;
325149028Ssam}
326149028Ssam
327138568Ssamstatic const struct ieee80211_aclator mac = {
328138568Ssam	.iac_name	= "mac",
329138568Ssam	.iac_attach	= acl_attach,
330138568Ssam	.iac_detach	= acl_detach,
331138568Ssam	.iac_check	= acl_check,
332138568Ssam	.iac_add	= acl_add,
333138568Ssam	.iac_remove	= acl_remove,
334138568Ssam	.iac_flush	= acl_free_all,
335138568Ssam	.iac_setpolicy	= acl_setpolicy,
336138568Ssam	.iac_getpolicy	= acl_getpolicy,
337149028Ssam	.iac_setioctl	= acl_setioctl,
338149028Ssam	.iac_getioctl	= acl_getioctl,
339138568Ssam};
340178354SsamIEEE80211_ACL_MODULE(wlan_acl, mac, 1);
341