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