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  * $FreeBSD: head/sys/net80211/ieee80211_acl.c 186302 2008-12-18 23:00:09Z sam $
26  * $DragonFly$
27  */
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 <netproto/802_11/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 	int			as_policy;
79 	int			as_nacls;
80 	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
81 	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
82 	struct ieee80211vap	*as_vap;
83 };
84 
85 /* simple hash is enough for variation of macaddr */
86 #define	ACL_HASH(addr)	\
87 	(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
88 
89 MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
90 
91 static	int acl_free_all(struct ieee80211vap *);
92 
93 /* number of references from net80211 layer */
94 static	int nrefs = 0;
95 
96 static int
97 acl_attach(struct ieee80211vap *vap)
98 {
99 	struct aclstate *as;
100 
101 	as = (struct aclstate *) kmalloc(sizeof(struct aclstate),
102 		M_80211_ACL, M_INTWAIT | M_ZERO);
103 	if (as == NULL)
104 		return 0;
105 	TAILQ_INIT(&as->as_list);
106 	as->as_policy = ACL_POLICY_OPEN;
107 	as->as_vap = vap;
108 	vap->iv_as = as;
109 	nrefs++;			/* NB: we assume caller locking */
110 	return 1;
111 }
112 
113 static void
114 acl_detach(struct ieee80211vap *vap)
115 {
116 	struct aclstate *as = vap->iv_as;
117 
118 	KASSERT(nrefs > 0, ("imbalanced attach/detach"));
119 	nrefs--;			/* NB: we assume caller locking */
120 
121 	acl_free_all(vap);
122 	vap->iv_as = NULL;
123 	kfree(as, M_80211_ACL);
124 }
125 
126 static __inline struct acl *
127 _find_acl(struct aclstate *as, const uint8_t *macaddr)
128 {
129 	struct acl *acl;
130 	int hash;
131 
132 	hash = ACL_HASH(macaddr);
133 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
134 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
135 			return acl;
136 	}
137 	return NULL;
138 }
139 
140 static void
141 _acl_free(struct aclstate *as, struct acl *acl)
142 {
143 
144 	TAILQ_REMOVE(&as->as_list, acl, acl_list);
145 	LIST_REMOVE(acl, acl_hash);
146 	kfree(acl, M_80211_ACL);
147 	as->as_nacls--;
148 }
149 
150 static int
151 acl_check(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
152 {
153 	struct aclstate *as = vap->iv_as;
154 
155 	switch (as->as_policy) {
156 	case ACL_POLICY_OPEN:
157 	case ACL_POLICY_RADIUS:
158 		return 1;
159 	case ACL_POLICY_ALLOW:
160 		return _find_acl(as, mac) != NULL;
161 	case ACL_POLICY_DENY:
162 		return _find_acl(as, mac) == NULL;
163 	}
164 	return 0;		/* should not happen */
165 }
166 
167 static int
168 acl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
169 {
170 	struct aclstate *as = vap->iv_as;
171 	struct acl *acl, *new;
172 	int hash;
173 
174 	new = (struct acl *) kmalloc(sizeof(struct acl), M_80211_ACL, M_INTWAIT | M_ZERO);
175 	if (new == NULL) {
176 		IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
177 			"ACL: add %6D failed, no memory\n", mac, ":");
178 		/* XXX statistic */
179 		return ENOMEM;
180 	}
181 
182 	hash = ACL_HASH(mac);
183 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
184 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
185 			kfree(new, M_80211_ACL);
186 			IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
187 				"ACL: add %6D failed, already present\n",
188 				mac, ":");
189 			return EEXIST;
190 		}
191 	}
192 	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
193 	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
194 	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
195 	as->as_nacls++;
196 
197 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
198 		"ACL: add %6D\n", mac, ":");
199 	return 0;
200 }
201 
202 static int
203 acl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
204 {
205 	struct aclstate *as = vap->iv_as;
206 	struct acl *acl;
207 
208 	acl = _find_acl(as, mac);
209 	if (acl != NULL)
210 		_acl_free(as, acl);
211 
212 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
213 		"ACL: remove %6D%s\n", mac, ":",
214 		acl == NULL ? ", not present" : "");
215 
216 	return (acl == NULL ? ENOENT : 0);
217 }
218 
219 static int
220 acl_free_all(struct ieee80211vap *vap)
221 {
222 	struct aclstate *as = vap->iv_as;
223 	struct acl *acl;
224 
225 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
226 
227 	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
228 		_acl_free(as, acl);
229 
230 	return 0;
231 }
232 
233 static int
234 acl_setpolicy(struct ieee80211vap *vap, int policy)
235 {
236 	struct aclstate *as = vap->iv_as;
237 
238 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
239 		"ACL: set policy to %u\n", policy);
240 
241 	switch (policy) {
242 	case IEEE80211_MACCMD_POLICY_OPEN:
243 		as->as_policy = ACL_POLICY_OPEN;
244 		break;
245 	case IEEE80211_MACCMD_POLICY_ALLOW:
246 		as->as_policy = ACL_POLICY_ALLOW;
247 		break;
248 	case IEEE80211_MACCMD_POLICY_DENY:
249 		as->as_policy = ACL_POLICY_DENY;
250 		break;
251 	case IEEE80211_MACCMD_POLICY_RADIUS:
252 		as->as_policy = ACL_POLICY_RADIUS;
253 		break;
254 	default:
255 		return EINVAL;
256 	}
257 	return 0;
258 }
259 
260 static int
261 acl_getpolicy(struct ieee80211vap *vap)
262 {
263 	struct aclstate *as = vap->iv_as;
264 
265 	return as->as_policy;
266 }
267 
268 static int
269 acl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
270 {
271 
272 	return EINVAL;
273 }
274 
275 static int
276 acl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
277 {
278 	struct aclstate *as = vap->iv_as;
279 	struct acl *acl;
280 	struct ieee80211req_maclist *ap;
281 	int error, space, i;
282 
283 	switch (ireq->i_val) {
284 	case IEEE80211_MACCMD_POLICY:
285 		ireq->i_val = as->as_policy;
286 		return 0;
287 	case IEEE80211_MACCMD_LIST:
288 		space = as->as_nacls * IEEE80211_ADDR_LEN;
289 		if (ireq->i_len == 0) {
290 			ireq->i_len = space;	/* return required space */
291 			return 0;		/* NB: must not error */
292 		}
293 		ap = (struct ieee80211req_maclist *) kmalloc(space,
294 		    M_TEMP, M_INTWAIT);
295 		if (ap == NULL)
296 			return ENOMEM;
297 		i = 0;
298 		TAILQ_FOREACH(acl, &as->as_list, acl_list) {
299 			IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr);
300 			i++;
301 		}
302 		if (ireq->i_len >= space) {
303 			error = copyout(ap, ireq->i_data, space);
304 			ireq->i_len = space;
305 		} else
306 			error = copyout(ap, ireq->i_data, ireq->i_len);
307 		kfree(ap, M_TEMP);
308 		return error;
309 	}
310 	return EINVAL;
311 }
312 
313 static const struct ieee80211_aclator mac = {
314 	.iac_name	= "mac",
315 	.iac_attach	= acl_attach,
316 	.iac_detach	= acl_detach,
317 	.iac_check	= acl_check,
318 	.iac_add	= acl_add,
319 	.iac_remove	= acl_remove,
320 	.iac_flush	= acl_free_all,
321 	.iac_setpolicy	= acl_setpolicy,
322 	.iac_getpolicy	= acl_getpolicy,
323 	.iac_setioctl	= acl_setioctl,
324 	.iac_getioctl	= acl_getioctl,
325 };
326 IEEE80211_ACL_MODULE(wlan_acl, mac, 1);
327