wps_upnp_web.c revision f21452aea786ac056eb01f1cbba4f553bd502747
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 (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
328		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
329		req = GET_DEVICE_XML_FILE;
330		extra_len = 3000;
331		if (iface->wps->friendly_name)
332			extra_len += os_strlen(iface->wps->friendly_name);
333		if (iface->wps->manufacturer_url)
334			extra_len += os_strlen(iface->wps->manufacturer_url);
335		if (iface->wps->model_description)
336			extra_len += os_strlen(iface->wps->model_description);
337		if (iface->wps->model_url)
338			extra_len += os_strlen(iface->wps->model_url);
339		if (iface->wps->upc)
340			extra_len += os_strlen(iface->wps->upc);
341	} else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
342		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
343		req = GET_SCPD_XML_FILE;
344		extra_len = os_strlen(wps_scpd_xml);
345	} else {
346		/* File not found */
347		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
348			   filename);
349		buf = wpabuf_alloc(200);
350		if (buf == NULL) {
351			http_request_deinit(hreq);
352			return;
353		}
354		wpabuf_put_str(buf,
355			       "HTTP/1.1 404 Not Found\r\n"
356			       "Connection: close\r\n");
357
358		http_put_date(buf);
359
360		/* terminating empty line */
361		wpabuf_put_str(buf, "\r\n");
362
363		goto send_buf;
364	}
365
366	buf = wpabuf_alloc(1000 + extra_len);
367	if (buf == NULL) {
368		http_request_deinit(hreq);
369		return;
370	}
371
372	wpabuf_put_str(buf,
373		       "HTTP/1.1 200 OK\r\n"
374		       "Content-Type: text/xml; charset=\"utf-8\"\r\n");
375	wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
376	wpabuf_put_str(buf, "Connection: close\r\n");
377	wpabuf_put_str(buf, "Content-Length: ");
378	/*
379	 * We will paste the length in later, leaving some extra whitespace.
380	 * HTTP code is supposed to be tolerant of extra whitespace.
381	 */
382	put_length_here = wpabuf_put(buf, 0);
383	wpabuf_put_str(buf, "        \r\n");
384
385	http_put_date(buf);
386
387	/* terminating empty line */
388	wpabuf_put_str(buf, "\r\n");
389
390	body_start = wpabuf_put(buf, 0);
391
392	switch (req) {
393	case GET_DEVICE_XML_FILE:
394		format_wps_device_xml(sm, buf);
395		break;
396	case GET_SCPD_XML_FILE:
397		wpabuf_put_str(buf, wps_scpd_xml);
398		break;
399	}
400
401	/* Now patch in the content length at the end */
402	body_length = (char *) wpabuf_put(buf, 0) - body_start;
403	os_snprintf(len_buf, 10, "%d", body_length);
404	os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
405
406send_buf:
407	http_request_send_and_deinit(hreq, buf);
408}
409
410
411static enum http_reply_code
412web_process_get_device_info(struct upnp_wps_device_sm *sm,
413			    struct wpabuf **reply, const char **replyname)
414{
415	static const char *name = "NewDeviceInfo";
416	struct wps_config cfg;
417	struct upnp_wps_device_interface *iface;
418	struct upnp_wps_peer *peer;
419
420	iface = dl_list_first(&sm->interfaces,
421			      struct upnp_wps_device_interface, list);
422	peer = &iface->peer;
423
424	wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
425
426	if (iface->ctx->ap_pin == NULL)
427		return HTTP_INTERNAL_SERVER_ERROR;
428
429	/*
430	 * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS
431	 * registration over UPnP with the AP acting as an Enrollee. It should
432	 * be noted that this is frequently used just to get the device data,
433	 * i.e., there may not be any intent to actually complete the
434	 * registration.
435	 */
436
437	if (peer->wps)
438		wps_deinit(peer->wps);
439
440	os_memset(&cfg, 0, sizeof(cfg));
441	cfg.wps = iface->wps;
442	cfg.pin = (u8 *) iface->ctx->ap_pin;
443	cfg.pin_len = os_strlen(iface->ctx->ap_pin);
444	peer->wps = wps_init(&cfg);
445	if (peer->wps) {
446		enum wsc_op_code op_code;
447		*reply = wps_get_msg(peer->wps, &op_code);
448		if (*reply == NULL) {
449			wps_deinit(peer->wps);
450			peer->wps = NULL;
451		}
452	} else
453		*reply = NULL;
454	if (*reply == NULL) {
455		wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
456		return HTTP_INTERNAL_SERVER_ERROR;
457	}
458	*replyname = name;
459	return HTTP_OK;
460}
461
462
463static enum http_reply_code
464web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
465			struct wpabuf **reply, const char **replyname)
466{
467	struct wpabuf *msg;
468	static const char *name = "NewOutMessage";
469	enum http_reply_code ret;
470	enum wps_process_res res;
471	enum wsc_op_code op_code;
472	struct upnp_wps_device_interface *iface;
473
474	iface = dl_list_first(&sm->interfaces,
475			      struct upnp_wps_device_interface, list);
476
477	/*
478	 * PutMessage is used by external UPnP-based Registrar to perform WPS
479	 * operation with the access point itself; as compared with
480	 * PutWLANResponse which is for proxying.
481	 */
482	wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
483	msg = xml_get_base64_item(data, "NewInMessage", &ret);
484	if (msg == NULL)
485		return ret;
486	res = wps_process_msg(iface->peer.wps, WSC_UPnP, msg);
487	if (res == WPS_FAILURE)
488		*reply = NULL;
489	else
490		*reply = wps_get_msg(iface->peer.wps, &op_code);
491	wpabuf_free(msg);
492	if (*reply == NULL)
493		return HTTP_INTERNAL_SERVER_ERROR;
494	*replyname = name;
495	return HTTP_OK;
496}
497
498
499static enum http_reply_code
500web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
501			      struct wpabuf **reply, const char **replyname)
502{
503	struct wpabuf *msg;
504	enum http_reply_code ret;
505	u8 macaddr[ETH_ALEN];
506	int ev_type;
507	int type;
508	char *val;
509	struct upnp_wps_device_interface *iface;
510	int ok = 0;
511
512	/*
513	 * External UPnP-based Registrar is passing us a message to be proxied
514	 * over to a Wi-Fi -based client of ours.
515	 */
516
517	wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
518	msg = xml_get_base64_item(data, "NewMessage", &ret);
519	if (msg == NULL) {
520		wpa_printf(MSG_DEBUG, "WPS UPnP: Could not extract NewMessage "
521			   "from PutWLANResponse");
522		return ret;
523	}
524	val = xml_get_first_item(data, "NewWLANEventType");
525	if (val == NULL) {
526		wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventType in "
527			   "PutWLANResponse");
528		wpabuf_free(msg);
529		return UPNP_ARG_VALUE_INVALID;
530	}
531	ev_type = atol(val);
532	os_free(val);
533	val = xml_get_first_item(data, "NewWLANEventMAC");
534	if (val == NULL) {
535		wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventMAC in "
536			   "PutWLANResponse");
537		wpabuf_free(msg);
538		return UPNP_ARG_VALUE_INVALID;
539	}
540	if (hwaddr_aton(val, macaddr)) {
541		wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid NewWLANEventMAC in "
542			   "PutWLANResponse: '%s'", val);
543#ifdef CONFIG_WPS_STRICT
544		{
545			struct wps_parse_attr attr;
546			if (wps_parse_msg(msg, &attr) < 0 || attr.version2) {
547				wpabuf_free(msg);
548				os_free(val);
549				return UPNP_ARG_VALUE_INVALID;
550			}
551		}
552#endif /* CONFIG_WPS_STRICT */
553		if (hwaddr_aton2(val, macaddr) > 0) {
554			/*
555			 * At least some versions of Intel PROset seem to be
556			 * using dot-deliminated MAC address format here.
557			 */
558			wpa_printf(MSG_DEBUG, "WPS UPnP: Workaround - allow "
559				   "incorrect MAC address format in "
560				   "NewWLANEventMAC: %s -> " MACSTR,
561				   val, MAC2STR(macaddr));
562		} else {
563			wpabuf_free(msg);
564			os_free(val);
565			return UPNP_ARG_VALUE_INVALID;
566		}
567	}
568	os_free(val);
569	if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
570		struct wps_parse_attr attr;
571		if (wps_parse_msg(msg, &attr) < 0 ||
572		    attr.msg_type == NULL)
573			type = -1;
574		else
575			type = *attr.msg_type;
576		wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
577	} else
578		type = -1;
579	dl_list_for_each(iface, &sm->interfaces,
580			 struct upnp_wps_device_interface, list) {
581		if (iface->ctx->rx_req_put_wlan_response &&
582		    iface->ctx->rx_req_put_wlan_response(iface->priv, ev_type,
583							 macaddr, msg, type)
584		    == 0)
585			ok = 1;
586	}
587
588	if (!ok) {
589		wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
590			   "rx_req_put_wlan_response");
591		wpabuf_free(msg);
592		return HTTP_INTERNAL_SERVER_ERROR;
593	}
594	wpabuf_free(msg);
595	*replyname = NULL;
596	*reply = NULL;
597	return HTTP_OK;
598}
599
600
601static int find_er_addr(struct subscription *s, struct sockaddr_in *cli)
602{
603	struct subscr_addr *a;
604
605	dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) {
606		if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr)
607			return 1;
608	}
609	return 0;
610}
611
612
613static struct subscription * find_er(struct upnp_wps_device_sm *sm,
614				     struct sockaddr_in *cli)
615{
616	struct subscription *s;
617	dl_list_for_each(s, &sm->subscriptions, struct subscription, list)
618		if (find_er_addr(s, cli))
619			return s;
620	return NULL;
621}
622
623
624static enum http_reply_code
625web_process_set_selected_registrar(struct upnp_wps_device_sm *sm,
626				   struct sockaddr_in *cli, char *data,
627				   struct wpabuf **reply,
628				   const char **replyname)
629{
630	struct wpabuf *msg;
631	enum http_reply_code ret;
632	struct subscription *s;
633	struct upnp_wps_device_interface *iface;
634	int err = 0;
635
636	wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
637	s = find_er(sm, cli);
638	if (s == NULL) {
639		wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar "
640			   "from unknown ER");
641		return UPNP_ACTION_FAILED;
642	}
643	msg = xml_get_base64_item(data, "NewMessage", &ret);
644	if (msg == NULL)
645		return ret;
646	dl_list_for_each(iface, &sm->interfaces,
647			 struct upnp_wps_device_interface, list) {
648		if (upnp_er_set_selected_registrar(iface->wps->registrar, s,
649						   msg))
650			err = 1;
651	}
652	wpabuf_free(msg);
653	if (err)
654		return HTTP_INTERNAL_SERVER_ERROR;
655	*replyname = NULL;
656	*reply = NULL;
657	return HTTP_OK;
658}
659
660
661static const char *soap_prefix =
662	"<?xml version=\"1.0\"?>\n"
663	"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
664	"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
665	"<s:Body>\n";
666static const char *soap_postfix =
667	"</s:Body>\n</s:Envelope>\n";
668
669static const char *soap_error_prefix =
670	"<s:Fault>\n"
671	"<faultcode>s:Client</faultcode>\n"
672	"<faultstring>UPnPError</faultstring>\n"
673	"<detail>\n"
674	"<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
675static const char *soap_error_postfix =
676	"<errorDescription>Error</errorDescription>\n"
677	"</UPnPError>\n"
678	"</detail>\n"
679	"</s:Fault>\n";
680
681static void web_connection_send_reply(struct http_request *req,
682				      enum http_reply_code ret,
683				      const char *action, int action_len,
684				      const struct wpabuf *reply,
685				      const char *replyname)
686{
687	struct wpabuf *buf;
688	char *replydata;
689	char *put_length_here = NULL;
690	char *body_start = NULL;
691
692	if (reply) {
693		size_t len;
694		replydata = (char *) base64_encode(wpabuf_head(reply),
695						   wpabuf_len(reply), &len);
696	} else
697		replydata = NULL;
698
699	/* Parameters of the response:
700	 *      action(action_len) -- action we are responding to
701	 *      replyname -- a name we need for the reply
702	 *      replydata -- NULL or null-terminated string
703	 */
704	buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
705			   (action_len > 0 ? action_len * 2 : 0));
706	if (buf == NULL) {
707		wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
708			   "POST");
709		os_free(replydata);
710		http_request_deinit(req);
711		return;
712	}
713
714	/*
715	 * Assuming we will be successful, put in the output header first.
716	 * Note: we do not keep connections alive (and httpread does
717	 * not support it)... therefore we must have Connection: close.
718	 */
719	if (ret == HTTP_OK) {
720		wpabuf_put_str(buf,
721			       "HTTP/1.1 200 OK\r\n"
722			       "Content-Type: text/xml; "
723			       "charset=\"utf-8\"\r\n");
724	} else {
725		wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
726	}
727	wpabuf_put_str(buf, http_connection_close);
728
729	wpabuf_put_str(buf, "Content-Length: ");
730	/*
731	 * We will paste the length in later, leaving some extra whitespace.
732	 * HTTP code is supposed to be tolerant of extra whitespace.
733	 */
734	put_length_here = wpabuf_put(buf, 0);
735	wpabuf_put_str(buf, "        \r\n");
736
737	http_put_date(buf);
738
739	/* terminating empty line */
740	wpabuf_put_str(buf, "\r\n");
741
742	body_start = wpabuf_put(buf, 0);
743
744	if (ret == HTTP_OK) {
745		wpabuf_put_str(buf, soap_prefix);
746		wpabuf_put_str(buf, "<u:");
747		wpabuf_put_data(buf, action, action_len);
748		wpabuf_put_str(buf, "Response xmlns:u=\"");
749		wpabuf_put_str(buf, urn_wfawlanconfig);
750		wpabuf_put_str(buf, "\">\n");
751		if (replydata && replyname) {
752			/* TODO: might possibly need to escape part of reply
753			 * data? ...
754			 * probably not, unlikely to have ampersand(&) or left
755			 * angle bracket (<) in it...
756			 */
757			wpabuf_printf(buf, "<%s>", replyname);
758			wpabuf_put_str(buf, replydata);
759			wpabuf_printf(buf, "</%s>\n", replyname);
760		}
761		wpabuf_put_str(buf, "</u:");
762		wpabuf_put_data(buf, action, action_len);
763		wpabuf_put_str(buf, "Response>\n");
764		wpabuf_put_str(buf, soap_postfix);
765	} else {
766		/* Error case */
767		wpabuf_put_str(buf, soap_prefix);
768		wpabuf_put_str(buf, soap_error_prefix);
769		wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
770		wpabuf_put_str(buf, soap_error_postfix);
771		wpabuf_put_str(buf, soap_postfix);
772	}
773	os_free(replydata);
774
775	/* Now patch in the content length at the end */
776	if (body_start && put_length_here) {
777		int body_length = (char *) wpabuf_put(buf, 0) - body_start;
778		char len_buf[10];
779		os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
780		os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
781	}
782
783	http_request_send_and_deinit(req, buf);
784}
785
786
787static const char * web_get_action(struct http_request *req,
788				   size_t *action_len)
789{
790	const char *match;
791	int match_len;
792	char *b;
793	char *action;
794
795	*action_len = 0;
796	/* The SOAPAction line of the header tells us what we want to do */
797	b = http_request_get_hdr_line(req, "SOAPAction:");
798	if (b == NULL)
799		return NULL;
800	if (*b == '"')
801		b++;
802	else
803		return NULL;
804	match = urn_wfawlanconfig;
805	match_len = os_strlen(urn_wfawlanconfig) - 1;
806	if (os_strncasecmp(b, match, match_len))
807		return NULL;
808	b += match_len;
809	/* skip over version */
810	while (isgraph(*b) && *b != '#')
811		b++;
812	if (*b != '#')
813		return NULL;
814	b++;
815	/* Following the sharp(#) should be the action and a double quote */
816	action = b;
817	while (isgraph(*b) && *b != '"')
818		b++;
819	if (*b != '"')
820		return NULL;
821	*action_len = b - action;
822	return action;
823}
824
825
826/* Given that we have received a header w/ POST, act upon it
827 *
828 * Format of POST (case-insensitive):
829 *
830 * First line must be:
831 *      POST /<file> HTTP/1.1
832 * Since we don't do anything fancy we just ignore other lines.
833 *
834 * Our response (if no error) which includes only required lines is:
835 * HTTP/1.1 200 OK
836 * Connection: close
837 * Content-Type: text/xml
838 * Date: <rfc1123-date>
839 *
840 * Header lines must end with \r\n
841 * Per RFC 2616, content-length: is not required but connection:close
842 * would appear to be required (given that we will be closing it!).
843 */
844static void web_connection_parse_post(struct upnp_wps_device_sm *sm,
845				      struct sockaddr_in *cli,
846				      struct http_request *req,
847				      const char *filename)
848{
849	enum http_reply_code ret;
850	char *data = http_request_get_data(req); /* body of http msg */
851	const char *action = NULL;
852	size_t action_len = 0;
853	const char *replyname = NULL; /* argument name for the reply */
854	struct wpabuf *reply = NULL; /* data for the reply */
855
856	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
857		wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
858			   filename);
859		ret = HTTP_NOT_FOUND;
860		goto bad;
861	}
862
863	ret = UPNP_INVALID_ACTION;
864	action = web_get_action(req, &action_len);
865	if (action == NULL)
866		goto bad;
867
868	if (!os_strncasecmp("GetDeviceInfo", action, action_len))
869		ret = web_process_get_device_info(sm, &reply, &replyname);
870	else if (!os_strncasecmp("PutMessage", action, action_len))
871		ret = web_process_put_message(sm, data, &reply, &replyname);
872	else if (!os_strncasecmp("PutWLANResponse", action, action_len))
873		ret = web_process_put_wlan_response(sm, data, &reply,
874						    &replyname);
875	else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
876		ret = web_process_set_selected_registrar(sm, cli, data, &reply,
877							 &replyname);
878	else
879		wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
880
881bad:
882	if (ret != HTTP_OK)
883		wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
884	web_connection_send_reply(req, ret, action, action_len, reply,
885				  replyname);
886	wpabuf_free(reply);
887}
888
889
890/* Given that we have received a header w/ SUBSCRIBE, act upon it
891 *
892 * Format of SUBSCRIBE (case-insensitive):
893 *
894 * First line must be:
895 *      SUBSCRIBE /wps_event HTTP/1.1
896 *
897 * Our response (if no error) which includes only required lines is:
898 * HTTP/1.1 200 OK
899 * Server: xx, UPnP/1.0, xx
900 * SID: uuid:xxxxxxxxx
901 * Timeout: Second-<n>
902 * Content-Length: 0
903 * Date: xxxx
904 *
905 * Header lines must end with \r\n
906 * Per RFC 2616, content-length: is not required but connection:close
907 * would appear to be required (given that we will be closing it!).
908 */
909static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
910					   struct http_request *req,
911					   const char *filename)
912{
913	struct wpabuf *buf;
914	char *b;
915	char *hdr = http_request_get_hdr(req);
916	char *h;
917	char *match;
918	int match_len;
919	char *end;
920	int len;
921	int got_nt = 0;
922	u8 uuid[UUID_LEN];
923	int got_uuid = 0;
924	char *callback_urls = NULL;
925	struct subscription *s = NULL;
926	enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
927
928	buf = wpabuf_alloc(1000);
929	if (buf == NULL) {
930		http_request_deinit(req);
931		return;
932	}
933
934	wpa_hexdump_ascii(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE",
935			  (u8 *) hdr, os_strlen(hdr));
936
937	/* Parse/validate headers */
938	h = hdr;
939	/* First line: SUBSCRIBE /wps_event HTTP/1.1
940	 * has already been parsed.
941	 */
942	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
943		ret = HTTP_PRECONDITION_FAILED;
944		goto error;
945	}
946	wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
947	end = os_strchr(h, '\n');
948
949	for (; end != NULL; h = end + 1) {
950		/* Option line by option line */
951		h = end + 1;
952		end = os_strchr(h, '\n');
953		if (end == NULL)
954			break; /* no unterminated lines allowed */
955
956		/* NT assures that it is our type of subscription;
957		 * not used for a renewal.
958		 **/
959		match = "NT:";
960		match_len = os_strlen(match);
961		if (os_strncasecmp(h, match, match_len) == 0) {
962			h += match_len;
963			while (*h == ' ' || *h == '\t')
964				h++;
965			match = "upnp:event";
966			match_len = os_strlen(match);
967			if (os_strncasecmp(h, match, match_len) != 0) {
968				ret = HTTP_BAD_REQUEST;
969				goto error;
970			}
971			got_nt = 1;
972			continue;
973		}
974		/* HOST should refer to us */
975#if 0
976		match = "HOST:";
977		match_len = os_strlen(match);
978		if (os_strncasecmp(h, match, match_len) == 0) {
979			h += match_len;
980			while (*h == ' ' || *h == '\t')
981				h++;
982			.....
983		}
984#endif
985		/* CALLBACK gives one or more URLs for NOTIFYs
986		 * to be sent as a result of the subscription.
987		 * Each URL is enclosed in angle brackets.
988		 */
989		match = "CALLBACK:";
990		match_len = os_strlen(match);
991		if (os_strncasecmp(h, match, match_len) == 0) {
992			h += match_len;
993			while (*h == ' ' || *h == '\t')
994				h++;
995			len = end - h;
996			os_free(callback_urls);
997			callback_urls = dup_binstr(h, len);
998			if (callback_urls == NULL) {
999				ret = HTTP_INTERNAL_SERVER_ERROR;
1000				goto error;
1001			}
1002			continue;
1003		}
1004		/* SID is only for renewal */
1005		match = "SID:";
1006		match_len = os_strlen(match);
1007		if (os_strncasecmp(h, match, match_len) == 0) {
1008			h += match_len;
1009			while (*h == ' ' || *h == '\t')
1010				h++;
1011			match = "uuid:";
1012			match_len = os_strlen(match);
1013			if (os_strncasecmp(h, match, match_len) != 0) {
1014				ret = HTTP_BAD_REQUEST;
1015				goto error;
1016			}
1017			h += match_len;
1018			while (*h == ' ' || *h == '\t')
1019				h++;
1020			if (uuid_str2bin(h, uuid)) {
1021				ret = HTTP_BAD_REQUEST;
1022				goto error;
1023			}
1024			got_uuid = 1;
1025			continue;
1026		}
1027		/* TIMEOUT is requested timeout, but apparently we can
1028		 * just ignore this.
1029		 */
1030	}
1031
1032	if (got_uuid) {
1033		/* renewal */
1034		wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewal");
1035		if (callback_urls) {
1036			ret = HTTP_BAD_REQUEST;
1037			goto error;
1038		}
1039		s = subscription_renew(sm, uuid);
1040		if (s == NULL) {
1041			char str[80];
1042			uuid_bin2str(uuid, str, sizeof(str));
1043			wpa_printf(MSG_DEBUG, "WPS UPnP: Could not find "
1044				   "SID %s", str);
1045			ret = HTTP_PRECONDITION_FAILED;
1046			goto error;
1047		}
1048	} else if (callback_urls) {
1049		wpa_printf(MSG_DEBUG, "WPS UPnP: New subscription");
1050		if (!got_nt) {
1051			ret = HTTP_PRECONDITION_FAILED;
1052			goto error;
1053		}
1054		s = subscription_start(sm, callback_urls);
1055		if (s == NULL) {
1056			ret = HTTP_INTERNAL_SERVER_ERROR;
1057			goto error;
1058		}
1059	} else {
1060		ret = HTTP_PRECONDITION_FAILED;
1061		goto error;
1062	}
1063
1064	/* success */
1065	http_put_reply_code(buf, HTTP_OK);
1066	wpabuf_put_str(buf, http_server_hdr);
1067	wpabuf_put_str(buf, http_connection_close);
1068	wpabuf_put_str(buf, "Content-Length: 0\r\n");
1069	wpabuf_put_str(buf, "SID: uuid:");
1070	/* subscription id */
1071	b = wpabuf_put(buf, 0);
1072	uuid_bin2str(s->uuid, b, 80);
1073	wpa_printf(MSG_DEBUG, "WPS UPnP: Assigned SID %s", b);
1074	wpabuf_put(buf, os_strlen(b));
1075	wpabuf_put_str(buf, "\r\n");
1076	wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
1077	http_put_date(buf);
1078	/* And empty line to terminate header: */
1079	wpabuf_put_str(buf, "\r\n");
1080
1081	os_free(callback_urls);
1082	http_request_send_and_deinit(req, buf);
1083	return;
1084
1085error:
1086	/* Per UPnP spec:
1087	* Errors
1088	* Incompatible headers
1089	*   400 Bad Request. If SID header and one of NT or CALLBACK headers
1090	*     are present, the publisher must respond with HTTP error
1091	*     400 Bad Request.
1092	* Missing or invalid CALLBACK
1093	*   412 Precondition Failed. If CALLBACK header is missing or does not
1094	*     contain a valid HTTP URL, the publisher must respond with HTTP
1095	*     error 412 Precondition Failed.
1096	* Invalid NT
1097	*   412 Precondition Failed. If NT header does not equal upnp:event,
1098	*     the publisher must respond with HTTP error 412 Precondition
1099	*     Failed.
1100	* [For resubscription, use 412 if unknown uuid].
1101	* Unable to accept subscription
1102	*   5xx. If a publisher is not able to accept a subscription (such as
1103	*     due to insufficient resources), it must respond with a
1104	*     HTTP 500-series error code.
1105	*   599 Too many subscriptions (not a standard HTTP error)
1106	*/
1107	wpa_printf(MSG_DEBUG, "WPS UPnP: SUBSCRIBE failed - return %d", ret);
1108	http_put_empty(buf, ret);
1109	http_request_send_and_deinit(req, buf);
1110	os_free(callback_urls);
1111}
1112
1113
1114/* Given that we have received a header w/ UNSUBSCRIBE, act upon it
1115 *
1116 * Format of UNSUBSCRIBE (case-insensitive):
1117 *
1118 * First line must be:
1119 *      UNSUBSCRIBE /wps_event HTTP/1.1
1120 *
1121 * Our response (if no error) which includes only required lines is:
1122 * HTTP/1.1 200 OK
1123 * Content-Length: 0
1124 *
1125 * Header lines must end with \r\n
1126 * Per RFC 2616, content-length: is not required but connection:close
1127 * would appear to be required (given that we will be closing it!).
1128 */
1129static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
1130					     struct http_request *req,
1131					     const char *filename)
1132{
1133	struct wpabuf *buf;
1134	char *hdr = http_request_get_hdr(req);
1135	char *h;
1136	char *match;
1137	int match_len;
1138	char *end;
1139	u8 uuid[UUID_LEN];
1140	int got_uuid = 0;
1141	struct subscription *s = NULL;
1142	enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1143
1144	/* Parse/validate headers */
1145	h = hdr;
1146	/* First line: UNSUBSCRIBE /wps_event HTTP/1.1
1147	 * has already been parsed.
1148	 */
1149	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1150		ret = HTTP_PRECONDITION_FAILED;
1151		goto send_msg;
1152	}
1153	wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
1154	end = os_strchr(h, '\n');
1155
1156	for (; end != NULL; h = end + 1) {
1157		/* Option line by option line */
1158		h = end + 1;
1159		end = os_strchr(h, '\n');
1160		if (end == NULL)
1161			break; /* no unterminated lines allowed */
1162
1163		/* HOST should refer to us */
1164#if 0
1165		match = "HOST:";
1166		match_len = os_strlen(match);
1167		if (os_strncasecmp(h, match, match_len) == 0) {
1168			h += match_len;
1169			while (*h == ' ' || *h == '\t')
1170				h++;
1171			.....
1172		}
1173#endif
1174		match = "SID:";
1175		match_len = os_strlen(match);
1176		if (os_strncasecmp(h, match, match_len) == 0) {
1177			h += match_len;
1178			while (*h == ' ' || *h == '\t')
1179				h++;
1180			match = "uuid:";
1181			match_len = os_strlen(match);
1182			if (os_strncasecmp(h, match, match_len) != 0) {
1183				ret = HTTP_BAD_REQUEST;
1184				goto send_msg;
1185			}
1186			h += match_len;
1187			while (*h == ' ' || *h == '\t')
1188				h++;
1189			if (uuid_str2bin(h, uuid)) {
1190				ret = HTTP_BAD_REQUEST;
1191				goto send_msg;
1192			}
1193			got_uuid = 1;
1194			continue;
1195		}
1196
1197		match = "NT:";
1198		match_len = os_strlen(match);
1199		if (os_strncasecmp(h, match, match_len) == 0) {
1200			ret = HTTP_BAD_REQUEST;
1201			goto send_msg;
1202		}
1203
1204		match = "CALLBACK:";
1205		match_len = os_strlen(match);
1206		if (os_strncasecmp(h, match, match_len) == 0) {
1207			ret = HTTP_BAD_REQUEST;
1208			goto send_msg;
1209		}
1210	}
1211
1212	if (got_uuid) {
1213		s = subscription_find(sm, uuid);
1214		if (s) {
1215			struct subscr_addr *sa;
1216			sa = dl_list_first(&s->addr_list, struct subscr_addr,
1217					   list);
1218			wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
1219				   s, (sa && sa->domain_and_port) ?
1220				   sa->domain_and_port : "-null-");
1221			dl_list_del(&s->list);
1222			subscription_destroy(s);
1223		} else {
1224			wpa_printf(MSG_INFO, "WPS UPnP: Could not find matching subscription to unsubscribe");
1225			ret = HTTP_PRECONDITION_FAILED;
1226			goto send_msg;
1227		}
1228	} else {
1229		wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
1230			   "found)");
1231		ret = HTTP_PRECONDITION_FAILED;
1232		goto send_msg;
1233	}
1234
1235	ret = HTTP_OK;
1236
1237send_msg:
1238	buf = wpabuf_alloc(200);
1239	if (buf == NULL) {
1240		http_request_deinit(req);
1241		return;
1242	}
1243	http_put_empty(buf, ret);
1244	http_request_send_and_deinit(req, buf);
1245}
1246
1247
1248/* Send error in response to unknown requests */
1249static void web_connection_unimplemented(struct http_request *req)
1250{
1251	struct wpabuf *buf;
1252	buf = wpabuf_alloc(200);
1253	if (buf == NULL) {
1254		http_request_deinit(req);
1255		return;
1256	}
1257	http_put_empty(buf, HTTP_UNIMPLEMENTED);
1258	http_request_send_and_deinit(req, buf);
1259}
1260
1261
1262
1263/* Called when we have gotten an apparently valid http request.
1264 */
1265static void web_connection_check_data(void *ctx, struct http_request *req)
1266{
1267	struct upnp_wps_device_sm *sm = ctx;
1268	enum httpread_hdr_type htype = http_request_get_type(req);
1269	char *filename = http_request_get_uri(req);
1270	struct sockaddr_in *cli = http_request_get_cli_addr(req);
1271
1272	if (!filename) {
1273		wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
1274		http_request_deinit(req);
1275		return;
1276	}
1277	/* Trim leading slashes from filename */
1278	while (*filename == '/')
1279		filename++;
1280
1281	wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
1282		   htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port));
1283
1284	switch (htype) {
1285	case HTTPREAD_HDR_TYPE_GET:
1286		web_connection_parse_get(sm, req, filename);
1287		break;
1288	case HTTPREAD_HDR_TYPE_POST:
1289		web_connection_parse_post(sm, cli, req, filename);
1290		break;
1291	case HTTPREAD_HDR_TYPE_SUBSCRIBE:
1292		web_connection_parse_subscribe(sm, req, filename);
1293		break;
1294	case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
1295		web_connection_parse_unsubscribe(sm, req, filename);
1296		break;
1297
1298		/* We are not required to support M-POST; just plain
1299		 * POST is supposed to work, so we only support that.
1300		 * If for some reason we need to support M-POST, it is
1301		 * mostly the same as POST, with small differences.
1302		 */
1303	default:
1304		/* Send 501 for anything else */
1305		web_connection_unimplemented(req);
1306		break;
1307	}
1308}
1309
1310
1311/*
1312 * Listening for web connections
1313 * We have a single TCP listening port, and hand off connections as we get
1314 * them.
1315 */
1316
1317void web_listener_stop(struct upnp_wps_device_sm *sm)
1318{
1319	http_server_deinit(sm->web_srv);
1320	sm->web_srv = NULL;
1321}
1322
1323
1324int web_listener_start(struct upnp_wps_device_sm *sm)
1325{
1326	struct in_addr addr;
1327	addr.s_addr = sm->ip_addr;
1328	sm->web_srv = http_server_init(&addr, -1, web_connection_check_data,
1329				       sm);
1330	if (sm->web_srv == NULL) {
1331		web_listener_stop(sm);
1332		return -1;
1333	}
1334	sm->web_port = http_server_get_port(sm->web_srv);
1335
1336	return 0;
1337}
1338