1189251Ssam/*
2189251Ssam * UPnP WPS Device
3189251Ssam * Copyright (c) 2000-2003 Intel Corporation
4189251Ssam * Copyright (c) 2006-2007 Sony Corporation
5189251Ssam * Copyright (c) 2008-2009 Atheros Communications
6252726Srpaulo * Copyright (c) 2009-2010, Jouni Malinen <j@w1.fi>
7189251Ssam *
8189251Ssam * See below for more details on licensing and code history.
9189251Ssam */
10189251Ssam
11189251Ssam/*
12189251Ssam * This has been greatly stripped down from the original file
13189251Ssam * (upnp_wps_device.c) by Ted Merrill, Atheros Communications
14189251Ssam * in order to eliminate use of the bulky libupnp library etc.
15189251Ssam *
16189251Ssam * History:
17189251Ssam * upnp_wps_device.c is/was a shim layer between wps_opt_upnp.c and
18189251Ssam * the libupnp library.
19189251Ssam * The layering (by Sony) was well done; only a very minor modification
20189251Ssam * to API of upnp_wps_device.c was required.
21189251Ssam * libupnp was found to be undesirable because:
22189251Ssam * -- It consumed too much code and data space
23189251Ssam * -- It uses multiple threads, making debugging more difficult
24189251Ssam *      and possibly reducing reliability.
25189251Ssam * -- It uses static variables and only supports one instance.
26189251Ssam * The shim and libupnp are here replaced by special code written
27189251Ssam * specifically for the needs of hostapd.
28189251Ssam * Various shortcuts can and are taken to keep the code size small.
29189251Ssam * Generally, execution time is not as crucial.
30189251Ssam *
31189251Ssam * BUGS:
32189251Ssam * -- UPnP requires that we be able to resolve domain names.
33189251Ssam * While uncommon, if we have to do it then it will stall the entire
34189251Ssam * hostapd program, which is bad.
35189251Ssam * This is because we use the standard linux getaddrinfo() function
36189251Ssam * which is syncronous.
37189251Ssam * An asyncronous solution would be to use the free "ares" library.
38189251Ssam * -- Does not have a robust output buffering scheme.  Uses a single
39189251Ssam * fixed size output buffer per TCP/HTTP connection, with possible (although
40189251Ssam * unlikely) possibility of overflow and likely excessive use of RAM.
41189251Ssam * A better solution would be to write the HTTP output as a buffered stream,
42189251Ssam * using chunking: (handle header specially, then) generate data with
43189251Ssam * a printf-like function into a buffer, catching buffer full condition,
44189251Ssam * then send it out surrounded by http chunking.
45189251Ssam * -- There is some code that could be separated out into the common
46189251Ssam * library to be shared with wpa_supplicant.
47189251Ssam * -- Needs renaming with module prefix to avoid polluting the debugger
48189251Ssam * namespace and causing possible collisions with other static fncs
49189251Ssam * and structure declarations when using the debugger.
50252726Srpaulo * -- The http error code generation is pretty bogus, hopefully no one cares.
51189251Ssam *
52189251Ssam * Author: Ted Merrill, Atheros Communications, based upon earlier work
53189251Ssam * as explained above and below.
54189251Ssam *
55189251Ssam * Copyright:
56189251Ssam * Copyright 2008 Atheros Communications.
57189251Ssam *
58189251Ssam * The original header (of upnp_wps_device.c) reads:
59189251Ssam *
60189251Ssam *  Copyright (c) 2006-2007 Sony Corporation. All Rights Reserved.
61189251Ssam *
62189251Ssam *  File Name: upnp_wps_device.c
63189251Ssam *  Description: EAP-WPS UPnP device source
64189251Ssam *
65189251Ssam *   Redistribution and use in source and binary forms, with or without
66189251Ssam *   modification, are permitted provided that the following conditions
67189251Ssam *   are met:
68189251Ssam *
69189251Ssam *     * Redistributions of source code must retain the above copyright
70189251Ssam *       notice, this list of conditions and the following disclaimer.
71189251Ssam *     * Redistributions in binary form must reproduce the above copyright
72189251Ssam *       notice, this list of conditions and the following disclaimer in
73189251Ssam *       the documentation and/or other materials provided with the
74189251Ssam *       distribution.
75189251Ssam *     * Neither the name of Sony Corporation nor the names of its
76189251Ssam *       contributors may be used to endorse or promote products derived
77189251Ssam *       from this software without specific prior written permission.
78189251Ssam *
79189251Ssam *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
80189251Ssam *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
81189251Ssam *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
82189251Ssam *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
83189251Ssam *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
84189251Ssam *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
85189251Ssam *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
86189251Ssam *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
87189251Ssam *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
88189251Ssam *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
89189251Ssam *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
90189251Ssam *
91189251Ssam * Portions from Intel libupnp files, e.g. genlib/net/http/httpreadwrite.c
92189251Ssam * typical header:
93189251Ssam *
94189251Ssam * Copyright (c) 2000-2003 Intel Corporation
95189251Ssam * All rights reserved.
96189251Ssam *
97189251Ssam * Redistribution and use in source and binary forms, with or without
98189251Ssam * modification, are permitted provided that the following conditions are met:
99189251Ssam *
100189251Ssam * * Redistributions of source code must retain the above copyright notice,
101189251Ssam * this list of conditions and the following disclaimer.
102189251Ssam * * Redistributions in binary form must reproduce the above copyright notice,
103189251Ssam * this list of conditions and the following disclaimer in the documentation
104189251Ssam * and/or other materials provided with the distribution.
105189251Ssam * * Neither name of Intel Corporation nor the names of its contributors
106189251Ssam * may be used to endorse or promote products derived from this software
107189251Ssam * without specific prior written permission.
108189251Ssam *
109189251Ssam * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
110189251Ssam * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
111189251Ssam * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
112189251Ssam * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
113189251Ssam * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
114189251Ssam * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
115189251Ssam * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
116189251Ssam * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
117189251Ssam * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
118189251Ssam * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
119189251Ssam * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
120189251Ssam*/
121189251Ssam
122189251Ssam/*
123189251Ssam * Overview of WPS over UPnP:
124189251Ssam *
125189251Ssam * UPnP is a protocol that allows devices to discover each other and control
126189251Ssam * each other. In UPnP terminology, a device is either a "device" (a server
127189251Ssam * that provides information about itself and allows itself to be controlled)
128189251Ssam * or a "control point" (a client that controls "devices") or possibly both.
129189251Ssam * This file implements a UPnP "device".
130189251Ssam *
131189251Ssam * For us, we use mostly basic UPnP discovery, but the control part of interest
132189251Ssam * is WPS carried via UPnP messages. There is quite a bit of basic UPnP
133189251Ssam * discovery to do before we can get to WPS, however.
134189251Ssam *
135189251Ssam * UPnP discovery begins with "devices" send out multicast UDP packets to a
136189251Ssam * certain fixed multicast IP address and port, and "control points" sending
137189251Ssam * out other such UDP packets.
138189251Ssam *
139189251Ssam * The packets sent by devices are NOTIFY packets (not to be confused with TCP
140189251Ssam * NOTIFY packets that are used later) and those sent by control points are
141189251Ssam * M-SEARCH packets. These packets contain a simple HTTP style header. The
142189251Ssam * packets are sent redundantly to get around packet loss. Devices respond to
143189251Ssam * M-SEARCH packets with HTTP-like UDP packets containing HTTP/1.1 200 OK
144189251Ssam * messages, which give similar information as the UDP NOTIFY packets.
145189251Ssam *
146189251Ssam * The above UDP packets advertise the (arbitrary) TCP ports that the
147189251Ssam * respective parties will listen to. The control point can then do a HTTP
148189251Ssam * SUBSCRIBE (something like an HTTP PUT) after which the device can do a
149189251Ssam * separate HTTP NOTIFY (also like an HTTP PUT) to do event messaging.
150189251Ssam *
151189251Ssam * The control point will also do HTTP GET of the "device file" listed in the
152189251Ssam * original UDP information from the device (see UPNP_WPS_DEVICE_XML_FILE
153189251Ssam * data), and based on this will do additional GETs... HTTP POSTs are done to
154189251Ssam * cause an action.
155189251Ssam *
156189251Ssam * Beyond some basic information in HTTP headers, additional information is in
157189251Ssam * the HTTP bodies, in a format set by the SOAP and XML standards, a markup
158189251Ssam * language related to HTML used for web pages. This language is intended to
159189251Ssam * provide the ultimate in self-documentation by providing a universal
160189251Ssam * namespace based on pseudo-URLs called URIs. Note that although a URI looks
161189251Ssam * like a URL (a web address), they are never accessed as such but are used
162189251Ssam * only as identifiers.
163189251Ssam *
164189251Ssam * The POST of a GetDeviceInfo gets information similar to what might be
165189251Ssam * obtained from a probe request or response on Wi-Fi. WPS messages M1-M8
166189251Ssam * are passed via a POST of a PutMessage; the M1-M8 WPS messages are converted
167189251Ssam * to a bin64 ascii representation for encapsulation. When proxying messages,
168189251Ssam * WLANEvent and PutWLANResponse are used.
169189251Ssam *
170189251Ssam * This of course glosses over a lot of details.
171189251Ssam */
172189251Ssam
173189251Ssam#include "includes.h"
174189251Ssam
175252726Srpaulo#include <time.h>
176189251Ssam#include <net/if.h>
177189251Ssam#include <netdb.h>
178189251Ssam#include <sys/ioctl.h>
179189251Ssam
180189251Ssam#include "common.h"
181189251Ssam#include "uuid.h"
182189251Ssam#include "base64.h"
183189251Ssam#include "wps.h"
184189251Ssam#include "wps_i.h"
185189251Ssam#include "wps_upnp.h"
186189251Ssam#include "wps_upnp_i.h"
187189251Ssam
188189251Ssam
189189251Ssam/*
190189251Ssam * UPnP allows a client ("control point") to send a server like us ("device")
191189251Ssam * a domain name for registration, and we are supposed to resolve it. This is
192189251Ssam * bad because, using the standard Linux library, we will stall the entire
193189251Ssam * hostapd waiting for resolution.
194189251Ssam *
195189251Ssam * The "correct" solution would be to use an event driven library for domain
196189251Ssam * name resolution such as "ares". However, this would increase code size
197189251Ssam * further. Since it is unlikely that we'll actually see such domain names, we
198189251Ssam * can just refuse to accept them.
199189251Ssam */
200189251Ssam#define NO_DOMAIN_NAME_RESOLUTION 1  /* 1 to allow only dotted ip addresses */
201189251Ssam
202189251Ssam
203189251Ssam/*
204189251Ssam * UPnP does not scale well. If we were in a room with thousands of control
205189251Ssam * points then potentially we could be expected to handle subscriptions for
206189251Ssam * each of them, which would exhaust our memory. So we must set a limit. In
207189251Ssam * practice we are unlikely to see more than one or two.
208189251Ssam */
209189251Ssam#define MAX_SUBSCRIPTIONS 4    /* how many subscribing clients we handle */
210189251Ssam#define MAX_ADDR_PER_SUBSCRIPTION 8
211189251Ssam
212252726Srpaulo/* Maximum number of Probe Request events per second */
213252726Srpaulo#define MAX_EVENTS_PER_SEC 5
214189251Ssam
215252726Srpaulo
216252726Srpaulostatic struct upnp_wps_device_sm *shared_upnp_device = NULL;
217252726Srpaulo
218252726Srpaulo
219189251Ssam/* Write the current date/time per RFC */
220189251Ssamvoid format_date(struct wpabuf *buf)
221189251Ssam{
222189251Ssam	const char *weekday_str = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
223189251Ssam	const char *month_str = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
224189251Ssam		"Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
225189251Ssam	struct tm *date;
226189251Ssam	time_t t;
227189251Ssam
228189251Ssam	t = time(NULL);
229189251Ssam	date = gmtime(&t);
230189251Ssam	wpabuf_printf(buf, "%s, %02d %s %d %02d:%02d:%02d GMT",
231189251Ssam		      &weekday_str[date->tm_wday * 4], date->tm_mday,
232189251Ssam		      &month_str[date->tm_mon * 4], date->tm_year + 1900,
233189251Ssam		      date->tm_hour, date->tm_min, date->tm_sec);
234189251Ssam}
235189251Ssam
236189251Ssam
237189251Ssam/***************************************************************************
238189251Ssam * UUIDs (unique identifiers)
239189251Ssam *
240189251Ssam * These are supposed to be unique in all the world.
241189251Ssam * Sometimes permanent ones are used, sometimes temporary ones
242189251Ssam * based on random numbers... there are different rules for valid content
243189251Ssam * of different types.
244189251Ssam * Each uuid is 16 bytes long.
245189251Ssam **************************************************************************/
246189251Ssam
247189251Ssam/* uuid_make -- construct a random UUID
248189251Ssam * The UPnP documents don't seem to offer any guidelines as to which method to
249189251Ssam * use for constructing UUIDs for subscriptions. Presumably any method from
250189251Ssam * rfc4122 is good enough; I've chosen random number method.
251189251Ssam */
252189251Ssamstatic void uuid_make(u8 uuid[UUID_LEN])
253189251Ssam{
254189251Ssam	os_get_random(uuid, UUID_LEN);
255189251Ssam
256189251Ssam	/* Replace certain bits as specified in rfc4122 or X.667 */
257189251Ssam	uuid[6] &= 0x0f; uuid[6] |= (4 << 4);   /* version 4 == random gen */
258189251Ssam	uuid[8] &= 0x3f; uuid[8] |= 0x80;
259189251Ssam}
260189251Ssam
261189251Ssam
262189251Ssam/*
263189251Ssam * Subscriber address handling.
264189251Ssam * Since a subscriber may have an arbitrary number of addresses, we have to
265189251Ssam * add a bunch of code to handle them.
266189251Ssam *
267189251Ssam * Addresses are passed in text, and MAY be domain names instead of the (usual
268189251Ssam * and expected) dotted IP addresses. Resolving domain names consumes a lot of
269189251Ssam * resources. Worse, we are currently using the standard Linux getaddrinfo()
270189251Ssam * which will block the entire program until complete or timeout! The proper
271189251Ssam * solution would be to use the "ares" library or similar with more state
272189251Ssam * machine steps etc. or just disable domain name resolution by setting
273189251Ssam * NO_DOMAIN_NAME_RESOLUTION to 1 at top of this file.
274189251Ssam */
275189251Ssam
276189251Ssam/* subscr_addr_delete -- delete single unlinked subscriber address
277189251Ssam * (be sure to unlink first if need be)
278189251Ssam */
279252726Srpaulovoid subscr_addr_delete(struct subscr_addr *a)
280189251Ssam{
281189251Ssam	/*
282189251Ssam	 * Note: do NOT free domain_and_port or path because they point to
283189251Ssam	 * memory within the allocation of "a".
284189251Ssam	 */
285189251Ssam	os_free(a);
286189251Ssam}
287189251Ssam
288189251Ssam
289189251Ssam/* subscr_addr_free_all -- unlink and delete list of subscriber addresses. */
290189251Ssamstatic void subscr_addr_free_all(struct subscription *s)
291189251Ssam{
292214734Srpaulo	struct subscr_addr *a, *tmp;
293214734Srpaulo	dl_list_for_each_safe(a, tmp, &s->addr_list, struct subscr_addr, list)
294214734Srpaulo	{
295214734Srpaulo		dl_list_del(&a->list);
296189251Ssam		subscr_addr_delete(a);
297189251Ssam	}
298189251Ssam}
299189251Ssam
300189251Ssam
301189251Ssam/* subscr_addr_add_url -- add address(es) for one url to subscription */
302252726Srpaulostatic void subscr_addr_add_url(struct subscription *s, const char *url,
303252726Srpaulo				size_t url_len)
304189251Ssam{
305189251Ssam	int alloc_len;
306189251Ssam	char *scratch_mem = NULL;
307189251Ssam	char *mem;
308252726Srpaulo	char *host;
309189251Ssam	char *delim;
310189251Ssam	char *path;
311189251Ssam	int port = 80;  /* port to send to (default is port 80) */
312189251Ssam	struct addrinfo hints;
313189251Ssam	struct addrinfo *result = NULL;
314189251Ssam	struct addrinfo *rp;
315189251Ssam	int rerr;
316252726Srpaulo	size_t host_len, path_len;
317189251Ssam
318189251Ssam	/* url MUST begin with http: */
319252726Srpaulo	if (url_len < 7 || os_strncasecmp(url, "http://", 7))
320189251Ssam		goto fail;
321189251Ssam	url += 7;
322252726Srpaulo	url_len -= 7;
323189251Ssam
324252726Srpaulo	/* Make a copy of the string to allow modification during parsing */
325252726Srpaulo	scratch_mem = os_malloc(url_len + 1);
326189251Ssam	if (scratch_mem == NULL)
327189251Ssam		goto fail;
328252726Srpaulo	os_memcpy(scratch_mem, url, url_len);
329252726Srpaulo	scratch_mem[url_len] = '\0';
330252726Srpaulo	wpa_printf(MSG_DEBUG, "WPS UPnP: Adding URL '%s'", scratch_mem);
331252726Srpaulo	host = scratch_mem;
332252726Srpaulo	path = os_strchr(host, '/');
333252726Srpaulo	if (path)
334252726Srpaulo		*path++ = '\0'; /* null terminate host */
335252726Srpaulo
336252726Srpaulo	/* Process and remove optional port component */
337252726Srpaulo	delim = os_strchr(host, ':');
338189251Ssam	if (delim) {
339252726Srpaulo		*delim = '\0'; /* null terminate host name for now */
340252726Srpaulo		if (isdigit(delim[1]))
341252726Srpaulo			port = atol(delim + 1);
342189251Ssam	}
343189251Ssam
344189251Ssam	/*
345189251Ssam	 * getaddrinfo does the right thing with dotted decimal notations, or
346189251Ssam	 * will resolve domain names. Resolving domain names will unfortunately
347189251Ssam	 * hang the entire program until it is resolved or it times out
348189251Ssam	 * internal to getaddrinfo; fortunately we think that the use of actual
349189251Ssam	 * domain names (vs. dotted decimal notations) should be uncommon.
350189251Ssam	 */
351189251Ssam	os_memset(&hints, 0, sizeof(struct addrinfo));
352189251Ssam	hints.ai_family = AF_INET;      /* IPv4 */
353189251Ssam	hints.ai_socktype = SOCK_STREAM;
354189251Ssam#if NO_DOMAIN_NAME_RESOLUTION
355189251Ssam	/* Suppress domain name resolutions that would halt
356189251Ssam	 * the program for periods of time
357189251Ssam	 */
358189251Ssam	hints.ai_flags = AI_NUMERICHOST;
359189251Ssam#else
360189251Ssam	/* Allow domain name resolution. */
361189251Ssam	hints.ai_flags = 0;
362189251Ssam#endif
363189251Ssam	hints.ai_protocol = 0;          /* Any protocol? */
364252726Srpaulo	rerr = getaddrinfo(host, NULL /* fill in port ourselves */,
365189251Ssam			   &hints, &result);
366189251Ssam	if (rerr) {
367189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Resolve error %d (%s) on: %s",
368252726Srpaulo			   rerr, gai_strerror(rerr), host);
369189251Ssam		goto fail;
370189251Ssam	}
371252726Srpaulo
372252726Srpaulo	if (delim)
373252726Srpaulo		*delim = ':'; /* Restore port */
374252726Srpaulo
375252726Srpaulo	host_len = os_strlen(host);
376252726Srpaulo	path_len = path ? os_strlen(path) : 0;
377252726Srpaulo	alloc_len = host_len + 1 + 1 + path_len + 1;
378252726Srpaulo
379189251Ssam	for (rp = result; rp; rp = rp->ai_next) {
380252726Srpaulo		struct subscr_addr *a;
381252726Srpaulo
382189251Ssam		/* Limit no. of address to avoid denial of service attack */
383214734Srpaulo		if (dl_list_len(&s->addr_list) >= MAX_ADDR_PER_SUBSCRIPTION) {
384189251Ssam			wpa_printf(MSG_INFO, "WPS UPnP: subscr_addr_add_url: "
385189251Ssam				   "Ignoring excessive addresses");
386189251Ssam			break;
387189251Ssam		}
388189251Ssam
389189251Ssam		a = os_zalloc(sizeof(*a) + alloc_len);
390189251Ssam		if (a == NULL)
391252726Srpaulo			break;
392252726Srpaulo		mem = (char *) (a + 1);
393189251Ssam		a->domain_and_port = mem;
394252726Srpaulo		os_memcpy(mem, host, host_len);
395252726Srpaulo		mem += host_len + 1;
396189251Ssam		a->path = mem;
397252726Srpaulo		if (path == NULL || path[0] != '/')
398189251Ssam			*mem++ = '/';
399252726Srpaulo		if (path)
400252726Srpaulo			os_memcpy(mem, path, path_len);
401189251Ssam		os_memcpy(&a->saddr, rp->ai_addr, sizeof(a->saddr));
402189251Ssam		a->saddr.sin_port = htons(port);
403189251Ssam
404214734Srpaulo		dl_list_add(&s->addr_list, &a->list);
405189251Ssam	}
406189251Ssam
407189251Ssamfail:
408189251Ssam	if (result)
409189251Ssam		freeaddrinfo(result);
410189251Ssam	os_free(scratch_mem);
411189251Ssam}
412189251Ssam
413189251Ssam
414189251Ssam/* subscr_addr_list_create -- create list from urls in string.
415189251Ssam *      Each url is enclosed by angle brackets.
416189251Ssam */
417189251Ssamstatic void subscr_addr_list_create(struct subscription *s,
418189251Ssam				    const char *url_list)
419189251Ssam{
420252726Srpaulo	const char *end;
421252726Srpaulo	wpa_printf(MSG_DEBUG, "WPS UPnP: Parsing URL list '%s'", url_list);
422189251Ssam	for (;;) {
423189251Ssam		while (*url_list == ' ' || *url_list == '\t')
424189251Ssam			url_list++;
425189251Ssam		if (*url_list != '<')
426189251Ssam			break;
427189251Ssam		url_list++;
428189251Ssam		end = os_strchr(url_list, '>');
429189251Ssam		if (end == NULL)
430189251Ssam			break;
431252726Srpaulo		subscr_addr_add_url(s, url_list, end - url_list);
432252726Srpaulo		url_list = end + 1;
433189251Ssam	}
434189251Ssam}
435189251Ssam
436189251Ssam
437189251Ssamint send_wpabuf(int fd, struct wpabuf *buf)
438189251Ssam{
439189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: Send %lu byte message",
440189251Ssam		   (unsigned long) wpabuf_len(buf));
441189251Ssam	errno = 0;
442189251Ssam	if (write(fd, wpabuf_head(buf), wpabuf_len(buf)) !=
443189251Ssam	    (int) wpabuf_len(buf)) {
444189251Ssam		wpa_printf(MSG_ERROR, "WPS UPnP: Failed to send buffer: "
445189251Ssam			   "errno=%d (%s)",
446189251Ssam			   errno, strerror(errno));
447189251Ssam		return -1;
448189251Ssam	}
449189251Ssam
450189251Ssam	return 0;
451189251Ssam}
452189251Ssam
453189251Ssam
454189251Ssamstatic void wpabuf_put_property(struct wpabuf *buf, const char *name,
455189251Ssam				const char *value)
456189251Ssam{
457189251Ssam	wpabuf_put_str(buf, "<e:property>");
458189251Ssam	wpabuf_printf(buf, "<%s>", name);
459189251Ssam	if (value)
460189251Ssam		wpabuf_put_str(buf, value);
461189251Ssam	wpabuf_printf(buf, "</%s>", name);
462189251Ssam	wpabuf_put_str(buf, "</e:property>\n");
463189251Ssam}
464189251Ssam
465189251Ssam
466189251Ssam/**
467189251Ssam * upnp_wps_device_send_event - Queue event messages for subscribers
468189251Ssam * @sm: WPS UPnP state machine from upnp_wps_device_init()
469189251Ssam *
470189251Ssam * This function queues the last WLANEvent to be sent for all currently
471189251Ssam * subscribed UPnP control points. sm->wlanevent must have been set with the
472189251Ssam * encoded data before calling this function.
473189251Ssam */
474189251Ssamstatic void upnp_wps_device_send_event(struct upnp_wps_device_sm *sm)
475189251Ssam{
476189251Ssam	/* Enqueue event message for all subscribers */
477189251Ssam	struct wpabuf *buf; /* holds event message */
478189251Ssam	int buf_size = 0;
479214734Srpaulo	struct subscription *s, *tmp;
480189251Ssam	/* Actually, utf-8 is the default, but it doesn't hurt to specify it */
481189251Ssam	const char *format_head =
482189251Ssam		"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
483189251Ssam		"<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n";
484189251Ssam	const char *format_tail = "</e:propertyset>\n";
485252726Srpaulo	struct os_time now;
486189251Ssam
487214734Srpaulo	if (dl_list_empty(&sm->subscriptions)) {
488189251Ssam		/* optimize */
489189251Ssam		return;
490189251Ssam	}
491189251Ssam
492252726Srpaulo	if (os_get_time(&now) == 0) {
493252726Srpaulo		if (now.sec != sm->last_event_sec) {
494252726Srpaulo			sm->last_event_sec = now.sec;
495252726Srpaulo			sm->num_events_in_sec = 1;
496252726Srpaulo		} else {
497252726Srpaulo			sm->num_events_in_sec++;
498252726Srpaulo			/*
499252726Srpaulo			 * In theory, this should apply to all WLANEvent
500252726Srpaulo			 * notifications, but EAP messages are of much higher
501252726Srpaulo			 * priority and Probe Request notifications should not
502252726Srpaulo			 * be allowed to drop EAP messages, so only throttle
503252726Srpaulo			 * Probe Request notifications.
504252726Srpaulo			 */
505252726Srpaulo			if (sm->num_events_in_sec > MAX_EVENTS_PER_SEC &&
506252726Srpaulo			    sm->wlanevent_type ==
507252726Srpaulo			    UPNP_WPS_WLANEVENT_TYPE_PROBE) {
508252726Srpaulo				wpa_printf(MSG_DEBUG, "WPS UPnP: Throttle "
509252726Srpaulo					   "event notifications (%u seen "
510252726Srpaulo					   "during one second)",
511252726Srpaulo					   sm->num_events_in_sec);
512252726Srpaulo				return;
513252726Srpaulo			}
514252726Srpaulo		}
515252726Srpaulo	}
516252726Srpaulo
517189251Ssam	/* Determine buffer size needed first */
518189251Ssam	buf_size += os_strlen(format_head);
519189251Ssam	buf_size += 50 + 2 * os_strlen("WLANEvent");
520189251Ssam	if (sm->wlanevent)
521189251Ssam		buf_size += os_strlen(sm->wlanevent);
522189251Ssam	buf_size += os_strlen(format_tail);
523189251Ssam
524189251Ssam	buf = wpabuf_alloc(buf_size);
525189251Ssam	if (buf == NULL)
526189251Ssam		return;
527189251Ssam	wpabuf_put_str(buf, format_head);
528189251Ssam	wpabuf_put_property(buf, "WLANEvent", sm->wlanevent);
529189251Ssam	wpabuf_put_str(buf, format_tail);
530189251Ssam
531189251Ssam	wpa_printf(MSG_MSGDUMP, "WPS UPnP: WLANEvent message:\n%s",
532189251Ssam		   (char *) wpabuf_head(buf));
533189251Ssam
534214734Srpaulo	dl_list_for_each_safe(s, tmp, &sm->subscriptions, struct subscription,
535214734Srpaulo			      list) {
536252726Srpaulo		event_add(s, buf,
537252726Srpaulo			  sm->wlanevent_type == UPNP_WPS_WLANEVENT_TYPE_PROBE);
538214734Srpaulo	}
539189251Ssam
540189251Ssam	wpabuf_free(buf);
541189251Ssam}
542189251Ssam
543189251Ssam
544189251Ssam/*
545189251Ssam * Event subscription (subscriber machines register with us to receive event
546189251Ssam * messages).
547189251Ssam * This is the result of an incoming HTTP over TCP SUBSCRIBE request.
548189251Ssam */
549189251Ssam
550189251Ssam/* subscription_destroy -- destroy an unlinked subscription
551189251Ssam * Be sure to unlink first if necessary.
552189251Ssam */
553189251Ssamvoid subscription_destroy(struct subscription *s)
554189251Ssam{
555252726Srpaulo	struct upnp_wps_device_interface *iface;
556189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: Destroy subscription %p", s);
557214734Srpaulo	subscr_addr_free_all(s);
558189251Ssam	event_delete_all(s);
559252726Srpaulo	dl_list_for_each(iface, &s->sm->interfaces,
560252726Srpaulo			 struct upnp_wps_device_interface, list)
561252726Srpaulo		upnp_er_remove_notification(iface->wps->registrar, s);
562189251Ssam	os_free(s);
563189251Ssam}
564189251Ssam
565189251Ssam
566189251Ssam/* subscription_list_age -- remove expired subscriptions */
567189251Ssamstatic void subscription_list_age(struct upnp_wps_device_sm *sm, time_t now)
568189251Ssam{
569214734Srpaulo	struct subscription *s, *tmp;
570214734Srpaulo	dl_list_for_each_safe(s, tmp, &sm->subscriptions,
571214734Srpaulo			      struct subscription, list) {
572214734Srpaulo		if (s->timeout_time > now)
573214734Srpaulo			break;
574189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: Removing aged subscription");
575214734Srpaulo		dl_list_del(&s->list);
576189251Ssam		subscription_destroy(s);
577189251Ssam	}
578189251Ssam}
579189251Ssam
580189251Ssam
581189251Ssam/* subscription_find -- return existing subscription matching uuid, if any
582189251Ssam * returns NULL if not found
583189251Ssam */
584189251Ssamstruct subscription * subscription_find(struct upnp_wps_device_sm *sm,
585189251Ssam					const u8 uuid[UUID_LEN])
586189251Ssam{
587214734Srpaulo	struct subscription *s;
588214734Srpaulo	dl_list_for_each(s, &sm->subscriptions, struct subscription, list) {
589189251Ssam		if (os_memcmp(s->uuid, uuid, UUID_LEN) == 0)
590189251Ssam			return s; /* Found match */
591214734Srpaulo	}
592189251Ssam	return NULL;
593189251Ssam}
594189251Ssam
595189251Ssam
596209158Srpaulostatic struct wpabuf * build_fake_wsc_ack(void)
597209158Srpaulo{
598209158Srpaulo	struct wpabuf *msg = wpabuf_alloc(100);
599209158Srpaulo	if (msg == NULL)
600209158Srpaulo		return NULL;
601209158Srpaulo	wpabuf_put_u8(msg, UPNP_WPS_WLANEVENT_TYPE_EAP);
602209158Srpaulo	wpabuf_put_str(msg, "00:00:00:00:00:00");
603214734Srpaulo	if (wps_build_version(msg) ||
604214734Srpaulo	    wps_build_msg_type(msg, WPS_WSC_ACK)) {
605214734Srpaulo		wpabuf_free(msg);
606214734Srpaulo		return NULL;
607214734Srpaulo	}
608209158Srpaulo	/* Enrollee Nonce */
609209158Srpaulo	wpabuf_put_be16(msg, ATTR_ENROLLEE_NONCE);
610209158Srpaulo	wpabuf_put_be16(msg, WPS_NONCE_LEN);
611209158Srpaulo	wpabuf_put(msg, WPS_NONCE_LEN);
612209158Srpaulo	/* Registrar Nonce */
613209158Srpaulo	wpabuf_put_be16(msg, ATTR_REGISTRAR_NONCE);
614209158Srpaulo	wpabuf_put_be16(msg, WPS_NONCE_LEN);
615209158Srpaulo	wpabuf_put(msg, WPS_NONCE_LEN);
616252726Srpaulo	wps_build_wfa_ext(msg, 0, NULL, 0);
617209158Srpaulo	return msg;
618209158Srpaulo}
619209158Srpaulo
620209158Srpaulo
621189251Ssam/* subscription_first_event -- send format/queue event that is automatically
622189251Ssam * sent on a new subscription.
623189251Ssam */
624189251Ssamstatic int subscription_first_event(struct subscription *s)
625189251Ssam{
626189251Ssam	/*
627189251Ssam	 * Actually, utf-8 is the default, but it doesn't hurt to specify it.
628189251Ssam	 *
629189251Ssam	 * APStatus is apparently a bit set,
630189251Ssam	 * 0x1 = configuration change (but is always set?)
631189251Ssam	 * 0x10 = ap is locked
632189251Ssam	 *
633189251Ssam	 * Per UPnP spec, we send out the last value of each variable, even
634189251Ssam	 * for WLANEvent, whatever it was.
635189251Ssam	 */
636189251Ssam	char *wlan_event;
637189251Ssam	struct wpabuf *buf;
638189251Ssam	int ap_status = 1;      /* TODO: add 0x10 if access point is locked */
639189251Ssam	const char *head =
640189251Ssam		"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
641189251Ssam		"<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n";
642189251Ssam	const char *tail = "</e:propertyset>\n";
643189251Ssam	char txt[10];
644252726Srpaulo	int ret;
645189251Ssam
646209158Srpaulo	if (s->sm->wlanevent == NULL) {
647209158Srpaulo		/*
648209158Srpaulo		 * There has been no events before the subscription. However,
649209158Srpaulo		 * UPnP device architecture specification requires all the
650209158Srpaulo		 * evented variables to be included, so generate a dummy event
651209158Srpaulo		 * for this particular case using a WSC_ACK and all-zeros
652209158Srpaulo		 * nonces. The ER (UPnP control point) will ignore this, but at
653209158Srpaulo		 * least it will learn that WLANEvent variable will be used in
654209158Srpaulo		 * event notifications in the future.
655209158Srpaulo		 */
656209158Srpaulo		struct wpabuf *msg;
657209158Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Use a fake WSC_ACK as the "
658209158Srpaulo			   "initial WLANEvent");
659209158Srpaulo		msg = build_fake_wsc_ack();
660209158Srpaulo		if (msg) {
661209158Srpaulo			s->sm->wlanevent = (char *)
662209158Srpaulo				base64_encode(wpabuf_head(msg),
663209158Srpaulo					      wpabuf_len(msg), NULL);
664209158Srpaulo			wpabuf_free(msg);
665209158Srpaulo		}
666209158Srpaulo	}
667209158Srpaulo
668189251Ssam	wlan_event = s->sm->wlanevent;
669189251Ssam	if (wlan_event == NULL || *wlan_event == '\0') {
670189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: WLANEvent not known for "
671189251Ssam			   "initial event message");
672189251Ssam		wlan_event = "";
673189251Ssam	}
674189251Ssam	buf = wpabuf_alloc(500 + os_strlen(wlan_event));
675189251Ssam	if (buf == NULL)
676252726Srpaulo		return -1;
677189251Ssam
678189251Ssam	wpabuf_put_str(buf, head);
679189251Ssam	wpabuf_put_property(buf, "STAStatus", "1");
680189251Ssam	os_snprintf(txt, sizeof(txt), "%d", ap_status);
681189251Ssam	wpabuf_put_property(buf, "APStatus", txt);
682189251Ssam	if (*wlan_event)
683189251Ssam		wpabuf_put_property(buf, "WLANEvent", wlan_event);
684189251Ssam	wpabuf_put_str(buf, tail);
685189251Ssam
686252726Srpaulo	ret = event_add(s, buf, 0);
687252726Srpaulo	if (ret) {
688189251Ssam		wpabuf_free(buf);
689252726Srpaulo		return ret;
690189251Ssam	}
691189251Ssam	wpabuf_free(buf);
692189251Ssam
693189251Ssam	return 0;
694189251Ssam}
695189251Ssam
696189251Ssam
697189251Ssam/**
698209158Srpaulo * subscription_start - Remember a UPnP control point to send events to.
699189251Ssam * @sm: WPS UPnP state machine from upnp_wps_device_init()
700209158Srpaulo * @callback_urls: Callback URLs
701189251Ssam * Returns: %NULL on error, or pointer to new subscription structure.
702189251Ssam */
703189251Ssamstruct subscription * subscription_start(struct upnp_wps_device_sm *sm,
704209158Srpaulo					 const char *callback_urls)
705189251Ssam{
706189251Ssam	struct subscription *s;
707189251Ssam	time_t now = time(NULL);
708189251Ssam	time_t expire = now + UPNP_SUBSCRIBE_SEC;
709189251Ssam
710189251Ssam	/* Get rid of expired subscriptions so we have room */
711189251Ssam	subscription_list_age(sm, now);
712189251Ssam
713189251Ssam	/* If too many subscriptions, remove oldest */
714214734Srpaulo	if (dl_list_len(&sm->subscriptions) >= MAX_SUBSCRIPTIONS) {
715214734Srpaulo		s = dl_list_first(&sm->subscriptions, struct subscription,
716214734Srpaulo				  list);
717189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Too many subscriptions, "
718189251Ssam			   "trashing oldest");
719214734Srpaulo		dl_list_del(&s->list);
720189251Ssam		subscription_destroy(s);
721189251Ssam	}
722189251Ssam
723189251Ssam	s = os_zalloc(sizeof(*s));
724189251Ssam	if (s == NULL)
725189251Ssam		return NULL;
726214734Srpaulo	dl_list_init(&s->addr_list);
727214734Srpaulo	dl_list_init(&s->event_queue);
728189251Ssam
729189251Ssam	s->sm = sm;
730189251Ssam	s->timeout_time = expire;
731189251Ssam	uuid_make(s->uuid);
732189251Ssam	subscr_addr_list_create(s, callback_urls);
733252726Srpaulo	if (dl_list_empty(&s->addr_list)) {
734252726Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: No valid callback URLs in "
735252726Srpaulo			   "'%s' - drop subscription", callback_urls);
736252726Srpaulo		subscription_destroy(s);
737252726Srpaulo		return NULL;
738252726Srpaulo	}
739252726Srpaulo
740189251Ssam	/* Add to end of list, since it has the highest expiration time */
741214734Srpaulo	dl_list_add_tail(&sm->subscriptions, &s->list);
742189251Ssam	/* Queue up immediate event message (our last event)
743189251Ssam	 * as required by UPnP spec.
744189251Ssam	 */
745189251Ssam	if (subscription_first_event(s)) {
746189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Dropping subscriber due to "
747189251Ssam			   "event backlog");
748214734Srpaulo		dl_list_del(&s->list);
749189251Ssam		subscription_destroy(s);
750189251Ssam		return NULL;
751189251Ssam	}
752189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription %p started with %s",
753189251Ssam		   s, callback_urls);
754189251Ssam	/* Schedule sending this */
755189251Ssam	event_send_all_later(sm);
756189251Ssam	return s;
757189251Ssam}
758189251Ssam
759189251Ssam
760189251Ssam/* subscription_renew -- find subscription and reset timeout */
761189251Ssamstruct subscription * subscription_renew(struct upnp_wps_device_sm *sm,
762189251Ssam					 const u8 uuid[UUID_LEN])
763189251Ssam{
764189251Ssam	time_t now = time(NULL);
765189251Ssam	time_t expire = now + UPNP_SUBSCRIBE_SEC;
766189251Ssam	struct subscription *s = subscription_find(sm, uuid);
767189251Ssam	if (s == NULL)
768189251Ssam		return NULL;
769189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewed");
770214734Srpaulo	dl_list_del(&s->list);
771189251Ssam	s->timeout_time = expire;
772189251Ssam	/* add back to end of list, since it now has highest expiry */
773214734Srpaulo	dl_list_add_tail(&sm->subscriptions, &s->list);
774189251Ssam	return s;
775189251Ssam}
776189251Ssam
777189251Ssam
778189251Ssam/**
779189251Ssam * upnp_wps_device_send_wlan_event - Event notification
780189251Ssam * @sm: WPS UPnP state machine from upnp_wps_device_init()
781189251Ssam * @from_mac_addr: Source (Enrollee) MAC address for the event
782189251Ssam * @ev_type: Event type
783189251Ssam * @msg: Event data
784189251Ssam * Returns: 0 on success, -1 on failure
785189251Ssam *
786189251Ssam * Tell external Registrars (UPnP control points) that something happened. In
787189251Ssam * particular, events include WPS messages from clients that are proxied to
788189251Ssam * external Registrars.
789189251Ssam */
790189251Ssamint upnp_wps_device_send_wlan_event(struct upnp_wps_device_sm *sm,
791189251Ssam				    const u8 from_mac_addr[ETH_ALEN],
792189251Ssam				    enum upnp_wps_wlanevent_type ev_type,
793189251Ssam				    const struct wpabuf *msg)
794189251Ssam{
795189251Ssam	int ret = -1;
796189251Ssam	char type[2];
797189251Ssam	const u8 *mac = from_mac_addr;
798189251Ssam	char mac_text[18];
799189251Ssam	u8 *raw = NULL;
800189251Ssam	size_t raw_len;
801189251Ssam	char *val;
802189251Ssam	size_t val_len;
803189251Ssam	int pos = 0;
804189251Ssam
805189251Ssam	if (!sm)
806189251Ssam		goto fail;
807189251Ssam
808189251Ssam	os_snprintf(type, sizeof(type), "%1u", ev_type);
809189251Ssam
810189251Ssam	raw_len = 1 + 17 + (msg ? wpabuf_len(msg) : 0);
811189251Ssam	raw = os_zalloc(raw_len);
812189251Ssam	if (!raw)
813189251Ssam		goto fail;
814189251Ssam
815189251Ssam	*(raw + pos) = (u8) ev_type;
816189251Ssam	pos += 1;
817189251Ssam	os_snprintf(mac_text, sizeof(mac_text), MACSTR, MAC2STR(mac));
818189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: Proxying WLANEvent from %s",
819189251Ssam		   mac_text);
820189251Ssam	os_memcpy(raw + pos, mac_text, 17);
821189251Ssam	pos += 17;
822189251Ssam	if (msg) {
823189251Ssam		os_memcpy(raw + pos, wpabuf_head(msg), wpabuf_len(msg));
824189251Ssam		pos += wpabuf_len(msg);
825189251Ssam	}
826189251Ssam	raw_len = pos;
827189251Ssam
828189251Ssam	val = (char *) base64_encode(raw, raw_len, &val_len);
829189251Ssam	if (val == NULL)
830189251Ssam		goto fail;
831189251Ssam
832189251Ssam	os_free(sm->wlanevent);
833189251Ssam	sm->wlanevent = val;
834252726Srpaulo	sm->wlanevent_type = ev_type;
835189251Ssam	upnp_wps_device_send_event(sm);
836189251Ssam
837189251Ssam	ret = 0;
838189251Ssam
839189251Ssamfail:
840189251Ssam	os_free(raw);
841189251Ssam
842189251Ssam	return ret;
843189251Ssam}
844189251Ssam
845189251Ssam
846214734Srpaulo#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
847209158Srpaulo#include <sys/sysctl.h>
848209158Srpaulo#include <net/route.h>
849209158Srpaulo#include <net/if_dl.h>
850209158Srpaulo
851209158Srpaulostatic int eth_get(const char *device, u8 ea[ETH_ALEN])
852209158Srpaulo{
853209158Srpaulo	struct if_msghdr *ifm;
854209158Srpaulo	struct sockaddr_dl *sdl;
855209158Srpaulo	u_char *p, *buf;
856209158Srpaulo	size_t len;
857209158Srpaulo	int mib[] = { CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, 0 };
858209158Srpaulo
859209158Srpaulo	if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0)
860209158Srpaulo		return -1;
861209158Srpaulo	if ((buf = os_malloc(len)) == NULL)
862209158Srpaulo		return -1;
863209158Srpaulo	if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
864209158Srpaulo		os_free(buf);
865209158Srpaulo		return -1;
866209158Srpaulo	}
867209158Srpaulo	for (p = buf; p < buf + len; p += ifm->ifm_msglen) {
868209158Srpaulo		ifm = (struct if_msghdr *)p;
869209158Srpaulo		sdl = (struct sockaddr_dl *)(ifm + 1);
870209158Srpaulo		if (ifm->ifm_type != RTM_IFINFO ||
871209158Srpaulo		    (ifm->ifm_addrs & RTA_IFP) == 0)
872209158Srpaulo			continue;
873209158Srpaulo		if (sdl->sdl_family != AF_LINK || sdl->sdl_nlen == 0 ||
874209158Srpaulo		    os_memcmp(sdl->sdl_data, device, sdl->sdl_nlen) != 0)
875209158Srpaulo			continue;
876209158Srpaulo		os_memcpy(ea, LLADDR(sdl), sdl->sdl_alen);
877209158Srpaulo		break;
878209158Srpaulo	}
879209158Srpaulo	os_free(buf);
880209158Srpaulo
881209158Srpaulo	if (p >= buf + len) {
882209158Srpaulo		errno = ESRCH;
883209158Srpaulo		return -1;
884209158Srpaulo	}
885209158Srpaulo	return 0;
886209158Srpaulo}
887209158Srpaulo#endif /* __FreeBSD__ */
888209158Srpaulo
889209158Srpaulo
890189251Ssam/**
891189251Ssam * get_netif_info - Get hw and IP addresses for network device
892189251Ssam * @net_if: Selected network interface name
893189251Ssam * @ip_addr: Buffer for returning IP address in network byte order
894189251Ssam * @ip_addr_text: Buffer for returning a pointer to allocated IP address text
895189251Ssam * @mac: Buffer for returning MAC address
896189251Ssam * Returns: 0 on success, -1 on failure
897189251Ssam */
898214734Srpauloint get_netif_info(const char *net_if, unsigned *ip_addr, char **ip_addr_text,
899214734Srpaulo		   u8 mac[ETH_ALEN])
900189251Ssam{
901189251Ssam	struct ifreq req;
902189251Ssam	int sock = -1;
903189251Ssam	struct sockaddr_in *addr;
904189251Ssam	struct in_addr in_addr;
905189251Ssam
906189251Ssam	*ip_addr_text = os_zalloc(16);
907214734Srpaulo	if (*ip_addr_text == NULL)
908189251Ssam		goto fail;
909189251Ssam
910189251Ssam	sock = socket(AF_INET, SOCK_DGRAM, 0);
911189251Ssam	if (sock < 0)
912189251Ssam		goto fail;
913189251Ssam
914189251Ssam	os_strlcpy(req.ifr_name, net_if, sizeof(req.ifr_name));
915189251Ssam	if (ioctl(sock, SIOCGIFADDR, &req) < 0) {
916189251Ssam		wpa_printf(MSG_ERROR, "WPS UPnP: SIOCGIFADDR failed: %d (%s)",
917189251Ssam			   errno, strerror(errno));
918189251Ssam		goto fail;
919189251Ssam	}
920189251Ssam	addr = (void *) &req.ifr_addr;
921189251Ssam	*ip_addr = addr->sin_addr.s_addr;
922189251Ssam	in_addr.s_addr = *ip_addr;
923189251Ssam	os_snprintf(*ip_addr_text, 16, "%s", inet_ntoa(in_addr));
924189251Ssam
925209158Srpaulo#ifdef __linux__
926189251Ssam	os_strlcpy(req.ifr_name, net_if, sizeof(req.ifr_name));
927189251Ssam	if (ioctl(sock, SIOCGIFHWADDR, &req) < 0) {
928189251Ssam		wpa_printf(MSG_ERROR, "WPS UPnP: SIOCGIFHWADDR failed: "
929189251Ssam			   "%d (%s)", errno, strerror(errno));
930189251Ssam		goto fail;
931189251Ssam	}
932189251Ssam	os_memcpy(mac, req.ifr_addr.sa_data, 6);
933214734Srpaulo#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
934209158Srpaulo	if (eth_get(net_if, mac) < 0) {
935209158Srpaulo		wpa_printf(MSG_ERROR, "WPS UPnP: Failed to get MAC address");
936209158Srpaulo		goto fail;
937209158Srpaulo	}
938209158Srpaulo#else
939209158Srpaulo#error MAC address fetch not implemented
940209158Srpaulo#endif
941189251Ssam
942189251Ssam	close(sock);
943189251Ssam	return 0;
944189251Ssam
945189251Ssamfail:
946189251Ssam	if (sock >= 0)
947189251Ssam		close(sock);
948189251Ssam	os_free(*ip_addr_text);
949189251Ssam	*ip_addr_text = NULL;
950189251Ssam	return -1;
951189251Ssam}
952189251Ssam
953189251Ssam
954214734Srpaulostatic void upnp_wps_free_msearchreply(struct dl_list *head)
955214734Srpaulo{
956214734Srpaulo	struct advertisement_state_machine *a, *tmp;
957214734Srpaulo	dl_list_for_each_safe(a, tmp, head, struct advertisement_state_machine,
958214734Srpaulo			      list)
959214734Srpaulo		msearchreply_state_machine_stop(a);
960214734Srpaulo}
961214734Srpaulo
962214734Srpaulo
963252726Srpaulostatic void upnp_wps_free_subscriptions(struct dl_list *head,
964252726Srpaulo					struct wps_registrar *reg)
965214734Srpaulo{
966214734Srpaulo	struct subscription *s, *tmp;
967214734Srpaulo	dl_list_for_each_safe(s, tmp, head, struct subscription, list) {
968252726Srpaulo		if (reg && s->reg != reg)
969252726Srpaulo			continue;
970214734Srpaulo		dl_list_del(&s->list);
971214734Srpaulo		subscription_destroy(s);
972214734Srpaulo	}
973214734Srpaulo}
974214734Srpaulo
975214734Srpaulo
976189251Ssam/**
977189251Ssam * upnp_wps_device_stop - Stop WPS UPnP operations on an interface
978189251Ssam * @sm: WPS UPnP state machine from upnp_wps_device_init()
979189251Ssam */
980252726Srpaulostatic void upnp_wps_device_stop(struct upnp_wps_device_sm *sm)
981189251Ssam{
982189251Ssam	if (!sm || !sm->started)
983189251Ssam		return;
984189251Ssam
985189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: Stop device");
986189251Ssam	web_listener_stop(sm);
987214734Srpaulo	upnp_wps_free_msearchreply(&sm->msearch_replies);
988252726Srpaulo	upnp_wps_free_subscriptions(&sm->subscriptions, NULL);
989189251Ssam
990209158Srpaulo	advertisement_state_machine_stop(sm, 1);
991189251Ssam
992189251Ssam	event_send_stop_all(sm);
993189251Ssam	os_free(sm->wlanevent);
994189251Ssam	sm->wlanevent = NULL;
995189251Ssam	os_free(sm->ip_addr_text);
996189251Ssam	sm->ip_addr_text = NULL;
997189251Ssam	if (sm->multicast_sd >= 0)
998189251Ssam		close(sm->multicast_sd);
999189251Ssam	sm->multicast_sd = -1;
1000189251Ssam	ssdp_listener_stop(sm);
1001189251Ssam
1002189251Ssam	sm->started = 0;
1003189251Ssam}
1004189251Ssam
1005189251Ssam
1006189251Ssam/**
1007189251Ssam * upnp_wps_device_start - Start WPS UPnP operations on an interface
1008189251Ssam * @sm: WPS UPnP state machine from upnp_wps_device_init()
1009189251Ssam * @net_if: Selected network interface name
1010189251Ssam * Returns: 0 on success, -1 on failure
1011189251Ssam */
1012252726Srpaulostatic int upnp_wps_device_start(struct upnp_wps_device_sm *sm, char *net_if)
1013189251Ssam{
1014189251Ssam	if (!sm || !net_if)
1015189251Ssam		return -1;
1016189251Ssam
1017189251Ssam	if (sm->started)
1018189251Ssam		upnp_wps_device_stop(sm);
1019189251Ssam
1020189251Ssam	sm->multicast_sd = -1;
1021189251Ssam	sm->ssdp_sd = -1;
1022189251Ssam	sm->started = 1;
1023189251Ssam	sm->advertise_count = 0;
1024189251Ssam
1025189251Ssam	/* Fix up linux multicast handling */
1026189251Ssam	if (add_ssdp_network(net_if))
1027189251Ssam		goto fail;
1028189251Ssam
1029189251Ssam	/* Determine which IP and mac address we're using */
1030214734Srpaulo	if (get_netif_info(net_if, &sm->ip_addr, &sm->ip_addr_text,
1031214734Srpaulo			   sm->mac_addr)) {
1032189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Could not get IP/MAC address "
1033189251Ssam			   "for %s. Does it have IP address?", net_if);
1034189251Ssam		goto fail;
1035189251Ssam	}
1036189251Ssam
1037189251Ssam	/* Listen for incoming TCP connections so that others
1038189251Ssam	 * can fetch our "xml files" from us.
1039189251Ssam	 */
1040189251Ssam	if (web_listener_start(sm))
1041189251Ssam		goto fail;
1042189251Ssam
1043189251Ssam	/* Set up for receiving discovery (UDP) packets */
1044189251Ssam	if (ssdp_listener_start(sm))
1045189251Ssam		goto fail;
1046189251Ssam
1047189251Ssam	/* Set up for sending multicast */
1048189251Ssam	if (ssdp_open_multicast(sm) < 0)
1049189251Ssam		goto fail;
1050189251Ssam
1051189251Ssam	/*
1052189251Ssam	 * Broadcast NOTIFY messages to let the world know we exist.
1053189251Ssam	 * This is done via a state machine since the messages should not be
1054189251Ssam	 * all sent out at once.
1055189251Ssam	 */
1056189251Ssam	if (advertisement_state_machine_start(sm))
1057189251Ssam		goto fail;
1058189251Ssam
1059189251Ssam	return 0;
1060189251Ssam
1061189251Ssamfail:
1062189251Ssam	upnp_wps_device_stop(sm);
1063189251Ssam	return -1;
1064189251Ssam}
1065189251Ssam
1066189251Ssam
1067252726Srpaulostatic struct upnp_wps_device_interface *
1068252726Srpauloupnp_wps_get_iface(struct upnp_wps_device_sm *sm, void *priv)
1069252726Srpaulo{
1070252726Srpaulo	struct upnp_wps_device_interface *iface;
1071252726Srpaulo	dl_list_for_each(iface, &sm->interfaces,
1072252726Srpaulo			 struct upnp_wps_device_interface, list) {
1073252726Srpaulo		if (iface->priv == priv)
1074252726Srpaulo			return iface;
1075252726Srpaulo	}
1076252726Srpaulo	return NULL;
1077252726Srpaulo}
1078252726Srpaulo
1079252726Srpaulo
1080189251Ssam/**
1081189251Ssam * upnp_wps_device_deinit - Deinitialize WPS UPnP
1082189251Ssam * @sm: WPS UPnP state machine from upnp_wps_device_init()
1083252726Srpaulo * @priv: External context data that was used in upnp_wps_device_init() call
1084189251Ssam */
1085252726Srpaulovoid upnp_wps_device_deinit(struct upnp_wps_device_sm *sm, void *priv)
1086189251Ssam{
1087252726Srpaulo	struct upnp_wps_device_interface *iface;
1088252726Srpaulo
1089189251Ssam	if (!sm)
1090189251Ssam		return;
1091189251Ssam
1092252726Srpaulo	iface = upnp_wps_get_iface(sm, priv);
1093252726Srpaulo	if (iface == NULL) {
1094252726Srpaulo		wpa_printf(MSG_ERROR, "WPS UPnP: Could not find the interface "
1095252726Srpaulo			   "instance to deinit");
1096252726Srpaulo		return;
1097252726Srpaulo	}
1098252726Srpaulo	wpa_printf(MSG_DEBUG, "WPS UPnP: Deinit interface instance %p", iface);
1099252726Srpaulo	if (dl_list_len(&sm->interfaces) == 1) {
1100252726Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Deinitializing last instance "
1101252726Srpaulo			   "- free global device instance");
1102252726Srpaulo		upnp_wps_device_stop(sm);
1103252726Srpaulo	} else
1104252726Srpaulo		upnp_wps_free_subscriptions(&sm->subscriptions,
1105252726Srpaulo					    iface->wps->registrar);
1106252726Srpaulo	dl_list_del(&iface->list);
1107189251Ssam
1108252726Srpaulo	if (iface->peer.wps)
1109252726Srpaulo		wps_deinit(iface->peer.wps);
1110252726Srpaulo	os_free(iface->ctx->ap_pin);
1111252726Srpaulo	os_free(iface->ctx);
1112252726Srpaulo	os_free(iface);
1113252726Srpaulo
1114252726Srpaulo	if (dl_list_empty(&sm->interfaces)) {
1115252726Srpaulo		os_free(sm->root_dir);
1116252726Srpaulo		os_free(sm->desc_url);
1117252726Srpaulo		os_free(sm);
1118252726Srpaulo		shared_upnp_device = NULL;
1119252726Srpaulo	}
1120189251Ssam}
1121189251Ssam
1122189251Ssam
1123189251Ssam/**
1124189251Ssam * upnp_wps_device_init - Initialize WPS UPnP
1125189251Ssam * @ctx: callback table; we must eventually free it
1126189251Ssam * @wps: Pointer to longterm WPS context
1127189251Ssam * @priv: External context data that will be used in callbacks
1128252726Srpaulo * @net_if: Selected network interface name
1129189251Ssam * Returns: WPS UPnP state or %NULL on failure
1130189251Ssam */
1131189251Ssamstruct upnp_wps_device_sm *
1132189251Ssamupnp_wps_device_init(struct upnp_wps_device_ctx *ctx, struct wps_context *wps,
1133252726Srpaulo		     void *priv, char *net_if)
1134189251Ssam{
1135189251Ssam	struct upnp_wps_device_sm *sm;
1136252726Srpaulo	struct upnp_wps_device_interface *iface;
1137252726Srpaulo	int start = 0;
1138189251Ssam
1139252726Srpaulo	iface = os_zalloc(sizeof(*iface));
1140252726Srpaulo	if (iface == NULL) {
1141252726Srpaulo		os_free(ctx->ap_pin);
1142252726Srpaulo		os_free(ctx);
1143189251Ssam		return NULL;
1144189251Ssam	}
1145252726Srpaulo	wpa_printf(MSG_DEBUG, "WPS UPnP: Init interface instance %p", iface);
1146189251Ssam
1147252726Srpaulo	iface->ctx = ctx;
1148252726Srpaulo	iface->wps = wps;
1149252726Srpaulo	iface->priv = priv;
1150189251Ssam
1151252726Srpaulo	if (shared_upnp_device) {
1152252726Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Share existing device "
1153252726Srpaulo			   "context");
1154252726Srpaulo		sm = shared_upnp_device;
1155252726Srpaulo	} else {
1156252726Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Initialize device context");
1157252726Srpaulo		sm = os_zalloc(sizeof(*sm));
1158252726Srpaulo		if (!sm) {
1159252726Srpaulo			wpa_printf(MSG_ERROR, "WPS UPnP: upnp_wps_device_init "
1160252726Srpaulo				   "failed");
1161252726Srpaulo			os_free(iface);
1162252726Srpaulo			os_free(ctx->ap_pin);
1163252726Srpaulo			os_free(ctx);
1164252726Srpaulo			return NULL;
1165252726Srpaulo		}
1166252726Srpaulo		shared_upnp_device = sm;
1167252726Srpaulo
1168252726Srpaulo		dl_list_init(&sm->msearch_replies);
1169252726Srpaulo		dl_list_init(&sm->subscriptions);
1170252726Srpaulo		dl_list_init(&sm->interfaces);
1171252726Srpaulo		start = 1;
1172252726Srpaulo	}
1173252726Srpaulo
1174252726Srpaulo	dl_list_add(&sm->interfaces, &iface->list);
1175252726Srpaulo
1176252726Srpaulo	if (start && upnp_wps_device_start(sm, net_if)) {
1177252726Srpaulo		upnp_wps_device_deinit(sm, priv);
1178252726Srpaulo		return NULL;
1179252726Srpaulo	}
1180252726Srpaulo
1181252726Srpaulo
1182189251Ssam	return sm;
1183189251Ssam}
1184189251Ssam
1185189251Ssam
1186189251Ssam/**
1187189251Ssam * upnp_wps_subscribers - Check whether there are any event subscribers
1188189251Ssam * @sm: WPS UPnP state machine from upnp_wps_device_init()
1189189251Ssam * Returns: 0 if no subscribers, 1 if subscribers
1190189251Ssam */
1191189251Ssamint upnp_wps_subscribers(struct upnp_wps_device_sm *sm)
1192189251Ssam{
1193214734Srpaulo	return !dl_list_empty(&sm->subscriptions);
1194189251Ssam}
1195214734Srpaulo
1196214734Srpaulo
1197214734Srpauloint upnp_wps_set_ap_pin(struct upnp_wps_device_sm *sm, const char *ap_pin)
1198214734Srpaulo{
1199252726Srpaulo	struct upnp_wps_device_interface *iface;
1200214734Srpaulo	if (sm == NULL)
1201214734Srpaulo		return 0;
1202214734Srpaulo
1203252726Srpaulo	dl_list_for_each(iface, &sm->interfaces,
1204252726Srpaulo			 struct upnp_wps_device_interface, list) {
1205252726Srpaulo		os_free(iface->ctx->ap_pin);
1206252726Srpaulo		if (ap_pin) {
1207252726Srpaulo			iface->ctx->ap_pin = os_strdup(ap_pin);
1208252726Srpaulo			if (iface->ctx->ap_pin == NULL)
1209252726Srpaulo				return -1;
1210252726Srpaulo		} else
1211252726Srpaulo			iface->ctx->ap_pin = NULL;
1212252726Srpaulo	}
1213214734Srpaulo
1214214734Srpaulo	return 0;
1215214734Srpaulo}
1216