1189251Ssam/*
2189251Ssam * UPnP WPS Device - Web connections
3189251Ssam * Copyright (c) 2000-2003 Intel Corporation
4189251Ssam * Copyright (c) 2006-2007 Sony Corporation
5189251Ssam * Copyright (c) 2008-2009 Atheros Communications
6189251Ssam * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
7189251Ssam *
8189251Ssam * See wps_upnp.c for more details on licensing and code history.
9189251Ssam */
10189251Ssam
11189251Ssam#include "includes.h"
12189251Ssam
13189251Ssam#include "common.h"
14189251Ssam#include "base64.h"
15189251Ssam#include "uuid.h"
16189251Ssam#include "httpread.h"
17214734Srpaulo#include "http_server.h"
18189251Ssam#include "wps_i.h"
19189251Ssam#include "wps_upnp.h"
20189251Ssam#include "wps_upnp_i.h"
21214734Srpaulo#include "upnp_xml.h"
22189251Ssam
23189251Ssam/***************************************************************************
24189251Ssam * Web connections (we serve pages of info about ourselves, handle
25189251Ssam * requests, etc. etc.).
26189251Ssam **************************************************************************/
27189251Ssam
28189251Ssam#define WEB_CONNECTION_TIMEOUT_SEC 30   /* Drop web connection after t.o. */
29189251Ssam#define WEB_CONNECTION_MAX_READ 8000    /* Max we'll read for TCP request */
30189251Ssam#define MAX_WEB_CONNECTIONS 10          /* max simultaneous web connects */
31189251Ssam
32189251Ssam
33189251Ssamstatic const char *urn_wfawlanconfig =
34189251Ssam	"urn:schemas-wifialliance-org:service:WFAWLANConfig:1";
35189251Ssamstatic const char *http_server_hdr =
36189251Ssam	"Server: unspecified, UPnP/1.0, unspecified\r\n";
37189251Ssamstatic const char *http_connection_close =
38189251Ssam	"Connection: close\r\n";
39189251Ssam
40189251Ssam/*
41189251Ssam * "Files" that we serve via HTTP. The format of these files is given by
42189251Ssam * WFA WPS specifications. Extra white space has been removed to save space.
43189251Ssam */
44189251Ssam
45189251Ssamstatic const char wps_scpd_xml[] =
46189251Ssam"<?xml version=\"1.0\"?>\n"
47189251Ssam"<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\n"
48189251Ssam"<specVersion><major>1</major><minor>0</minor></specVersion>\n"
49189251Ssam"<actionList>\n"
50189251Ssam"<action>\n"
51189251Ssam"<name>GetDeviceInfo</name>\n"
52189251Ssam"<argumentList>\n"
53189251Ssam"<argument>\n"
54189251Ssam"<name>NewDeviceInfo</name>\n"
55189251Ssam"<direction>out</direction>\n"
56189251Ssam"<relatedStateVariable>DeviceInfo</relatedStateVariable>\n"
57189251Ssam"</argument>\n"
58189251Ssam"</argumentList>\n"
59189251Ssam"</action>\n"
60189251Ssam"<action>\n"
61189251Ssam"<name>PutMessage</name>\n"
62189251Ssam"<argumentList>\n"
63189251Ssam"<argument>\n"
64189251Ssam"<name>NewInMessage</name>\n"
65189251Ssam"<direction>in</direction>\n"
66189251Ssam"<relatedStateVariable>InMessage</relatedStateVariable>\n"
67189251Ssam"</argument>\n"
68189251Ssam"<argument>\n"
69189251Ssam"<name>NewOutMessage</name>\n"
70189251Ssam"<direction>out</direction>\n"
71189251Ssam"<relatedStateVariable>OutMessage</relatedStateVariable>\n"
72189251Ssam"</argument>\n"
73189251Ssam"</argumentList>\n"
74189251Ssam"</action>\n"
75189251Ssam"<action>\n"
76189251Ssam"<name>PutWLANResponse</name>\n"
77189251Ssam"<argumentList>\n"
78189251Ssam"<argument>\n"
79189251Ssam"<name>NewMessage</name>\n"
80189251Ssam"<direction>in</direction>\n"
81189251Ssam"<relatedStateVariable>Message</relatedStateVariable>\n"
82189251Ssam"</argument>\n"
83189251Ssam"<argument>\n"
84189251Ssam"<name>NewWLANEventType</name>\n"
85189251Ssam"<direction>in</direction>\n"
86189251Ssam"<relatedStateVariable>WLANEventType</relatedStateVariable>\n"
87189251Ssam"</argument>\n"
88189251Ssam"<argument>\n"
89189251Ssam"<name>NewWLANEventMAC</name>\n"
90189251Ssam"<direction>in</direction>\n"
91189251Ssam"<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n"
92189251Ssam"</argument>\n"
93189251Ssam"</argumentList>\n"
94189251Ssam"</action>\n"
95189251Ssam"<action>\n"
96189251Ssam"<name>SetSelectedRegistrar</name>\n"
97189251Ssam"<argumentList>\n"
98189251Ssam"<argument>\n"
99189251Ssam"<name>NewMessage</name>\n"
100189251Ssam"<direction>in</direction>\n"
101189251Ssam"<relatedStateVariable>Message</relatedStateVariable>\n"
102189251Ssam"</argument>\n"
103189251Ssam"</argumentList>\n"
104189251Ssam"</action>\n"
105189251Ssam"</actionList>\n"
106189251Ssam"<serviceStateTable>\n"
107189251Ssam"<stateVariable sendEvents=\"no\">\n"
108189251Ssam"<name>Message</name>\n"
109189251Ssam"<dataType>bin.base64</dataType>\n"
110189251Ssam"</stateVariable>\n"
111189251Ssam"<stateVariable sendEvents=\"no\">\n"
112189251Ssam"<name>InMessage</name>\n"
113189251Ssam"<dataType>bin.base64</dataType>\n"
114189251Ssam"</stateVariable>\n"
115189251Ssam"<stateVariable sendEvents=\"no\">\n"
116189251Ssam"<name>OutMessage</name>\n"
117189251Ssam"<dataType>bin.base64</dataType>\n"
118189251Ssam"</stateVariable>\n"
119189251Ssam"<stateVariable sendEvents=\"no\">\n"
120189251Ssam"<name>DeviceInfo</name>\n"
121189251Ssam"<dataType>bin.base64</dataType>\n"
122189251Ssam"</stateVariable>\n"
123189251Ssam"<stateVariable sendEvents=\"yes\">\n"
124189251Ssam"<name>APStatus</name>\n"
125189251Ssam"<dataType>ui1</dataType>\n"
126189251Ssam"</stateVariable>\n"
127189251Ssam"<stateVariable sendEvents=\"yes\">\n"
128189251Ssam"<name>STAStatus</name>\n"
129189251Ssam"<dataType>ui1</dataType>\n"
130189251Ssam"</stateVariable>\n"
131189251Ssam"<stateVariable sendEvents=\"yes\">\n"
132189251Ssam"<name>WLANEvent</name>\n"
133189251Ssam"<dataType>bin.base64</dataType>\n"
134189251Ssam"</stateVariable>\n"
135189251Ssam"<stateVariable sendEvents=\"no\">\n"
136189251Ssam"<name>WLANEventType</name>\n"
137189251Ssam"<dataType>ui1</dataType>\n"
138189251Ssam"</stateVariable>\n"
139189251Ssam"<stateVariable sendEvents=\"no\">\n"
140189251Ssam"<name>WLANEventMAC</name>\n"
141189251Ssam"<dataType>string</dataType>\n"
142189251Ssam"</stateVariable>\n"
143189251Ssam"<stateVariable sendEvents=\"no\">\n"
144189251Ssam"<name>WLANResponse</name>\n"
145189251Ssam"<dataType>bin.base64</dataType>\n"
146189251Ssam"</stateVariable>\n"
147189251Ssam"</serviceStateTable>\n"
148189251Ssam"</scpd>\n"
149189251Ssam;
150189251Ssam
151189251Ssam
152189251Ssamstatic const char *wps_device_xml_prefix =
153189251Ssam	"<?xml version=\"1.0\"?>\n"
154189251Ssam	"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
155189251Ssam	"<specVersion>\n"
156189251Ssam	"<major>1</major>\n"
157189251Ssam	"<minor>0</minor>\n"
158189251Ssam	"</specVersion>\n"
159189251Ssam	"<device>\n"
160189251Ssam	"<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1"
161189251Ssam	"</deviceType>\n";
162189251Ssam
163189251Ssamstatic const char *wps_device_xml_postfix =
164189251Ssam	"<serviceList>\n"
165189251Ssam	"<service>\n"
166189251Ssam	"<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1"
167189251Ssam	"</serviceType>\n"
168189251Ssam	"<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>"
169189251Ssam	"\n"
170189251Ssam	"<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n"
171189251Ssam	"<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n"
172189251Ssam	"<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n"
173189251Ssam	"</service>\n"
174189251Ssam	"</serviceList>\n"
175189251Ssam	"</device>\n"
176189251Ssam	"</root>\n";
177189251Ssam
178189251Ssam
179189251Ssam/* format_wps_device_xml -- produce content of "file" wps_device.xml
180189251Ssam * (UPNP_WPS_DEVICE_XML_FILE)
181189251Ssam */
182189251Ssamstatic void format_wps_device_xml(struct upnp_wps_device_sm *sm,
183189251Ssam				  struct wpabuf *buf)
184189251Ssam{
185189251Ssam	const char *s;
186189251Ssam	char uuid_string[80];
187189251Ssam
188189251Ssam	wpabuf_put_str(buf, wps_device_xml_prefix);
189189251Ssam
190189251Ssam	/*
191189251Ssam	 * Add required fields with default values if not configured. Add
192189251Ssam	 * optional and recommended fields only if configured.
193189251Ssam	 */
194189251Ssam	s = sm->wps->friendly_name;
195189251Ssam	s = ((s && *s) ? s : "WPS Access Point");
196189251Ssam	xml_add_tagged_data(buf, "friendlyName", s);
197189251Ssam
198189251Ssam	s = sm->wps->dev.manufacturer;
199189251Ssam	s = ((s && *s) ? s : "");
200189251Ssam	xml_add_tagged_data(buf, "manufacturer", s);
201189251Ssam
202189251Ssam	if (sm->wps->manufacturer_url)
203189251Ssam		xml_add_tagged_data(buf, "manufacturerURL",
204189251Ssam				    sm->wps->manufacturer_url);
205189251Ssam
206189251Ssam	if (sm->wps->model_description)
207189251Ssam		xml_add_tagged_data(buf, "modelDescription",
208189251Ssam				    sm->wps->model_description);
209189251Ssam
210189251Ssam	s = sm->wps->dev.model_name;
211189251Ssam	s = ((s && *s) ? s : "");
212189251Ssam	xml_add_tagged_data(buf, "modelName", s);
213189251Ssam
214189251Ssam	if (sm->wps->dev.model_number)
215189251Ssam		xml_add_tagged_data(buf, "modelNumber",
216189251Ssam				    sm->wps->dev.model_number);
217189251Ssam
218189251Ssam	if (sm->wps->model_url)
219189251Ssam		xml_add_tagged_data(buf, "modelURL", sm->wps->model_url);
220189251Ssam
221189251Ssam	if (sm->wps->dev.serial_number)
222189251Ssam		xml_add_tagged_data(buf, "serialNumber",
223189251Ssam				    sm->wps->dev.serial_number);
224189251Ssam
225189251Ssam	uuid_bin2str(sm->wps->uuid, uuid_string, sizeof(uuid_string));
226189251Ssam	s = uuid_string;
227189251Ssam	/* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
228189251Ssam	 * easily...
229189251Ssam	 */
230189251Ssam	wpabuf_put_str(buf, "<UDN>uuid:");
231189251Ssam	xml_data_encode(buf, s, os_strlen(s));
232189251Ssam	wpabuf_put_str(buf, "</UDN>\n");
233189251Ssam
234189251Ssam	if (sm->wps->upc)
235189251Ssam		xml_add_tagged_data(buf, "UPC", sm->wps->upc);
236189251Ssam
237189251Ssam	wpabuf_put_str(buf, wps_device_xml_postfix);
238189251Ssam}
239189251Ssam
240189251Ssam
241189251Ssamstatic void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
242189251Ssam{
243189251Ssam	wpabuf_put_str(buf, "HTTP/1.1 ");
244189251Ssam	switch (code) {
245189251Ssam	case HTTP_OK:
246189251Ssam		wpabuf_put_str(buf, "200 OK\r\n");
247189251Ssam		break;
248189251Ssam	case HTTP_BAD_REQUEST:
249189251Ssam		wpabuf_put_str(buf, "400 Bad request\r\n");
250189251Ssam		break;
251189251Ssam	case HTTP_PRECONDITION_FAILED:
252189251Ssam		wpabuf_put_str(buf, "412 Precondition failed\r\n");
253189251Ssam		break;
254189251Ssam	case HTTP_UNIMPLEMENTED:
255189251Ssam		wpabuf_put_str(buf, "501 Unimplemented\r\n");
256189251Ssam		break;
257189251Ssam	case HTTP_INTERNAL_SERVER_ERROR:
258189251Ssam	default:
259189251Ssam		wpabuf_put_str(buf, "500 Internal server error\r\n");
260189251Ssam		break;
261189251Ssam	}
262189251Ssam}
263189251Ssam
264189251Ssam
265189251Ssamstatic void http_put_date(struct wpabuf *buf)
266189251Ssam{
267189251Ssam	wpabuf_put_str(buf, "Date: ");
268189251Ssam	format_date(buf);
269189251Ssam	wpabuf_put_str(buf, "\r\n");
270189251Ssam}
271189251Ssam
272189251Ssam
273189251Ssamstatic void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
274189251Ssam{
275189251Ssam	http_put_reply_code(buf, code);
276189251Ssam	wpabuf_put_str(buf, http_server_hdr);
277189251Ssam	wpabuf_put_str(buf, http_connection_close);
278189251Ssam	wpabuf_put_str(buf, "Content-Length: 0\r\n"
279189251Ssam		       "\r\n");
280189251Ssam}
281189251Ssam
282189251Ssam
283189251Ssam/* Given that we have received a header w/ GET, act upon it
284189251Ssam *
285189251Ssam * Format of GET (case-insensitive):
286189251Ssam *
287189251Ssam * First line must be:
288189251Ssam *      GET /<file> HTTP/1.1
289189251Ssam * Since we don't do anything fancy we just ignore other lines.
290189251Ssam *
291189251Ssam * Our response (if no error) which includes only required lines is:
292189251Ssam * HTTP/1.1 200 OK
293189251Ssam * Connection: close
294189251Ssam * Content-Type: text/xml
295189251Ssam * Date: <rfc1123-date>
296189251Ssam *
297189251Ssam * Header lines must end with \r\n
298189251Ssam * Per RFC 2616, content-length: is not required but connection:close
299189251Ssam * would appear to be required (given that we will be closing it!).
300189251Ssam */
301214734Srpaulostatic void web_connection_parse_get(struct upnp_wps_device_sm *sm,
302214734Srpaulo				     struct http_request *hreq, char *filename)
303189251Ssam{
304189251Ssam	struct wpabuf *buf; /* output buffer, allocated */
305189251Ssam	char *put_length_here;
306189251Ssam	char *body_start;
307189251Ssam	enum {
308189251Ssam		GET_DEVICE_XML_FILE,
309189251Ssam		GET_SCPD_XML_FILE
310189251Ssam	} req;
311189251Ssam	size_t extra_len = 0;
312189251Ssam	int body_length;
313189251Ssam	char len_buf[10];
314189251Ssam
315189251Ssam	/*
316189251Ssam	 * It is not required that filenames be case insensitive but it is
317189251Ssam	 * allowed and cannot hurt here.
318189251Ssam	 */
319189251Ssam	if (filename == NULL)
320189251Ssam		filename = "(null)"; /* just in case */
321189251Ssam	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
322189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
323189251Ssam		req = GET_DEVICE_XML_FILE;
324189251Ssam		extra_len = 3000;
325189251Ssam		if (sm->wps->friendly_name)
326189251Ssam			extra_len += os_strlen(sm->wps->friendly_name);
327189251Ssam		if (sm->wps->manufacturer_url)
328189251Ssam			extra_len += os_strlen(sm->wps->manufacturer_url);
329189251Ssam		if (sm->wps->model_description)
330189251Ssam			extra_len += os_strlen(sm->wps->model_description);
331189251Ssam		if (sm->wps->model_url)
332189251Ssam			extra_len += os_strlen(sm->wps->model_url);
333189251Ssam		if (sm->wps->upc)
334189251Ssam			extra_len += os_strlen(sm->wps->upc);
335189251Ssam	} else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
336189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
337189251Ssam		req = GET_SCPD_XML_FILE;
338189251Ssam		extra_len = os_strlen(wps_scpd_xml);
339189251Ssam	} else {
340189251Ssam		/* File not found */
341189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
342189251Ssam			   filename);
343189251Ssam		buf = wpabuf_alloc(200);
344214734Srpaulo		if (buf == NULL) {
345214734Srpaulo			http_request_deinit(hreq);
346189251Ssam			return;
347214734Srpaulo		}
348189251Ssam		wpabuf_put_str(buf,
349189251Ssam			       "HTTP/1.1 404 Not Found\r\n"
350189251Ssam			       "Connection: close\r\n");
351189251Ssam
352189251Ssam		http_put_date(buf);
353189251Ssam
354189251Ssam		/* terminating empty line */
355189251Ssam		wpabuf_put_str(buf, "\r\n");
356189251Ssam
357189251Ssam		goto send_buf;
358189251Ssam	}
359189251Ssam
360189251Ssam	buf = wpabuf_alloc(1000 + extra_len);
361214734Srpaulo	if (buf == NULL) {
362214734Srpaulo		http_request_deinit(hreq);
363189251Ssam		return;
364214734Srpaulo	}
365189251Ssam
366189251Ssam	wpabuf_put_str(buf,
367189251Ssam		       "HTTP/1.1 200 OK\r\n"
368189251Ssam		       "Content-Type: text/xml; charset=\"utf-8\"\r\n");
369189251Ssam	wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
370189251Ssam	wpabuf_put_str(buf, "Connection: close\r\n");
371189251Ssam	wpabuf_put_str(buf, "Content-Length: ");
372189251Ssam	/*
373189251Ssam	 * We will paste the length in later, leaving some extra whitespace.
374189251Ssam	 * HTTP code is supposed to be tolerant of extra whitespace.
375189251Ssam	 */
376189251Ssam	put_length_here = wpabuf_put(buf, 0);
377189251Ssam	wpabuf_put_str(buf, "        \r\n");
378189251Ssam
379189251Ssam	http_put_date(buf);
380189251Ssam
381189251Ssam	/* terminating empty line */
382189251Ssam	wpabuf_put_str(buf, "\r\n");
383189251Ssam
384189251Ssam	body_start = wpabuf_put(buf, 0);
385189251Ssam
386189251Ssam	switch (req) {
387189251Ssam	case GET_DEVICE_XML_FILE:
388189251Ssam		format_wps_device_xml(sm, buf);
389189251Ssam		break;
390189251Ssam	case GET_SCPD_XML_FILE:
391189251Ssam		wpabuf_put_str(buf, wps_scpd_xml);
392189251Ssam		break;
393189251Ssam	}
394189251Ssam
395189251Ssam	/* Now patch in the content length at the end */
396189251Ssam	body_length = (char *) wpabuf_put(buf, 0) - body_start;
397189251Ssam	os_snprintf(len_buf, 10, "%d", body_length);
398189251Ssam	os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
399189251Ssam
400189251Ssamsend_buf:
401214734Srpaulo	http_request_send_and_deinit(hreq, buf);
402189251Ssam}
403189251Ssam
404189251Ssam
405189251Ssamstatic enum http_reply_code
406189251Ssamweb_process_get_device_info(struct upnp_wps_device_sm *sm,
407189251Ssam			    struct wpabuf **reply, const char **replyname)
408189251Ssam{
409189251Ssam	static const char *name = "NewDeviceInfo";
410214734Srpaulo	struct wps_config cfg;
411214734Srpaulo	struct upnp_wps_peer *peer = &sm->peer;
412189251Ssam
413189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
414214734Srpaulo
415214734Srpaulo	if (sm->ctx->ap_pin == NULL)
416189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
417214734Srpaulo
418214734Srpaulo	/*
419214734Srpaulo	 * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS
420214734Srpaulo	 * registration over UPnP with the AP acting as an Enrollee. It should
421214734Srpaulo	 * be noted that this is frequently used just to get the device data,
422214734Srpaulo	 * i.e., there may not be any intent to actually complete the
423214734Srpaulo	 * registration.
424214734Srpaulo	 */
425214734Srpaulo
426214734Srpaulo	if (peer->wps)
427214734Srpaulo		wps_deinit(peer->wps);
428214734Srpaulo
429214734Srpaulo	os_memset(&cfg, 0, sizeof(cfg));
430214734Srpaulo	cfg.wps = sm->wps;
431214734Srpaulo	cfg.pin = (u8 *) sm->ctx->ap_pin;
432214734Srpaulo	cfg.pin_len = os_strlen(sm->ctx->ap_pin);
433214734Srpaulo	peer->wps = wps_init(&cfg);
434214734Srpaulo	if (peer->wps) {
435214734Srpaulo		enum wsc_op_code op_code;
436214734Srpaulo		*reply = wps_get_msg(peer->wps, &op_code);
437214734Srpaulo		if (*reply == NULL) {
438214734Srpaulo			wps_deinit(peer->wps);
439214734Srpaulo			peer->wps = NULL;
440214734Srpaulo		}
441214734Srpaulo	} else
442214734Srpaulo		*reply = NULL;
443189251Ssam	if (*reply == NULL) {
444189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
445189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
446189251Ssam	}
447189251Ssam	*replyname = name;
448189251Ssam	return HTTP_OK;
449189251Ssam}
450189251Ssam
451189251Ssam
452189251Ssamstatic enum http_reply_code
453189251Ssamweb_process_put_message(struct upnp_wps_device_sm *sm, char *data,
454189251Ssam			struct wpabuf **reply, const char **replyname)
455189251Ssam{
456189251Ssam	struct wpabuf *msg;
457189251Ssam	static const char *name = "NewOutMessage";
458189251Ssam	enum http_reply_code ret;
459214734Srpaulo	enum wps_process_res res;
460214734Srpaulo	enum wsc_op_code op_code;
461189251Ssam
462189251Ssam	/*
463189251Ssam	 * PutMessage is used by external UPnP-based Registrar to perform WPS
464189251Ssam	 * operation with the access point itself; as compared with
465189251Ssam	 * PutWLANResponse which is for proxying.
466189251Ssam	 */
467189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
468214734Srpaulo	msg = xml_get_base64_item(data, "NewInMessage", &ret);
469189251Ssam	if (msg == NULL)
470189251Ssam		return ret;
471214734Srpaulo	res = wps_process_msg(sm->peer.wps, WSC_UPnP, msg);
472214734Srpaulo	if (res == WPS_FAILURE)
473214734Srpaulo		*reply = NULL;
474214734Srpaulo	else
475214734Srpaulo		*reply = wps_get_msg(sm->peer.wps, &op_code);
476189251Ssam	wpabuf_free(msg);
477189251Ssam	if (*reply == NULL)
478189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
479189251Ssam	*replyname = name;
480189251Ssam	return HTTP_OK;
481189251Ssam}
482189251Ssam
483189251Ssam
484189251Ssamstatic enum http_reply_code
485189251Ssamweb_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
486189251Ssam			      struct wpabuf **reply, const char **replyname)
487189251Ssam{
488189251Ssam	struct wpabuf *msg;
489189251Ssam	enum http_reply_code ret;
490189251Ssam	u8 macaddr[ETH_ALEN];
491189251Ssam	int ev_type;
492189251Ssam	int type;
493189251Ssam	char *val;
494189251Ssam
495189251Ssam	/*
496189251Ssam	 * External UPnP-based Registrar is passing us a message to be proxied
497189251Ssam	 * over to a Wi-Fi -based client of ours.
498189251Ssam	 */
499189251Ssam
500189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
501214734Srpaulo	msg = xml_get_base64_item(data, "NewMessage", &ret);
502214734Srpaulo	if (msg == NULL) {
503214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Could not extract NewMessage "
504214734Srpaulo			   "from PutWLANResponse");
505189251Ssam		return ret;
506214734Srpaulo	}
507214734Srpaulo	val = xml_get_first_item(data, "NewWLANEventType");
508214734Srpaulo	if (val == NULL) {
509214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventType in "
510214734Srpaulo			   "PutWLANResponse");
511189251Ssam		wpabuf_free(msg);
512189251Ssam		return UPNP_ARG_VALUE_INVALID;
513189251Ssam	}
514189251Ssam	ev_type = atol(val);
515189251Ssam	os_free(val);
516214734Srpaulo	val = xml_get_first_item(data, "NewWLANEventMAC");
517214734Srpaulo	if (val == NULL) {
518214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventMAC in "
519214734Srpaulo			   "PutWLANResponse");
520189251Ssam		wpabuf_free(msg);
521189251Ssam		return UPNP_ARG_VALUE_INVALID;
522189251Ssam	}
523214734Srpaulo	if (hwaddr_aton(val, macaddr)) {
524214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid NewWLANEventMAC in "
525214734Srpaulo			   "PutWLANResponse: '%s'", val);
526214734Srpaulo		if (hwaddr_aton2(val, macaddr) > 0) {
527214734Srpaulo			/*
528214734Srpaulo			 * At least some versions of Intel PROset seem to be
529214734Srpaulo			 * using dot-deliminated MAC address format here.
530214734Srpaulo			 */
531214734Srpaulo			wpa_printf(MSG_DEBUG, "WPS UPnP: Workaround - allow "
532214734Srpaulo				   "incorrect MAC address format in "
533214734Srpaulo				   "NewWLANEventMAC");
534214734Srpaulo		} else {
535214734Srpaulo			wpabuf_free(msg);
536214734Srpaulo			os_free(val);
537214734Srpaulo			return UPNP_ARG_VALUE_INVALID;
538214734Srpaulo		}
539214734Srpaulo	}
540189251Ssam	os_free(val);
541189251Ssam	if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
542189251Ssam		struct wps_parse_attr attr;
543189251Ssam		if (wps_parse_msg(msg, &attr) < 0 ||
544189251Ssam		    attr.msg_type == NULL)
545189251Ssam			type = -1;
546189251Ssam		else
547189251Ssam			type = *attr.msg_type;
548189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
549189251Ssam	} else
550189251Ssam		type = -1;
551189251Ssam	if (!sm->ctx->rx_req_put_wlan_response ||
552189251Ssam	    sm->ctx->rx_req_put_wlan_response(sm->priv, ev_type, macaddr, msg,
553189251Ssam					      type)) {
554189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
555189251Ssam			   "rx_req_put_wlan_response");
556189251Ssam		wpabuf_free(msg);
557189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
558189251Ssam	}
559189251Ssam	wpabuf_free(msg);
560189251Ssam	*replyname = NULL;
561189251Ssam	*reply = NULL;
562189251Ssam	return HTTP_OK;
563189251Ssam}
564189251Ssam
565189251Ssam
566214734Srpaulostatic int find_er_addr(struct subscription *s, struct sockaddr_in *cli)
567189251Ssam{
568214734Srpaulo	struct subscr_addr *a;
569189251Ssam
570214734Srpaulo	dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) {
571214734Srpaulo		if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr)
572214734Srpaulo			return 1;
573189251Ssam	}
574214734Srpaulo	return 0;
575189251Ssam}
576189251Ssam
577189251Ssam
578214734Srpaulostatic struct subscription * find_er(struct upnp_wps_device_sm *sm,
579214734Srpaulo				     struct sockaddr_in *cli)
580189251Ssam{
581214734Srpaulo	struct subscription *s;
582214734Srpaulo	dl_list_for_each(s, &sm->subscriptions, struct subscription, list)
583214734Srpaulo		if (find_er_addr(s, cli))
584214734Srpaulo			return s;
585214734Srpaulo	return NULL;
586189251Ssam}
587189251Ssam
588189251Ssam
589189251Ssamstatic enum http_reply_code
590214734Srpauloweb_process_set_selected_registrar(struct upnp_wps_device_sm *sm,
591214734Srpaulo				   struct sockaddr_in *cli, char *data,
592214734Srpaulo				   struct wpabuf **reply,
593214734Srpaulo				   const char **replyname)
594189251Ssam{
595189251Ssam	struct wpabuf *msg;
596189251Ssam	enum http_reply_code ret;
597214734Srpaulo	struct subscription *s;
598189251Ssam
599214734Srpaulo	wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
600214734Srpaulo	s = find_er(sm, cli);
601214734Srpaulo	if (s == NULL) {
602214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar "
603214734Srpaulo			   "from unknown ER");
604214734Srpaulo		return UPNP_ACTION_FAILED;
605189251Ssam	}
606214734Srpaulo	msg = xml_get_base64_item(data, "NewMessage", &ret);
607189251Ssam	if (msg == NULL)
608189251Ssam		return ret;
609214734Srpaulo	if (upnp_er_set_selected_registrar(sm->wps->registrar, s, msg)) {
610189251Ssam		wpabuf_free(msg);
611189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
612189251Ssam	}
613189251Ssam	wpabuf_free(msg);
614189251Ssam	*replyname = NULL;
615189251Ssam	*reply = NULL;
616189251Ssam	return HTTP_OK;
617189251Ssam}
618189251Ssam
619189251Ssam
620189251Ssamstatic const char *soap_prefix =
621189251Ssam	"<?xml version=\"1.0\"?>\n"
622189251Ssam	"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
623189251Ssam	"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
624189251Ssam	"<s:Body>\n";
625189251Ssamstatic const char *soap_postfix =
626189251Ssam	"</s:Body>\n</s:Envelope>\n";
627189251Ssam
628189251Ssamstatic const char *soap_error_prefix =
629189251Ssam	"<s:Fault>\n"
630189251Ssam	"<faultcode>s:Client</faultcode>\n"
631189251Ssam	"<faultstring>UPnPError</faultstring>\n"
632189251Ssam	"<detail>\n"
633189251Ssam	"<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
634189251Ssamstatic const char *soap_error_postfix =
635189251Ssam	"<errorDescription>Error</errorDescription>\n"
636189251Ssam	"</UPnPError>\n"
637189251Ssam	"</detail>\n"
638189251Ssam	"</s:Fault>\n";
639189251Ssam
640214734Srpaulostatic void web_connection_send_reply(struct http_request *req,
641189251Ssam				      enum http_reply_code ret,
642189251Ssam				      const char *action, int action_len,
643189251Ssam				      const struct wpabuf *reply,
644189251Ssam				      const char *replyname)
645189251Ssam{
646189251Ssam	struct wpabuf *buf;
647189251Ssam	char *replydata;
648189251Ssam	char *put_length_here = NULL;
649189251Ssam	char *body_start = NULL;
650189251Ssam
651189251Ssam	if (reply) {
652189251Ssam		size_t len;
653189251Ssam		replydata = (char *) base64_encode(wpabuf_head(reply),
654189251Ssam						   wpabuf_len(reply), &len);
655189251Ssam	} else
656189251Ssam		replydata = NULL;
657189251Ssam
658189251Ssam	/* Parameters of the response:
659189251Ssam	 *      action(action_len) -- action we are responding to
660189251Ssam	 *      replyname -- a name we need for the reply
661189251Ssam	 *      replydata -- NULL or null-terminated string
662189251Ssam	 */
663189251Ssam	buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
664189251Ssam			   (action_len > 0 ? action_len * 2 : 0));
665189251Ssam	if (buf == NULL) {
666189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
667189251Ssam			   "POST");
668189251Ssam		os_free(replydata);
669214734Srpaulo		http_request_deinit(req);
670189251Ssam		return;
671189251Ssam	}
672189251Ssam
673189251Ssam	/*
674189251Ssam	 * Assuming we will be successful, put in the output header first.
675189251Ssam	 * Note: we do not keep connections alive (and httpread does
676189251Ssam	 * not support it)... therefore we must have Connection: close.
677189251Ssam	 */
678189251Ssam	if (ret == HTTP_OK) {
679189251Ssam		wpabuf_put_str(buf,
680189251Ssam			       "HTTP/1.1 200 OK\r\n"
681189251Ssam			       "Content-Type: text/xml; "
682189251Ssam			       "charset=\"utf-8\"\r\n");
683189251Ssam	} else {
684189251Ssam		wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
685189251Ssam	}
686189251Ssam	wpabuf_put_str(buf, http_connection_close);
687189251Ssam
688189251Ssam	wpabuf_put_str(buf, "Content-Length: ");
689189251Ssam	/*
690189251Ssam	 * We will paste the length in later, leaving some extra whitespace.
691189251Ssam	 * HTTP code is supposed to be tolerant of extra whitespace.
692189251Ssam	 */
693189251Ssam	put_length_here = wpabuf_put(buf, 0);
694189251Ssam	wpabuf_put_str(buf, "        \r\n");
695189251Ssam
696189251Ssam	http_put_date(buf);
697189251Ssam
698189251Ssam	/* terminating empty line */
699189251Ssam	wpabuf_put_str(buf, "\r\n");
700189251Ssam
701189251Ssam	body_start = wpabuf_put(buf, 0);
702189251Ssam
703189251Ssam	if (ret == HTTP_OK) {
704189251Ssam		wpabuf_put_str(buf, soap_prefix);
705189251Ssam		wpabuf_put_str(buf, "<u:");
706189251Ssam		wpabuf_put_data(buf, action, action_len);
707189251Ssam		wpabuf_put_str(buf, "Response xmlns:u=\"");
708189251Ssam		wpabuf_put_str(buf, urn_wfawlanconfig);
709189251Ssam		wpabuf_put_str(buf, "\">\n");
710189251Ssam		if (replydata && replyname) {
711189251Ssam			/* TODO: might possibly need to escape part of reply
712189251Ssam			 * data? ...
713189251Ssam			 * probably not, unlikely to have ampersand(&) or left
714189251Ssam			 * angle bracket (<) in it...
715189251Ssam			 */
716189251Ssam			wpabuf_printf(buf, "<%s>", replyname);
717189251Ssam			wpabuf_put_str(buf, replydata);
718189251Ssam			wpabuf_printf(buf, "</%s>\n", replyname);
719189251Ssam		}
720189251Ssam		wpabuf_put_str(buf, "</u:");
721189251Ssam		wpabuf_put_data(buf, action, action_len);
722189251Ssam		wpabuf_put_str(buf, "Response>\n");
723189251Ssam		wpabuf_put_str(buf, soap_postfix);
724189251Ssam	} else {
725189251Ssam		/* Error case */
726189251Ssam		wpabuf_put_str(buf, soap_prefix);
727189251Ssam		wpabuf_put_str(buf, soap_error_prefix);
728189251Ssam		wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
729189251Ssam		wpabuf_put_str(buf, soap_error_postfix);
730189251Ssam		wpabuf_put_str(buf, soap_postfix);
731189251Ssam	}
732189251Ssam	os_free(replydata);
733189251Ssam
734189251Ssam	/* Now patch in the content length at the end */
735189251Ssam	if (body_start && put_length_here) {
736189251Ssam		int body_length = (char *) wpabuf_put(buf, 0) - body_start;
737189251Ssam		char len_buf[10];
738189251Ssam		os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
739189251Ssam		os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
740189251Ssam	}
741189251Ssam
742214734Srpaulo	http_request_send_and_deinit(req, buf);
743189251Ssam}
744189251Ssam
745189251Ssam
746214734Srpaulostatic const char * web_get_action(struct http_request *req,
747214734Srpaulo				   size_t *action_len)
748189251Ssam{
749189251Ssam	const char *match;
750189251Ssam	int match_len;
751189251Ssam	char *b;
752189251Ssam	char *action;
753189251Ssam
754189251Ssam	*action_len = 0;
755189251Ssam	/* The SOAPAction line of the header tells us what we want to do */
756214734Srpaulo	b = http_request_get_hdr_line(req, "SOAPAction:");
757189251Ssam	if (b == NULL)
758189251Ssam		return NULL;
759189251Ssam	if (*b == '"')
760189251Ssam		b++;
761189251Ssam	else
762189251Ssam		return NULL;
763189251Ssam	match = urn_wfawlanconfig;
764189251Ssam	match_len = os_strlen(urn_wfawlanconfig) - 1;
765189251Ssam	if (os_strncasecmp(b, match, match_len))
766189251Ssam		return NULL;
767189251Ssam	b += match_len;
768189251Ssam	/* skip over version */
769189251Ssam	while (isgraph(*b) && *b != '#')
770189251Ssam		b++;
771189251Ssam	if (*b != '#')
772189251Ssam		return NULL;
773189251Ssam	b++;
774189251Ssam	/* Following the sharp(#) should be the action and a double quote */
775189251Ssam	action = b;
776189251Ssam	while (isgraph(*b) && *b != '"')
777189251Ssam		b++;
778189251Ssam	if (*b != '"')
779189251Ssam		return NULL;
780189251Ssam	*action_len = b - action;
781189251Ssam	return action;
782189251Ssam}
783189251Ssam
784189251Ssam
785189251Ssam/* Given that we have received a header w/ POST, act upon it
786189251Ssam *
787189251Ssam * Format of POST (case-insensitive):
788189251Ssam *
789189251Ssam * First line must be:
790189251Ssam *      POST /<file> HTTP/1.1
791189251Ssam * Since we don't do anything fancy we just ignore other lines.
792189251Ssam *
793189251Ssam * Our response (if no error) which includes only required lines is:
794189251Ssam * HTTP/1.1 200 OK
795189251Ssam * Connection: close
796189251Ssam * Content-Type: text/xml
797189251Ssam * Date: <rfc1123-date>
798189251Ssam *
799189251Ssam * Header lines must end with \r\n
800189251Ssam * Per RFC 2616, content-length: is not required but connection:close
801189251Ssam * would appear to be required (given that we will be closing it!).
802189251Ssam */
803214734Srpaulostatic void web_connection_parse_post(struct upnp_wps_device_sm *sm,
804214734Srpaulo				      struct sockaddr_in *cli,
805214734Srpaulo				      struct http_request *req,
806189251Ssam				      const char *filename)
807189251Ssam{
808189251Ssam	enum http_reply_code ret;
809214734Srpaulo	char *data = http_request_get_data(req); /* body of http msg */
810214734Srpaulo	const char *action = NULL;
811214734Srpaulo	size_t action_len = 0;
812189251Ssam	const char *replyname = NULL; /* argument name for the reply */
813189251Ssam	struct wpabuf *reply = NULL; /* data for the reply */
814189251Ssam
815214734Srpaulo	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
816214734Srpaulo		wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
817214734Srpaulo			   filename);
818214734Srpaulo		ret = HTTP_NOT_FOUND;
819214734Srpaulo		goto bad;
820214734Srpaulo	}
821214734Srpaulo
822189251Ssam	ret = UPNP_INVALID_ACTION;
823214734Srpaulo	action = web_get_action(req, &action_len);
824189251Ssam	if (action == NULL)
825189251Ssam		goto bad;
826189251Ssam
827189251Ssam	if (!os_strncasecmp("GetDeviceInfo", action, action_len))
828189251Ssam		ret = web_process_get_device_info(sm, &reply, &replyname);
829189251Ssam	else if (!os_strncasecmp("PutMessage", action, action_len))
830189251Ssam		ret = web_process_put_message(sm, data, &reply, &replyname);
831189251Ssam	else if (!os_strncasecmp("PutWLANResponse", action, action_len))
832189251Ssam		ret = web_process_put_wlan_response(sm, data, &reply,
833189251Ssam						    &replyname);
834189251Ssam	else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
835214734Srpaulo		ret = web_process_set_selected_registrar(sm, cli, data, &reply,
836189251Ssam							 &replyname);
837189251Ssam	else
838189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
839189251Ssam
840189251Ssambad:
841189251Ssam	if (ret != HTTP_OK)
842189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
843214734Srpaulo	web_connection_send_reply(req, ret, action, action_len, reply,
844189251Ssam				  replyname);
845189251Ssam	wpabuf_free(reply);
846189251Ssam}
847189251Ssam
848189251Ssam
849189251Ssam/* Given that we have received a header w/ SUBSCRIBE, act upon it
850189251Ssam *
851189251Ssam * Format of SUBSCRIBE (case-insensitive):
852189251Ssam *
853189251Ssam * First line must be:
854189251Ssam *      SUBSCRIBE /wps_event HTTP/1.1
855189251Ssam *
856189251Ssam * Our response (if no error) which includes only required lines is:
857189251Ssam * HTTP/1.1 200 OK
858189251Ssam * Server: xx, UPnP/1.0, xx
859189251Ssam * SID: uuid:xxxxxxxxx
860189251Ssam * Timeout: Second-<n>
861189251Ssam * Content-Length: 0
862189251Ssam * Date: xxxx
863189251Ssam *
864189251Ssam * Header lines must end with \r\n
865189251Ssam * Per RFC 2616, content-length: is not required but connection:close
866189251Ssam * would appear to be required (given that we will be closing it!).
867189251Ssam */
868214734Srpaulostatic void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
869214734Srpaulo					   struct http_request *req,
870189251Ssam					   const char *filename)
871189251Ssam{
872189251Ssam	struct wpabuf *buf;
873189251Ssam	char *b;
874214734Srpaulo	char *hdr = http_request_get_hdr(req);
875189251Ssam	char *h;
876189251Ssam	char *match;
877189251Ssam	int match_len;
878189251Ssam	char *end;
879189251Ssam	int len;
880189251Ssam	int got_nt = 0;
881189251Ssam	u8 uuid[UUID_LEN];
882189251Ssam	int got_uuid = 0;
883189251Ssam	char *callback_urls = NULL;
884189251Ssam	struct subscription *s = NULL;
885189251Ssam	enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
886189251Ssam
887189251Ssam	buf = wpabuf_alloc(1000);
888214734Srpaulo	if (buf == NULL) {
889214734Srpaulo		http_request_deinit(req);
890189251Ssam		return;
891214734Srpaulo	}
892189251Ssam
893189251Ssam	/* Parse/validate headers */
894189251Ssam	h = hdr;
895189251Ssam	/* First line: SUBSCRIBE /wps_event HTTP/1.1
896189251Ssam	 * has already been parsed.
897189251Ssam	 */
898189251Ssam	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
899189251Ssam		ret = HTTP_PRECONDITION_FAILED;
900189251Ssam		goto error;
901189251Ssam	}
902189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
903189251Ssam	end = os_strchr(h, '\n');
904189251Ssam
905189251Ssam	for (; end != NULL; h = end + 1) {
906189251Ssam		/* Option line by option line */
907189251Ssam		h = end + 1;
908189251Ssam		end = os_strchr(h, '\n');
909189251Ssam		if (end == NULL)
910189251Ssam			break; /* no unterminated lines allowed */
911189251Ssam
912189251Ssam		/* NT assures that it is our type of subscription;
913189251Ssam		 * not used for a renewl.
914189251Ssam		 **/
915189251Ssam		match = "NT:";
916189251Ssam		match_len = os_strlen(match);
917189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
918189251Ssam			h += match_len;
919189251Ssam			while (*h == ' ' || *h == '\t')
920189251Ssam				h++;
921189251Ssam			match = "upnp:event";
922189251Ssam			match_len = os_strlen(match);
923189251Ssam			if (os_strncasecmp(h, match, match_len) != 0) {
924189251Ssam				ret = HTTP_BAD_REQUEST;
925189251Ssam				goto error;
926189251Ssam			}
927189251Ssam			got_nt = 1;
928189251Ssam			continue;
929189251Ssam		}
930189251Ssam		/* HOST should refer to us */
931189251Ssam#if 0
932189251Ssam		match = "HOST:";
933189251Ssam		match_len = os_strlen(match);
934189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
935189251Ssam			h += match_len;
936189251Ssam			while (*h == ' ' || *h == '\t')
937189251Ssam				h++;
938189251Ssam			.....
939189251Ssam		}
940189251Ssam#endif
941189251Ssam		/* CALLBACK gives one or more URLs for NOTIFYs
942189251Ssam		 * to be sent as a result of the subscription.
943189251Ssam		 * Each URL is enclosed in angle brackets.
944189251Ssam		 */
945189251Ssam		match = "CALLBACK:";
946189251Ssam		match_len = os_strlen(match);
947189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
948189251Ssam			h += match_len;
949189251Ssam			while (*h == ' ' || *h == '\t')
950189251Ssam				h++;
951189251Ssam			len = end - h;
952189251Ssam			os_free(callback_urls);
953189251Ssam			callback_urls = os_malloc(len + 1);
954189251Ssam			if (callback_urls == NULL) {
955189251Ssam				ret = HTTP_INTERNAL_SERVER_ERROR;
956189251Ssam				goto error;
957189251Ssam			}
958189251Ssam			os_memcpy(callback_urls, h, len);
959189251Ssam			callback_urls[len] = 0;
960189251Ssam			continue;
961189251Ssam		}
962189251Ssam		/* SID is only for renewal */
963189251Ssam		match = "SID:";
964189251Ssam		match_len = os_strlen(match);
965189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
966189251Ssam			h += match_len;
967189251Ssam			while (*h == ' ' || *h == '\t')
968189251Ssam				h++;
969189251Ssam			match = "uuid:";
970189251Ssam			match_len = os_strlen(match);
971189251Ssam			if (os_strncasecmp(h, match, match_len) != 0) {
972189251Ssam				ret = HTTP_BAD_REQUEST;
973189251Ssam				goto error;
974189251Ssam			}
975189251Ssam			h += match_len;
976189251Ssam			while (*h == ' ' || *h == '\t')
977189251Ssam				h++;
978189251Ssam			if (uuid_str2bin(h, uuid)) {
979189251Ssam				ret = HTTP_BAD_REQUEST;
980189251Ssam				goto error;
981189251Ssam			}
982189251Ssam			got_uuid = 1;
983189251Ssam			continue;
984189251Ssam		}
985189251Ssam		/* TIMEOUT is requested timeout, but apparently we can
986189251Ssam		 * just ignore this.
987189251Ssam		 */
988189251Ssam	}
989189251Ssam
990189251Ssam	if (got_uuid) {
991189251Ssam		/* renewal */
992189251Ssam		if (callback_urls) {
993189251Ssam			ret = HTTP_BAD_REQUEST;
994189251Ssam			goto error;
995189251Ssam		}
996189251Ssam		s = subscription_renew(sm, uuid);
997189251Ssam		if (s == NULL) {
998189251Ssam			ret = HTTP_PRECONDITION_FAILED;
999189251Ssam			goto error;
1000189251Ssam		}
1001189251Ssam	} else if (callback_urls) {
1002189251Ssam		if (!got_nt) {
1003189251Ssam			ret = HTTP_PRECONDITION_FAILED;
1004189251Ssam			goto error;
1005189251Ssam		}
1006189251Ssam		s = subscription_start(sm, callback_urls);
1007189251Ssam		if (s == NULL) {
1008189251Ssam			ret = HTTP_INTERNAL_SERVER_ERROR;
1009189251Ssam			goto error;
1010189251Ssam		}
1011189251Ssam	} else {
1012189251Ssam		ret = HTTP_PRECONDITION_FAILED;
1013189251Ssam		goto error;
1014189251Ssam	}
1015189251Ssam
1016189251Ssam	/* success */
1017189251Ssam	http_put_reply_code(buf, HTTP_OK);
1018189251Ssam	wpabuf_put_str(buf, http_server_hdr);
1019189251Ssam	wpabuf_put_str(buf, http_connection_close);
1020189251Ssam	wpabuf_put_str(buf, "Content-Length: 0\r\n");
1021189251Ssam	wpabuf_put_str(buf, "SID: uuid:");
1022189251Ssam	/* subscription id */
1023189251Ssam	b = wpabuf_put(buf, 0);
1024189251Ssam	uuid_bin2str(s->uuid, b, 80);
1025189251Ssam	wpabuf_put(buf, os_strlen(b));
1026189251Ssam	wpabuf_put_str(buf, "\r\n");
1027189251Ssam	wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
1028189251Ssam	http_put_date(buf);
1029189251Ssam	/* And empty line to terminate header: */
1030189251Ssam	wpabuf_put_str(buf, "\r\n");
1031189251Ssam
1032189251Ssam	os_free(callback_urls);
1033214734Srpaulo	http_request_send_and_deinit(req, buf);
1034189251Ssam	return;
1035189251Ssam
1036189251Ssamerror:
1037189251Ssam	/* Per UPnP spec:
1038189251Ssam	* Errors
1039189251Ssam	* Incompatible headers
1040189251Ssam	*   400 Bad Request. If SID header and one of NT or CALLBACK headers
1041189251Ssam	*     are present, the publisher must respond with HTTP error
1042189251Ssam	*     400 Bad Request.
1043189251Ssam	* Missing or invalid CALLBACK
1044189251Ssam	*   412 Precondition Failed. If CALLBACK header is missing or does not
1045189251Ssam	*     contain a valid HTTP URL, the publisher must respond with HTTP
1046189251Ssam	*     error 412 Precondition Failed.
1047189251Ssam	* Invalid NT
1048189251Ssam	*   412 Precondition Failed. If NT header does not equal upnp:event,
1049189251Ssam	*     the publisher must respond with HTTP error 412 Precondition
1050189251Ssam	*     Failed.
1051189251Ssam	* [For resubscription, use 412 if unknown uuid].
1052189251Ssam	* Unable to accept subscription
1053189251Ssam	*   5xx. If a publisher is not able to accept a subscription (such as
1054189251Ssam	*     due to insufficient resources), it must respond with a
1055189251Ssam	*     HTTP 500-series error code.
1056189251Ssam	*   599 Too many subscriptions (not a standard HTTP error)
1057189251Ssam	*/
1058189251Ssam	http_put_empty(buf, ret);
1059214734Srpaulo	http_request_send_and_deinit(req, buf);
1060209158Srpaulo	os_free(callback_urls);
1061189251Ssam}
1062189251Ssam
1063189251Ssam
1064189251Ssam/* Given that we have received a header w/ UNSUBSCRIBE, act upon it
1065189251Ssam *
1066189251Ssam * Format of UNSUBSCRIBE (case-insensitive):
1067189251Ssam *
1068189251Ssam * First line must be:
1069189251Ssam *      UNSUBSCRIBE /wps_event HTTP/1.1
1070189251Ssam *
1071189251Ssam * Our response (if no error) which includes only required lines is:
1072189251Ssam * HTTP/1.1 200 OK
1073189251Ssam * Content-Length: 0
1074189251Ssam *
1075189251Ssam * Header lines must end with \r\n
1076189251Ssam * Per RFC 2616, content-length: is not required but connection:close
1077189251Ssam * would appear to be required (given that we will be closing it!).
1078189251Ssam */
1079214734Srpaulostatic void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
1080214734Srpaulo					     struct http_request *req,
1081189251Ssam					     const char *filename)
1082189251Ssam{
1083189251Ssam	struct wpabuf *buf;
1084214734Srpaulo	char *hdr = http_request_get_hdr(req);
1085189251Ssam	char *h;
1086189251Ssam	char *match;
1087189251Ssam	int match_len;
1088189251Ssam	char *end;
1089189251Ssam	u8 uuid[UUID_LEN];
1090189251Ssam	int got_uuid = 0;
1091189251Ssam	struct subscription *s = NULL;
1092189251Ssam	enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1093189251Ssam
1094189251Ssam	/* Parse/validate headers */
1095189251Ssam	h = hdr;
1096189251Ssam	/* First line: UNSUBSCRIBE /wps_event HTTP/1.1
1097189251Ssam	 * has already been parsed.
1098189251Ssam	 */
1099189251Ssam	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1100189251Ssam		ret = HTTP_PRECONDITION_FAILED;
1101189251Ssam		goto send_msg;
1102189251Ssam	}
1103189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
1104189251Ssam	end = os_strchr(h, '\n');
1105189251Ssam
1106189251Ssam	for (; end != NULL; h = end + 1) {
1107189251Ssam		/* Option line by option line */
1108189251Ssam		h = end + 1;
1109189251Ssam		end = os_strchr(h, '\n');
1110189251Ssam		if (end == NULL)
1111189251Ssam			break; /* no unterminated lines allowed */
1112189251Ssam
1113189251Ssam		/* HOST should refer to us */
1114189251Ssam#if 0
1115189251Ssam		match = "HOST:";
1116189251Ssam		match_len = os_strlen(match);
1117189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
1118189251Ssam			h += match_len;
1119189251Ssam			while (*h == ' ' || *h == '\t')
1120189251Ssam				h++;
1121189251Ssam			.....
1122189251Ssam		}
1123189251Ssam#endif
1124189251Ssam		/* SID is only for renewal */
1125189251Ssam		match = "SID:";
1126189251Ssam		match_len = os_strlen(match);
1127189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
1128189251Ssam			h += match_len;
1129189251Ssam			while (*h == ' ' || *h == '\t')
1130189251Ssam				h++;
1131189251Ssam			match = "uuid:";
1132189251Ssam			match_len = os_strlen(match);
1133189251Ssam			if (os_strncasecmp(h, match, match_len) != 0) {
1134189251Ssam				ret = HTTP_BAD_REQUEST;
1135189251Ssam				goto send_msg;
1136189251Ssam			}
1137189251Ssam			h += match_len;
1138189251Ssam			while (*h == ' ' || *h == '\t')
1139189251Ssam				h++;
1140189251Ssam			if (uuid_str2bin(h, uuid)) {
1141189251Ssam				ret = HTTP_BAD_REQUEST;
1142189251Ssam				goto send_msg;
1143189251Ssam			}
1144189251Ssam			got_uuid = 1;
1145189251Ssam			continue;
1146189251Ssam		}
1147189251Ssam	}
1148189251Ssam
1149189251Ssam	if (got_uuid) {
1150189251Ssam		s = subscription_find(sm, uuid);
1151189251Ssam		if (s) {
1152214734Srpaulo			struct subscr_addr *sa;
1153214734Srpaulo			sa = dl_list_first(&s->addr_list, struct subscr_addr,
1154214734Srpaulo					   list);
1155189251Ssam			wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
1156214734Srpaulo				   s, (sa && sa->domain_and_port) ?
1157214734Srpaulo				   sa->domain_and_port : "-null-");
1158214734Srpaulo			dl_list_del(&s->list);
1159189251Ssam			subscription_destroy(s);
1160189251Ssam		}
1161189251Ssam	} else {
1162189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
1163189251Ssam			   "found)");
1164189251Ssam		ret = HTTP_PRECONDITION_FAILED;
1165189251Ssam		goto send_msg;
1166189251Ssam	}
1167189251Ssam
1168189251Ssam	ret = HTTP_OK;
1169189251Ssam
1170189251Ssamsend_msg:
1171189251Ssam	buf = wpabuf_alloc(200);
1172214734Srpaulo	if (buf == NULL) {
1173214734Srpaulo		http_request_deinit(req);
1174189251Ssam		return;
1175214734Srpaulo	}
1176189251Ssam	http_put_empty(buf, ret);
1177214734Srpaulo	http_request_send_and_deinit(req, buf);
1178189251Ssam}
1179189251Ssam
1180189251Ssam
1181189251Ssam/* Send error in response to unknown requests */
1182214734Srpaulostatic void web_connection_unimplemented(struct http_request *req)
1183189251Ssam{
1184189251Ssam	struct wpabuf *buf;
1185189251Ssam	buf = wpabuf_alloc(200);
1186214734Srpaulo	if (buf == NULL) {
1187214734Srpaulo		http_request_deinit(req);
1188189251Ssam		return;
1189214734Srpaulo	}
1190189251Ssam	http_put_empty(buf, HTTP_UNIMPLEMENTED);
1191214734Srpaulo	http_request_send_and_deinit(req, buf);
1192189251Ssam}
1193189251Ssam
1194189251Ssam
1195189251Ssam
1196189251Ssam/* Called when we have gotten an apparently valid http request.
1197189251Ssam */
1198214734Srpaulostatic void web_connection_check_data(void *ctx, struct http_request *req)
1199189251Ssam{
1200214734Srpaulo	struct upnp_wps_device_sm *sm = ctx;
1201214734Srpaulo	enum httpread_hdr_type htype = http_request_get_type(req);
1202214734Srpaulo	char *filename = http_request_get_uri(req);
1203214734Srpaulo	struct sockaddr_in *cli = http_request_get_cli_addr(req);
1204189251Ssam
1205189251Ssam	if (!filename) {
1206189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
1207214734Srpaulo		http_request_deinit(req);
1208189251Ssam		return;
1209189251Ssam	}
1210189251Ssam	/* Trim leading slashes from filename */
1211189251Ssam	while (*filename == '/')
1212189251Ssam		filename++;
1213189251Ssam
1214189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
1215214734Srpaulo		   htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port));
1216189251Ssam
1217189251Ssam	switch (htype) {
1218189251Ssam	case HTTPREAD_HDR_TYPE_GET:
1219214734Srpaulo		web_connection_parse_get(sm, req, filename);
1220189251Ssam		break;
1221189251Ssam	case HTTPREAD_HDR_TYPE_POST:
1222214734Srpaulo		web_connection_parse_post(sm, cli, req, filename);
1223189251Ssam		break;
1224189251Ssam	case HTTPREAD_HDR_TYPE_SUBSCRIBE:
1225214734Srpaulo		web_connection_parse_subscribe(sm, req, filename);
1226189251Ssam		break;
1227189251Ssam	case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
1228214734Srpaulo		web_connection_parse_unsubscribe(sm, req, filename);
1229189251Ssam		break;
1230214734Srpaulo
1231189251Ssam		/* We are not required to support M-POST; just plain
1232189251Ssam		 * POST is supposed to work, so we only support that.
1233189251Ssam		 * If for some reason we need to support M-POST, it is
1234189251Ssam		 * mostly the same as POST, with small differences.
1235189251Ssam		 */
1236189251Ssam	default:
1237189251Ssam		/* Send 501 for anything else */
1238214734Srpaulo		web_connection_unimplemented(req);
1239189251Ssam		break;
1240189251Ssam	}
1241189251Ssam}
1242189251Ssam
1243189251Ssam
1244189251Ssam/*
1245189251Ssam * Listening for web connections
1246189251Ssam * We have a single TCP listening port, and hand off connections as we get
1247189251Ssam * them.
1248189251Ssam */
1249189251Ssam
1250189251Ssamvoid web_listener_stop(struct upnp_wps_device_sm *sm)
1251189251Ssam{
1252214734Srpaulo	http_server_deinit(sm->web_srv);
1253214734Srpaulo	sm->web_srv = NULL;
1254189251Ssam}
1255189251Ssam
1256189251Ssam
1257189251Ssamint web_listener_start(struct upnp_wps_device_sm *sm)
1258189251Ssam{
1259214734Srpaulo	struct in_addr addr;
1260214734Srpaulo	addr.s_addr = sm->ip_addr;
1261214734Srpaulo	sm->web_srv = http_server_init(&addr, -1, web_connection_check_data,
1262214734Srpaulo				       sm);
1263214734Srpaulo	if (sm->web_srv == NULL) {
1264214734Srpaulo		web_listener_stop(sm);
1265214734Srpaulo		return -1;
1266189251Ssam	}
1267214734Srpaulo	sm->web_port = http_server_get_port(sm->web_srv);
1268189251Ssam
1269189251Ssam	return 0;
1270189251Ssam}
1271