Merged the hostap_2.6 updates, and the Leap of Faith work, from the hostap_update...
[mech_eap.git] / libeap / src / wps / wps_upnp_web.c
index 250ad8e..7548e84 100644 (file)
@@ -179,7 +179,8 @@ static const char *wps_device_xml_postfix =
 /* format_wps_device_xml -- produce content of "file" wps_device.xml
  * (UPNP_WPS_DEVICE_XML_FILE)
  */
-static void format_wps_device_xml(struct upnp_wps_device_sm *sm,
+static void format_wps_device_xml(struct upnp_wps_device_interface *iface,
+                                 struct upnp_wps_device_sm *sm,
                                  struct wpabuf *buf)
 {
        const char *s;
@@ -191,38 +192,38 @@ static void format_wps_device_xml(struct upnp_wps_device_sm *sm,
         * Add required fields with default values if not configured. Add
         * optional and recommended fields only if configured.
         */
-       s = sm->wps->friendly_name;
+       s = iface->wps->friendly_name;
        s = ((s && *s) ? s : "WPS Access Point");
        xml_add_tagged_data(buf, "friendlyName", s);
 
-       s = sm->wps->dev.manufacturer;
+       s = iface->wps->dev.manufacturer;
        s = ((s && *s) ? s : "");
        xml_add_tagged_data(buf, "manufacturer", s);
 
-       if (sm->wps->manufacturer_url)
+       if (iface->wps->manufacturer_url)
                xml_add_tagged_data(buf, "manufacturerURL",
-                                   sm->wps->manufacturer_url);
+                                   iface->wps->manufacturer_url);
 
-       if (sm->wps->model_description)
+       if (iface->wps->model_description)
                xml_add_tagged_data(buf, "modelDescription",
-                                   sm->wps->model_description);
+                                   iface->wps->model_description);
 
-       s = sm->wps->dev.model_name;
+       s = iface->wps->dev.model_name;
        s = ((s && *s) ? s : "");
        xml_add_tagged_data(buf, "modelName", s);
 
-       if (sm->wps->dev.model_number)
+       if (iface->wps->dev.model_number)
                xml_add_tagged_data(buf, "modelNumber",
-                                   sm->wps->dev.model_number);
+                                   iface->wps->dev.model_number);
 
-       if (sm->wps->model_url)
-               xml_add_tagged_data(buf, "modelURL", sm->wps->model_url);
+       if (iface->wps->model_url)
+               xml_add_tagged_data(buf, "modelURL", iface->wps->model_url);
 
-       if (sm->wps->dev.serial_number)
+       if (iface->wps->dev.serial_number)
                xml_add_tagged_data(buf, "serialNumber",
-                                   sm->wps->dev.serial_number);
+                                   iface->wps->dev.serial_number);
 
-       uuid_bin2str(sm->wps->uuid, uuid_string, sizeof(uuid_string));
+       uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string));
        s = uuid_string;
        /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
         * easily...
@@ -231,8 +232,8 @@ static void format_wps_device_xml(struct upnp_wps_device_sm *sm,
        xml_data_encode(buf, s, os_strlen(s));
        wpabuf_put_str(buf, "</UDN>\n");
 
-       if (sm->wps->upc)
-               xml_add_tagged_data(buf, "UPC", sm->wps->upc);
+       if (iface->wps->upc)
+               xml_add_tagged_data(buf, "UPC", iface->wps->upc);
 
        wpabuf_put_str(buf, wps_device_xml_postfix);
 }
