wps_upnp_web.c revision 8d520ff1dc2da35cdca849e982051b86468016d8
1/*
2 * UPnP WPS Device - Web connections
3 * Copyright (c) 2000-2003 Intel Corporation
4 * Copyright (c) 2006-2007 Sony Corporation
5 * Copyright (c) 2008-2009 Atheros Communications
6 * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
7 *
8 * See wps_upnp.c for more details on licensing and code history.
9 */
10
11#include "includes.h"
12
13#include "common.h"
14#include "base64.h"
15#include "uuid.h"
16#include "httpread.h"
17#include "http_server.h"
18#include "wps_i.h"
19#include "wps_upnp.h"
20#include "wps_upnp_i.h"
21#include "upnp_xml.h"
22
23/***************************************************************************
24 * Web connections (we serve pages of info about ourselves, handle
25 * requests, etc. etc.).
26 **************************************************************************/
27
28#define WEB_CONNECTION_TIMEOUT_SEC 30   /* Drop web connection after t.o. */
29#define WEB_CONNECTION_MAX_READ 8000    /* Max we'll read for TCP request */
30#define MAX_WEB_CONNECTIONS 10          /* max simultaneous web connects */
31
32
33static const char *urn_wfawlanconfig =
34	"urn:schemas-wifialliance-org:service:WFAWLANConfig:1";
35static const char *http_server_hdr =
36	"Server: unspecified, UPnP/1.0, unspecified\r\n";
37static const char *http_connection_close =
38	"Connection: close\r\n";
39
40/*
41 * "Files" that we serve via HTTP. The format of these files is given by
42 * WFA WPS specifications. Extra white space has been removed to save space.
43 */
44
45static const char wps_scpd_xml[] =
46"<?xml version=\"1.0\"?>\n"
47"<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\n"
48"<specVersion><major>1</major><minor>0</minor></specVersion>\n"
49"<actionList>\n"
50"<action>\n"
51"<name>GetDeviceInfo</name>\n"
52"<argumentList>\n"
53"<argument>\n"
54"<name>NewDeviceInfo</name>\n"
55"<direction>out</direction>\n"
56"<relatedStateVariable>DeviceInfo</relatedStateVariable>\n"
57"</argument>\n"
58"</argumentList>\n"
59"</action>\n"
60"<action>\n"
61"<name>PutMessage</name>\n"
62"<argumentList>\n"
63"<argument>\n"
64"<name>NewInMessage</name>\n"
65"<direction>in</direction>\n"
66"<relatedStateVariable>InMessage</relatedStateVariable>\n"
67"</argument>\n"
68"<argument>\n"
69"<name>NewOutMessage</name>\n"
70"<direction>out</direction>\n"
71"<relatedStateVariable>OutMessage</relatedStateVariable>\n"
72"</argument>\n"
73"</argumentList>\n"
74"</action>\n"
75"<action>\n"
76"<name>PutWLANResponse</name>\n"
77"<argumentList>\n"
78"<argument>\n"
79"<name>NewMessage</name>\n"
80"<direction>in</direction>\n"
81"<relatedStateVariable>Message</relatedStateVariable>\n"
82"</argument>\n"
83"<argument>\n"
84"<name>NewWLANEventType</name>\n"
85"<direction>in</direction>\n"
86"<relatedStateVariable>WLANEventType</relatedStateVariable>\n"
87"</argument>\n"
88"<argument>\n"
89"<name>NewWLANEventMAC</name>\n"
90"<direction>in</direction>\n"
91"<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n"
92"</argument>\n"
93"</argumentList>\n"
94"</action>\n"
95"<action>\n"
96"<name>SetSelectedRegistrar</name>\n"
97"<argumentList>\n"
98"<argument>\n"
99"<name>NewMessage</name>\n"
100"<direction>in</direction>\n"
101"<relatedStateVariable>Message</relatedStateVariable>\n"
102"</argument>\n"
103"</argumentList>\n"
104"</action>\n"
105"</actionList>\n"
106"<serviceStateTable>\n"
107"<stateVariable sendEvents=\"no\">\n"
108"<name>Message</name>\n"
109"<dataType>bin.base64</dataType>\n"
110"</stateVariable>\n"
111"<stateVariable sendEvents=\"no\">\n"
112"<name>InMessage</name>\n"
113"<dataType>bin.base64</dataType>\n"
114"</stateVariable>\n"
115"<stateVariable sendEvents=\"no\">\n"
116"<name>OutMessage</name>\n"
117"<dataType>bin.base64</dataType>\n"
118"</stateVariable>\n"
119"<stateVariable sendEvents=\"no\">\n"
120"<name>DeviceInfo</name>\n"
121"<dataType>bin.base64</dataType>\n"
122"</stateVariable>\n"
123"<stateVariable sendEvents=\"yes\">\n"
124"<name>APStatus</name>\n"
125"<dataType>ui1</dataType>\n"
126"</stateVariable>\n"
127"<stateVariable sendEvents=\"yes\">\n"
128"<name>STAStatus</name>\n"
129"<dataType>ui1</dataType>\n"
130"</stateVariable>\n"
131"<stateVariable sendEvents=\"yes\">\n"
132"<name>WLANEvent</name>\n"
133"<dataType>bin.base64</dataType>\n"
134"</stateVariable>\n"
135"<stateVariable sendEvents=\"no\">\n"
136"<name>WLANEventType</name>\n"
137"<dataType>ui1</dataType>\n"
138"</stateVariable>\n"
139"<stateVariable sendEvents=\"no\">\n"
140"<name>WLANEventMAC</name>\n"
141"<dataType>string</dataType>\n"
142"</stateVariable>\n"
143"<stateVariable sendEvents=\"no\">\n"
144"<name>WLANResponse</name>\n"
145"<dataType>bin.base64</dataType>\n"
146"</stateVariable>\n"
147"</serviceStateTable>\n"
148"</scpd>\n"
149;
150
151
152static const char *wps_device_xml_prefix =
153	"<?xml version=\"1.0\"?>\n"
154	"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
155	"<specVersion>\n"
156	"<major>1</major>\n"
157	"<minor>0</minor>\n"
158	"</specVersion>\n"
159	"<device>\n"
160	"<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1"
161	"</deviceType>\n";
162
163static const char *wps_device_xml_postfix =
164	"<serviceList>\n"
165	"<service>\n"
166	"<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1"
167	"</serviceType>\n"
168	"<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>"
169	"\n"
170	"<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n"
171	"<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n"
172	"<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n"
173	"</service>\n"
174	"</serviceList>\n"
175	"</device>\n"
176	"</root>\n";
177
178
179/* format_wps_device_xml -- produce content of "file" wps_device.xml
180 * (UPNP_WPS_DEVICE_XML_FILE)
181 */
182static void format_wps_device_xml(struct upnp_wps_device_sm *sm,
183				  struct wpabuf *buf)
184{
185	const char *s;
186	char uuid_string[80];
187	struct upnp_wps_device_interface *iface;
188
189	iface = dl_list_first(&sm->interfaces,
190			      struct upnp_wps_device_interface, list);
191
192	wpabuf_put_str(buf, wps_device_xml_prefix);
193
194	/*
195	 * Add required fields with default values if not configured. Add
196	 * optional and recommended fields only if configured.
197	 */
198	s = iface->wps->friendly_name;
199	s = ((s && *s) ? s : "WPS Access Point");
200	xml_add_tagged_data(buf, "friendlyName", s);
201
202	s = iface->wps->dev.manufacturer;
203	s = ((s && *s) ? s : "");
204	xml_add_tagged_data(buf, "manufacturer", s);
205
206	if (iface->wps->manufacturer_url)
207		xml_add_tagged_data(buf, "manufacturerURL",
208				    iface->wps->manufacturer_url);
209
210	if (iface->wps->model_description)
211		xml_add_tagged_data(buf, "modelDescription",
212				    iface->wps->model_description);
213
214	s = iface->wps->dev.model_name;
215	s = ((s && *s) ? s : "");
216	xml_add_tagged_data(buf, "modelName", s);
217
218	if (iface->wps->dev.model_number)
219		xml_add_tagged_data(buf, "modelNumber",
220				    iface->wps->dev.model_number);
221
222	if (iface->wps->model_url)
223		xml_add_tagged_data(buf, "modelURL", iface->wps->model_url);
224
225	if (iface->wps->dev.serial_number)
226		xml_add_tagged_data(buf, "serialNumber",
227				    iface->wps->dev.serial_number);
228
229	uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string));
230	s = uuid_string;
231	/* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
232	 * easily...
233	 */
234	wpabuf_put_str(buf, "<UDN>uuid:");
235	xml_data_encode(buf, s, os_strlen(s));
236	wpabuf_put_str(buf, "</UDN>\n");
237
238	if (iface->wps->upc)
239		xml_add_tagged_data(buf, "UPC", iface->wps->upc);
240
241	wpabuf_put_str(buf, wps_device_xml_postfix);
242}
243
244
245static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
246{
247	wpabuf_put_str(buf, "HTTP/1.1 ");
248	switch (code) {
249	case HTTP_OK:
250		wpabuf_put_str(buf, "200 OK\r\n");
251		break;
252	case HTTP_BAD_REQUEST:
253		wpabuf_put_str(buf, "400 Bad request\r\n");
254		break;
255	case HTTP_PRECONDITION_FAILED:
256		wpabuf_put_str(buf, "412 Precondition failed\r\n");
257		break;
258	case HTTP_UNIMPLEMENTED:
259		wpabuf_put_str(buf, "501 Unimplemented\r\n");
260		break;
261	case HTTP_INTERNAL_SERVER_ERROR:
262	default:
263		wpabuf_put_str(buf, "500 Internal server error\r\n");
264		break;
265	}
266}
267
268
269static void http_put_date(struct wpabuf *buf)
270{
271	wpabuf_put_str(buf, "Date: ");
272	format_date(buf);
273	wpabuf_put_str(buf, "\r\n");
274}
275
276
277static void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
278{
279	http_put_reply_code(buf, code);
280	wpabuf_put_str(buf, http_server_hdr);
281	wpabuf_put_str(buf, http_connection_close);
282	wpabuf_put_str(buf, "Content-Length: 0\r\n"
283		       "\r\n");
284}
285
286
287/* Given that we have received a header w/ GET, act upon it
288 *
289 * Format of GET (case-insensitive):
290 *
291 * First line must be:
292 *      GET /<file> HTTP/1.1
293 * Since we don't do anything fancy we just ignore other lines.
294 *
295 * Our response (if no error) which includes only required lines is:
296 * HTTP/1.1 200 OK
297 * Connection: close
298 * Content-Type: text/xml
299 * Date: <rfc1123-date>
300 *
301 * Header lines must end with \r\n
302 * Per RFC 2616, content-length: is not required but connection:close
303 * would appear to be required (given that we will be closing it!).
304 */
305static void web_connection_parse_get(struct upnp_wps_device_sm *sm,
306				     struct http_request *hreq, char *filename)
307{
308	struct wpabuf *buf; /* output buffer, allocated */
309	char *put_length_here;
310	char *body_start;
311	enum {
312		GET_DEVICE_XML_FILE,
313		GET_SCPD_XML_FILE
314	} req;
315	size_t extra_len = 0;
316	int body_length;
317	char len_buf[10];
318	struct upnp_wps_device_interface *iface;
319
320	iface = dl_list_first(&sm->interfaces,
321			      struct upnp_wps_device_interface, list);
322
323	/*
324	 * It is not required that filenames be case insensitive but it is
325	 * allowed and cannot hurt here.
326	 */
327	if (filename == NULL)
328		filename = "(null)"; /* just in case */
329	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
330		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
331		req = GET_DEVICE_XML_FILE;
332		extra_len = 3000;
333		if (iface->wps->friendly_name)
334			extra_len += os_strlen(iface->wps->friendly_name);
335		if (iface->wps->manufacturer_url)
336			extra_len += os_strlen(iface->wps->manufacturer_url);
337		if (iface->wps->model_description)
338			extra_len += os_strlen(iface->wps->model_description);
339		if (iface->wps->model_url)
340			extra_len += os_strlen(iface->wps->model_url);
341		if (iface->wps->upc)
342			extra_len += os_strlen(iface->wps->upc);
343	} else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
344		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
345		req = GET_SCPD_XML_FILE;
346		extra_len = os_strlen(wps_scpd_xml);
347	} else {
348		/* File not found */
349		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
350			   filename);
351		buf = wpabuf_alloc(200);
352		if (buf == NULL) {
353			http_request_deinit(hreq);
354			return;
355		}
356		wpabuf_put_str(buf,
357			       "HTTP/1.1 404 Not Found\r\n"
358			       "Connection: close\r\n");
359
360		http_put_date(buf);
361
362		/* terminating empty line */
363		wpabuf_put_str(buf, "\r\n");
364
365		goto send_buf;
366	}
367
368	buf = wpabuf_alloc(1000 + extra_len);
369	if (buf == NULL) {
370		http_request_deinit(hreq);
371		return;
372	}
373
374	wpabuf_put_str(buf,
375		       "HTTP/1.1 200 OK\r\n"
376		       "Content-Type: text/xml; charset=\"utf-8\"\r\n");
377	wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
378	wpabuf_put_str(buf, "Connection: close\r\n");
379	wpabuf_put_str(buf, "Content-Length: ");
380	/*
381	 * We will paste the length in later, leaving some extra whitespace.
382	 * HTTP code is supposed to be tolerant of extra whitespace.
383	 */
384	put_length_here = wpabuf_put(buf, 0);
385	wpabuf_put_str(buf, "        \r\n");
386
387	http_put_date(buf);
388
389	/* terminating empty line */
390	wpabuf_put_str(buf, "\r\n");
391
392	body_start = wpabuf_put(buf, 0);
393
394	switch (req) {
395	case GET_DEVICE_XML_FILE:
396		format_wps_device_xml(sm, buf);
397		break;
398	case GET_SCPD_XML_FILE:
399		wpabuf_put_str(buf, wps_scpd_xml);
400		break;
401	}
402
403	/* Now patch in the content length at the end */
404	body_length = (char *) wpabuf_put(buf, 0) - body_start;
405	os_snprintf(len_buf, 10, "%d", body_length);
406	os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
407
408send_buf:
409	http_request_send_and_deinit(hreq, buf);
410}
411
412
413static enum http_reply_code
414web_process_get_device_info(struct upnp_wps_device_sm *sm,
415			    struct wpabuf **reply, const char **replyname)
416{
417	static const char *name = "NewDeviceInfo";
418	struct wps_config cfg;
419	struct upnp_wps_device_interface *iface;
420	struct upnp_wps_peer *peer;
421
422	iface = dl_list_first(&sm->interfaces,
423			      struct upnp_wps_device_interface, list);
424	peer = &iface->peer;
425
426	wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
427
428	if (iface->ctx->ap_pin == NULL)
429		return HTTP_INTERNAL_SERVER_ERROR;
430
431	/*
432	 * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS
433	 * registration over UPnP with the AP acting as an Enrollee. It should
434	 * be noted that this is frequently used just to get the device data,
435	 * i.e., there may not be any intent to actually complete the
436	 * registration.
437	 */
438
439	if (peer->wps)
440		wps_deinit(peer->wps);
441
442	os_memset(&cfg, 0, sizeof(cfg));
443	cfg.wps = iface->wps;
444	cfg.pin = (u8 *) iface->ctx->ap_pin;
445	cfg.pin_len = os_strlen(iface->ctx->ap_pin);
446	peer->wps = wps_init(&cfg);
447	if (peer->wps) {
448		enum wsc_op_code op_code;
449		*reply = wps_get_msg(peer->wps, &op_code);
450		if (*reply == NULL) {
451			wps_deinit(peer->wps);
452			peer->wps = NULL;
453		}
454	} else
455		*reply = NULL;
456	if (*reply == NULL) {
457		wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
458		return HTTP_INTERNAL_SERVER_ERROR;
459	}
460	*replyname = name;
461	return HTTP_OK;
462}
463
464
465static enum http_reply_code
466web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
467			struct wpabuf **reply, const char **replyname)
468{
469	struct wpabuf *msg;
470	static const char *name = "NewOutMessage";
471	enum http_reply_code ret;
472	enum wps_process_res res;
473	enum wsc_op_code op_code;
474	struct upnp_wps_device_interface *iface;
475
476	iface = dl_list_first(&sm->interfaces,
477			      struct upnp_wps_device_interface, list);
478
479	/*
480	 * PutMessage is used by external UPnP-based Registrar to perform WPS
481	 * operation with the access point itself; as compared with
482	 * PutWLANResponse which is for proxying.
483	 */
484	wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
485	msg = xml_get_base64_item(data, "NewInMessage", &ret);
486	if (msg == NULL)
487		return ret;
488	res = wps_process_msg(iface->peer.wps, WSC_UPnP, msg);
489	if (res == WPS_FAILURE)
490		*reply = NULL;
491	else
492		*reply = wps_get_msg(iface->peer.wps, &op_code);
493	wpabuf_free(msg);
494	if (*reply == NULL)
495		return HTTP_INTERNAL_SERVER_ERROR;
496	*replyname = name;
497	return HTTP_OK;
498}
499
500
501static enum http_reply_code
502web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
503			      struct wpabuf **reply, const char **replyname)
504{
505	struct wpabuf *msg;
506	enum http_reply_code ret;
507	u8 macaddr[ETH_ALEN];
508	int ev_type;
509	int type;
510	char *val;
511	struct upnp_wps_device_interface *iface;
512	int ok = 0;
513
514	/*
515	 * External UPnP-based Registrar is passing us a message to be proxied
516	 * over to a Wi-Fi -based client of ours.
517	 */
518
519	wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
520	msg = xml_get_base64_item(data, "NewMessage", &ret);
521	if (msg == NULL) {
522		wpa_printf(MSG_DEBUG, "WPS UPnP: Could not extract NewMessage "
523			   "from PutWLANResponse");
524		return ret;
525	}
526	val = xml_get_first_item(data, "NewWLANEventType");
527	if (val == NULL) {
528		wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventType in "
529			   "PutWLANResponse");
530		wpabuf_free(msg);
531		return UPNP_ARG_VALUE_INVALID;
532	}
533	ev_type = atol(val);
534	os_free(val);
535	val = xml_get_first_item(data, "NewWLANEventMAC");
536	if (val == NULL) {
537		wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventMAC in "
538			   "PutWLANResponse");
539		wpabuf_free(msg);
540		return UPNP_ARG_VALUE_INVALID;
541	}
542	if (hwaddr_aton(val, macaddr)) {
543		wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid NewWLANEventMAC in "
544			   "PutWLANResponse: '%s'", val);
545#ifdef CONFIG_WPS_STRICT
546		{
547			struct wps_parse_attr attr;
548			if (wps_parse_msg(msg, &attr) < 0 || attr.version2) {
549				wpabuf_free(msg);
550				os_free(val);
551				return UPNP_ARG_VALUE_INVALID;
552			}
553		}
554#endif /* CONFIG_WPS_STRICT */
555		if (hwaddr_aton2(val, macaddr) > 0) {
556			/*
557			 * At least some versions of Intel PROset seem to be
558			 * using dot-deliminated MAC address format here.
559			 */
560			wpa_printf(MSG_DEBUG, "WPS UPnP: Workaround - allow "
561				   "incorrect MAC address format in "
562				   "NewWLANEventMAC: %s -> " MACSTR,
563				   val, MAC2STR(macaddr));
564		} else {
565			wpabuf_free(msg);
566			os_free(val);
567			return UPNP_ARG_VALUE_INVALID;
568		}
569	}
570	os_free(val);
571	if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
572		struct wps_parse_attr attr;
573		if (wps_parse_msg(msg, &attr) < 0 ||
574		    attr.msg_type == NULL)
575			type = -1;
576		else
577			type = *attr.msg_type;
578		wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
579	} else
580		type = -1;
581	dl_list_for_each(iface, &sm->interfaces,
582			 struct upnp_wps_device_interface, list) {
583		if (iface->ctx->rx_req_put_wlan_response &&
584		    iface->ctx->rx_req_put_wlan_response(iface->priv, ev_type,
585							 macaddr, msg, type)
586		    == 0)
587			ok = 1;
588	}
589
590	if (!ok) {
591		wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
592			   "rx_req_put_wlan_response");
593		wpabuf_free(msg);
594		return HTTP_INTERNAL_SERVER_ERROR;
595	}
596	wpabuf_free(msg);
597	*replyname = NULL;
598	*reply = NULL;
599	return HTTP_OK;
600}
601
602
603static int find_er_addr(struct subscription *s, struct sockaddr_in *cli)
604{
605	struct subscr_addr *a;
606
607	dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) {
608		if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr)
609			return 1;
610	}
611	return 0;
612}
613
614
615static struct subscription * find_er(struct upnp_wps_device_sm *sm,
616				     struct sockaddr_in *cli)
617{
618	struct subscription *s;
619	dl_list_for_each(s, &sm->subscriptions, struct subscription, list)
620		if (find_er_addr(s, cli))
621			return s;
622	return NULL;
623}
624
625
626static enum http_reply_code
627web_process_set_selected_registrar(struct upnp_wps_device_sm *sm,
628				   struct sockaddr_in *cli, char *data,
629				   struct wpabuf **reply,
630				   const char **replyname)
631{
632	struct wpabuf *msg;
633	enum http_reply_code ret;
634	struct subscription *s;
635	struct upnp_wps_device_interface *iface;
636	int err = 0;
637
638	wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
639	s = find_er(sm, cli);
640	if (s == NULL) {
641		wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar "
642			   "from unknown ER");
643		return UPNP_ACTION_FAILED;
644	}
645	msg = xml_get_base64_item(data, "NewMessage", &ret);
646	if (msg == NULL)
647		return ret;
648	dl_list_for_each(iface, &sm->interfaces,
649			 struct upnp_wps_device_interface, list) {
650		if (upnp_er_set_selected_registrar(iface->wps->registrar, s,
651						   msg))
652			err = 1;
653	}
654	wpabuf_free(msg);
655	if (err)
656		return HTTP_INTERNAL_SERVER_ERROR;
657	*replyname = NULL;
658	*reply = NULL;
659	return HTTP_OK;
660}
661
662
663static const char *soap_prefix =
664	"<?xml version=\"1.0\"?>\n"
665	"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
666	"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
667	"<s:Body>\n";
668static const char *soap_postfix =
669	"</s:Body>\n</s:Envelope>\n";
670
671static const char *soap_error_prefix =
672	"<s:Fault>\n"
673	"<faultcode>s:Client</faultcode>\n"
674	"<faultstring>UPnPError</faultstring>\n"
675	"<detail>\n"
676	"<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
677static const char *soap_error_postfix =
678	"<errorDescription>Error</errorDescription>\n"
679	"</UPnPError>\n"
680	"</detail>\n"
681	"</s:Fault>\n";
682
683static void web_connection_send_reply(struct http_request *req,
684				      enum http_reply_code ret,
685				      const char *action, int action_len,
686				      const struct wpabuf *reply,
687				      const char *replyname)
688{
689	struct wpabuf *buf;
690	char *replydata;
691	char *put_length_here = NULL;
692	char *body_start = NULL;
693
694	if (reply) {
695		size_t len;
696		replydata = (char *) base64_encode(wpabuf_head(reply),
697						   wpabuf_len(reply), &len);
698	} else
699		replydata = NULL;
700
701	/* Parameters of the response:
702	 *      action(action_len) -- action we are responding to
703	 *      replyname -- a name we need for the reply
704	 *      replydata -- NULL or null-terminated string
705	 */
706	buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
707			   (action_len > 0 ? action_len * 2 : 0));
708	if (buf == NULL) {
709		wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
710			   "POST");
711		os_free(replydata);
712		http_request_deinit(req);
713		return;
714	}
715
716	/*
717	 * Assuming we will be successful, put in the output header first.
718	 * Note: we do not keep connections alive (and httpread does
719	 * not support it)... therefore we must have Connection: close.
720	 */
721	if (ret == HTTP_OK) {
722		wpabuf_put_str(buf,
723			       "HTTP/1.1 200 OK\r\n"
724			       "Content-Type: text/xml; "
725			       "charset=\"utf-8\"\r\n");
726	} else {
727		wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
728	}
729	wpabuf_put_str(buf, http_connection_close);
730
731	wpabuf_put_str(buf, "Content-Length: ");
732	/*
733	 * We will paste the length in later, leaving some extra whitespace.
734	 * HTTP code is supposed to be tolerant of extra whitespace.
735	 */
736	put_length_here = wpabuf_put(buf, 0);
737	wpabuf_put_str(buf, "        \r\n");
738
739	http_put_date(buf);
740
741	/* terminating empty line */
742	wpabuf_put_str(buf, "\r\n");
743
744	body_start = wpabuf_put(buf, 0);
745
746	if (ret == HTTP_OK) {
747		wpabuf_put_str(buf, soap_prefix);
748		wpabuf_put_str(buf, "<u:");
749		wpabuf_put_data(buf, action, action_len);
750		wpabuf_put_str(buf, "Response xmlns:u=\"");
751		wpabuf_put_str(buf, urn_wfawlanconfig);
752		wpabuf_put_str(buf, "\">\n");
753		if (replydata && replyname) {
754			/* TODO: might possibly need to escape part of reply
755			 * data? ...
756			 * probably not, unlikely to have ampersand(&) or left
757			 * angle bracket (<) in it...
758			 */
759			wpabuf_printf(buf, "<%s>", replyname);
760			wpabuf_put_str(buf, replydata);
761			wpabuf_printf(buf, "</%s>\n", replyname);
762		}
763		wpabuf_put_str(buf, "</u:");
764		wpabuf_put_data(buf, action, action_len);
765		wpabuf_put_str(buf, "Response>\n");
766		wpabuf_put_str(buf, soap_postfix);
767	} else {
768		/* Error case */
769		wpabuf_put_str(buf, soap_prefix);
770		wpabuf_put_str(buf, soap_error_prefix);
771		wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
772		wpabuf_put_str(buf, soap_error_postfix);
773		wpabuf_put_str(buf, soap_postfix);
774	}
775	os_free(replydata);
776
777	/* Now patch in the content length at the end */
778	if (body_start && put_length_here) {
779		int body_length = (char *) wpabuf_put(buf, 0) - body_start;
780		char len_buf[10];
781		os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
782		os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
783	}
784
785	http_request_send_and_deinit(req, buf);
786}
787
788
789static const char * web_get_action(struct http_request *req,
790				   size_t *action_len)
791{
792	const char *match;
793	int match_len;
794	char *b;
795	char *action;
796
797	*action_len = 0;
798	/* The SOAPAction line of the header tells us what we want to do */
799	b = http_request_get_hdr_line(req, "SOAPAction:");
800	if (b == NULL)
801		return NULL;
802	if (*b == '"')
803		b++;
804	else
805		return NULL;
806	match = urn_wfawlanconfig;
807	match_len = os_strlen(urn_wfawlanconfig) - 1;
808	if (os_strncasecmp(b, match, match_len))
809		return NULL;
810	b += match_len;
811	/* skip over version */
812	while (isgraph(*b) && *b != '#')
813		b++;
814	if (*b != '#')
815		return NULL;
816	b++;
817	/* Following the sharp(#) should be the action and a double quote */
818	action = b;
819	while (isgraph(*b) && *b != '"')
820		b++;
821	if (*b != '"')
822		return NULL;
823	*action_len = b - action;
824	return action;
825}
826
827
828/* Given that we have received a header w/ POST, act upon it
829 *
830 * Format of POST (case-insensitive):
831 *
832 * First line must be:
833 *      POST /<file> HTTP/1.1
834 * Since we don't do anything fancy we just ignore other lines.
835 *
836 * Our response (if no error) which includes only required lines is:
837 * HTTP/1.1 200 OK
838 * Connection: close
839 * Content-Type: text/xml
840 * Date: <rfc1123-date>
841 *
842 * Header lines must end with \r\n
843 * Per RFC 2616, content-length: is not required but connection:close
844 * would appear to be required (given that we will be closing it!).
845 */
846static void web_connection_parse_post(struct upnp_wps_device_sm *sm,
847				      struct sockaddr_in *cli,
848				      struct http_request *req,
849				      const char *filename)
850{
851	enum http_reply_code ret;
852	char *data = http_request_get_data(req); /* body of http msg */
853	const char *action = NULL;
854	size_t action_len = 0;
855	const char *replyname = NULL; /* argument name for the reply */
856	struct wpabuf *reply = NULL; /* data for the reply */
857
858	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
859		wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
860			   filename);
861		ret = HTTP_NOT_FOUND;
862		goto bad;
863	}
864
865	ret = UPNP_INVALID_ACTION;
866	action = web_get_action(req, &action_len);
867	if (action == NULL)
868		goto bad;
869
870	if (!os_strncasecmp("GetDeviceInfo", action, action_len))
871		ret = web_process_get_device_info(sm, &reply, &replyname);
872	else if (!os_strncasecmp("PutMessage", action, action_len))
873		ret = web_process_put_message(sm, data, &reply, &replyname);
874	else if (!os_strncasecmp("PutWLANResponse", action, action_len))
875		ret = web_process_put_wlan_response(sm, data, &reply,
876						    &replyname);
877	else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
878		ret = web_process_set_selected_registrar(sm, cli, data, &reply,
879							 &replyname);
880	else
881		wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
882
883bad:
884	if (ret != HTTP_OK)
885		wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
886	web_connection_send_reply(req, ret, action, action_len, reply,
887				  replyname);
888	wpabuf_free(reply);
889}
890
891
892/* Given that we have received a header w/ SUBSCRIBE, act upon it
893 *
894 * Format of SUBSCRIBE (case-insensitive):
895 *
896 * First line must be:
897 *      SUBSCRIBE /wps_event HTTP/1.1
898 *
899 * Our response (if no error) which includes only required lines is:
900 * HTTP/1.1 200 OK
901 * Server: xx, UPnP/1.0, xx
902 * SID: uuid:xxxxxxxxx
903 * Timeout: Second-<n>
904 * Content-Length: 0
905 * Date: xxxx
906 *
907 * Header lines must end with \r\n
908 * Per RFC 2616, content-length: is not required but connection:close
909 * would appear to be required (given that we will be closing it!).
910 */
911static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
912					   struct http_request *req,
913					   const char *filename)
914{
915	struct wpabuf *buf;
916	char *b;
917	char *hdr = http_request_get_hdr(req);
918	char *h;
919	char *match;
920	int match_len;
921	char *end;
922	int len;
923	int got_nt = 0;
924	u8 uuid[UUID_LEN];
925	int got_uuid = 0;
926	char *callback_urls = NULL;
927	struct subscription *s = NULL;
928	enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
929
930	buf = wpabuf_alloc(1000);
931	if (buf == NULL) {
932		http_request_deinit(req);
933		return;
934	}
935
936	wpa_hexdump_ascii(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE",
937			  (u8 *) hdr, os_strlen(hdr));
938
939	/* Parse/validate headers */
940	h = hdr;
941	/* First line: SUBSCRIBE /wps_event HTTP/1.1
942	 * has already been parsed.
943	 */
944	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
945		ret = HTTP_PRECONDITION_FAILED;
946		goto error;
947	}
948	wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
949	end = os_strchr(h, '\n');
950
951	for (; end != NULL; h = end + 1) {
952		/* Option line by option line */
953		h = end + 1;
954		end = os_strchr(h, '\n');
955		if (end == NULL)
956			break; /* no unterminated lines allowed */
957
958		/* NT assures that it is our type of subscription;
959		 * not used for a renewl.
960		 **/
961		match = "NT:";
962		match_len = os_strlen(match);
963		if (os_strncasecmp(h, match, match_len) == 0) {
964			h += match_len;
965			while (*h == ' ' || *h == '\t')
966				h++;
967			match = "upnp:event";
968			match_len = os_strlen(match);
969			if (os_strncasecmp(h, match, match_len) != 0) {
970				ret = HTTP_BAD_REQUEST;
971				goto error;
972			}
973			got_nt = 1;
974			continue;
975		}
976		/* HOST should refer to us */
977#if 0
978		match = "HOST:";
979		match_len = os_strlen(match);
980		if (os_strncasecmp(h, match, match_len) == 0) {
981			h += match_len;
982			while (*h == ' ' || *h == '\t')
983				h++;
984			.....
985		}
986#endif
987		/* CALLBACK gives one or more URLs for NOTIFYs
988		 * to be sent as a result of the subscription.
989		 * Each URL is enclosed in angle brackets.
990		 */
991		match = "CALLBACK:";
992		match_len = os_strlen(match);
993		if (os_strncasecmp(h, match, match_len) == 0) {
994			h += match_len;
995			while (*h == ' ' || *h == '\t')
996				h++;
997			len = end - h;
998			os_free(callback_urls);
999			callback_urls = os_malloc(len + 1);
1000			if (callback_urls == NULL) {
1001				ret = HTTP_INTERNAL_SERVER_ERROR;
1002				goto error;
1003			}
1004			os_memcpy(callback_urls, h, len);
1005			callback_urls[len] = 0;
1006			continue;
1007		}
1008		/* SID is only for renewal */
1009		match = "SID:";
1010		match_len = os_strlen(match);
1011		if (os_strncasecmp(h, match, match_len) == 0) {
1012			h += match_len;
1013			while (*h == ' ' || *h == '\t')
1014				h++;
1015			match = "uuid:";
1016			match_len = os_strlen(match);
1017			if (os_strncasecmp(h, match, match_len) != 0) {
1018				ret = HTTP_BAD_REQUEST;
1019				goto error;
1020			}
1021			h += match_len;
1022			while (*h == ' ' || *h == '\t')
1023				h++;
1024			if (uuid_str2bin(h, uuid)) {
1025				ret = HTTP_BAD_REQUEST;
1026				goto error;
1027			}
1028			got_uuid = 1;
1029			continue;
1030		}
1031		/* TIMEOUT is requested timeout, but apparently we can
1032		 * just ignore this.
1033		 */
1034	}
1035
1036	if (got_uuid) {
1037		/* renewal */
1038		wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewal");
1039		if (callback_urls) {
1040			ret = HTTP_BAD_REQUEST;
1041			goto error;
1042		}
1043		s = subscription_renew(sm, uuid);
1044		if (s == NULL) {
1045			char str[80];
1046			uuid_bin2str(uuid, str, sizeof(str));
1047			wpa_printf(MSG_DEBUG, "WPS UPnP: Could not find "
1048				   "SID %s", str);
1049			ret = HTTP_PRECONDITION_FAILED;
1050			goto error;
1051		}
1052	} else if (callback_urls) {
1053		wpa_printf(MSG_DEBUG, "WPS UPnP: New subscription");
1054		if (!got_nt) {
1055			ret = HTTP_PRECONDITION_FAILED;
1056			goto error;
1057		}
1058		s = subscription_start(sm, callback_urls);
1059		if (s == NULL) {
1060			ret = HTTP_INTERNAL_SERVER_ERROR;
1061			goto error;
1062		}
1063	} else {
1064		ret = HTTP_PRECONDITION_FAILED;
1065		goto error;
1066	}
1067
1068	/* success */
1069	http_put_reply_code(buf, HTTP_OK);
1070	wpabuf_put_str(buf, http_server_hdr);
1071	wpabuf_put_str(buf, http_connection_close);
1072	wpabuf_put_str(buf, "Content-Length: 0\r\n");
1073	wpabuf_put_str(buf, "SID: uuid:");
1074	/* subscription id */
1075	b = wpabuf_put(buf, 0);
1076	uuid_bin2str(s->uuid, b, 80);
1077	wpa_printf(MSG_DEBUG, "WPS UPnP: Assigned SID %s", b);
1078	wpabuf_put(buf, os_strlen(b));
1079	wpabuf_put_str(buf, "\r\n");
1080	wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
1081	http_put_date(buf);
1082	/* And empty line to terminate header: */
1083	wpabuf_put_str(buf, "\r\n");
1084
1085	os_free(callback_urls);
1086	http_request_send_and_deinit(req, buf);
1087	return;
1088
1089error:
1090	/* Per UPnP spec:
1091	* Errors
1092	* Incompatible headers
1093	*   400 Bad Request. If SID header and one of NT or CALLBACK headers
1094	*     are present, the publisher must respond with HTTP error
1095	*     400 Bad Request.
1096	* Missing or invalid CALLBACK
1097	*   412 Precondition Failed. If CALLBACK header is missing or does not
1098	*     contain a valid HTTP URL, the publisher must respond with HTTP
1099	*     error 412 Precondition Failed.
1100	* Invalid NT
1101	*   412 Precondition Failed. If NT header does not equal upnp:event,
1102	*     the publisher must respond with HTTP error 412 Precondition
1103	*     Failed.
1104	* [For resubscription, use 412 if unknown uuid].
1105	* Unable to accept subscription
1106	*   5xx. If a publisher is not able to accept a subscription (such as
1107	*     due to insufficient resources), it must respond with a
1108	*     HTTP 500-series error code.
1109	*   599 Too many subscriptions (not a standard HTTP error)
1110	*/
1111	wpa_printf(MSG_DEBUG, "WPS UPnP: SUBSCRIBE failed - return %d", ret);
1112	http_put_empty(buf, ret);
1113	http_request_send_and_deinit(req, buf);
1114	os_free(callback_urls);
1115}
1116
1117
1118/* Given that we have received a header w/ UNSUBSCRIBE, act upon it
1119 *
1120 * Format of UNSUBSCRIBE (case-insensitive):
1121 *
1122 * First line must be:
1123 *      UNSUBSCRIBE /wps_event HTTP/1.1
1124 *
1125 * Our response (if no error) which includes only required lines is:
1126 * HTTP/1.1 200 OK
1127 * Content-Length: 0
1128 *
1129 * Header lines must end with \r\n
1130 * Per RFC 2616, content-length: is not required but connection:close
1131 * would appear to be required (given that we will be closing it!).
1132 */
1133static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
1134					     struct http_request *req,
1135					     const char *filename)
1136{
1137	struct wpabuf *buf;
1138	char *hdr = http_request_get_hdr(req);
1139	char *h;
1140	char *match;
1141	int match_len;
1142	char *end;
1143	u8 uuid[UUID_LEN];
1144	int got_uuid = 0;
1145	struct subscription *s = NULL;
1146	enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1147
1148	/* Parse/validate headers */
1149	h = hdr;
1150	/* First line: UNSUBSCRIBE /wps_event HTTP/1.1
1151	 * has already been parsed.
1152	 */
1153	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1154		ret = HTTP_PRECONDITION_FAILED;
1155		goto send_msg;
1156	}
1157	wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
1158	end = os_strchr(h, '\n');
1159
1160	for (; end != NULL; h = end + 1) {
1161		/* Option line by option line */
1162		h = end + 1;
1163		end = os_strchr(h, '\n');
1164		if (end == NULL)
1165			break; /* no unterminated lines allowed */
1166
1167		/* HOST should refer to us */
1168#if 0
1169		match = "HOST:";
1170		match_len = os_strlen(match);
1171		if (os_strncasecmp(h, match, match_len) == 0) {
1172			h += match_len;
1173			while (*h == ' ' || *h == '\t')
1174				h++;
1175			.....
1176		}
1177#endif
1178		/* SID is only for renewal */
1179		match = "SID:";
1180		match_len = os_strlen(match);
1181		if (os_strncasecmp(h, match, match_len) == 0) {
1182			h += match_len;
1183			while (*h == ' ' || *h == '\t')
1184				h++;
1185			match = "uuid:";
1186			match_len = os_strlen(match);
1187			if (os_strncasecmp(h, match, match_len) != 0) {
1188				ret = HTTP_BAD_REQUEST;
1189				goto send_msg;
1190			}
1191			h += match_len;
1192			while (*h == ' ' || *h == '\t')
1193				h++;
1194			if (uuid_str2bin(h, uuid)) {
1195				ret = HTTP_BAD_REQUEST;
1196				goto send_msg;
1197			}
1198			got_uuid = 1;
1199			continue;
1200		}
1201	}
1202
1203	if (got_uuid) {
1204		s = subscription_find(sm, uuid);
1205		if (s) {
1206			struct subscr_addr *sa;
1207			sa = dl_list_first(&s->addr_list, struct subscr_addr,
1208					   list);
1209			wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
1210				   s, (sa && sa->domain_and_port) ?
1211				   sa->domain_and_port : "-null-");
1212			dl_list_del(&s->list);
1213			subscription_destroy(s);
1214		}
1215	} else {
1216		wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
1217			   "found)");
1218		ret = HTTP_PRECONDITION_FAILED;
1219		goto send_msg;
1220	}
1221
1222	ret = HTTP_OK;
1223
1224send_msg:
1225	buf = wpabuf_alloc(200);
1226	if (buf == NULL) {
1227		http_request_deinit(req);
1228		return;
1229	}
1230	http_put_empty(buf, ret);
1231	http_request_send_and_deinit(req, buf);
1232}
1233
1234
1235/* Send error in response to unknown requests */
1236static void web_connection_unimplemented(struct http_request *req)
1237{
1238	struct wpabuf *buf;
1239	buf = wpabuf_alloc(200);
1240	if (buf == NULL) {
1241		http_request_deinit(req);
1242		return;
1243	}
1244	http_put_empty(buf, HTTP_UNIMPLEMENTED);
1245	http_request_send_and_deinit(req, buf);
1246}
1247
1248
1249
1250/* Called when we have gotten an apparently valid http request.
1251 */
1252static void web_connection_check_data(void *ctx, struct http_request *req)
1253{
1254	struct upnp_wps_device_sm *sm = ctx;
1255	enum httpread_hdr_type htype = http_request_get_type(req);
1256	char *filename = http_request_get_uri(req);
1257	struct sockaddr_in *cli = http_request_get_cli_addr(req);
1258
1259	if (!filename) {
1260		wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
1261		http_request_deinit(req);
1262		return;
1263	}
1264	/* Trim leading slashes from filename */
1265	while (*filename == '/')
1266		filename++;
1267
1268	wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
1269		   htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port));
1270
1271	switch (htype) {
1272	case HTTPREAD_HDR_TYPE_GET:
1273		web_connection_parse_get(sm, req, filename);
1274		break;
1275	case HTTPREAD_HDR_TYPE_POST:
1276		web_connection_parse_post(sm, cli, req, filename);
1277		break;
1278	case HTTPREAD_HDR_TYPE_SUBSCRIBE:
1279		web_connection_parse_subscribe(sm, req, filename);
1280		break;
1281	case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
1282		web_connection_parse_unsubscribe(sm, req, filename);
1283		break;
1284
1285		/* We are not required to support M-POST; just plain
1286		 * POST is supposed to work, so we only support that.
1287		 * If for some reason we need to support M-POST, it is
1288		 * mostly the same as POST, with small differences.
1289		 */
1290	default:
1291		/* Send 501 for anything else */
1292		web_connection_unimplemented(req);
1293		break;
1294	}
1295}
1296
1297
1298/*
1299 * Listening for web connections
1300 * We have a single TCP listening port, and hand off connections as we get
1301 * them.
1302 */
1303
1304void web_listener_stop(struct upnp_wps_device_sm *sm)
1305{
1306	http_server_deinit(sm->web_srv);
1307	sm->web_srv = NULL;
1308}
1309
1310
1311int web_listener_start(struct upnp_wps_device_sm *sm)
1312{
1313	struct in_addr addr;
1314	addr.s_addr = sm->ip_addr;
1315	sm->web_srv = http_server_init(&addr, -1, web_connection_check_data,
1316				       sm);
1317	if (sm->web_srv == NULL) {
1318		web_listener_stop(sm);
1319		return -1;
1320	}
1321	sm->web_port = http_server_get_port(sm->web_srv);
1322
1323	return 0;
1324}
1325