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