]>
Commit | Line | Data |
---|---|---|
f620268f JM |
1 | /* |
2 | * UPnP SSDP for WPS | |
3 | * Copyright (c) 2000-2003 Intel Corporation | |
4 | * Copyright (c) 2006-2007 Sony Corporation | |
5 | * Copyright (c) 2008-2009 Atheros Communications | |
6 | * Copyright (c) 2009, Jouni Malinen <j@w1.fi> | |
7 | * | |
8 | * See wps_upnp.c for more details on licensing and code history. | |
9 | */ | |
10 | ||
11 | #include "includes.h" | |
12 | ||
13 | #include <fcntl.h> | |
14 | #include <sys/ioctl.h> | |
15 | #include <net/route.h> | |
16 | ||
17 | #include "common.h" | |
18 | #include "uuid.h" | |
19 | #include "eloop.h" | |
20 | #include "wps.h" | |
21 | #include "wps_upnp.h" | |
22 | #include "wps_upnp_i.h" | |
23 | ||
24 | #define UPNP_CACHE_SEC (UPNP_CACHE_SEC_MIN + 1) /* cache time we use */ | |
25 | #define UPNP_CACHE_SEC_MIN 1800 /* min cachable time per UPnP standard */ | |
26 | #define UPNP_ADVERTISE_REPEAT 2 /* no more than 3 */ | |
f620268f JM |
27 | #define MAX_MSEARCH 20 /* max simultaneous M-SEARCH replies ongoing */ |
28 | #define SSDP_TARGET "239.0.0.0" | |
29 | #define SSDP_NETMASK "255.0.0.0" | |
30 | ||
31 | ||
32 | /* Check tokens for equality, where tokens consist of letters, digits, | |
33 | * underscore and hyphen, and are matched case insensitive. | |
34 | */ | |
35 | static int token_eq(const char *s1, const char *s2) | |
36 | { | |
37 | int c1; | |
38 | int c2; | |
39 | int end1 = 0; | |
40 | int end2 = 0; | |
41 | for (;;) { | |
42 | c1 = *s1++; | |
43 | c2 = *s2++; | |
44 | if (isalpha(c1) && isupper(c1)) | |
45 | c1 = tolower(c1); | |
46 | if (isalpha(c2) && isupper(c2)) | |
47 | c2 = tolower(c2); | |
48 | end1 = !(isalnum(c1) || c1 == '_' || c1 == '-'); | |
49 | end2 = !(isalnum(c2) || c2 == '_' || c2 == '-'); | |
50 | if (end1 || end2 || c1 != c2) | |
51 | break; | |
52 | } | |
e80e5163 | 53 | return end1 && end2; /* reached end of both words? */ |
f620268f JM |
54 | } |
55 | ||
56 | ||
57 | /* Return length of token (see above for definition of token) */ | |
58 | static int token_length(const char *s) | |
59 | { | |
60 | const char *begin = s; | |
61 | for (;; s++) { | |
62 | int c = *s; | |
63 | int end = !(isalnum(c) || c == '_' || c == '-'); | |
64 | if (end) | |
65 | break; | |
66 | } | |
67 | return s - begin; | |
68 | } | |
69 | ||
70 | ||
71 | /* return length of interword separation. | |
72 | * This accepts only spaces/tabs and thus will not traverse a line | |
73 | * or buffer ending. | |
74 | */ | |
75 | static int word_separation_length(const char *s) | |
76 | { | |
77 | const char *begin = s; | |
78 | for (;; s++) { | |
79 | int c = *s; | |
80 | if (c == ' ' || c == '\t') | |
81 | continue; | |
82 | break; | |
83 | } | |
84 | return s - begin; | |
85 | } | |
86 | ||
87 | ||
88 | /* No. of chars through (including) end of line */ | |
89 | static int line_length(const char *l) | |
90 | { | |
91 | const char *lp = l; | |
92 | while (*lp && *lp != '\n') | |
93 | lp++; | |
94 | if (*lp == '\n') | |
95 | lp++; | |
96 | return lp - l; | |
97 | } | |
98 | ||
99 | ||
100 | /* No. of chars excluding trailing whitespace */ | |
101 | static int line_length_stripped(const char *l) | |
102 | { | |
103 | const char *lp = l + line_length(l); | |
104 | while (lp > l && !isgraph(lp[-1])) | |
105 | lp--; | |
106 | return lp - l; | |
107 | } | |
108 | ||
109 | ||
110 | static int str_starts(const char *str, const char *start) | |
111 | { | |
112 | return os_strncmp(str, start, os_strlen(start)) == 0; | |
113 | } | |
114 | ||
115 | ||
116 | /*************************************************************************** | |
117 | * Advertisements. | |
118 | * These are multicast to the world to tell them we are here. | |
119 | * The individual packets are spread out in time to limit loss, | |
120 | * and then after a much longer period of time the whole sequence | |
121 | * is repeated again (for NOTIFYs only). | |
122 | **************************************************************************/ | |
123 | ||
124 | /** | |
125 | * next_advertisement - Build next message and advance the state machine | |
126 | * @a: Advertisement state | |
127 | * @islast: Buffer for indicating whether this is the last message (= 1) | |
128 | * Returns: The new message (caller is responsible for freeing this) | |
129 | * | |
130 | * Note: next_advertisement is shared code with msearchreply_* functions | |
131 | */ | |
132 | static struct wpabuf * | |
133 | next_advertisement(struct advertisement_state_machine *a, int *islast) | |
134 | { | |
135 | struct wpabuf *msg; | |
136 | char *NTString = ""; | |
137 | char uuid_string[80]; | |
138 | ||
139 | *islast = 0; | |
140 | uuid_bin2str(a->sm->wps->uuid, uuid_string, sizeof(uuid_string)); | |
141 | msg = wpabuf_alloc(800); /* more than big enough */ | |
142 | if (msg == NULL) | |
143 | goto fail; | |
144 | switch (a->type) { | |
145 | case ADVERTISE_UP: | |
146 | case ADVERTISE_DOWN: | |
147 | NTString = "NT"; | |
148 | wpabuf_put_str(msg, "NOTIFY * HTTP/1.1\r\n"); | |
149 | wpabuf_printf(msg, "HOST: %s:%d\r\n", | |
150 | UPNP_MULTICAST_ADDRESS, UPNP_MULTICAST_PORT); | |
151 | wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n", | |
152 | UPNP_CACHE_SEC); | |
153 | wpabuf_printf(msg, "NTS: %s\r\n", | |
154 | (a->type == ADVERTISE_UP ? | |
155 | "ssdp:alive" : "ssdp:byebye")); | |
156 | break; | |
157 | case MSEARCH_REPLY: | |
158 | NTString = "ST"; | |
159 | wpabuf_put_str(msg, "HTTP/1.1 200 OK\r\n"); | |
160 | wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n", | |
161 | UPNP_CACHE_SEC); | |
162 | ||
163 | wpabuf_put_str(msg, "DATE: "); | |
164 | format_date(msg); | |
165 | wpabuf_put_str(msg, "\r\n"); | |
166 | ||
167 | wpabuf_put_str(msg, "EXT:\r\n"); | |
168 | break; | |
169 | } | |
170 | ||
171 | if (a->type != ADVERTISE_DOWN) { | |
172 | /* Where others may get our XML files from */ | |
173 | wpabuf_printf(msg, "LOCATION: http://%s:%d/%s\r\n", | |
174 | a->sm->ip_addr_text, a->sm->web_port, | |
175 | UPNP_WPS_DEVICE_XML_FILE); | |
176 | } | |
177 | ||
178 | /* The SERVER line has three comma-separated fields: | |
179 | * operating system / version | |
180 | * upnp version | |
181 | * software package / version | |
182 | * However, only the UPnP version is really required, the | |
183 | * others can be place holders... for security reasons | |
184 | * it is better to NOT provide extra information. | |
185 | */ | |
186 | wpabuf_put_str(msg, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"); | |
187 | ||
188 | switch (a->state / UPNP_ADVERTISE_REPEAT) { | |
189 | case 0: | |
190 | wpabuf_printf(msg, "%s: upnp:rootdevice\r\n", NTString); | |
191 | wpabuf_printf(msg, "USN: uuid:%s::upnp:rootdevice\r\n", | |
192 | uuid_string); | |
193 | break; | |
194 | case 1: | |
195 | wpabuf_printf(msg, "%s: uuid:%s\r\n", NTString, uuid_string); | |
196 | wpabuf_printf(msg, "USN: uuid:%s\r\n", uuid_string); | |
197 | break; | |
198 | case 2: | |
199 | wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:device:" | |
200 | "WFADevice:1\r\n", NTString); | |
201 | wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-" | |
202 | "org:device:WFADevice:1\r\n", uuid_string); | |
203 | break; | |
204 | case 3: | |
205 | wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:service:" | |
206 | "WFAWLANConfig:1\r\n", NTString); | |
207 | wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-" | |
208 | "org:service:WFAWLANConfig:1\r\n", uuid_string); | |
209 | break; | |
210 | } | |
211 | wpabuf_put_str(msg, "\r\n"); | |
212 | ||
213 | if (a->state + 1 >= 4 * UPNP_ADVERTISE_REPEAT) | |
214 | *islast = 1; | |
215 | ||
216 | return msg; | |
217 | ||
218 | fail: | |
219 | wpabuf_free(msg); | |
220 | return NULL; | |
221 | } | |
222 | ||
223 | ||
224 | static void advertisement_state_machine_handler(void *eloop_data, | |
225 | void *user_ctx); | |
226 | ||
227 | ||
228 | /** | |
229 | * advertisement_state_machine_stop - Stop SSDP advertisements | |
230 | * @sm: WPS UPnP state machine from upnp_wps_device_init() | |
231 | */ | |
232 | void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm) | |
233 | { | |
234 | eloop_cancel_timeout(advertisement_state_machine_handler, NULL, sm); | |
235 | } | |
236 | ||
237 | ||
238 | static void advertisement_state_machine_handler(void *eloop_data, | |
239 | void *user_ctx) | |
240 | { | |
241 | struct upnp_wps_device_sm *sm = user_ctx; | |
242 | struct advertisement_state_machine *a = &sm->advertisement; | |
243 | struct wpabuf *msg; | |
244 | int next_timeout_msec = 100; | |
245 | int next_timeout_sec = 0; | |
246 | struct sockaddr_in dest; | |
247 | int islast = 0; | |
248 | ||
249 | /* | |
250 | * Each is sent twice (in case lost) w/ 100 msec delay between; | |
251 | * spec says no more than 3 times. | |
252 | * One pair for rootdevice, one pair for uuid, and a pair each for | |
253 | * each of the two urns. | |
254 | * The entire sequence must be repeated before cache control timeout | |
255 | * (which is min 1800 seconds), | |
256 | * recommend random portion of half of the advertised cache control age | |
257 | * to ensure against loss... perhaps 1800/4 + rand*1800/4 ? | |
258 | * Delay random interval < 100 msec prior to initial sending. | |
259 | * TTL of 4 | |
260 | */ | |
261 | ||
262 | wpa_printf(MSG_MSGDUMP, "WPS UPnP: Advertisement state=%d", a->state); | |
263 | msg = next_advertisement(a, &islast); | |
264 | if (msg == NULL) | |
265 | return; | |
266 | ||
267 | os_memset(&dest, 0, sizeof(dest)); | |
268 | dest.sin_family = AF_INET; | |
269 | dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS); | |
270 | dest.sin_port = htons(UPNP_MULTICAST_PORT); | |
271 | ||
272 | if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0, | |
273 | (struct sockaddr *) &dest, sizeof(dest)) == -1) { | |
274 | wpa_printf(MSG_ERROR, "WPS UPnP: Advertisement sendto failed:" | |
275 | "%d (%s)", errno, strerror(errno)); | |
276 | next_timeout_msec = 0; | |
277 | next_timeout_sec = 10; /* ... later */ | |
278 | } else if (islast) { | |
279 | a->state = 0; /* wrap around */ | |
280 | if (a->type == ADVERTISE_DOWN) { | |
281 | wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_DOWN->UP"); | |
282 | a->type = ADVERTISE_UP; | |
283 | /* do it all over again right away */ | |
284 | } else { | |
285 | u16 r; | |
286 | /* | |
287 | * Start over again after a long timeout | |
288 | * (see notes above) | |
289 | */ | |
290 | next_timeout_msec = 0; | |
291 | os_get_random((void *) &r, sizeof(r)); | |
292 | next_timeout_sec = UPNP_CACHE_SEC / 4 + | |
293 | (((UPNP_CACHE_SEC / 4) * r) >> 16); | |
294 | sm->advertise_count++; | |
295 | wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_UP (#%u); " | |
296 | "next in %d sec", | |
297 | sm->advertise_count, next_timeout_sec); | |
298 | } | |
299 | } else { | |
300 | a->state++; | |
301 | } | |
302 | ||
303 | wpabuf_free(msg); | |
304 | ||
305 | eloop_register_timeout(next_timeout_sec, next_timeout_msec, | |
306 | advertisement_state_machine_handler, NULL, sm); | |
307 | } | |
308 | ||
309 | ||
310 | /** | |
311 | * advertisement_state_machine_start - Start SSDP advertisements | |
312 | * @sm: WPS UPnP state machine from upnp_wps_device_init() | |
313 | * Returns: 0 on success, -1 on failure | |
314 | */ | |
315 | int advertisement_state_machine_start(struct upnp_wps_device_sm *sm) | |
316 | { | |
317 | struct advertisement_state_machine *a = &sm->advertisement; | |
318 | int next_timeout_msec; | |
319 | ||
320 | advertisement_state_machine_stop(sm); | |
321 | ||
322 | /* | |
323 | * Start out advertising down, this automatically switches | |
324 | * to advertising up which signals our restart. | |
325 | */ | |
326 | a->type = ADVERTISE_DOWN; | |
327 | a->state = 0; | |
328 | a->sm = sm; | |
329 | /* (other fields not used here) */ | |
330 | ||
331 | /* First timeout should be random interval < 100 msec */ | |
332 | next_timeout_msec = (100 * (os_random() & 0xFF)) >> 8; | |
333 | return eloop_register_timeout(0, next_timeout_msec, | |
334 | advertisement_state_machine_handler, | |
335 | NULL, sm); | |
336 | } | |
337 | ||
338 | ||
339 | /*************************************************************************** | |
340 | * M-SEARCH replies | |
341 | * These are very similar to the multicast advertisements, with some | |
342 | * small changes in data content; and they are sent (UDP) to a specific | |
343 | * unicast address instead of multicast. | |
344 | * They are sent in response to a UDP M-SEARCH packet. | |
345 | **************************************************************************/ | |
346 | ||
347 | static void msearchreply_state_machine_handler(void *eloop_data, | |
348 | void *user_ctx); | |
349 | ||
350 | ||
351 | /** | |
352 | * msearchreply_state_machine_stop - Stop M-SEARCH reply state machine | |
353 | * @a: Selected advertisement/reply state | |
354 | */ | |
355 | void msearchreply_state_machine_stop(struct advertisement_state_machine *a) | |
356 | { | |
357 | struct upnp_wps_device_sm *sm = a->sm; | |
358 | wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH stop"); | |
359 | if (a->next == a) { | |
360 | sm->msearch_replies = NULL; | |
e80e5163 JM |
361 | } else { |
362 | if (sm->msearch_replies == a) | |
f620268f | 363 | sm->msearch_replies = a->next; |
f620268f JM |
364 | a->next->prev = a->prev; |
365 | a->prev->next = a->next; | |
366 | } | |
367 | os_free(a); | |
368 | sm->n_msearch_replies--; | |
369 | } | |
370 | ||
371 | ||
372 | static void msearchreply_state_machine_handler(void *eloop_data, | |
373 | void *user_ctx) | |
374 | { | |
375 | struct advertisement_state_machine *a = user_ctx; | |
376 | struct upnp_wps_device_sm *sm = a->sm; | |
377 | struct wpabuf *msg; | |
378 | int next_timeout_msec = 100; | |
379 | int next_timeout_sec = 0; | |
380 | int islast = 0; | |
381 | ||
382 | /* | |
383 | * Each response is sent twice (in case lost) w/ 100 msec delay | |
384 | * between; spec says no more than 3 times. | |
385 | * One pair for rootdevice, one pair for uuid, and a pair each for | |
386 | * each of the two urns. | |
387 | */ | |
388 | ||
389 | /* TODO: should only send the requested response types */ | |
390 | ||
391 | wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply state=%d (%s:%d)", | |
392 | a->state, inet_ntoa(a->client.sin_addr), | |
393 | ntohs(a->client.sin_port)); | |
394 | msg = next_advertisement(a, &islast); | |
395 | if (msg == NULL) | |
396 | return; | |
397 | ||
398 | /* | |
399 | * Send it on the multicast socket to avoid having to set up another | |
400 | * socket. | |
401 | */ | |
402 | if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0, | |
403 | (struct sockaddr *) &a->client, sizeof(a->client)) < 0) { | |
404 | wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply sendto " | |
405 | "errno %d (%s) for %s:%d", | |
406 | errno, strerror(errno), | |
407 | inet_ntoa(a->client.sin_addr), | |
408 | ntohs(a->client.sin_port)); | |
409 | /* Ignore error and hope for the best */ | |
410 | } | |
411 | wpabuf_free(msg); | |
412 | if (islast) { | |
413 | wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply done"); | |
414 | msearchreply_state_machine_stop(a); | |
415 | return; | |
416 | } | |
417 | a->state++; | |
418 | ||
419 | wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply in %d.%03d sec", | |
420 | next_timeout_sec, next_timeout_msec); | |
421 | eloop_register_timeout(next_timeout_sec, next_timeout_msec, | |
422 | msearchreply_state_machine_handler, sm, a); | |
423 | } | |
424 | ||
425 | ||
426 | /** | |
427 | * msearchreply_state_machine_start - Reply to M-SEARCH discovery request | |
428 | * @sm: WPS UPnP state machine from upnp_wps_device_init() | |
429 | * @client: Client address | |
430 | * @mx: Maximum delay in seconds | |
431 | * | |
432 | * Use TTL of 4 (this was done when socket set up). | |
433 | * A response should be given in randomized portion of min(MX,120) seconds | |
434 | * | |
435 | * UPnP-arch-DeviceArchitecture, 1.2.3: | |
436 | * To be found, a device must send a UDP response to the source IP address and | |
437 | * port that sent the request to the multicast channel. Devices respond if the | |
438 | * ST header of the M-SEARCH request is "ssdp:all", "upnp:rootdevice", "uuid:" | |
439 | * followed by a UUID that exactly matches one advertised by the device. | |
440 | */ | |
441 | static void msearchreply_state_machine_start(struct upnp_wps_device_sm *sm, | |
442 | struct sockaddr_in *client, | |
443 | int mx) | |
444 | { | |
445 | struct advertisement_state_machine *a; | |
446 | int next_timeout_sec; | |
447 | int next_timeout_msec; | |
448 | ||
449 | wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply start (%d " | |
450 | "outstanding)", sm->n_msearch_replies); | |
451 | if (sm->n_msearch_replies >= MAX_MSEARCH) { | |
452 | wpa_printf(MSG_INFO, "WPS UPnP: Too many outstanding " | |
453 | "M-SEARCH replies"); | |
454 | return; | |
455 | } | |
456 | ||
457 | a = os_zalloc(sizeof(*a)); | |
458 | if (a == NULL) | |
459 | return; | |
460 | a->type = MSEARCH_REPLY; | |
461 | a->state = 0; | |
462 | a->sm = sm; | |
bfd67fc4 | 463 | os_memcpy(&a->client, client, sizeof(*client)); |
f620268f JM |
464 | /* Wait time depending on MX value */ |
465 | next_timeout_msec = (1000 * mx * (os_random() & 0xFF)) >> 8; | |
466 | next_timeout_sec = next_timeout_msec / 1000; | |
467 | next_timeout_msec = next_timeout_msec % 1000; | |
468 | if (eloop_register_timeout(next_timeout_sec, next_timeout_msec, | |
469 | msearchreply_state_machine_handler, sm, | |
470 | a)) { | |
471 | /* No way to recover (from malloc failure) */ | |
472 | goto fail; | |
473 | } | |
474 | /* Remember for future cleanup */ | |
475 | if (sm->msearch_replies) { | |
476 | a->next = sm->msearch_replies; | |
477 | a->prev = a->next->prev; | |
478 | a->prev->next = a; | |
479 | a->next->prev = a; | |
480 | } else { | |
481 | sm->msearch_replies = a->next = a->prev = a; | |
482 | } | |
483 | sm->n_msearch_replies++; | |
484 | return; | |
485 | ||
486 | fail: | |
487 | wpa_printf(MSG_INFO, "WPS UPnP: M-SEARCH reply failure!"); | |
488 | eloop_cancel_timeout(msearchreply_state_machine_handler, sm, a); | |
489 | os_free(a); | |
490 | } | |
491 | ||
492 | ||
493 | /** | |
494 | * ssdp_parse_msearch - Process a received M-SEARCH | |
495 | * @sm: WPS UPnP state machine from upnp_wps_device_init() | |
496 | * @client: Client address | |
497 | * @data: NULL terminated M-SEARCH message | |
498 | * | |
499 | * Given that we have received a header w/ M-SEARCH, act upon it | |
500 | * | |
501 | * Format of M-SEARCH (case insensitive!): | |
502 | * | |
503 | * First line must be: | |
504 | * M-SEARCH * HTTP/1.1 | |
505 | * Other lines in arbitrary order: | |
506 | * HOST:239.255.255.250:1900 | |
507 | * ST:<varies -- must match> | |
508 | * MAN:"ssdp:discover" | |
509 | * MX:<varies> | |
510 | * | |
511 | * It should be noted that when Microsoft Vista is still learning its IP | |
512 | * address, it sends out host lines like: HOST:[FF02::C]:1900 | |
513 | */ | |
514 | static void ssdp_parse_msearch(struct upnp_wps_device_sm *sm, | |
515 | struct sockaddr_in *client, const char *data) | |
516 | { | |
517 | const char *start = data; | |
518 | const char *end; | |
519 | int got_host = 0; | |
520 | int got_st = 0, st_match = 0; | |
521 | int got_man = 0; | |
522 | int got_mx = 0; | |
523 | int mx = 0; | |
524 | ||
525 | /* | |
526 | * Skip first line M-SEARCH * HTTP/1.1 | |
527 | * (perhaps we should check remainder of the line for syntax) | |
528 | */ | |
529 | data += line_length(data); | |
530 | ||
531 | /* Parse remaining lines */ | |
532 | for (; *data != '\0'; data += line_length(data)) { | |
533 | end = data + line_length_stripped(data); | |
534 | if (token_eq(data, "host")) { | |
535 | /* The host line indicates who the packet | |
536 | * is addressed to... but do we really care? | |
537 | * Note that Microsoft sometimes does funny | |
538 | * stuff with the HOST: line. | |
539 | */ | |
540 | #if 0 /* could be */ | |
541 | data += token_length(data); | |
542 | data += word_separation_length(data); | |
543 | if (*data != ':') | |
544 | goto bad; | |
545 | data++; | |
546 | data += word_separation_length(data); | |
547 | /* UPNP_MULTICAST_ADDRESS */ | |
548 | if (!str_starts(data, "239.255.255.250")) | |
549 | goto bad; | |
550 | data += os_strlen("239.255.255.250"); | |
551 | if (*data == ':') { | |
552 | if (!str_starts(data, ":1900")) | |
553 | goto bad; | |
554 | } | |
555 | #endif /* could be */ | |
556 | got_host = 1; | |
557 | continue; | |
558 | } else if (token_eq(data, "st")) { | |
559 | /* There are a number of forms; we look | |
560 | * for one that matches our case. | |
561 | */ | |
562 | got_st = 1; | |
563 | data += token_length(data); | |
564 | data += word_separation_length(data); | |
565 | if (*data != ':') | |
566 | continue; | |
567 | data++; | |
568 | data += word_separation_length(data); | |
569 | if (str_starts(data, "ssdp:all")) { | |
570 | st_match = 1; | |
571 | continue; | |
572 | } | |
573 | if (str_starts(data, "upnp:rootdevice")) { | |
574 | st_match = 1; | |
575 | continue; | |
576 | } | |
577 | if (str_starts(data, "uuid:")) { | |
578 | char uuid_string[80]; | |
579 | data += os_strlen("uuid:"); | |
580 | uuid_bin2str(sm->wps->uuid, uuid_string, | |
581 | sizeof(uuid_string)); | |
582 | if (str_starts(data, uuid_string)) | |
583 | st_match = 1; | |
584 | continue; | |
585 | } | |
586 | #if 0 | |
587 | /* FIX: should we really reply to IGD string? */ | |
588 | if (str_starts(data, "urn:schemas-upnp-org:device:" | |
589 | "InternetGatewayDevice:1")) { | |
590 | st_match = 1; | |
591 | continue; | |
592 | } | |
593 | #endif | |
594 | if (str_starts(data, "urn:schemas-wifialliance-org:" | |
595 | "service:WFAWLANConfig:1")) { | |
596 | st_match = 1; | |
597 | continue; | |
598 | } | |
599 | if (str_starts(data, "urn:schemas-wifialliance-org:" | |
600 | "device:WFADevice:1")) { | |
601 | st_match = 1; | |
602 | continue; | |
603 | } | |
604 | continue; | |
605 | } else if (token_eq(data, "man")) { | |
606 | data += token_length(data); | |
607 | data += word_separation_length(data); | |
608 | if (*data != ':') | |
609 | continue; | |
610 | data++; | |
611 | data += word_separation_length(data); | |
612 | if (!str_starts(data, "\"ssdp:discover\"")) { | |
613 | wpa_printf(MSG_DEBUG, "WPS UPnP: Unexpected " | |
614 | "M-SEARCH man-field"); | |
615 | goto bad; | |
616 | } | |
617 | got_man = 1; | |
618 | continue; | |
619 | } else if (token_eq(data, "mx")) { | |
620 | data += token_length(data); | |
621 | data += word_separation_length(data); | |
622 | if (*data != ':') | |
623 | continue; | |
624 | data++; | |
625 | data += word_separation_length(data); | |
626 | mx = atol(data); | |
627 | got_mx = 1; | |
628 | continue; | |
629 | } | |
630 | /* ignore anything else */ | |
631 | } | |
632 | if (!got_host || !got_st || !got_man || !got_mx || mx < 0) { | |
633 | wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid M-SEARCH: %d %d %d " | |
634 | "%d mx=%d", got_host, got_st, got_man, got_mx, mx); | |
635 | goto bad; | |
636 | } | |
637 | if (!st_match) { | |
638 | wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored M-SEARCH (no ST " | |
639 | "match)"); | |
640 | return; | |
641 | } | |
642 | if (mx > 120) | |
643 | mx = 120; /* UPnP-arch-DeviceArchitecture, 1.2.3 */ | |
644 | msearchreply_state_machine_start(sm, client, mx); | |
645 | return; | |
646 | ||
647 | bad: | |
648 | wpa_printf(MSG_INFO, "WPS UPnP: Failed to parse M-SEARCH"); | |
649 | wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH data:\n%s", start); | |
650 | } | |
651 | ||
652 | ||
653 | /* Listening for (UDP) discovery (M-SEARCH) packets */ | |
654 | ||
655 | /** | |
656 | * ssdp_listener_stop - Stop SSDP listered | |
657 | * @sm: WPS UPnP state machine from upnp_wps_device_init() | |
658 | * | |
e9bcfebf | 659 | * This function stops the SSDP listener that was started by calling |
f620268f JM |
660 | * ssdp_listener_start(). |
661 | */ | |
662 | void ssdp_listener_stop(struct upnp_wps_device_sm *sm) | |
663 | { | |
664 | if (sm->ssdp_sd_registered) { | |
665 | eloop_unregister_sock(sm->ssdp_sd, EVENT_TYPE_READ); | |
666 | sm->ssdp_sd_registered = 0; | |
667 | } | |
668 | ||
669 | if (sm->ssdp_sd != -1) { | |
670 | close(sm->ssdp_sd); | |
671 | sm->ssdp_sd = -1; | |
672 | } | |
673 | ||
674 | eloop_cancel_timeout(msearchreply_state_machine_handler, sm, | |
675 | ELOOP_ALL_CTX); | |
676 | } | |
677 | ||
678 | ||
679 | static void ssdp_listener_handler(int sd, void *eloop_ctx, void *sock_ctx) | |
680 | { | |
681 | struct upnp_wps_device_sm *sm = sock_ctx; | |
682 | struct sockaddr_in addr; /* client address */ | |
683 | socklen_t addr_len; | |
684 | int nread; | |
685 | char buf[MULTICAST_MAX_READ], *pos; | |
686 | ||
687 | addr_len = sizeof(addr); | |
688 | nread = recvfrom(sm->ssdp_sd, buf, sizeof(buf) - 1, 0, | |
689 | (struct sockaddr *) &addr, &addr_len); | |
690 | if (nread <= 0) | |
691 | return; | |
692 | buf[nread] = '\0'; /* need null termination for algorithm */ | |
693 | ||
694 | if (str_starts(buf, "NOTIFY ")) { | |
695 | /* | |
696 | * Silently ignore NOTIFYs to avoid filling debug log with | |
697 | * unwanted messages. | |
698 | */ | |
699 | return; | |
700 | } | |
701 | ||
702 | pos = os_strchr(buf, '\n'); | |
703 | if (pos) | |
704 | *pos = '\0'; | |
705 | wpa_printf(MSG_MSGDUMP, "WPS UPnP: Received SSDP packet from %s:%d: " | |
706 | "%s", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), buf); | |
707 | if (pos) | |
708 | *pos = '\n'; | |
709 | ||
710 | /* Parse first line */ | |
711 | if (os_strncasecmp(buf, "M-SEARCH", os_strlen("M-SEARCH")) == 0 && | |
712 | !isgraph(buf[strlen("M-SEARCH")])) { | |
713 | ssdp_parse_msearch(sm, &addr, buf); | |
714 | return; | |
715 | } | |
716 | ||
717 | /* Ignore anything else */ | |
718 | } | |
719 | ||
720 | ||
e9bcfebf | 721 | int ssdp_listener_open(void) |
f620268f | 722 | { |
f620268f JM |
723 | struct sockaddr_in addr; |
724 | struct ip_mreq mcast_addr; | |
725 | int on = 1; | |
726 | /* per UPnP spec, keep IP packet time to live (TTL) small */ | |
727 | unsigned char ttl = 4; | |
e9bcfebf | 728 | int sd; |
f620268f | 729 | |
e9bcfebf | 730 | sd = socket(AF_INET, SOCK_DGRAM, 0); |
f620268f JM |
731 | if (sd < 0) |
732 | goto fail; | |
733 | if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0) | |
734 | goto fail; | |
735 | if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) | |
736 | goto fail; | |
737 | os_memset(&addr, 0, sizeof(addr)); | |
738 | addr.sin_family = AF_INET; | |
739 | addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
740 | addr.sin_port = htons(UPNP_MULTICAST_PORT); | |
741 | if (bind(sd, (struct sockaddr *) &addr, sizeof(addr))) | |
742 | goto fail; | |
743 | os_memset(&mcast_addr, 0, sizeof(mcast_addr)); | |
744 | mcast_addr.imr_interface.s_addr = htonl(INADDR_ANY); | |
745 | mcast_addr.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS); | |
746 | if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, | |
747 | (char *) &mcast_addr, sizeof(mcast_addr))) | |
748 | goto fail; | |
749 | if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, | |
750 | &ttl, sizeof(ttl))) | |
751 | goto fail; | |
e9bcfebf JM |
752 | |
753 | return sd; | |
754 | ||
755 | fail: | |
756 | if (sd >= 0) | |
757 | close(sd); | |
758 | return -1; | |
759 | } | |
760 | ||
761 | ||
762 | /** | |
763 | * ssdp_listener_start - Set up for receiving discovery (UDP) packets | |
764 | * @sm: WPS UPnP state machine from upnp_wps_device_init() | |
765 | * Returns: 0 on success, -1 on failure | |
766 | * | |
767 | * The SSDP listener is stopped by calling ssdp_listener_stop(). | |
768 | */ | |
769 | int ssdp_listener_start(struct upnp_wps_device_sm *sm) | |
770 | { | |
771 | sm->ssdp_sd = ssdp_listener_open(); | |
772 | ||
773 | if (eloop_register_sock(sm->ssdp_sd, EVENT_TYPE_READ, | |
774 | ssdp_listener_handler, NULL, sm)) | |
f620268f JM |
775 | goto fail; |
776 | sm->ssdp_sd_registered = 1; | |
777 | return 0; | |
778 | ||
779 | fail: | |
780 | /* Error */ | |
781 | wpa_printf(MSG_ERROR, "WPS UPnP: ssdp_listener_start failed"); | |
782 | ssdp_listener_stop(sm); | |
783 | return -1; | |
784 | } | |
785 | ||
786 | ||
787 | /** | |
788 | * add_ssdp_network - Add routing entry for SSDP | |
789 | * @net_if: Selected network interface name | |
790 | * Returns: 0 on success, -1 on failure | |
791 | * | |
792 | * This function assures that the multicast address will be properly | |
793 | * handled by Linux networking code (by a modification to routing tables). | |
794 | * This must be done per network interface. It really only needs to be done | |
795 | * once after booting up, but it does not hurt to call this more frequently | |
796 | * "to be safe". | |
797 | */ | |
e9bcfebf | 798 | int add_ssdp_network(const char *net_if) |
f620268f | 799 | { |
22498d6d | 800 | #ifdef __linux__ |
f620268f JM |
801 | int ret = -1; |
802 | int sock = -1; | |
803 | struct rtentry rt; | |
804 | struct sockaddr_in *sin; | |
805 | ||
806 | if (!net_if) | |
807 | goto fail; | |
808 | ||
809 | os_memset(&rt, 0, sizeof(rt)); | |
810 | sock = socket(AF_INET, SOCK_DGRAM, 0); | |
811 | if (sock < 0) | |
812 | goto fail; | |
813 | ||
e9bcfebf | 814 | rt.rt_dev = (char *) net_if; |
0ae7b086 | 815 | sin = aliasing_hide_typecast(&rt.rt_dst, struct sockaddr_in); |
f620268f JM |
816 | sin->sin_family = AF_INET; |
817 | sin->sin_port = 0; | |
818 | sin->sin_addr.s_addr = inet_addr(SSDP_TARGET); | |
0ae7b086 | 819 | sin = aliasing_hide_typecast(&rt.rt_genmask, struct sockaddr_in); |
f620268f JM |
820 | sin->sin_family = AF_INET; |
821 | sin->sin_port = 0; | |
822 | sin->sin_addr.s_addr = inet_addr(SSDP_NETMASK); | |
823 | rt.rt_flags = RTF_UP; | |
824 | if (ioctl(sock, SIOCADDRT, &rt) < 0) { | |
825 | if (errno == EPERM) { | |
826 | wpa_printf(MSG_DEBUG, "add_ssdp_network: No " | |
827 | "permissions to add routing table entry"); | |
828 | /* Continue to allow testing as non-root */ | |
829 | } else if (errno != EEXIST) { | |
830 | wpa_printf(MSG_INFO, "add_ssdp_network() ioctl errno " | |
831 | "%d (%s)", errno, strerror(errno)); | |
832 | goto fail; | |
833 | } | |
834 | } | |
835 | ||
836 | ret = 0; | |
837 | ||
838 | fail: | |
839 | if (sock >= 0) | |
840 | close(sock); | |
841 | ||
842 | return ret; | |
22498d6d JM |
843 | #else /* __linux__ */ |
844 | return 0; | |
845 | #endif /* __linux__ */ | |
f620268f JM |
846 | } |
847 | ||
848 | ||
e9bcfebf | 849 | int ssdp_open_multicast_sock(u32 ip_addr) |
f620268f | 850 | { |
e9bcfebf | 851 | int sd; |
f620268f JM |
852 | /* per UPnP-arch-DeviceArchitecture, 1. Discovery, keep IP packet |
853 | * time to live (TTL) small */ | |
854 | unsigned char ttl = 4; | |
855 | ||
e9bcfebf | 856 | sd = socket(AF_INET, SOCK_DGRAM, 0); |
f620268f JM |
857 | if (sd < 0) |
858 | return -1; | |
859 | ||
860 | #if 0 /* maybe ok if we sometimes block on writes */ | |
861 | if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0) | |
862 | return -1; | |
863 | #endif | |
864 | ||
865 | if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, | |
e9bcfebf | 866 | &ip_addr, sizeof(ip_addr))) |
f620268f JM |
867 | return -1; |
868 | if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, | |
869 | &ttl, sizeof(ttl))) | |
870 | return -1; | |
871 | ||
872 | #if 0 /* not needed, because we don't receive using multicast_sd */ | |
873 | { | |
874 | struct ip_mreq mreq; | |
875 | mreq.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS); | |
e9bcfebf | 876 | mreq.imr_interface.s_addr = ip_addr; |
f620268f JM |
877 | wpa_printf(MSG_DEBUG, "WPS UPnP: Multicast addr 0x%x if addr " |
878 | "0x%x", | |
879 | mreq.imr_multiaddr.s_addr, | |
880 | mreq.imr_interface.s_addr); | |
881 | if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, | |
882 | sizeof(mreq))) { | |
883 | wpa_printf(MSG_ERROR, | |
884 | "WPS UPnP: setsockopt " | |
885 | "IP_ADD_MEMBERSHIP errno %d (%s)", | |
886 | errno, strerror(errno)); | |
887 | return -1; | |
888 | } | |
889 | } | |
890 | #endif /* not needed */ | |
891 | ||
892 | /* | |
893 | * TODO: What about IP_MULTICAST_LOOP? It seems to be on by default? | |
894 | * which aids debugging I suppose but isn't really necessary? | |
895 | */ | |
896 | ||
e9bcfebf JM |
897 | return sd; |
898 | } | |
899 | ||
900 | ||
901 | /** | |
902 | * ssdp_open_multicast - Open socket for sending multicast SSDP messages | |
903 | * @sm: WPS UPnP state machine from upnp_wps_device_init() | |
904 | * Returns: 0 on success, -1 on failure | |
905 | */ | |
906 | int ssdp_open_multicast(struct upnp_wps_device_sm *sm) | |
907 | { | |
908 | sm->multicast_sd = ssdp_open_multicast_sock(sm->ip_addr); | |
909 | if (sm->multicast_sd < 0) | |
910 | return -1; | |
f620268f JM |
911 | return 0; |
912 | } |