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]; 187189251Ssam 188189251Ssam wpabuf_put_str(buf, wps_device_xml_prefix); 189189251Ssam 190189251Ssam /* 191189251Ssam * Add required fields with default values if not configured. Add 192189251Ssam * optional and recommended fields only if configured. 193189251Ssam */ 194189251Ssam s = sm->wps->friendly_name; 195189251Ssam s = ((s && *s) ? s : "WPS Access Point"); 196189251Ssam xml_add_tagged_data(buf, "friendlyName", s); 197189251Ssam 198189251Ssam s = sm->wps->dev.manufacturer; 199189251Ssam s = ((s && *s) ? s : ""); 200189251Ssam xml_add_tagged_data(buf, "manufacturer", s); 201189251Ssam 202189251Ssam if (sm->wps->manufacturer_url) 203189251Ssam xml_add_tagged_data(buf, "manufacturerURL", 204189251Ssam sm->wps->manufacturer_url); 205189251Ssam 206189251Ssam if (sm->wps->model_description) 207189251Ssam xml_add_tagged_data(buf, "modelDescription", 208189251Ssam sm->wps->model_description); 209189251Ssam 210189251Ssam s = sm->wps->dev.model_name; 211189251Ssam s = ((s && *s) ? s : ""); 212189251Ssam xml_add_tagged_data(buf, "modelName", s); 213189251Ssam 214189251Ssam if (sm->wps->dev.model_number) 215189251Ssam xml_add_tagged_data(buf, "modelNumber", 216189251Ssam sm->wps->dev.model_number); 217189251Ssam 218189251Ssam if (sm->wps->model_url) 219189251Ssam xml_add_tagged_data(buf, "modelURL", sm->wps->model_url); 220189251Ssam 221189251Ssam if (sm->wps->dev.serial_number) 222189251Ssam xml_add_tagged_data(buf, "serialNumber", 223189251Ssam sm->wps->dev.serial_number); 224189251Ssam 225189251Ssam uuid_bin2str(sm->wps->uuid, uuid_string, sizeof(uuid_string)); 226189251Ssam s = uuid_string; 227189251Ssam /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data() 228189251Ssam * easily... 229189251Ssam */ 230189251Ssam wpabuf_put_str(buf, "<UDN>uuid:"); 231189251Ssam xml_data_encode(buf, s, os_strlen(s)); 232189251Ssam wpabuf_put_str(buf, "</UDN>\n"); 233189251Ssam 234189251Ssam if (sm->wps->upc) 235189251Ssam xml_add_tagged_data(buf, "UPC", sm->wps->upc); 236189251Ssam 237189251Ssam wpabuf_put_str(buf, wps_device_xml_postfix); 238189251Ssam} 239189251Ssam 240189251Ssam 241189251Ssamstatic void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code) 242189251Ssam{ 243189251Ssam wpabuf_put_str(buf, "HTTP/1.1 "); 244189251Ssam switch (code) { 245189251Ssam case HTTP_OK: 246189251Ssam wpabuf_put_str(buf, "200 OK\r\n"); 247189251Ssam break; 248189251Ssam case HTTP_BAD_REQUEST: 249189251Ssam wpabuf_put_str(buf, "400 Bad request\r\n"); 250189251Ssam break; 251189251Ssam case HTTP_PRECONDITION_FAILED: 252189251Ssam wpabuf_put_str(buf, "412 Precondition failed\r\n"); 253189251Ssam break; 254189251Ssam case HTTP_UNIMPLEMENTED: 255189251Ssam wpabuf_put_str(buf, "501 Unimplemented\r\n"); 256189251Ssam break; 257189251Ssam case HTTP_INTERNAL_SERVER_ERROR: 258189251Ssam default: 259189251Ssam wpabuf_put_str(buf, "500 Internal server error\r\n"); 260189251Ssam break; 261189251Ssam } 262189251Ssam} 263189251Ssam 264189251Ssam 265189251Ssamstatic void http_put_date(struct wpabuf *buf) 266189251Ssam{ 267189251Ssam wpabuf_put_str(buf, "Date: "); 268189251Ssam format_date(buf); 269189251Ssam wpabuf_put_str(buf, "\r\n"); 270189251Ssam} 271189251Ssam 272189251Ssam 273189251Ssamstatic void http_put_empty(struct wpabuf *buf, enum http_reply_code code) 274189251Ssam{ 275189251Ssam http_put_reply_code(buf, code); 276189251Ssam wpabuf_put_str(buf, http_server_hdr); 277189251Ssam wpabuf_put_str(buf, http_connection_close); 278189251Ssam wpabuf_put_str(buf, "Content-Length: 0\r\n" 279189251Ssam "\r\n"); 280189251Ssam} 281189251Ssam 282189251Ssam 283189251Ssam/* Given that we have received a header w/ GET, act upon it 284189251Ssam * 285189251Ssam * Format of GET (case-insensitive): 286189251Ssam * 287189251Ssam * First line must be: 288189251Ssam * GET /<file> HTTP/1.1 289189251Ssam * Since we don't do anything fancy we just ignore other lines. 290189251Ssam * 291189251Ssam * Our response (if no error) which includes only required lines is: 292189251Ssam * HTTP/1.1 200 OK 293189251Ssam * Connection: close 294189251Ssam * Content-Type: text/xml 295189251Ssam * Date: <rfc1123-date> 296189251Ssam * 297189251Ssam * Header lines must end with \r\n 298189251Ssam * Per RFC 2616, content-length: is not required but connection:close 299189251Ssam * would appear to be required (given that we will be closing it!). 300189251Ssam */ 301214734Srpaulostatic void web_connection_parse_get(struct upnp_wps_device_sm *sm, 302214734Srpaulo struct http_request *hreq, char *filename) 303189251Ssam{ 304189251Ssam struct wpabuf *buf; /* output buffer, allocated */ 305189251Ssam char *put_length_here; 306189251Ssam char *body_start; 307189251Ssam enum { 308189251Ssam GET_DEVICE_XML_FILE, 309189251Ssam GET_SCPD_XML_FILE 310189251Ssam } req; 311189251Ssam size_t extra_len = 0; 312189251Ssam int body_length; 313189251Ssam char len_buf[10]; 314189251Ssam 315189251Ssam /* 316189251Ssam * It is not required that filenames be case insensitive but it is 317189251Ssam * allowed and cannot hurt here. 318189251Ssam */ 319189251Ssam if (filename == NULL) 320189251Ssam filename = "(null)"; /* just in case */ 321189251Ssam if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) { 322189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML"); 323189251Ssam req = GET_DEVICE_XML_FILE; 324189251Ssam extra_len = 3000; 325189251Ssam if (sm->wps->friendly_name) 326189251Ssam extra_len += os_strlen(sm->wps->friendly_name); 327189251Ssam if (sm->wps->manufacturer_url) 328189251Ssam extra_len += os_strlen(sm->wps->manufacturer_url); 329189251Ssam if (sm->wps->model_description) 330189251Ssam extra_len += os_strlen(sm->wps->model_description); 331189251Ssam if (sm->wps->model_url) 332189251Ssam extra_len += os_strlen(sm->wps->model_url); 333189251Ssam if (sm->wps->upc) 334189251Ssam extra_len += os_strlen(sm->wps->upc); 335189251Ssam } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) { 336189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML"); 337189251Ssam req = GET_SCPD_XML_FILE; 338189251Ssam extra_len = os_strlen(wps_scpd_xml); 339189251Ssam } else { 340189251Ssam /* File not found */ 341189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s", 342189251Ssam filename); 343189251Ssam buf = wpabuf_alloc(200); 344214734Srpaulo if (buf == NULL) { 345214734Srpaulo http_request_deinit(hreq); 346189251Ssam return; 347214734Srpaulo } 348189251Ssam wpabuf_put_str(buf, 349189251Ssam "HTTP/1.1 404 Not Found\r\n" 350189251Ssam "Connection: close\r\n"); 351189251Ssam 352189251Ssam http_put_date(buf); 353189251Ssam 354189251Ssam /* terminating empty line */ 355189251Ssam wpabuf_put_str(buf, "\r\n"); 356189251Ssam 357189251Ssam goto send_buf; 358189251Ssam } 359189251Ssam 360189251Ssam buf = wpabuf_alloc(1000 + extra_len); 361214734Srpaulo if (buf == NULL) { 362214734Srpaulo http_request_deinit(hreq); 363189251Ssam return; 364214734Srpaulo } 365189251Ssam 366189251Ssam wpabuf_put_str(buf, 367189251Ssam "HTTP/1.1 200 OK\r\n" 368189251Ssam "Content-Type: text/xml; charset=\"utf-8\"\r\n"); 369189251Ssam wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n"); 370189251Ssam wpabuf_put_str(buf, "Connection: close\r\n"); 371189251Ssam wpabuf_put_str(buf, "Content-Length: "); 372189251Ssam /* 373189251Ssam * We will paste the length in later, leaving some extra whitespace. 374189251Ssam * HTTP code is supposed to be tolerant of extra whitespace. 375189251Ssam */ 376189251Ssam put_length_here = wpabuf_put(buf, 0); 377189251Ssam wpabuf_put_str(buf, " \r\n"); 378189251Ssam 379189251Ssam http_put_date(buf); 380189251Ssam 381189251Ssam /* terminating empty line */ 382189251Ssam wpabuf_put_str(buf, "\r\n"); 383189251Ssam 384189251Ssam body_start = wpabuf_put(buf, 0); 385189251Ssam 386189251Ssam switch (req) { 387189251Ssam case GET_DEVICE_XML_FILE: 388189251Ssam format_wps_device_xml(sm, buf); 389189251Ssam break; 390189251Ssam case GET_SCPD_XML_FILE: 391189251Ssam wpabuf_put_str(buf, wps_scpd_xml); 392189251Ssam break; 393189251Ssam } 394189251Ssam 395189251Ssam /* Now patch in the content length at the end */ 396189251Ssam body_length = (char *) wpabuf_put(buf, 0) - body_start; 397189251Ssam os_snprintf(len_buf, 10, "%d", body_length); 398189251Ssam os_memcpy(put_length_here, len_buf, os_strlen(len_buf)); 399189251Ssam 400189251Ssamsend_buf: 401214734Srpaulo http_request_send_and_deinit(hreq, buf); 402189251Ssam} 403189251Ssam 404189251Ssam 405189251Ssamstatic enum http_reply_code 406189251Ssamweb_process_get_device_info(struct upnp_wps_device_sm *sm, 407189251Ssam struct wpabuf **reply, const char **replyname) 408189251Ssam{ 409189251Ssam static const char *name = "NewDeviceInfo"; 410214734Srpaulo struct wps_config cfg; 411214734Srpaulo struct upnp_wps_peer *peer = &sm->peer; 412189251Ssam 413189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo"); 414214734Srpaulo 415214734Srpaulo if (sm->ctx->ap_pin == NULL) 416189251Ssam return HTTP_INTERNAL_SERVER_ERROR; 417214734Srpaulo 418214734Srpaulo /* 419214734Srpaulo * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS 420214734Srpaulo * registration over UPnP with the AP acting as an Enrollee. It should 421214734Srpaulo * be noted that this is frequently used just to get the device data, 422214734Srpaulo * i.e., there may not be any intent to actually complete the 423214734Srpaulo * registration. 424214734Srpaulo */ 425214734Srpaulo 426214734Srpaulo if (peer->wps) 427214734Srpaulo wps_deinit(peer->wps); 428214734Srpaulo 429214734Srpaulo os_memset(&cfg, 0, sizeof(cfg)); 430214734Srpaulo cfg.wps = sm->wps; 431214734Srpaulo cfg.pin = (u8 *) sm->ctx->ap_pin; 432214734Srpaulo cfg.pin_len = os_strlen(sm->ctx->ap_pin); 433214734Srpaulo peer->wps = wps_init(&cfg); 434214734Srpaulo if (peer->wps) { 435214734Srpaulo enum wsc_op_code op_code; 436214734Srpaulo *reply = wps_get_msg(peer->wps, &op_code); 437214734Srpaulo if (*reply == NULL) { 438214734Srpaulo wps_deinit(peer->wps); 439214734Srpaulo peer->wps = NULL; 440214734Srpaulo } 441214734Srpaulo } else 442214734Srpaulo *reply = NULL; 443189251Ssam if (*reply == NULL) { 444189251Ssam wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo"); 445189251Ssam return HTTP_INTERNAL_SERVER_ERROR; 446189251Ssam } 447189251Ssam *replyname = name; 448189251Ssam return HTTP_OK; 449189251Ssam} 450189251Ssam 451189251Ssam 452189251Ssamstatic enum http_reply_code 453189251Ssamweb_process_put_message(struct upnp_wps_device_sm *sm, char *data, 454189251Ssam struct wpabuf **reply, const char **replyname) 455189251Ssam{ 456189251Ssam struct wpabuf *msg; 457189251Ssam static const char *name = "NewOutMessage"; 458189251Ssam enum http_reply_code ret; 459214734Srpaulo enum wps_process_res res; 460214734Srpaulo enum wsc_op_code op_code; 461189251Ssam 462189251Ssam /* 463189251Ssam * PutMessage is used by external UPnP-based Registrar to perform WPS 464189251Ssam * operation with the access point itself; as compared with 465189251Ssam * PutWLANResponse which is for proxying. 466189251Ssam */ 467189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage"); 468214734Srpaulo msg = xml_get_base64_item(data, "NewInMessage", &ret); 469189251Ssam if (msg == NULL) 470189251Ssam return ret; 471214734Srpaulo res = wps_process_msg(sm->peer.wps, WSC_UPnP, msg); 472214734Srpaulo if (res == WPS_FAILURE) 473214734Srpaulo *reply = NULL; 474214734Srpaulo else 475214734Srpaulo *reply = wps_get_msg(sm->peer.wps, &op_code); 476189251Ssam wpabuf_free(msg); 477189251Ssam if (*reply == NULL) 478189251Ssam return HTTP_INTERNAL_SERVER_ERROR; 479189251Ssam *replyname = name; 480189251Ssam return HTTP_OK; 481189251Ssam} 482189251Ssam 483189251Ssam 484189251Ssamstatic enum http_reply_code 485189251Ssamweb_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data, 486189251Ssam struct wpabuf **reply, const char **replyname) 487189251Ssam{ 488189251Ssam struct wpabuf *msg; 489189251Ssam enum http_reply_code ret; 490189251Ssam u8 macaddr[ETH_ALEN]; 491189251Ssam int ev_type; 492189251Ssam int type; 493189251Ssam char *val; 494189251Ssam 495189251Ssam /* 496189251Ssam * External UPnP-based Registrar is passing us a message to be proxied 497189251Ssam * over to a Wi-Fi -based client of ours. 498189251Ssam */ 499189251Ssam 500189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse"); 501214734Srpaulo msg = xml_get_base64_item(data, "NewMessage", &ret); 502214734Srpaulo if (msg == NULL) { 503214734Srpaulo wpa_printf(MSG_DEBUG, "WPS UPnP: Could not extract NewMessage " 504214734Srpaulo "from PutWLANResponse"); 505189251Ssam return ret; 506214734Srpaulo } 507214734Srpaulo val = xml_get_first_item(data, "NewWLANEventType"); 508214734Srpaulo if (val == NULL) { 509214734Srpaulo wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventType in " 510214734Srpaulo "PutWLANResponse"); 511189251Ssam wpabuf_free(msg); 512189251Ssam return UPNP_ARG_VALUE_INVALID; 513189251Ssam } 514189251Ssam ev_type = atol(val); 515189251Ssam os_free(val); 516214734Srpaulo val = xml_get_first_item(data, "NewWLANEventMAC"); 517214734Srpaulo if (val == NULL) { 518214734Srpaulo wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventMAC in " 519214734Srpaulo "PutWLANResponse"); 520189251Ssam wpabuf_free(msg); 521189251Ssam return UPNP_ARG_VALUE_INVALID; 522189251Ssam } 523214734Srpaulo if (hwaddr_aton(val, macaddr)) { 524214734Srpaulo wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid NewWLANEventMAC in " 525214734Srpaulo "PutWLANResponse: '%s'", val); 526214734Srpaulo if (hwaddr_aton2(val, macaddr) > 0) { 527214734Srpaulo /* 528214734Srpaulo * At least some versions of Intel PROset seem to be 529214734Srpaulo * using dot-deliminated MAC address format here. 530214734Srpaulo */ 531214734Srpaulo wpa_printf(MSG_DEBUG, "WPS UPnP: Workaround - allow " 532214734Srpaulo "incorrect MAC address format in " 533214734Srpaulo "NewWLANEventMAC"); 534214734Srpaulo } else { 535214734Srpaulo wpabuf_free(msg); 536214734Srpaulo os_free(val); 537214734Srpaulo return UPNP_ARG_VALUE_INVALID; 538214734Srpaulo } 539214734Srpaulo } 540189251Ssam os_free(val); 541189251Ssam if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) { 542189251Ssam struct wps_parse_attr attr; 543189251Ssam if (wps_parse_msg(msg, &attr) < 0 || 544189251Ssam attr.msg_type == NULL) 545189251Ssam type = -1; 546189251Ssam else 547189251Ssam type = *attr.msg_type; 548189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type); 549189251Ssam } else 550189251Ssam type = -1; 551189251Ssam if (!sm->ctx->rx_req_put_wlan_response || 552189251Ssam sm->ctx->rx_req_put_wlan_response(sm->priv, ev_type, macaddr, msg, 553189251Ssam type)) { 554189251Ssam wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->" 555189251Ssam "rx_req_put_wlan_response"); 556189251Ssam wpabuf_free(msg); 557189251Ssam return HTTP_INTERNAL_SERVER_ERROR; 558189251Ssam } 559189251Ssam wpabuf_free(msg); 560189251Ssam *replyname = NULL; 561189251Ssam *reply = NULL; 562189251Ssam return HTTP_OK; 563189251Ssam} 564189251Ssam 565189251Ssam 566214734Srpaulostatic int find_er_addr(struct subscription *s, struct sockaddr_in *cli) 567189251Ssam{ 568214734Srpaulo struct subscr_addr *a; 569189251Ssam 570214734Srpaulo dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) { 571214734Srpaulo if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr) 572214734Srpaulo return 1; 573189251Ssam } 574214734Srpaulo return 0; 575189251Ssam} 576189251Ssam 577189251Ssam 578214734Srpaulostatic struct subscription * find_er(struct upnp_wps_device_sm *sm, 579214734Srpaulo struct sockaddr_in *cli) 580189251Ssam{ 581214734Srpaulo struct subscription *s; 582214734Srpaulo dl_list_for_each(s, &sm->subscriptions, struct subscription, list) 583214734Srpaulo if (find_er_addr(s, cli)) 584214734Srpaulo return s; 585214734Srpaulo return NULL; 586189251Ssam} 587189251Ssam 588189251Ssam 589189251Ssamstatic enum http_reply_code 590214734Srpauloweb_process_set_selected_registrar(struct upnp_wps_device_sm *sm, 591214734Srpaulo struct sockaddr_in *cli, char *data, 592214734Srpaulo struct wpabuf **reply, 593214734Srpaulo const char **replyname) 594189251Ssam{ 595189251Ssam struct wpabuf *msg; 596189251Ssam enum http_reply_code ret; 597214734Srpaulo struct subscription *s; 598189251Ssam 599214734Srpaulo wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar"); 600214734Srpaulo s = find_er(sm, cli); 601214734Srpaulo if (s == NULL) { 602214734Srpaulo wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar " 603214734Srpaulo "from unknown ER"); 604214734Srpaulo return UPNP_ACTION_FAILED; 605189251Ssam } 606214734Srpaulo msg = xml_get_base64_item(data, "NewMessage", &ret); 607189251Ssam if (msg == NULL) 608189251Ssam return ret; 609214734Srpaulo if (upnp_er_set_selected_registrar(sm->wps->registrar, s, msg)) { 610189251Ssam wpabuf_free(msg); 611189251Ssam return HTTP_INTERNAL_SERVER_ERROR; 612189251Ssam } 613189251Ssam wpabuf_free(msg); 614189251Ssam *replyname = NULL; 615189251Ssam *reply = NULL; 616189251Ssam return HTTP_OK; 617189251Ssam} 618189251Ssam 619189251Ssam 620189251Ssamstatic const char *soap_prefix = 621189251Ssam "<?xml version=\"1.0\"?>\n" 622189251Ssam "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " 623189251Ssam "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" 624189251Ssam "<s:Body>\n"; 625189251Ssamstatic const char *soap_postfix = 626189251Ssam "</s:Body>\n</s:Envelope>\n"; 627189251Ssam 628189251Ssamstatic const char *soap_error_prefix = 629189251Ssam "<s:Fault>\n" 630189251Ssam "<faultcode>s:Client</faultcode>\n" 631189251Ssam "<faultstring>UPnPError</faultstring>\n" 632189251Ssam "<detail>\n" 633189251Ssam "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n"; 634189251Ssamstatic const char *soap_error_postfix = 635189251Ssam "<errorDescription>Error</errorDescription>\n" 636189251Ssam "</UPnPError>\n" 637189251Ssam "</detail>\n" 638189251Ssam "</s:Fault>\n"; 639189251Ssam 640214734Srpaulostatic void web_connection_send_reply(struct http_request *req, 641189251Ssam enum http_reply_code ret, 642189251Ssam const char *action, int action_len, 643189251Ssam const struct wpabuf *reply, 644189251Ssam const char *replyname) 645189251Ssam{ 646189251Ssam struct wpabuf *buf; 647189251Ssam char *replydata; 648189251Ssam char *put_length_here = NULL; 649189251Ssam char *body_start = NULL; 650189251Ssam 651189251Ssam if (reply) { 652189251Ssam size_t len; 653189251Ssam replydata = (char *) base64_encode(wpabuf_head(reply), 654189251Ssam wpabuf_len(reply), &len); 655189251Ssam } else 656189251Ssam replydata = NULL; 657189251Ssam 658189251Ssam /* Parameters of the response: 659189251Ssam * action(action_len) -- action we are responding to 660189251Ssam * replyname -- a name we need for the reply 661189251Ssam * replydata -- NULL or null-terminated string 662189251Ssam */ 663189251Ssam buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) + 664189251Ssam (action_len > 0 ? action_len * 2 : 0)); 665189251Ssam if (buf == NULL) { 666189251Ssam wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to " 667189251Ssam "POST"); 668189251Ssam os_free(replydata); 669214734Srpaulo http_request_deinit(req); 670189251Ssam return; 671189251Ssam } 672189251Ssam 673189251Ssam /* 674189251Ssam * Assuming we will be successful, put in the output header first. 675189251Ssam * Note: we do not keep connections alive (and httpread does 676189251Ssam * not support it)... therefore we must have Connection: close. 677189251Ssam */ 678189251Ssam if (ret == HTTP_OK) { 679189251Ssam wpabuf_put_str(buf, 680189251Ssam "HTTP/1.1 200 OK\r\n" 681189251Ssam "Content-Type: text/xml; " 682189251Ssam "charset=\"utf-8\"\r\n"); 683189251Ssam } else { 684189251Ssam wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret); 685189251Ssam } 686189251Ssam wpabuf_put_str(buf, http_connection_close); 687189251Ssam 688189251Ssam wpabuf_put_str(buf, "Content-Length: "); 689189251Ssam /* 690189251Ssam * We will paste the length in later, leaving some extra whitespace. 691189251Ssam * HTTP code is supposed to be tolerant of extra whitespace. 692189251Ssam */ 693189251Ssam put_length_here = wpabuf_put(buf, 0); 694189251Ssam wpabuf_put_str(buf, " \r\n"); 695189251Ssam 696189251Ssam http_put_date(buf); 697189251Ssam 698189251Ssam /* terminating empty line */ 699189251Ssam wpabuf_put_str(buf, "\r\n"); 700189251Ssam 701189251Ssam body_start = wpabuf_put(buf, 0); 702189251Ssam 703189251Ssam if (ret == HTTP_OK) { 704189251Ssam wpabuf_put_str(buf, soap_prefix); 705189251Ssam wpabuf_put_str(buf, "<u:"); 706189251Ssam wpabuf_put_data(buf, action, action_len); 707189251Ssam wpabuf_put_str(buf, "Response xmlns:u=\""); 708189251Ssam wpabuf_put_str(buf, urn_wfawlanconfig); 709189251Ssam wpabuf_put_str(buf, "\">\n"); 710189251Ssam if (replydata && replyname) { 711189251Ssam /* TODO: might possibly need to escape part of reply 712189251Ssam * data? ... 713189251Ssam * probably not, unlikely to have ampersand(&) or left 714189251Ssam * angle bracket (<) in it... 715189251Ssam */ 716189251Ssam wpabuf_printf(buf, "<%s>", replyname); 717189251Ssam wpabuf_put_str(buf, replydata); 718189251Ssam wpabuf_printf(buf, "</%s>\n", replyname); 719189251Ssam } 720189251Ssam wpabuf_put_str(buf, "</u:"); 721189251Ssam wpabuf_put_data(buf, action, action_len); 722189251Ssam wpabuf_put_str(buf, "Response>\n"); 723189251Ssam wpabuf_put_str(buf, soap_postfix); 724189251Ssam } else { 725189251Ssam /* Error case */ 726189251Ssam wpabuf_put_str(buf, soap_prefix); 727189251Ssam wpabuf_put_str(buf, soap_error_prefix); 728189251Ssam wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret); 729189251Ssam wpabuf_put_str(buf, soap_error_postfix); 730189251Ssam wpabuf_put_str(buf, soap_postfix); 731189251Ssam } 732189251Ssam os_free(replydata); 733189251Ssam 734189251Ssam /* Now patch in the content length at the end */ 735189251Ssam if (body_start && put_length_here) { 736189251Ssam int body_length = (char *) wpabuf_put(buf, 0) - body_start; 737189251Ssam char len_buf[10]; 738189251Ssam os_snprintf(len_buf, sizeof(len_buf), "%d", body_length); 739189251Ssam os_memcpy(put_length_here, len_buf, os_strlen(len_buf)); 740189251Ssam } 741189251Ssam 742214734Srpaulo http_request_send_and_deinit(req, buf); 743189251Ssam} 744189251Ssam 745189251Ssam 746214734Srpaulostatic const char * web_get_action(struct http_request *req, 747214734Srpaulo size_t *action_len) 748189251Ssam{ 749189251Ssam const char *match; 750189251Ssam int match_len; 751189251Ssam char *b; 752189251Ssam char *action; 753189251Ssam 754189251Ssam *action_len = 0; 755189251Ssam /* The SOAPAction line of the header tells us what we want to do */ 756214734Srpaulo b = http_request_get_hdr_line(req, "SOAPAction:"); 757189251Ssam if (b == NULL) 758189251Ssam return NULL; 759189251Ssam if (*b == '"') 760189251Ssam b++; 761189251Ssam else 762189251Ssam return NULL; 763189251Ssam match = urn_wfawlanconfig; 764189251Ssam match_len = os_strlen(urn_wfawlanconfig) - 1; 765189251Ssam if (os_strncasecmp(b, match, match_len)) 766189251Ssam return NULL; 767189251Ssam b += match_len; 768189251Ssam /* skip over version */ 769189251Ssam while (isgraph(*b) && *b != '#') 770189251Ssam b++; 771189251Ssam if (*b != '#') 772189251Ssam return NULL; 773189251Ssam b++; 774189251Ssam /* Following the sharp(#) should be the action and a double quote */ 775189251Ssam action = b; 776189251Ssam while (isgraph(*b) && *b != '"') 777189251Ssam b++; 778189251Ssam if (*b != '"') 779189251Ssam return NULL; 780189251Ssam *action_len = b - action; 781189251Ssam return action; 782189251Ssam} 783189251Ssam 784189251Ssam 785189251Ssam/* Given that we have received a header w/ POST, act upon it 786189251Ssam * 787189251Ssam * Format of POST (case-insensitive): 788189251Ssam * 789189251Ssam * First line must be: 790189251Ssam * POST /<file> HTTP/1.1 791189251Ssam * Since we don't do anything fancy we just ignore other lines. 792189251Ssam * 793189251Ssam * Our response (if no error) which includes only required lines is: 794189251Ssam * HTTP/1.1 200 OK 795189251Ssam * Connection: close 796189251Ssam * Content-Type: text/xml 797189251Ssam * Date: <rfc1123-date> 798189251Ssam * 799189251Ssam * Header lines must end with \r\n 800189251Ssam * Per RFC 2616, content-length: is not required but connection:close 801189251Ssam * would appear to be required (given that we will be closing it!). 802189251Ssam */ 803214734Srpaulostatic void web_connection_parse_post(struct upnp_wps_device_sm *sm, 804214734Srpaulo struct sockaddr_in *cli, 805214734Srpaulo struct http_request *req, 806189251Ssam const char *filename) 807189251Ssam{ 808189251Ssam enum http_reply_code ret; 809214734Srpaulo char *data = http_request_get_data(req); /* body of http msg */ 810214734Srpaulo const char *action = NULL; 811214734Srpaulo size_t action_len = 0; 812189251Ssam const char *replyname = NULL; /* argument name for the reply */ 813189251Ssam struct wpabuf *reply = NULL; /* data for the reply */ 814189251Ssam 815214734Srpaulo if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) { 816214734Srpaulo wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s", 817214734Srpaulo filename); 818214734Srpaulo ret = HTTP_NOT_FOUND; 819214734Srpaulo goto bad; 820214734Srpaulo } 821214734Srpaulo 822189251Ssam ret = UPNP_INVALID_ACTION; 823214734Srpaulo action = web_get_action(req, &action_len); 824189251Ssam if (action == NULL) 825189251Ssam goto bad; 826189251Ssam 827189251Ssam if (!os_strncasecmp("GetDeviceInfo", action, action_len)) 828189251Ssam ret = web_process_get_device_info(sm, &reply, &replyname); 829189251Ssam else if (!os_strncasecmp("PutMessage", action, action_len)) 830189251Ssam ret = web_process_put_message(sm, data, &reply, &replyname); 831189251Ssam else if (!os_strncasecmp("PutWLANResponse", action, action_len)) 832189251Ssam ret = web_process_put_wlan_response(sm, data, &reply, 833189251Ssam &replyname); 834189251Ssam else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len)) 835214734Srpaulo ret = web_process_set_selected_registrar(sm, cli, data, &reply, 836189251Ssam &replyname); 837189251Ssam else 838189251Ssam wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type"); 839189251Ssam 840189251Ssambad: 841189251Ssam if (ret != HTTP_OK) 842189251Ssam wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret); 843214734Srpaulo web_connection_send_reply(req, ret, action, action_len, reply, 844189251Ssam replyname); 845189251Ssam wpabuf_free(reply); 846189251Ssam} 847189251Ssam 848189251Ssam 849189251Ssam/* Given that we have received a header w/ SUBSCRIBE, act upon it 850189251Ssam * 851189251Ssam * Format of SUBSCRIBE (case-insensitive): 852189251Ssam * 853189251Ssam * First line must be: 854189251Ssam * SUBSCRIBE /wps_event HTTP/1.1 855189251Ssam * 856189251Ssam * Our response (if no error) which includes only required lines is: 857189251Ssam * HTTP/1.1 200 OK 858189251Ssam * Server: xx, UPnP/1.0, xx 859189251Ssam * SID: uuid:xxxxxxxxx 860189251Ssam * Timeout: Second-<n> 861189251Ssam * Content-Length: 0 862189251Ssam * Date: xxxx 863189251Ssam * 864189251Ssam * Header lines must end with \r\n 865189251Ssam * Per RFC 2616, content-length: is not required but connection:close 866189251Ssam * would appear to be required (given that we will be closing it!). 867189251Ssam */ 868214734Srpaulostatic void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm, 869214734Srpaulo struct http_request *req, 870189251Ssam const char *filename) 871189251Ssam{ 872189251Ssam struct wpabuf *buf; 873189251Ssam char *b; 874214734Srpaulo char *hdr = http_request_get_hdr(req); 875189251Ssam char *h; 876189251Ssam char *match; 877189251Ssam int match_len; 878189251Ssam char *end; 879189251Ssam int len; 880189251Ssam int got_nt = 0; 881189251Ssam u8 uuid[UUID_LEN]; 882189251Ssam int got_uuid = 0; 883189251Ssam char *callback_urls = NULL; 884189251Ssam struct subscription *s = NULL; 885189251Ssam enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR; 886189251Ssam 887189251Ssam buf = wpabuf_alloc(1000); 888214734Srpaulo if (buf == NULL) { 889214734Srpaulo http_request_deinit(req); 890189251Ssam return; 891214734Srpaulo } 892189251Ssam 893189251Ssam /* Parse/validate headers */ 894189251Ssam h = hdr; 895189251Ssam /* First line: SUBSCRIBE /wps_event HTTP/1.1 896189251Ssam * has already been parsed. 897189251Ssam */ 898189251Ssam if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) { 899189251Ssam ret = HTTP_PRECONDITION_FAILED; 900189251Ssam goto error; 901189251Ssam } 902189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event"); 903189251Ssam end = os_strchr(h, '\n'); 904189251Ssam 905189251Ssam for (; end != NULL; h = end + 1) { 906189251Ssam /* Option line by option line */ 907189251Ssam h = end + 1; 908189251Ssam end = os_strchr(h, '\n'); 909189251Ssam if (end == NULL) 910189251Ssam break; /* no unterminated lines allowed */ 911189251Ssam 912189251Ssam /* NT assures that it is our type of subscription; 913189251Ssam * not used for a renewl. 914189251Ssam **/ 915189251Ssam match = "NT:"; 916189251Ssam match_len = os_strlen(match); 917189251Ssam if (os_strncasecmp(h, match, match_len) == 0) { 918189251Ssam h += match_len; 919189251Ssam while (*h == ' ' || *h == '\t') 920189251Ssam h++; 921189251Ssam match = "upnp:event"; 922189251Ssam match_len = os_strlen(match); 923189251Ssam if (os_strncasecmp(h, match, match_len) != 0) { 924189251Ssam ret = HTTP_BAD_REQUEST; 925189251Ssam goto error; 926189251Ssam } 927189251Ssam got_nt = 1; 928189251Ssam continue; 929189251Ssam } 930189251Ssam /* HOST should refer to us */ 931189251Ssam#if 0 932189251Ssam match = "HOST:"; 933189251Ssam match_len = os_strlen(match); 934189251Ssam if (os_strncasecmp(h, match, match_len) == 0) { 935189251Ssam h += match_len; 936189251Ssam while (*h == ' ' || *h == '\t') 937189251Ssam h++; 938189251Ssam ..... 939189251Ssam } 940189251Ssam#endif 941189251Ssam /* CALLBACK gives one or more URLs for NOTIFYs 942189251Ssam * to be sent as a result of the subscription. 943189251Ssam * Each URL is enclosed in angle brackets. 944189251Ssam */ 945189251Ssam match = "CALLBACK:"; 946189251Ssam match_len = os_strlen(match); 947189251Ssam if (os_strncasecmp(h, match, match_len) == 0) { 948189251Ssam h += match_len; 949189251Ssam while (*h == ' ' || *h == '\t') 950189251Ssam h++; 951189251Ssam len = end - h; 952189251Ssam os_free(callback_urls); 953189251Ssam callback_urls = os_malloc(len + 1); 954189251Ssam if (callback_urls == NULL) { 955189251Ssam ret = HTTP_INTERNAL_SERVER_ERROR; 956189251Ssam goto error; 957189251Ssam } 958189251Ssam os_memcpy(callback_urls, h, len); 959189251Ssam callback_urls[len] = 0; 960189251Ssam continue; 961189251Ssam } 962189251Ssam /* SID is only for renewal */ 963189251Ssam match = "SID:"; 964189251Ssam match_len = os_strlen(match); 965189251Ssam if (os_strncasecmp(h, match, match_len) == 0) { 966189251Ssam h += match_len; 967189251Ssam while (*h == ' ' || *h == '\t') 968189251Ssam h++; 969189251Ssam match = "uuid:"; 970189251Ssam match_len = os_strlen(match); 971189251Ssam if (os_strncasecmp(h, match, match_len) != 0) { 972189251Ssam ret = HTTP_BAD_REQUEST; 973189251Ssam goto error; 974189251Ssam } 975189251Ssam h += match_len; 976189251Ssam while (*h == ' ' || *h == '\t') 977189251Ssam h++; 978189251Ssam if (uuid_str2bin(h, uuid)) { 979189251Ssam ret = HTTP_BAD_REQUEST; 980189251Ssam goto error; 981189251Ssam } 982189251Ssam got_uuid = 1; 983189251Ssam continue; 984189251Ssam } 985189251Ssam /* TIMEOUT is requested timeout, but apparently we can 986189251Ssam * just ignore this. 987189251Ssam */ 988189251Ssam } 989189251Ssam 990189251Ssam if (got_uuid) { 991189251Ssam /* renewal */ 992189251Ssam if (callback_urls) { 993189251Ssam ret = HTTP_BAD_REQUEST; 994189251Ssam goto error; 995189251Ssam } 996189251Ssam s = subscription_renew(sm, uuid); 997189251Ssam if (s == NULL) { 998189251Ssam ret = HTTP_PRECONDITION_FAILED; 999189251Ssam goto error; 1000189251Ssam } 1001189251Ssam } else if (callback_urls) { 1002189251Ssam if (!got_nt) { 1003189251Ssam ret = HTTP_PRECONDITION_FAILED; 1004189251Ssam goto error; 1005189251Ssam } 1006189251Ssam s = subscription_start(sm, callback_urls); 1007189251Ssam if (s == NULL) { 1008189251Ssam ret = HTTP_INTERNAL_SERVER_ERROR; 1009189251Ssam goto error; 1010189251Ssam } 1011189251Ssam } else { 1012189251Ssam ret = HTTP_PRECONDITION_FAILED; 1013189251Ssam goto error; 1014189251Ssam } 1015189251Ssam 1016189251Ssam /* success */ 1017189251Ssam http_put_reply_code(buf, HTTP_OK); 1018189251Ssam wpabuf_put_str(buf, http_server_hdr); 1019189251Ssam wpabuf_put_str(buf, http_connection_close); 1020189251Ssam wpabuf_put_str(buf, "Content-Length: 0\r\n"); 1021189251Ssam wpabuf_put_str(buf, "SID: uuid:"); 1022189251Ssam /* subscription id */ 1023189251Ssam b = wpabuf_put(buf, 0); 1024189251Ssam uuid_bin2str(s->uuid, b, 80); 1025189251Ssam wpabuf_put(buf, os_strlen(b)); 1026189251Ssam wpabuf_put_str(buf, "\r\n"); 1027189251Ssam wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC); 1028189251Ssam http_put_date(buf); 1029189251Ssam /* And empty line to terminate header: */ 1030189251Ssam wpabuf_put_str(buf, "\r\n"); 1031189251Ssam 1032189251Ssam os_free(callback_urls); 1033214734Srpaulo http_request_send_and_deinit(req, buf); 1034189251Ssam return; 1035189251Ssam 1036189251Ssamerror: 1037189251Ssam /* Per UPnP spec: 1038189251Ssam * Errors 1039189251Ssam * Incompatible headers 1040189251Ssam * 400 Bad Request. If SID header and one of NT or CALLBACK headers 1041189251Ssam * are present, the publisher must respond with HTTP error 1042189251Ssam * 400 Bad Request. 1043189251Ssam * Missing or invalid CALLBACK 1044189251Ssam * 412 Precondition Failed. If CALLBACK header is missing or does not 1045189251Ssam * contain a valid HTTP URL, the publisher must respond with HTTP 1046189251Ssam * error 412 Precondition Failed. 1047189251Ssam * Invalid NT 1048189251Ssam * 412 Precondition Failed. If NT header does not equal upnp:event, 1049189251Ssam * the publisher must respond with HTTP error 412 Precondition 1050189251Ssam * Failed. 1051189251Ssam * [For resubscription, use 412 if unknown uuid]. 1052189251Ssam * Unable to accept subscription 1053189251Ssam * 5xx. If a publisher is not able to accept a subscription (such as 1054189251Ssam * due to insufficient resources), it must respond with a 1055189251Ssam * HTTP 500-series error code. 1056189251Ssam * 599 Too many subscriptions (not a standard HTTP error) 1057189251Ssam */ 1058189251Ssam http_put_empty(buf, ret); 1059214734Srpaulo http_request_send_and_deinit(req, buf); 1060209158Srpaulo os_free(callback_urls); 1061189251Ssam} 1062189251Ssam 1063189251Ssam 1064189251Ssam/* Given that we have received a header w/ UNSUBSCRIBE, act upon it 1065189251Ssam * 1066189251Ssam * Format of UNSUBSCRIBE (case-insensitive): 1067189251Ssam * 1068189251Ssam * First line must be: 1069189251Ssam * UNSUBSCRIBE /wps_event HTTP/1.1 1070189251Ssam * 1071189251Ssam * Our response (if no error) which includes only required lines is: 1072189251Ssam * HTTP/1.1 200 OK 1073189251Ssam * Content-Length: 0 1074189251Ssam * 1075189251Ssam * Header lines must end with \r\n 1076189251Ssam * Per RFC 2616, content-length: is not required but connection:close 1077189251Ssam * would appear to be required (given that we will be closing it!). 1078189251Ssam */ 1079214734Srpaulostatic void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, 1080214734Srpaulo struct http_request *req, 1081189251Ssam const char *filename) 1082189251Ssam{ 1083189251Ssam struct wpabuf *buf; 1084214734Srpaulo char *hdr = http_request_get_hdr(req); 1085189251Ssam char *h; 1086189251Ssam char *match; 1087189251Ssam int match_len; 1088189251Ssam char *end; 1089189251Ssam u8 uuid[UUID_LEN]; 1090189251Ssam int got_uuid = 0; 1091189251Ssam struct subscription *s = NULL; 1092189251Ssam enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR; 1093189251Ssam 1094189251Ssam /* Parse/validate headers */ 1095189251Ssam h = hdr; 1096189251Ssam /* First line: UNSUBSCRIBE /wps_event HTTP/1.1 1097189251Ssam * has already been parsed. 1098189251Ssam */ 1099189251Ssam if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) { 1100189251Ssam ret = HTTP_PRECONDITION_FAILED; 1101189251Ssam goto send_msg; 1102189251Ssam } 1103189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event"); 1104189251Ssam end = os_strchr(h, '\n'); 1105189251Ssam 1106189251Ssam for (; end != NULL; h = end + 1) { 1107189251Ssam /* Option line by option line */ 1108189251Ssam h = end + 1; 1109189251Ssam end = os_strchr(h, '\n'); 1110189251Ssam if (end == NULL) 1111189251Ssam break; /* no unterminated lines allowed */ 1112189251Ssam 1113189251Ssam /* HOST should refer to us */ 1114189251Ssam#if 0 1115189251Ssam match = "HOST:"; 1116189251Ssam match_len = os_strlen(match); 1117189251Ssam if (os_strncasecmp(h, match, match_len) == 0) { 1118189251Ssam h += match_len; 1119189251Ssam while (*h == ' ' || *h == '\t') 1120189251Ssam h++; 1121189251Ssam ..... 1122189251Ssam } 1123189251Ssam#endif 1124189251Ssam /* SID is only for renewal */ 1125189251Ssam match = "SID:"; 1126189251Ssam match_len = os_strlen(match); 1127189251Ssam if (os_strncasecmp(h, match, match_len) == 0) { 1128189251Ssam h += match_len; 1129189251Ssam while (*h == ' ' || *h == '\t') 1130189251Ssam h++; 1131189251Ssam match = "uuid:"; 1132189251Ssam match_len = os_strlen(match); 1133189251Ssam if (os_strncasecmp(h, match, match_len) != 0) { 1134189251Ssam ret = HTTP_BAD_REQUEST; 1135189251Ssam goto send_msg; 1136189251Ssam } 1137189251Ssam h += match_len; 1138189251Ssam while (*h == ' ' || *h == '\t') 1139189251Ssam h++; 1140189251Ssam if (uuid_str2bin(h, uuid)) { 1141189251Ssam ret = HTTP_BAD_REQUEST; 1142189251Ssam goto send_msg; 1143189251Ssam } 1144189251Ssam got_uuid = 1; 1145189251Ssam continue; 1146189251Ssam } 1147189251Ssam } 1148189251Ssam 1149189251Ssam if (got_uuid) { 1150189251Ssam s = subscription_find(sm, uuid); 1151189251Ssam if (s) { 1152214734Srpaulo struct subscr_addr *sa; 1153214734Srpaulo sa = dl_list_first(&s->addr_list, struct subscr_addr, 1154214734Srpaulo list); 1155189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s", 1156214734Srpaulo s, (sa && sa->domain_and_port) ? 1157214734Srpaulo sa->domain_and_port : "-null-"); 1158214734Srpaulo dl_list_del(&s->list); 1159189251Ssam subscription_destroy(s); 1160189251Ssam } 1161189251Ssam } else { 1162189251Ssam wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not " 1163189251Ssam "found)"); 1164189251Ssam ret = HTTP_PRECONDITION_FAILED; 1165189251Ssam goto send_msg; 1166189251Ssam } 1167189251Ssam 1168189251Ssam ret = HTTP_OK; 1169189251Ssam 1170189251Ssamsend_msg: 1171189251Ssam buf = wpabuf_alloc(200); 1172214734Srpaulo if (buf == NULL) { 1173214734Srpaulo http_request_deinit(req); 1174189251Ssam return; 1175214734Srpaulo } 1176189251Ssam http_put_empty(buf, ret); 1177214734Srpaulo http_request_send_and_deinit(req, buf); 1178189251Ssam} 1179189251Ssam 1180189251Ssam 1181189251Ssam/* Send error in response to unknown requests */ 1182214734Srpaulostatic void web_connection_unimplemented(struct http_request *req) 1183189251Ssam{ 1184189251Ssam struct wpabuf *buf; 1185189251Ssam buf = wpabuf_alloc(200); 1186214734Srpaulo if (buf == NULL) { 1187214734Srpaulo http_request_deinit(req); 1188189251Ssam return; 1189214734Srpaulo } 1190189251Ssam http_put_empty(buf, HTTP_UNIMPLEMENTED); 1191214734Srpaulo http_request_send_and_deinit(req, buf); 1192189251Ssam} 1193189251Ssam 1194189251Ssam 1195189251Ssam 1196189251Ssam/* Called when we have gotten an apparently valid http request. 1197189251Ssam */ 1198214734Srpaulostatic void web_connection_check_data(void *ctx, struct http_request *req) 1199189251Ssam{ 1200214734Srpaulo struct upnp_wps_device_sm *sm = ctx; 1201214734Srpaulo enum httpread_hdr_type htype = http_request_get_type(req); 1202214734Srpaulo char *filename = http_request_get_uri(req); 1203214734Srpaulo struct sockaddr_in *cli = http_request_get_cli_addr(req); 1204189251Ssam 1205189251Ssam if (!filename) { 1206189251Ssam wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI"); 1207214734Srpaulo http_request_deinit(req); 1208189251Ssam return; 1209189251Ssam } 1210189251Ssam /* Trim leading slashes from filename */ 1211189251Ssam while (*filename == '/') 1212189251Ssam filename++; 1213189251Ssam 1214189251Ssam wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d", 1215214734Srpaulo htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port)); 1216189251Ssam 1217189251Ssam switch (htype) { 1218189251Ssam case HTTPREAD_HDR_TYPE_GET: 1219214734Srpaulo web_connection_parse_get(sm, req, filename); 1220189251Ssam break; 1221189251Ssam case HTTPREAD_HDR_TYPE_POST: 1222214734Srpaulo web_connection_parse_post(sm, cli, req, filename); 1223189251Ssam break; 1224189251Ssam case HTTPREAD_HDR_TYPE_SUBSCRIBE: 1225214734Srpaulo web_connection_parse_subscribe(sm, req, filename); 1226189251Ssam break; 1227189251Ssam case HTTPREAD_HDR_TYPE_UNSUBSCRIBE: 1228214734Srpaulo web_connection_parse_unsubscribe(sm, req, filename); 1229189251Ssam break; 1230214734Srpaulo 1231189251Ssam /* We are not required to support M-POST; just plain 1232189251Ssam * POST is supposed to work, so we only support that. 1233189251Ssam * If for some reason we need to support M-POST, it is 1234189251Ssam * mostly the same as POST, with small differences. 1235189251Ssam */ 1236189251Ssam default: 1237189251Ssam /* Send 501 for anything else */ 1238214734Srpaulo web_connection_unimplemented(req); 1239189251Ssam break; 1240189251Ssam } 1241189251Ssam} 1242189251Ssam 1243189251Ssam 1244189251Ssam/* 1245189251Ssam * Listening for web connections 1246189251Ssam * We have a single TCP listening port, and hand off connections as we get 1247189251Ssam * them. 1248189251Ssam */ 1249189251Ssam 1250189251Ssamvoid web_listener_stop(struct upnp_wps_device_sm *sm) 1251189251Ssam{ 1252214734Srpaulo http_server_deinit(sm->web_srv); 1253214734Srpaulo sm->web_srv = NULL; 1254189251Ssam} 1255189251Ssam 1256189251Ssam 1257189251Ssamint web_listener_start(struct upnp_wps_device_sm *sm) 1258189251Ssam{ 1259214734Srpaulo struct in_addr addr; 1260214734Srpaulo addr.s_addr = sm->ip_addr; 1261214734Srpaulo sm->web_srv = http_server_init(&addr, -1, web_connection_check_data, 1262214734Srpaulo sm); 1263214734Srpaulo if (sm->web_srv == NULL) { 1264214734Srpaulo web_listener_stop(sm); 1265214734Srpaulo return -1; 1266189251Ssam } 1267214734Srpaulo sm->web_port = http_server_get_port(sm->web_srv); 1268189251Ssam 1269189251Ssam return 0; 1270189251Ssam} 1271