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