@@ -299,7 +300,8 @@ static void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
  * would appear to be required (given that we will be closing it!).
  */
 static void web_connection_parse_get(struct upnp_wps_device_sm *sm,
-                                    struct http_request *hreq, char *filename)
+                                    struct http_request *hreq,
+                                    const char *filename)
 {
        struct wpabuf *buf; /* output buffer, allocated */
        char *put_length_here;
@@ -311,27 +313,33 @@ static void web_connection_parse_get(struct upnp_wps_device_sm *sm,
        size_t extra_len = 0;
        int body_length;
        char len_buf[10];
+       struct upnp_wps_device_interface *iface;
+
+       iface = dl_list_first(&sm->interfaces,
+                             struct upnp_wps_device_interface, list);
+       if (iface == NULL) {
+               http_request_deinit(hreq);
+               return;
+       }
 
        /*
         * It is not required that filenames be case insensitive but it is
         * allowed and cannot hurt here.
         */
-       if (filename == NULL)
-               filename = "(null)"; /* just in case */
        if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
                wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
                req = GET_DEVICE_XML_FILE;
                extra_len = 3000;
-               if (sm->wps->friendly_name)
-                       extra_len += os_strlen(sm->wps->friendly_name);
-               if (sm->wps->manufacturer_url)
-                       extra_len += os_strlen(sm->wps->manufacturer_url);
-               if (sm->wps->model_description)
-                       extra_len += os_strlen(sm->wps->model_description);
-               if (sm->wps->model_url)
-                       extra_len += os_strlen(sm->wps->model_url);
-               if (sm->wps->upc)
-                       extra_len += os_strlen(sm->wps->upc);
+               if (iface->wps->friendly_name)
+                       extra_len += os_strlen(iface->wps->friendly_name);
+               if (iface->wps->manufacturer_url)
+                       extra_len += os_strlen(iface->wps->manufacturer_url);
+               if (iface->wps->model_description)
+                       extra_len += os_strlen(iface->wps->model_description);
+               if (iface->wps->model_url)
+                       extra_len += os_strlen(iface->wps->model_url);
+               if (iface->wps->upc)
+                       extra_len += os_strlen(iface->wps->upc);
        } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
                wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
                req = GET_SCPD_XML_FILE;
@@ -385,7 +393,7 @@ static void web_connection_parse_get(struct upnp_wps_device_sm *sm,
 
        switch (req) {
        case GET_DEVICE_XML_FILE:
-               format_wps_device_xml(sm, buf);
+               format_wps_device_xml(iface, sm, buf);
                break;
        case GET_SCPD_XML_FILE:
                wpabuf_put_str(buf, wps_scpd_xml);
@@ -402,17 +410,34 @@ send_buf:
 }
 
 
+static void wps_upnp_peer_del(struct upnp_wps_peer *peer)
+{
+       dl_list_del(&peer->list);
+       if (peer->wps)
+               wps_deinit(peer->wps);
+       os_free(peer);
+}
+
+
 static enum http_reply_code
 web_process_get_device_info(struct upnp_wps_device_sm *sm,
                            struct wpabuf **reply, const char **replyname)
 {
        static const char *name = "NewDeviceInfo";
        struct wps_config cfg;
-       struct upnp_wps_peer *peer = &sm->peer;
+       struct upnp_wps_device_interface *iface;
+       struct upnp_wps_peer *peer;
+
+       iface = dl_list_first(&sm->interfaces,
+                             struct upnp_wps_device_interface, list);
 
        wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
 
-       if (sm->ctx->ap_pin == NULL)
+       if (!iface || iface->ctx->ap_pin == NULL)
+               return HTTP_INTERNAL_SERVER_ERROR;
+
+       peer = os_zalloc(sizeof(*peer));
+       if (!peer)
                return HTTP_INTERNAL_SERVER_ERROR;
 
        /*
@@ -423,13 +448,10 @@ web_process_get_device_info(struct upnp_wps_device_sm *sm,
         * registration.
         */
 
-       if (peer->wps)
-               wps_deinit(peer->wps);
-
        os_memset(&cfg, 0, sizeof(cfg));
-       cfg.wps = sm->wps;
-       cfg.pin = (u8 *) sm->ctx->ap_pin;
-       cfg.pin_len = os_strlen(sm->ctx->ap_pin);
+       cfg.wps = iface->wps;
+       cfg.pin = (u8 *) iface->ctx->ap_pin;
+       cfg.pin_len = os_strlen(iface->ctx->ap_pin);
        peer->wps = wps_init(&cfg);
        if (peer->wps) {
                enum wsc_op_code op_code;
@@ -442,8 +464,22 @@ web_process_get_device_info(struct upnp_wps_device_sm *sm,
                *reply = NULL;
        if (*reply == NULL) {
                wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
+               os_free(peer);
                return HTTP_INTERNAL_SERVER_ERROR;
        }
+
+       if (dl_list_len(&iface->peers) > 3) {
+               struct upnp_wps_peer *old;
+
+               old = dl_list_first(&iface->peers, struct upnp_wps_peer, list);
+               if (old) {
+                       wpa_printf(MSG_DEBUG, "WPS UPnP: Drop oldest active session");
+                       wps_upnp_peer_del(old);
+               }
+       }
+       dl_list_add_tail(&iface->peers, &peer->list);
+       /* TODO: Could schedule a timeout to free the entry */
+
        *replyname = name;
        return HTTP_OK;
 }
@@ -458,6 +494,14 @@ web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
        enum http_reply_code ret;
        enum wps_process_res res;
        enum wsc_op_code op_code;
+       struct upnp_wps_device_interface *iface;
+       struct wps_parse_attr attr;
+       struct upnp_wps_peer *tmp, *peer;
+
+       iface = dl_list_first(&sm->interfaces,
+                             struct upnp_wps_device_interface, list);
+       if (!iface)
+               return HTTP_INTERNAL_SERVER_ERROR;
 
        /*
         * PutMessage is used by external UPnP-based Registrar to perform WPS
@@ -468,11 +512,56 @@ web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
        msg = xml_get_base64_item(data, "NewInMessage", &ret);
        if (msg == NULL)
                return ret;
-       res = wps_process_msg(sm->peer.wps, WSC_UPnP, msg);
-       if (res == WPS_FAILURE)
+
+       if (wps_parse_msg(msg, &attr)) {
+               wpa_printf(MSG_DEBUG,
+                          "WPS UPnP: Could not parse PutMessage - NewInMessage");
+               wpabuf_free(msg);
+               return HTTP_BAD_REQUEST;
+       }
+
+       /* Find a matching active peer session */
+       peer = NULL;
+       dl_list_for_each(tmp, &iface->peers, struct upnp_wps_peer, list) {
+               if (!tmp->wps)
+                       continue;
+               if (attr.enrollee_nonce &&
+                   os_memcmp(tmp->wps->nonce_e, attr.enrollee_nonce,
+                             WPS_NONCE_LEN) != 0)
+                       continue; /* Enrollee nonce mismatch */
+               if (attr.msg_type &&
+                   *attr.msg_type != WPS_M2 &&
+                   *attr.msg_type != WPS_M2D &&
+                   attr.registrar_nonce &&
+                   os_memcmp(tmp->wps->nonce_r, attr.registrar_nonce,
+                             WPS_NONCE_LEN) != 0)
+                       continue; /* Registrar nonce mismatch */
+               peer = tmp;
+               break;
+       }
+       if (!peer) {
+               /*
+                 Try to use the first entry in case message could work with
+                * it. The actual handler function will reject this, if needed.
+                * This maintains older behavior where only a single peer entry
+                * was supported.
+                */
+               peer = dl_list_first(&iface->peers, struct upnp_wps_peer, list);
+       }
+       if (!peer || !peer->wps) {
+               wpa_printf(MSG_DEBUG, "WPS UPnP: No active peer entry found");
+               wpabuf_free(msg);
+               return HTTP_BAD_REQUEST;
+       }
+
+       res = wps_process_msg(peer->wps, WSC_UPnP, msg);
+       if (res == WPS_FAILURE) {
                *reply = NULL;
-       else
-               *reply = wps_get_msg(sm->peer.wps, &op_code);
+               wpa_printf(MSG_DEBUG, "WPS UPnP: Drop active peer session");
+               wps_upnp_peer_del(peer);
+       } else {
+               *reply = wps_get_msg(peer->wps, &op_code);
+       }
        wpabuf_free(msg);
        if (*reply == NULL)
                return HTTP_INTERNAL_SERVER_ERROR;
@@ -491,6 +580,8 @@ web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
        int ev_type;
        int type;
        char *val;
+       struct upnp_wps_device_interface *iface;
+       int ok = 0;
 
        /*
         * External UPnP-based Registrar is passing us a message to be proxied
@@ -559,9 +650,16 @@ web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
                wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
        } else
                type = -1;
-       if (!sm->ctx->rx_req_put_wlan_response ||
-           sm->ctx->rx_req_put_wlan_response(sm->priv, ev_type, macaddr, msg,
-                                             type)) {
+       dl_list_for_each(iface, &sm->interfaces,
+                        struct upnp_wps_device_interface, list) {
+               if (iface->ctx->rx_req_put_wlan_response &&
+                   iface->ctx->rx_req_put_wlan_response(iface->priv, ev_type,
+                                                        macaddr, msg, type)
+                   == 0)
+                       ok = 1;
+       }
+
+       if (!ok) {
                wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
                           "rx_req_put_wlan_response");
                wpabuf_free(msg);
@@ -606,6 +704,8 @@ web_process_set_selected_registrar(struct upnp_wps_device_sm *sm,
        struct wpabuf *msg;
        enum http_reply_code ret;
        struct subscription *s;
+       struct upnp_wps_device_interface *iface;
+       int err = 0;
 
        wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
        s = find_er(sm, cli);
@@ -617,11 +717,15 @@ web_process_set_selected_registrar(struct upnp_wps_device_sm *sm,
        msg = xml_get_base64_item(data, "NewMessage", &ret);
        if (msg == NULL)
                return ret;
-       if (upnp_er_set_selected_registrar(sm->wps->registrar, s, msg)) {
-               wpabuf_free(msg);
-               return HTTP_INTERNAL_SERVER_ERROR;
+       dl_list_for_each(iface, &sm->interfaces,
+                        struct upnp_wps_device_interface, list) {
+               if (upnp_er_set_selected_registrar(iface->wps->registrar, s,
+                                                  msg))
+                       err = 1;
        }
        wpabuf_free(msg);
+       if (err)
+               return HTTP_INTERNAL_SERVER_ERROR;
        *replyname = NULL;
        *reply = NULL;
        return HTTP_OK;
@@ -901,6 +1005,9 @@ static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
                return;
        }
 
+       wpa_hexdump_ascii(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE",
+                         (u8 *) hdr, os_strlen(hdr));
+
        /* Parse/validate headers */
        h = hdr;
        /* First line: SUBSCRIBE /wps_event HTTP/1.1
@@ -913,7 +1020,7 @@ static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
        wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
        end = os_strchr(h, '\n');
 
-       for (; end != NULL; h = end + 1) {
+       while (end) {
                /* Option line by option line */
                h = end + 1;
                end = os_strchr(h, '\n');
@@ -921,7 +1028,7 @@ static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
                        break; /* no unterminated lines allowed */
 
                /* NT assures that it is our type of subscription;
-                * not used for a renewl.
+                * not used for a renewal.
                 **/
                match = "NT:";
                match_len = os_strlen(match);
@@ -961,13 +1068,13 @@ static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
                                h++;
                        len = end - h;
                        os_free(callback_urls);
-                       callback_urls = os_malloc(len + 1);
+                       callback_urls = dup_binstr(h, len);
                        if (callback_urls == NULL) {
                                ret = HTTP_INTERNAL_SERVER_ERROR;
                                goto error;
                        }
-                       os_memcpy(callback_urls, h, len);
-                       callback_urls[len] = 0;
+                       if (len > 0 && callback_urls[len - 1] == '\r')
+                               callback_urls[len - 1] = '\0';
                        continue;
                }
                /* SID is only for renewal */
@@ -1000,16 +1107,22 @@ static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
 
        if (got_uuid) {
                /* renewal */
+               wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewal");
                if (callback_urls) {
                        ret = HTTP_BAD_REQUEST;
                        goto error;
                }
                s = subscription_renew(sm, uuid);
                if (s == NULL) {
+                       char str[80];
+                       uuid_bin2str(uuid, str, sizeof(str));
+                       wpa_printf(MSG_DEBUG, "WPS UPnP: Could not find "
+                                  "SID %s", str);
                        ret = HTTP_PRECONDITION_FAILED;
                        goto error;
                }
        } else if (callback_urls) {
+               wpa_printf(MSG_DEBUG, "WPS UPnP: New subscription");
                if (!got_nt) {
                        ret = HTTP_PRECONDITION_FAILED;
                        goto error;
@@ -1033,6 +1146,7 @@ static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
        /* subscription id */
        b = wpabuf_put(buf, 0);
        uuid_bin2str(s->uuid, b, 80);
+       wpa_printf(MSG_DEBUG, "WPS UPnP: Assigned SID %s", b);
        wpabuf_put(buf, os_strlen(b));
        wpabuf_put_str(buf, "\r\n");
        wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
@@ -1066,6 +1180,7 @@ error:
        *     HTTP 500-series error code.
        *   599 Too many subscriptions (not a standard HTTP error)
        */
+       wpa_printf(MSG_DEBUG, "WPS UPnP: SUBSCRIBE failed - return %d", ret);
        http_put_empty(buf, ret);
        http_request_send_and_deinit(req, buf);
        os_free(callback_urls);
@@ -1114,7 +1229,7 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
        wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
        end = os_strchr(h, '\n');
 
-       for (; end != NULL; h = end + 1) {
+       while (end) {
                /* Option line by option line */
                h = end + 1;
                end = os_strchr(h, '\n');
@@ -1132,7 +1247,6 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
                        .....
                }
 #endif
-               /* SID is only for renewal */
                match = "SID:";
                match_len = os_strlen(match);
                if (os_strncasecmp(h, match, match_len) == 0) {
@@ -1155,19 +1269,44 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
                        got_uuid = 1;
                        continue;
                }
+
+               match = "NT:";
+               match_len = os_strlen(match);
+               if (os_strncasecmp(h, match, match_len) == 0) {
+                       ret = HTTP_BAD_REQUEST;
+                       goto send_msg;
+               }
+
+               match = "CALLBACK:";
+               match_len = os_strlen(match);
+               if (os_strncasecmp(h, match, match_len) == 0) {
+                       ret = HTTP_BAD_REQUEST;
+                       goto send_msg;
+               }
        }
 
        if (got_uuid) {
+               char str[80];
+
+               uuid_bin2str(uuid, str, sizeof(str));
+
                s = subscription_find(sm, uuid);
                if (s) {
                        struct subscr_addr *sa;
                        sa = dl_list_first(&s->addr_list, struct subscr_addr,
                                           list);
-                       wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
-                                  s, (sa && sa->domain_and_port) ?
+                       wpa_printf(MSG_DEBUG,
+                                  "WPS UPnP: Unsubscribing %p (SID %s) %s",
+                                  s, str, (sa && sa->domain_and_port) ?
                                   sa->domain_and_port : "-null-");
                        dl_list_del(&s->list);
                        subscription_destroy(s);
+               } else {
+                       wpa_printf(MSG_INFO,
+                                  "WPS UPnP: Could not find matching subscription to unsubscribe (SID %s)",
+                                  str);
+                       ret = HTTP_PRECONDITION_FAILED;
+                       goto send_msg;
                }
        } else {
                wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "