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>
8 * See wps_upnp.c for more details on licensing and code history.
17 #include "http_server.h"
20 #include "wps_upnp_i.h"
23 /***************************************************************************
24 * Web connections (we serve pages of info about ourselves, handle
25 * requests, etc. etc.).
26 **************************************************************************/
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 */
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";
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.
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"
51 "<name>GetDeviceInfo</name>\n"
54 "<name>NewDeviceInfo</name>\n"
55 "<direction>out</direction>\n"
56 "<relatedStateVariable>DeviceInfo</relatedStateVariable>\n"
61 "<name>PutMessage</name>\n"
64 "<name>NewInMessage</name>\n"
65 "<direction>in</direction>\n"
66 "<relatedStateVariable>InMessage</relatedStateVariable>\n"
69 "<name>NewOutMessage</name>\n"
70 "<direction>out</direction>\n"
71 "<relatedStateVariable>OutMessage</relatedStateVariable>\n"
76 "<name>GetAPSettings</name>\n"
79 "<name>NewMessage</name>\n"
80 "<direction>in</direction>\n"
81 "<relatedStateVariable>Message</relatedStateVariable>\n"
84 "<name>NewAPSettings</name>\n"
85 "<direction>out</direction>\n"
86 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
91 "<name>SetAPSettings</name>\n"
94 "<name>APSettings</name>\n"
95 "<direction>in</direction>\n"
96 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
101 "<name>DelAPSettings</name>\n"
104 "<name>NewAPSettings</name>\n"
105 "<direction>in</direction>\n"
106 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
111 "<name>GetSTASettings</name>\n"
114 "<name>NewMessage</name>\n"
115 "<direction>in</direction>\n"
116 "<relatedStateVariable>Message</relatedStateVariable>\n"
119 "<name>NewSTASettings</name>\n"
120 "<direction>out</direction>\n"
121 "<relatedStateVariable>STASettings</relatedStateVariable>\n"
126 "<name>SetSTASettings</name>\n"
129 "<name>NewSTASettings</name>\n"
130 "<direction>out</direction>\n"
131 "<relatedStateVariable>STASettings</relatedStateVariable>\n"
136 "<name>DelSTASettings</name>\n"
139 "<name>NewSTASettings</name>\n"
140 "<direction>in</direction>\n"
141 "<relatedStateVariable>STASettings</relatedStateVariable>\n"
146 "<name>PutWLANResponse</name>\n"
149 "<name>NewMessage</name>\n"
150 "<direction>in</direction>\n"
151 "<relatedStateVariable>Message</relatedStateVariable>\n"
154 "<name>NewWLANEventType</name>\n"
155 "<direction>in</direction>\n"
156 "<relatedStateVariable>WLANEventType</relatedStateVariable>\n"
159 "<name>NewWLANEventMAC</name>\n"
160 "<direction>in</direction>\n"
161 "<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n"
166 "<name>SetSelectedRegistrar</name>\n"
169 "<name>NewMessage</name>\n"
170 "<direction>in</direction>\n"
171 "<relatedStateVariable>Message</relatedStateVariable>\n"
176 "<name>RebootAP</name>\n"
179 "<name>NewAPSettings</name>\n"
180 "<direction>in</direction>\n"
181 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
186 "<name>ResetAP</name>\n"
189 "<name>NewMessage</name>\n"
190 "<direction>in</direction>\n"
191 "<relatedStateVariable>Message</relatedStateVariable>\n"
196 "<name>RebootSTA</name>\n"
199 "<name>NewSTASettings</name>\n"
200 "<direction>in</direction>\n"
201 "<relatedStateVariable>APSettings</relatedStateVariable>\n"
206 "<name>ResetSTA</name>\n"
209 "<name>NewMessage</name>\n"
210 "<direction>in</direction>\n"
211 "<relatedStateVariable>Message</relatedStateVariable>\n"
216 "<serviceStateTable>\n"
217 "<stateVariable sendEvents=\"no\">\n"
218 "<name>Message</name>\n"
219 "<dataType>bin.base64</dataType>\n"
221 "<stateVariable sendEvents=\"no\">\n"
222 "<name>InMessage</name>\n"
223 "<dataType>bin.base64</dataType>\n"
225 "<stateVariable sendEvents=\"no\">\n"
226 "<name>OutMessage</name>\n"
227 "<dataType>bin.base64</dataType>\n"
229 "<stateVariable sendEvents=\"no\">\n"
230 "<name>DeviceInfo</name>\n"
231 "<dataType>bin.base64</dataType>\n"
233 "<stateVariable sendEvents=\"no\">\n"
234 "<name>APSettings</name>\n"
235 "<dataType>bin.base64</dataType>\n"
237 "<stateVariable sendEvents=\"yes\">\n"
238 "<name>APStatus</name>\n"
239 "<dataType>ui1</dataType>\n"
241 "<stateVariable sendEvents=\"no\">\n"
242 "<name>STASettings</name>\n"
243 "<dataType>bin.base64</dataType>\n"
245 "<stateVariable sendEvents=\"yes\">\n"
246 "<name>STAStatus</name>\n"
247 "<dataType>ui1</dataType>\n"
249 "<stateVariable sendEvents=\"yes\">\n"
250 "<name>WLANEvent</name>\n"
251 "<dataType>bin.base64</dataType>\n"
253 "<stateVariable sendEvents=\"no\">\n"
254 "<name>WLANEventType</name>\n"
255 "<dataType>ui1</dataType>\n"
257 "<stateVariable sendEvents=\"no\">\n"
258 "<name>WLANEventMAC</name>\n"
259 "<dataType>string</dataType>\n"
261 "<stateVariable sendEvents=\"no\">\n"
262 "<name>WLANResponse</name>\n"
263 "<dataType>bin.base64</dataType>\n"
265 "</serviceStateTable>\n"
270 static const char *wps_device_xml_prefix =
271 "<?xml version=\"1.0\"?>\n"
272 "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
278 "<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1"
281 static const char *wps_device_xml_postfix =
284 "<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1"
286 "<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>"
288 "<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n"
289 "<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n"
290 "<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n"
297 /* format_wps_device_xml -- produce content of "file" wps_device.xml
298 * (UPNP_WPS_DEVICE_XML_FILE)
300 static void format_wps_device_xml(struct upnp_wps_device_sm *sm,
304 char uuid_string[80];
306 wpabuf_put_str(buf, wps_device_xml_prefix);
309 * Add required fields with default values if not configured. Add
310 * optional and recommended fields only if configured.
312 s = sm->wps->friendly_name;
313 s = ((s && *s) ? s : "WPS Access Point");
314 xml_add_tagged_data(buf, "friendlyName", s);
316 s = sm->wps->dev.manufacturer;
317 s = ((s && *s) ? s : "");
318 xml_add_tagged_data(buf, "manufacturer", s);
320 if (sm->wps->manufacturer_url)
321 xml_add_tagged_data(buf, "manufacturerURL",
322 sm->wps->manufacturer_url);
324 if (sm->wps->model_description)
325 xml_add_tagged_data(buf, "modelDescription",
326 sm->wps->model_description);
328 s = sm->wps->dev.model_name;
329 s = ((s && *s) ? s : "");
330 xml_add_tagged_data(buf, "modelName", s);
332 if (sm->wps->dev.model_number)
333 xml_add_tagged_data(buf, "modelNumber",
334 sm->wps->dev.model_number);
336 if (sm->wps->model_url)
337 xml_add_tagged_data(buf, "modelURL", sm->wps->model_url);
339 if (sm->wps->dev.serial_number)
340 xml_add_tagged_data(buf, "serialNumber",
341 sm->wps->dev.serial_number);
343 uuid_bin2str(sm->wps->uuid, uuid_string, sizeof(uuid_string));
345 /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
348 wpabuf_put_str(buf, "<UDN>uuid:");
349 xml_data_encode(buf, s, os_strlen(s));
350 wpabuf_put_str(buf, "</UDN>\n");
353 xml_add_tagged_data(buf, "UPC", sm->wps->upc);
355 wpabuf_put_str(buf, wps_device_xml_postfix);
359 static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
361 wpabuf_put_str(buf, "HTTP/1.1 ");
364 wpabuf_put_str(buf, "200 OK\r\n");
366 case HTTP_BAD_REQUEST:
367 wpabuf_put_str(buf, "400 Bad request\r\n");
369 case HTTP_PRECONDITION_FAILED:
370 wpabuf_put_str(buf, "412 Precondition failed\r\n");
372 case HTTP_UNIMPLEMENTED:
373 wpabuf_put_str(buf, "501 Unimplemented\r\n");
375 case HTTP_INTERNAL_SERVER_ERROR:
377 wpabuf_put_str(buf, "500 Internal server error\r\n");
383 static void http_put_date(struct wpabuf *buf)
385 wpabuf_put_str(buf, "Date: ");
387 wpabuf_put_str(buf, "\r\n");
391 static void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
393 http_put_reply_code(buf, code);
394 wpabuf_put_str(buf, http_server_hdr);
395 wpabuf_put_str(buf, http_connection_close);
396 wpabuf_put_str(buf, "Content-Length: 0\r\n"
401 /* Given that we have received a header w/ GET, act upon it
403 * Format of GET (case-insensitive):
405 * First line must be:
406 * GET /<file> HTTP/1.1
407 * Since we don't do anything fancy we just ignore other lines.
409 * Our response (if no error) which includes only required lines is:
412 * Content-Type: text/xml
413 * Date: <rfc1123-date>
415 * Header lines must end with \r\n
416 * Per RFC 2616, content-length: is not required but connection:close
417 * would appear to be required (given that we will be closing it!).
419 static void web_connection_parse_get(struct upnp_wps_device_sm *sm,
420 struct http_request *hreq, char *filename)
422 struct wpabuf *buf; /* output buffer, allocated */
423 char *put_length_here;
429 size_t extra_len = 0;
434 * It is not required that filenames be case insensitive but it is
435 * allowed and cannot hurt here.
437 if (filename == NULL)
438 filename = "(null)"; /* just in case */
439 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
440 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
441 req = GET_DEVICE_XML_FILE;
443 if (sm->wps->friendly_name)
444 extra_len += os_strlen(sm->wps->friendly_name);
445 if (sm->wps->manufacturer_url)
446 extra_len += os_strlen(sm->wps->manufacturer_url);
447 if (sm->wps->model_description)
448 extra_len += os_strlen(sm->wps->model_description);
449 if (sm->wps->model_url)
450 extra_len += os_strlen(sm->wps->model_url);
452 extra_len += os_strlen(sm->wps->upc);
453 } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
454 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
455 req = GET_SCPD_XML_FILE;
456 extra_len = os_strlen(wps_scpd_xml);
459 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
461 buf = wpabuf_alloc(200);
463 http_request_deinit(hreq);
467 "HTTP/1.1 404 Not Found\r\n"
468 "Connection: close\r\n");
472 /* terminating empty line */
473 wpabuf_put_str(buf, "\r\n");
478 buf = wpabuf_alloc(1000 + extra_len);
480 http_request_deinit(hreq);
485 "HTTP/1.1 200 OK\r\n"
486 "Content-Type: text/xml; charset=\"utf-8\"\r\n");
487 wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
488 wpabuf_put_str(buf, "Connection: close\r\n");
489 wpabuf_put_str(buf, "Content-Length: ");
491 * We will paste the length in later, leaving some extra whitespace.
492 * HTTP code is supposed to be tolerant of extra whitespace.
494 put_length_here = wpabuf_put(buf, 0);
495 wpabuf_put_str(buf, " \r\n");
499 /* terminating empty line */
500 wpabuf_put_str(buf, "\r\n");
502 body_start = wpabuf_put(buf, 0);
505 case GET_DEVICE_XML_FILE:
506 format_wps_device_xml(sm, buf);
508 case GET_SCPD_XML_FILE:
509 wpabuf_put_str(buf, wps_scpd_xml);
513 /* Now patch in the content length at the end */
514 body_length = (char *) wpabuf_put(buf, 0) - body_start;
515 os_snprintf(len_buf, 10, "%d", body_length);
516 os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
519 http_request_send_and_deinit(hreq, buf);
523 static enum http_reply_code
524 web_process_get_device_info(struct upnp_wps_device_sm *sm,
525 struct wpabuf **reply, const char **replyname)
527 static const char *name = "NewDeviceInfo";
529 wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
530 if (sm->ctx->rx_req_get_device_info == NULL)
531 return HTTP_INTERNAL_SERVER_ERROR;
532 *reply = sm->ctx->rx_req_get_device_info(sm->priv, &sm->peer);
533 if (*reply == NULL) {
534 wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
535 return HTTP_INTERNAL_SERVER_ERROR;
542 static enum http_reply_code
543 web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
544 struct wpabuf **reply, const char **replyname)
547 static const char *name = "NewOutMessage";
548 enum http_reply_code ret;
551 * PutMessage is used by external UPnP-based Registrar to perform WPS
552 * operation with the access point itself; as compared with
553 * PutWLANResponse which is for proxying.
555 wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
556 if (sm->ctx->rx_req_put_message == NULL)
557 return HTTP_INTERNAL_SERVER_ERROR;
558 msg = xml_get_base64_item(data, "NewInMessage", &ret);
561 *reply = sm->ctx->rx_req_put_message(sm->priv, &sm->peer, msg);
564 return HTTP_INTERNAL_SERVER_ERROR;
570 static enum http_reply_code
571 web_process_get_ap_settings(struct upnp_wps_device_sm *sm, char *data,
572 struct wpabuf **reply, const char **replyname)
575 static const char *name = "NewAPSettings";
576 enum http_reply_code ret;
578 wpa_printf(MSG_DEBUG, "WPS UPnP: GetAPSettings");
579 if (sm->ctx->rx_req_get_ap_settings == NULL)
580 return HTTP_INTERNAL_SERVER_ERROR;
581 msg = xml_get_base64_item(data, "NewMessage", &ret);
584 *reply = sm->ctx->rx_req_get_ap_settings(sm->priv, msg);
587 return HTTP_INTERNAL_SERVER_ERROR;
593 static enum http_reply_code
594 web_process_set_ap_settings(struct upnp_wps_device_sm *sm, char *data,
595 struct wpabuf **reply, const char **replyname)
598 enum http_reply_code ret;
600 wpa_printf(MSG_DEBUG, "WPS UPnP: SetAPSettings");
601 msg = xml_get_base64_item(data, "NewAPSettings", &ret);
604 if (!sm->ctx->rx_req_set_ap_settings ||
605 sm->ctx->rx_req_set_ap_settings(sm->priv, msg)) {
607 return HTTP_INTERNAL_SERVER_ERROR;
616 static enum http_reply_code
617 web_process_del_ap_settings(struct upnp_wps_device_sm *sm, char *data,
618 struct wpabuf **reply, const char **replyname)
621 enum http_reply_code ret;
623 wpa_printf(MSG_DEBUG, "WPS UPnP: DelAPSettings");
624 msg = xml_get_base64_item(data, "NewAPSettings", &ret);
627 if (!sm->ctx->rx_req_del_ap_settings ||
628 sm->ctx->rx_req_del_ap_settings(sm->priv, msg)) {
630 return HTTP_INTERNAL_SERVER_ERROR;
639 static enum http_reply_code
640 web_process_get_sta_settings(struct upnp_wps_device_sm *sm, char *data,
641 struct wpabuf **reply, const char **replyname)
644 static const char *name = "NewSTASettings";
645 enum http_reply_code ret;
647 wpa_printf(MSG_DEBUG, "WPS UPnP: GetSTASettings");
648 if (sm->ctx->rx_req_get_sta_settings == NULL)
649 return HTTP_INTERNAL_SERVER_ERROR;
650 msg = xml_get_base64_item(data, "NewMessage", &ret);
653 *reply = sm->ctx->rx_req_get_sta_settings(sm->priv, msg);
656 return HTTP_INTERNAL_SERVER_ERROR;
662 static enum http_reply_code
663 web_process_set_sta_settings(struct upnp_wps_device_sm *sm, char *data,
664 struct wpabuf **reply, const char **replyname)
667 enum http_reply_code ret;
669 wpa_printf(MSG_DEBUG, "WPS UPnP: SetSTASettings");
670 msg = xml_get_base64_item(data, "NewSTASettings", &ret);
673 if (!sm->ctx->rx_req_set_sta_settings ||
674 sm->ctx->rx_req_set_sta_settings(sm->priv, msg)) {
676 return HTTP_INTERNAL_SERVER_ERROR;
685 static enum http_reply_code
686 web_process_del_sta_settings(struct upnp_wps_device_sm *sm, char *data,
687 struct wpabuf **reply, const char **replyname)
690 enum http_reply_code ret;
692 wpa_printf(MSG_DEBUG, "WPS UPnP: DelSTASettings");
693 msg = xml_get_base64_item(data, "NewSTASettings", &ret);
696 if (!sm->ctx->rx_req_del_sta_settings ||
697 sm->ctx->rx_req_del_sta_settings(sm->priv, msg)) {
699 return HTTP_INTERNAL_SERVER_ERROR;
708 static enum http_reply_code
709 web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
710 struct wpabuf **reply, const char **replyname)
713 enum http_reply_code ret;
714 u8 macaddr[ETH_ALEN];
720 * External UPnP-based Registrar is passing us a message to be proxied
721 * over to a Wi-Fi -based client of ours.
724 wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
725 msg = xml_get_base64_item(data, "NewMessage", &ret);
728 val = xml_get_first_item(data, "NewWLANEventType");
731 return UPNP_ARG_VALUE_INVALID;
735 val = xml_get_first_item(data, "NewWLANEventMAC");
736 if (val == NULL || hwaddr_aton(val, macaddr)) {
739 return UPNP_ARG_VALUE_INVALID;
742 if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
743 struct wps_parse_attr attr;
744 if (wps_parse_msg(msg, &attr) < 0 ||
745 attr.msg_type == NULL)
748 type = *attr.msg_type;
749 wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
752 if (!sm->ctx->rx_req_put_wlan_response ||
753 sm->ctx->rx_req_put_wlan_response(sm->priv, ev_type, macaddr, msg,
755 wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
756 "rx_req_put_wlan_response");
758 return HTTP_INTERNAL_SERVER_ERROR;
767 static enum http_reply_code
768 web_process_set_selected_registrar(struct upnp_wps_device_sm *sm, char *data,
769 struct wpabuf **reply,
770 const char **replyname)
773 enum http_reply_code ret;
775 wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
776 msg = xml_get_base64_item(data, "NewMessage", &ret);
779 if (!sm->ctx->rx_req_set_selected_registrar ||
780 sm->ctx->rx_req_set_selected_registrar(sm->priv, msg)) {
782 return HTTP_INTERNAL_SERVER_ERROR;
791 static enum http_reply_code
792 web_process_reboot_ap(struct upnp_wps_device_sm *sm, char *data,
793 struct wpabuf **reply, const char **replyname)
796 enum http_reply_code ret;
798 wpa_printf(MSG_DEBUG, "WPS UPnP: RebootAP");
799 msg = xml_get_base64_item(data, "NewAPSettings", &ret);
802 if (!sm->ctx->rx_req_reboot_ap ||
803 sm->ctx->rx_req_reboot_ap(sm->priv, msg)) {
805 return HTTP_INTERNAL_SERVER_ERROR;
814 static enum http_reply_code
815 web_process_reset_ap(struct upnp_wps_device_sm *sm, char *data,
816 struct wpabuf **reply, const char **replyname)
819 enum http_reply_code ret;
821 wpa_printf(MSG_DEBUG, "WPS UPnP: ResetAP");
822 msg = xml_get_base64_item(data, "NewMessage", &ret);
825 if (!sm->ctx->rx_req_reset_ap ||
826 sm->ctx->rx_req_reset_ap(sm->priv, msg)) {
828 return HTTP_INTERNAL_SERVER_ERROR;
837 static enum http_reply_code
838 web_process_reboot_sta(struct upnp_wps_device_sm *sm, char *data,
839 struct wpabuf **reply, const char **replyname)
842 enum http_reply_code ret;
844 wpa_printf(MSG_DEBUG, "WPS UPnP: RebootSTA");
845 msg = xml_get_base64_item(data, "NewSTASettings", &ret);
848 if (!sm->ctx->rx_req_reboot_sta ||
849 sm->ctx->rx_req_reboot_sta(sm->priv, msg)) {
851 return HTTP_INTERNAL_SERVER_ERROR;
860 static enum http_reply_code
861 web_process_reset_sta(struct upnp_wps_device_sm *sm, char *data,
862 struct wpabuf **reply, const char **replyname)
865 enum http_reply_code ret;
867 wpa_printf(MSG_DEBUG, "WPS UPnP: ResetSTA");
868 msg = xml_get_base64_item(data, "NewMessage", &ret);
871 if (!sm->ctx->rx_req_reset_sta ||
872 sm->ctx->rx_req_reset_sta(sm->priv, msg)) {
874 return HTTP_INTERNAL_SERVER_ERROR;
883 static const char *soap_prefix =
884 "<?xml version=\"1.0\"?>\n"
885 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
886 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
888 static const char *soap_postfix =
889 "</s:Body>\n</s:Envelope>\n";
891 static const char *soap_error_prefix =
893 "<faultcode>s:Client</faultcode>\n"
894 "<faultstring>UPnPError</faultstring>\n"
896 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
897 static const char *soap_error_postfix =
898 "<errorDescription>Error</errorDescription>\n"
903 static void web_connection_send_reply(struct http_request *req,
904 enum http_reply_code ret,
905 const char *action, int action_len,
906 const struct wpabuf *reply,
907 const char *replyname)
911 char *put_length_here = NULL;
912 char *body_start = NULL;
916 replydata = (char *) base64_encode(wpabuf_head(reply),
917 wpabuf_len(reply), &len);
921 /* Parameters of the response:
922 * action(action_len) -- action we are responding to
923 * replyname -- a name we need for the reply
924 * replydata -- NULL or null-terminated string
926 buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
927 (action_len > 0 ? action_len * 2 : 0));
929 wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
932 http_request_deinit(req);
937 * Assuming we will be successful, put in the output header first.
938 * Note: we do not keep connections alive (and httpread does
939 * not support it)... therefore we must have Connection: close.
941 if (ret == HTTP_OK) {
943 "HTTP/1.1 200 OK\r\n"
944 "Content-Type: text/xml; "
945 "charset=\"utf-8\"\r\n");
947 wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
949 wpabuf_put_str(buf, http_connection_close);
951 wpabuf_put_str(buf, "Content-Length: ");
953 * We will paste the length in later, leaving some extra whitespace.
954 * HTTP code is supposed to be tolerant of extra whitespace.
956 put_length_here = wpabuf_put(buf, 0);
957 wpabuf_put_str(buf, " \r\n");
961 /* terminating empty line */
962 wpabuf_put_str(buf, "\r\n");
964 body_start = wpabuf_put(buf, 0);
966 if (ret == HTTP_OK) {
967 wpabuf_put_str(buf, soap_prefix);
968 wpabuf_put_str(buf, "<u:");
969 wpabuf_put_data(buf, action, action_len);
970 wpabuf_put_str(buf, "Response xmlns:u=\"");
971 wpabuf_put_str(buf, urn_wfawlanconfig);
972 wpabuf_put_str(buf, "\">\n");
973 if (replydata && replyname) {
974 /* TODO: might possibly need to escape part of reply
976 * probably not, unlikely to have ampersand(&) or left
977 * angle bracket (<) in it...
979 wpabuf_printf(buf, "<%s>", replyname);
980 wpabuf_put_str(buf, replydata);
981 wpabuf_printf(buf, "</%s>\n", replyname);
983 wpabuf_put_str(buf, "</u:");
984 wpabuf_put_data(buf, action, action_len);
985 wpabuf_put_str(buf, "Response>\n");
986 wpabuf_put_str(buf, soap_postfix);
989 wpabuf_put_str(buf, soap_prefix);
990 wpabuf_put_str(buf, soap_error_prefix);
991 wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
992 wpabuf_put_str(buf, soap_error_postfix);
993 wpabuf_put_str(buf, soap_postfix);
997 /* Now patch in the content length at the end */
998 if (body_start && put_length_here) {
999 int body_length = (char *) wpabuf_put(buf, 0) - body_start;
1001 os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
1002 os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
1005 http_request_send_and_deinit(req, buf);
1009 static const char * web_get_action(struct http_request *req,
1010 const char *filename, size_t *action_len)
1018 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
1019 wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
1023 /* The SOAPAction line of the header tells us what we want to do */
1024 b = http_request_get_hdr_line(req, "SOAPAction:");
1031 match = urn_wfawlanconfig;
1032 match_len = os_strlen(urn_wfawlanconfig) - 1;
1033 if (os_strncasecmp(b, match, match_len))
1036 /* skip over version */
1037 while (isgraph(*b) && *b != '#')
1042 /* Following the sharp(#) should be the action and a double quote */
1044 while (isgraph(*b) && *b != '"')
1048 *action_len = b - action;
1053 /* Given that we have received a header w/ POST, act upon it
1055 * Format of POST (case-insensitive):
1057 * First line must be:
1058 * POST /<file> HTTP/1.1
1059 * Since we don't do anything fancy we just ignore other lines.
1061 * Our response (if no error) which includes only required lines is:
1064 * Content-Type: text/xml
1065 * Date: <rfc1123-date>
1067 * Header lines must end with \r\n
1068 * Per RFC 2616, content-length: is not required but connection:close
1069 * would appear to be required (given that we will be closing it!).
1071 static void web_connection_parse_post(struct upnp_wps_device_sm *sm,
1072 struct http_request *req,
1073 const char *filename)
1075 enum http_reply_code ret;
1076 char *data = http_request_get_data(req); /* body of http msg */
1079 const char *replyname = NULL; /* argument name for the reply */
1080 struct wpabuf *reply = NULL; /* data for the reply */
1082 ret = UPNP_INVALID_ACTION;
1083 action = web_get_action(req, filename, &action_len);
1088 * There are quite a few possible actions. Although we appear to
1089 * support them all here, not all of them are necessarily supported by
1090 * callbacks at higher levels.
1092 if (!os_strncasecmp("GetDeviceInfo", action, action_len))
1093 ret = web_process_get_device_info(sm, &reply, &replyname);
1094 else if (!os_strncasecmp("PutMessage", action, action_len))
1095 ret = web_process_put_message(sm, data, &reply, &replyname);
1096 else if (!os_strncasecmp("GetAPSettings", action, action_len))
1097 ret = web_process_get_ap_settings(sm, data, &reply,
1099 else if (!os_strncasecmp("SetAPSettings", action, action_len))
1100 ret = web_process_set_ap_settings(sm, data, &reply,
1102 else if (!os_strncasecmp("DelAPSettings", action, action_len))
1103 ret = web_process_del_ap_settings(sm, data, &reply,
1105 else if (!os_strncasecmp("GetSTASettings", action, action_len))
1106 ret = web_process_get_sta_settings(sm, data, &reply,
1108 else if (!os_strncasecmp("SetSTASettings", action, action_len))
1109 ret = web_process_set_sta_settings(sm, data, &reply,
1111 else if (!os_strncasecmp("DelSTASettings", action, action_len))
1112 ret = web_process_del_sta_settings(sm, data, &reply,
1114 else if (!os_strncasecmp("PutWLANResponse", action, action_len))
1115 ret = web_process_put_wlan_response(sm, data, &reply,
1117 else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
1118 ret = web_process_set_selected_registrar(sm, data, &reply,
1120 else if (!os_strncasecmp("RebootAP", action, action_len))
1121 ret = web_process_reboot_ap(sm, data, &reply, &replyname);
1122 else if (!os_strncasecmp("ResetAP", action, action_len))
1123 ret = web_process_reset_ap(sm, data, &reply, &replyname);
1124 else if (!os_strncasecmp("RebootSTA", action, action_len))
1125 ret = web_process_reboot_sta(sm, data, &reply, &replyname);
1126 else if (!os_strncasecmp("ResetSTA", action, action_len))
1127 ret = web_process_reset_sta(sm, data, &reply, &replyname);
1129 wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
1133 wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
1134 web_connection_send_reply(req, ret, action, action_len, reply,
1140 /* Given that we have received a header w/ SUBSCRIBE, act upon it
1142 * Format of SUBSCRIBE (case-insensitive):
1144 * First line must be:
1145 * SUBSCRIBE /wps_event HTTP/1.1
1147 * Our response (if no error) which includes only required lines is:
1149 * Server: xx, UPnP/1.0, xx
1150 * SID: uuid:xxxxxxxxx
1151 * Timeout: Second-<n>
1155 * Header lines must end with \r\n
1156 * Per RFC 2616, content-length: is not required but connection:close
1157 * would appear to be required (given that we will be closing it!).
1159 static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
1160 struct http_request *req,
1161 const char *filename)
1165 char *hdr = http_request_get_hdr(req);
1174 char *callback_urls = NULL;
1175 struct subscription *s = NULL;
1176 enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1178 buf = wpabuf_alloc(1000);
1180 http_request_deinit(req);
1184 /* Parse/validate headers */
1186 /* First line: SUBSCRIBE /wps_event HTTP/1.1
1187 * has already been parsed.
1189 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1190 ret = HTTP_PRECONDITION_FAILED;
1193 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
1194 end = os_strchr(h, '\n');
1196 for (; end != NULL; h = end + 1) {
1197 /* Option line by option line */
1199 end = os_strchr(h, '\n');
1201 break; /* no unterminated lines allowed */
1203 /* NT assures that it is our type of subscription;
1204 * not used for a renewl.
1207 match_len = os_strlen(match);
1208 if (os_strncasecmp(h, match, match_len) == 0) {
1210 while (*h == ' ' || *h == '\t')
1212 match = "upnp:event";
1213 match_len = os_strlen(match);
1214 if (os_strncasecmp(h, match, match_len) != 0) {
1215 ret = HTTP_BAD_REQUEST;
1221 /* HOST should refer to us */
1224 match_len = os_strlen(match);
1225 if (os_strncasecmp(h, match, match_len) == 0) {
1227 while (*h == ' ' || *h == '\t')
1232 /* CALLBACK gives one or more URLs for NOTIFYs
1233 * to be sent as a result of the subscription.
1234 * Each URL is enclosed in angle brackets.
1236 match = "CALLBACK:";
1237 match_len = os_strlen(match);
1238 if (os_strncasecmp(h, match, match_len) == 0) {
1240 while (*h == ' ' || *h == '\t')
1243 os_free(callback_urls);
1244 callback_urls = os_malloc(len + 1);
1245 if (callback_urls == NULL) {
1246 ret = HTTP_INTERNAL_SERVER_ERROR;
1249 os_memcpy(callback_urls, h, len);
1250 callback_urls[len] = 0;
1253 /* SID is only for renewal */
1255 match_len = os_strlen(match);
1256 if (os_strncasecmp(h, match, match_len) == 0) {
1258 while (*h == ' ' || *h == '\t')
1261 match_len = os_strlen(match);
1262 if (os_strncasecmp(h, match, match_len) != 0) {
1263 ret = HTTP_BAD_REQUEST;
1267 while (*h == ' ' || *h == '\t')
1269 if (uuid_str2bin(h, uuid)) {
1270 ret = HTTP_BAD_REQUEST;
1276 /* TIMEOUT is requested timeout, but apparently we can
1283 if (callback_urls) {
1284 ret = HTTP_BAD_REQUEST;
1287 s = subscription_renew(sm, uuid);
1289 ret = HTTP_PRECONDITION_FAILED;
1292 } else if (callback_urls) {
1294 ret = HTTP_PRECONDITION_FAILED;
1297 s = subscription_start(sm, callback_urls);
1299 ret = HTTP_INTERNAL_SERVER_ERROR;
1302 callback_urls = NULL; /* is now owned by subscription */
1304 ret = HTTP_PRECONDITION_FAILED;
1309 http_put_reply_code(buf, HTTP_OK);
1310 wpabuf_put_str(buf, http_server_hdr);
1311 wpabuf_put_str(buf, http_connection_close);
1312 wpabuf_put_str(buf, "Content-Length: 0\r\n");
1313 wpabuf_put_str(buf, "SID: uuid:");
1314 /* subscription id */
1315 b = wpabuf_put(buf, 0);
1316 uuid_bin2str(s->uuid, b, 80);
1317 wpabuf_put(buf, os_strlen(b));
1318 wpabuf_put_str(buf, "\r\n");
1319 wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
1321 /* And empty line to terminate header: */
1322 wpabuf_put_str(buf, "\r\n");
1324 os_free(callback_urls);
1325 http_request_send_and_deinit(req, buf);
1331 * Incompatible headers
1332 * 400 Bad Request. If SID header and one of NT or CALLBACK headers
1333 * are present, the publisher must respond with HTTP error
1335 * Missing or invalid CALLBACK
1336 * 412 Precondition Failed. If CALLBACK header is missing or does not
1337 * contain a valid HTTP URL, the publisher must respond with HTTP
1338 * error 412 Precondition Failed.
1340 * 412 Precondition Failed. If NT header does not equal upnp:event,
1341 * the publisher must respond with HTTP error 412 Precondition
1343 * [For resubscription, use 412 if unknown uuid].
1344 * Unable to accept subscription
1345 * 5xx. If a publisher is not able to accept a subscription (such as
1346 * due to insufficient resources), it must respond with a
1347 * HTTP 500-series error code.
1348 * 599 Too many subscriptions (not a standard HTTP error)
1350 http_put_empty(buf, ret);
1351 http_request_send_and_deinit(req, buf);
1355 /* Given that we have received a header w/ UNSUBSCRIBE, act upon it
1357 * Format of UNSUBSCRIBE (case-insensitive):
1359 * First line must be:
1360 * UNSUBSCRIBE /wps_event HTTP/1.1
1362 * Our response (if no error) which includes only required lines is:
1366 * Header lines must end with \r\n
1367 * Per RFC 2616, content-length: is not required but connection:close
1368 * would appear to be required (given that we will be closing it!).
1370 static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
1371 struct http_request *req,
1372 const char *filename)
1375 char *hdr = http_request_get_hdr(req);
1382 struct subscription *s = NULL;
1383 enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
1385 /* Parse/validate headers */
1387 /* First line: UNSUBSCRIBE /wps_event HTTP/1.1
1388 * has already been parsed.
1390 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
1391 ret = HTTP_PRECONDITION_FAILED;
1394 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
1395 end = os_strchr(h, '\n');
1397 for (; end != NULL; h = end + 1) {
1398 /* Option line by option line */
1400 end = os_strchr(h, '\n');
1402 break; /* no unterminated lines allowed */
1404 /* HOST should refer to us */
1407 match_len = os_strlen(match);
1408 if (os_strncasecmp(h, match, match_len) == 0) {
1410 while (*h == ' ' || *h == '\t')
1415 /* SID is only for renewal */
1417 match_len = os_strlen(match);
1418 if (os_strncasecmp(h, match, match_len) == 0) {
1420 while (*h == ' ' || *h == '\t')
1423 match_len = os_strlen(match);
1424 if (os_strncasecmp(h, match, match_len) != 0) {
1425 ret = HTTP_BAD_REQUEST;
1429 while (*h == ' ' || *h == '\t')
1431 if (uuid_str2bin(h, uuid)) {
1432 ret = HTTP_BAD_REQUEST;
1441 s = subscription_find(sm, uuid);
1443 wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
1445 (s && s->addr_list &&
1446 s->addr_list->domain_and_port) ?
1447 s->addr_list->domain_and_port : "-null-");
1448 subscription_unlink(s);
1449 subscription_destroy(s);
1452 wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
1454 ret = HTTP_PRECONDITION_FAILED;
1461 buf = wpabuf_alloc(200);
1463 http_request_deinit(req);
1466 http_put_empty(buf, ret);
1467 http_request_send_and_deinit(req, buf);
1471 /* Send error in response to unknown requests */
1472 static void web_connection_unimplemented(struct http_request *req)
1475 buf = wpabuf_alloc(200);
1477 http_request_deinit(req);
1480 http_put_empty(buf, HTTP_UNIMPLEMENTED);
1481 http_request_send_and_deinit(req, buf);
1486 /* Called when we have gotten an apparently valid http request.
1488 static void web_connection_check_data(void *ctx, struct http_request *req)
1490 struct upnp_wps_device_sm *sm = ctx;
1491 enum httpread_hdr_type htype = http_request_get_type(req);
1492 char *filename = http_request_get_uri(req);
1493 struct sockaddr_in *cli = http_request_get_cli_addr(req);
1496 wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
1497 http_request_deinit(req);
1500 /* Trim leading slashes from filename */
1501 while (*filename == '/')
1504 wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
1505 htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port));
1508 case HTTPREAD_HDR_TYPE_GET:
1509 web_connection_parse_get(sm, req, filename);
1511 case HTTPREAD_HDR_TYPE_POST:
1512 web_connection_parse_post(sm, req, filename);
1514 case HTTPREAD_HDR_TYPE_SUBSCRIBE:
1515 web_connection_parse_subscribe(sm, req, filename);
1517 case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
1518 web_connection_parse_unsubscribe(sm, req, filename);
1521 /* We are not required to support M-POST; just plain
1522 * POST is supposed to work, so we only support that.
1523 * If for some reason we need to support M-POST, it is
1524 * mostly the same as POST, with small differences.
1527 /* Send 501 for anything else */
1528 web_connection_unimplemented(req);
1535 * Listening for web connections
1536 * We have a single TCP listening port, and hand off connections as we get
1540 void web_listener_stop(struct upnp_wps_device_sm *sm)
1542 http_server_deinit(sm->web_srv);
1547 int web_listener_start(struct upnp_wps_device_sm *sm)
1549 struct in_addr addr;
1550 addr.s_addr = sm->ip_addr;
1551 sm->web_srv = http_server_init(&addr, -1, web_connection_check_data,
1553 if (sm->web_srv == NULL) {
1554 web_listener_stop(sm);
1557 sm->web_port = http_server_get_port(sm->web_srv);