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