xref: /freebsd/contrib/wpa/src/wps/wps_upnp_ssdp.c (revision 780fb4a2fa9a9aee5ac48a60b790f567c0dc13e9)
139beb93cSSam Leffler /*
239beb93cSSam Leffler  * UPnP SSDP for WPS
339beb93cSSam Leffler  * Copyright (c) 2000-2003 Intel Corporation
439beb93cSSam Leffler  * Copyright (c) 2006-2007 Sony Corporation
539beb93cSSam Leffler  * Copyright (c) 2008-2009 Atheros Communications
65b9c547cSRui Paulo  * Copyright (c) 2009-2013, Jouni Malinen <j@w1.fi>
739beb93cSSam Leffler  *
839beb93cSSam Leffler  * See wps_upnp.c for more details on licensing and code history.
939beb93cSSam Leffler  */
1039beb93cSSam Leffler 
1139beb93cSSam Leffler #include "includes.h"
1239beb93cSSam Leffler 
1339beb93cSSam Leffler #include <fcntl.h>
1439beb93cSSam Leffler #include <sys/ioctl.h>
1539beb93cSSam Leffler #include <net/route.h>
165b9c547cSRui Paulo #ifdef __linux__
175b9c547cSRui Paulo #include <net/if.h>
185b9c547cSRui Paulo #endif /* __linux__ */
1939beb93cSSam Leffler 
2039beb93cSSam Leffler #include "common.h"
2139beb93cSSam Leffler #include "uuid.h"
2239beb93cSSam Leffler #include "eloop.h"
2339beb93cSSam Leffler #include "wps.h"
2439beb93cSSam Leffler #include "wps_upnp.h"
2539beb93cSSam Leffler #include "wps_upnp_i.h"
2639beb93cSSam Leffler 
2739beb93cSSam Leffler #define UPNP_CACHE_SEC (UPNP_CACHE_SEC_MIN + 1) /* cache time we use */
2839beb93cSSam Leffler #define UPNP_CACHE_SEC_MIN 1800 /* min cachable time per UPnP standard */
2939beb93cSSam Leffler #define UPNP_ADVERTISE_REPEAT 2 /* no more than 3 */
3039beb93cSSam Leffler #define MAX_MSEARCH 20          /* max simultaneous M-SEARCH replies ongoing */
3139beb93cSSam Leffler #define SSDP_TARGET  "239.0.0.0"
3239beb93cSSam Leffler #define SSDP_NETMASK "255.0.0.0"
3339beb93cSSam Leffler 
3439beb93cSSam Leffler 
3539beb93cSSam Leffler /* Check tokens for equality, where tokens consist of letters, digits,
3639beb93cSSam Leffler  * underscore and hyphen, and are matched case insensitive.
3739beb93cSSam Leffler  */
token_eq(const char * s1,const char * s2)3839beb93cSSam Leffler static int token_eq(const char *s1, const char *s2)
3939beb93cSSam Leffler {
4039beb93cSSam Leffler 	int c1;
4139beb93cSSam Leffler 	int c2;
4239beb93cSSam Leffler 	int end1 = 0;
4339beb93cSSam Leffler 	int end2 = 0;
4439beb93cSSam Leffler 	for (;;) {
4539beb93cSSam Leffler 		c1 = *s1++;
4639beb93cSSam Leffler 		c2 = *s2++;
4739beb93cSSam Leffler 		if (isalpha(c1) && isupper(c1))
4839beb93cSSam Leffler 			c1 = tolower(c1);
4939beb93cSSam Leffler 		if (isalpha(c2) && isupper(c2))
5039beb93cSSam Leffler 			c2 = tolower(c2);
5139beb93cSSam Leffler 		end1 = !(isalnum(c1) || c1 == '_' || c1 == '-');
5239beb93cSSam Leffler 		end2 = !(isalnum(c2) || c2 == '_' || c2 == '-');
5339beb93cSSam Leffler 		if (end1 || end2 || c1 != c2)
5439beb93cSSam Leffler 			break;
5539beb93cSSam Leffler 	}
5639beb93cSSam Leffler 	return end1 && end2; /* reached end of both words? */
5739beb93cSSam Leffler }
5839beb93cSSam Leffler 
5939beb93cSSam Leffler 
6039beb93cSSam Leffler /* Return length of token (see above for definition of token) */
token_length(const char * s)6139beb93cSSam Leffler static int token_length(const char *s)
6239beb93cSSam Leffler {
6339beb93cSSam Leffler 	const char *begin = s;
6439beb93cSSam Leffler 	for (;; s++) {
6539beb93cSSam Leffler 		int c = *s;
6639beb93cSSam Leffler 		int end = !(isalnum(c) || c == '_' || c == '-');
6739beb93cSSam Leffler 		if (end)
6839beb93cSSam Leffler 			break;
6939beb93cSSam Leffler 	}
7039beb93cSSam Leffler 	return s - begin;
7139beb93cSSam Leffler }
7239beb93cSSam Leffler 
7339beb93cSSam Leffler 
7439beb93cSSam Leffler /* return length of interword separation.
7539beb93cSSam Leffler  * This accepts only spaces/tabs and thus will not traverse a line
7639beb93cSSam Leffler  * or buffer ending.
7739beb93cSSam Leffler  */
word_separation_length(const char * s)7839beb93cSSam Leffler static int word_separation_length(const char *s)
7939beb93cSSam Leffler {
8039beb93cSSam Leffler 	const char *begin = s;
8139beb93cSSam Leffler 	for (;; s++) {
8239beb93cSSam Leffler 		int c = *s;
8339beb93cSSam Leffler 		if (c == ' ' || c == '\t')
8439beb93cSSam Leffler 			continue;
8539beb93cSSam Leffler 		break;
8639beb93cSSam Leffler 	}
8739beb93cSSam Leffler 	return s - begin;
8839beb93cSSam Leffler }
8939beb93cSSam Leffler 
9039beb93cSSam Leffler 
9139beb93cSSam Leffler /* No. of chars through (including) end of line */
line_length(const char * l)9239beb93cSSam Leffler static int line_length(const char *l)
9339beb93cSSam Leffler {
9439beb93cSSam Leffler 	const char *lp = l;
9539beb93cSSam Leffler 	while (*lp && *lp != '\n')
9639beb93cSSam Leffler 		lp++;
9739beb93cSSam Leffler 	if (*lp == '\n')
9839beb93cSSam Leffler 		lp++;
9939beb93cSSam Leffler 	return lp - l;
10039beb93cSSam Leffler }
10139beb93cSSam Leffler 
10239beb93cSSam Leffler 
10339beb93cSSam Leffler /***************************************************************************
10439beb93cSSam Leffler  * Advertisements.
10539beb93cSSam Leffler  * These are multicast to the world to tell them we are here.
10639beb93cSSam Leffler  * The individual packets are spread out in time to limit loss,
10739beb93cSSam Leffler  * and then after a much longer period of time the whole sequence
10839beb93cSSam Leffler  * is repeated again (for NOTIFYs only).
10939beb93cSSam Leffler  **************************************************************************/
11039beb93cSSam Leffler 
11139beb93cSSam Leffler /**
11239beb93cSSam Leffler  * next_advertisement - Build next message and advance the state machine
11339beb93cSSam Leffler  * @a: Advertisement state
11439beb93cSSam Leffler  * @islast: Buffer for indicating whether this is the last message (= 1)
11539beb93cSSam Leffler  * Returns: The new message (caller is responsible for freeing this)
11639beb93cSSam Leffler  *
11739beb93cSSam Leffler  * Note: next_advertisement is shared code with msearchreply_* functions
11839beb93cSSam Leffler  */
11939beb93cSSam Leffler static struct wpabuf *
next_advertisement(struct upnp_wps_device_sm * sm,struct advertisement_state_machine * a,int * islast)120e28a4053SRui Paulo next_advertisement(struct upnp_wps_device_sm *sm,
121e28a4053SRui Paulo 		   struct advertisement_state_machine *a, int *islast)
12239beb93cSSam Leffler {
12339beb93cSSam Leffler 	struct wpabuf *msg;
12439beb93cSSam Leffler 	char *NTString = "";
12539beb93cSSam Leffler 	char uuid_string[80];
126f05cddf9SRui Paulo 	struct upnp_wps_device_interface *iface;
12739beb93cSSam Leffler 
12839beb93cSSam Leffler 	*islast = 0;
129f05cddf9SRui Paulo 	iface = dl_list_first(&sm->interfaces,
130f05cddf9SRui Paulo 			      struct upnp_wps_device_interface, list);
1315b9c547cSRui Paulo 	if (!iface)
1325b9c547cSRui Paulo 		return NULL;
133f05cddf9SRui Paulo 	uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string));
13439beb93cSSam Leffler 	msg = wpabuf_alloc(800); /* more than big enough */
13539beb93cSSam Leffler 	if (msg == NULL)
136*325151a3SRui Paulo 		return NULL;
13739beb93cSSam Leffler 	switch (a->type) {
13839beb93cSSam Leffler 	case ADVERTISE_UP:
13939beb93cSSam Leffler 	case ADVERTISE_DOWN:
14039beb93cSSam Leffler 		NTString = "NT";
14139beb93cSSam Leffler 		wpabuf_put_str(msg, "NOTIFY * HTTP/1.1\r\n");
14239beb93cSSam Leffler 		wpabuf_printf(msg, "HOST: %s:%d\r\n",
14339beb93cSSam Leffler 			      UPNP_MULTICAST_ADDRESS, UPNP_MULTICAST_PORT);
14439beb93cSSam Leffler 		wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
14539beb93cSSam Leffler 			      UPNP_CACHE_SEC);
14639beb93cSSam Leffler 		wpabuf_printf(msg, "NTS: %s\r\n",
14739beb93cSSam Leffler 			      (a->type == ADVERTISE_UP ?
14839beb93cSSam Leffler 			       "ssdp:alive" : "ssdp:byebye"));
14939beb93cSSam Leffler 		break;
15039beb93cSSam Leffler 	case MSEARCH_REPLY:
15139beb93cSSam Leffler 		NTString = "ST";
15239beb93cSSam Leffler 		wpabuf_put_str(msg, "HTTP/1.1 200 OK\r\n");
15339beb93cSSam Leffler 		wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
15439beb93cSSam Leffler 			      UPNP_CACHE_SEC);
15539beb93cSSam Leffler 
15639beb93cSSam Leffler 		wpabuf_put_str(msg, "DATE: ");
15739beb93cSSam Leffler 		format_date(msg);
15839beb93cSSam Leffler 		wpabuf_put_str(msg, "\r\n");
15939beb93cSSam Leffler 
16039beb93cSSam Leffler 		wpabuf_put_str(msg, "EXT:\r\n");
16139beb93cSSam Leffler 		break;
16239beb93cSSam Leffler 	}
16339beb93cSSam Leffler 
16439beb93cSSam Leffler 	if (a->type != ADVERTISE_DOWN) {
16539beb93cSSam Leffler 		/* Where others may get our XML files from */
16639beb93cSSam Leffler 		wpabuf_printf(msg, "LOCATION: http://%s:%d/%s\r\n",
167e28a4053SRui Paulo 			      sm->ip_addr_text, sm->web_port,
16839beb93cSSam Leffler 			      UPNP_WPS_DEVICE_XML_FILE);
16939beb93cSSam Leffler 	}
17039beb93cSSam Leffler 
17139beb93cSSam Leffler 	/* The SERVER line has three comma-separated fields:
17239beb93cSSam Leffler 	 *      operating system / version
17339beb93cSSam Leffler 	 *      upnp version
17439beb93cSSam Leffler 	 *      software package / version
17539beb93cSSam Leffler 	 * However, only the UPnP version is really required, the
17639beb93cSSam Leffler 	 * others can be place holders... for security reasons
17739beb93cSSam Leffler 	 * it is better to NOT provide extra information.
17839beb93cSSam Leffler 	 */
17939beb93cSSam Leffler 	wpabuf_put_str(msg, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n");
18039beb93cSSam Leffler 
18139beb93cSSam Leffler 	switch (a->state / UPNP_ADVERTISE_REPEAT) {
18239beb93cSSam Leffler 	case 0:
18339beb93cSSam Leffler 		wpabuf_printf(msg, "%s: upnp:rootdevice\r\n", NTString);
18439beb93cSSam Leffler 		wpabuf_printf(msg, "USN: uuid:%s::upnp:rootdevice\r\n",
18539beb93cSSam Leffler 			      uuid_string);
18639beb93cSSam Leffler 		break;
18739beb93cSSam Leffler 	case 1:
18839beb93cSSam Leffler 		wpabuf_printf(msg, "%s: uuid:%s\r\n", NTString, uuid_string);
18939beb93cSSam Leffler 		wpabuf_printf(msg, "USN: uuid:%s\r\n", uuid_string);
19039beb93cSSam Leffler 		break;
19139beb93cSSam Leffler 	case 2:
19239beb93cSSam Leffler 		wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:device:"
19339beb93cSSam Leffler 			      "WFADevice:1\r\n", NTString);
19439beb93cSSam Leffler 		wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
19539beb93cSSam Leffler 			      "org:device:WFADevice:1\r\n", uuid_string);
19639beb93cSSam Leffler 		break;
19739beb93cSSam Leffler 	case 3:
19839beb93cSSam Leffler 		wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:service:"
19939beb93cSSam Leffler 			      "WFAWLANConfig:1\r\n", NTString);
20039beb93cSSam Leffler 		wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
20139beb93cSSam Leffler 			      "org:service:WFAWLANConfig:1\r\n", uuid_string);
20239beb93cSSam Leffler 		break;
20339beb93cSSam Leffler 	}
20439beb93cSSam Leffler 	wpabuf_put_str(msg, "\r\n");
20539beb93cSSam Leffler 
20639beb93cSSam Leffler 	if (a->state + 1 >= 4 * UPNP_ADVERTISE_REPEAT)
20739beb93cSSam Leffler 		*islast = 1;
20839beb93cSSam Leffler 
20939beb93cSSam Leffler 	return msg;
21039beb93cSSam Leffler }
21139beb93cSSam Leffler 
21239beb93cSSam Leffler 
21339beb93cSSam Leffler static void advertisement_state_machine_handler(void *eloop_data,
21439beb93cSSam Leffler 						void *user_ctx);
21539beb93cSSam Leffler 
21639beb93cSSam Leffler 
21739beb93cSSam Leffler /**
21839beb93cSSam Leffler  * advertisement_state_machine_stop - Stop SSDP advertisements
21939beb93cSSam Leffler  * @sm: WPS UPnP state machine from upnp_wps_device_init()
2203157ba21SRui Paulo  * @send_byebye: Send byebye advertisement messages immediately
22139beb93cSSam Leffler  */
advertisement_state_machine_stop(struct upnp_wps_device_sm * sm,int send_byebye)2223157ba21SRui Paulo void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm,
2233157ba21SRui Paulo 				      int send_byebye)
22439beb93cSSam Leffler {
2253157ba21SRui Paulo 	struct advertisement_state_machine *a = &sm->advertisement;
2263157ba21SRui Paulo 	int islast = 0;
2273157ba21SRui Paulo 	struct wpabuf *msg;
2283157ba21SRui Paulo 	struct sockaddr_in dest;
2293157ba21SRui Paulo 
23039beb93cSSam Leffler 	eloop_cancel_timeout(advertisement_state_machine_handler, NULL, sm);
2313157ba21SRui Paulo 	if (!send_byebye || sm->multicast_sd < 0)
2323157ba21SRui Paulo 		return;
2333157ba21SRui Paulo 
2343157ba21SRui Paulo 	a->type = ADVERTISE_DOWN;
2353157ba21SRui Paulo 	a->state = 0;
2363157ba21SRui Paulo 
2373157ba21SRui Paulo 	os_memset(&dest, 0, sizeof(dest));
2383157ba21SRui Paulo 	dest.sin_family = AF_INET;
2393157ba21SRui Paulo 	dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
2403157ba21SRui Paulo 	dest.sin_port = htons(UPNP_MULTICAST_PORT);
2413157ba21SRui Paulo 
2423157ba21SRui Paulo 	while (!islast) {
243e28a4053SRui Paulo 		msg = next_advertisement(sm, a, &islast);
2443157ba21SRui Paulo 		if (msg == NULL)
2453157ba21SRui Paulo 			break;
2463157ba21SRui Paulo 		if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg),
2473157ba21SRui Paulo 			   0, (struct sockaddr *) &dest, sizeof(dest)) < 0) {
2483157ba21SRui Paulo 			wpa_printf(MSG_INFO, "WPS UPnP: Advertisement sendto "
2493157ba21SRui Paulo 				   "failed: %d (%s)", errno, strerror(errno));
2503157ba21SRui Paulo 		}
2513157ba21SRui Paulo 		wpabuf_free(msg);
2523157ba21SRui Paulo 		a->state++;
2533157ba21SRui Paulo 	}
25439beb93cSSam Leffler }
25539beb93cSSam Leffler 
25639beb93cSSam Leffler 
advertisement_state_machine_handler(void * eloop_data,void * user_ctx)25739beb93cSSam Leffler static void advertisement_state_machine_handler(void *eloop_data,
25839beb93cSSam Leffler 						void *user_ctx)
25939beb93cSSam Leffler {
26039beb93cSSam Leffler 	struct upnp_wps_device_sm *sm = user_ctx;
26139beb93cSSam Leffler 	struct advertisement_state_machine *a = &sm->advertisement;
26239beb93cSSam Leffler 	struct wpabuf *msg;
26339beb93cSSam Leffler 	int next_timeout_msec = 100;
26439beb93cSSam Leffler 	int next_timeout_sec = 0;
26539beb93cSSam Leffler 	struct sockaddr_in dest;
26639beb93cSSam Leffler 	int islast = 0;
26739beb93cSSam Leffler 
26839beb93cSSam Leffler 	/*
26939beb93cSSam Leffler 	 * Each is sent twice (in case lost) w/ 100 msec delay between;
27039beb93cSSam Leffler 	 * spec says no more than 3 times.
27139beb93cSSam Leffler 	 * One pair for rootdevice, one pair for uuid, and a pair each for
27239beb93cSSam Leffler 	 * each of the two urns.
27339beb93cSSam Leffler 	 * The entire sequence must be repeated before cache control timeout
27439beb93cSSam Leffler 	 * (which  is min  1800 seconds),
27539beb93cSSam Leffler 	 * recommend random portion of half of the advertised cache control age
27639beb93cSSam Leffler 	 * to ensure against loss... perhaps 1800/4 + rand*1800/4 ?
27739beb93cSSam Leffler 	 * Delay random interval < 100 msec prior to initial sending.
27839beb93cSSam Leffler 	 * TTL of 4
27939beb93cSSam Leffler 	 */
28039beb93cSSam Leffler 
28139beb93cSSam Leffler 	wpa_printf(MSG_MSGDUMP, "WPS UPnP: Advertisement state=%d", a->state);
282e28a4053SRui Paulo 	msg = next_advertisement(sm, a, &islast);
28339beb93cSSam Leffler 	if (msg == NULL)
28439beb93cSSam Leffler 		return;
28539beb93cSSam Leffler 
28639beb93cSSam Leffler 	os_memset(&dest, 0, sizeof(dest));
28739beb93cSSam Leffler 	dest.sin_family = AF_INET;
28839beb93cSSam Leffler 	dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
28939beb93cSSam Leffler 	dest.sin_port = htons(UPNP_MULTICAST_PORT);
29039beb93cSSam Leffler 
29139beb93cSSam Leffler 	if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
29239beb93cSSam Leffler 		   (struct sockaddr *) &dest, sizeof(dest)) == -1) {
29339beb93cSSam Leffler 		wpa_printf(MSG_ERROR, "WPS UPnP: Advertisement sendto failed:"
29439beb93cSSam Leffler 			   "%d (%s)", errno, strerror(errno));
29539beb93cSSam Leffler 		next_timeout_msec = 0;
29639beb93cSSam Leffler 		next_timeout_sec = 10; /* ... later */
29739beb93cSSam Leffler 	} else if (islast) {
29839beb93cSSam Leffler 		a->state = 0; /* wrap around */
29939beb93cSSam Leffler 		if (a->type == ADVERTISE_DOWN) {
30039beb93cSSam Leffler 			wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_DOWN->UP");
30139beb93cSSam Leffler 			a->type = ADVERTISE_UP;
30239beb93cSSam Leffler 			/* do it all over again right away */
30339beb93cSSam Leffler 		} else {
30439beb93cSSam Leffler 			u16 r;
30539beb93cSSam Leffler 			/*
30639beb93cSSam Leffler 			 * Start over again after a long timeout
30739beb93cSSam Leffler 			 * (see notes above)
30839beb93cSSam Leffler 			 */
30939beb93cSSam Leffler 			next_timeout_msec = 0;
3105b9c547cSRui Paulo 			if (os_get_random((void *) &r, sizeof(r)) < 0)
3115b9c547cSRui Paulo 				r = 32768;
31239beb93cSSam Leffler 			next_timeout_sec = UPNP_CACHE_SEC / 4 +
31339beb93cSSam Leffler 				(((UPNP_CACHE_SEC / 4) * r) >> 16);
31439beb93cSSam Leffler 			sm->advertise_count++;
31539beb93cSSam Leffler 			wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_UP (#%u); "
31639beb93cSSam Leffler 				   "next in %d sec",
31739beb93cSSam Leffler 				   sm->advertise_count, next_timeout_sec);
31839beb93cSSam Leffler 		}
31939beb93cSSam Leffler 	} else {
32039beb93cSSam Leffler 		a->state++;
32139beb93cSSam Leffler 	}
32239beb93cSSam Leffler 
32339beb93cSSam Leffler 	wpabuf_free(msg);
32439beb93cSSam Leffler 
32539beb93cSSam Leffler 	eloop_register_timeout(next_timeout_sec, next_timeout_msec,
32639beb93cSSam Leffler 			       advertisement_state_machine_handler, NULL, sm);
32739beb93cSSam Leffler }
32839beb93cSSam Leffler 
32939beb93cSSam Leffler 
33039beb93cSSam Leffler /**
33139beb93cSSam Leffler  * advertisement_state_machine_start - Start SSDP advertisements
33239beb93cSSam Leffler  * @sm: WPS UPnP state machine from upnp_wps_device_init()
33339beb93cSSam Leffler  * Returns: 0 on success, -1 on failure
33439beb93cSSam Leffler  */
advertisement_state_machine_start(struct upnp_wps_device_sm * sm)33539beb93cSSam Leffler int advertisement_state_machine_start(struct upnp_wps_device_sm *sm)
33639beb93cSSam Leffler {
33739beb93cSSam Leffler 	struct advertisement_state_machine *a = &sm->advertisement;
33839beb93cSSam Leffler 	int next_timeout_msec;
33939beb93cSSam Leffler 
3403157ba21SRui Paulo 	advertisement_state_machine_stop(sm, 0);
34139beb93cSSam Leffler 
34239beb93cSSam Leffler 	/*
34339beb93cSSam Leffler 	 * Start out advertising down, this automatically switches
34439beb93cSSam Leffler 	 * to advertising up which signals our restart.
34539beb93cSSam Leffler 	 */
34639beb93cSSam Leffler 	a->type = ADVERTISE_DOWN;
34739beb93cSSam Leffler 	a->state = 0;
34839beb93cSSam Leffler 	/* (other fields not used here) */
34939beb93cSSam Leffler 
35039beb93cSSam Leffler 	/* First timeout should be random interval < 100 msec */
35139beb93cSSam Leffler 	next_timeout_msec = (100 * (os_random() & 0xFF)) >> 8;
35239beb93cSSam Leffler 	return eloop_register_timeout(0, next_timeout_msec,
35339beb93cSSam Leffler 				      advertisement_state_machine_handler,
35439beb93cSSam Leffler 				      NULL, sm);
35539beb93cSSam Leffler }
35639beb93cSSam Leffler 
35739beb93cSSam Leffler 
35839beb93cSSam Leffler /***************************************************************************
35939beb93cSSam Leffler  * M-SEARCH replies
36039beb93cSSam Leffler  * These are very similar to the multicast advertisements, with some
36139beb93cSSam Leffler  * small changes in data content; and they are sent (UDP) to a specific
36239beb93cSSam Leffler  * unicast address instead of multicast.
36339beb93cSSam Leffler  * They are sent in response to a UDP M-SEARCH packet.
36439beb93cSSam Leffler  **************************************************************************/
36539beb93cSSam Leffler 
36639beb93cSSam Leffler /**
36739beb93cSSam Leffler  * msearchreply_state_machine_stop - Stop M-SEARCH reply state machine
36839beb93cSSam Leffler  * @a: Selected advertisement/reply state
36939beb93cSSam Leffler  */
msearchreply_state_machine_stop(struct advertisement_state_machine * a)37039beb93cSSam Leffler void msearchreply_state_machine_stop(struct advertisement_state_machine *a)
37139beb93cSSam Leffler {
37239beb93cSSam Leffler 	wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH stop");
373e28a4053SRui Paulo 	dl_list_del(&a->list);
37439beb93cSSam Leffler 	os_free(a);
37539beb93cSSam Leffler }
37639beb93cSSam Leffler 
37739beb93cSSam Leffler 
msearchreply_state_machine_handler(void * eloop_data,void * user_ctx)37839beb93cSSam Leffler static void msearchreply_state_machine_handler(void *eloop_data,
37939beb93cSSam Leffler 					       void *user_ctx)
38039beb93cSSam Leffler {
38139beb93cSSam Leffler 	struct advertisement_state_machine *a = user_ctx;
382e28a4053SRui Paulo 	struct upnp_wps_device_sm *sm = eloop_data;
38339beb93cSSam Leffler 	struct wpabuf *msg;
38439beb93cSSam Leffler 	int next_timeout_msec = 100;
38539beb93cSSam Leffler 	int next_timeout_sec = 0;
38639beb93cSSam Leffler 	int islast = 0;
38739beb93cSSam Leffler 
38839beb93cSSam Leffler 	/*
38939beb93cSSam Leffler 	 * Each response is sent twice (in case lost) w/ 100 msec delay
39039beb93cSSam Leffler 	 * between; spec says no more than 3 times.
39139beb93cSSam Leffler 	 * One pair for rootdevice, one pair for uuid, and a pair each for
39239beb93cSSam Leffler 	 * each of the two urns.
39339beb93cSSam Leffler 	 */
39439beb93cSSam Leffler 
39539beb93cSSam Leffler 	/* TODO: should only send the requested response types */
39639beb93cSSam Leffler 
39739beb93cSSam Leffler 	wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply state=%d (%s:%d)",
39839beb93cSSam Leffler 		   a->state, inet_ntoa(a->client.sin_addr),
39939beb93cSSam Leffler 		   ntohs(a->client.sin_port));
400e28a4053SRui Paulo 	msg = next_advertisement(sm, a, &islast);
40139beb93cSSam Leffler 	if (msg == NULL)
40239beb93cSSam Leffler 		return;
40339beb93cSSam Leffler 
40439beb93cSSam Leffler 	/*
40539beb93cSSam Leffler 	 * Send it on the multicast socket to avoid having to set up another
40639beb93cSSam Leffler 	 * socket.
40739beb93cSSam Leffler 	 */
40839beb93cSSam Leffler 	if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
40939beb93cSSam Leffler 		   (struct sockaddr *) &a->client, sizeof(a->client)) < 0) {
41039beb93cSSam Leffler 		wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply sendto "
41139beb93cSSam Leffler 			   "errno %d (%s) for %s:%d",
41239beb93cSSam Leffler 			   errno, strerror(errno),
41339beb93cSSam Leffler 			   inet_ntoa(a->client.sin_addr),
41439beb93cSSam Leffler 			   ntohs(a->client.sin_port));
41539beb93cSSam Leffler 		/* Ignore error and hope for the best */
41639beb93cSSam Leffler 	}
41739beb93cSSam Leffler 	wpabuf_free(msg);
41839beb93cSSam Leffler 	if (islast) {
41939beb93cSSam Leffler 		wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply done");
42039beb93cSSam Leffler 		msearchreply_state_machine_stop(a);
42139beb93cSSam Leffler 		return;
42239beb93cSSam Leffler 	}
42339beb93cSSam Leffler 	a->state++;
42439beb93cSSam Leffler 
42539beb93cSSam Leffler 	wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply in %d.%03d sec",
42639beb93cSSam Leffler 		   next_timeout_sec, next_timeout_msec);
42739beb93cSSam Leffler 	eloop_register_timeout(next_timeout_sec, next_timeout_msec,
42839beb93cSSam Leffler 			       msearchreply_state_machine_handler, sm, a);
42939beb93cSSam Leffler }
43039beb93cSSam Leffler 
43139beb93cSSam Leffler 
43239beb93cSSam Leffler /**
43339beb93cSSam Leffler  * msearchreply_state_machine_start - Reply to M-SEARCH discovery request
43439beb93cSSam Leffler  * @sm: WPS UPnP state machine from upnp_wps_device_init()
43539beb93cSSam Leffler  * @client: Client address
43639beb93cSSam Leffler  * @mx: Maximum delay in seconds
43739beb93cSSam Leffler  *
43839beb93cSSam Leffler  * Use TTL of 4 (this was done when socket set up).
43939beb93cSSam Leffler  * A response should be given in randomized portion of min(MX,120) seconds
44039beb93cSSam Leffler  *
44139beb93cSSam Leffler  * UPnP-arch-DeviceArchitecture, 1.2.3:
44239beb93cSSam Leffler  * To be found, a device must send a UDP response to the source IP address and
44339beb93cSSam Leffler  * port that sent the request to the multicast channel. Devices respond if the
44439beb93cSSam Leffler  * ST header of the M-SEARCH request is "ssdp:all", "upnp:rootdevice", "uuid:"
44539beb93cSSam Leffler  * followed by a UUID that exactly matches one advertised by the device.
44639beb93cSSam Leffler  */
msearchreply_state_machine_start(struct upnp_wps_device_sm * sm,struct sockaddr_in * client,int mx)44739beb93cSSam Leffler static void msearchreply_state_machine_start(struct upnp_wps_device_sm *sm,
44839beb93cSSam Leffler 					     struct sockaddr_in *client,
44939beb93cSSam Leffler 					     int mx)
45039beb93cSSam Leffler {
45139beb93cSSam Leffler 	struct advertisement_state_machine *a;
45239beb93cSSam Leffler 	int next_timeout_sec;
45339beb93cSSam Leffler 	int next_timeout_msec;
454e28a4053SRui Paulo 	int replies;
45539beb93cSSam Leffler 
456e28a4053SRui Paulo 	replies = dl_list_len(&sm->msearch_replies);
45739beb93cSSam Leffler 	wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply start (%d "
458e28a4053SRui Paulo 		   "outstanding)", replies);
459e28a4053SRui Paulo 	if (replies >= MAX_MSEARCH) {
46039beb93cSSam Leffler 		wpa_printf(MSG_INFO, "WPS UPnP: Too many outstanding "
46139beb93cSSam Leffler 			   "M-SEARCH replies");
46239beb93cSSam Leffler 		return;
46339beb93cSSam Leffler 	}
46439beb93cSSam Leffler 
46539beb93cSSam Leffler 	a = os_zalloc(sizeof(*a));
46639beb93cSSam Leffler 	if (a == NULL)
46739beb93cSSam Leffler 		return;
46839beb93cSSam Leffler 	a->type = MSEARCH_REPLY;
46939beb93cSSam Leffler 	a->state = 0;
4703157ba21SRui Paulo 	os_memcpy(&a->client, client, sizeof(*client));
47139beb93cSSam Leffler 	/* Wait time depending on MX value */
47239beb93cSSam Leffler 	next_timeout_msec = (1000 * mx * (os_random() & 0xFF)) >> 8;
47339beb93cSSam Leffler 	next_timeout_sec = next_timeout_msec / 1000;
47439beb93cSSam Leffler 	next_timeout_msec = next_timeout_msec % 1000;
47539beb93cSSam Leffler 	if (eloop_register_timeout(next_timeout_sec, next_timeout_msec,
47639beb93cSSam Leffler 				   msearchreply_state_machine_handler, sm,
47739beb93cSSam Leffler 				   a)) {
47839beb93cSSam Leffler 		/* No way to recover (from malloc failure) */
47939beb93cSSam Leffler 		goto fail;
48039beb93cSSam Leffler 	}
48139beb93cSSam Leffler 	/* Remember for future cleanup */
482e28a4053SRui Paulo 	dl_list_add(&sm->msearch_replies, &a->list);
48339beb93cSSam Leffler 	return;
48439beb93cSSam Leffler 
48539beb93cSSam Leffler fail:
48639beb93cSSam Leffler 	wpa_printf(MSG_INFO, "WPS UPnP: M-SEARCH reply failure!");
48739beb93cSSam Leffler 	eloop_cancel_timeout(msearchreply_state_machine_handler, sm, a);
48839beb93cSSam Leffler 	os_free(a);
48939beb93cSSam Leffler }
49039beb93cSSam Leffler 
49139beb93cSSam Leffler 
49239beb93cSSam Leffler /**
49339beb93cSSam Leffler  * ssdp_parse_msearch - Process a received M-SEARCH
49439beb93cSSam Leffler  * @sm: WPS UPnP state machine from upnp_wps_device_init()
49539beb93cSSam Leffler  * @client: Client address
49639beb93cSSam Leffler  * @data: NULL terminated M-SEARCH message
49739beb93cSSam Leffler  *
49839beb93cSSam Leffler  * Given that we have received a header w/ M-SEARCH, act upon it
49939beb93cSSam Leffler  *
50039beb93cSSam Leffler  * Format of M-SEARCH (case insensitive!):
50139beb93cSSam Leffler  *
50239beb93cSSam Leffler  * First line must be:
50339beb93cSSam Leffler  *      M-SEARCH * HTTP/1.1
50439beb93cSSam Leffler  * Other lines in arbitrary order:
50539beb93cSSam Leffler  *      HOST:239.255.255.250:1900
50639beb93cSSam Leffler  *      ST:<varies -- must match>
50739beb93cSSam Leffler  *      MAN:"ssdp:discover"
50839beb93cSSam Leffler  *      MX:<varies>
50939beb93cSSam Leffler  *
51039beb93cSSam Leffler  * It should be noted that when Microsoft Vista is still learning its IP
51139beb93cSSam Leffler  * address, it sends out host lines like: HOST:[FF02::C]:1900
51239beb93cSSam Leffler  */
ssdp_parse_msearch(struct upnp_wps_device_sm * sm,struct sockaddr_in * client,const char * data)51339beb93cSSam Leffler static void ssdp_parse_msearch(struct upnp_wps_device_sm *sm,
51439beb93cSSam Leffler 			       struct sockaddr_in *client, const char *data)
51539beb93cSSam Leffler {
516e28a4053SRui Paulo #ifndef CONFIG_NO_STDOUT_DEBUG
51739beb93cSSam Leffler 	const char *start = data;
518e28a4053SRui Paulo #endif /* CONFIG_NO_STDOUT_DEBUG */
51939beb93cSSam Leffler 	int got_host = 0;
52039beb93cSSam Leffler 	int got_st = 0, st_match = 0;
52139beb93cSSam Leffler 	int got_man = 0;
52239beb93cSSam Leffler 	int got_mx = 0;
52339beb93cSSam Leffler 	int mx = 0;
52439beb93cSSam Leffler 
52539beb93cSSam Leffler 	/*
52639beb93cSSam Leffler 	 * Skip first line M-SEARCH * HTTP/1.1
52739beb93cSSam Leffler 	 * (perhaps we should check remainder of the line for syntax)
52839beb93cSSam Leffler 	 */
52939beb93cSSam Leffler 	data += line_length(data);
53039beb93cSSam Leffler 
53139beb93cSSam Leffler 	/* Parse remaining lines */
53239beb93cSSam Leffler 	for (; *data != '\0'; data += line_length(data)) {
53339beb93cSSam Leffler 		if (token_eq(data, "host")) {
53439beb93cSSam Leffler 			/* The host line indicates who the packet
53539beb93cSSam Leffler 			 * is addressed to... but do we really care?
53639beb93cSSam Leffler 			 * Note that Microsoft sometimes does funny
53739beb93cSSam Leffler 			 * stuff with the HOST: line.
53839beb93cSSam Leffler 			 */
53939beb93cSSam Leffler #if 0   /* could be */
54039beb93cSSam Leffler 			data += token_length(data);
54139beb93cSSam Leffler 			data += word_separation_length(data);
54239beb93cSSam Leffler 			if (*data != ':')
54339beb93cSSam Leffler 				goto bad;
54439beb93cSSam Leffler 			data++;
54539beb93cSSam Leffler 			data += word_separation_length(data);
54639beb93cSSam Leffler 			/* UPNP_MULTICAST_ADDRESS */
54739beb93cSSam Leffler 			if (!str_starts(data, "239.255.255.250"))
54839beb93cSSam Leffler 				goto bad;
54939beb93cSSam Leffler 			data += os_strlen("239.255.255.250");
55039beb93cSSam Leffler 			if (*data == ':') {
55139beb93cSSam Leffler 				if (!str_starts(data, ":1900"))
55239beb93cSSam Leffler 					goto bad;
55339beb93cSSam Leffler 			}
55439beb93cSSam Leffler #endif  /* could be */
55539beb93cSSam Leffler 			got_host = 1;
55639beb93cSSam Leffler 			continue;
55739beb93cSSam Leffler 		} else if (token_eq(data, "st")) {
55839beb93cSSam Leffler 			/* There are a number of forms; we look
55939beb93cSSam Leffler 			 * for one that matches our case.
56039beb93cSSam Leffler 			 */
56139beb93cSSam Leffler 			got_st = 1;
56239beb93cSSam Leffler 			data += token_length(data);
56339beb93cSSam Leffler 			data += word_separation_length(data);
56439beb93cSSam Leffler 			if (*data != ':')
56539beb93cSSam Leffler 				continue;
56639beb93cSSam Leffler 			data++;
56739beb93cSSam Leffler 			data += word_separation_length(data);
56839beb93cSSam Leffler 			if (str_starts(data, "ssdp:all")) {
56939beb93cSSam Leffler 				st_match = 1;
57039beb93cSSam Leffler 				continue;
57139beb93cSSam Leffler 			}
57239beb93cSSam Leffler 			if (str_starts(data, "upnp:rootdevice")) {
57339beb93cSSam Leffler 				st_match = 1;
57439beb93cSSam Leffler 				continue;
57539beb93cSSam Leffler 			}
57639beb93cSSam Leffler 			if (str_starts(data, "uuid:")) {
57739beb93cSSam Leffler 				char uuid_string[80];
578f05cddf9SRui Paulo 				struct upnp_wps_device_interface *iface;
579f05cddf9SRui Paulo 				iface = dl_list_first(
580f05cddf9SRui Paulo 					&sm->interfaces,
581f05cddf9SRui Paulo 					struct upnp_wps_device_interface,
582f05cddf9SRui Paulo 					list);
5835b9c547cSRui Paulo 				if (!iface)
5845b9c547cSRui Paulo 					continue;
58539beb93cSSam Leffler 				data += os_strlen("uuid:");
586f05cddf9SRui Paulo 				uuid_bin2str(iface->wps->uuid, uuid_string,
58739beb93cSSam Leffler 					     sizeof(uuid_string));
58839beb93cSSam Leffler 				if (str_starts(data, uuid_string))
58939beb93cSSam Leffler 					st_match = 1;
59039beb93cSSam Leffler 				continue;
59139beb93cSSam Leffler 			}
59239beb93cSSam Leffler #if 0
59339beb93cSSam Leffler 			/* FIX: should we really reply to IGD string? */
59439beb93cSSam Leffler 			if (str_starts(data, "urn:schemas-upnp-org:device:"
59539beb93cSSam Leffler 				       "InternetGatewayDevice:1")) {
59639beb93cSSam Leffler 				st_match = 1;
59739beb93cSSam Leffler 				continue;
59839beb93cSSam Leffler 			}
59939beb93cSSam Leffler #endif
60039beb93cSSam Leffler 			if (str_starts(data, "urn:schemas-wifialliance-org:"
60139beb93cSSam Leffler 				       "service:WFAWLANConfig:1")) {
60239beb93cSSam Leffler 				st_match = 1;
60339beb93cSSam Leffler 				continue;
60439beb93cSSam Leffler 			}
60539beb93cSSam Leffler 			if (str_starts(data, "urn:schemas-wifialliance-org:"
60639beb93cSSam Leffler 				       "device:WFADevice:1")) {
60739beb93cSSam Leffler 				st_match = 1;
60839beb93cSSam Leffler 				continue;
60939beb93cSSam Leffler 			}
61039beb93cSSam Leffler 			continue;
61139beb93cSSam Leffler 		} else if (token_eq(data, "man")) {
61239beb93cSSam Leffler 			data += token_length(data);
61339beb93cSSam Leffler 			data += word_separation_length(data);
61439beb93cSSam Leffler 			if (*data != ':')
61539beb93cSSam Leffler 				continue;
61639beb93cSSam Leffler 			data++;
61739beb93cSSam Leffler 			data += word_separation_length(data);
61839beb93cSSam Leffler 			if (!str_starts(data, "\"ssdp:discover\"")) {
61939beb93cSSam Leffler 				wpa_printf(MSG_DEBUG, "WPS UPnP: Unexpected "
62039beb93cSSam Leffler 					   "M-SEARCH man-field");
62139beb93cSSam Leffler 				goto bad;
62239beb93cSSam Leffler 			}
62339beb93cSSam Leffler 			got_man = 1;
62439beb93cSSam Leffler 			continue;
62539beb93cSSam Leffler 		} else if (token_eq(data, "mx")) {
62639beb93cSSam Leffler 			data += token_length(data);
62739beb93cSSam Leffler 			data += word_separation_length(data);
62839beb93cSSam Leffler 			if (*data != ':')
62939beb93cSSam Leffler 				continue;
63039beb93cSSam Leffler 			data++;
63139beb93cSSam Leffler 			data += word_separation_length(data);
63239beb93cSSam Leffler 			mx = atol(data);
63339beb93cSSam Leffler 			got_mx = 1;
63439beb93cSSam Leffler 			continue;
63539beb93cSSam Leffler 		}
63639beb93cSSam Leffler 		/* ignore anything else */
63739beb93cSSam Leffler 	}
63839beb93cSSam Leffler 	if (!got_host || !got_st || !got_man || !got_mx || mx < 0) {
63939beb93cSSam Leffler 		wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid M-SEARCH: %d %d %d "
64039beb93cSSam Leffler 			   "%d mx=%d", got_host, got_st, got_man, got_mx, mx);
64139beb93cSSam Leffler 		goto bad;
64239beb93cSSam Leffler 	}
64339beb93cSSam Leffler 	if (!st_match) {
64439beb93cSSam Leffler 		wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored M-SEARCH (no ST "
64539beb93cSSam Leffler 			   "match)");
64639beb93cSSam Leffler 		return;
64739beb93cSSam Leffler 	}
64839beb93cSSam Leffler 	if (mx > 120)
64939beb93cSSam Leffler 		mx = 120; /* UPnP-arch-DeviceArchitecture, 1.2.3 */
65039beb93cSSam Leffler 	msearchreply_state_machine_start(sm, client, mx);
65139beb93cSSam Leffler 	return;
65239beb93cSSam Leffler 
65339beb93cSSam Leffler bad:
65439beb93cSSam Leffler 	wpa_printf(MSG_INFO, "WPS UPnP: Failed to parse M-SEARCH");
65539beb93cSSam Leffler 	wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH data:\n%s", start);
65639beb93cSSam Leffler }
65739beb93cSSam Leffler 
65839beb93cSSam Leffler 
65939beb93cSSam Leffler /* Listening for (UDP) discovery (M-SEARCH) packets */
66039beb93cSSam Leffler 
66139beb93cSSam Leffler /**
66239beb93cSSam Leffler  * ssdp_listener_stop - Stop SSDP listered
66339beb93cSSam Leffler  * @sm: WPS UPnP state machine from upnp_wps_device_init()
66439beb93cSSam Leffler  *
665e28a4053SRui Paulo  * This function stops the SSDP listener that was started by calling
66639beb93cSSam Leffler  * ssdp_listener_start().
66739beb93cSSam Leffler  */
ssdp_listener_stop(struct upnp_wps_device_sm * sm)66839beb93cSSam Leffler void ssdp_listener_stop(struct upnp_wps_device_sm *sm)
66939beb93cSSam Leffler {
67039beb93cSSam Leffler 	if (sm->ssdp_sd_registered) {
67139beb93cSSam Leffler 		eloop_unregister_sock(sm->ssdp_sd, EVENT_TYPE_READ);
67239beb93cSSam Leffler 		sm->ssdp_sd_registered = 0;
67339beb93cSSam Leffler 	}
67439beb93cSSam Leffler 
67539beb93cSSam Leffler 	if (sm->ssdp_sd != -1) {
67639beb93cSSam Leffler 		close(sm->ssdp_sd);
67739beb93cSSam Leffler 		sm->ssdp_sd = -1;
67839beb93cSSam Leffler 	}
67939beb93cSSam Leffler 
68039beb93cSSam Leffler 	eloop_cancel_timeout(msearchreply_state_machine_handler, sm,
68139beb93cSSam Leffler 			     ELOOP_ALL_CTX);
68239beb93cSSam Leffler }
68339beb93cSSam Leffler 
68439beb93cSSam Leffler 
ssdp_listener_handler(int sd,void * eloop_ctx,void * sock_ctx)68539beb93cSSam Leffler static void ssdp_listener_handler(int sd, void *eloop_ctx, void *sock_ctx)
68639beb93cSSam Leffler {
68739beb93cSSam Leffler 	struct upnp_wps_device_sm *sm = sock_ctx;
68839beb93cSSam Leffler 	struct sockaddr_in addr; /* client address */
68939beb93cSSam Leffler 	socklen_t addr_len;
69039beb93cSSam Leffler 	int nread;
69139beb93cSSam Leffler 	char buf[MULTICAST_MAX_READ], *pos;
69239beb93cSSam Leffler 
69339beb93cSSam Leffler 	addr_len = sizeof(addr);
69439beb93cSSam Leffler 	nread = recvfrom(sm->ssdp_sd, buf, sizeof(buf) - 1, 0,
69539beb93cSSam Leffler 			 (struct sockaddr *) &addr, &addr_len);
69639beb93cSSam Leffler 	if (nread <= 0)
69739beb93cSSam Leffler 		return;
69839beb93cSSam Leffler 	buf[nread] = '\0'; /* need null termination for algorithm */
69939beb93cSSam Leffler 
70039beb93cSSam Leffler 	if (str_starts(buf, "NOTIFY ")) {
70139beb93cSSam Leffler 		/*
70239beb93cSSam Leffler 		 * Silently ignore NOTIFYs to avoid filling debug log with
70339beb93cSSam Leffler 		 * unwanted messages.
70439beb93cSSam Leffler 		 */
70539beb93cSSam Leffler 		return;
70639beb93cSSam Leffler 	}
70739beb93cSSam Leffler 
70839beb93cSSam Leffler 	pos = os_strchr(buf, '\n');
70939beb93cSSam Leffler 	if (pos)
71039beb93cSSam Leffler 		*pos = '\0';
71139beb93cSSam Leffler 	wpa_printf(MSG_MSGDUMP, "WPS UPnP: Received SSDP packet from %s:%d: "
71239beb93cSSam Leffler 		   "%s", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), buf);
71339beb93cSSam Leffler 	if (pos)
71439beb93cSSam Leffler 		*pos = '\n';
71539beb93cSSam Leffler 
71639beb93cSSam Leffler 	/* Parse first line */
71739beb93cSSam Leffler 	if (os_strncasecmp(buf, "M-SEARCH", os_strlen("M-SEARCH")) == 0 &&
71839beb93cSSam Leffler 	    !isgraph(buf[strlen("M-SEARCH")])) {
71939beb93cSSam Leffler 		ssdp_parse_msearch(sm, &addr, buf);
72039beb93cSSam Leffler 		return;
72139beb93cSSam Leffler 	}
72239beb93cSSam Leffler 
72339beb93cSSam Leffler 	/* Ignore anything else */
72439beb93cSSam Leffler }
72539beb93cSSam Leffler 
72639beb93cSSam Leffler 
ssdp_listener_open(void)727e28a4053SRui Paulo int ssdp_listener_open(void)
72839beb93cSSam Leffler {
72939beb93cSSam Leffler 	struct sockaddr_in addr;
73039beb93cSSam Leffler 	struct ip_mreq mcast_addr;
73139beb93cSSam Leffler 	int on = 1;
73239beb93cSSam Leffler 	/* per UPnP spec, keep IP packet time to live (TTL) small */
73339beb93cSSam Leffler 	unsigned char ttl = 4;
734e28a4053SRui Paulo 	int sd;
73539beb93cSSam Leffler 
736e28a4053SRui Paulo 	sd = socket(AF_INET, SOCK_DGRAM, 0);
737*325151a3SRui Paulo 	if (sd < 0 ||
738*325151a3SRui Paulo 	    fcntl(sd, F_SETFL, O_NONBLOCK) != 0 ||
739*325151a3SRui Paulo 	    setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
74039beb93cSSam Leffler 		goto fail;
74139beb93cSSam Leffler 	os_memset(&addr, 0, sizeof(addr));
74239beb93cSSam Leffler 	addr.sin_family = AF_INET;
74339beb93cSSam Leffler 	addr.sin_addr.s_addr = htonl(INADDR_ANY);
74439beb93cSSam Leffler 	addr.sin_port = htons(UPNP_MULTICAST_PORT);
74539beb93cSSam Leffler 	if (bind(sd, (struct sockaddr *) &addr, sizeof(addr)))
74639beb93cSSam Leffler 		goto fail;
74739beb93cSSam Leffler 	os_memset(&mcast_addr, 0, sizeof(mcast_addr));
74839beb93cSSam Leffler 	mcast_addr.imr_interface.s_addr = htonl(INADDR_ANY);
74939beb93cSSam Leffler 	mcast_addr.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
75039beb93cSSam Leffler 	if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
751*325151a3SRui Paulo 		       (char *) &mcast_addr, sizeof(mcast_addr)) ||
752*325151a3SRui Paulo 	    setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
75339beb93cSSam Leffler 		       &ttl, sizeof(ttl)))
75439beb93cSSam Leffler 		goto fail;
755e28a4053SRui Paulo 
756e28a4053SRui Paulo 	return sd;
757e28a4053SRui Paulo 
758e28a4053SRui Paulo fail:
759e28a4053SRui Paulo 	if (sd >= 0)
760e28a4053SRui Paulo 		close(sd);
761e28a4053SRui Paulo 	return -1;
762e28a4053SRui Paulo }
763e28a4053SRui Paulo 
764e28a4053SRui Paulo 
765e28a4053SRui Paulo /**
766e28a4053SRui Paulo  * ssdp_listener_start - Set up for receiving discovery (UDP) packets
767e28a4053SRui Paulo  * @sm: WPS UPnP state machine from upnp_wps_device_init()
768e28a4053SRui Paulo  * Returns: 0 on success, -1 on failure
769e28a4053SRui Paulo  *
770e28a4053SRui Paulo  * The SSDP listener is stopped by calling ssdp_listener_stop().
771e28a4053SRui Paulo  */
ssdp_listener_start(struct upnp_wps_device_sm * sm)772e28a4053SRui Paulo int ssdp_listener_start(struct upnp_wps_device_sm *sm)
773e28a4053SRui Paulo {
774e28a4053SRui Paulo 	sm->ssdp_sd = ssdp_listener_open();
775e28a4053SRui Paulo 
776e28a4053SRui Paulo 	if (eloop_register_sock(sm->ssdp_sd, EVENT_TYPE_READ,
777e28a4053SRui Paulo 				ssdp_listener_handler, NULL, sm))
77839beb93cSSam Leffler 		goto fail;
77939beb93cSSam Leffler 	sm->ssdp_sd_registered = 1;
78039beb93cSSam Leffler 	return 0;
78139beb93cSSam Leffler 
78239beb93cSSam Leffler fail:
78339beb93cSSam Leffler 	/* Error */
78439beb93cSSam Leffler 	wpa_printf(MSG_ERROR, "WPS UPnP: ssdp_listener_start failed");
78539beb93cSSam Leffler 	ssdp_listener_stop(sm);
78639beb93cSSam Leffler 	return -1;
78739beb93cSSam Leffler }
78839beb93cSSam Leffler 
78939beb93cSSam Leffler 
79039beb93cSSam Leffler /**
79139beb93cSSam Leffler  * add_ssdp_network - Add routing entry for SSDP
79239beb93cSSam Leffler  * @net_if: Selected network interface name
79339beb93cSSam Leffler  * Returns: 0 on success, -1 on failure
79439beb93cSSam Leffler  *
79539beb93cSSam Leffler  * This function assures that the multicast address will be properly
79639beb93cSSam Leffler  * handled by Linux networking code (by a modification to routing tables).
79739beb93cSSam Leffler  * This must be done per network interface. It really only needs to be done
79839beb93cSSam Leffler  * once after booting up, but it does not hurt to call this more frequently
79939beb93cSSam Leffler  * "to be safe".
80039beb93cSSam Leffler  */
add_ssdp_network(const char * net_if)801e28a4053SRui Paulo int add_ssdp_network(const char *net_if)
80239beb93cSSam Leffler {
8033157ba21SRui Paulo #ifdef __linux__
80439beb93cSSam Leffler 	int ret = -1;
80539beb93cSSam Leffler 	int sock = -1;
80639beb93cSSam Leffler 	struct rtentry rt;
80739beb93cSSam Leffler 	struct sockaddr_in *sin;
80839beb93cSSam Leffler 
80939beb93cSSam Leffler 	if (!net_if)
81039beb93cSSam Leffler 		goto fail;
81139beb93cSSam Leffler 
81239beb93cSSam Leffler 	os_memset(&rt, 0, sizeof(rt));
81339beb93cSSam Leffler 	sock = socket(AF_INET, SOCK_DGRAM, 0);
81439beb93cSSam Leffler 	if (sock < 0)
81539beb93cSSam Leffler 		goto fail;
81639beb93cSSam Leffler 
817e28a4053SRui Paulo 	rt.rt_dev = (char *) net_if;
8183157ba21SRui Paulo 	sin = aliasing_hide_typecast(&rt.rt_dst, struct sockaddr_in);
81939beb93cSSam Leffler 	sin->sin_family = AF_INET;
82039beb93cSSam Leffler 	sin->sin_port = 0;
82139beb93cSSam Leffler 	sin->sin_addr.s_addr = inet_addr(SSDP_TARGET);
8223157ba21SRui Paulo 	sin = aliasing_hide_typecast(&rt.rt_genmask, struct sockaddr_in);
82339beb93cSSam Leffler 	sin->sin_family = AF_INET;
82439beb93cSSam Leffler 	sin->sin_port = 0;
82539beb93cSSam Leffler 	sin->sin_addr.s_addr = inet_addr(SSDP_NETMASK);
82639beb93cSSam Leffler 	rt.rt_flags = RTF_UP;
82739beb93cSSam Leffler 	if (ioctl(sock, SIOCADDRT, &rt) < 0) {
82839beb93cSSam Leffler 		if (errno == EPERM) {
82939beb93cSSam Leffler 			wpa_printf(MSG_DEBUG, "add_ssdp_network: No "
83039beb93cSSam Leffler 				   "permissions to add routing table entry");
83139beb93cSSam Leffler 			/* Continue to allow testing as non-root */
83239beb93cSSam Leffler 		} else if (errno != EEXIST) {
83339beb93cSSam Leffler 			wpa_printf(MSG_INFO, "add_ssdp_network() ioctl errno "
83439beb93cSSam Leffler 				   "%d (%s)", errno, strerror(errno));
83539beb93cSSam Leffler 			goto fail;
83639beb93cSSam Leffler 		}
83739beb93cSSam Leffler 	}
83839beb93cSSam Leffler 
83939beb93cSSam Leffler 	ret = 0;
84039beb93cSSam Leffler 
84139beb93cSSam Leffler fail:
84239beb93cSSam Leffler 	if (sock >= 0)
84339beb93cSSam Leffler 		close(sock);
84439beb93cSSam Leffler 
84539beb93cSSam Leffler 	return ret;
8463157ba21SRui Paulo #else /* __linux__ */
8473157ba21SRui Paulo 	return 0;
8483157ba21SRui Paulo #endif /* __linux__ */
84939beb93cSSam Leffler }
85039beb93cSSam Leffler 
85139beb93cSSam Leffler 
ssdp_open_multicast_sock(u32 ip_addr,const char * forced_ifname)8525b9c547cSRui Paulo int ssdp_open_multicast_sock(u32 ip_addr, const char *forced_ifname)
85339beb93cSSam Leffler {
854e28a4053SRui Paulo 	int sd;
85539beb93cSSam Leffler 	 /* per UPnP-arch-DeviceArchitecture, 1. Discovery, keep IP packet
85639beb93cSSam Leffler 	  * time to live (TTL) small */
85739beb93cSSam Leffler 	unsigned char ttl = 4;
85839beb93cSSam Leffler 
859e28a4053SRui Paulo 	sd = socket(AF_INET, SOCK_DGRAM, 0);
86039beb93cSSam Leffler 	if (sd < 0)
86139beb93cSSam Leffler 		return -1;
86239beb93cSSam Leffler 
8635b9c547cSRui Paulo 	if (forced_ifname) {
8645b9c547cSRui Paulo #ifdef __linux__
8655b9c547cSRui Paulo 		struct ifreq req;
8665b9c547cSRui Paulo 		os_memset(&req, 0, sizeof(req));
8675b9c547cSRui Paulo 		os_strlcpy(req.ifr_name, forced_ifname, sizeof(req.ifr_name));
8685b9c547cSRui Paulo 		if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &req,
8695b9c547cSRui Paulo 			       sizeof(req)) < 0) {
8705b9c547cSRui Paulo 			wpa_printf(MSG_INFO, "WPS UPnP: Failed to bind "
8715b9c547cSRui Paulo 				   "multicast socket to ifname %s: %s",
8725b9c547cSRui Paulo 				   forced_ifname, strerror(errno));
8735b9c547cSRui Paulo 			close(sd);
8745b9c547cSRui Paulo 			return -1;
8755b9c547cSRui Paulo 		}
8765b9c547cSRui Paulo #endif /* __linux__ */
8775b9c547cSRui Paulo 	}
8785b9c547cSRui Paulo 
87939beb93cSSam Leffler #if 0   /* maybe ok if we sometimes block on writes */
880f05cddf9SRui Paulo 	if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0) {
881f05cddf9SRui Paulo 		close(sd);
88239beb93cSSam Leffler 		return -1;
883f05cddf9SRui Paulo 	}
88439beb93cSSam Leffler #endif
88539beb93cSSam Leffler 
88639beb93cSSam Leffler 	if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF,
887f05cddf9SRui Paulo 		       &ip_addr, sizeof(ip_addr))) {
888f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "WPS: setsockopt(IP_MULTICAST_IF) %x: "
889f05cddf9SRui Paulo 			   "%d (%s)", ip_addr, errno, strerror(errno));
890f05cddf9SRui Paulo 		close(sd);
89139beb93cSSam Leffler 		return -1;
892f05cddf9SRui Paulo 	}
89339beb93cSSam Leffler 	if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
894f05cddf9SRui Paulo 		       &ttl, sizeof(ttl))) {
895f05cddf9SRui Paulo 		wpa_printf(MSG_DEBUG, "WPS: setsockopt(IP_MULTICAST_TTL): "
896f05cddf9SRui Paulo 			   "%d (%s)", errno, strerror(errno));
897f05cddf9SRui Paulo 		close(sd);
89839beb93cSSam Leffler 		return -1;
899f05cddf9SRui Paulo 	}
90039beb93cSSam Leffler 
90139beb93cSSam Leffler #if 0   /* not needed, because we don't receive using multicast_sd */
90239beb93cSSam Leffler 	{
90339beb93cSSam Leffler 		struct ip_mreq mreq;
90439beb93cSSam Leffler 		mreq.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
905e28a4053SRui Paulo 		mreq.imr_interface.s_addr = ip_addr;
90639beb93cSSam Leffler 		wpa_printf(MSG_DEBUG, "WPS UPnP: Multicast addr 0x%x if addr "
90739beb93cSSam Leffler 			   "0x%x",
90839beb93cSSam Leffler 			   mreq.imr_multiaddr.s_addr,
90939beb93cSSam Leffler 			   mreq.imr_interface.s_addr);
91039beb93cSSam Leffler 		if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
91139beb93cSSam Leffler 				sizeof(mreq))) {
91239beb93cSSam Leffler 			wpa_printf(MSG_ERROR,
91339beb93cSSam Leffler 				   "WPS UPnP: setsockopt "
91439beb93cSSam Leffler 				   "IP_ADD_MEMBERSHIP errno %d (%s)",
91539beb93cSSam Leffler 				   errno, strerror(errno));
916f05cddf9SRui Paulo 			close(sd);
91739beb93cSSam Leffler 			return -1;
91839beb93cSSam Leffler 		}
91939beb93cSSam Leffler 	}
92039beb93cSSam Leffler #endif  /* not needed */
92139beb93cSSam Leffler 
92239beb93cSSam Leffler 	/*
92339beb93cSSam Leffler 	 * TODO: What about IP_MULTICAST_LOOP? It seems to be on by default?
92439beb93cSSam Leffler 	 * which aids debugging I suppose but isn't really necessary?
92539beb93cSSam Leffler 	 */
92639beb93cSSam Leffler 
927e28a4053SRui Paulo 	return sd;
928e28a4053SRui Paulo }
929e28a4053SRui Paulo 
930e28a4053SRui Paulo 
931e28a4053SRui Paulo /**
932e28a4053SRui Paulo  * ssdp_open_multicast - Open socket for sending multicast SSDP messages
933e28a4053SRui Paulo  * @sm: WPS UPnP state machine from upnp_wps_device_init()
934e28a4053SRui Paulo  * Returns: 0 on success, -1 on failure
935e28a4053SRui Paulo  */
ssdp_open_multicast(struct upnp_wps_device_sm * sm)936e28a4053SRui Paulo int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
937e28a4053SRui Paulo {
9385b9c547cSRui Paulo 	sm->multicast_sd = ssdp_open_multicast_sock(sm->ip_addr, NULL);
939e28a4053SRui Paulo 	if (sm->multicast_sd < 0)
940e28a4053SRui Paulo 		return -1;
94139beb93cSSam Leffler 	return 0;
94239beb93cSSam Leffler }
943