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];
187252726Srpaulo	struct upnp_wps_device_interface *iface;
188189251Ssam
189252726Srpaulo	iface = dl_list_first(&sm->interfaces,
190252726Srpaulo			      struct upnp_wps_device_interface, list);
191252726Srpaulo
192189251Ssam	wpabuf_put_str(buf, wps_device_xml_prefix);
193189251Ssam
194189251Ssam	/*
195189251Ssam	 * Add required fields with default values if not configured. Add
196189251Ssam	 * optional and recommended fields only if configured.
197189251Ssam	 */
198252726Srpaulo	s = iface->wps->friendly_name;
199189251Ssam	s = ((s && *s) ? s : "WPS Access Point");
200189251Ssam	xml_add_tagged_data(buf, "friendlyName", s);
201189251Ssam
202252726Srpaulo	s = iface->wps->dev.manufacturer;
203189251Ssam	s = ((s && *s) ? s : "");
204189251Ssam	xml_add_tagged_data(buf, "manufacturer", s);
205189251Ssam
206252726Srpaulo	if (iface->wps->manufacturer_url)
207189251Ssam		xml_add_tagged_data(buf, "manufacturerURL",
208252726Srpaulo				    iface->wps->manufacturer_url);
209189251Ssam
210252726Srpaulo	if (iface->wps->model_description)
211189251Ssam		xml_add_tagged_data(buf, "modelDescription",
212252726Srpaulo				    iface->wps->model_description);
213189251Ssam
214252726Srpaulo	s = iface->wps->dev.model_name;
215189251Ssam	s = ((s && *s) ? s : "");
216189251Ssam	xml_add_tagged_data(buf, "modelName", s);
217189251Ssam
218252726Srpaulo	if (iface->wps->dev.model_number)
219189251Ssam		xml_add_tagged_data(buf, "modelNumber",
220252726Srpaulo				    iface->wps->dev.model_number);
221189251Ssam
222252726Srpaulo	if (iface->wps->model_url)
223252726Srpaulo		xml_add_tagged_data(buf, "modelURL", iface->wps->model_url);
224189251Ssam
225252726Srpaulo	if (iface->wps->dev.serial_number)
226189251Ssam		xml_add_tagged_data(buf, "serialNumber",
227252726Srpaulo				    iface->wps->dev.serial_number);
228189251Ssam
229252726Srpaulo	uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string));
230189251Ssam	s = uuid_string;
231189251Ssam	/* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
232189251Ssam	 * easily...
233189251Ssam	 */
234189251Ssam	wpabuf_put_str(buf, "<UDN>uuid:");
235189251Ssam	xml_data_encode(buf, s, os_strlen(s));
236189251Ssam	wpabuf_put_str(buf, "</UDN>\n");
237189251Ssam
238252726Srpaulo	if (iface->wps->upc)
239252726Srpaulo		xml_add_tagged_data(buf, "UPC", iface->wps->upc);
240189251Ssam
241189251Ssam	wpabuf_put_str(buf, wps_device_xml_postfix);
242189251Ssam}
243189251Ssam
244189251Ssam
245189251Ssamstatic void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
246189251Ssam{
247189251Ssam	wpabuf_put_str(buf, "HTTP/1.1 ");
248189251Ssam	switch (code) {
249189251Ssam	case HTTP_OK:
250189251Ssam		wpabuf_put_str(buf, "200 OK\r\n");
251189251Ssam		break;
252189251Ssam	case HTTP_BAD_REQUEST:
253189251Ssam		wpabuf_put_str(buf, "400 Bad request\r\n");
254189251Ssam		break;
255189251Ssam	case HTTP_PRECONDITION_FAILED:
256189251Ssam		wpabuf_put_str(buf, "412 Precondition failed\r\n");
257189251Ssam		break;
258189251Ssam	case HTTP_UNIMPLEMENTED:
259189251Ssam		wpabuf_put_str(buf, "501 Unimplemented\r\n");
260189251Ssam		break;
261189251Ssam	case HTTP_INTERNAL_SERVER_ERROR:
262189251Ssam	default:
263189251Ssam		wpabuf_put_str(buf, "500 Internal server error\r\n");
264189251Ssam		break;
265189251Ssam	}
266189251Ssam}
267189251Ssam
268189251Ssam
269189251Ssamstatic void http_put_date(struct wpabuf *buf)
270189251Ssam{
271189251Ssam	wpabuf_put_str(buf, "Date: ");
272189251Ssam	format_date(buf);
273189251Ssam	wpabuf_put_str(buf, "\r\n");
274189251Ssam}
275189251Ssam
276189251Ssam
277189251Ssamstatic void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
278189251Ssam{
279189251Ssam	http_put_reply_code(buf, code);
280189251Ssam	wpabuf_put_str(buf, http_server_hdr);
281189251Ssam	wpabuf_put_str(buf, http_connection_close);
282189251Ssam	wpabuf_put_str(buf, "Content-Length: 0\r\n"
283189251Ssam		       "\r\n");
284189251Ssam}
285189251Ssam
286189251Ssam
287189251Ssam/* Given that we have received a header w/ GET, act upon it
288189251Ssam *
289189251Ssam * Format of GET (case-insensitive):
290189251Ssam *
291189251Ssam * First line must be:
292189251Ssam *      GET /<file> HTTP/1.1
293189251Ssam * Since we don't do anything fancy we just ignore other lines.
294189251Ssam *
295189251Ssam * Our response (if no error) which includes only required lines is:
296189251Ssam * HTTP/1.1 200 OK
297189251Ssam * Connection: close
298189251Ssam * Content-Type: text/xml
299189251Ssam * Date: <rfc1123-date>
300189251Ssam *
301189251Ssam * Header lines must end with \r\n
302189251Ssam * Per RFC 2616, content-length: is not required but connection:close
303189251Ssam * would appear to be required (given that we will be closing it!).
304189251Ssam */
305214734Srpaulostatic void web_connection_parse_get(struct upnp_wps_device_sm *sm,
306214734Srpaulo				     struct http_request *hreq, char *filename)
307189251Ssam{
308189251Ssam	struct wpabuf *buf; /* output buffer, allocated */
309189251Ssam	char *put_length_here;
310189251Ssam	char *body_start;
311189251Ssam	enum {
312189251Ssam		GET_DEVICE_XML_FILE,
313189251Ssam		GET_SCPD_XML_FILE
314189251Ssam	} req;
315189251Ssam	size_t extra_len = 0;
316189251Ssam	int body_length;
317189251Ssam	char len_buf[10];
318252726Srpaulo	struct upnp_wps_device_interface *iface;
319189251Ssam
320252726Srpaulo	iface = dl_list_first(&sm->interfaces,
321252726Srpaulo			      struct upnp_wps_device_interface, list);
322252726Srpaulo
323189251Ssam	/*
324189251Ssam	 * It is not required that filenames be case insensitive but it is
325189251Ssam	 * allowed and cannot hurt here.
326189251Ssam	 */
327189251Ssam	if (filename == NULL)
328189251Ssam		filename = "(null)"; /* just in case */
329189251Ssam	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
330189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
331189251Ssam		req = GET_DEVICE_XML_FILE;
332189251Ssam		extra_len = 3000;
333252726Srpaulo		if (iface->wps->friendly_name)
334252726Srpaulo			extra_len += os_strlen(iface->wps->friendly_name);
335252726Srpaulo		if (iface->wps->manufacturer_url)
336252726Srpaulo			extra_len += os_strlen(iface->wps->manufacturer_url);
337252726Srpaulo		if (iface->wps->model_description)
338252726Srpaulo			extra_len += os_strlen(iface->wps->model_description);
339252726Srpaulo		if (iface->wps->model_url)
340252726Srpaulo			extra_len += os_strlen(iface->wps->model_url);
341252726Srpaulo		if (iface->wps->upc)
342252726Srpaulo			extra_len += os_strlen(iface->wps->upc);
343189251Ssam	} else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
344189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
345189251Ssam		req = GET_SCPD_XML_FILE;
346189251Ssam		extra_len = os_strlen(wps_scpd_xml);
347189251Ssam	} else {
348189251Ssam		/* File not found */
349189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
350189251Ssam			   filename);
351189251Ssam		buf = wpabuf_alloc(200);
352214734Srpaulo		if (buf == NULL) {
353214734Srpaulo			http_request_deinit(hreq);
354189251Ssam			return;
355214734Srpaulo		}
356189251Ssam		wpabuf_put_str(buf,
357189251Ssam			       "HTTP/1.1 404 Not Found\r\n"
358189251Ssam			       "Connection: close\r\n");
359189251Ssam
360189251Ssam		http_put_date(buf);
361189251Ssam
362189251Ssam		/* terminating empty line */
363189251Ssam		wpabuf_put_str(buf, "\r\n");
364189251Ssam
365189251Ssam		goto send_buf;
366189251Ssam	}
367189251Ssam
368189251Ssam	buf = wpabuf_alloc(1000 + extra_len);
369214734Srpaulo	if (buf == NULL) {
370214734Srpaulo		http_request_deinit(hreq);
371189251Ssam		return;
372214734Srpaulo	}
373189251Ssam
374189251Ssam	wpabuf_put_str(buf,
375189251Ssam		       "HTTP/1.1 200 OK\r\n"
376189251Ssam		       "Content-Type: text/xml; charset=\"utf-8\"\r\n");
377189251Ssam	wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
378189251Ssam	wpabuf_put_str(buf, "Connection: close\r\n");
379189251Ssam	wpabuf_put_str(buf, "Content-Length: ");
380189251Ssam	/*
381189251Ssam	 * We will paste the length in later, leaving some extra whitespace.
382189251Ssam	 * HTTP code is supposed to be tolerant of extra whitespace.
383189251Ssam	 */
384189251Ssam	put_length_here = wpabuf_put(buf, 0);
385189251Ssam	wpabuf_put_str(buf, "        \r\n");
386189251Ssam
387189251Ssam	http_put_date(buf);
388189251Ssam
389189251Ssam	/* terminating empty line */
390189251Ssam	wpabuf_put_str(buf, "\r\n");
391189251Ssam
392189251Ssam	body_start = wpabuf_put(buf, 0);
393189251Ssam
394189251Ssam	switch (req) {
395189251Ssam	case GET_DEVICE_XML_FILE:
396189251Ssam		format_wps_device_xml(sm, buf);
397189251Ssam		break;
398189251Ssam	case GET_SCPD_XML_FILE:
399189251Ssam		wpabuf_put_str(buf, wps_scpd_xml);
400189251Ssam		break;
401189251Ssam	}
402189251Ssam
403189251Ssam	/* Now patch in the content length at the end */
404189251Ssam	body_length = (char *) wpabuf_put(buf, 0) - body_start;
405189251Ssam	os_snprintf(len_buf, 10, "%d", body_length);
406189251Ssam	os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
407189251Ssam
408189251Ssamsend_buf:
409214734Srpaulo	http_request_send_and_deinit(hreq, buf);
410189251Ssam}
411189251Ssam
412189251Ssam
413189251Ssamstatic enum http_reply_code
414189251Ssamweb_process_get_device_info(struct upnp_wps_device_sm *sm,
415189251Ssam			    struct wpabuf **reply, const char **replyname)
416189251Ssam{
417189251Ssam	static const char *name = "NewDeviceInfo";
418214734Srpaulo	struct wps_config cfg;
419252726Srpaulo	struct upnp_wps_device_interface *iface;
420252726Srpaulo	struct upnp_wps_peer *peer;
421189251Ssam
422252726Srpaulo	iface = dl_list_first(&sm->interfaces,
423252726Srpaulo			      struct upnp_wps_device_interface, list);
424252726Srpaulo	peer = &iface->peer;
425252726Srpaulo
426189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
427214734Srpaulo
428252726Srpaulo	if (iface->ctx->ap_pin == NULL)
429189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
430214734Srpaulo
431214734Srpaulo	/*
432214734Srpaulo	 * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS
433214734Srpaulo	 * registration over UPnP with the AP acting as an Enrollee. It should
434214734Srpaulo	 * be noted that this is frequently used just to get the device data,
435214734Srpaulo	 * i.e., there may not be any intent to actually complete the
436214734Srpaulo	 * registration.
437214734Srpaulo	 */
438214734Srpaulo
439214734Srpaulo	if (peer->wps)
440214734Srpaulo		wps_deinit(peer->wps);
441214734Srpaulo
442214734Srpaulo	os_memset(&cfg, 0, sizeof(cfg));
443252726Srpaulo	cfg.wps = iface->wps;
444252726Srpaulo	cfg.pin = (u8 *) iface->ctx->ap_pin;
445252726Srpaulo	cfg.pin_len = os_strlen(iface->ctx->ap_pin);
446214734Srpaulo	peer->wps = wps_init(&cfg);
447214734Srpaulo	if (peer->wps) {
448214734Srpaulo		enum wsc_op_code op_code;
449214734Srpaulo		*reply = wps_get_msg(peer->wps, &op_code);
450214734Srpaulo		if (*reply == NULL) {
451214734Srpaulo			wps_deinit(peer->wps);
452214734Srpaulo			peer->wps = NULL;
453214734Srpaulo		}
454214734Srpaulo	} else
455214734Srpaulo		*reply = NULL;
456189251Ssam	if (*reply == NULL) {
457189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
458189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
459189251Ssam	}
460189251Ssam	*replyname = name;
461189251Ssam	return HTTP_OK;
462189251Ssam}
463189251Ssam
464189251Ssam
465189251Ssamstatic enum http_reply_code
466189251Ssamweb_process_put_message(struct upnp_wps_device_sm *sm, char *data,
467189251Ssam			struct wpabuf **reply, const char **replyname)
468189251Ssam{
469189251Ssam	struct wpabuf *msg;
470189251Ssam	static const char *name = "NewOutMessage";
471189251Ssam	enum http_reply_code ret;
472214734Srpaulo	enum wps_process_res res;
473214734Srpaulo	enum wsc_op_code op_code;
474252726Srpaulo	struct upnp_wps_device_interface *iface;
475189251Ssam
476252726Srpaulo	iface = dl_list_first(&sm->interfaces,
477252726Srpaulo			      struct upnp_wps_device_interface, list);
478252726Srpaulo
479189251Ssam	/*
480189251Ssam	 * PutMessage is used by external UPnP-based Registrar to perform WPS
481189251Ssam	 * operation with the access point itself; as compared with
482189251Ssam	 * PutWLANResponse which is for proxying.
483189251Ssam	 */
484189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
485214734Srpaulo	msg = xml_get_base64_item(data, "NewInMessage", &ret);
486189251Ssam	if (msg == NULL)
487189251Ssam		return ret;
488252726Srpaulo	res = wps_process_msg(iface->peer.wps, WSC_UPnP, msg);
489214734Srpaulo	if (res == WPS_FAILURE)
490214734Srpaulo		*reply = NULL;
491214734Srpaulo	else
492252726Srpaulo		*reply = wps_get_msg(iface->peer.wps, &op_code);
493189251Ssam	wpabuf_free(msg);
494189251Ssam	if (*reply == NULL)
495189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
496189251Ssam	*replyname = name;
497189251Ssam	return HTTP_OK;
498189251Ssam}
499189251Ssam
500189251Ssam
501189251Ssamstatic enum http_reply_code
502189251Ssamweb_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
503189251Ssam			      struct wpabuf **reply, const char **replyname)
504189251Ssam{
505189251Ssam	struct wpabuf *msg;
506189251Ssam	enum http_reply_code ret;
507189251Ssam	u8 macaddr[ETH_ALEN];
508189251Ssam	int ev_type;
509189251Ssam	int type;
510189251Ssam	char *val;
511252726Srpaulo	struct upnp_wps_device_interface *iface;
512252726Srpaulo	int ok = 0;
513189251Ssam
514189251Ssam	/*
515189251Ssam	 * External UPnP-based Registrar is passing us a message to be proxied
516189251Ssam	 * over to a Wi-Fi -based client of ours.
517189251Ssam	 */
518189251Ssam
519189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
520214734Srpaulo	msg = xml_get_base64_item(data, "NewMessage", &ret);
521214734Srpaulo	if (msg == NULL) {
522214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Could not extract NewMessage "
523214734Srpaulo			   "from PutWLANResponse");
524189251Ssam		return ret;
525214734Srpaulo	}
526214734Srpaulo	val = xml_get_first_item(data, "NewWLANEventType");
527214734Srpaulo	if (val == NULL) {
528214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventType in "
529214734Srpaulo			   "PutWLANResponse");
530189251Ssam		wpabuf_free(msg);
531189251Ssam		return UPNP_ARG_VALUE_INVALID;
532189251Ssam	}
533189251Ssam	ev_type = atol(val);
534189251Ssam	os_free(val);
535214734Srpaulo	val = xml_get_first_item(data, "NewWLANEventMAC");
536214734Srpaulo	if (val == NULL) {
537214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventMAC in "
538214734Srpaulo			   "PutWLANResponse");
539189251Ssam		wpabuf_free(msg);
540189251Ssam		return UPNP_ARG_VALUE_INVALID;
541189251Ssam	}
542214734Srpaulo	if (hwaddr_aton(val, macaddr)) {
543214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid NewWLANEventMAC in "
544214734Srpaulo			   "PutWLANResponse: '%s'", val);
545252726Srpaulo#ifdef CONFIG_WPS_STRICT
546252726Srpaulo		{
547252726Srpaulo			struct wps_parse_attr attr;
548252726Srpaulo			if (wps_parse_msg(msg, &attr) < 0 || attr.version2) {
549252726Srpaulo				wpabuf_free(msg);
550252726Srpaulo				os_free(val);
551252726Srpaulo				return UPNP_ARG_VALUE_INVALID;
552252726Srpaulo			}
553252726Srpaulo		}
554252726Srpaulo#endif /* CONFIG_WPS_STRICT */
555214734Srpaulo		if (hwaddr_aton2(val, macaddr) > 0) {
556214734Srpaulo			/*
557214734Srpaulo			 * At least some versions of Intel PROset seem to be
558214734Srpaulo			 * using dot-deliminated MAC address format here.
559214734Srpaulo			 */
560214734Srpaulo			wpa_printf(MSG_DEBUG, "WPS UPnP: Workaround - allow "
561214734Srpaulo				   "incorrect MAC address format in "
562252726Srpaulo				   "NewWLANEventMAC: %s -> " MACSTR,
563252726Srpaulo				   val, MAC2STR(macaddr));
564214734Srpaulo		} else {
565214734Srpaulo			wpabuf_free(msg);
566214734Srpaulo			os_free(val);
567214734Srpaulo			return UPNP_ARG_VALUE_INVALID;
568214734Srpaulo		}
569214734Srpaulo	}
570189251Ssam	os_free(val);
571189251Ssam	if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
572189251Ssam		struct wps_parse_attr attr;
573189251Ssam		if (wps_parse_msg(msg, &attr) < 0 ||
574189251Ssam		    attr.msg_type == NULL)
575189251Ssam			type = -1;
576189251Ssam		else
577189251Ssam			type = *attr.msg_type;
578189251Ssam		wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
579189251Ssam	} else
580189251Ssam		type = -1;
581252726Srpaulo	dl_list_for_each(iface, &sm->interfaces,
582252726Srpaulo			 struct upnp_wps_device_interface, list) {
583252726Srpaulo		if (iface->ctx->rx_req_put_wlan_response &&
584252726Srpaulo		    iface->ctx->rx_req_put_wlan_response(iface->priv, ev_type,
585252726Srpaulo							 macaddr, msg, type)
586252726Srpaulo		    == 0)
587252726Srpaulo			ok = 1;
588252726Srpaulo	}
589252726Srpaulo
590252726Srpaulo	if (!ok) {
591189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
592189251Ssam			   "rx_req_put_wlan_response");
593189251Ssam		wpabuf_free(msg);
594189251Ssam		return HTTP_INTERNAL_SERVER_ERROR;
595189251Ssam	}
596189251Ssam	wpabuf_free(msg);
597189251Ssam	*replyname = NULL;
598189251Ssam	*reply = NULL;
599189251Ssam	return HTTP_OK;
600189251Ssam}
601189251Ssam
602189251Ssam
603214734Srpaulostatic int find_er_addr(struct subscription *s, struct sockaddr_in *cli)
604189251Ssam{
605214734Srpaulo	struct subscr_addr *a;
606189251Ssam
607214734Srpaulo	dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) {
608214734Srpaulo		if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr)
609214734Srpaulo			return 1;
610189251Ssam	}
611214734Srpaulo	return 0;
612189251Ssam}
613189251Ssam
614189251Ssam
615214734Srpaulostatic struct subscription * find_er(struct upnp_wps_device_sm *sm,
616214734Srpaulo				     struct sockaddr_in *cli)
617189251Ssam{
618214734Srpaulo	struct subscription *s;
619214734Srpaulo	dl_list_for_each(s, &sm->subscriptions, struct subscription, list)
620214734Srpaulo		if (find_er_addr(s, cli))
621214734Srpaulo			return s;
622214734Srpaulo	return NULL;
623189251Ssam}
624189251Ssam
625189251Ssam
626189251Ssamstatic enum http_reply_code
627214734Srpauloweb_process_set_selected_registrar(struct upnp_wps_device_sm *sm,
628214734Srpaulo				   struct sockaddr_in *cli, char *data,
629214734Srpaulo				   struct wpabuf **reply,
630214734Srpaulo				   const char **replyname)
631189251Ssam{
632189251Ssam	struct wpabuf *msg;
633189251Ssam	enum http_reply_code ret;
634214734Srpaulo	struct subscription *s;
635252726Srpaulo	struct upnp_wps_device_interface *iface;
636252726Srpaulo	int err = 0;
637189251Ssam
638214734Srpaulo	wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
639214734Srpaulo	s = find_er(sm, cli);
640214734Srpaulo	if (s == NULL) {
641214734Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar "
642214734Srpaulo			   "from unknown ER");
643214734Srpaulo		return UPNP_ACTION_FAILED;
644189251Ssam	}
645214734Srpaulo	msg = xml_get_base64_item(data, "NewMessage", &ret);
646189251Ssam	if (msg == NULL)
647189251Ssam		return ret;
648252726Srpaulo	dl_list_for_each(iface, &sm->interfaces,
649252726Srpaulo			 struct upnp_wps_device_interface, list) {
650252726Srpaulo		if (upnp_er_set_selected_registrar(iface->wps->registrar, s,
651252726Srpaulo						   msg))
652252726Srpaulo			err = 1;
653189251Ssam	}
654189251Ssam	wpabuf_free(msg);
655252726Srpaulo	if (err)
656252726Srpaulo		return HTTP_INTERNAL_SERVER_ERROR;
657189251Ssam	*replyname = NULL;
658189251Ssam	*reply = NULL;
659189251Ssam	return HTTP_OK;
660189251Ssam}
661189251Ssam
662189251Ssam
663189251Ssamstatic const char *soap_prefix =
664189251Ssam	"<?xml version=\"1.0\"?>\n"
665189251Ssam	"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
666189251Ssam	"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
667189251Ssam	"<s:Body>\n";
668189251Ssamstatic const char *soap_postfix =
669189251Ssam	"</s:Body>\n</s:Envelope>\n";
670189251Ssam
671189251Ssamstatic const char *soap_error_prefix =
672189251Ssam	"<s:Fault>\n"
673189251Ssam	"<faultcode>s:Client</faultcode>\n"
674189251Ssam	"<faultstring>UPnPError</faultstring>\n"
675189251Ssam	"<detail>\n"
676189251Ssam	"<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
677189251Ssamstatic const char *soap_error_postfix =
678189251Ssam	"<errorDescription>Error</errorDescription>\n"
679189251Ssam	"</UPnPError>\n"
680189251Ssam	"</detail>\n"
681189251Ssam	"</s:Fault>\n";
682189251Ssam
683214734Srpaulostatic void web_connection_send_reply(struct http_request *req,
684189251Ssam				      enum http_reply_code ret,
685189251Ssam				      const char *action, int action_len,
686189251Ssam				      const struct wpabuf *reply,
687189251Ssam				      const char *replyname)
688189251Ssam{
689189251Ssam	struct wpabuf *buf;
690189251Ssam	char *replydata;
691189251Ssam	char *put_length_here = NULL;
692189251Ssam	char *body_start = NULL;
693189251Ssam
694189251Ssam	if (reply) {
695189251Ssam		size_t len;
696189251Ssam		replydata = (char *) base64_encode(wpabuf_head(reply),
697189251Ssam						   wpabuf_len(reply), &len);
698189251Ssam	} else
699189251Ssam		replydata = NULL;
700189251Ssam
701189251Ssam	/* Parameters of the response:
702189251Ssam	 *      action(action_len) -- action we are responding to
703189251Ssam	 *      replyname -- a name we need for the reply
704189251Ssam	 *      replydata -- NULL or null-terminated string
705189251Ssam	 */
706189251Ssam	buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
707189251Ssam			   (action_len > 0 ? action_len * 2 : 0));
708189251Ssam	if (buf == NULL) {
709189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
710189251Ssam			   "POST");
711189251Ssam		os_free(replydata);
712214734Srpaulo		http_request_deinit(req);
713189251Ssam		return;
714189251Ssam	}
715189251Ssam
716189251Ssam	/*
717189251Ssam	 * Assuming we will be successful, put in the output header first.
718189251Ssam	 * Note: we do not keep connections alive (and httpread does
719189251Ssam	 * not support it)... therefore we must have Connection: close.
720189251Ssam	 */
721189251Ssam	if (ret == HTTP_OK) {
722189251Ssam		wpabuf_put_str(buf,
723189251Ssam			       "HTTP/1.1 200 OK\r\n"
724189251Ssam			       "Content-Type: text/xml; "
725189251Ssam			       "charset=\"utf-8\"\r\n");
726189251Ssam	} else {
727189251Ssam		wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
728189251Ssam	}
729189251Ssam	wpabuf_put_str(buf, http_connection_close);
730189251Ssam
731189251Ssam	wpabuf_put_str(buf, "Content-Length: ");
732189251Ssam	/*
733189251Ssam	 * We will paste the length in later, leaving some extra whitespace.
734189251Ssam	 * HTTP code is supposed to be tolerant of extra whitespace.
735189251Ssam	 */
736189251Ssam	put_length_here = wpabuf_put(buf, 0);
737189251Ssam	wpabuf_put_str(buf, "        \r\n");
738189251Ssam
739189251Ssam	http_put_date(buf);
740189251Ssam
741189251Ssam	/* terminating empty line */
742189251Ssam	wpabuf_put_str(buf, "\r\n");
743189251Ssam
744189251Ssam	body_start = wpabuf_put(buf, 0);
745189251Ssam
746189251Ssam	if (ret == HTTP_OK) {
747189251Ssam		wpabuf_put_str(buf, soap_prefix);
748189251Ssam		wpabuf_put_str(buf, "<u:");
749189251Ssam		wpabuf_put_data(buf, action, action_len);
750189251Ssam		wpabuf_put_str(buf, "Response xmlns:u=\"");
751189251Ssam		wpabuf_put_str(buf, urn_wfawlanconfig);
752189251Ssam		wpabuf_put_str(buf, "\">\n");
753189251Ssam		if (replydata && replyname) {
754189251Ssam			/* TODO: might possibly need to escape part of reply
755189251Ssam			 * data? ...
756189251Ssam			 * probably not, unlikely to have ampersand(&) or left
757189251Ssam			 * angle bracket (<) in it...
758189251Ssam			 */
759189251Ssam			wpabuf_printf(buf, "<%s>", replyname);
760189251Ssam			wpabuf_put_str(buf, replydata);
761189251Ssam			wpabuf_printf(buf, "</%s>\n", replyname);
762189251Ssam		}
763189251Ssam		wpabuf_put_str(buf, "</u:");
764189251Ssam		wpabuf_put_data(buf, action, action_len);
765189251Ssam		wpabuf_put_str(buf, "Response>\n");
766189251Ssam		wpabuf_put_str(buf, soap_postfix);
767189251Ssam	} else {
768189251Ssam		/* Error case */
769189251Ssam		wpabuf_put_str(buf, soap_prefix);
770189251Ssam		wpabuf_put_str(buf, soap_error_prefix);
771189251Ssam		wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
772189251Ssam		wpabuf_put_str(buf, soap_error_postfix);
773189251Ssam		wpabuf_put_str(buf, soap_postfix);
774189251Ssam	}
775189251Ssam	os_free(replydata);
776189251Ssam
777189251Ssam	/* Now patch in the content length at the end */
778189251Ssam	if (body_start && put_length_here) {
779189251Ssam		int body_length = (char *) wpabuf_put(buf, 0) - body_start;
780189251Ssam		char len_buf[10];
781189251Ssam		os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
782189251Ssam		os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
783189251Ssam	}
784189251Ssam
785214734Srpaulo	http_request_send_and_deinit(req, buf);
786189251Ssam}
787189251Ssam
788189251Ssam
789214734Srpaulostatic const char * web_get_action(struct http_request *req,
790214734Srpaulo				   size_t *action_len)
791189251Ssam{
792189251Ssam	const char *match;
793189251Ssam	int match_len;
794189251Ssam	char *b;
795189251Ssam	char *action;
796189251Ssam
797189251Ssam	*action_len = 0;
798189251Ssam	/* The SOAPAction line of the header tells us what we want to do */
799214734Srpaulo	b = http_request_get_hdr_line(req, "SOAPAction:");
800189251Ssam	if (b == NULL)
801189251Ssam		return NULL;
802189251Ssam	if (*b == '"')
803189251Ssam		b++;
804189251Ssam	else
805189251Ssam		return NULL;
806189251Ssam	match = urn_wfawlanconfig;
807189251Ssam	match_len = os_strlen(urn_wfawlanconfig) - 1;
808189251Ssam	if (os_strncasecmp(b, match, match_len))
809189251Ssam		return NULL;
810189251Ssam	b += match_len;
811189251Ssam	/* skip over version */
812189251Ssam	while (isgraph(*b) && *b != '#')
813189251Ssam		b++;
814189251Ssam	if (*b != '#')
815189251Ssam		return NULL;
816189251Ssam	b++;
817189251Ssam	/* Following the sharp(#) should be the action and a double quote */
818189251Ssam	action = b;
819189251Ssam	while (isgraph(*b) && *b != '"')
820189251Ssam		b++;
821189251Ssam	if (*b != '"')
822189251Ssam		return NULL;
823189251Ssam	*action_len = b - action;
824189251Ssam	return action;
825189251Ssam}
826189251Ssam
827189251Ssam
828189251Ssam/* Given that we have received a header w/ POST, act upon it
829189251Ssam *
830189251Ssam * Format of POST (case-insensitive):
831189251Ssam *
832189251Ssam * First line must be:
833189251Ssam *      POST /<file> HTTP/1.1
834189251Ssam * Since we don't do anything fancy we just ignore other lines.
835189251Ssam *
836189251Ssam * Our response (if no error) which includes only required lines is:
837189251Ssam * HTTP/1.1 200 OK
838189251Ssam * Connection: close
839189251Ssam * Content-Type: text/xml
840189251Ssam * Date: <rfc1123-date>
841189251Ssam *
842189251Ssam * Header lines must end with \r\n
843189251Ssam * Per RFC 2616, content-length: is not required but connection:close
844189251Ssam * would appear to be required (given that we will be closing it!).
845189251Ssam */
846214734Srpaulostatic void web_connection_parse_post(struct upnp_wps_device_sm *sm,
847214734Srpaulo				      struct sockaddr_in *cli,
848214734Srpaulo				      struct http_request *req,
849189251Ssam				      const char *filename)
850189251Ssam{
851189251Ssam	enum http_reply_code ret;
852214734Srpaulo	char *data = http_request_get_data(req); /* body of http msg */
853214734Srpaulo	const char *action = NULL;
854214734Srpaulo	size_t action_len = 0;
855189251Ssam	const char *replyname = NULL; /* argument name for the reply */
856189251Ssam	struct wpabuf *reply = NULL; /* data for the reply */
857189251Ssam
858214734Srpaulo	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
859214734Srpaulo		wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
860214734Srpaulo			   filename);
861214734Srpaulo		ret = HTTP_NOT_FOUND;
862214734Srpaulo		goto bad;
863214734Srpaulo	}
864214734Srpaulo
865189251Ssam	ret = UPNP_INVALID_ACTION;
866214734Srpaulo	action = web_get_action(req, &action_len);
867189251Ssam	if (action == NULL)
868189251Ssam		goto bad;
869189251Ssam
870189251Ssam	if (!os_strncasecmp("GetDeviceInfo", action, action_len))
871189251Ssam		ret = web_process_get_device_info(sm, &reply, &replyname);
872189251Ssam	else if (!os_strncasecmp("PutMessage", action, action_len))
873189251Ssam		ret = web_process_put_message(sm, data, &reply, &replyname);
874189251Ssam	else if (!os_strncasecmp("PutWLANResponse", action, action_len))
875189251Ssam		ret = web_process_put_wlan_response(sm, data, &reply,
876189251Ssam						    &replyname);
877189251Ssam	else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
878214734Srpaulo		ret = web_process_set_selected_registrar(sm, cli, data, &reply,
879189251Ssam							 &replyname);
880189251Ssam	else
881189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
882189251Ssam
883189251Ssambad:
884189251Ssam	if (ret != HTTP_OK)
885189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
886214734Srpaulo	web_connection_send_reply(req, ret, action, action_len, reply,
887189251Ssam				  replyname);
888189251Ssam	wpabuf_free(reply);
889189251Ssam}
890189251Ssam
891189251Ssam
892189251Ssam/* Given that we have received a header w/ SUBSCRIBE, act upon it
893189251Ssam *
894189251Ssam * Format of SUBSCRIBE (case-insensitive):
895189251Ssam *
896189251Ssam * First line must be:
897189251Ssam *      SUBSCRIBE /wps_event HTTP/1.1
898189251Ssam *
899189251Ssam * Our response (if no error) which includes only required lines is:
900189251Ssam * HTTP/1.1 200 OK
901189251Ssam * Server: xx, UPnP/1.0, xx
902189251Ssam * SID: uuid:xxxxxxxxx
903189251Ssam * Timeout: Second-<n>
904189251Ssam * Content-Length: 0
905189251Ssam * Date: xxxx
906189251Ssam *
907189251Ssam * Header lines must end with \r\n
908189251Ssam * Per RFC 2616, content-length: is not required but connection:close
909189251Ssam * would appear to be required (given that we will be closing it!).
910189251Ssam */
911214734Srpaulostatic void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
912214734Srpaulo					   struct http_request *req,
913189251Ssam					   const char *filename)
914189251Ssam{
915189251Ssam	struct wpabuf *buf;
916189251Ssam	char *b;
917214734Srpaulo	char *hdr = http_request_get_hdr(req);
918189251Ssam	char *h;
919189251Ssam	char *match;
920189251Ssam	int match_len;
921189251Ssam	char *end;
922189251Ssam	int len;
923189251Ssam	int got_nt = 0;
924189251Ssam	u8 uuid[UUID_LEN];
925189251Ssam	int got_uuid = 0;
926189251Ssam	char *callback_urls = NULL;
927189251Ssam	struct subscription *s = NULL;
928189251Ssam	enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
929189251Ssam
930189251Ssam	buf = wpabuf_alloc(1000);
931214734Srpaulo	if (buf == NULL) {
932214734Srpaulo		http_request_deinit(req);
933189251Ssam		return;
934214734Srpaulo	}
935189251Ssam
936252726Srpaulo	wpa_hexdump_ascii(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE",
937252726Srpaulo			  (u8 *) hdr, os_strlen(hdr));
938252726Srpaulo
939189251Ssam	/* Parse/validate headers */
940189251Ssam	h = hdr;
941189251Ssam	/* First line: SUBSCRIBE /wps_event HTTP/1.1
942189251Ssam	 * has already been parsed.
943189251Ssam	 */
944189251Ssam	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
945189251Ssam		ret = HTTP_PRECONDITION_FAILED;
946189251Ssam		goto error;
947189251Ssam	}
948189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
949189251Ssam	end = os_strchr(h, '\n');
950189251Ssam
951189251Ssam	for (; end != NULL; h = end + 1) {
952189251Ssam		/* Option line by option line */
953189251Ssam		h = end + 1;
954189251Ssam		end = os_strchr(h, '\n');
955189251Ssam		if (end == NULL)
956189251Ssam			break; /* no unterminated lines allowed */
957189251Ssam
958189251Ssam		/* NT assures that it is our type of subscription;
959252726Srpaulo		 * not used for a renewal.
960189251Ssam		 **/
961189251Ssam		match = "NT:";
962189251Ssam		match_len = os_strlen(match);
963189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
964189251Ssam			h += match_len;
965189251Ssam			while (*h == ' ' || *h == '\t')
966189251Ssam				h++;
967189251Ssam			match = "upnp:event";
968189251Ssam			match_len = os_strlen(match);
969189251Ssam			if (os_strncasecmp(h, match, match_len) != 0) {
970189251Ssam				ret = HTTP_BAD_REQUEST;
971189251Ssam				goto error;
972189251Ssam			}
973189251Ssam			got_nt = 1;
974189251Ssam			continue;
975189251Ssam		}
976189251Ssam		/* HOST should refer to us */
977189251Ssam#if 0
978189251Ssam		match = "HOST:";
979189251Ssam		match_len = os_strlen(match);
980189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
981189251Ssam			h += match_len;
982189251Ssam			while (*h == ' ' || *h == '\t')
983189251Ssam				h++;
984189251Ssam			.....
985189251Ssam		}
986189251Ssam#endif
987189251Ssam		/* CALLBACK gives one or more URLs for NOTIFYs
988189251Ssam		 * to be sent as a result of the subscription.
989189251Ssam		 * Each URL is enclosed in angle brackets.
990189251Ssam		 */
991189251Ssam		match = "CALLBACK:";
992189251Ssam		match_len = os_strlen(match);
993189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
994189251Ssam			h += match_len;
995189251Ssam			while (*h == ' ' || *h == '\t')
996189251Ssam				h++;
997189251Ssam			len = end - h;
998189251Ssam			os_free(callback_urls);
999189251Ssam			callback_urls = os_malloc(len + 1);
1000189251Ssam			if (callback_urls == NULL) {
1001189251Ssam				ret = HTTP_INTERNAL_SERVER_ERROR;
1002189251Ssam				goto error;
1003189251Ssam			}
1004189251Ssam			os_memcpy(callback_urls, h, len);
1005189251Ssam			callback_urls[len] = 0;
1006189251Ssam			continue;
1007189251Ssam		}
1008189251Ssam		/* SID is only for renewal */
1009189251Ssam		match = "SID:";
1010189251Ssam		match_len = os_strlen(match);
1011189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
1012189251Ssam			h += match_len;
1013189251Ssam			while (*h == ' ' || *h == '\t')
1014189251Ssam				h++;
1015189251Ssam			match = "uuid:";
1016189251Ssam			match_len = os_strlen(match);
1017189251Ssam			if (os_strncasecmp(h, match, match_len) != 0) {
1018189251Ssam				ret = HTTP_BAD_REQUEST;
1019189251Ssam				goto error;
1020189251Ssam			}
1021189251Ssam			h += match_len;
1022189251Ssam			while (*h == ' ' || *h == '\t')
1023189251Ssam				h++;
1024189251Ssam			if (uuid_str2bin(h, uuid)) {
1025189251Ssam				ret = HTTP_BAD_REQUEST;
1026189251Ssam				goto error;
1027189251Ssam			}
1028189251Ssam			got_uuid = 1;
1029189251Ssam			continue;
1030189251Ssam		}
1031189251Ssam		/* TIMEOUT is requested timeout, but apparently we can
1032189251Ssam		 * just ignore this.
1033189251Ssam		 */
1034189251Ssam	}
1035189251Ssam
1036189251Ssam	if (got_uuid) {
1037189251Ssam		/* renewal */
1038252726Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewal");
1039189251Ssam		if (callback_urls) {
1040189251Ssam			ret = HTTP_BAD_REQUEST;
1041189251Ssam			goto error;
1042189251Ssam		}
1043189251Ssam		s = subscription_renew(sm, uuid);
1044189251Ssam		if (s == NULL) {
1045252726Srpaulo			char str[80];
1046252726Srpaulo			uuid_bin2str(uuid, str, sizeof(str));
1047252726Srpaulo			wpa_printf(MSG_DEBUG, "WPS UPnP: Could not find "
1048252726Srpaulo				   "SID %s", str);
1049189251Ssam			ret = HTTP_PRECONDITION_FAILED;
1050189251Ssam			goto error;
1051189251Ssam		}
1052189251Ssam	} else if (callback_urls) {
1053252726Srpaulo		wpa_printf(MSG_DEBUG, "WPS UPnP: New subscription");
1054189251Ssam		if (!got_nt) {
1055189251Ssam			ret = HTTP_PRECONDITION_FAILED;
1056189251Ssam			goto error;
1057189251Ssam		}
1058189251Ssam		s = subscription_start(sm, callback_urls);
1059189251Ssam		if (s == NULL) {
1060189251Ssam			ret = HTTP_INTERNAL_SERVER_ERROR;
1061189251Ssam			goto error;
1062189251Ssam		}
1063189251Ssam	} else {
1064189251Ssam		ret = HTTP_PRECONDITION_FAILED;
1065189251Ssam		goto error;
1066189251Ssam	}
1067189251Ssam
1068189251Ssam	/* success */
1069189251Ssam	http_put_reply_code(buf, HTTP_OK);
1070189251Ssam	wpabuf_put_str(buf, http_server_hdr);
1071189251Ssam	wpabuf_put_str(buf, http_connection_close);
1072189251Ssam	wpabuf_put_str(buf, "Content-Length: 0\r\n");
1073189251Ssam	wpabuf_put_str(buf, "SID: uuid:");
1074189251Ssam	/* subscription id */
1075189251Ssam	b = wpabuf_put(buf, 0);
1076189251Ssam	uuid_bin2str(s->uuid, b, 80);
1077252726Srpaulo	wpa_printf(MSG_DEBUG, "WPS UPnP: Assigned SID %s", b);
1078189251Ssam	wpabuf_put(buf, os_strlen(b));
1079189251Ssam	wpabuf_put_str(buf, "\r\n");
1080189251Ssam	wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
1081189251Ssam	http_put_date(buf);
1082189251Ssam	/* And empty line to terminate header: */
1083189251Ssam	wpabuf_put_str(buf, "\r\n");
1084189251Ssam
1085189251Ssam	os_free(callback_urls);
1086214734Srpaulo	http_request_send_and_deinit(req, buf);
1087189251Ssam	return;
1088189251Ssam
1089189251Ssamerror:
1090189251Ssam	/* Per UPnP spec:
1091189251Ssam	* Errors
1092189251Ssam	* Incompatible headers
1093189251Ssam	*   400 Bad Request. If SID header and one of NT or CALLBACK headers
1094189251Ssam	*     are present, the publisher must respond with HTTP error
1095189251Ssam	*     400 Bad Request.
1096189251Ssam	* Missing or invalid CALLBACK
1097189251Ssam	*   412 Precondition Failed. If CALLBACK header is missing or does not
1098189251Ssam	*     contain a valid HTTP URL, the publisher must respond with HTTP
1099189251Ssam	*     error 412 Precondition Failed.
1100189251Ssam	* Invalid NT
1101189251Ssam	*   412 Precondition Failed. If NT header does not equal upnp:event,
1102189251Ssam	*     the publisher must respond with HTTP error 412 Precondition
1103189251Ssam	*     Failed.
1104189251Ssam	* [For resubscription, use 412 if unknown uuid].
1105189251Ssam	* Unable to accept subscription
1106189251Ssam	*   5xx. If a publisher is not able to accept a subscription (such as
1107189251Ssam	*     due to insufficient resources), it must respond with a
1108189251Ssam	*     HTTP 500-series error code.
1109189251Ssam	*   599 Too many subscriptions (not a standard HTTP error)
1110189251Ssam	*/
1111252726Srpaulo	wpa_printf(MSG_DEBUG, "WPS UPnP: SUBSCRIBE failed - return %d", ret);
1112189251Ssam	http_put_empty(buf, ret);
1113214734Srpaulo	http_request_send_and_deinit(req, buf);
1114209158Srpaulo	os_free(callback_urls);
1115189251Ssam}
1116189251Ssam
1117189251Ssam
1118189251Ssam/* Given that we have received a header w/ UNSUBSCRIBE, act upon it
1119189251Ssam *
1120189251Ssam * Format of UNSUBSCRIBE (case-insensitive):
1121189251Ssam *
1122189251Ssam * First line must be:
1123189251Ssam *      UNSUBSCRIBE /wps_event HTTP/1.1
1124189251Ssam *
1125189251Ssam * Our response (if no error) which includes only required lines is:
1126189251Ssam * HTTP/1.1 200 OK
1127189251Ssam * Content-Length: 0
1128189251Ssam *
1129189251Ssam * Header lines must end with \r\n
1130189251Ssam * Per RFC 2616, content-length: is not required but connection:close
1131189251Ssam * would appear to be required (given that we will be closing it!).
1132189251Ssam */
1133214734Srpaulostatic void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
1134214734Srpaulo					     struct http_request *req,
1135189251Ssam					     const char *filename)
1136189251Ssam{
1137189251Ssam	struct wpabuf *buf;
1138214734Srpaulo	char *hdr = http_request_get_hdr(req);
1139189251Ssam	char *h;
1140189251Ssam	char *match;
1141189251Ssam	int match_len;
1142189251Ssam	char *end;
1143189251Ssam	u8 uuid[UUID_LEN];
1144189251Ssam	int got_uuid = 0;
1145189251Ssam	struct subscription *s = NULL;
1146189251Ssam	enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1147189251Ssam
1148189251Ssam	/* Parse/validate headers */
1149189251Ssam	h = hdr;
1150189251Ssam	/* First line: UNSUBSCRIBE /wps_event HTTP/1.1
1151189251Ssam	 * has already been parsed.
1152189251Ssam	 */
1153189251Ssam	if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1154189251Ssam		ret = HTTP_PRECONDITION_FAILED;
1155189251Ssam		goto send_msg;
1156189251Ssam	}
1157189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
1158189251Ssam	end = os_strchr(h, '\n');
1159189251Ssam
1160189251Ssam	for (; end != NULL; h = end + 1) {
1161189251Ssam		/* Option line by option line */
1162189251Ssam		h = end + 1;
1163189251Ssam		end = os_strchr(h, '\n');
1164189251Ssam		if (end == NULL)
1165189251Ssam			break; /* no unterminated lines allowed */
1166189251Ssam
1167189251Ssam		/* HOST should refer to us */
1168189251Ssam#if 0
1169189251Ssam		match = "HOST:";
1170189251Ssam		match_len = os_strlen(match);
1171189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
1172189251Ssam			h += match_len;
1173189251Ssam			while (*h == ' ' || *h == '\t')
1174189251Ssam				h++;
1175189251Ssam			.....
1176189251Ssam		}
1177189251Ssam#endif
1178189251Ssam		/* SID is only for renewal */
1179189251Ssam		match = "SID:";
1180189251Ssam		match_len = os_strlen(match);
1181189251Ssam		if (os_strncasecmp(h, match, match_len) == 0) {
1182189251Ssam			h += match_len;
1183189251Ssam			while (*h == ' ' || *h == '\t')
1184189251Ssam				h++;
1185189251Ssam			match = "uuid:";
1186189251Ssam			match_len = os_strlen(match);
1187189251Ssam			if (os_strncasecmp(h, match, match_len) != 0) {
1188189251Ssam				ret = HTTP_BAD_REQUEST;
1189189251Ssam				goto send_msg;
1190189251Ssam			}
1191189251Ssam			h += match_len;
1192189251Ssam			while (*h == ' ' || *h == '\t')
1193189251Ssam				h++;
1194189251Ssam			if (uuid_str2bin(h, uuid)) {
1195189251Ssam				ret = HTTP_BAD_REQUEST;
1196189251Ssam				goto send_msg;
1197189251Ssam			}
1198189251Ssam			got_uuid = 1;
1199189251Ssam			continue;
1200189251Ssam		}
1201189251Ssam	}
1202189251Ssam
1203189251Ssam	if (got_uuid) {
1204189251Ssam		s = subscription_find(sm, uuid);
1205189251Ssam		if (s) {
1206214734Srpaulo			struct subscr_addr *sa;
1207214734Srpaulo			sa = dl_list_first(&s->addr_list, struct subscr_addr,
1208214734Srpaulo					   list);
1209189251Ssam			wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
1210214734Srpaulo				   s, (sa && sa->domain_and_port) ?
1211214734Srpaulo				   sa->domain_and_port : "-null-");
1212214734Srpaulo			dl_list_del(&s->list);
1213189251Ssam			subscription_destroy(s);
1214189251Ssam		}
1215189251Ssam	} else {
1216189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
1217189251Ssam			   "found)");
1218189251Ssam		ret = HTTP_PRECONDITION_FAILED;
1219189251Ssam		goto send_msg;
1220189251Ssam	}
1221189251Ssam
1222189251Ssam	ret = HTTP_OK;
1223189251Ssam
1224189251Ssamsend_msg:
1225189251Ssam	buf = wpabuf_alloc(200);
1226214734Srpaulo	if (buf == NULL) {
1227214734Srpaulo		http_request_deinit(req);
1228189251Ssam		return;
1229214734Srpaulo	}
1230189251Ssam	http_put_empty(buf, ret);
1231214734Srpaulo	http_request_send_and_deinit(req, buf);
1232189251Ssam}
1233189251Ssam
1234189251Ssam
1235189251Ssam/* Send error in response to unknown requests */
1236214734Srpaulostatic void web_connection_unimplemented(struct http_request *req)
1237189251Ssam{
1238189251Ssam	struct wpabuf *buf;
1239189251Ssam	buf = wpabuf_alloc(200);
1240214734Srpaulo	if (buf == NULL) {
1241214734Srpaulo		http_request_deinit(req);
1242189251Ssam		return;
1243214734Srpaulo	}
1244189251Ssam	http_put_empty(buf, HTTP_UNIMPLEMENTED);
1245214734Srpaulo	http_request_send_and_deinit(req, buf);
1246189251Ssam}
1247189251Ssam
1248189251Ssam
1249189251Ssam
1250189251Ssam/* Called when we have gotten an apparently valid http request.
1251189251Ssam */
1252214734Srpaulostatic void web_connection_check_data(void *ctx, struct http_request *req)
1253189251Ssam{
1254214734Srpaulo	struct upnp_wps_device_sm *sm = ctx;
1255214734Srpaulo	enum httpread_hdr_type htype = http_request_get_type(req);
1256214734Srpaulo	char *filename = http_request_get_uri(req);
1257214734Srpaulo	struct sockaddr_in *cli = http_request_get_cli_addr(req);
1258189251Ssam
1259189251Ssam	if (!filename) {
1260189251Ssam		wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
1261214734Srpaulo		http_request_deinit(req);
1262189251Ssam		return;
1263189251Ssam	}
1264189251Ssam	/* Trim leading slashes from filename */
1265189251Ssam	while (*filename == '/')
1266189251Ssam		filename++;
1267189251Ssam
1268189251Ssam	wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
1269214734Srpaulo		   htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port));
1270189251Ssam
1271189251Ssam	switch (htype) {
1272189251Ssam	case HTTPREAD_HDR_TYPE_GET:
1273214734Srpaulo		web_connection_parse_get(sm, req, filename);
1274189251Ssam		break;
1275189251Ssam	case HTTPREAD_HDR_TYPE_POST:
1276214734Srpaulo		web_connection_parse_post(sm, cli, req, filename);
1277189251Ssam		break;
1278189251Ssam	case HTTPREAD_HDR_TYPE_SUBSCRIBE:
1279214734Srpaulo		web_connection_parse_subscribe(sm, req, filename);
1280189251Ssam		break;
1281189251Ssam	case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
1282214734Srpaulo		web_connection_parse_unsubscribe(sm, req, filename);
1283189251Ssam		break;
1284214734Srpaulo
1285189251Ssam		/* We are not required to support M-POST; just plain
1286189251Ssam		 * POST is supposed to work, so we only support that.
1287189251Ssam		 * If for some reason we need to support M-POST, it is
1288189251Ssam		 * mostly the same as POST, with small differences.
1289189251Ssam		 */
1290189251Ssam	default:
1291189251Ssam		/* Send 501 for anything else */
1292214734Srpaulo		web_connection_unimplemented(req);
1293189251Ssam		break;
1294189251Ssam	}
1295189251Ssam}
1296189251Ssam
1297189251Ssam
1298189251Ssam/*
1299189251Ssam * Listening for web connections
1300189251Ssam * We have a single TCP listening port, and hand off connections as we get
1301189251Ssam * them.
1302189251Ssam */
1303189251Ssam
1304189251Ssamvoid web_listener_stop(struct upnp_wps_device_sm *sm)
1305189251Ssam{
1306214734Srpaulo	http_server_deinit(sm->web_srv);
1307214734Srpaulo	sm->web_srv = NULL;
1308189251Ssam}
1309189251Ssam
1310189251Ssam
1311189251Ssamint web_listener_start(struct upnp_wps_device_sm *sm)
1312189251Ssam{
1313214734Srpaulo	struct in_addr addr;
1314214734Srpaulo	addr.s_addr = sm->ip_addr;
1315214734Srpaulo	sm->web_srv = http_server_init(&addr, -1, web_connection_check_data,
1316214734Srpaulo				       sm);
1317214734Srpaulo	if (sm->web_srv == NULL) {
1318214734Srpaulo		web_listener_stop(sm);
1319214734Srpaulo		return -1;
1320189251Ssam	}
1321214734Srpaulo	sm->web_port = http_server_get_port(sm->web_srv);
1322189251Ssam
1323189251Ssam	return 0;
1324189251Ssam}
1325