1 /*-
2  * Copyright (c) 2004-2008 Sam Leffler, Errno Consulting
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 /*
30  * IEEE 802.11 MAC ACL support.
31  *
32  * When this module is loaded the sender address of each auth mgt
33  * frame is passed to the iac_check method and the module indicates
34  * if the frame should be accepted or rejected.  If the policy is
35  * set to ACL_POLICY_OPEN then all frames are accepted w/o checking
36  * the address.  Otherwise, the address is looked up in the database
37  * and if found the frame is either accepted (ACL_POLICY_ALLOW)
38  * or rejected (ACL_POLICY_DENT).
39  */
40 #include "opt_wlan.h"
41 
42 #include <sys/param.h>
43 #include <sys/kernel.h>
44 #include <sys/systm.h>
45 #include <sys/malloc.h>
46 #include <sys/mbuf.h>
47 #include <sys/module.h>
48 #include <sys/queue.h>
49 
50 #include <sys/socket.h>
51 
52 #include <net/if.h>
53 #include <net/if_var.h>
54 #include <net/if_media.h>
55 #include <net/ethernet.h>
56 #include <net/route.h>
57 
58 #include <netproto/802_11/ieee80211_var.h>
59 
60 enum {
61 	ACL_POLICY_OPEN		= 0,	/* open, don't check ACL's */
62 	ACL_POLICY_ALLOW	= 1,	/* allow traffic from MAC */
63 	ACL_POLICY_DENY		= 2,	/* deny traffic from MAC */
64 	/*
65 	 * NB: ACL_POLICY_RADIUS must be the same value as
66 	 *     IEEE80211_MACCMD_POLICY_RADIUS because of the way
67 	 *     acl_getpolicy() works.
68 	 */
69 	ACL_POLICY_RADIUS	= 7,	/* defer to RADIUS ACL server */
70 };
71 
72 #define	ACL_HASHSIZE	32
73 
74 struct acl {
75 	TAILQ_ENTRY(acl)	acl_list;
76 	LIST_ENTRY(acl)		acl_hash;
77 	uint8_t			acl_macaddr[IEEE80211_ADDR_LEN];
78 };
79 struct aclstate {
80 	acl_lock_t		as_lock;
81 	int			as_policy;
82 	uint32_t		as_nacls;
83 	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
84 	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
85 	struct ieee80211vap	*as_vap;
86 };
87 
88 /* simple hash is enough for variation of macaddr */
89 #define	ACL_HASH(addr)	\
90 	(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
91 
92 static MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
93 
94 static	int acl_free_all(struct ieee80211vap *);
95 
96 /* number of references from net80211 layer */
97 static	int nrefs = 0;
98 
99 static int
100 acl_attach(struct ieee80211vap *vap)
101 {
102 	struct aclstate *as;
103 
104 #if defined(__DragonFly__)
105 	as = (struct aclstate *) kmalloc(sizeof(struct aclstate),
106 		M_80211_ACL, M_INTWAIT | M_ZERO);
107 #else
108 	as = (struct aclstate *) IEEE80211_MALLOC(sizeof(struct aclstate),
109 		M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
110 #endif
111 	if (as == NULL)
112 		return 0;
113 	ACL_LOCK_INIT(as, "acl");
114 	TAILQ_INIT(&as->as_list);
115 	as->as_policy = ACL_POLICY_OPEN;
116 	as->as_vap = vap;
117 	vap->iv_as = as;
118 	nrefs++;			/* NB: we assume caller locking */
119 	return 1;
120 }
121 
122 static void
123 acl_detach(struct ieee80211vap *vap)
124 {
125 	struct aclstate *as = vap->iv_as;
126 
127 	KASSERT(nrefs > 0, ("imbalanced attach/detach"));
128 	nrefs--;			/* NB: we assume caller locking */
129 
130 	acl_free_all(vap);
131 	vap->iv_as = NULL;
132 	ACL_LOCK_DESTROY(as);
133 	IEEE80211_FREE(as, M_80211_ACL);
134 }
135 
136 static __inline struct acl *
137 _find_acl(struct aclstate *as, const uint8_t *macaddr)
138 {
139 	struct acl *acl;
140 	int hash;
141 
142 	hash = ACL_HASH(macaddr);
143 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
144 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
145 			return acl;
146 	}
147 	return NULL;
148 }
149 
150 static void
151 _acl_free(struct aclstate *as, struct acl *acl)
152 {
153 	ACL_LOCK_ASSERT(as);
154 
155 	TAILQ_REMOVE(&as->as_list, acl, acl_list);
156 	LIST_REMOVE(acl, acl_hash);
157 	IEEE80211_FREE(acl, M_80211_ACL);
158 	as->as_nacls--;
159 }
160 
161 static int
162 acl_check(struct ieee80211vap *vap, const struct ieee80211_frame *wh)
163 {
164 	struct aclstate *as = vap->iv_as;
165 
166 	switch (as->as_policy) {
167 	case ACL_POLICY_OPEN:
168 	case ACL_POLICY_RADIUS:
169 		return 1;
170 	case ACL_POLICY_ALLOW:
171 		return _find_acl(as, wh->i_addr2) != NULL;
172 	case ACL_POLICY_DENY:
173 		return _find_acl(as, wh->i_addr2) == NULL;
174 	}
175 	return 0;		/* should not happen */
176 }
177 
178 static int
179 acl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
180 {
181 	struct aclstate *as = vap->iv_as;
182 	struct acl *acl, *new;
183 	int hash;
184 
185 #if defined(__DragonFly__)
186 	new = (struct acl *) kmalloc(sizeof(struct acl),
187 		M_80211_ACL, M_INTWAIT | M_ZERO);
188 #else
189 	new = (struct acl *) IEEE80211_MALLOC(sizeof(struct acl),
190 		M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
191 #endif
192 	if (new == NULL) {
193 		IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
194 			"ACL: add %s failed, no memory\n", ether_sprintf(mac));
195 		/* XXX statistic */
196 		return ENOMEM;
197 	}
198 
199 	ACL_LOCK(as);
200 	hash = ACL_HASH(mac);
201 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
202 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
203 			ACL_UNLOCK(as);
204 			IEEE80211_FREE(new, M_80211_ACL);
205 			IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
206 				"ACL: add %s failed, already present\n",
207 				ether_sprintf(mac));
208 			return EEXIST;
209 		}
210 	}
211 	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
212 	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
213 	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
214 	as->as_nacls++;
215 	ACL_UNLOCK(as);
216 
217 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
218 		"ACL: add %s\n", ether_sprintf(mac));
219 	return 0;
220 }
221 
222 static int
223 acl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
224 {
225 	struct aclstate *as = vap->iv_as;
226 	struct acl *acl;
227 
228 	ACL_LOCK(as);
229 	acl = _find_acl(as, mac);
230 	if (acl != NULL)
231 		_acl_free(as, acl);
232 	ACL_UNLOCK(as);
233 
234 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
235 		"ACL: remove %s%s\n", ether_sprintf(mac),
236 		acl == NULL ? ", not present" : "");
237 
238 	return (acl == NULL ? ENOENT : 0);
239 }
240 
241 static int
242 acl_free_all(struct ieee80211vap *vap)
243 {
244 	struct aclstate *as = vap->iv_as;
245 	struct acl *acl;
246 
247 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
248 
249 	ACL_LOCK(as);
250 	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
251 		_acl_free(as, acl);
252 	ACL_UNLOCK(as);
253 
254 	return 0;
255 }
256 
257 static int
258 acl_setpolicy(struct ieee80211vap *vap, int policy)
259 {
260 	struct aclstate *as = vap->iv_as;
261 
262 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
263 		"ACL: set policy to %u\n", policy);
264 
265 	switch (policy) {
266 	case IEEE80211_MACCMD_POLICY_OPEN:
267 		as->as_policy = ACL_POLICY_OPEN;
268 		break;
269 	case IEEE80211_MACCMD_POLICY_ALLOW:
270 		as->as_policy = ACL_POLICY_ALLOW;
271 		break;
272 	case IEEE80211_MACCMD_POLICY_DENY:
273 		as->as_policy = ACL_POLICY_DENY;
274 		break;
275 	case IEEE80211_MACCMD_POLICY_RADIUS:
276 		as->as_policy = ACL_POLICY_RADIUS;
277 		break;
278 	default:
279 		return EINVAL;
280 	}
281 	return 0;
282 }
283 
284 static int
285 acl_getpolicy(struct ieee80211vap *vap)
286 {
287 	struct aclstate *as = vap->iv_as;
288 
289 	return as->as_policy;
290 }
291 
292 static int
293 acl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
294 {
295 
296 	return EINVAL;
297 }
298 
299 static int
300 acl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
301 {
302 	struct aclstate *as = vap->iv_as;
303 	struct acl *acl;
304 	struct ieee80211req_maclist *ap;
305 	int error;
306 	uint32_t i, space;
307 
308 	switch (ireq->i_val) {
309 	case IEEE80211_MACCMD_POLICY:
310 		ireq->i_val = as->as_policy;
311 		return 0;
312 	case IEEE80211_MACCMD_LIST:
313 		space = as->as_nacls * IEEE80211_ADDR_LEN;
314 		if (ireq->i_len == 0) {
315 			ireq->i_len = space;	/* return required space */
316 			return 0;		/* NB: must not error */
317 		}
318 #if defined(__DragonFly__)
319 		ap = (struct ieee80211req_maclist *) kmalloc(space,
320 		    M_TEMP, M_INTWAIT);
321 #else
322 		ap = (struct ieee80211req_maclist *) IEEE80211_MALLOC(space,
323 		    M_TEMP, IEEE80211_M_NOWAIT);
324 #endif
325 		if (ap == NULL)
326 			return ENOMEM;
327 		i = 0;
328 		ACL_LOCK(as);
329 		TAILQ_FOREACH(acl, &as->as_list, acl_list) {
330 			IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr);
331 			i++;
332 		}
333 		ACL_UNLOCK(as);
334 		if (ireq->i_len >= space) {
335 			error = copyout(ap, ireq->i_data, space);
336 			ireq->i_len = space;
337 		} else
338 			error = copyout(ap, ireq->i_data, ireq->i_len);
339 		IEEE80211_FREE(ap, M_TEMP);
340 		return error;
341 	}
342 	return EINVAL;
343 }
344 
345 static const struct ieee80211_aclator mac = {
346 	.iac_name	= "mac",
347 	.iac_attach	= acl_attach,
348 	.iac_detach	= acl_detach,
349 	.iac_check	= acl_check,
350 	.iac_add	= acl_add,
351 	.iac_remove	= acl_remove,
352 	.iac_flush	= acl_free_all,
353 	.iac_setpolicy	= acl_setpolicy,
354 	.iac_getpolicy	= acl_getpolicy,
355 	.iac_setioctl	= acl_setioctl,
356 	.iac_getioctl	= acl_getioctl,
357 };
358 IEEE80211_ACL_MODULE(wlan_acl, mac, 1);
359