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