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