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_media.h>
54 #include <net/ethernet.h>
55 #include <net/route.h>
56 
57 #include <netproto/802_11/ieee80211_var.h>
58 
59 enum {
60 	ACL_POLICY_OPEN		= 0,	/* open, don't check ACL's */
61 	ACL_POLICY_ALLOW	= 1,	/* allow traffic from MAC */
62 	ACL_POLICY_DENY		= 2,	/* deny traffic from MAC */
63 	/*
64 	 * NB: ACL_POLICY_RADIUS must be the same value as
65 	 *     IEEE80211_MACCMD_POLICY_RADIUS because of the way
66 	 *     acl_getpolicy() works.
67 	 */
68 	ACL_POLICY_RADIUS	= 7,	/* defer to RADIUS ACL server */
69 };
70 
71 #define	ACL_HASHSIZE	32
72 
73 struct acl {
74 	TAILQ_ENTRY(acl)	acl_list;
75 	LIST_ENTRY(acl)		acl_hash;
76 	uint8_t			acl_macaddr[IEEE80211_ADDR_LEN];
77 };
78 struct aclstate {
79 	acl_lock_t		as_lock;
80 	int			as_policy;
81 	uint32_t		as_nacls;
82 	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
83 	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
84 	struct ieee80211vap	*as_vap;
85 };
86 
87 /* simple hash is enough for variation of macaddr */
88 #define	ACL_HASH(addr)	\
89 	(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
90 
91 static MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
92 
93 static	int acl_free_all(struct ieee80211vap *);
94 
95 /* number of references from net80211 layer */
96 static	int nrefs = 0;
97 
98 static int
99 acl_attach(struct ieee80211vap *vap)
100 {
101 	struct aclstate *as;
102 
103 #if defined(__DragonFly__)
104 	as = (struct aclstate *) kmalloc(sizeof(struct aclstate),
105 		M_80211_ACL, M_INTWAIT | M_ZERO);
106 #else
107 	as = (struct aclstate *) IEEE80211_MALLOC(sizeof(struct aclstate),
108 		M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
109 #endif
110 	if (as == NULL)
111 		return 0;
112 	ACL_LOCK_INIT(as, "acl");
113 	TAILQ_INIT(&as->as_list);
114 	as->as_policy = ACL_POLICY_OPEN;
115 	as->as_vap = vap;
116 	vap->iv_as = as;
117 	nrefs++;			/* NB: we assume caller locking */
118 	return 1;
119 }
120 
121 static void
122 acl_detach(struct ieee80211vap *vap)
123 {
124 	struct aclstate *as = vap->iv_as;
125 
126 	KASSERT(nrefs > 0, ("imbalanced attach/detach"));
127 	nrefs--;			/* NB: we assume caller locking */
128 
129 	acl_free_all(vap);
130 	vap->iv_as = NULL;
131 	ACL_LOCK_DESTROY(as);
132 	IEEE80211_FREE(as, M_80211_ACL);
133 }
134 
135 static __inline struct acl *
136 _find_acl(struct aclstate *as, const uint8_t *macaddr)
137 {
138 	struct acl *acl;
139 	int hash;
140 
141 	hash = ACL_HASH(macaddr);
142 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
143 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
144 			return acl;
145 	}
146 	return NULL;
147 }
148 
149 static void
150 _acl_free(struct aclstate *as, struct acl *acl)
151 {
152 	ACL_LOCK_ASSERT(as);
153 
154 	TAILQ_REMOVE(&as->as_list, acl, acl_list);
155 	LIST_REMOVE(acl, acl_hash);
156 	IEEE80211_FREE(acl, M_80211_ACL);
157 	as->as_nacls--;
158 }
159 
160 static int
161 acl_check(struct ieee80211vap *vap, const struct ieee80211_frame *wh)
162 {
163 	struct aclstate *as = vap->iv_as;
164 
165 	switch (as->as_policy) {
166 	case ACL_POLICY_OPEN:
167 	case ACL_POLICY_RADIUS:
168 		return 1;
169 	case ACL_POLICY_ALLOW:
170 		return _find_acl(as, wh->i_addr2) != NULL;
171 	case ACL_POLICY_DENY:
172 		return _find_acl(as, wh->i_addr2) == NULL;
173 	}
174 	return 0;		/* should not happen */
175 }
176 
177 static int
178 acl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
179 {
180 	struct aclstate *as = vap->iv_as;
181 	struct acl *acl, *new;
182 	int hash;
183 
184 #if defined(__DragonFly__)
185 	new = (struct acl *) kmalloc(sizeof(struct acl),
186 		M_80211_ACL, M_INTWAIT | M_ZERO);
187 #else
188 	new = (struct acl *) IEEE80211_MALLOC(sizeof(struct acl),
189 		M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
190 #endif
191 	if (new == NULL) {
192 		IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
193 			"ACL: add %s failed, no memory\n", ether_sprintf(mac));
194 		/* XXX statistic */
195 		return ENOMEM;
196 	}
197 
198 	ACL_LOCK(as);
199 	hash = ACL_HASH(mac);
200 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
201 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
202 			ACL_UNLOCK(as);
203 			IEEE80211_FREE(new, M_80211_ACL);
204 			IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
205 				"ACL: add %s failed, already present\n",
206 				ether_sprintf(mac));
207 			return EEXIST;
208 		}
209 	}
210 	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
211 	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
212 	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
213 	as->as_nacls++;
214 	ACL_UNLOCK(as);
215 
216 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
217 		"ACL: add %s\n", ether_sprintf(mac));
218 	return 0;
219 }
220 
221 static int
222 acl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
223 {
224 	struct aclstate *as = vap->iv_as;
225 	struct acl *acl;
226 
227 	ACL_LOCK(as);
228 	acl = _find_acl(as, mac);
229 	if (acl != NULL)
230 		_acl_free(as, acl);
231 	ACL_UNLOCK(as);
232 
233 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
234 		"ACL: remove %s%s\n", ether_sprintf(mac),
235 		acl == NULL ? ", not present" : "");
236 
237 	return (acl == NULL ? ENOENT : 0);
238 }
239 
240 static int
241 acl_free_all(struct ieee80211vap *vap)
242 {
243 	struct aclstate *as = vap->iv_as;
244 	struct acl *acl;
245 
246 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
247 
248 	ACL_LOCK(as);
249 	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
250 		_acl_free(as, acl);
251 	ACL_UNLOCK(as);
252 
253 	return 0;
254 }
255 
256 static int
257 acl_setpolicy(struct ieee80211vap *vap, int policy)
258 {
259 	struct aclstate *as = vap->iv_as;
260 
261 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
262 		"ACL: set policy to %u\n", policy);
263 
264 	switch (policy) {
265 	case IEEE80211_MACCMD_POLICY_OPEN:
266 		as->as_policy = ACL_POLICY_OPEN;
267 		break;
268 	case IEEE80211_MACCMD_POLICY_ALLOW:
269 		as->as_policy = ACL_POLICY_ALLOW;
270 		break;
271 	case IEEE80211_MACCMD_POLICY_DENY:
272 		as->as_policy = ACL_POLICY_DENY;
273 		break;
274 	case IEEE80211_MACCMD_POLICY_RADIUS:
275 		as->as_policy = ACL_POLICY_RADIUS;
276 		break;
277 	default:
278 		return EINVAL;
279 	}
280 	return 0;
281 }
282 
283 static int
284 acl_getpolicy(struct ieee80211vap *vap)
285 {
286 	struct aclstate *as = vap->iv_as;
287 
288 	return as->as_policy;
289 }
290 
291 static int
292 acl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
293 {
294 
295 	return EINVAL;
296 }
297 
298 static int
299 acl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
300 {
301 	struct aclstate *as = vap->iv_as;
302 	struct acl *acl;
303 	struct ieee80211req_maclist *ap;
304 	int error;
305 	uint32_t i, space;
306 
307 	switch (ireq->i_val) {
308 	case IEEE80211_MACCMD_POLICY:
309 		ireq->i_val = as->as_policy;
310 		return 0;
311 	case IEEE80211_MACCMD_LIST:
312 		space = as->as_nacls * IEEE80211_ADDR_LEN;
313 		if (ireq->i_len == 0) {
314 			ireq->i_len = space;	/* return required space */
315 			return 0;		/* NB: must not error */
316 		}
317 #if defined(__DragonFly__)
318 		ap = (struct ieee80211req_maclist *) kmalloc(space,
319 		    M_TEMP, M_INTWAIT);
320 #else
321 		ap = (struct ieee80211req_maclist *) IEEE80211_MALLOC(space,
322 		    M_TEMP, IEEE80211_M_NOWAIT);
323 #endif
324 		if (ap == NULL)
325 			return ENOMEM;
326 		i = 0;
327 		ACL_LOCK(as);
328 		TAILQ_FOREACH(acl, &as->as_list, acl_list) {
329 			IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr);
330 			i++;
331 		}
332 		ACL_UNLOCK(as);
333 		if (ireq->i_len >= space) {
334 			error = copyout(ap, ireq->i_data, space);
335 			ireq->i_len = space;
336 		} else
337 			error = copyout(ap, ireq->i_data, ireq->i_len);
338 		IEEE80211_FREE(ap, M_TEMP);
339 		return error;
340 	}
341 	return EINVAL;
342 }
343 
344 static const struct ieee80211_aclator mac = {
345 	.iac_name	= "mac",
346 	.iac_attach	= acl_attach,
347 	.iac_detach	= acl_detach,
348 	.iac_check	= acl_check,
349 	.iac_add	= acl_add,
350 	.iac_remove	= acl_remove,
351 	.iac_flush	= acl_free_all,
352 	.iac_setpolicy	= acl_setpolicy,
353 	.iac_getpolicy	= acl_getpolicy,
354 	.iac_setioctl	= acl_setioctl,
355 	.iac_getioctl	= acl_getioctl,
356 };
357 IEEE80211_ACL_MODULE(wlan_acl, mac, 1);
358