wps_upnp_web.c

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

Generated on Sat Nov 21 23:16:55 2009 for hostapd by  doxygen 1.6.1