diff options
Diffstat (limited to 'freebsd/contrib/wpa/src/wps/wps_upnp_web.c')
-rw-r--r-- | freebsd/contrib/wpa/src/wps/wps_upnp_web.c | 1352 |
1 files changed, 1352 insertions, 0 deletions
diff --git a/freebsd/contrib/wpa/src/wps/wps_upnp_web.c b/freebsd/contrib/wpa/src/wps/wps_upnp_web.c new file mode 100644 index 00000000..8c3bbd1b --- /dev/null +++ b/freebsd/contrib/wpa/src/wps/wps_upnp_web.c @@ -0,0 +1,1352 @@ +#include <machine/rtems-bsd-user-space.h> + +/* + * UPnP WPS Device - Web connections + * Copyright (c) 2000-2003 Intel Corporation + * Copyright (c) 2006-2007 Sony Corporation + * Copyright (c) 2008-2009 Atheros Communications + * Copyright (c) 2009, Jouni Malinen <j@w1.fi> + * + * See wps_upnp.c for more details on licensing and code history. + */ + +#include "includes.h" + +#include "common.h" +#include "base64.h" +#include "uuid.h" +#include "httpread.h" +#include "http_server.h" +#include "wps_i.h" +#include "wps_upnp.h" +#include "wps_upnp_i.h" +#include "upnp_xml.h" + +/*************************************************************************** + * Web connections (we serve pages of info about ourselves, handle + * requests, etc. etc.). + **************************************************************************/ + +#define WEB_CONNECTION_TIMEOUT_SEC 30 /* Drop web connection after t.o. */ +#define WEB_CONNECTION_MAX_READ 8000 /* Max we'll read for TCP request */ +#define MAX_WEB_CONNECTIONS 10 /* max simultaneous web connects */ + + +static const char *urn_wfawlanconfig = + "urn:schemas-wifialliance-org:service:WFAWLANConfig:1"; +static const char *http_server_hdr = + "Server: unspecified, UPnP/1.0, unspecified\r\n"; +static const char *http_connection_close = + "Connection: close\r\n"; + +/* + * "Files" that we serve via HTTP. The format of these files is given by + * WFA WPS specifications. Extra white space has been removed to save space. + */ + +static const char wps_scpd_xml[] = +"<?xml version=\"1.0\"?>\n" +"<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\n" +"<specVersion><major>1</major><minor>0</minor></specVersion>\n" +"<actionList>\n" +"<action>\n" +"<name>GetDeviceInfo</name>\n" +"<argumentList>\n" +"<argument>\n" +"<name>NewDeviceInfo</name>\n" +"<direction>out</direction>\n" +"<relatedStateVariable>DeviceInfo</relatedStateVariable>\n" +"</argument>\n" +"</argumentList>\n" +"</action>\n" +"<action>\n" +"<name>PutMessage</name>\n" +"<argumentList>\n" +"<argument>\n" +"<name>NewInMessage</name>\n" +"<direction>in</direction>\n" +"<relatedStateVariable>InMessage</relatedStateVariable>\n" +"</argument>\n" +"<argument>\n" +"<name>NewOutMessage</name>\n" +"<direction>out</direction>\n" +"<relatedStateVariable>OutMessage</relatedStateVariable>\n" +"</argument>\n" +"</argumentList>\n" +"</action>\n" +"<action>\n" +"<name>PutWLANResponse</name>\n" +"<argumentList>\n" +"<argument>\n" +"<name>NewMessage</name>\n" +"<direction>in</direction>\n" +"<relatedStateVariable>Message</relatedStateVariable>\n" +"</argument>\n" +"<argument>\n" +"<name>NewWLANEventType</name>\n" +"<direction>in</direction>\n" +"<relatedStateVariable>WLANEventType</relatedStateVariable>\n" +"</argument>\n" +"<argument>\n" +"<name>NewWLANEventMAC</name>\n" +"<direction>in</direction>\n" +"<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n" +"</argument>\n" +"</argumentList>\n" +"</action>\n" +"<action>\n" +"<name>SetSelectedRegistrar</name>\n" +"<argumentList>\n" +"<argument>\n" +"<name>NewMessage</name>\n" +"<direction>in</direction>\n" +"<relatedStateVariable>Message</relatedStateVariable>\n" +"</argument>\n" +"</argumentList>\n" +"</action>\n" +"</actionList>\n" +"<serviceStateTable>\n" +"<stateVariable sendEvents=\"no\">\n" +"<name>Message</name>\n" +"<dataType>bin.base64</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"no\">\n" +"<name>InMessage</name>\n" +"<dataType>bin.base64</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"no\">\n" +"<name>OutMessage</name>\n" +"<dataType>bin.base64</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"no\">\n" +"<name>DeviceInfo</name>\n" +"<dataType>bin.base64</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"yes\">\n" +"<name>APStatus</name>\n" +"<dataType>ui1</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"yes\">\n" +"<name>STAStatus</name>\n" +"<dataType>ui1</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"yes\">\n" +"<name>WLANEvent</name>\n" +"<dataType>bin.base64</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"no\">\n" +"<name>WLANEventType</name>\n" +"<dataType>ui1</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"no\">\n" +"<name>WLANEventMAC</name>\n" +"<dataType>string</dataType>\n" +"</stateVariable>\n" +"<stateVariable sendEvents=\"no\">\n" +"<name>WLANResponse</name>\n" +"<dataType>bin.base64</dataType>\n" +"</stateVariable>\n" +"</serviceStateTable>\n" +"</scpd>\n" +; + + +static const char *wps_device_xml_prefix = + "<?xml version=\"1.0\"?>\n" + "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n" + "<specVersion>\n" + "<major>1</major>\n" + "<minor>0</minor>\n" + "</specVersion>\n" + "<device>\n" + "<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1" + "</deviceType>\n"; + +static const char *wps_device_xml_postfix = + "<serviceList>\n" + "<service>\n" + "<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1" + "</serviceType>\n" + "<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>" + "\n" + "<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n" + "<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n" + "<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n" + "</service>\n" + "</serviceList>\n" + "</device>\n" + "</root>\n"; + + +/* format_wps_device_xml -- produce content of "file" wps_device.xml + * (UPNP_WPS_DEVICE_XML_FILE) + */ +static void format_wps_device_xml(struct upnp_wps_device_interface *iface, + struct upnp_wps_device_sm *sm, + struct wpabuf *buf) +{ + const char *s; + char uuid_string[80]; + + wpabuf_put_str(buf, wps_device_xml_prefix); + + /* + * Add required fields with default values if not configured. Add + * optional and recommended fields only if configured. + */ + s = iface->wps->friendly_name; + s = ((s && *s) ? s : "WPS Access Point"); + xml_add_tagged_data(buf, "friendlyName", s); + + s = iface->wps->dev.manufacturer; + s = ((s && *s) ? s : ""); + xml_add_tagged_data(buf, "manufacturer", s); + + if (iface->wps->manufacturer_url) + xml_add_tagged_data(buf, "manufacturerURL", + iface->wps->manufacturer_url); + + if (iface->wps->model_description) + xml_add_tagged_data(buf, "modelDescription", + iface->wps->model_description); + + s = iface->wps->dev.model_name; + s = ((s && *s) ? s : ""); + xml_add_tagged_data(buf, "modelName", s); + + if (iface->wps->dev.model_number) + xml_add_tagged_data(buf, "modelNumber", + iface->wps->dev.model_number); + + if (iface->wps->model_url) + xml_add_tagged_data(buf, "modelURL", iface->wps->model_url); + + if (iface->wps->dev.serial_number) + xml_add_tagged_data(buf, "serialNumber", + iface->wps->dev.serial_number); + + uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string)); + s = uuid_string; + /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data() + * easily... + */ + wpabuf_put_str(buf, "<UDN>uuid:"); + xml_data_encode(buf, s, os_strlen(s)); + wpabuf_put_str(buf, "</UDN>\n"); + + if (iface->wps->upc) + xml_add_tagged_data(buf, "UPC", iface->wps->upc); + + wpabuf_put_str(buf, wps_device_xml_postfix); +} + + +static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code) +{ + wpabuf_put_str(buf, "HTTP/1.1 "); + switch (code) { + case HTTP_OK: + wpabuf_put_str(buf, "200 OK\r\n"); + break; + case HTTP_BAD_REQUEST: + wpabuf_put_str(buf, "400 Bad request\r\n"); + break; + case HTTP_PRECONDITION_FAILED: + wpabuf_put_str(buf, "412 Precondition failed\r\n"); + break; + case HTTP_UNIMPLEMENTED: + wpabuf_put_str(buf, "501 Unimplemented\r\n"); + break; + case HTTP_INTERNAL_SERVER_ERROR: + default: + wpabuf_put_str(buf, "500 Internal server error\r\n"); + break; + } +} + + +static void http_put_date(struct wpabuf *buf) +{ + wpabuf_put_str(buf, "Date: "); + format_date(buf); + wpabuf_put_str(buf, "\r\n"); +} + + +static void http_put_empty(struct wpabuf *buf, enum http_reply_code code) +{ + http_put_reply_code(buf, code); + wpabuf_put_str(buf, http_server_hdr); + wpabuf_put_str(buf, http_connection_close); + wpabuf_put_str(buf, "Content-Length: 0\r\n" + "\r\n"); +} + + +/* Given that we have received a header w/ GET, act upon it + * + * Format of GET (case-insensitive): + * + * First line must be: + * GET /<file> HTTP/1.1 + * Since we don't do anything fancy we just ignore other lines. + * + * Our response (if no error) which includes only required lines is: + * HTTP/1.1 200 OK + * Connection: close + * Content-Type: text/xml + * Date: <rfc1123-date> + * + * Header lines must end with \r\n + * Per RFC 2616, content-length: is not required but connection:close + * would appear to be required (given that we will be closing it!). + */ +static void web_connection_parse_get(struct upnp_wps_device_sm *sm, + struct http_request *hreq, char *filename) +{ + struct wpabuf *buf; /* output buffer, allocated */ + char *put_length_here; + char *body_start; + enum { + GET_DEVICE_XML_FILE, + GET_SCPD_XML_FILE + } req; + size_t extra_len = 0; + int body_length; + char len_buf[10]; + struct upnp_wps_device_interface *iface; + + iface = dl_list_first(&sm->interfaces, + struct upnp_wps_device_interface, list); + if (iface == NULL) { + http_request_deinit(hreq); + return; + } + + /* + * It is not required that filenames be case insensitive but it is + * allowed and cannot hurt here. + */ + if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) { + wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML"); + req = GET_DEVICE_XML_FILE; + extra_len = 3000; + if (iface->wps->friendly_name) + extra_len += os_strlen(iface->wps->friendly_name); + if (iface->wps->manufacturer_url) + extra_len += os_strlen(iface->wps->manufacturer_url); + if (iface->wps->model_description) + extra_len += os_strlen(iface->wps->model_description); + if (iface->wps->model_url) + extra_len += os_strlen(iface->wps->model_url); + if (iface->wps->upc) + extra_len += os_strlen(iface->wps->upc); + } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) { + wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML"); + req = GET_SCPD_XML_FILE; + extra_len = os_strlen(wps_scpd_xml); + } else { + /* File not found */ + wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s", + filename); + buf = wpabuf_alloc(200); + if (buf == NULL) { + http_request_deinit(hreq); + return; + } + wpabuf_put_str(buf, + "HTTP/1.1 404 Not Found\r\n" + "Connection: close\r\n"); + + http_put_date(buf); + + /* terminating empty line */ + wpabuf_put_str(buf, "\r\n"); + + goto send_buf; + } + + buf = wpabuf_alloc(1000 + extra_len); + if (buf == NULL) { + http_request_deinit(hreq); + return; + } + + wpabuf_put_str(buf, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/xml; charset=\"utf-8\"\r\n"); + wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n"); + wpabuf_put_str(buf, "Connection: close\r\n"); + wpabuf_put_str(buf, "Content-Length: "); + /* + * We will paste the length in later, leaving some extra whitespace. + * HTTP code is supposed to be tolerant of extra whitespace. + */ + put_length_here = wpabuf_put(buf, 0); + wpabuf_put_str(buf, " \r\n"); + + http_put_date(buf); + + /* terminating empty line */ + wpabuf_put_str(buf, "\r\n"); + + body_start = wpabuf_put(buf, 0); + + switch (req) { + case GET_DEVICE_XML_FILE: + format_wps_device_xml(iface, sm, buf); + break; + case GET_SCPD_XML_FILE: + wpabuf_put_str(buf, wps_scpd_xml); + break; + } + + /* Now patch in the content length at the end */ + body_length = (char *) wpabuf_put(buf, 0) - body_start; + os_snprintf(len_buf, 10, "%d", body_length); + os_memcpy(put_length_here, len_buf, os_strlen(len_buf)); + +send_buf: + http_request_send_and_deinit(hreq, buf); +} + + +static enum http_reply_code +web_process_get_device_info(struct upnp_wps_device_sm *sm, + struct wpabuf **reply, const char **replyname) +{ + static const char *name = "NewDeviceInfo"; + struct wps_config cfg; + struct upnp_wps_device_interface *iface; + struct upnp_wps_peer *peer; + + iface = dl_list_first(&sm->interfaces, + struct upnp_wps_device_interface, list); + + wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo"); + + if (!iface || iface->ctx->ap_pin == NULL) + return HTTP_INTERNAL_SERVER_ERROR; + + peer = &iface->peer; + + /* + * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS + * registration over UPnP with the AP acting as an Enrollee. It should + * be noted that this is frequently used just to get the device data, + * i.e., there may not be any intent to actually complete the + * registration. + */ + + if (peer->wps) + wps_deinit(peer->wps); + + os_memset(&cfg, 0, sizeof(cfg)); + cfg.wps = iface->wps; + cfg.pin = (u8 *) iface->ctx->ap_pin; + cfg.pin_len = os_strlen(iface->ctx->ap_pin); + peer->wps = wps_init(&cfg); + if (peer->wps) { + enum wsc_op_code op_code; + *reply = wps_get_msg(peer->wps, &op_code); + if (*reply == NULL) { + wps_deinit(peer->wps); + peer->wps = NULL; + } + } else + *reply = NULL; + if (*reply == NULL) { + wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo"); + return HTTP_INTERNAL_SERVER_ERROR; + } + *replyname = name; + return HTTP_OK; +} + + +static enum http_reply_code +web_process_put_message(struct upnp_wps_device_sm *sm, char *data, + struct wpabuf **reply, const char **replyname) +{ + struct wpabuf *msg; + static const char *name = "NewOutMessage"; + enum http_reply_code ret; + enum wps_process_res res; + enum wsc_op_code op_code; + struct upnp_wps_device_interface *iface; + + iface = dl_list_first(&sm->interfaces, + struct upnp_wps_device_interface, list); + if (!iface) + return HTTP_INTERNAL_SERVER_ERROR; + + /* + * PutMessage is used by external UPnP-based Registrar to perform WPS + * operation with the access point itself; as compared with + * PutWLANResponse which is for proxying. + */ + wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage"); + msg = xml_get_base64_item(data, "NewInMessage", &ret); + if (msg == NULL) + return ret; + res = wps_process_msg(iface->peer.wps, WSC_UPnP, msg); + if (res == WPS_FAILURE) + *reply = NULL; + else + *reply = wps_get_msg(iface->peer.wps, &op_code); + wpabuf_free(msg); + if (*reply == NULL) + return HTTP_INTERNAL_SERVER_ERROR; + *replyname = name; + return HTTP_OK; +} + + +static enum http_reply_code +web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data, + struct wpabuf **reply, const char **replyname) +{ + struct wpabuf *msg; + enum http_reply_code ret; + u8 macaddr[ETH_ALEN]; + int ev_type; + int type; + char *val; + struct upnp_wps_device_interface *iface; + int ok = 0; + + /* + * External UPnP-based Registrar is passing us a message to be proxied + * over to a Wi-Fi -based client of ours. + */ + + wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse"); + msg = xml_get_base64_item(data, "NewMessage", &ret); + if (msg == NULL) { + wpa_printf(MSG_DEBUG, "WPS UPnP: Could not extract NewMessage " + "from PutWLANResponse"); + return ret; + } + val = xml_get_first_item(data, "NewWLANEventType"); + if (val == NULL) { + wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventType in " + "PutWLANResponse"); + wpabuf_free(msg); + return UPNP_ARG_VALUE_INVALID; + } + ev_type = atol(val); + os_free(val); + val = xml_get_first_item(data, "NewWLANEventMAC"); + if (val == NULL) { + wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventMAC in " + "PutWLANResponse"); + wpabuf_free(msg); + return UPNP_ARG_VALUE_INVALID; + } + if (hwaddr_aton(val, macaddr)) { + wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid NewWLANEventMAC in " + "PutWLANResponse: '%s'", val); +#ifdef CONFIG_WPS_STRICT + { + struct wps_parse_attr attr; + if (wps_parse_msg(msg, &attr) < 0 || attr.version2) { + wpabuf_free(msg); + os_free(val); + return UPNP_ARG_VALUE_INVALID; + } + } +#endif /* CONFIG_WPS_STRICT */ + if (hwaddr_aton2(val, macaddr) > 0) { + /* + * At least some versions of Intel PROset seem to be + * using dot-deliminated MAC address format here. + */ + wpa_printf(MSG_DEBUG, "WPS UPnP: Workaround - allow " + "incorrect MAC address format in " + "NewWLANEventMAC: %s -> " MACSTR, + val, MAC2STR(macaddr)); + } else { + wpabuf_free(msg); + os_free(val); + return UPNP_ARG_VALUE_INVALID; + } + } + os_free(val); + if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) { + struct wps_parse_attr attr; + if (wps_parse_msg(msg, &attr) < 0 || + attr.msg_type == NULL) + type = -1; + else + type = *attr.msg_type; + wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type); + } else + type = -1; + dl_list_for_each(iface, &sm->interfaces, + struct upnp_wps_device_interface, list) { + if (iface->ctx->rx_req_put_wlan_response && + iface->ctx->rx_req_put_wlan_response(iface->priv, ev_type, + macaddr, msg, type) + == 0) + ok = 1; + } + + if (!ok) { + wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->" + "rx_req_put_wlan_response"); + wpabuf_free(msg); + return HTTP_INTERNAL_SERVER_ERROR; + } + wpabuf_free(msg); + *replyname = NULL; + *reply = NULL; + return HTTP_OK; +} + + +static int find_er_addr(struct subscription *s, struct sockaddr_in *cli) +{ + struct subscr_addr *a; + + dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) { + if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr) + return 1; + } + return 0; +} + + +static struct subscription * find_er(struct upnp_wps_device_sm *sm, + struct sockaddr_in *cli) +{ + struct subscription *s; + dl_list_for_each(s, &sm->subscriptions, struct subscription, list) + if (find_er_addr(s, cli)) + return s; + return NULL; +} + + +static enum http_reply_code +web_process_set_selected_registrar(struct upnp_wps_device_sm *sm, + struct sockaddr_in *cli, char *data, + struct wpabuf **reply, + const char **replyname) +{ + struct wpabuf *msg; + enum http_reply_code ret; + struct subscription *s; + struct upnp_wps_device_interface *iface; + int err = 0; + + wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar"); + s = find_er(sm, cli); + if (s == NULL) { + wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar " + "from unknown ER"); + return UPNP_ACTION_FAILED; + } + msg = xml_get_base64_item(data, "NewMessage", &ret); + if (msg == NULL) + return ret; + dl_list_for_each(iface, &sm->interfaces, + struct upnp_wps_device_interface, list) { + if (upnp_er_set_selected_registrar(iface->wps->registrar, s, + msg)) + err = 1; + } + wpabuf_free(msg); + if (err) + return HTTP_INTERNAL_SERVER_ERROR; + *replyname = NULL; + *reply = NULL; + return HTTP_OK; +} + + +static const char *soap_prefix = + "<?xml version=\"1.0\"?>\n" + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" + "<s:Body>\n"; +static const char *soap_postfix = + "</s:Body>\n</s:Envelope>\n"; + +static const char *soap_error_prefix = + "<s:Fault>\n" + "<faultcode>s:Client</faultcode>\n" + "<faultstring>UPnPError</faultstring>\n" + "<detail>\n" + "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n"; +static const char *soap_error_postfix = + "<errorDescription>Error</errorDescription>\n" + "</UPnPError>\n" + "</detail>\n" + "</s:Fault>\n"; + +static void web_connection_send_reply(struct http_request *req, + enum http_reply_code ret, + const char *action, int action_len, + const struct wpabuf *reply, + const char *replyname) +{ + struct wpabuf *buf; + char *replydata; + char *put_length_here = NULL; + char *body_start = NULL; + + if (reply) { + size_t len; + replydata = (char *) base64_encode(wpabuf_head(reply), + wpabuf_len(reply), &len); + } else + replydata = NULL; + + /* Parameters of the response: + * action(action_len) -- action we are responding to + * replyname -- a name we need for the reply + * replydata -- NULL or null-terminated string + */ + buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) + + (action_len > 0 ? action_len * 2 : 0)); + if (buf == NULL) { + wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to " + "POST"); + os_free(replydata); + http_request_deinit(req); + return; + } + + /* + * Assuming we will be successful, put in the output header first. + * Note: we do not keep connections alive (and httpread does + * not support it)... therefore we must have Connection: close. + */ + if (ret == HTTP_OK) { + wpabuf_put_str(buf, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/xml; " + "charset=\"utf-8\"\r\n"); + } else { + wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret); + } + wpabuf_put_str(buf, http_connection_close); + + wpabuf_put_str(buf, "Content-Length: "); + /* + * We will paste the length in later, leaving some extra whitespace. + * HTTP code is supposed to be tolerant of extra whitespace. + */ + put_length_here = wpabuf_put(buf, 0); + wpabuf_put_str(buf, " \r\n"); + + http_put_date(buf); + + /* terminating empty line */ + wpabuf_put_str(buf, "\r\n"); + + body_start = wpabuf_put(buf, 0); + + if (ret == HTTP_OK) { + wpabuf_put_str(buf, soap_prefix); + wpabuf_put_str(buf, "<u:"); + wpabuf_put_data(buf, action, action_len); + wpabuf_put_str(buf, "Response xmlns:u=\""); + wpabuf_put_str(buf, urn_wfawlanconfig); + wpabuf_put_str(buf, "\">\n"); + if (replydata && replyname) { + /* TODO: might possibly need to escape part of reply + * data? ... + * probably not, unlikely to have ampersand(&) or left + * angle bracket (<) in it... + */ + wpabuf_printf(buf, "<%s>", replyname); + wpabuf_put_str(buf, replydata); + wpabuf_printf(buf, "</%s>\n", replyname); + } + wpabuf_put_str(buf, "</u:"); + wpabuf_put_data(buf, action, action_len); + wpabuf_put_str(buf, "Response>\n"); + wpabuf_put_str(buf, soap_postfix); + } else { + /* Error case */ + wpabuf_put_str(buf, soap_prefix); + wpabuf_put_str(buf, soap_error_prefix); + wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret); + wpabuf_put_str(buf, soap_error_postfix); + wpabuf_put_str(buf, soap_postfix); + } + os_free(replydata); + + /* Now patch in the content length at the end */ + if (body_start && put_length_here) { + int body_length = (char *) wpabuf_put(buf, 0) - body_start; + char len_buf[10]; + os_snprintf(len_buf, sizeof(len_buf), "%d", body_length); + os_memcpy(put_length_here, len_buf, os_strlen(len_buf)); + } + + http_request_send_and_deinit(req, buf); +} + + +static const char * web_get_action(struct http_request *req, + size_t *action_len) +{ + const char *match; + int match_len; + char *b; + char *action; + + *action_len = 0; + /* The SOAPAction line of the header tells us what we want to do */ + b = http_request_get_hdr_line(req, "SOAPAction:"); + if (b == NULL) + return NULL; + if (*b == '"') + b++; + else + return NULL; + match = urn_wfawlanconfig; + match_len = os_strlen(urn_wfawlanconfig) - 1; + if (os_strncasecmp(b, match, match_len)) + return NULL; + b += match_len; + /* skip over version */ + while (isgraph(*b) && *b != '#') + b++; + if (*b != '#') + return NULL; + b++; + /* Following the sharp(#) should be the action and a double quote */ + action = b; + while (isgraph(*b) && *b != '"') + b++; + if (*b != '"') + return NULL; + *action_len = b - action; + return action; +} + + +/* Given that we have received a header w/ POST, act upon it + * + * Format of POST (case-insensitive): + * + * First line must be: + * POST /<file> HTTP/1.1 + * Since we don't do anything fancy we just ignore other lines. + * + * Our response (if no error) which includes only required lines is: + * HTTP/1.1 200 OK + * Connection: close + * Content-Type: text/xml + * Date: <rfc1123-date> + * + * Header lines must end with \r\n + * Per RFC 2616, content-length: is not required but connection:close + * would appear to be required (given that we will be closing it!). + */ +static void web_connection_parse_post(struct upnp_wps_device_sm *sm, + struct sockaddr_in *cli, + struct http_request *req, + const char *filename) +{ + enum http_reply_code ret; + char *data = http_request_get_data(req); /* body of http msg */ + const char *action = NULL; + size_t action_len = 0; + const char *replyname = NULL; /* argument name for the reply */ + struct wpabuf *reply = NULL; /* data for the reply */ + + if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) { + wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s", + filename); + ret = HTTP_NOT_FOUND; + goto bad; + } + + ret = UPNP_INVALID_ACTION; + action = web_get_action(req, &action_len); + if (action == NULL) + goto bad; + + if (!os_strncasecmp("GetDeviceInfo", action, action_len)) + ret = web_process_get_device_info(sm, &reply, &replyname); + else if (!os_strncasecmp("PutMessage", action, action_len)) + ret = web_process_put_message(sm, data, &reply, &replyname); + else if (!os_strncasecmp("PutWLANResponse", action, action_len)) + ret = web_process_put_wlan_response(sm, data, &reply, + &replyname); + else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len)) + ret = web_process_set_selected_registrar(sm, cli, data, &reply, + &replyname); + else + wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type"); + +bad: + if (ret != HTTP_OK) + wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret); + web_connection_send_reply(req, ret, action, action_len, reply, + replyname); + wpabuf_free(reply); +} + + +/* Given that we have received a header w/ SUBSCRIBE, act upon it + * + * Format of SUBSCRIBE (case-insensitive): + * + * First line must be: + * SUBSCRIBE /wps_event HTTP/1.1 + * + * Our response (if no error) which includes only required lines is: + * HTTP/1.1 200 OK + * Server: xx, UPnP/1.0, xx + * SID: uuid:xxxxxxxxx + * Timeout: Second-<n> + * Content-Length: 0 + * Date: xxxx + * + * Header lines must end with \r\n + * Per RFC 2616, content-length: is not required but connection:close + * would appear to be required (given that we will be closing it!). + */ +static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm, + struct http_request *req, + const char *filename) +{ + struct wpabuf *buf; + char *b; + char *hdr = http_request_get_hdr(req); + char *h; + char *match; + int match_len; + char *end; + int len; + int got_nt = 0; + u8 uuid[UUID_LEN]; + int got_uuid = 0; + char *callback_urls = NULL; + struct subscription *s = NULL; + enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR; + + buf = wpabuf_alloc(1000); + if (buf == NULL) { + http_request_deinit(req); + return; + } + + wpa_hexdump_ascii(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE", + (u8 *) hdr, os_strlen(hdr)); + + /* Parse/validate headers */ + h = hdr; + /* First line: SUBSCRIBE /wps_event HTTP/1.1 + * has already been parsed. + */ + if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) { + ret = HTTP_PRECONDITION_FAILED; + goto error; + } + wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event"); + end = os_strchr(h, '\n'); + + while (end) { + /* Option line by option line */ + h = end + 1; + end = os_strchr(h, '\n'); + if (end == NULL) + break; /* no unterminated lines allowed */ + + /* NT assures that it is our type of subscription; + * not used for a renewal. + **/ + match = "NT:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + h += match_len; + while (*h == ' ' || *h == '\t') + h++; + match = "upnp:event"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) != 0) { + ret = HTTP_BAD_REQUEST; + goto error; + } + got_nt = 1; + continue; + } + /* HOST should refer to us */ +#if 0 + match = "HOST:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + h += match_len; + while (*h == ' ' || *h == '\t') + h++; + ..... + } +#endif + /* CALLBACK gives one or more URLs for NOTIFYs + * to be sent as a result of the subscription. + * Each URL is enclosed in angle brackets. + */ + match = "CALLBACK:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + h += match_len; + while (*h == ' ' || *h == '\t') + h++; + len = end - h; + os_free(callback_urls); + callback_urls = dup_binstr(h, len); + if (callback_urls == NULL) { + ret = HTTP_INTERNAL_SERVER_ERROR; + goto error; + } + if (len > 0 && callback_urls[len - 1] == '\r') + callback_urls[len - 1] = '\0'; + continue; + } + /* SID is only for renewal */ + match = "SID:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + h += match_len; + while (*h == ' ' || *h == '\t') + h++; + match = "uuid:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) != 0) { + ret = HTTP_BAD_REQUEST; + goto error; + } + h += match_len; + while (*h == ' ' || *h == '\t') + h++; + if (uuid_str2bin(h, uuid)) { + ret = HTTP_BAD_REQUEST; + goto error; + } + got_uuid = 1; + continue; + } + /* TIMEOUT is requested timeout, but apparently we can + * just ignore this. + */ + } + + if (got_uuid) { + /* renewal */ + wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewal"); + if (callback_urls) { + ret = HTTP_BAD_REQUEST; + goto error; + } + s = subscription_renew(sm, uuid); + if (s == NULL) { + char str[80]; + uuid_bin2str(uuid, str, sizeof(str)); + wpa_printf(MSG_DEBUG, "WPS UPnP: Could not find " + "SID %s", str); + ret = HTTP_PRECONDITION_FAILED; + goto error; + } + } else if (callback_urls) { + wpa_printf(MSG_DEBUG, "WPS UPnP: New subscription"); + if (!got_nt) { + ret = HTTP_PRECONDITION_FAILED; + goto error; + } + s = subscription_start(sm, callback_urls); + if (s == NULL) { + ret = HTTP_INTERNAL_SERVER_ERROR; + goto error; + } + } else { + ret = HTTP_PRECONDITION_FAILED; + goto error; + } + + /* success */ + http_put_reply_code(buf, HTTP_OK); + wpabuf_put_str(buf, http_server_hdr); + wpabuf_put_str(buf, http_connection_close); + wpabuf_put_str(buf, "Content-Length: 0\r\n"); + wpabuf_put_str(buf, "SID: uuid:"); + /* subscription id */ + b = wpabuf_put(buf, 0); + uuid_bin2str(s->uuid, b, 80); + wpa_printf(MSG_DEBUG, "WPS UPnP: Assigned SID %s", b); + wpabuf_put(buf, os_strlen(b)); + wpabuf_put_str(buf, "\r\n"); + wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC); + http_put_date(buf); + /* And empty line to terminate header: */ + wpabuf_put_str(buf, "\r\n"); + + os_free(callback_urls); + http_request_send_and_deinit(req, buf); + return; + +error: + /* Per UPnP spec: + * Errors + * Incompatible headers + * 400 Bad Request. If SID header and one of NT or CALLBACK headers + * are present, the publisher must respond with HTTP error + * 400 Bad Request. + * Missing or invalid CALLBACK + * 412 Precondition Failed. If CALLBACK header is missing or does not + * contain a valid HTTP URL, the publisher must respond with HTTP + * error 412 Precondition Failed. + * Invalid NT + * 412 Precondition Failed. If NT header does not equal upnp:event, + * the publisher must respond with HTTP error 412 Precondition + * Failed. + * [For resubscription, use 412 if unknown uuid]. + * Unable to accept subscription + * 5xx. If a publisher is not able to accept a subscription (such as + * due to insufficient resources), it must respond with a + * HTTP 500-series error code. + * 599 Too many subscriptions (not a standard HTTP error) + */ + wpa_printf(MSG_DEBUG, "WPS UPnP: SUBSCRIBE failed - return %d", ret); + http_put_empty(buf, ret); + http_request_send_and_deinit(req, buf); + os_free(callback_urls); +} + + +/* Given that we have received a header w/ UNSUBSCRIBE, act upon it + * + * Format of UNSUBSCRIBE (case-insensitive): + * + * First line must be: + * UNSUBSCRIBE /wps_event HTTP/1.1 + * + * Our response (if no error) which includes only required lines is: + * HTTP/1.1 200 OK + * Content-Length: 0 + * + * Header lines must end with \r\n + * Per RFC 2616, content-length: is not required but connection:close + * would appear to be required (given that we will be closing it!). + */ +static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, + struct http_request *req, + const char *filename) +{ + struct wpabuf *buf; + char *hdr = http_request_get_hdr(req); + char *h; + char *match; + int match_len; + char *end; + u8 uuid[UUID_LEN]; + int got_uuid = 0; + struct subscription *s = NULL; + enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR; + + /* Parse/validate headers */ + h = hdr; + /* First line: UNSUBSCRIBE /wps_event HTTP/1.1 + * has already been parsed. + */ + if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) { + ret = HTTP_PRECONDITION_FAILED; + goto send_msg; + } + wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event"); + end = os_strchr(h, '\n'); + + while (end) { + /* Option line by option line */ + h = end + 1; + end = os_strchr(h, '\n'); + if (end == NULL) + break; /* no unterminated lines allowed */ + + /* HOST should refer to us */ +#if 0 + match = "HOST:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + h += match_len; + while (*h == ' ' || *h == '\t') + h++; + ..... + } +#endif + match = "SID:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + h += match_len; + while (*h == ' ' || *h == '\t') + h++; + match = "uuid:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) != 0) { + ret = HTTP_BAD_REQUEST; + goto send_msg; + } + h += match_len; + while (*h == ' ' || *h == '\t') + h++; + if (uuid_str2bin(h, uuid)) { + ret = HTTP_BAD_REQUEST; + goto send_msg; + } + got_uuid = 1; + continue; + } + + match = "NT:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + ret = HTTP_BAD_REQUEST; + goto send_msg; + } + + match = "CALLBACK:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + ret = HTTP_BAD_REQUEST; + goto send_msg; + } + } + + if (got_uuid) { + char str[80]; + + uuid_bin2str(uuid, str, sizeof(str)); + + s = subscription_find(sm, uuid); + if (s) { + struct subscr_addr *sa; + sa = dl_list_first(&s->addr_list, struct subscr_addr, + list); + wpa_printf(MSG_DEBUG, + "WPS UPnP: Unsubscribing %p (SID %s) %s", + s, str, (sa && sa->domain_and_port) ? + sa->domain_and_port : "-null-"); + dl_list_del(&s->list); + subscription_destroy(s); + } else { + wpa_printf(MSG_INFO, + "WPS UPnP: Could not find matching subscription to unsubscribe (SID %s)", + str); + ret = HTTP_PRECONDITION_FAILED; + goto send_msg; + } + } else { + wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not " + "found)"); + ret = HTTP_PRECONDITION_FAILED; + goto send_msg; + } + + ret = HTTP_OK; + +send_msg: + buf = wpabuf_alloc(200); + if (buf == NULL) { + http_request_deinit(req); + return; + } + http_put_empty(buf, ret); + http_request_send_and_deinit(req, buf); +} + + +/* Send error in response to unknown requests */ +static void web_connection_unimplemented(struct http_request *req) +{ + struct wpabuf *buf; + buf = wpabuf_alloc(200); + if (buf == NULL) { + http_request_deinit(req); + return; + } + http_put_empty(buf, HTTP_UNIMPLEMENTED); + http_request_send_and_deinit(req, buf); +} + + + +/* Called when we have gotten an apparently valid http request. + */ +static void web_connection_check_data(void *ctx, struct http_request *req) +{ + struct upnp_wps_device_sm *sm = ctx; + enum httpread_hdr_type htype = http_request_get_type(req); + char *filename = http_request_get_uri(req); + struct sockaddr_in *cli = http_request_get_cli_addr(req); + + if (!filename) { + wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI"); + http_request_deinit(req); + return; + } + /* Trim leading slashes from filename */ + while (*filename == '/') + filename++; + + wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d", + htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port)); + + switch (htype) { + case HTTPREAD_HDR_TYPE_GET: + web_connection_parse_get(sm, req, filename); + break; + case HTTPREAD_HDR_TYPE_POST: + web_connection_parse_post(sm, cli, req, filename); + break; + case HTTPREAD_HDR_TYPE_SUBSCRIBE: + web_connection_parse_subscribe(sm, req, filename); + break; + case HTTPREAD_HDR_TYPE_UNSUBSCRIBE: + web_connection_parse_unsubscribe(sm, req, filename); + break; + + /* We are not required to support M-POST; just plain + * POST is supposed to work, so we only support that. + * If for some reason we need to support M-POST, it is + * mostly the same as POST, with small differences. + */ + default: + /* Send 501 for anything else */ + web_connection_unimplemented(req); + break; + } +} + + +/* + * Listening for web connections + * We have a single TCP listening port, and hand off connections as we get + * them. + */ + +void web_listener_stop(struct upnp_wps_device_sm *sm) +{ + http_server_deinit(sm->web_srv); + sm->web_srv = NULL; +} + + +int web_listener_start(struct upnp_wps_device_sm *sm) +{ + struct in_addr addr; + addr.s_addr = sm->ip_addr; + sm->web_srv = http_server_init(&addr, -1, web_connection_check_data, + sm); + if (sm->web_srv == NULL) { + web_listener_stop(sm); + return -1; + } + sm->web_port = http_server_get_port(sm->web_srv); + + return 0; +} |