]>
Commit | Line | Data |
---|---|---|
6fc6879b JM |
1 | /* |
2 | * WPA Supplicant / UDP socket -based control interface | |
3 | * Copyright (c) 2004-2005, Jouni Malinen <j@w1.fi> | |
4 | * | |
0f3d578e JM |
5 | * This software may be distributed under the terms of the BSD license. |
6 | * See README for more details. | |
6fc6879b JM |
7 | */ |
8 | ||
9 | #include "includes.h" | |
10 | ||
11 | #include "common.h" | |
12 | #include "eloop.h" | |
13 | #include "config.h" | |
14 | #include "eapol_supp/eapol_supp_sm.h" | |
15 | #include "wpa_supplicant_i.h" | |
16 | #include "ctrl_iface.h" | |
90973fb2 | 17 | #include "common/wpa_ctrl.h" |
6fc6879b JM |
18 | |
19 | ||
20 | #define COOKIE_LEN 8 | |
21 | ||
22 | /* Per-interface ctrl_iface */ | |
23 | ||
24 | /** | |
25 | * struct wpa_ctrl_dst - Internal data structure of control interface monitors | |
26 | * | |
27 | * This structure is used to store information about registered control | |
28 | * interface monitors into struct wpa_supplicant. This data is private to | |
29 | * ctrl_iface_udp.c and should not be touched directly from other files. | |
30 | */ | |
31 | struct wpa_ctrl_dst { | |
32 | struct wpa_ctrl_dst *next; | |
33 | struct sockaddr_in addr; | |
34 | socklen_t addrlen; | |
35 | int debug_level; | |
36 | int errors; | |
37 | }; | |
38 | ||
39 | ||
40 | struct ctrl_iface_priv { | |
41 | struct wpa_supplicant *wpa_s; | |
42 | int sock; | |
43 | struct wpa_ctrl_dst *ctrl_dst; | |
44 | u8 cookie[COOKIE_LEN]; | |
45 | }; | |
46 | ||
47 | ||
48 | static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, | |
49 | int level, const char *buf, | |
50 | size_t len); | |
51 | ||
52 | ||
53 | static int wpa_supplicant_ctrl_iface_attach(struct ctrl_iface_priv *priv, | |
54 | struct sockaddr_in *from, | |
55 | socklen_t fromlen) | |
56 | { | |
57 | struct wpa_ctrl_dst *dst; | |
58 | ||
59 | dst = os_zalloc(sizeof(*dst)); | |
60 | if (dst == NULL) | |
61 | return -1; | |
62 | os_memcpy(&dst->addr, from, sizeof(struct sockaddr_in)); | |
63 | dst->addrlen = fromlen; | |
64 | dst->debug_level = MSG_INFO; | |
65 | dst->next = priv->ctrl_dst; | |
66 | priv->ctrl_dst = dst; | |
67 | wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor attached %s:%d", | |
68 | inet_ntoa(from->sin_addr), ntohs(from->sin_port)); | |
69 | return 0; | |
70 | } | |
71 | ||
72 | ||
73 | static int wpa_supplicant_ctrl_iface_detach(struct ctrl_iface_priv *priv, | |
74 | struct sockaddr_in *from, | |
75 | socklen_t fromlen) | |
76 | { | |
77 | struct wpa_ctrl_dst *dst, *prev = NULL; | |
78 | ||
79 | dst = priv->ctrl_dst; | |
80 | while (dst) { | |
81 | if (from->sin_addr.s_addr == dst->addr.sin_addr.s_addr && | |
82 | from->sin_port == dst->addr.sin_port) { | |
83 | if (prev == NULL) | |
84 | priv->ctrl_dst = dst->next; | |
85 | else | |
86 | prev->next = dst->next; | |
87 | os_free(dst); | |
88 | wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor detached " | |
89 | "%s:%d", inet_ntoa(from->sin_addr), | |
90 | ntohs(from->sin_port)); | |
91 | return 0; | |
92 | } | |
93 | prev = dst; | |
94 | dst = dst->next; | |
95 | } | |
96 | return -1; | |
97 | } | |
98 | ||
99 | ||
100 | static int wpa_supplicant_ctrl_iface_level(struct ctrl_iface_priv *priv, | |
101 | struct sockaddr_in *from, | |
102 | socklen_t fromlen, | |
103 | char *level) | |
104 | { | |
105 | struct wpa_ctrl_dst *dst; | |
106 | ||
107 | wpa_printf(MSG_DEBUG, "CTRL_IFACE LEVEL %s", level); | |
108 | ||
109 | dst = priv->ctrl_dst; | |
110 | while (dst) { | |
111 | if (from->sin_addr.s_addr == dst->addr.sin_addr.s_addr && | |
112 | from->sin_port == dst->addr.sin_port) { | |
113 | wpa_printf(MSG_DEBUG, "CTRL_IFACE changed monitor " | |
114 | "level %s:%d", inet_ntoa(from->sin_addr), | |
115 | ntohs(from->sin_port)); | |
116 | dst->debug_level = atoi(level); | |
117 | return 0; | |
118 | } | |
119 | dst = dst->next; | |
120 | } | |
121 | ||
122 | return -1; | |
123 | } | |
124 | ||
125 | ||
126 | static char * | |
127 | wpa_supplicant_ctrl_iface_get_cookie(struct ctrl_iface_priv *priv, | |
128 | size_t *reply_len) | |
129 | { | |
130 | char *reply; | |
131 | reply = os_malloc(7 + 2 * COOKIE_LEN + 1); | |
132 | if (reply == NULL) { | |
133 | *reply_len = 1; | |
134 | return NULL; | |
135 | } | |
136 | ||
137 | os_memcpy(reply, "COOKIE=", 7); | |
138 | wpa_snprintf_hex(reply + 7, 2 * COOKIE_LEN + 1, | |
139 | priv->cookie, COOKIE_LEN); | |
140 | ||
141 | *reply_len = 7 + 2 * COOKIE_LEN; | |
142 | return reply; | |
143 | } | |
144 | ||
145 | ||
146 | static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, | |
147 | void *sock_ctx) | |
148 | { | |
149 | struct wpa_supplicant *wpa_s = eloop_ctx; | |
150 | struct ctrl_iface_priv *priv = sock_ctx; | |
151 | char buf[256], *pos; | |
152 | int res; | |
153 | struct sockaddr_in from; | |
154 | socklen_t fromlen = sizeof(from); | |
155 | char *reply = NULL; | |
156 | size_t reply_len = 0; | |
157 | int new_attached = 0; | |
158 | u8 cookie[COOKIE_LEN]; | |
159 | ||
160 | res = recvfrom(sock, buf, sizeof(buf) - 1, 0, | |
161 | (struct sockaddr *) &from, &fromlen); | |
162 | if (res < 0) { | |
163 | perror("recvfrom(ctrl_iface)"); | |
164 | return; | |
165 | } | |
166 | if (from.sin_addr.s_addr != htonl((127 << 24) | 1)) { | |
167 | /* | |
168 | * The OS networking stack is expected to drop this kind of | |
169 | * frames since the socket is bound to only localhost address. | |
170 | * Just in case, drop the frame if it is coming from any other | |
171 | * address. | |
172 | */ | |
173 | wpa_printf(MSG_DEBUG, "CTRL: Drop packet from unexpected " | |
174 | "source %s", inet_ntoa(from.sin_addr)); | |
175 | return; | |
176 | } | |
177 | buf[res] = '\0'; | |
178 | ||
179 | if (os_strcmp(buf, "GET_COOKIE") == 0) { | |
180 | reply = wpa_supplicant_ctrl_iface_get_cookie(priv, &reply_len); | |
181 | goto done; | |
182 | } | |
183 | ||
184 | /* | |
185 | * Require that the client includes a prefix with the 'cookie' value | |
186 | * fetched with GET_COOKIE command. This is used to verify that the | |
187 | * client has access to a bidirectional link over UDP in order to | |
188 | * avoid attacks using forged localhost IP address even if the OS does | |
189 | * not block such frames from remote destinations. | |
190 | */ | |
191 | if (os_strncmp(buf, "COOKIE=", 7) != 0) { | |
192 | wpa_printf(MSG_DEBUG, "CTLR: No cookie in the request - " | |
193 | "drop request"); | |
194 | return; | |
195 | } | |
196 | ||
197 | if (hexstr2bin(buf + 7, cookie, COOKIE_LEN) < 0) { | |
198 | wpa_printf(MSG_DEBUG, "CTLR: Invalid cookie format in the " | |
199 | "request - drop request"); | |
200 | return; | |
201 | } | |
202 | ||
203 | if (os_memcmp(cookie, priv->cookie, COOKIE_LEN) != 0) { | |
204 | wpa_printf(MSG_DEBUG, "CTLR: Invalid cookie in the request - " | |
205 | "drop request"); | |
206 | return; | |
207 | } | |
208 | ||
209 | pos = buf + 7 + 2 * COOKIE_LEN; | |
210 | while (*pos == ' ') | |
211 | pos++; | |
212 | ||
213 | if (os_strcmp(pos, "ATTACH") == 0) { | |
214 | if (wpa_supplicant_ctrl_iface_attach(priv, &from, fromlen)) | |
215 | reply_len = 1; | |
216 | else { | |
217 | new_attached = 1; | |
218 | reply_len = 2; | |
219 | } | |
220 | } else if (os_strcmp(pos, "DETACH") == 0) { | |
221 | if (wpa_supplicant_ctrl_iface_detach(priv, &from, fromlen)) | |
222 | reply_len = 1; | |
223 | else | |
224 | reply_len = 2; | |
225 | } else if (os_strncmp(pos, "LEVEL ", 6) == 0) { | |
226 | if (wpa_supplicant_ctrl_iface_level(priv, &from, fromlen, | |
227 | pos + 6)) | |
228 | reply_len = 1; | |
229 | else | |
230 | reply_len = 2; | |
231 | } else { | |
232 | reply = wpa_supplicant_ctrl_iface_process(wpa_s, pos, | |
233 | &reply_len); | |
234 | } | |
235 | ||
236 | done: | |
237 | if (reply) { | |
238 | sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from, | |
239 | fromlen); | |
240 | os_free(reply); | |
241 | } else if (reply_len == 1) { | |
242 | sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) &from, | |
243 | fromlen); | |
244 | } else if (reply_len == 2) { | |
245 | sendto(sock, "OK\n", 3, 0, (struct sockaddr *) &from, | |
246 | fromlen); | |
247 | } | |
248 | ||
249 | if (new_attached) | |
250 | eapol_sm_notify_ctrl_attached(wpa_s->eapol); | |
251 | } | |
252 | ||
253 | ||
254 | static void wpa_supplicant_ctrl_iface_msg_cb(void *ctx, int level, | |
255 | const char *txt, size_t len) | |
256 | { | |
257 | struct wpa_supplicant *wpa_s = ctx; | |
258 | if (wpa_s == NULL || wpa_s->ctrl_iface == NULL) | |
259 | return; | |
260 | wpa_supplicant_ctrl_iface_send(wpa_s->ctrl_iface, level, txt, len); | |
261 | } | |
262 | ||
263 | ||
264 | struct ctrl_iface_priv * | |
265 | wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s) | |
266 | { | |
267 | struct ctrl_iface_priv *priv; | |
268 | struct sockaddr_in addr; | |
269 | ||
270 | priv = os_zalloc(sizeof(*priv)); | |
271 | if (priv == NULL) | |
272 | return NULL; | |
273 | priv->wpa_s = wpa_s; | |
274 | priv->sock = -1; | |
275 | os_get_random(priv->cookie, COOKIE_LEN); | |
276 | ||
277 | if (wpa_s->conf->ctrl_interface == NULL) | |
278 | return priv; | |
279 | ||
280 | priv->sock = socket(PF_INET, SOCK_DGRAM, 0); | |
281 | if (priv->sock < 0) { | |
282 | perror("socket(PF_INET)"); | |
283 | goto fail; | |
284 | } | |
285 | ||
286 | os_memset(&addr, 0, sizeof(addr)); | |
287 | addr.sin_family = AF_INET; | |
288 | addr.sin_addr.s_addr = htonl((127 << 24) | 1); | |
289 | addr.sin_port = htons(WPA_CTRL_IFACE_PORT); | |
290 | if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { | |
291 | perror("bind(AF_INET)"); | |
292 | goto fail; | |
293 | } | |
294 | ||
295 | eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive, | |
296 | wpa_s, priv); | |
297 | wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb); | |
298 | ||
299 | return priv; | |
300 | ||
301 | fail: | |
302 | if (priv->sock >= 0) | |
303 | close(priv->sock); | |
304 | os_free(priv); | |
305 | return NULL; | |
306 | } | |
307 | ||
308 | ||
309 | void wpa_supplicant_ctrl_iface_deinit(struct ctrl_iface_priv *priv) | |
310 | { | |
311 | struct wpa_ctrl_dst *dst, *prev; | |
312 | ||
313 | if (priv->sock > -1) { | |
314 | eloop_unregister_read_sock(priv->sock); | |
315 | if (priv->ctrl_dst) { | |
316 | /* | |
317 | * Wait a second before closing the control socket if | |
318 | * there are any attached monitors in order to allow | |
319 | * them to receive any pending messages. | |
320 | */ | |
321 | wpa_printf(MSG_DEBUG, "CTRL_IFACE wait for attached " | |
322 | "monitors to receive messages"); | |
323 | os_sleep(1, 0); | |
324 | } | |
325 | close(priv->sock); | |
326 | priv->sock = -1; | |
327 | } | |
328 | ||
329 | dst = priv->ctrl_dst; | |
330 | while (dst) { | |
331 | prev = dst; | |
332 | dst = dst->next; | |
333 | os_free(prev); | |
334 | } | |
335 | os_free(priv); | |
336 | } | |
337 | ||
338 | ||
339 | static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, | |
340 | int level, const char *buf, | |
341 | size_t len) | |
342 | { | |
343 | struct wpa_ctrl_dst *dst, *next; | |
344 | char levelstr[10]; | |
345 | int idx; | |
346 | char *sbuf; | |
347 | int llen; | |
348 | ||
349 | dst = priv->ctrl_dst; | |
350 | if (priv->sock < 0 || dst == NULL) | |
351 | return; | |
352 | ||
353 | os_snprintf(levelstr, sizeof(levelstr), "<%d>", level); | |
354 | ||
355 | llen = os_strlen(levelstr); | |
356 | sbuf = os_malloc(llen + len); | |
357 | if (sbuf == NULL) | |
358 | return; | |
359 | ||
360 | os_memcpy(sbuf, levelstr, llen); | |
361 | os_memcpy(sbuf + llen, buf, len); | |
362 | ||
363 | idx = 0; | |
364 | while (dst) { | |
365 | next = dst->next; | |
366 | if (level >= dst->debug_level) { | |
367 | wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor send %s:%d", | |
368 | inet_ntoa(dst->addr.sin_addr), | |
369 | ntohs(dst->addr.sin_port)); | |
370 | if (sendto(priv->sock, sbuf, llen + len, 0, | |
371 | (struct sockaddr *) &dst->addr, | |
372 | sizeof(dst->addr)) < 0) { | |
373 | perror("sendto(CTRL_IFACE monitor)"); | |
374 | dst->errors++; | |
375 | if (dst->errors > 10) { | |
376 | wpa_supplicant_ctrl_iface_detach( | |
377 | priv, &dst->addr, | |
378 | dst->addrlen); | |
379 | } | |
380 | } else | |
381 | dst->errors = 0; | |
382 | } | |
383 | idx++; | |
384 | dst = next; | |
385 | } | |
386 | os_free(sbuf); | |
387 | } | |
388 | ||
389 | ||
390 | void wpa_supplicant_ctrl_iface_wait(struct ctrl_iface_priv *priv) | |
391 | { | |
392 | wpa_printf(MSG_DEBUG, "CTRL_IFACE - %s - wait for monitor", | |
393 | priv->wpa_s->ifname); | |
394 | eloop_wait_for_read_sock(priv->sock); | |
395 | } | |
396 | ||
397 | ||
398 | /* Global ctrl_iface */ | |
399 | ||
400 | struct ctrl_iface_global_priv { | |
401 | int sock; | |
402 | u8 cookie[COOKIE_LEN]; | |
403 | }; | |
404 | ||
405 | ||
406 | static char * | |
407 | wpa_supplicant_global_get_cookie(struct ctrl_iface_global_priv *priv, | |
408 | size_t *reply_len) | |
409 | { | |
410 | char *reply; | |
411 | reply = os_malloc(7 + 2 * COOKIE_LEN + 1); | |
412 | if (reply == NULL) { | |
413 | *reply_len = 1; | |
414 | return NULL; | |
415 | } | |
416 | ||
417 | os_memcpy(reply, "COOKIE=", 7); | |
418 | wpa_snprintf_hex(reply + 7, 2 * COOKIE_LEN + 1, | |
419 | priv->cookie, COOKIE_LEN); | |
420 | ||
421 | *reply_len = 7 + 2 * COOKIE_LEN; | |
422 | return reply; | |
423 | } | |
424 | ||
425 | ||
426 | static void wpa_supplicant_global_ctrl_iface_receive(int sock, void *eloop_ctx, | |
427 | void *sock_ctx) | |
428 | { | |
429 | struct wpa_global *global = eloop_ctx; | |
430 | struct ctrl_iface_global_priv *priv = sock_ctx; | |
431 | char buf[256], *pos; | |
432 | int res; | |
433 | struct sockaddr_in from; | |
434 | socklen_t fromlen = sizeof(from); | |
435 | char *reply; | |
436 | size_t reply_len; | |
437 | u8 cookie[COOKIE_LEN]; | |
438 | ||
439 | res = recvfrom(sock, buf, sizeof(buf) - 1, 0, | |
440 | (struct sockaddr *) &from, &fromlen); | |
441 | if (res < 0) { | |
442 | perror("recvfrom(ctrl_iface)"); | |
443 | return; | |
444 | } | |
445 | if (from.sin_addr.s_addr != htonl((127 << 24) | 1)) { | |
446 | /* | |
447 | * The OS networking stack is expected to drop this kind of | |
448 | * frames since the socket is bound to only localhost address. | |
449 | * Just in case, drop the frame if it is coming from any other | |
450 | * address. | |
451 | */ | |
452 | wpa_printf(MSG_DEBUG, "CTRL: Drop packet from unexpected " | |
453 | "source %s", inet_ntoa(from.sin_addr)); | |
454 | return; | |
455 | } | |
456 | buf[res] = '\0'; | |
457 | ||
458 | if (os_strcmp(buf, "GET_COOKIE") == 0) { | |
459 | reply = wpa_supplicant_global_get_cookie(priv, &reply_len); | |
460 | goto done; | |
461 | } | |
462 | ||
463 | if (os_strncmp(buf, "COOKIE=", 7) != 0) { | |
464 | wpa_printf(MSG_DEBUG, "CTLR: No cookie in the request - " | |
465 | "drop request"); | |
466 | return; | |
467 | } | |
468 | ||
469 | if (hexstr2bin(buf + 7, cookie, COOKIE_LEN) < 0) { | |
470 | wpa_printf(MSG_DEBUG, "CTLR: Invalid cookie format in the " | |
471 | "request - drop request"); | |
472 | return; | |
473 | } | |
474 | ||
475 | if (os_memcmp(cookie, priv->cookie, COOKIE_LEN) != 0) { | |
476 | wpa_printf(MSG_DEBUG, "CTLR: Invalid cookie in the request - " | |
477 | "drop request"); | |
478 | return; | |
479 | } | |
480 | ||
481 | pos = buf + 7 + 2 * COOKIE_LEN; | |
482 | while (*pos == ' ') | |
483 | pos++; | |
484 | ||
485 | reply = wpa_supplicant_global_ctrl_iface_process(global, pos, | |
486 | &reply_len); | |
487 | ||
488 | done: | |
489 | if (reply) { | |
490 | sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from, | |
491 | fromlen); | |
492 | os_free(reply); | |
493 | } else if (reply_len) { | |
494 | sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) &from, | |
495 | fromlen); | |
496 | } | |
497 | } | |
498 | ||
499 | ||
500 | struct ctrl_iface_global_priv * | |
501 | wpa_supplicant_global_ctrl_iface_init(struct wpa_global *global) | |
502 | { | |
503 | struct ctrl_iface_global_priv *priv; | |
504 | struct sockaddr_in addr; | |
505 | ||
506 | priv = os_zalloc(sizeof(*priv)); | |
507 | if (priv == NULL) | |
508 | return NULL; | |
509 | priv->sock = -1; | |
510 | os_get_random(priv->cookie, COOKIE_LEN); | |
511 | ||
512 | if (global->params.ctrl_interface == NULL) | |
513 | return priv; | |
514 | ||
515 | wpa_printf(MSG_DEBUG, "Global control interface '%s'", | |
516 | global->params.ctrl_interface); | |
517 | ||
518 | priv->sock = socket(PF_INET, SOCK_DGRAM, 0); | |
519 | if (priv->sock < 0) { | |
520 | perror("socket(PF_INET)"); | |
521 | goto fail; | |
522 | } | |
523 | ||
524 | os_memset(&addr, 0, sizeof(addr)); | |
525 | addr.sin_family = AF_INET; | |
526 | addr.sin_addr.s_addr = htonl((127 << 24) | 1); | |
527 | addr.sin_port = htons(WPA_GLOBAL_CTRL_IFACE_PORT); | |
528 | if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { | |
529 | perror("bind(AF_INET)"); | |
530 | goto fail; | |
531 | } | |
532 | ||
533 | eloop_register_read_sock(priv->sock, | |
534 | wpa_supplicant_global_ctrl_iface_receive, | |
535 | global, priv); | |
536 | ||
537 | return priv; | |
538 | ||
539 | fail: | |
540 | if (priv->sock >= 0) | |
541 | close(priv->sock); | |
542 | os_free(priv); | |
543 | return NULL; | |
544 | } | |
545 | ||
546 | ||
547 | void | |
548 | wpa_supplicant_global_ctrl_iface_deinit(struct ctrl_iface_global_priv *priv) | |
549 | { | |
550 | if (priv->sock >= 0) { | |
551 | eloop_unregister_read_sock(priv->sock); | |
552 | close(priv->sock); | |
553 | } | |
554 | os_free(priv); | |
555 | } |