$search
00001 /* 00002 * UPnP WPS Device - Web connections 00003 * Copyright (c) 2000-2003 Intel Corporation 00004 * Copyright (c) 2006-2007 Sony Corporation 00005 * Copyright (c) 2008-2009 Atheros Communications 00006 * Copyright (c) 2009, Jouni Malinen <j@w1.fi> 00007 * 00008 * See wps_upnp.c for more details on licensing and code history. 00009 */ 00010 00011 #include "includes.h" 00012 00013 #include "common.h" 00014 #include "base64.h" 00015 #include "uuid.h" 00016 #include "httpread.h" 00017 #include "http_server.h" 00018 #include "wps_i.h" 00019 #include "wps_upnp.h" 00020 #include "wps_upnp_i.h" 00021 #include "upnp_xml.h" 00022 00023 /*************************************************************************** 00024 * Web connections (we serve pages of info about ourselves, handle 00025 * requests, etc. etc.). 00026 **************************************************************************/ 00027 00028 #define WEB_CONNECTION_TIMEOUT_SEC 30 /* Drop web connection after t.o. */ 00029 #define WEB_CONNECTION_MAX_READ 8000 /* Max we'll read for TCP request */ 00030 #define MAX_WEB_CONNECTIONS 10 /* max simultaneous web connects */ 00031 00032 00033 static const char *urn_wfawlanconfig = 00034 "urn:schemas-wifialliance-org:service:WFAWLANConfig:1"; 00035 static const char *http_server_hdr = 00036 "Server: unspecified, UPnP/1.0, unspecified\r\n"; 00037 static const char *http_connection_close = 00038 "Connection: close\r\n"; 00039 00040 /* 00041 * "Files" that we serve via HTTP. The format of these files is given by 00042 * WFA WPS specifications. Extra white space has been removed to save space. 00043 */ 00044 00045 static const char wps_scpd_xml[] = 00046 "<?xml version=\"1.0\"?>\n" 00047 "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\n" 00048 "<specVersion><major>1</major><minor>0</minor></specVersion>\n" 00049 "<actionList>\n" 00050 "<action>\n" 00051 "<name>GetDeviceInfo</name>\n" 00052 "<argumentList>\n" 00053 "<argument>\n" 00054 "<name>NewDeviceInfo</name>\n" 00055 "<direction>out</direction>\n" 00056 "<relatedStateVariable>DeviceInfo</relatedStateVariable>\n" 00057 "</argument>\n" 00058 "</argumentList>\n" 00059 "</action>\n" 00060 "<action>\n" 00061 "<name>PutMessage</name>\n" 00062 "<argumentList>\n" 00063 "<argument>\n" 00064 "<name>NewInMessage</name>\n" 00065 "<direction>in</direction>\n" 00066 "<relatedStateVariable>InMessage</relatedStateVariable>\n" 00067 "</argument>\n" 00068 "<argument>\n" 00069 "<name>NewOutMessage</name>\n" 00070 "<direction>out</direction>\n" 00071 "<relatedStateVariable>OutMessage</relatedStateVariable>\n" 00072 "</argument>\n" 00073 "</argumentList>\n" 00074 "</action>\n" 00075 "<action>\n" 00076 "<name>PutWLANResponse</name>\n" 00077 "<argumentList>\n" 00078 "<argument>\n" 00079 "<name>NewMessage</name>\n" 00080 "<direction>in</direction>\n" 00081 "<relatedStateVariable>Message</relatedStateVariable>\n" 00082 "</argument>\n" 00083 "<argument>\n" 00084 "<name>NewWLANEventType</name>\n" 00085 "<direction>in</direction>\n" 00086 "<relatedStateVariable>WLANEventType</relatedStateVariable>\n" 00087 "</argument>\n" 00088 "<argument>\n" 00089 "<name>NewWLANEventMAC</name>\n" 00090 "<direction>in</direction>\n" 00091 "<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n" 00092 "</argument>\n" 00093 "</argumentList>\n" 00094 "</action>\n" 00095 "<action>\n" 00096 "<name>SetSelectedRegistrar</name>\n" 00097 "<argumentList>\n" 00098 "<argument>\n" 00099 "<name>NewMessage</name>\n" 00100 "<direction>in</direction>\n" 00101 "<relatedStateVariable>Message</relatedStateVariable>\n" 00102 "</argument>\n" 00103 "</argumentList>\n" 00104 "</action>\n" 00105 "</actionList>\n" 00106 "<serviceStateTable>\n" 00107 "<stateVariable sendEvents=\"no\">\n" 00108 "<name>Message</name>\n" 00109 "<dataType>bin.base64</dataType>\n" 00110 "</stateVariable>\n" 00111 "<stateVariable sendEvents=\"no\">\n" 00112 "<name>InMessage</name>\n" 00113 "<dataType>bin.base64</dataType>\n" 00114 "</stateVariable>\n" 00115 "<stateVariable sendEvents=\"no\">\n" 00116 "<name>OutMessage</name>\n" 00117 "<dataType>bin.base64</dataType>\n" 00118 "</stateVariable>\n" 00119 "<stateVariable sendEvents=\"no\">\n" 00120 "<name>DeviceInfo</name>\n" 00121 "<dataType>bin.base64</dataType>\n" 00122 "</stateVariable>\n" 00123 "<stateVariable sendEvents=\"yes\">\n" 00124 "<name>APStatus</name>\n" 00125 "<dataType>ui1</dataType>\n" 00126 "</stateVariable>\n" 00127 "<stateVariable sendEvents=\"yes\">\n" 00128 "<name>STAStatus</name>\n" 00129 "<dataType>ui1</dataType>\n" 00130 "</stateVariable>\n" 00131 "<stateVariable sendEvents=\"yes\">\n" 00132 "<name>WLANEvent</name>\n" 00133 "<dataType>bin.base64</dataType>\n" 00134 "</stateVariable>\n" 00135 "<stateVariable sendEvents=\"no\">\n" 00136 "<name>WLANEventType</name>\n" 00137 "<dataType>ui1</dataType>\n" 00138 "</stateVariable>\n" 00139 "<stateVariable sendEvents=\"no\">\n" 00140 "<name>WLANEventMAC</name>\n" 00141 "<dataType>string</dataType>\n" 00142 "</stateVariable>\n" 00143 "<stateVariable sendEvents=\"no\">\n" 00144 "<name>WLANResponse</name>\n" 00145 "<dataType>bin.base64</dataType>\n" 00146 "</stateVariable>\n" 00147 "</serviceStateTable>\n" 00148 "</scpd>\n" 00149 ; 00150 00151 00152 static const char *wps_device_xml_prefix = 00153 "<?xml version=\"1.0\"?>\n" 00154 "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n" 00155 "<specVersion>\n" 00156 "<major>1</major>\n" 00157 "<minor>0</minor>\n" 00158 "</specVersion>\n" 00159 "<device>\n" 00160 "<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1" 00161 "</deviceType>\n"; 00162 00163 static const char *wps_device_xml_postfix = 00164 "<serviceList>\n" 00165 "<service>\n" 00166 "<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1" 00167 "</serviceType>\n" 00168 "<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>" 00169 "\n" 00170 "<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n" 00171 "<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n" 00172 "<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n" 00173 "</service>\n" 00174 "</serviceList>\n" 00175 "</device>\n" 00176 "</root>\n"; 00177 00178 00179 /* format_wps_device_xml -- produce content of "file" wps_device.xml 00180 * (UPNP_WPS_DEVICE_XML_FILE) 00181 */ 00182 static void format_wps_device_xml(struct upnp_wps_device_sm *sm, 00183 struct wpabuf *buf) 00184 { 00185 const char *s; 00186 char uuid_string[80]; 00187 00188 wpabuf_put_str(buf, wps_device_xml_prefix); 00189 00190 /* 00191 * Add required fields with default values if not configured. Add 00192 * optional and recommended fields only if configured. 00193 */ 00194 s = sm->wps->friendly_name; 00195 s = ((s && *s) ? s : "WPS Access Point"); 00196 xml_add_tagged_data(buf, "friendlyName", s); 00197 00198 s = sm->wps->dev.manufacturer; 00199 s = ((s && *s) ? s : ""); 00200 xml_add_tagged_data(buf, "manufacturer", s); 00201 00202 if (sm->wps->manufacturer_url) 00203 xml_add_tagged_data(buf, "manufacturerURL", 00204 sm->wps->manufacturer_url); 00205 00206 if (sm->wps->model_description) 00207 xml_add_tagged_data(buf, "modelDescription", 00208 sm->wps->model_description); 00209 00210 s = sm->wps->dev.model_name; 00211 s = ((s && *s) ? s : ""); 00212 xml_add_tagged_data(buf, "modelName", s); 00213 00214 if (sm->wps->dev.model_number) 00215 xml_add_tagged_data(buf, "modelNumber", 00216 sm->wps->dev.model_number); 00217 00218 if (sm->wps->model_url) 00219 xml_add_tagged_data(buf, "modelURL", sm->wps->model_url); 00220 00221 if (sm->wps->dev.serial_number) 00222 xml_add_tagged_data(buf, "serialNumber", 00223 sm->wps->dev.serial_number); 00224 00225 uuid_bin2str(sm->wps->uuid, uuid_string, sizeof(uuid_string)); 00226 s = uuid_string; 00227 /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data() 00228 * easily... 00229 */ 00230 wpabuf_put_str(buf, "<UDN>uuid:"); 00231 xml_data_encode(buf, s, os_strlen(s)); 00232 wpabuf_put_str(buf, "</UDN>\n"); 00233 00234 if (sm->wps->upc) 00235 xml_add_tagged_data(buf, "UPC", sm->wps->upc); 00236 00237 wpabuf_put_str(buf, wps_device_xml_postfix); 00238 } 00239 00240 00241 static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code) 00242 { 00243 wpabuf_put_str(buf, "HTTP/1.1 "); 00244 switch (code) { 00245 case HTTP_OK: 00246 wpabuf_put_str(buf, "200 OK\r\n"); 00247 break; 00248 case HTTP_BAD_REQUEST: 00249 wpabuf_put_str(buf, "400 Bad request\r\n"); 00250 break; 00251 case HTTP_PRECONDITION_FAILED: 00252 wpabuf_put_str(buf, "412 Precondition failed\r\n"); 00253 break; 00254 case HTTP_UNIMPLEMENTED: 00255 wpabuf_put_str(buf, "501 Unimplemented\r\n"); 00256 break; 00257 case HTTP_INTERNAL_SERVER_ERROR: 00258 default: 00259 wpabuf_put_str(buf, "500 Internal server error\r\n"); 00260 break; 00261 } 00262 } 00263 00264 00265 static void http_put_date(struct wpabuf *buf) 00266 { 00267 wpabuf_put_str(buf, "Date: "); 00268 format_date(buf); 00269 wpabuf_put_str(buf, "\r\n"); 00270 } 00271 00272 00273 static void http_put_empty(struct wpabuf *buf, enum http_reply_code code) 00274 { 00275 http_put_reply_code(buf, code); 00276 wpabuf_put_str(buf, http_server_hdr); 00277 wpabuf_put_str(buf, http_connection_close); 00278 wpabuf_put_str(buf, "Content-Length: 0\r\n" 00279 "\r\n"); 00280 } 00281 00282 00283 /* Given that we have received a header w/ GET, act upon it 00284 * 00285 * Format of GET (case-insensitive): 00286 * 00287 * First line must be: 00288 * GET /<file> HTTP/1.1 00289 * Since we don't do anything fancy we just ignore other lines. 00290 * 00291 * Our response (if no error) which includes only required lines is: 00292 * HTTP/1.1 200 OK 00293 * Connection: close 00294 * Content-Type: text/xml 00295 * Date: <rfc1123-date> 00296 * 00297 * Header lines must end with \r\n 00298 * Per RFC 2616, content-length: is not required but connection:close 00299 * would appear to be required (given that we will be closing it!). 00300 */ 00301 static void web_connection_parse_get(struct upnp_wps_device_sm *sm, 00302 struct http_request *hreq, char *filename) 00303 { 00304 struct wpabuf *buf; /* output buffer, allocated */ 00305 char *put_length_here; 00306 char *body_start; 00307 enum { 00308 GET_DEVICE_XML_FILE, 00309 GET_SCPD_XML_FILE 00310 } req; 00311 size_t extra_len = 0; 00312 int body_length; 00313 char len_buf[10]; 00314 00315 /* 00316 * It is not required that filenames be case insensitive but it is 00317 * allowed and cannot hurt here. 00318 */ 00319 if (filename == NULL) 00320 filename = "(null)"; /* just in case */ 00321 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) { 00322 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML"); 00323 req = GET_DEVICE_XML_FILE; 00324 extra_len = 3000; 00325 if (sm->wps->friendly_name) 00326 extra_len += os_strlen(sm->wps->friendly_name); 00327 if (sm->wps->manufacturer_url) 00328 extra_len += os_strlen(sm->wps->manufacturer_url); 00329 if (sm->wps->model_description) 00330 extra_len += os_strlen(sm->wps->model_description); 00331 if (sm->wps->model_url) 00332 extra_len += os_strlen(sm->wps->model_url); 00333 if (sm->wps->upc) 00334 extra_len += os_strlen(sm->wps->upc); 00335 } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) { 00336 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML"); 00337 req = GET_SCPD_XML_FILE; 00338 extra_len = os_strlen(wps_scpd_xml); 00339 } else { 00340 /* File not found */ 00341 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s", 00342 filename); 00343 buf = wpabuf_alloc(200); 00344 if (buf == NULL) { 00345 http_request_deinit(hreq); 00346 return; 00347 } 00348 wpabuf_put_str(buf, 00349 "HTTP/1.1 404 Not Found\r\n" 00350 "Connection: close\r\n"); 00351 00352 http_put_date(buf); 00353 00354 /* terminating empty line */ 00355 wpabuf_put_str(buf, "\r\n"); 00356 00357 goto send_buf; 00358 } 00359 00360 buf = wpabuf_alloc(1000 + extra_len); 00361 if (buf == NULL) { 00362 http_request_deinit(hreq); 00363 return; 00364 } 00365 00366 wpabuf_put_str(buf, 00367 "HTTP/1.1 200 OK\r\n" 00368 "Content-Type: text/xml; charset=\"utf-8\"\r\n"); 00369 wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n"); 00370 wpabuf_put_str(buf, "Connection: close\r\n"); 00371 wpabuf_put_str(buf, "Content-Length: "); 00372 /* 00373 * We will paste the length in later, leaving some extra whitespace. 00374 * HTTP code is supposed to be tolerant of extra whitespace. 00375 */ 00376 put_length_here = wpabuf_put(buf, 0); 00377 wpabuf_put_str(buf, " \r\n"); 00378 00379 http_put_date(buf); 00380 00381 /* terminating empty line */ 00382 wpabuf_put_str(buf, "\r\n"); 00383 00384 body_start = wpabuf_put(buf, 0); 00385 00386 switch (req) { 00387 case GET_DEVICE_XML_FILE: 00388 format_wps_device_xml(sm, buf); 00389 break; 00390 case GET_SCPD_XML_FILE: 00391 wpabuf_put_str(buf, wps_scpd_xml); 00392 break; 00393 } 00394 00395 /* Now patch in the content length at the end */ 00396 body_length = (char *) wpabuf_put(buf, 0) - body_start; 00397 os_snprintf(len_buf, 10, "%d", body_length); 00398 os_memcpy(put_length_here, len_buf, os_strlen(len_buf)); 00399 00400 send_buf: 00401 http_request_send_and_deinit(hreq, buf); 00402 } 00403 00404 00405 static enum http_reply_code 00406 web_process_get_device_info(struct upnp_wps_device_sm *sm, 00407 struct wpabuf **reply, const char **replyname) 00408 { 00409 static const char *name = "NewDeviceInfo"; 00410 struct wps_config cfg; 00411 struct upnp_wps_peer *peer = &sm->peer; 00412 00413 wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo"); 00414 00415 /* 00416 * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS 00417 * registration over UPnP with the AP acting as an Enrollee. It should 00418 * be noted that this is frequently used just to get the device data, 00419 * i.e., there may not be any intent to actually complete the 00420 * registration. 00421 */ 00422 00423 if (peer->wps) 00424 wps_deinit(peer->wps); 00425 00426 os_memset(&cfg, 0, sizeof(cfg)); 00427 cfg.wps = sm->wps; 00428 cfg.pin = (u8 *) sm->ctx->ap_pin; 00429 cfg.pin_len = os_strlen(sm->ctx->ap_pin); 00430 peer->wps = wps_init(&cfg); 00431 if (peer->wps) { 00432 enum wsc_op_code op_code; 00433 *reply = wps_get_msg(peer->wps, &op_code); 00434 if (*reply == NULL) { 00435 wps_deinit(peer->wps); 00436 peer->wps = NULL; 00437 } 00438 } else 00439 *reply = NULL; 00440 if (*reply == NULL) { 00441 wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo"); 00442 return HTTP_INTERNAL_SERVER_ERROR; 00443 } 00444 *replyname = name; 00445 return HTTP_OK; 00446 } 00447 00448 00449 static enum http_reply_code 00450 web_process_put_message(struct upnp_wps_device_sm *sm, char *data, 00451 struct wpabuf **reply, const char **replyname) 00452 { 00453 struct wpabuf *msg; 00454 static const char *name = "NewOutMessage"; 00455 enum http_reply_code ret; 00456 enum wps_process_res res; 00457 enum wsc_op_code op_code; 00458 00459 /* 00460 * PutMessage is used by external UPnP-based Registrar to perform WPS 00461 * operation with the access point itself; as compared with 00462 * PutWLANResponse which is for proxying. 00463 */ 00464 wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage"); 00465 msg = xml_get_base64_item(data, "NewInMessage", &ret); 00466 if (msg == NULL) 00467 return ret; 00468 res = wps_process_msg(sm->peer.wps, WSC_UPnP, msg); 00469 if (res == WPS_FAILURE) 00470 *reply = NULL; 00471 else 00472 *reply = wps_get_msg(sm->peer.wps, &op_code); 00473 wpabuf_free(msg); 00474 if (*reply == NULL) 00475 return HTTP_INTERNAL_SERVER_ERROR; 00476 *replyname = name; 00477 return HTTP_OK; 00478 } 00479 00480 00481 static enum http_reply_code 00482 web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data, 00483 struct wpabuf **reply, const char **replyname) 00484 { 00485 struct wpabuf *msg; 00486 enum http_reply_code ret; 00487 u8 macaddr[ETH_ALEN]; 00488 int ev_type; 00489 int type; 00490 char *val; 00491 00492 /* 00493 * External UPnP-based Registrar is passing us a message to be proxied 00494 * over to a Wi-Fi -based client of ours. 00495 */ 00496 00497 wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse"); 00498 msg = xml_get_base64_item(data, "NewMessage", &ret); 00499 if (msg == NULL) { 00500 wpa_printf(MSG_DEBUG, "WPS UPnP: Could not extract NewMessage " 00501 "from PutWLANResponse"); 00502 return ret; 00503 } 00504 val = xml_get_first_item(data, "NewWLANEventType"); 00505 if (val == NULL) { 00506 wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventType in " 00507 "PutWLANResponse"); 00508 wpabuf_free(msg); 00509 return UPNP_ARG_VALUE_INVALID; 00510 } 00511 ev_type = atol(val); 00512 os_free(val); 00513 val = xml_get_first_item(data, "NewWLANEventMAC"); 00514 if (val == NULL) { 00515 wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventMAC in " 00516 "PutWLANResponse"); 00517 wpabuf_free(msg); 00518 return UPNP_ARG_VALUE_INVALID; 00519 } 00520 if (hwaddr_aton(val, macaddr)) { 00521 wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid NewWLANEventMAC in " 00522 "PutWLANResponse: '%s'", val); 00523 if (hwaddr_aton2(val, macaddr) > 0) { 00524 /* 00525 * At least some versions of Intel PROset seem to be 00526 * using dot-deliminated MAC address format here. 00527 */ 00528 wpa_printf(MSG_DEBUG, "WPS UPnP: Workaround - allow " 00529 "incorrect MAC address format in " 00530 "NewWLANEventMAC"); 00531 } else { 00532 wpabuf_free(msg); 00533 os_free(val); 00534 return UPNP_ARG_VALUE_INVALID; 00535 } 00536 } 00537 os_free(val); 00538 if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) { 00539 struct wps_parse_attr attr; 00540 if (wps_parse_msg(msg, &attr) < 0 || 00541 attr.msg_type == NULL) 00542 type = -1; 00543 else 00544 type = *attr.msg_type; 00545 wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type); 00546 } else 00547 type = -1; 00548 if (!sm->ctx->rx_req_put_wlan_response || 00549 sm->ctx->rx_req_put_wlan_response(sm->priv, ev_type, macaddr, msg, 00550 type)) { 00551 wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->" 00552 "rx_req_put_wlan_response"); 00553 wpabuf_free(msg); 00554 return HTTP_INTERNAL_SERVER_ERROR; 00555 } 00556 wpabuf_free(msg); 00557 *replyname = NULL; 00558 *reply = NULL; 00559 return HTTP_OK; 00560 } 00561 00562 00563 static int find_er_addr(struct subscription *s, struct sockaddr_in *cli) 00564 { 00565 struct subscr_addr *a; 00566 00567 dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) { 00568 if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr) 00569 return 1; 00570 } 00571 return 0; 00572 } 00573 00574 00575 static struct subscription * find_er(struct upnp_wps_device_sm *sm, 00576 struct sockaddr_in *cli) 00577 { 00578 struct subscription *s; 00579 dl_list_for_each(s, &sm->subscriptions, struct subscription, list) 00580 if (find_er_addr(s, cli)) 00581 return s; 00582 return NULL; 00583 } 00584 00585 00586 static enum http_reply_code 00587 web_process_set_selected_registrar(struct upnp_wps_device_sm *sm, 00588 struct sockaddr_in *cli, char *data, 00589 struct wpabuf **reply, 00590 const char **replyname) 00591 { 00592 struct wpabuf *msg; 00593 enum http_reply_code ret; 00594 struct subscription *s; 00595 00596 wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar"); 00597 s = find_er(sm, cli); 00598 if (s == NULL) { 00599 wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar " 00600 "from unknown ER"); 00601 return UPNP_ACTION_FAILED; 00602 } 00603 msg = xml_get_base64_item(data, "NewMessage", &ret); 00604 if (msg == NULL) 00605 return ret; 00606 if (upnp_er_set_selected_registrar(sm->wps->registrar, s, msg)) { 00607 wpabuf_free(msg); 00608 return HTTP_INTERNAL_SERVER_ERROR; 00609 } 00610 wpabuf_free(msg); 00611 *replyname = NULL; 00612 *reply = NULL; 00613 return HTTP_OK; 00614 } 00615 00616 00617 static const char *soap_prefix = 00618 "<?xml version=\"1.0\"?>\n" 00619 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " 00620 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" 00621 "<s:Body>\n"; 00622 static const char *soap_postfix = 00623 "</s:Body>\n</s:Envelope>\n"; 00624 00625 static const char *soap_error_prefix = 00626 "<s:Fault>\n" 00627 "<faultcode>s:Client</faultcode>\n" 00628 "<faultstring>UPnPError</faultstring>\n" 00629 "<detail>\n" 00630 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n"; 00631 static const char *soap_error_postfix = 00632 "<errorDescription>Error</errorDescription>\n" 00633 "</UPnPError>\n" 00634 "</detail>\n" 00635 "</s:Fault>\n"; 00636 00637 static void web_connection_send_reply(struct http_request *req, 00638 enum http_reply_code ret, 00639 const char *action, int action_len, 00640 const struct wpabuf *reply, 00641 const char *replyname) 00642 { 00643 struct wpabuf *buf; 00644 char *replydata; 00645 char *put_length_here = NULL; 00646 char *body_start = NULL; 00647 00648 if (reply) { 00649 size_t len; 00650 replydata = (char *) base64_encode(wpabuf_head(reply), 00651 wpabuf_len(reply), &len); 00652 } else 00653 replydata = NULL; 00654 00655 /* Parameters of the response: 00656 * action(action_len) -- action we are responding to 00657 * replyname -- a name we need for the reply 00658 * replydata -- NULL or null-terminated string 00659 */ 00660 buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) + 00661 (action_len > 0 ? action_len * 2 : 0)); 00662 if (buf == NULL) { 00663 wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to " 00664 "POST"); 00665 os_free(replydata); 00666 http_request_deinit(req); 00667 return; 00668 } 00669 00670 /* 00671 * Assuming we will be successful, put in the output header first. 00672 * Note: we do not keep connections alive (and httpread does 00673 * not support it)... therefore we must have Connection: close. 00674 */ 00675 if (ret == HTTP_OK) { 00676 wpabuf_put_str(buf, 00677 "HTTP/1.1 200 OK\r\n" 00678 "Content-Type: text/xml; " 00679 "charset=\"utf-8\"\r\n"); 00680 } else { 00681 wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret); 00682 } 00683 wpabuf_put_str(buf, http_connection_close); 00684 00685 wpabuf_put_str(buf, "Content-Length: "); 00686 /* 00687 * We will paste the length in later, leaving some extra whitespace. 00688 * HTTP code is supposed to be tolerant of extra whitespace. 00689 */ 00690 put_length_here = wpabuf_put(buf, 0); 00691 wpabuf_put_str(buf, " \r\n"); 00692 00693 http_put_date(buf); 00694 00695 /* terminating empty line */ 00696 wpabuf_put_str(buf, "\r\n"); 00697 00698 body_start = wpabuf_put(buf, 0); 00699 00700 if (ret == HTTP_OK) { 00701 wpabuf_put_str(buf, soap_prefix); 00702 wpabuf_put_str(buf, "<u:"); 00703 wpabuf_put_data(buf, action, action_len); 00704 wpabuf_put_str(buf, "Response xmlns:u=\""); 00705 wpabuf_put_str(buf, urn_wfawlanconfig); 00706 wpabuf_put_str(buf, "\">\n"); 00707 if (replydata && replyname) { 00708 /* TODO: might possibly need to escape part of reply 00709 * data? ... 00710 * probably not, unlikely to have ampersand(&) or left 00711 * angle bracket (<) in it... 00712 */ 00713 wpabuf_printf(buf, "<%s>", replyname); 00714 wpabuf_put_str(buf, replydata); 00715 wpabuf_printf(buf, "</%s>\n", replyname); 00716 } 00717 wpabuf_put_str(buf, "</u:"); 00718 wpabuf_put_data(buf, action, action_len); 00719 wpabuf_put_str(buf, "Response>\n"); 00720 wpabuf_put_str(buf, soap_postfix); 00721 } else { 00722 /* Error case */ 00723 wpabuf_put_str(buf, soap_prefix); 00724 wpabuf_put_str(buf, soap_error_prefix); 00725 wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret); 00726 wpabuf_put_str(buf, soap_error_postfix); 00727 wpabuf_put_str(buf, soap_postfix); 00728 } 00729 os_free(replydata); 00730 00731 /* Now patch in the content length at the end */ 00732 if (body_start && put_length_here) { 00733 int body_length = (char *) wpabuf_put(buf, 0) - body_start; 00734 char len_buf[10]; 00735 os_snprintf(len_buf, sizeof(len_buf), "%d", body_length); 00736 os_memcpy(put_length_here, len_buf, os_strlen(len_buf)); 00737 } 00738 00739 http_request_send_and_deinit(req, buf); 00740 } 00741 00742 00743 static const char * web_get_action(struct http_request *req, 00744 size_t *action_len) 00745 { 00746 const char *match; 00747 int match_len; 00748 char *b; 00749 char *action; 00750 00751 *action_len = 0; 00752 /* The SOAPAction line of the header tells us what we want to do */ 00753 b = http_request_get_hdr_line(req, "SOAPAction:"); 00754 if (b == NULL) 00755 return NULL; 00756 if (*b == '"') 00757 b++; 00758 else 00759 return NULL; 00760 match = urn_wfawlanconfig; 00761 match_len = os_strlen(urn_wfawlanconfig) - 1; 00762 if (os_strncasecmp(b, match, match_len)) 00763 return NULL; 00764 b += match_len; 00765 /* skip over version */ 00766 while (isgraph(*b) && *b != '#') 00767 b++; 00768 if (*b != '#') 00769 return NULL; 00770 b++; 00771 /* Following the sharp(#) should be the action and a double quote */ 00772 action = b; 00773 while (isgraph(*b) && *b != '"') 00774 b++; 00775 if (*b != '"') 00776 return NULL; 00777 *action_len = b - action; 00778 return action; 00779 } 00780 00781 00782 /* Given that we have received a header w/ POST, act upon it 00783 * 00784 * Format of POST (case-insensitive): 00785 * 00786 * First line must be: 00787 * POST /<file> HTTP/1.1 00788 * Since we don't do anything fancy we just ignore other lines. 00789 * 00790 * Our response (if no error) which includes only required lines is: 00791 * HTTP/1.1 200 OK 00792 * Connection: close 00793 * Content-Type: text/xml 00794 * Date: <rfc1123-date> 00795 * 00796 * Header lines must end with \r\n 00797 * Per RFC 2616, content-length: is not required but connection:close 00798 * would appear to be required (given that we will be closing it!). 00799 */ 00800 static void web_connection_parse_post(struct upnp_wps_device_sm *sm, 00801 struct sockaddr_in *cli, 00802 struct http_request *req, 00803 const char *filename) 00804 { 00805 enum http_reply_code ret; 00806 char *data = http_request_get_data(req); /* body of http msg */ 00807 const char *action = NULL; 00808 size_t action_len = 0; 00809 const char *replyname = NULL; /* argument name for the reply */ 00810 struct wpabuf *reply = NULL; /* data for the reply */ 00811 00812 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) { 00813 wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s", 00814 filename); 00815 ret = HTTP_NOT_FOUND; 00816 goto bad; 00817 } 00818 00819 ret = UPNP_INVALID_ACTION; 00820 action = web_get_action(req, &action_len); 00821 if (action == NULL) 00822 goto bad; 00823 00824 if (!os_strncasecmp("GetDeviceInfo", action, action_len)) 00825 ret = web_process_get_device_info(sm, &reply, &replyname); 00826 else if (!os_strncasecmp("PutMessage", action, action_len)) 00827 ret = web_process_put_message(sm, data, &reply, &replyname); 00828 else if (!os_strncasecmp("PutWLANResponse", action, action_len)) 00829 ret = web_process_put_wlan_response(sm, data, &reply, 00830 &replyname); 00831 else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len)) 00832 ret = web_process_set_selected_registrar(sm, cli, data, &reply, 00833 &replyname); 00834 else 00835 wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type"); 00836 00837 bad: 00838 if (ret != HTTP_OK) 00839 wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret); 00840 web_connection_send_reply(req, ret, action, action_len, reply, 00841 replyname); 00842 wpabuf_free(reply); 00843 } 00844 00845 00846 /* Given that we have received a header w/ SUBSCRIBE, act upon it 00847 * 00848 * Format of SUBSCRIBE (case-insensitive): 00849 * 00850 * First line must be: 00851 * SUBSCRIBE /wps_event HTTP/1.1 00852 * 00853 * Our response (if no error) which includes only required lines is: 00854 * HTTP/1.1 200 OK 00855 * Server: xx, UPnP/1.0, xx 00856 * SID: uuid:xxxxxxxxx 00857 * Timeout: Second-<n> 00858 * Content-Length: 0 00859 * Date: xxxx 00860 * 00861 * Header lines must end with \r\n 00862 * Per RFC 2616, content-length: is not required but connection:close 00863 * would appear to be required (given that we will be closing it!). 00864 */ 00865 static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm, 00866 struct http_request *req, 00867 const char *filename) 00868 { 00869 struct wpabuf *buf; 00870 char *b; 00871 char *hdr = http_request_get_hdr(req); 00872 char *h; 00873 char *match; 00874 int match_len; 00875 char *end; 00876 int len; 00877 int got_nt = 0; 00878 u8 uuid[UUID_LEN]; 00879 int got_uuid = 0; 00880 char *callback_urls = NULL; 00881 struct subscription *s = NULL; 00882 enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR; 00883 00884 buf = wpabuf_alloc(1000); 00885 if (buf == NULL) { 00886 http_request_deinit(req); 00887 return; 00888 } 00889 00890 /* Parse/validate headers */ 00891 h = hdr; 00892 /* First line: SUBSCRIBE /wps_event HTTP/1.1 00893 * has already been parsed. 00894 */ 00895 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) { 00896 ret = HTTP_PRECONDITION_FAILED; 00897 goto error; 00898 } 00899 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event"); 00900 end = os_strchr(h, '\n'); 00901 00902 for (; end != NULL; h = end + 1) { 00903 /* Option line by option line */ 00904 h = end + 1; 00905 end = os_strchr(h, '\n'); 00906 if (end == NULL) 00907 break; /* no unterminated lines allowed */ 00908 00909 /* NT assures that it is our type of subscription; 00910 * not used for a renewl. 00911 **/ 00912 match = "NT:"; 00913 match_len = os_strlen(match); 00914 if (os_strncasecmp(h, match, match_len) == 0) { 00915 h += match_len; 00916 while (*h == ' ' || *h == '\t') 00917 h++; 00918 match = "upnp:event"; 00919 match_len = os_strlen(match); 00920 if (os_strncasecmp(h, match, match_len) != 0) { 00921 ret = HTTP_BAD_REQUEST; 00922 goto error; 00923 } 00924 got_nt = 1; 00925 continue; 00926 } 00927 /* HOST should refer to us */ 00928 #if 0 00929 match = "HOST:"; 00930 match_len = os_strlen(match); 00931 if (os_strncasecmp(h, match, match_len) == 0) { 00932 h += match_len; 00933 while (*h == ' ' || *h == '\t') 00934 h++; 00935 ..... 00936 } 00937 #endif 00938 /* CALLBACK gives one or more URLs for NOTIFYs 00939 * to be sent as a result of the subscription. 00940 * Each URL is enclosed in angle brackets. 00941 */ 00942 match = "CALLBACK:"; 00943 match_len = os_strlen(match); 00944 if (os_strncasecmp(h, match, match_len) == 0) { 00945 h += match_len; 00946 while (*h == ' ' || *h == '\t') 00947 h++; 00948 len = end - h; 00949 os_free(callback_urls); 00950 callback_urls = os_malloc(len + 1); 00951 if (callback_urls == NULL) { 00952 ret = HTTP_INTERNAL_SERVER_ERROR; 00953 goto error; 00954 } 00955 os_memcpy(callback_urls, h, len); 00956 callback_urls[len] = 0; 00957 continue; 00958 } 00959 /* SID is only for renewal */ 00960 match = "SID:"; 00961 match_len = os_strlen(match); 00962 if (os_strncasecmp(h, match, match_len) == 0) { 00963 h += match_len; 00964 while (*h == ' ' || *h == '\t') 00965 h++; 00966 match = "uuid:"; 00967 match_len = os_strlen(match); 00968 if (os_strncasecmp(h, match, match_len) != 0) { 00969 ret = HTTP_BAD_REQUEST; 00970 goto error; 00971 } 00972 h += match_len; 00973 while (*h == ' ' || *h == '\t') 00974 h++; 00975 if (uuid_str2bin(h, uuid)) { 00976 ret = HTTP_BAD_REQUEST; 00977 goto error; 00978 } 00979 got_uuid = 1; 00980 continue; 00981 } 00982 /* TIMEOUT is requested timeout, but apparently we can 00983 * just ignore this. 00984 */ 00985 } 00986 00987 if (got_uuid) { 00988 /* renewal */ 00989 if (callback_urls) { 00990 ret = HTTP_BAD_REQUEST; 00991 goto error; 00992 } 00993 s = subscription_renew(sm, uuid); 00994 if (s == NULL) { 00995 ret = HTTP_PRECONDITION_FAILED; 00996 goto error; 00997 } 00998 } else if (callback_urls) { 00999 if (!got_nt) { 01000 ret = HTTP_PRECONDITION_FAILED; 01001 goto error; 01002 } 01003 s = subscription_start(sm, callback_urls); 01004 if (s == NULL) { 01005 ret = HTTP_INTERNAL_SERVER_ERROR; 01006 goto error; 01007 } 01008 } else { 01009 ret = HTTP_PRECONDITION_FAILED; 01010 goto error; 01011 } 01012 01013 /* success */ 01014 http_put_reply_code(buf, HTTP_OK); 01015 wpabuf_put_str(buf, http_server_hdr); 01016 wpabuf_put_str(buf, http_connection_close); 01017 wpabuf_put_str(buf, "Content-Length: 0\r\n"); 01018 wpabuf_put_str(buf, "SID: uuid:"); 01019 /* subscription id */ 01020 b = wpabuf_put(buf, 0); 01021 uuid_bin2str(s->uuid, b, 80); 01022 wpabuf_put(buf, os_strlen(b)); 01023 wpabuf_put_str(buf, "\r\n"); 01024 wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC); 01025 http_put_date(buf); 01026 /* And empty line to terminate header: */ 01027 wpabuf_put_str(buf, "\r\n"); 01028 01029 os_free(callback_urls); 01030 http_request_send_and_deinit(req, buf); 01031 return; 01032 01033 error: 01034 /* Per UPnP spec: 01035 * Errors 01036 * Incompatible headers 01037 * 400 Bad Request. If SID header and one of NT or CALLBACK headers 01038 * are present, the publisher must respond with HTTP error 01039 * 400 Bad Request. 01040 * Missing or invalid CALLBACK 01041 * 412 Precondition Failed. If CALLBACK header is missing or does not 01042 * contain a valid HTTP URL, the publisher must respond with HTTP 01043 * error 412 Precondition Failed. 01044 * Invalid NT 01045 * 412 Precondition Failed. If NT header does not equal upnp:event, 01046 * the publisher must respond with HTTP error 412 Precondition 01047 * Failed. 01048 * [For resubscription, use 412 if unknown uuid]. 01049 * Unable to accept subscription 01050 * 5xx. If a publisher is not able to accept a subscription (such as 01051 * due to insufficient resources), it must respond with a 01052 * HTTP 500-series error code. 01053 * 599 Too many subscriptions (not a standard HTTP error) 01054 */ 01055 http_put_empty(buf, ret); 01056 http_request_send_and_deinit(req, buf); 01057 os_free(callback_urls); 01058 } 01059 01060 01061 /* Given that we have received a header w/ UNSUBSCRIBE, act upon it 01062 * 01063 * Format of UNSUBSCRIBE (case-insensitive): 01064 * 01065 * First line must be: 01066 * UNSUBSCRIBE /wps_event HTTP/1.1 01067 * 01068 * Our response (if no error) which includes only required lines is: 01069 * HTTP/1.1 200 OK 01070 * Content-Length: 0 01071 * 01072 * Header lines must end with \r\n 01073 * Per RFC 2616, content-length: is not required but connection:close 01074 * would appear to be required (given that we will be closing it!). 01075 */ 01076 static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, 01077 struct http_request *req, 01078 const char *filename) 01079 { 01080 struct wpabuf *buf; 01081 char *hdr = http_request_get_hdr(req); 01082 char *h; 01083 char *match; 01084 int match_len; 01085 char *end; 01086 u8 uuid[UUID_LEN]; 01087 int got_uuid = 0; 01088 struct subscription *s = NULL; 01089 enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR; 01090 01091 /* Parse/validate headers */ 01092 h = hdr; 01093 /* First line: UNSUBSCRIBE /wps_event HTTP/1.1 01094 * has already been parsed. 01095 */ 01096 if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) { 01097 ret = HTTP_PRECONDITION_FAILED; 01098 goto send_msg; 01099 } 01100 wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event"); 01101 end = os_strchr(h, '\n'); 01102 01103 for (; end != NULL; h = end + 1) { 01104 /* Option line by option line */ 01105 h = end + 1; 01106 end = os_strchr(h, '\n'); 01107 if (end == NULL) 01108 break; /* no unterminated lines allowed */ 01109 01110 /* HOST should refer to us */ 01111 #if 0 01112 match = "HOST:"; 01113 match_len = os_strlen(match); 01114 if (os_strncasecmp(h, match, match_len) == 0) { 01115 h += match_len; 01116 while (*h == ' ' || *h == '\t') 01117 h++; 01118 ..... 01119 } 01120 #endif 01121 /* SID is only for renewal */ 01122 match = "SID:"; 01123 match_len = os_strlen(match); 01124 if (os_strncasecmp(h, match, match_len) == 0) { 01125 h += match_len; 01126 while (*h == ' ' || *h == '\t') 01127 h++; 01128 match = "uuid:"; 01129 match_len = os_strlen(match); 01130 if (os_strncasecmp(h, match, match_len) != 0) { 01131 ret = HTTP_BAD_REQUEST; 01132 goto send_msg; 01133 } 01134 h += match_len; 01135 while (*h == ' ' || *h == '\t') 01136 h++; 01137 if (uuid_str2bin(h, uuid)) { 01138 ret = HTTP_BAD_REQUEST; 01139 goto send_msg; 01140 } 01141 got_uuid = 1; 01142 continue; 01143 } 01144 } 01145 01146 if (got_uuid) { 01147 s = subscription_find(sm, uuid); 01148 if (s) { 01149 struct subscr_addr *sa; 01150 sa = dl_list_first(&s->addr_list, struct subscr_addr, 01151 list); 01152 wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s", 01153 s, (sa && sa->domain_and_port) ? 01154 sa->domain_and_port : "-null-"); 01155 dl_list_del(&s->list); 01156 subscription_destroy(s); 01157 } 01158 } else { 01159 wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not " 01160 "found)"); 01161 ret = HTTP_PRECONDITION_FAILED; 01162 goto send_msg; 01163 } 01164 01165 ret = HTTP_OK; 01166 01167 send_msg: 01168 buf = wpabuf_alloc(200); 01169 if (buf == NULL) { 01170 http_request_deinit(req); 01171 return; 01172 } 01173 http_put_empty(buf, ret); 01174 http_request_send_and_deinit(req, buf); 01175 } 01176 01177 01178 /* Send error in response to unknown requests */ 01179 static void web_connection_unimplemented(struct http_request *req) 01180 { 01181 struct wpabuf *buf; 01182 buf = wpabuf_alloc(200); 01183 if (buf == NULL) { 01184 http_request_deinit(req); 01185 return; 01186 } 01187 http_put_empty(buf, HTTP_UNIMPLEMENTED); 01188 http_request_send_and_deinit(req, buf); 01189 } 01190 01191 01192 01193 /* Called when we have gotten an apparently valid http request. 01194 */ 01195 static void web_connection_check_data(void *ctx, struct http_request *req) 01196 { 01197 struct upnp_wps_device_sm *sm = ctx; 01198 enum httpread_hdr_type htype = http_request_get_type(req); 01199 char *filename = http_request_get_uri(req); 01200 struct sockaddr_in *cli = http_request_get_cli_addr(req); 01201 01202 if (!filename) { 01203 wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI"); 01204 http_request_deinit(req); 01205 return; 01206 } 01207 /* Trim leading slashes from filename */ 01208 while (*filename == '/') 01209 filename++; 01210 01211 wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d", 01212 htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port)); 01213 01214 switch (htype) { 01215 case HTTPREAD_HDR_TYPE_GET: 01216 web_connection_parse_get(sm, req, filename); 01217 break; 01218 case HTTPREAD_HDR_TYPE_POST: 01219 web_connection_parse_post(sm, cli, req, filename); 01220 break; 01221 case HTTPREAD_HDR_TYPE_SUBSCRIBE: 01222 web_connection_parse_subscribe(sm, req, filename); 01223 break; 01224 case HTTPREAD_HDR_TYPE_UNSUBSCRIBE: 01225 web_connection_parse_unsubscribe(sm, req, filename); 01226 break; 01227 01228 /* We are not required to support M-POST; just plain 01229 * POST is supposed to work, so we only support that. 01230 * If for some reason we need to support M-POST, it is 01231 * mostly the same as POST, with small differences. 01232 */ 01233 default: 01234 /* Send 501 for anything else */ 01235 web_connection_unimplemented(req); 01236 break; 01237 } 01238 } 01239 01240 01241 /* 01242 * Listening for web connections 01243 * We have a single TCP listening port, and hand off connections as we get 01244 * them. 01245 */ 01246 01247 void web_listener_stop(struct upnp_wps_device_sm *sm) 01248 { 01249 http_server_deinit(sm->web_srv); 01250 sm->web_srv = NULL; 01251 } 01252 01253 01254 int web_listener_start(struct upnp_wps_device_sm *sm) 01255 { 01256 struct in_addr addr; 01257 addr.s_addr = sm->ip_addr; 01258 sm->web_srv = http_server_init(&addr, -1, web_connection_check_data, 01259 sm); 01260 if (sm->web_srv == NULL) { 01261 web_listener_stop(sm); 01262 return -1; 01263 } 01264 sm->web_port = http_server_get_port(sm->web_srv); 01265 01266 return 0; 01267 }