aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--hostapd/ChangeLog1
-rw-r--r--hostapd/Makefile10
-rw-r--r--hostapd/config.c23
-rw-r--r--hostapd/config.h6
-rw-r--r--hostapd/hostapd.conf22
-rw-r--r--hostapd/hostapd.h2
-rw-r--r--hostapd/wps_hostapd.c268
-rw-r--r--src/eap_peer/eap_wsc.c1
-rw-r--r--src/eap_server/eap_wsc.c32
-rw-r--r--src/utils/wpabuf.c17
-rw-r--r--src/utils/wpabuf.h8
-rw-r--r--src/wps/httpread.c858
-rw-r--r--src/wps/httpread.h123
-rw-r--r--src/wps/wps.h44
-rw-r--r--src/wps/wps_enrollee.c9
-rw-r--r--src/wps/wps_i.h2
-rw-r--r--src/wps/wps_registrar.c169
-rw-r--r--src/wps/wps_upnp.c1056
-rw-r--r--src/wps/wps_upnp.h66
-rw-r--r--src/wps/wps_upnp_event.c525
-rw-r--r--src/wps/wps_upnp_i.h193
-rw-r--r--src/wps/wps_upnp_ssdp.c887
-rw-r--r--src/wps/wps_upnp_web.c1959
23 files changed, 6273 insertions, 8 deletions
diff --git a/hostapd/ChangeLog b/hostapd/ChangeLog
index 1033aa2..d8e3b2e 100644
--- a/hostapd/ChangeLog
+++ b/hostapd/ChangeLog
@@ -4,6 +4,7 @@ ChangeLog for hostapd
* increased hostapd_cli ping interval to 5 seconds and made this
configurable with a new command line options (-G<seconds>)
* driver_nl80211: use Linux socket filter to improve performance
+ * added support for external Registrars with WPS (UPnP transport)
2009-01-06 - v0.6.7
* added support for Wi-Fi Protected Setup (WPS)
diff --git a/hostapd/Makefile b/hostapd/Makefile
index 138f58e..5a0655b 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -283,6 +283,16 @@ NEED_DH_GROUPS=y
NEED_SHA256=y
NEED_CRYPTO=y
NEED_BASE64=y
+
+ifdef CONFIG_WPS_UPNP
+CFLAGS += -DCONFIG_WPS_UPNP
+OBJS += ../src/wps/wps_upnp.o
+OBJS += ../src/wps/wps_upnp_ssdp.o
+OBJS += ../src/wps/wps_upnp_web.o
+OBJS += ../src/wps/wps_upnp_event.o
+OBJS += ../src/wps/httpread.o
+endif
+
endif
ifdef CONFIG_EAP_IKEV2
diff --git a/hostapd/config.c b/hostapd/config.c
index adecbf1..455e94e 100644
--- a/hostapd/config.c
+++ b/hostapd/config.c
@@ -2224,6 +2224,23 @@ struct hostapd_config * hostapd_config_read(const char *fname)
line, pos);
errors++;
}
+ } else if (os_strcmp(buf, "upnp_iface") == 0) {
+ bss->upnp_iface = os_strdup(pos);
+ } else if (os_strcmp(buf, "friendly_name") == 0) {
+ os_free(bss->friendly_name);
+ bss->friendly_name = os_strdup(pos);
+ } else if (os_strcmp(buf, "manufacturer_url") == 0) {
+ os_free(bss->manufacturer_url);
+ bss->manufacturer_url = os_strdup(pos);
+ } else if (os_strcmp(buf, "model_description") == 0) {
+ os_free(bss->model_description);
+ bss->model_description = os_strdup(pos);
+ } else if (os_strcmp(buf, "model_url") == 0) {
+ os_free(bss->model_url);
+ bss->model_url = os_strdup(pos);
+ } else if (os_strcmp(buf, "upc") == 0) {
+ os_free(bss->upc);
+ bss->upc = os_strdup(pos);
#endif /* CONFIG_WPS */
} else {
wpa_printf(MSG_ERROR, "Line %d: unknown configuration "
@@ -2431,6 +2448,12 @@ static void hostapd_config_free_bss(struct hostapd_bss_config *conf)
os_free(conf->ap_pin);
os_free(conf->extra_cred);
os_free(conf->ap_settings);
+ os_free(conf->upnp_iface);
+ os_free(conf->friendly_name);
+ os_free(conf->manufacturer_url);
+ os_free(conf->model_description);
+ os_free(conf->model_url);
+ os_free(conf->upc);
#endif /* CONFIG_WPS */
}
diff --git a/hostapd/config.h b/hostapd/config.h
index 87be506..3159682 100644
--- a/hostapd/config.h
+++ b/hostapd/config.h
@@ -306,6 +306,12 @@ struct hostapd_bss_config {
int wps_cred_processing;
u8 *ap_settings;
size_t ap_settings_len;
+ char *upnp_iface;
+ char *friendly_name;
+ char *manufacturer_url;
+ char *model_description;
+ char *model_url;
+ char *upc;
#endif /* CONFIG_WPS */
};
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 9f9d7e3..a4a033a 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -971,6 +971,28 @@ own_ip_addr=127.0.0.1
# attribute.
#ap_settings=hostapd.ap_settings
+# WPS UPnP interface
+# If set, support for external Registrars is enabled.
+#upnp_iface=br0
+
+# Friendly Name (required for UPnP)
+# Short description for end use. Should be less than 64 characters.
+#friendly_name=WPS Access Point
+
+# Manufacturer URL (optional for UPnP)
+#manufacturer_url=http://www.example.com/
+
+# Model Description (recommended for UPnP)
+# Long description for end user. Should be less than 128 characters.
+#model_description=Wireless Access Point
+
+# Model URL (optional for UPnP)
+#model_url=http://www.example.com/model/
+
+# Universal Product Code (optional for UPnP)
+# 12-digit, all-numeric code that identifies the consumer package.
+#upc=123456789012
+
##### Multiple BSSID support ##################################################
#
# Above configuration is using the default interface (wlan#, or multi-SSID VLAN
diff --git a/hostapd/hostapd.h b/hostapd/hostapd.h
index 1f90b3d..26f30d7 100644
--- a/hostapd/hostapd.h
+++ b/hostapd/hostapd.h
@@ -100,6 +100,7 @@ struct hostap_sta_driver_data {
struct wpa_driver_ops;
struct wpa_ctrl_dst;
struct radius_server_data;
+struct upnp_wps_device_sm;
#ifdef CONFIG_FULL_DYNAMIC_VLAN
struct full_dynamic_vlan;
@@ -171,6 +172,7 @@ struct hostapd_data {
u8 *wps_probe_resp_ie;
size_t wps_probe_resp_ie_len;
unsigned int ap_pin_failures;
+ struct upnp_wps_device_sm *wps_upnp;
#endif /* CONFIG_WPS */
};
diff --git a/hostapd/wps_hostapd.c b/hostapd/wps_hostapd.c
index e15c0cc..797cacc 100644
--- a/hostapd/wps_hostapd.c
+++ b/hostapd/wps_hostapd.c
@@ -20,12 +20,22 @@
#include "uuid.h"
#include "wpa_ctrl.h"
#include "ieee802_11_defs.h"
+#include "sta_info.h"
+#include "eapol_sm.h"
#include "wps/wps.h"
#include "wps/wps_defs.h"
#include "wps/wps_dev_attr.h"
#include "wps_hostapd.h"
+#ifdef CONFIG_WPS_UPNP
+#include "wps/wps_upnp.h"
+static int hostapd_wps_upnp_init(struct hostapd_data *hapd,
+ struct wps_context *wps);
+static void hostapd_wps_upnp_deinit(struct hostapd_data *hapd);
+#endif /* CONFIG_WPS_UPNP */
+
+
static int hostapd_wps_new_psk_cb(void *ctx, const u8 *mac_addr, const u8 *psk,
size_t psk_len)
{
@@ -619,6 +629,22 @@ int hostapd_init_wps(struct hostapd_data *hapd,
return -1;
}
+#ifdef CONFIG_WPS_UPNP
+ wps->friendly_name = hapd->conf->friendly_name;
+ wps->manufacturer_url = hapd->conf->manufacturer_url;
+ wps->model_description = hapd->conf->model_description;
+ wps->model_url = hapd->conf->model_url;
+ wps->upc = hapd->conf->upc;
+
+ if (hostapd_wps_upnp_init(hapd, wps) < 0) {
+ wpa_printf(MSG_ERROR, "Failed to initialize WPS UPnP");
+ wps_registrar_deinit(wps->registrar);
+ os_free(wps->network_key);
+ os_free(wps);
+ return -1;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
hapd->wps = wps;
return 0;
@@ -629,9 +655,13 @@ void hostapd_deinit_wps(struct hostapd_data *hapd)
{
if (hapd->wps == NULL)
return;
+#ifdef CONFIG_WPS_UPNP
+ hostapd_wps_upnp_deinit(hapd);
+#endif /* CONFIG_WPS_UPNP */
wps_registrar_deinit(hapd->wps->registrar);
os_free(hapd->wps->network_key);
wps_device_data_free(&hapd->wps->dev);
+ wpabuf_free(hapd->wps->upnp_msg);
os_free(hapd->wps);
hapd->wps = NULL;
hostapd_wps_clear_ies(hapd);
@@ -705,8 +735,244 @@ void hostapd_wps_probe_req_rx(struct hostapd_data *hapd, const u8 *addr,
pos += 2 + pos[1];
}
- if (wpabuf_len(wps_ie) > 0)
+ if (wpabuf_len(wps_ie) > 0) {
wps_registrar_probe_req_rx(hapd->wps->registrar, addr, wps_ie);
+#ifdef CONFIG_WPS_UPNP
+ /* FIX: what exactly should be included in the WLANEvent?
+ * WPS attributes? Full ProbeReq frame? */
+ upnp_wps_device_send_wlan_event(hapd->wps_upnp, addr,
+ UPNP_WPS_WLANEVENT_TYPE_PROBE,
+ wps_ie);
+#endif /* CONFIG_WPS_UPNP */
+ }
wpabuf_free(wps_ie);
}
+
+
+#ifdef CONFIG_WPS_UPNP
+
+static struct wpabuf *
+hostapd_rx_req_get_device_info(void *priv, struct upnp_wps_peer *peer)
+{
+ struct hostapd_data *hapd = priv;
+ struct wps_config cfg;
+ struct wps_data *wps;
+ enum wsc_op_code op_code;
+ struct wpabuf *m1;
+
+ /*
+ * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS
+ * registration over UPnP with the AP acting as an Enrollee. It should
+ * be noted that this is frequently used just to get the device data,
+ * i.e., there may not be any intent to actually complete the
+ * registration.
+ */
+
+ if (peer->wps)
+ wps_deinit(peer->wps);
+
+ os_memset(&cfg, 0, sizeof(cfg));
+ cfg.wps = hapd->wps;
+ cfg.pin = (u8 *) hapd->conf->ap_pin;
+ cfg.pin_len = os_strlen(hapd->conf->ap_pin);
+ wps = wps_init(&cfg);
+ if (wps == NULL)
+ return NULL;
+
+ m1 = wps_get_msg(wps, &op_code);
+ if (m1 == NULL) {
+ wps_deinit(wps);
+ return NULL;
+ }
+
+ peer->wps = wps;
+
+ return m1;
+}
+
+
+static struct wpabuf *
+hostapd_rx_req_put_message(void *priv, struct upnp_wps_peer *peer,
+ const struct wpabuf *msg)
+{
+ enum wps_process_res res;
+ enum wsc_op_code op_code;
+
+ /* PutMessage: msg = InMessage, return OutMessage */
+ res = wps_process_msg(peer->wps, WSC_UPnP, msg);
+ if (res == WPS_FAILURE)
+ return NULL;
+ return wps_get_msg(peer->wps, &op_code);
+}
+
+
+static struct wpabuf *
+hostapd_rx_req_get_ap_settings(void *priv, const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return NULL;
+}
+
+
+static int hostapd_rx_req_set_ap_settings(void *priv, const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return -1;
+}
+
+
+static int hostapd_rx_req_del_ap_settings(void *priv, const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return -1;
+}
+
+
+static struct wpabuf *
+hostapd_rx_req_get_sta_settings(void *priv, const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return NULL;
+}
+
+
+static int hostapd_rx_req_set_sta_settings(void *priv,
+ const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return -1;
+}
+
+
+static int hostapd_rx_req_del_sta_settings(void *priv,
+ const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return -1;
+}
+
+
+static int hostapd_rx_req_put_wlan_event_response(
+ void *priv, enum upnp_wps_wlanevent_type ev_type,
+ const u8 *mac_addr, const struct wpabuf *msg)
+{
+ struct hostapd_data *hapd = priv;
+ struct sta_info *sta;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse ev_type=%d mac_addr="
+ MACSTR, ev_type, MAC2STR(mac_addr));
+ wpa_hexdump_ascii(MSG_MSGDUMP, "WPS UPnP: PutWLANResponse NewMessage",
+ wpabuf_head(msg), wpabuf_len(msg));
+ if (ev_type != UPNP_WPS_WLANEVENT_TYPE_EAP) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored unexpected "
+ "PutWLANResponse WLANEventType %d", ev_type);
+ return -1;
+ }
+
+ /*
+ * EAP response to ongoing to WPS Registration. Send it to EAP-WSC
+ * server implementation for delivery to the peer.
+ */
+
+ /* TODO: support multiple pending UPnP messages */
+ os_memcpy(hapd->wps->upnp_msg_addr, mac_addr, ETH_ALEN);
+ wpabuf_free(hapd->wps->upnp_msg);
+ hapd->wps->upnp_msg = wpabuf_dup(msg);
+
+ sta = ap_get_sta(hapd, mac_addr);
+ if (sta)
+ return eapol_auth_eap_pending_cb(sta->eapol_sm,
+ hapd->wps->pending_session);
+
+ return 0;
+}
+
+
+static int hostapd_rx_req_set_selected_registrar(void *priv,
+ const struct wpabuf *msg)
+{
+ struct hostapd_data *hapd = priv;
+ return wps_registrar_set_selected_registrar(hapd->wps->registrar, msg);
+}
+
+
+static int hostapd_rx_req_reboot_ap(void *priv, const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return -1;
+}
+
+
+static int hostapd_rx_req_reset_ap(void *priv, const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return -1;
+}
+
+
+static int hostapd_rx_req_reboot_sta(void *priv, const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return -1;
+}
+
+
+static int hostapd_rx_req_reset_sta(void *priv, const struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: TODO %s", __func__);
+ return -1;
+}
+
+
+static int hostapd_wps_upnp_init(struct hostapd_data *hapd,
+ struct wps_context *wps)
+{
+ struct upnp_wps_device_ctx *ctx;
+
+ if (!hapd->conf->upnp_iface)
+ return 0;
+ ctx = os_zalloc(sizeof(*ctx));
+ if (ctx == NULL)
+ return -1;
+
+ ctx->rx_req_get_device_info = hostapd_rx_req_get_device_info;
+ ctx->rx_req_put_message = hostapd_rx_req_put_message;
+ ctx->rx_req_get_ap_settings = hostapd_rx_req_get_ap_settings;
+ ctx->rx_req_set_ap_settings = hostapd_rx_req_set_ap_settings;
+ ctx->rx_req_del_ap_settings = hostapd_rx_req_del_ap_settings;
+ ctx->rx_req_get_sta_settings = hostapd_rx_req_get_sta_settings;
+ ctx->rx_req_set_sta_settings = hostapd_rx_req_set_sta_settings;
+ ctx->rx_req_del_sta_settings = hostapd_rx_req_del_sta_settings;
+ ctx->rx_req_put_wlan_event_response =
+ hostapd_rx_req_put_wlan_event_response;
+ ctx->rx_req_set_selected_registrar =
+ hostapd_rx_req_set_selected_registrar;
+ ctx->rx_req_reboot_ap = hostapd_rx_req_reboot_ap;
+ ctx->rx_req_reset_ap = hostapd_rx_req_reset_ap;
+ ctx->rx_req_reboot_sta = hostapd_rx_req_reboot_sta;
+ ctx->rx_req_reset_sta = hostapd_rx_req_reset_sta;
+
+ hapd->wps_upnp = upnp_wps_device_init(ctx, wps, hapd);
+ if (hapd->wps_upnp == NULL) {
+ os_free(ctx);
+ return -1;
+ }
+ wps->wps_upnp = hapd->wps_upnp;
+
+ if (upnp_wps_device_start(hapd->wps_upnp, hapd->conf->upnp_iface)) {
+ upnp_wps_device_deinit(hapd->wps_upnp);
+ hapd->wps_upnp = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void hostapd_wps_upnp_deinit(struct hostapd_data *hapd)
+{
+ upnp_wps_device_deinit(hapd->wps_upnp);
+}
+
+#endif /* CONFIG_WPS_UPNP */
diff --git a/src/eap_peer/eap_wsc.c b/src/eap_peer/eap_wsc.c
index 35c9cce..17e42f4 100644
--- a/src/eap_peer/eap_wsc.c
+++ b/src/eap_peer/eap_wsc.c
@@ -405,6 +405,7 @@ static struct wpabuf * eap_wsc_process(struct eap_sm *sm, void *priv,
eap_wsc_state(data, MESG);
break;
case WPS_FAILURE:
+ case WPS_PENDING:
wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing failed");
eap_wsc_state(data, FAIL);
break;
diff --git a/src/eap_server/eap_wsc.c b/src/eap_server/eap_wsc.c
index c22c544..097e8c4 100644
--- a/src/eap_server/eap_wsc.c
+++ b/src/eap_server/eap_wsc.c
@@ -15,6 +15,7 @@
#include "includes.h"
#include "common.h"
+#include "eloop.h"
#include "eap_i.h"
#include "eap_common/eap_wsc_common.h"
#include "wps/wps.h"
@@ -29,6 +30,7 @@ struct eap_wsc_data {
size_t out_used;
size_t fragment_size;
struct wps_data *wps;
+ int ext_reg_timeout;
};
@@ -62,6 +64,21 @@ static void eap_wsc_state(struct eap_wsc_data *data, int state)
}
+static void eap_wsc_ext_reg_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+ struct eap_sm *sm = eloop_ctx;
+ struct eap_wsc_data *data = timeout_ctx;
+
+ if (sm->method_pending != METHOD_PENDING_WAIT)
+ return;
+
+ wpa_printf(MSG_DEBUG, "EAP-WSC: Timeout while waiting for an External "
+ "Registrar");
+ data->ext_reg_timeout = 1;
+ eap_sm_pending_cb(sm);
+}
+
+
static void * eap_wsc_init(struct eap_sm *sm)
{
struct eap_wsc_data *data;
@@ -123,6 +140,7 @@ static void * eap_wsc_init(struct eap_sm *sm)
static void eap_wsc_reset(struct eap_sm *sm, void *priv)
{
struct eap_wsc_data *data = priv;
+ eloop_cancel_timeout(eap_wsc_ext_reg_timeout, sm, data);
wpabuf_free(data->in_buf);
wpabuf_free(data->out_buf);
wps_deinit(data->wps);
@@ -324,6 +342,12 @@ static void eap_wsc_process(struct eap_sm *sm, void *priv,
enum wps_process_res res;
struct wpabuf tmpbuf;
+ eloop_cancel_timeout(eap_wsc_ext_reg_timeout, sm, data);
+ if (data->ext_reg_timeout) {
+ eap_wsc_state(data, FAIL);
+ return;
+ }
+
pos = eap_hdr_validate(EAP_VENDOR_WFA, EAP_VENDOR_TYPE_WSC,
respData, &len);
if (pos == NULL || len < 2)
@@ -409,6 +433,14 @@ static void eap_wsc_process(struct eap_sm *sm, void *priv,
wpa_printf(MSG_DEBUG, "EAP-WSC: WPS processing failed");
eap_wsc_state(data, FAIL);
break;
+ case WPS_PENDING:
+ eap_wsc_state(data, MSG);
+ sm->method_pending = METHOD_PENDING_WAIT;
+ sm->wps->pending_session = sm;
+ eloop_cancel_timeout(eap_wsc_ext_reg_timeout, sm, data);
+ eloop_register_timeout(5, 0, eap_wsc_ext_reg_timeout,
+ sm, data);
+ break;
}
if (data->in_buf != &tmpbuf)
diff --git a/src/utils/wpabuf.c b/src/utils/wpabuf.c
index e809690..c544179 100644
--- a/src/utils/wpabuf.c
+++ b/src/utils/wpabuf.c
@@ -1,6 +1,6 @@
/*
* Dynamic data buffer
- * Copyright (c) 2007-2008, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2007-2009, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -195,3 +195,18 @@ struct wpabuf * wpabuf_zeropad(struct wpabuf *buf, size_t len)
return ret;
}
+
+
+void wpabuf_printf(struct wpabuf *buf, char *fmt, ...)
+{
+ va_list ap;
+ void *tmp = wpabuf_mhead_u8(buf) + wpabuf_len(buf);
+ int res;
+
+ va_start(ap, fmt);
+ res = vsnprintf(tmp, buf->size - buf->used, fmt, ap);
+ va_end(ap);
+ if (res < 0 || (size_t) res >= buf->size - buf->used)
+ wpabuf_overflow(buf, res);
+ buf->used += res;
+}
diff --git a/src/utils/wpabuf.h b/src/utils/wpabuf.h
index 5d435ab..bd8f09e 100644
--- a/src/utils/wpabuf.h
+++ b/src/utils/wpabuf.h
@@ -1,6 +1,6 @@
/*
* Dynamic data buffer
- * Copyright (c) 2007, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2007-2009, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -38,6 +38,7 @@ void wpabuf_free(struct wpabuf *buf);
void * wpabuf_put(struct wpabuf *buf, size_t len);
struct wpabuf * wpabuf_concat(struct wpabuf *a, struct wpabuf *b);
struct wpabuf * wpabuf_zeropad(struct wpabuf *buf, size_t len);
+void wpabuf_printf(struct wpabuf *buf, char *fmt, ...) PRINTF_FORMAT(2, 3);
/**
@@ -147,4 +148,9 @@ static inline void wpabuf_set(struct wpabuf *buf, const void *data, size_t len)
buf->size = buf->used = len;
}
+static inline void wpabuf_put_str(struct wpabuf *dst, const char *str)
+{
+ wpabuf_put_data(dst, str, os_strlen(str));
+}
+
#endif /* WPABUF_H */
diff --git a/src/wps/httpread.c b/src/wps/httpread.c
new file mode 100644
index 0000000..313b468
--- /dev/null
+++ b/src/wps/httpread.c
@@ -0,0 +1,858 @@
+/**
+ * httpread - Manage reading file(s) from HTTP/TCP socket
+ * Author: Ted Merrill
+ * Copyright 2008 Atheros Communications
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ *
+ * The files are buffered via internal callbacks from eloop, then presented to
+ * an application callback routine when completely read into memory. May also
+ * be used if no file is expected but just to get the header, including HTTP
+ * replies (e.g. HTTP/1.1 200 OK etc.).
+ *
+ * This does not attempt to be an optimally efficient implementation, but does
+ * attempt to be of reasonably small size and memory consumption; assuming that
+ * only small files are to be read. A maximum file size is provided by
+ * application and enforced.
+ *
+ * It is assumed that the application does not expect any of the following:
+ * -- transfer encoding other than chunked
+ * -- trailer fields
+ * It is assumed that, even if the other side requested that the connection be
+ * kept open, that we will close it (thus HTTP messages sent by application
+ * should have the connection closed field); this is allowed by HTTP/1.1 and
+ * simplifies things for us.
+ *
+ * Other limitations:
+ * -- HTTP header may not exceed a hard-coded size.
+ *
+ * Notes:
+ * This code would be massively simpler without some of the new features of
+ * HTTP/1.1, especially chunked data.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "eloop.h"
+#include "httpread.h"
+
+
+/* Tunable parameters */
+#define HTTPREAD_READBUF_SIZE 1024 /* read in chunks of this size */
+#define HTTPREAD_HEADER_MAX_SIZE 4096 /* max allowed for headers */
+#define HTTPREAD_BODYBUF_DELTA 4096 /* increase allocation by this */
+
+#if 0
+/* httpread_debug -- set this global variable > 0 e.g. from debugger
+ * to enable debugs (larger numbers for more debugs)
+ * Make this a #define of 0 to eliminate the debugging code.
+ */
+int httpread_debug = 99;
+#else
+#define httpread_debug 0 /* eliminates even the debugging code */
+#endif
+
+
+/* control instance -- actual definition (opaque to application)
+ */
+struct httpread {
+ /* information from creation */
+ int sd; /* descriptor of TCP socket to read from */
+ void (*cb)(struct httpread *handle, void *cookie,
+ enum httpread_event e); /* call on event */
+ void *cookie; /* pass to callback */
+ int max_bytes; /* maximum file size else abort it */
+ int timeout_seconds; /* 0 or total duration timeout period */
+
+ /* dynamically used information follows */
+ int sd_registered; /* nonzero if we need to unregister socket */
+ int to_registered; /* nonzero if we need to unregister timeout */
+
+ int got_hdr; /* nonzero when header is finalized */
+ char hdr[HTTPREAD_HEADER_MAX_SIZE+1]; /* headers stored here */
+ int hdr_nbytes;
+
+ enum httpread_hdr_type hdr_type;
+ int version; /* 1 if we've seen 1.1 */
+ int reply_code; /* for type REPLY, e.g. 200 for HTTP/1.1 200 OK */
+ int got_content_length; /* true if we know content length for sure */
+ int content_length; /* body length, iff got_content_length */
+ int chunked; /* nonzero for chunked data */
+ char *uri;
+
+ int got_body; /* nonzero when body is finalized */
+ char *body;
+ int body_nbytes;
+ int body_alloc_nbytes; /* amount allocated */
+
+ int got_file; /* here when we are done */
+
+ /* The following apply if data is chunked: */
+ int in_chunk_data; /* 0=in/at header, 1=in the data or tail*/
+ int chunk_start; /* offset in body of chunk hdr or data */
+ int chunk_size; /* data of chunk (not hdr or ending CRLF)*/
+ int in_trailer; /* in header fields after data (chunked only)*/
+ enum trailer_state {
+ trailer_line_begin = 0,
+ trailer_empty_cr, /* empty line + CR */
+ trailer_nonempty,
+ trailer_nonempty_cr,
+ } trailer_state;
+};
+
+
+/* Check words for equality, where words consist of graphical characters
+ * delimited by whitespace
+ * Returns nonzero if "equal" doing case insensitive comparison.
+ */
+static int word_eq(char *s1, char *s2)
+{
+ int c1;
+ int c2;
+ int end1 = 0;
+ int end2 = 0;
+ for (;;) {
+ c1 = *s1++;
+ c2 = *s2++;
+ if (isalpha(c1) && isupper(c1))
+ c1 = tolower(c1);
+ if (isalpha(c2) && isupper(c2))
+ c2 = tolower(c2);
+ end1 = !isgraph(c1);
+ end2 = !isgraph(c2);
+ if (end1 || end2 || c1 != c2)
+ break;
+ }
+ return end1 && end2; /* reached end of both words? */
+}
+
+
+/* convert hex to binary
+ * Requires that c have been previously tested true with isxdigit().
+ */
+static int hex_value(int c)
+{
+ if (isdigit(c))
+ return c - '0';
+ if (islower(c))
+ return 10 + c - 'a';
+ return 10 + c - 'A';
+}
+
+
+static void httpread_timeout_handler(void *eloop_data, void *user_ctx);
+
+/* httpread_destroy -- if h is non-NULL, clean up
+ * This must eventually be called by the application following
+ * call of the application's callback and may be called
+ * earlier if desired.
+ */
+void httpread_destroy(struct httpread *h)
+{
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG, "ENTER httpread_destroy(%p)", h);
+ if (!h)
+ return;
+
+ if (h->to_registered)
+ eloop_cancel_timeout(httpread_timeout_handler, NULL, h);
+ h->to_registered = 0;
+ if (h->sd_registered)
+ eloop_unregister_sock(h->sd, EVENT_TYPE_READ);
+ h->sd_registered = 0;
+ os_free(h->body);
+ os_free(h->uri);
+ os_memset(h, 0, sizeof(*h)); /* aid debugging */
+ h->sd = -1; /* aid debugging */
+ os_free(h);
+}
+
+
+/* httpread_timeout_handler -- called on excessive total duration
+ */
+static void httpread_timeout_handler(void *eloop_data, void *user_ctx)
+{
+ struct httpread *h = user_ctx;
+ wpa_printf(MSG_DEBUG, "httpread timeout (%p)", h);
+ h->to_registered = 0; /* is self-cancelling */
+ (*h->cb)(h, h->cookie, HTTPREAD_EVENT_TIMEOUT);
+}
+
+
+/* Analyze options only so far as is needed to correctly obtain the file.
+ * The application can look at the raw header to find other options.
+ */
+static int httpread_hdr_option_analyze(
+ struct httpread *h,
+ char *hbp /* pointer to current line in header buffer */
+ )
+{
+ if (word_eq(hbp, "CONTENT-LENGTH:")) {
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ if (!isdigit(*hbp))
+ return -1;
+ h->content_length = atol(hbp);
+ h->got_content_length = 1;
+ return 0;
+ }
+ if (word_eq(hbp, "TRANSFER_ENCODING:")) {
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ /* There should (?) be no encodings of interest
+ * other than chunked...
+ */
+ if (os_strncmp(hbp, "CHUNKED", 7)) {
+ h->chunked = 1;
+ h->in_chunk_data = 0;
+ /* ignore possible ;<parameters> */
+ }
+ return 0;
+ }
+ /* skip anything we don't know, which is a lot */
+ return 0;
+}
+
+
+static int httpread_hdr_analyze(struct httpread *h)
+{
+ char *hbp = h->hdr; /* pointer into h->hdr */
+ int standard_first_line = 1;
+
+ /* First line is special */
+ h->hdr_type = HTTPREAD_HDR_TYPE_UNKNOWN;
+ if (!isgraph(*hbp))
+ goto bad;
+ if (os_strncmp(hbp, "HTTP/", 5) == 0) {
+ h->hdr_type = HTTPREAD_HDR_TYPE_REPLY;
+ standard_first_line = 0;
+ hbp += 5;
+ if (hbp[0] == '1' && hbp[1] == '.' &&
+ isdigit(hbp[2]) && hbp[2] != '0')
+ h->version = 1;
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ if (!isdigit(*hbp))
+ goto bad;
+ h->reply_code = atol(hbp);
+ } else if (word_eq(hbp, "GET"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_GET;
+ else if (word_eq(hbp, "HEAD"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_HEAD;
+ else if (word_eq(hbp, "POST"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_POST;
+ else if (word_eq(hbp, "PUT"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_PUT;
+ else if (word_eq(hbp, "DELETE"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_DELETE;
+ else if (word_eq(hbp, "TRACE"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_TRACE;
+ else if (word_eq(hbp, "CONNECT"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_CONNECT;
+ else if (word_eq(hbp, "NOTIFY"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_NOTIFY;
+ else if (word_eq(hbp, "M-SEARCH"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_M_SEARCH;
+ else if (word_eq(hbp, "M-POST"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_M_POST;
+ else if (word_eq(hbp, "SUBSCRIBE"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_SUBSCRIBE;
+ else if (word_eq(hbp, "UNSUBSCRIBE"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_UNSUBSCRIBE;
+ else {
+ }
+
+ if (standard_first_line) {
+ char *rawuri;
+ char *uri;
+ /* skip type */
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ /* parse uri.
+ * Find length, allocate memory for translated
+ * copy, then translate by changing %<hex><hex>
+ * into represented value.
+ */
+ rawuri = hbp;
+ while (isgraph(*hbp))
+ hbp++;
+ h->uri = os_malloc((hbp - rawuri) + 1);
+ if (h->uri == NULL)
+ goto bad;
+ uri = h->uri;
+ while (rawuri < hbp) {
+ int c = *rawuri;
+ if (c == '%' &&
+ isxdigit(rawuri[1]) && isxdigit(rawuri[2])) {
+ *uri++ = (hex_value(rawuri[1]) << 4) |
+ hex_value(rawuri[2]);
+ rawuri += 3;
+ } else {
+ *uri++ = c;
+ rawuri++;
+ }
+ }
+ *uri = 0; /* null terminate */
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ /* get version */
+ if (0 == strncmp(hbp, "HTTP/", 5)) {
+ hbp += 5;
+ if (hbp[0] == '1' && hbp[1] == '.' &&
+ isdigit(hbp[2]) && hbp[2] != '0')
+ h->version = 1;
+ }
+ }
+ /* skip rest of line */
+ while (*hbp)
+ if (*hbp++ == '\n')
+ break;
+
+ /* Remainder of lines are options, in any order;
+ * or empty line to terminate
+ */
+ for (;;) {
+ /* Empty line to terminate */
+ if (hbp[0] == '\n' ||
+ (hbp[0] == '\r' && hbp[1] == '\n'))
+ break;
+ if (!isgraph(*hbp))
+ goto bad;
+ if (httpread_hdr_option_analyze(h, hbp))
+ goto bad;
+ /* skip line */
+ while (*hbp)
+ if (*hbp++ == '\n')
+ break;
+ }
+
+ /* chunked overrides content-length always */
+ if (h->chunked)
+ h->got_content_length = 0;
+
+ /* For some types, we should not try to read a body
+ * This is in addition to the application determining
+ * that we should not read a body.
+ */
+ switch (h->hdr_type) {
+ case HTTPREAD_HDR_TYPE_REPLY:
+ /* Some codes can have a body and some not.
+ * For now, just assume that any other than 200
+ * do not...
+ */
+ if (h->reply_code != 200)
+ h->max_bytes = 0;
+ break;
+ case HTTPREAD_HDR_TYPE_GET:
+ case HTTPREAD_HDR_TYPE_HEAD:
+ /* in practice it appears that it is assumed
+ * that GETs have a body length of 0... ?
+ */
+ if (h->chunked == 0 && h->got_content_length == 0)
+ h->max_bytes = 0;
+ break;
+ case HTTPREAD_HDR_TYPE_POST:
+ case HTTPREAD_HDR_TYPE_PUT:
+ case HTTPREAD_HDR_TYPE_DELETE:
+ case HTTPREAD_HDR_TYPE_TRACE:
+ case HTTPREAD_HDR_TYPE_CONNECT:
+ case HTTPREAD_HDR_TYPE_NOTIFY:
+ case HTTPREAD_HDR_TYPE_M_SEARCH:
+ case HTTPREAD_HDR_TYPE_M_POST:
+ case HTTPREAD_HDR_TYPE_SUBSCRIBE:
+ case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
+ default:
+ break;
+ }
+
+ return 0;
+
+bad:
+ /* Error */
+ return -1;
+}
+
+
+/* httpread_read_handler -- called when socket ready to read
+ *
+ * Note: any extra data we read past end of transmitted file is ignored;
+ * if we were to support keeping connections open for multiple files then
+ * this would have to be addressed.
+ */
+static void httpread_read_handler(int sd, void *eloop_ctx, void *sock_ctx)
+{
+ struct httpread *h = sock_ctx;
+ int nread;
+ char *rbp; /* pointer into read buffer */
+ char *hbp; /* pointer into header buffer */
+ char *bbp; /* pointer into body buffer */
+ char readbuf[HTTPREAD_READBUF_SIZE]; /* temp use to read into */
+
+ if (httpread_debug >= 20)
+ wpa_printf(MSG_DEBUG, "ENTER httpread_read_handler(%p)", h);
+
+ /* read some at a time, then search for the interal
+ * boundaries between header and data and etc.
+ */
+ nread = read(h->sd, readbuf, sizeof(readbuf));
+ if (nread < 0)
+ goto bad;
+ if (nread == 0) {
+ /* end of transmission... this may be normal
+ * or may be an error... in some cases we can't
+ * tell which so we must assume it is normal then.
+ */
+ if (!h->got_hdr) {
+ /* Must at least have completed header */
+ wpa_printf(MSG_DEBUG, "httpread premature eof(%p)", h);
+ goto bad;
+ }
+ if (h->chunked || h->got_content_length) {
+ /* Premature EOF; e.g. dropped connection */
+ wpa_printf(MSG_DEBUG,
+ "httpread premature eof(%p) %d/%d",
+ h, h->body_nbytes,
+ h->content_length);
+ goto bad;
+ }
+ /* No explicit length, hopefully we have all the data
+ * although dropped connections can cause false
+ * end
+ */
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG, "httpread ok eof(%p)", h);
+ h->got_body = 1;
+ goto got_file;
+ }
+ rbp = readbuf;
+
+ /* Header consists of text lines (terminated by both CR and LF)
+ * and an empty line (CR LF only).
+ */
+ if (!h->got_hdr) {
+ hbp = h->hdr + h->hdr_nbytes;
+ /* add to headers until:
+ * -- we run out of data in read buffer
+ * -- or, we run out of header buffer room
+ * -- or, we get double CRLF in headers
+ */
+ for (;;) {
+ if (nread == 0)
+ goto get_more;
+ if (h->hdr_nbytes == HTTPREAD_HEADER_MAX_SIZE) {
+ goto bad;
+ }
+ *hbp++ = *rbp++;
+ nread--;
+ h->hdr_nbytes++;
+ if (h->hdr_nbytes >= 4 &&
+ hbp[-1] == '\n' &&
+ hbp[-2] == '\r' &&
+ hbp[-3] == '\n' &&
+ hbp[-4] == '\r' ) {
+ h->got_hdr = 1;
+ *hbp = 0; /* null terminate */
+ break;
+ }
+ }
+ /* here we've just finished reading the header */
+ if (httpread_hdr_analyze(h)) {
+ wpa_printf(MSG_DEBUG, "httpread bad hdr(%p)", h);
+ goto bad;
+ }
+ if (h->max_bytes == 0) {
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG,
+ "httpread no body hdr end(%p)", h);
+ goto got_file;
+ }
+ if (h->got_content_length && h->content_length == 0) {
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG,
+ "httpread zero content length(%p)",
+ h);
+ goto got_file;
+ }
+ }
+
+ /* Certain types of requests never have data and so
+ * must be specially recognized.
+ */
+ if (!os_strncasecmp(h->hdr, "SUBSCRIBE", 9) ||
+ !os_strncasecmp(h->hdr, "UNSUBSCRIBE", 11) ||
+ !os_strncasecmp(h->hdr, "HEAD", 4) ||
+ !os_strncasecmp(h->hdr, "GET", 3)) {
+ if (!h->got_body) {
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG,
+ "httpread NO BODY for sp. type");
+ }
+ h->got_body = 1;
+ goto got_file;
+ }
+
+ /* Data can be just plain binary data, or if "chunked"
+ * consists of chunks each with a header, ending with
+ * an ending header.
+ */
+ if (!h->got_body) {
+ /* Here to get (more of) body */
+ /* ensure we have enough room for worst case for body
+ * plus a null termination character
+ */
+ if (h->body_alloc_nbytes < (h->body_nbytes + nread + 1)) {
+ char *new_body;
+ int new_alloc_nbytes;
+
+ if (h->body_nbytes >= h->max_bytes)
+ goto bad;
+ new_alloc_nbytes = h->body_alloc_nbytes +
+ HTTPREAD_BODYBUF_DELTA;
+ /* For content-length case, the first time
+ * through we allocate the whole amount
+ * we need.
+ */
+ if (h->got_content_length &&
+ new_alloc_nbytes < (h->content_length + 1))
+ new_alloc_nbytes = h->content_length + 1;
+ if ((new_body = os_realloc(h->body, new_alloc_nbytes))
+ == NULL)
+ goto bad;
+
+ h->body = new_body;
+ h->body_alloc_nbytes = new_alloc_nbytes;
+ }
+ /* add bytes */
+ bbp = h->body + h->body_nbytes;
+ for (;;) {
+ int ncopy;
+ /* See if we need to stop */
+ if (h->chunked && h->in_chunk_data == 0) {
+ /* in chunk header */
+ char *cbp = h->body + h->chunk_start;
+ if (bbp-cbp >= 2 && bbp[-2] == '\r' &&
+ bbp[-1] == '\n') {
+ /* end of chunk hdr line */
+ /* hdr line consists solely
+ * of a hex numeral and CFLF
+ */
+ if (!isxdigit(*cbp))
+ goto bad;
+ h->chunk_size = strtoul(cbp, NULL, 16);
+ /* throw away chunk header
+ * so we have only real data
+ */
+ h->body_nbytes = h->chunk_start;
+ bbp = cbp;
+ if (h->chunk_size == 0) {
+ /* end of chunking */
+ /* trailer follows */
+ h->in_trailer = 1;
+ if (httpread_debug >= 20)
+ wpa_printf(
+ MSG_DEBUG,
+ "httpread end chunks(%p)", h);
+ break;
+ }
+ h->in_chunk_data = 1;
+ /* leave chunk_start alone */
+ }
+ } else if (h->chunked) {
+ /* in chunk data */
+ if ((h->body_nbytes - h->chunk_start) ==
+ (h->chunk_size + 2)) {
+ /* end of chunk reached,
+ * new chunk starts
+ */
+ /* check chunk ended w/ CRLF
+ * which we'll throw away
+ */
+ if (bbp[-1] == '\n' &&
+ bbp[-2] == '\r') {
+ } else
+ goto bad;
+ h->body_nbytes -= 2;
+ bbp -= 2;
+ h->chunk_start = h->body_nbytes;
+ h->in_chunk_data = 0;
+ h->chunk_size = 0; /* just in case */
+ }
+ } else if (h->got_content_length &&
+ h->body_nbytes >= h->content_length) {
+ h->got_body = 1;
+ if (httpread_debug >= 10)
+ wpa_printf(
+ MSG_DEBUG,
+ "httpread got content(%p)", h);
+ goto got_file;
+ }
+ if (nread <= 0)
+ break;
+ /* Now transfer. Optimize using memcpy where we can. */
+ if (h->chunked && h->in_chunk_data) {
+ /* copy up to remainder of chunk data
+ * plus the required CR+LF at end
+ */
+ ncopy = (h->chunk_start + h->chunk_size + 2) -
+ h->body_nbytes;
+ } else if (h->chunked) {
+ /*in chunk header -- don't optimize */
+ *bbp++ = *rbp++;
+ nread--;
+ h->body_nbytes++;
+ continue;
+ } else if (h->got_content_length) {
+ ncopy = h->content_length - h->body_nbytes;
+ } else {
+ ncopy = nread;
+ }
+ /* Note: should never be 0 */
+ if (ncopy > nread)
+ ncopy = nread;
+ os_memcpy(bbp, rbp, ncopy);
+ bbp += ncopy;
+ h->body_nbytes += ncopy;
+ rbp += ncopy;
+ nread -= ncopy;
+ } /* body copy loop */
+ } /* !got_body */
+ if (h->chunked && h->in_trailer) {
+ /* If "chunked" then there is always a trailer,
+ * consisting of zero or more non-empty lines
+ * ending with CR LF and then an empty line w/ CR LF.
+ * We do NOT support trailers except to skip them --
+ * this is supported (generally) by the http spec.
+ */
+ bbp = h->body + h->body_nbytes;
+ for (;;) {
+ int c;
+ if (nread <= 0)
+ break;
+ c = *rbp++;
+ nread--;
+ switch (h->trailer_state) {
+ case trailer_line_begin:
+ if (c == '\r')
+ h->trailer_state = trailer_empty_cr;
+ else
+ h->trailer_state = trailer_nonempty;
+ break;
+ case trailer_empty_cr:
+ /* end empty line */
+ if (c == '\n') {
+ h->trailer_state = trailer_line_begin;
+ h->in_trailer = 0;
+ if (httpread_debug >= 10)
+ wpa_printf(
+ MSG_DEBUG,
+ "httpread got content(%p)", h);
+ h->got_body = 1;
+ goto got_file;
+ }
+ h->trailer_state = trailer_nonempty;
+ break;
+ case trailer_nonempty:
+ if (c == '\r')
+ h->trailer_state = trailer_nonempty_cr;
+ break;
+ case trailer_nonempty_cr:
+ if (c == '\n')
+ h->trailer_state = trailer_line_begin;
+ else
+ h->trailer_state = trailer_nonempty;
+ break;
+ }
+ }
+ }
+ goto get_more;
+
+bad:
+ /* Error */
+ wpa_printf(MSG_DEBUG, "httpread read/parse failure (%p)", h);
+ (*h->cb)(h, h->cookie, HTTPREAD_EVENT_ERROR);
+ return;
+
+get_more:
+ return;
+
+got_file:
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG,
+ "httpread got file %d bytes type %d",
+ h->body_nbytes, h->hdr_type);
+ /* Null terminate for convenience of some applications */
+ if (h->body)
+ h->body[h->body_nbytes] = 0; /* null terminate */
+ h->got_file = 1;
+ /* Assume that we do NOT support keeping connection alive,
+ * and just in case somehow we don't get destroyed right away,
+ * unregister now.
+ */
+ if (h->sd_registered)
+ eloop_unregister_sock(h->sd, EVENT_TYPE_READ);
+ h->sd_registered = 0;
+ /* The application can destroy us whenever they feel like...
+ * cancel timeout.
+ */
+ if (h->to_registered)
+ eloop_cancel_timeout(httpread_timeout_handler, NULL, h);
+ h->to_registered = 0;
+ (*h->cb)(h, h->cookie, HTTPREAD_EVENT_FILE_READY);
+}
+
+
+/* httpread_create -- start a new reading session making use of eloop.
+ * The new instance will use the socket descriptor for reading (until
+ * it gets a file and not after) but will not close the socket, even
+ * when the instance is destroyed (the application must do that).
+ * Return NULL on error.
+ *
+ * Provided that httpread_create successfully returns a handle,
+ * the callback fnc is called to handle httpread_event events.
+ * The caller should do destroy on any errors or unknown events.
+ *
+ * Pass max_bytes == 0 to not read body at all (required for e.g.
+ * reply to HEAD request).
+ */
+struct httpread * httpread_create(
+ int sd, /* descriptor of TCP socket to read from */
+ void (*cb)(struct httpread *handle, void *cookie,
+ enum httpread_event e), /* call on event */
+ void *cookie, /* pass to callback */
+ int max_bytes, /* maximum body size else abort it */
+ int timeout_seconds /* 0; or total duration timeout period */
+ )
+{
+ struct httpread *h = NULL;
+
+ h = os_zalloc(sizeof(*h));
+ if (h == NULL)
+ goto fail;
+ h->sd = sd;
+ h->cb = cb;
+ h->cookie = cookie;
+ h->max_bytes = max_bytes;
+ h->timeout_seconds = timeout_seconds;
+
+ if (timeout_seconds > 0) {
+ if (eloop_register_timeout(timeout_seconds, 0,
+ httpread_timeout_handler,
+ NULL, h)) {
+ /* No way to recover (from malloc failure) */
+ goto fail;
+ }
+ h->to_registered = 1;
+ }
+ if (eloop_register_sock(sd, EVENT_TYPE_READ, httpread_read_handler,
+ NULL, h)) {
+ /* No way to recover (from malloc failure) */
+ goto fail;
+ }
+ h->sd_registered = 1;
+ return h;
+
+fail:
+
+ /* Error */
+ httpread_destroy(h);
+ return NULL;
+}
+
+
+/* httpread_hdr_type_get -- When file is ready, returns header type. */
+enum httpread_hdr_type httpread_hdr_type_get(struct httpread *h)
+{
+ return h->hdr_type;
+}
+
+
+/* httpread_uri_get -- When file is ready, uri_get returns (translated) URI
+ * or possibly NULL (which would be an error).
+ */
+char * httpread_uri_get(struct httpread *h)
+{
+ return h->uri;
+}
+
+
+/* httpread_reply_code_get -- When reply is ready, returns reply code */
+int httpread_reply_code_get(struct httpread *h)
+{
+ return h->reply_code;
+}
+
+
+/* httpread_length_get -- When file is ready, returns file length. */
+int httpread_length_get(struct httpread *h)
+{
+ return h->body_nbytes;
+}
+
+
+/* httpread_data_get -- When file is ready, returns file content
+ * with null byte appened.
+ * Might return NULL in some error condition.
+ */
+void * httpread_data_get(struct httpread *h)
+{
+ return h->body ? h->body : "";
+}
+
+
+/* httpread_hdr_get -- When file is ready, returns header content
+ * with null byte appended.
+ * Might return NULL in some error condition.
+ */
+char * httpread_hdr_get(struct httpread *h)
+{
+ return h->hdr;
+}
+
+
+/* httpread_hdr_line_get -- When file is ready, returns pointer
+ * to line within header content matching the given tag
+ * (after the tag itself and any spaces/tabs).
+ *
+ * The tag should end with a colon for reliable matching.
+ *
+ * If not found, returns NULL;
+ */
+char * httpread_hdr_line_get(struct httpread *h, const char *tag)
+{
+ int tag_len = os_strlen(tag);
+ char *hdr = h->hdr;
+ hdr = os_strchr(hdr, '\n');
+ if (hdr == NULL)
+ return NULL;
+ hdr++;
+ for (;;) {
+ if (!os_strncasecmp(hdr, tag, tag_len)) {
+ hdr += tag_len;
+ while (*hdr == ' ' || *hdr == '\t')
+ hdr++;
+ return hdr;
+ }
+ hdr = os_strchr(hdr, '\n');
+ if (hdr == NULL)
+ return NULL;
+ hdr++;
+ }
+}
diff --git a/src/wps/httpread.h b/src/wps/httpread.h
new file mode 100644
index 0000000..fb1ecb7
--- /dev/null
+++ b/src/wps/httpread.h
@@ -0,0 +1,123 @@
+/**
+ * httpread - Manage reading file(s) from HTTP/TCP socket
+ * Author: Ted Merrill
+ * Copyright 2008 Atheros Communications
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef HTTPREAD_H
+#define HTTPREAD_H
+
+/* event types (passed to callback) */
+enum httpread_event {
+ HTTPREAD_EVENT_FILE_READY = 1, /* including reply ready */
+ HTTPREAD_EVENT_TIMEOUT = 2,
+ HTTPREAD_EVENT_ERROR = 3 /* misc. error, esp malloc error */
+};
+
+
+/* header type detected
+ * available to callback via call to httpread_reply_code_get()
+ */
+enum httpread_hdr_type {
+ HTTPREAD_HDR_TYPE_UNKNOWN = 0, /* none of the following */
+ HTTPREAD_HDR_TYPE_REPLY = 1, /* hdr begins w/ HTTP/ */
+ HTTPREAD_HDR_TYPE_GET = 2, /* hdr begins with GET<sp> */
+ HTTPREAD_HDR_TYPE_HEAD = 3, /* hdr begins with HEAD<sp> */
+ HTTPREAD_HDR_TYPE_POST = 4, /* hdr begins with POST<sp> */
+ HTTPREAD_HDR_TYPE_PUT = 5, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_DELETE = 6, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_TRACE = 7, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_CONNECT = 8, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_NOTIFY = 9, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_M_SEARCH = 10, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_M_POST = 11, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_SUBSCRIBE = 12, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_UNSUBSCRIBE = 13, /* hdr begins with ... */
+
+ HTTPREAD_N_HDR_TYPES /* keep last */
+};
+
+
+/* control instance -- opaque struct declaration
+ */
+struct httpread;
+
+
+/* httpread_destroy -- if h is non-NULL, clean up
+ * This must eventually be called by the application following
+ * call of the application's callback and may be called
+ * earlier if desired.
+ */
+void httpread_destroy(struct httpread *h);
+
+/* httpread_create -- start a new reading session making use of eloop.
+ * The new instance will use the socket descriptor for reading (until
+ * it gets a file and not after) but will not close the socket, even
+ * when the instance is destroyed (the application must do that).
+ * Return NULL on error.
+ *
+ * Provided that httpread_create successfully returns a handle,
+ * the callback fnc is called to handle httpread_event events.
+ * The caller should do destroy on any errors or unknown events.
+ *
+ * Pass max_bytes == 0 to not read body at all (required for e.g.
+ * reply to HEAD request).
+ */
+struct httpread * httpread_create(
+ int sd, /* descriptor of TCP socket to read from */
+ void (*cb)(struct httpread *handle, void *cookie,
+ enum httpread_event e), /* call on event */
+ void *cookie, /* pass to callback */
+ int max_bytes, /* maximum file size else abort it */
+ int timeout_seconds /* 0; or total duration timeout period */
+ );
+
+/* httpread_hdr_type_get -- When file is ready, returns header type.
+ */
+enum httpread_hdr_type httpread_hdr_type_get(struct httpread *h);
+
+
+/* httpread_uri_get -- When file is ready, uri_get returns (translated) URI
+ * or possibly NULL (which would be an error).
+ */
+char *httpread_uri_get(struct httpread *h);
+
+/* httpread_reply_code_get -- When reply is ready, returns reply code */
+int httpread_reply_code_get(struct httpread *h);
+
+
+/* httpread_length_get -- When file is ready, returns file length. */
+int httpread_length_get(struct httpread *h);
+
+/* httpread_data_get -- When file is ready, returns file content
+ * with null byte appened.
+ * Might return NULL in some error condition.
+ */
+void * httpread_data_get(struct httpread *h);
+
+/* httpread_hdr_get -- When file is ready, returns header content
+ * with null byte appended.
+ * Might return NULL in some error condition.
+ */
+char * httpread_hdr_get(struct httpread *h);
+
+/* httpread_hdr_line_get -- When file is ready, returns pointer
+ * to line within header content matching the given tag
+ * (after the tag itself and any spaces/tabs).
+ *
+ * The tag should end with a colon for reliable matching.
+ *
+ * If not found, returns NULL;
+ */
+char * httpread_hdr_line_get(struct httpread *h, const char *tag);
+
+#endif /* HTTPREAD_H */
diff --git a/src/wps/wps.h b/src/wps/wps.h
index e18adab..c42ce1e 100644
--- a/src/wps/wps.h
+++ b/src/wps/wps.h
@@ -21,6 +21,7 @@
* enum wsc_op_code - EAP-WSC OP-Code values
*/
enum wsc_op_code {
+ WSC_UPnP = 0 /* No OP Code in UPnP transport */,
WSC_Start = 0x01,
WSC_ACK = 0x02,
WSC_NACK = 0x03,
@@ -30,6 +31,7 @@ enum wsc_op_code {
};
struct wps_registrar;
+struct upnp_wps_device_sm;
/**
* struct wps_credential - WPS Credential
@@ -142,7 +144,13 @@ enum wps_process_res {
/**
* WPS_FAILURE - Processing failed
*/
- WPS_FAILURE
+ WPS_FAILURE,
+
+ /**
+ * WPS_PENDING - Processing continues, but waiting for an external
+ * event (e.g., UPnP message from an external Registrar)
+ */
+ WPS_PENDING
};
enum wps_process_res wps_process_msg(struct wps_data *wps,
enum wsc_op_code op_code,
@@ -420,6 +428,31 @@ struct wps_context {
size_t ap_settings_len;
/**
+ * friendly_name - Friendly Name (required for UPnP)
+ */
+ char *friendly_name;
+
+ /**
+ * manufacturer_url - Manufacturer URL (optional for UPnP)
+ */
+ char *manufacturer_url;
+
+ /**
+ * model_description - Model Description (recommended for UPnP)
+ */
+ char *model_description;
+
+ /**
+ * model_url - Model URL (optional for UPnP)
+ */
+ char *model_url;
+
+ /**
+ * upc - Universal Product Code (optional for UPnP)
+ */
+ char *upc;
+
+ /**
* cred_cb - Callback to notify that new Credentials were received
* @ctx: Higher layer context data (cb_ctx)
* @cred: The received Credential
@@ -440,6 +473,13 @@ struct wps_context {
* cb_ctx: Higher layer context data for callbacks
*/
void *cb_ctx;
+
+ struct upnp_wps_device_sm *wps_upnp;
+
+ /* TODO: support multiple pending messages from UPnP PutWLANResponse */
+ u8 upnp_msg_addr[ETH_ALEN];
+ struct wpabuf *upnp_msg;
+ void *pending_session;
};
@@ -455,6 +495,8 @@ int wps_registrar_button_pushed(struct wps_registrar *reg);
void wps_registrar_probe_req_rx(struct wps_registrar *reg, const u8 *addr,
const struct wpabuf *wps_data);
int wps_registrar_update_ie(struct wps_registrar *reg);
+int wps_registrar_set_selected_registrar(struct wps_registrar *reg,
+ const struct wpabuf *msg);
unsigned int wps_pin_checksum(unsigned int pin);
unsigned int wps_pin_valid(unsigned int pin);
diff --git a/src/wps/wps_enrollee.c b/src/wps/wps_enrollee.c
index c3be248..6b6bc27 100644
--- a/src/wps/wps_enrollee.c
+++ b/src/wps/wps_enrollee.c
@@ -32,7 +32,13 @@ static int wps_build_mac_addr(struct wps_data *wps, struct wpabuf *msg)
static int wps_build_wps_state(struct wps_data *wps, struct wpabuf *msg)
{
- wpa_printf(MSG_DEBUG, "WPS: * Wi-Fi Protected Setup State");
+ u8 state;
+ if (wps->wps->ap)
+ state = wps->wps->wps_state;
+ else
+ state = WPS_STATE_NOT_CONFIGURED;
+ wpa_printf(MSG_DEBUG, "WPS: * Wi-Fi Protected Setup State (%d)",
+ state);
wpabuf_put_be16(msg, ATTR_WPS_STATE);
wpabuf_put_be16(msg, 1);
wpabuf_put_u8(msg, WPS_STATE_NOT_CONFIGURED);
@@ -1155,6 +1161,7 @@ enum wps_process_res wps_enrollee_process_msg(struct wps_data *wps,
switch (op_code) {
case WSC_MSG:
+ case WSC_UPnP:
return wps_process_wsc_msg(wps, msg);
case WSC_ACK:
return wps_process_wsc_ack(wps, msg);
diff --git a/src/wps/wps_i.h b/src/wps/wps_i.h
index 7221af3..b32a68f 100644
--- a/src/wps/wps_i.h
+++ b/src/wps/wps_i.h
@@ -101,6 +101,8 @@ struct wps_data {
* config_error - Configuration Error value to be used in NACK
*/
u16 config_error;
+
+ int ext_reg;
};
diff --git a/src/wps/wps_registrar.c b/src/wps/wps_registrar.c
index 8ef982b..2490682 100644
--- a/src/wps/wps_registrar.c
+++ b/src/wps/wps_registrar.c
@@ -21,6 +21,7 @@
#include "eloop.h"
#include "wps_i.h"
#include "wps_dev_attr.h"
+#include "wps_upnp.h"
struct wps_uuid_pin {
@@ -95,11 +96,15 @@ struct wps_registrar {
int skip_cred_build;
struct wpabuf *extra_cred;
int disable_auto_conf;
+ int sel_reg_dev_password_id_override;
+ int sel_reg_config_methods_override;
};
static int wps_set_ie(struct wps_registrar *reg);
static void wps_registrar_pbc_timeout(void *eloop_ctx, void *timeout_ctx);
+static void wps_registrar_set_selected_timeout(void *eloop_ctx,
+ void *timeout_ctx);
static void wps_registrar_add_pbc_session(struct wps_registrar *reg,
@@ -243,6 +248,8 @@ static int wps_build_sel_reg_dev_password_id(struct wps_registrar *reg,
u16 id = reg->pbc ? DEV_PW_PUSHBUTTON : DEV_PW_DEFAULT;
if (!reg->selected_registrar)
return 0;
+ if (reg->sel_reg_dev_password_id_override >= 0)
+ id = reg->sel_reg_dev_password_id_override;
wpa_printf(MSG_DEBUG, "WPS: * Device Password ID (%d)", id);
wpabuf_put_be16(msg, ATTR_DEV_PASSWORD_ID);
wpabuf_put_be16(msg, 2);
@@ -260,6 +267,8 @@ static int wps_build_sel_reg_config_methods(struct wps_registrar *reg,
methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON;
if (reg->pbc)
methods |= WPS_CONFIG_PUSHBUTTON;
+ if (reg->sel_reg_config_methods_override >= 0)
+ methods = reg->sel_reg_config_methods_override;
wpa_printf(MSG_DEBUG, "WPS: * Selected Registrar Config Methods (%x)",
methods);
wpabuf_put_be16(msg, ATTR_SELECTED_REGISTRAR_CONFIG_METHODS);
@@ -340,6 +349,7 @@ wps_registrar_init(struct wps_context *wps,
}
}
reg->disable_auto_conf = cfg->disable_auto_conf;
+ reg->sel_reg_dev_password_id_override = -1;
if (wps_set_ie(reg)) {
wps_registrar_deinit(reg);
@@ -359,6 +369,7 @@ void wps_registrar_deinit(struct wps_registrar *reg)
if (reg == NULL)
return;
eloop_cancel_timeout(wps_registrar_pbc_timeout, reg, NULL);
+ eloop_cancel_timeout(wps_registrar_set_selected_timeout, reg, NULL);
wps_free_pins(reg->pins);
wps_free_pbc_sessions(reg->pbc_sessions);
wpabuf_free(reg->extra_cred);
@@ -1311,6 +1322,22 @@ struct wpabuf * wps_registrar_get_msg(struct wps_data *wps,
{
struct wpabuf *msg;
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && wps->wps->upnp_msg) {
+ wpa_printf(MSG_DEBUG, "WPS: Use pending message from UPnP");
+ msg = wps->wps->upnp_msg;
+ wps->wps->upnp_msg = NULL;
+ *op_code = WSC_MSG; /* FIX: ack/nack */
+ wps->ext_reg = 1;
+ return msg;
+ }
+ if (wps->ext_reg) {
+ wpa_printf(MSG_DEBUG, "WPS: Using external Registrar, but no "
+ "pending message available");
+ return NULL;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
switch (wps->state) {
case SEND_M2:
if (wps_get_dev_password(wps) < 0)
@@ -1927,6 +1954,17 @@ static enum wps_process_res wps_process_wsc_msg(struct wps_data *wps,
switch (*attr.msg_type) {
case WPS_M1:
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && attr.mac_addr) {
+ /* Remove old pending messages when starting new run */
+ wpabuf_free(wps->wps->upnp_msg);
+ wps->wps->upnp_msg = NULL;
+
+ upnp_wps_device_send_wlan_event(
+ wps->wps->wps_upnp, attr.mac_addr,
+ UPNP_WPS_WLANEVENT_TYPE_EAP, msg);
+ }
+#endif /* CONFIG_WPS_UPNP */
ret = wps_process_m1(wps, &attr);
break;
case WPS_M3:
@@ -1988,6 +2026,17 @@ static enum wps_process_res wps_process_wsc_ack(struct wps_data *wps,
return WPS_FAILURE;
}
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && wps->ext_reg && wps->state == RECV_M2D_ACK &&
+ upnp_wps_subscribers(wps->wps->wps_upnp)) {
+ if (wps->wps->upnp_msg)
+ return WPS_CONTINUE;
+ wpa_printf(MSG_DEBUG, "WPS: Wait for response from an "
+ "external Registrar");
+ return WPS_PENDING;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
if (attr.registrar_nonce == NULL ||
os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0))
{
@@ -2002,8 +2051,16 @@ static enum wps_process_res wps_process_wsc_ack(struct wps_data *wps,
}
if (wps->state == RECV_M2D_ACK) {
- /* TODO: support for multiple registrars and sending of
- * multiple M2/M2D messages */
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp &&
+ upnp_wps_subscribers(wps->wps->wps_upnp)) {
+ if (wps->wps->upnp_msg)
+ return WPS_CONTINUE;
+ wpa_printf(MSG_DEBUG, "WPS: Wait for response from an "
+ "external Registrar");
+ return WPS_PENDING;
+ }
+#endif /* CONFIG_WPS_UPNP */
wpa_printf(MSG_DEBUG, "WPS: No more registrars available - "
"terminate negotiation");
@@ -2044,6 +2101,14 @@ static enum wps_process_res wps_process_wsc_nack(struct wps_data *wps,
return WPS_FAILURE;
}
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && wps->ext_reg) {
+ wpa_printf(MSG_DEBUG, "WPS: Negotiation using external "
+ "Registrar terminated by the Enrollee");
+ return WPS_FAILURE;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
if (attr.registrar_nonce == NULL ||
os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0))
{
@@ -2094,7 +2159,8 @@ static enum wps_process_res wps_process_wsc_done(struct wps_data *wps,
wpa_printf(MSG_DEBUG, "WPS: Received WSC_Done");
- if (wps->state != RECV_DONE) {
+ if (wps->state != RECV_DONE &&
+ (!wps->wps->wps_upnp || !wps->ext_reg)) {
wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
"receiving WSC_Done", wps->state);
return WPS_FAILURE;
@@ -2120,6 +2186,14 @@ static enum wps_process_res wps_process_wsc_done(struct wps_data *wps,
return WPS_FAILURE;
}
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && wps->ext_reg) {
+ wpa_printf(MSG_DEBUG, "WPS: Negotiation using external "
+ "Registrar completed successfully");
+ return WPS_DONE;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
if (attr.registrar_nonce == NULL ||
os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0))
{
@@ -2202,6 +2276,30 @@ enum wps_process_res wps_registrar_process_msg(struct wps_data *wps,
"op_code=%d)",
(unsigned long) wpabuf_len(msg), op_code);
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && wps->ext_reg && wps->wps->upnp_msg == NULL &&
+ (op_code == WSC_MSG || op_code == WSC_Done)) {
+ struct wps_parse_attr attr;
+ int type;
+ if (wps_parse_msg(msg, &attr) < 0 || attr.msg_type == NULL)
+ type = -1;
+ else
+ type = *attr.msg_type;
+ wpa_printf(MSG_DEBUG, "WPS: Sending received message (type %d)"
+ " to external Registrar for processing", type);
+ upnp_wps_device_send_wlan_event(wps->wps->wps_upnp,
+ wps->mac_addr_e,
+ UPNP_WPS_WLANEVENT_TYPE_EAP,
+ msg);
+ if (op_code == WSC_MSG)
+ return WPS_PENDING;
+ } else if (wps->wps->wps_upnp && wps->ext_reg && op_code == WSC_MSG) {
+ wpa_printf(MSG_DEBUG, "WPS: Skip internal processing - using "
+ "external Registrar");
+ return WPS_CONTINUE;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
switch (op_code) {
case WSC_MSG:
return wps_process_wsc_msg(wps, msg);
@@ -2227,3 +2325,68 @@ int wps_registrar_update_ie(struct wps_registrar *reg)
{
return wps_set_ie(reg);
}
+
+
+static void wps_registrar_set_selected_timeout(void *eloop_ctx,
+ void *timeout_ctx)
+{
+ struct wps_registrar *reg = eloop_ctx;
+
+ wpa_printf(MSG_DEBUG, "WPS: SetSelectedRegistrar timed out - "
+ "unselect Registrar");
+ reg->selected_registrar = 0;
+ reg->pbc = 0;
+ reg->sel_reg_dev_password_id_override = -1;
+ reg->sel_reg_config_methods_override = -1;
+ wps_set_ie(reg);
+}
+
+
+/**
+ * wps_registrar_set_selected_registrar - Notification of SetSelectedRegistrar
+ * @reg: Registrar data from wps_registrar_init()
+ * @msg: Received message from SetSelectedRegistrar
+ * @msg_len: Length of msg in octets
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is called when an AP receives a SetSelectedRegistrar UPnP
+ * message.
+ */
+int wps_registrar_set_selected_registrar(struct wps_registrar *reg,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+
+ wpa_hexdump_buf(MSG_MSGDUMP, "WPS: SetSelectedRegistrar attributes",
+ msg);
+
+ if (wps_parse_msg(msg, &attr) < 0 ||
+ attr.version == NULL || *attr.version != WPS_VERSION) {
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported SetSelectedRegistrar "
+ "version 0x%x", attr.version ? *attr.version : 0);
+ return -1;
+ }
+
+ if (attr.selected_registrar == NULL ||
+ *attr.selected_registrar == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: SetSelectedRegistrar: Disable "
+ "Selected Registrar");
+ eloop_cancel_timeout(wps_registrar_set_selected_timeout, reg,
+ NULL);
+ wps_registrar_set_selected_timeout(reg, NULL);
+ return 0;
+ }
+
+ reg->selected_registrar = 1;
+ reg->sel_reg_dev_password_id_override = attr.dev_password_id ?
+ WPA_GET_BE16(attr.dev_password_id) : DEV_PW_DEFAULT;
+ reg->sel_reg_config_methods_override = attr.sel_reg_config_methods ?
+ WPA_GET_BE16(attr.sel_reg_config_methods) : -1;
+ wps_set_ie(reg);
+
+ eloop_cancel_timeout(wps_registrar_set_selected_timeout, reg, NULL);
+ eloop_register_timeout(WPS_PBC_WALK_TIME, 0,
+ wps_registrar_set_selected_timeout,
+ reg, NULL);
+ return 0;
+}
diff --git a/src/wps/wps_upnp.c b/src/wps/wps_upnp.c
new file mode 100644
index 0000000..bebecc2
--- /dev/null
+++ b/src/wps/wps_upnp.c
@@ -0,0 +1,1056 @@
+/*
+ * UPnP WPS Device
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See below for more details on licensing and code history.
+ */
+
+/*
+ * This has been greatly stripped down from the original file
+ * (upnp_wps_device.c) by Ted Merrill, Atheros Communications
+ * in order to eliminate use of the bulky libupnp library etc.
+ *
+ * History:
+ * upnp_wps_device.c is/was a shim layer between wps_opt_upnp.c and
+ * the libupnp library.
+ * The layering (by Sony) was well done; only a very minor modification
+ * to API of upnp_wps_device.c was required.
+ * libupnp was found to be undesirable because:
+ * -- It consumed too much code and data space
+ * -- It uses multiple threads, making debugging more difficult
+ * and possibly reducing reliability.
+ * -- It uses static variables and only supports one instance.
+ * The shim and libupnp are here replaced by special code written
+ * specifically for the needs of hostapd.
+ * Various shortcuts can and are taken to keep the code size small.
+ * Generally, execution time is not as crucial.
+ *
+ * BUGS:
+ * -- UPnP requires that we be able to resolve domain names.
+ * While uncommon, if we have to do it then it will stall the entire
+ * hostapd program, which is bad.
+ * This is because we use the standard linux getaddrinfo() function
+ * which is syncronous.
+ * An asyncronous solution would be to use the free "ares" library.
+ * -- Does not have a robust output buffering scheme. Uses a single
+ * fixed size output buffer per TCP/HTTP connection, with possible (although
+ * unlikely) possibility of overflow and likely excessive use of RAM.
+ * A better solution would be to write the HTTP output as a buffered stream,
+ * using chunking: (handle header specially, then) generate data with
+ * a printf-like function into a buffer, catching buffer full condition,
+ * then send it out surrounded by http chunking.
+ * -- There is some code that could be separated out into the common
+ * library to be shared with wpa_supplicant.
+ * -- Needs renaming with module prefix to avoid polluting the debugger
+ * namespace and causing possible collisions with other static fncs
+ * and structure declarations when using the debugger.
+ * -- Just what should be in the first event message sent after subscription
+ * for the WLANEvent field? If i pass it empty, Vista replies with OK
+ * but apparently barfs on the message.
+ * -- The http error code generation is pretty bogus, hopefully noone cares.
+ *
+ * Author: Ted Merrill, Atheros Communications, based upon earlier work
+ * as explained above and below.
+ *
+ * Copyright:
+ * Copyright 2008 Atheros Communications.
+ *
+ * The original header (of upnp_wps_device.c) reads:
+ *
+ * Copyright (c) 2006-2007 Sony Corporation. All Rights Reserved.
+ *
+ * File Name: upnp_wps_device.c
+ * Description: EAP-WPS UPnP device source
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Sony Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Portions from Intel libupnp files, e.g. genlib/net/http/httpreadwrite.c
+ * typical header:
+ *
+ * Copyright (c) 2000-2003 Intel Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ * Overview of WPS over UPnP:
+ *
+ * UPnP is a protocol that allows devices to discover each other and control
+ * each other. In UPnP terminology, a device is either a "device" (a server
+ * that provides information about itself and allows itself to be controlled)
+ * or a "control point" (a client that controls "devices") or possibly both.
+ * This file implements a UPnP "device".
+ *
+ * For us, we use mostly basic UPnP discovery, but the control part of interest
+ * is WPS carried via UPnP messages. There is quite a bit of basic UPnP
+ * discovery to do before we can get to WPS, however.
+ *
+ * UPnP discovery begins with "devices" send out multicast UDP packets to a
+ * certain fixed multicast IP address and port, and "control points" sending
+ * out other such UDP packets.
+ *
+ * The packets sent by devices are NOTIFY packets (not to be confused with TCP
+ * NOTIFY packets that are used later) and those sent by control points are
+ * M-SEARCH packets. These packets contain a simple HTTP style header. The
+ * packets are sent redundantly to get around packet loss. Devices respond to
+ * M-SEARCH packets with HTTP-like UDP packets containing HTTP/1.1 200 OK
+ * messages, which give similar information as the UDP NOTIFY packets.
+ *
+ * The above UDP packets advertise the (arbitrary) TCP ports that the
+ * respective parties will listen to. The control point can then do a HTTP
+ * SUBSCRIBE (something like an HTTP PUT) after which the device can do a
+ * separate HTTP NOTIFY (also like an HTTP PUT) to do event messaging.
+ *
+ * The control point will also do HTTP GET of the "device file" listed in the
+ * original UDP information from the device (see UPNP_WPS_DEVICE_XML_FILE
+ * data), and based on this will do additional GETs... HTTP POSTs are done to
+ * cause an action.
+ *
+ * Beyond some basic information in HTTP headers, additional information is in
+ * the HTTP bodies, in a format set by the SOAP and XML standards, a markup
+ * language related to HTML used for web pages. This language is intended to
+ * provide the ultimate in self-documentation by providing a universal
+ * namespace based on pseudo-URLs called URIs. Note that although a URI looks
+ * like a URL (a web address), they are never accessed as such but are used
+ * only as identifiers.
+ *
+ * The POST of a GetDeviceInfo gets information similar to what might be
+ * obtained from a probe request or response on Wi-Fi. WPS messages M1-M8
+ * are passed via a POST of a PutMessage; the M1-M8 WPS messages are converted
+ * to a bin64 ascii representation for encapsulation. When proxying messages,
+ * WLANEvent and PutWLANResponse are used.
+ *
+ * This of course glosses over a lot of details.
+ */
+
+#include "includes.h"
+
+#include <assert.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <sys/ioctl.h>
+
+#include "common.h"
+#include "uuid.h"
+#include "base64.h"
+#include "wps.h"
+#include "wps_i.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+
+/*
+ * UPnP allows a client ("control point") to send a server like us ("device")
+ * a domain name for registration, and we are supposed to resolve it. This is
+ * bad because, using the standard Linux library, we will stall the entire
+ * hostapd waiting for resolution.
+ *
+ * The "correct" solution would be to use an event driven library for domain
+ * name resolution such as "ares". However, this would increase code size
+ * further. Since it is unlikely that we'll actually see such domain names, we
+ * can just refuse to accept them.
+ */
+#define NO_DOMAIN_NAME_RESOLUTION 1 /* 1 to allow only dotted ip addresses */
+
+
+/*
+ * UPnP does not scale well. If we were in a room with thousands of control
+ * points then potentially we could be expected to handle subscriptions for
+ * each of them, which would exhaust our memory. So we must set a limit. In
+ * practice we are unlikely to see more than one or two.
+ */
+#define MAX_SUBSCRIPTIONS 4 /* how many subscribing clients we handle */
+#define MAX_ADDR_PER_SUBSCRIPTION 8
+
+
+/* Write the current date/time per RFC */
+void format_date(struct wpabuf *buf)
+{
+ const char *weekday_str = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
+ const char *month_str = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
+ "Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
+ struct tm *date;
+ time_t t;
+
+ t = time(NULL);
+ date = gmtime(&t);
+ wpabuf_printf(buf, "%s, %02d %s %d %02d:%02d:%02d GMT",
+ &weekday_str[date->tm_wday * 4], date->tm_mday,
+ &month_str[date->tm_mon * 4], date->tm_year + 1900,
+ date->tm_hour, date->tm_min, date->tm_sec);
+}
+
+
+/***************************************************************************
+ * UUIDs (unique identifiers)
+ *
+ * These are supposed to be unique in all the world.
+ * Sometimes permanent ones are used, sometimes temporary ones
+ * based on random numbers... there are different rules for valid content
+ * of different types.
+ * Each uuid is 16 bytes long.
+ **************************************************************************/
+
+/* uuid_make -- construct a random UUID
+ * The UPnP documents don't seem to offer any guidelines as to which method to
+ * use for constructing UUIDs for subscriptions. Presumably any method from
+ * rfc4122 is good enough; I've chosen random number method.
+ */
+static void uuid_make(u8 uuid[UUID_LEN])
+{
+ os_get_random(uuid, UUID_LEN);
+
+ /* Replace certain bits as specified in rfc4122 or X.667 */
+ uuid[6] &= 0x0f; uuid[6] |= (4 << 4); /* version 4 == random gen */
+ uuid[8] &= 0x3f; uuid[8] |= 0x80;
+}
+
+
+/*
+ * Subscriber address handling.
+ * Since a subscriber may have an arbitrary number of addresses, we have to
+ * add a bunch of code to handle them.
+ *
+ * Addresses are passed in text, and MAY be domain names instead of the (usual
+ * and expected) dotted IP addresses. Resolving domain names consumes a lot of
+ * resources. Worse, we are currently using the standard Linux getaddrinfo()
+ * which will block the entire program until complete or timeout! The proper
+ * solution would be to use the "ares" library or similar with more state
+ * machine steps etc. or just disable domain name resolution by setting
+ * NO_DOMAIN_NAME_RESOLUTION to 1 at top of this file.
+ */
+
+/* subscr_addr_delete -- delete single unlinked subscriber address
+ * (be sure to unlink first if need be)
+ */
+static void subscr_addr_delete(struct subscr_addr *a)
+{
+ /*
+ * Note: do NOT free domain_and_port or path because they point to
+ * memory within the allocation of "a".
+ */
+ os_free(a);
+}
+
+
+/* subscr_addr_unlink -- unlink subscriber address from linked list */
+static void subscr_addr_unlink(struct subscription *s, struct subscr_addr *a)
+{
+ struct subscr_addr **listp = &s->addr_list;
+ s->n_addr--;
+ a->next->prev = a->prev;
+ a->prev->next = a->next;
+ if (*listp == a) {
+ if (a == a->next) {
+ /* last in queue */
+ *listp = NULL;
+ assert(s->n_addr == 0);
+ } else {
+ *listp = a->next;
+ }
+ }
+}
+
+
+/* subscr_addr_free_all -- unlink and delete list of subscriber addresses. */
+static void subscr_addr_free_all(struct subscription *s)
+{
+ struct subscr_addr **listp = &s->addr_list;
+ struct subscr_addr *a;
+ while ((a = *listp) != NULL) {
+ subscr_addr_unlink(s, a);
+ subscr_addr_delete(a);
+ }
+}
+
+
+/* subscr_addr_link -- add subscriber address to list of addresses */
+static void subscr_addr_link(struct subscription *s, struct subscr_addr *a)
+{
+ struct subscr_addr **listp = &s->addr_list;
+ s->n_addr++;
+ if (*listp == NULL) {
+ *listp = a->next = a->prev = a;
+ } else {
+ a->next = *listp;
+ a->prev = (*listp)->prev;
+ a->prev->next = a;
+ a->next->prev = a;
+ }
+}
+
+
+/* subscr_addr_add_url -- add address(es) for one url to subscription */
+static void subscr_addr_add_url(struct subscription *s, const char *url)
+{
+ int alloc_len;
+ char *scratch_mem = NULL;
+ char *mem;
+ char *domain_and_port;
+ char *delim;
+ char *path;
+ char *domain;
+ int port = 80; /* port to send to (default is port 80) */
+ struct addrinfo hints;
+ struct addrinfo *result = NULL;
+ struct addrinfo *rp;
+ int rerr;
+ struct subscr_addr *a = NULL;
+
+ /* url MUST begin with http: */
+ if (os_strncasecmp(url, "http://", 7))
+ goto fail;
+ url += 7;
+
+ /* allocate memory for the extra stuff we need */
+ alloc_len = (2 * (os_strlen(url) + 1));
+ scratch_mem = os_zalloc(alloc_len);
+ if (scratch_mem == NULL)
+ goto fail;
+ mem = scratch_mem;
+ strcpy(mem, url);
+ domain_and_port = mem;
+ mem += 1 + os_strlen(mem);
+ delim = os_strchr(domain_and_port, '/');
+ if (delim) {
+ *delim++ = 0; /* null terminate domain and port */
+ path = delim;
+ } else {
+ path = domain_and_port + os_strlen(domain_and_port);
+ }
+ domain = mem;
+ strcpy(domain, domain_and_port);
+ delim = strchr(domain, ':');
+ if (delim) {
+ *delim++ = 0; /* null terminate domain */
+ if (isdigit(*delim))
+ port = atol(delim);
+ }
+
+ /*
+ * getaddrinfo does the right thing with dotted decimal notations, or
+ * will resolve domain names. Resolving domain names will unfortunately
+ * hang the entire program until it is resolved or it times out
+ * internal to getaddrinfo; fortunately we think that the use of actual
+ * domain names (vs. dotted decimal notations) should be uncommon.
+ */
+ os_memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_INET; /* IPv4 */
+ hints.ai_socktype = SOCK_STREAM;
+#if NO_DOMAIN_NAME_RESOLUTION
+ /* Suppress domain name resolutions that would halt
+ * the program for periods of time
+ */
+ hints.ai_flags = AI_NUMERICHOST;
+#else
+ /* Allow domain name resolution. */
+ hints.ai_flags = 0;
+#endif
+ hints.ai_protocol = 0; /* Any protocol? */
+ rerr = getaddrinfo(domain, NULL /* fill in port ourselves */,
+ &hints, &result);
+ if (rerr) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Resolve error %d (%s) on: %s",
+ rerr, gai_strerror(rerr), domain);
+ goto fail;
+ }
+ for (rp = result; rp; rp = rp->ai_next) {
+ /* Limit no. of address to avoid denial of service attack */
+ if (s->n_addr >= MAX_ADDR_PER_SUBSCRIPTION) {
+ wpa_printf(MSG_INFO, "WPS UPnP: subscr_addr_add_url: "
+ "Ignoring excessive addresses");
+ break;
+ }
+
+ a = os_zalloc(sizeof(*a) + alloc_len);
+ if (a == NULL)
+ continue;
+ a->s = s;
+ mem = (void *) (a + 1);
+ a->domain_and_port = mem;
+ strcpy(mem, domain_and_port);
+ mem += 1 + strlen(mem);
+ a->path = mem;
+ if (path[0] != '/')
+ *mem++ = '/';
+ strcpy(mem, path);
+ mem += 1 + strlen(mem);
+ os_memcpy(&a->saddr, rp->ai_addr, sizeof(a->saddr));
+ a->saddr.sin_port = htons(port);
+
+ subscr_addr_link(s, a);
+ a = NULL; /* don't free it below */
+ }
+
+fail:
+ if (result)
+ freeaddrinfo(result);
+ os_free(scratch_mem);
+ os_free(a);
+}
+
+
+/* subscr_addr_list_create -- create list from urls in string.
+ * Each url is enclosed by angle brackets.
+ */
+static void subscr_addr_list_create(struct subscription *s,
+ const char *url_list)
+{
+ char *end;
+ for (;;) {
+ while (*url_list == ' ' || *url_list == '\t')
+ url_list++;
+ if (*url_list != '<')
+ break;
+ url_list++;
+ end = os_strchr(url_list, '>');
+ if (end == NULL)
+ break;
+ *end++ = 0;
+ subscr_addr_add_url(s, url_list);
+ url_list = end;
+ }
+}
+
+
+int send_wpabuf(int fd, struct wpabuf *buf)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: %lu byte response",
+ (unsigned long) wpabuf_len(buf));
+ errno = 0;
+ if (write(fd, wpabuf_head(buf), wpabuf_len(buf)) !=
+ (int) wpabuf_len(buf)) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: Failed to send buffer: "
+ "errno=%d (%s)",
+ errno, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void wpabuf_put_property(struct wpabuf *buf, const char *name,
+ const char *value)
+{
+ wpabuf_put_str(buf, "<e:property>");
+ wpabuf_printf(buf, "<%s>", name);
+ if (value)
+ wpabuf_put_str(buf, value);
+ wpabuf_printf(buf, "</%s>", name);
+ wpabuf_put_str(buf, "</e:property>\n");
+}
+
+
+/**
+ * upnp_wps_device_send_event - Queue event messages for subscribers
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ *
+ * This function queues the last WLANEvent to be sent for all currently
+ * subscribed UPnP control points. sm->wlanevent must have been set with the
+ * encoded data before calling this function.
+ */
+static void upnp_wps_device_send_event(struct upnp_wps_device_sm *sm)
+{
+ /* Enqueue event message for all subscribers */
+ struct wpabuf *buf; /* holds event message */
+ int buf_size = 0;
+ struct subscription *s;
+ /* Actually, utf-8 is the default, but it doesn't hurt to specify it */
+ const char *format_head =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n";
+ const char *format_tail = "</e:propertyset>\n";
+
+ if (sm->subscriptions == NULL) {
+ /* optimize */
+ return;
+ }
+
+ /* Determine buffer size needed first */
+ buf_size += os_strlen(format_head);
+ buf_size += 50 + 2 * os_strlen("WLANEvent");
+ if (sm->wlanevent)
+ buf_size += os_strlen(sm->wlanevent);
+ buf_size += os_strlen(format_tail);
+
+ buf = wpabuf_alloc(buf_size);
+ if (buf == NULL)
+ return;
+ wpabuf_put_str(buf, format_head);
+ wpabuf_put_property(buf, "WLANEvent", sm->wlanevent);
+ wpabuf_put_str(buf, format_tail);
+
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: WLANEvent message:\n%s",
+ (char *) wpabuf_head(buf));
+
+ s = sm->subscriptions;
+ do {
+ if (event_add(s, buf)) {
+ struct subscription *s_old = s;
+ wpa_printf(MSG_INFO, "WPS UPnP: Dropping "
+ "subscriber due to event backlog");
+ s = s_old->next;
+ subscription_unlink(s_old);
+ subscription_destroy(s_old);
+ } else {
+ s = s->next;
+ }
+ } while (s != sm->subscriptions);
+
+ wpabuf_free(buf);
+}
+
+
+/*
+ * Event subscription (subscriber machines register with us to receive event
+ * messages).
+ * This is the result of an incoming HTTP over TCP SUBSCRIBE request.
+ */
+
+/* subscription_unlink -- remove from the active list */
+void subscription_unlink(struct subscription *s)
+{
+ struct upnp_wps_device_sm *sm = s->sm;
+
+ if (s->next == s) {
+ /* only one? */
+ sm->subscriptions = NULL;
+ } else {
+ if (sm->subscriptions == s) {
+ sm->subscriptions = s->next;
+ }
+ s->next->prev = s->prev;
+ s->prev->next = s->next;
+ }
+ sm->n_subscriptions--;
+}
+
+
+/* subscription_link_to_end -- link to end of active list
+ * (should have high expiry time!)
+ */
+static void subscription_link_to_end(struct subscription *s)
+{
+ struct upnp_wps_device_sm *sm = s->sm;
+
+ if (sm->subscriptions) {
+ s->next = sm->subscriptions;
+ s->prev = s->next->prev;
+ s->prev->next = s;
+ s->next->prev = s;
+ } else {
+ sm->subscriptions = s->next = s->prev = s;
+ }
+ sm->n_subscriptions++;
+}
+
+
+/* subscription_destroy -- destroy an unlinked subscription
+ * Be sure to unlink first if necessary.
+ */
+void subscription_destroy(struct subscription *s)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Destroy subscription %p", s);
+ if (s->addr_list)
+ subscr_addr_free_all(s);
+ event_delete_all(s);
+ os_free(s);
+}
+
+
+/* subscription_list_age -- remove expired subscriptions */
+static void subscription_list_age(struct upnp_wps_device_sm *sm, time_t now)
+{
+ struct subscription *s;
+ while ((s = sm->subscriptions) != NULL && s->timeout_time < now) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Removing aged subscription");
+ subscription_unlink(s);
+ subscription_destroy(s);
+ }
+}
+
+
+/* subscription_find -- return existing subscription matching uuid, if any
+ * returns NULL if not found
+ */
+struct subscription * subscription_find(struct upnp_wps_device_sm *sm,
+ const u8 uuid[UUID_LEN])
+{
+ struct subscription *s0 = sm->subscriptions;
+ struct subscription *s = s0;
+
+ if (s0 == NULL)
+ return NULL;
+ do {
+ if (os_memcmp(s->uuid, uuid, UUID_LEN) == 0)
+ return s; /* Found match */
+ s = s->next;
+ } while (s != s0);
+
+ return NULL;
+}
+
+
+/* subscription_first_event -- send format/queue event that is automatically
+ * sent on a new subscription.
+ */
+static int subscription_first_event(struct subscription *s)
+{
+ /*
+ * Actually, utf-8 is the default, but it doesn't hurt to specify it.
+ *
+ * APStatus is apparently a bit set,
+ * 0x1 = configuration change (but is always set?)
+ * 0x10 = ap is locked
+ *
+ * Per UPnP spec, we send out the last value of each variable, even
+ * for WLANEvent, whatever it was.
+ */
+ char *wlan_event;
+ struct wpabuf *buf;
+ int ap_status = 1; /* TODO: add 0x10 if access point is locked */
+ const char *head =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n";
+ const char *tail = "</e:propertyset>\n";
+ char txt[10];
+
+ wlan_event = s->sm->wlanevent;
+ if (wlan_event == NULL || *wlan_event == '\0') {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: WLANEvent not known for "
+ "initial event message");
+ wlan_event = "";
+ }
+ buf = wpabuf_alloc(500 + os_strlen(wlan_event));
+ if (buf == NULL)
+ return 1;
+
+ wpabuf_put_str(buf, head);
+ wpabuf_put_property(buf, "STAStatus", "1");
+ os_snprintf(txt, sizeof(txt), "%d", ap_status);
+ wpabuf_put_property(buf, "APStatus", txt);
+ if (*wlan_event)
+ wpabuf_put_property(buf, "WLANEvent", wlan_event);
+ wpabuf_put_str(buf, tail);
+
+ if (event_add(s, buf)) {
+ wpabuf_free(buf);
+ return 1;
+ }
+ wpabuf_free(buf);
+
+ return 0;
+}
+
+
+/**
+ * subscription_start - Rremember a UPnP control point to send events to.
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @callback_urls: malloc' mem given to the subscription
+ * Returns: %NULL on error, or pointer to new subscription structure.
+ */
+struct subscription * subscription_start(struct upnp_wps_device_sm *sm,
+ char *callback_urls)
+{
+ struct subscription *s = NULL;
+ time_t now = time(NULL);
+ time_t expire = now + UPNP_SUBSCRIBE_SEC;
+
+ /* Get rid of expired subscriptions so we have room */
+ subscription_list_age(sm, now);
+
+ /* If too many subscriptions, remove oldest */
+ if (sm->n_subscriptions >= MAX_SUBSCRIPTIONS) {
+ struct subscription *s = sm->subscriptions;
+ wpa_printf(MSG_INFO, "WPS UPnP: Too many subscriptions, "
+ "trashing oldest");
+ subscription_unlink(s);
+ subscription_destroy(s);
+ }
+
+ s = os_zalloc(sizeof(*s));
+ if (s == NULL)
+ return NULL;
+
+ s->sm = sm;
+ s->timeout_time = expire;
+ uuid_make(s->uuid);
+ subscr_addr_list_create(s, callback_urls);
+ /* Add to end of list, since it has the highest expiration time */
+ subscription_link_to_end(s);
+ /* Queue up immediate event message (our last event)
+ * as required by UPnP spec.
+ */
+ if (subscription_first_event(s)) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Dropping subscriber due to "
+ "event backlog");
+ subscription_unlink(s);
+ subscription_destroy(s);
+ return NULL;
+ }
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription %p started with %s",
+ s, callback_urls);
+ os_free(callback_urls);
+ /* Schedule sending this */
+ event_send_all_later(sm);
+ return s;
+}
+
+
+/* subscription_renew -- find subscription and reset timeout */
+struct subscription * subscription_renew(struct upnp_wps_device_sm *sm,
+ const u8 uuid[UUID_LEN])
+{
+ time_t now = time(NULL);
+ time_t expire = now + UPNP_SUBSCRIBE_SEC;
+ struct subscription *s = subscription_find(sm, uuid);
+ if (s == NULL)
+ return NULL;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewed");
+ subscription_unlink(s);
+ s->timeout_time = expire;
+ /* add back to end of list, since it now has highest expiry */
+ subscription_link_to_end(s);
+ return s;
+}
+
+
+/**
+ * upnp_wps_device_send_wlan_event - Event notification
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @from_mac_addr: Source (Enrollee) MAC address for the event
+ * @ev_type: Event type
+ * @msg: Event data
+ * Returns: 0 on success, -1 on failure
+ *
+ * Tell external Registrars (UPnP control points) that something happened. In
+ * particular, events include WPS messages from clients that are proxied to
+ * external Registrars.
+ */
+int upnp_wps_device_send_wlan_event(struct upnp_wps_device_sm *sm,
+ const u8 from_mac_addr[ETH_ALEN],
+ enum upnp_wps_wlanevent_type ev_type,
+ const struct wpabuf *msg)
+{
+ int ret = -1;
+ char type[2];
+ const u8 *mac = from_mac_addr;
+ char mac_text[18];
+ u8 *raw = NULL;
+ size_t raw_len;
+ char *val;
+ size_t val_len;
+ int pos = 0;
+
+ if (!sm)
+ goto fail;
+
+ os_snprintf(type, sizeof(type), "%1u", ev_type);
+
+ raw_len = 1 + 17 + (msg ? wpabuf_len(msg) : 0);
+ raw = os_zalloc(raw_len);
+ if (!raw)
+ goto fail;
+
+ *(raw + pos) = (u8) ev_type;
+ pos += 1;
+ os_snprintf(mac_text, sizeof(mac_text), MACSTR, MAC2STR(mac));
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Proxying WLANEvent from %s",
+ mac_text);
+ os_memcpy(raw + pos, mac_text, 17);
+ pos += 17;
+ if (msg) {
+ os_memcpy(raw + pos, wpabuf_head(msg), wpabuf_len(msg));
+ pos += wpabuf_len(msg);
+ }
+ raw_len = pos;
+
+ val = (char *) base64_encode(raw, raw_len, &val_len);
+ if (val == NULL)
+ goto fail;
+
+ os_free(sm->wlanevent);
+ sm->wlanevent = val;
+ upnp_wps_device_send_event(sm);
+
+ ret = 0;
+
+fail:
+ os_free(raw);
+
+ return ret;
+}
+
+
+/**
+ * get_netif_info - Get hw and IP addresses for network device
+ * @net_if: Selected network interface name
+ * @ip_addr: Buffer for returning IP address in network byte order
+ * @ip_addr_text: Buffer for returning a pointer to allocated IP address text
+ * @mac: Buffer for returning MAC address
+ * @mac_addr_text: Buffer for returning allocated MAC address text
+ * Returns: 0 on success, -1 on failure
+ */
+static int get_netif_info(const char *net_if, unsigned *ip_addr,
+ char **ip_addr_text, u8 mac[ETH_ALEN],
+ char **mac_addr_text)
+{
+ struct ifreq req;
+ int sock = -1;
+ struct sockaddr_in *addr;
+ struct in_addr in_addr;
+
+ *ip_addr_text = os_zalloc(16);
+ *mac_addr_text = os_zalloc(18);
+ if (*ip_addr_text == NULL || *mac_addr_text == NULL)
+ goto fail;
+
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+ goto fail;
+
+ os_strncpy(req.ifr_name, net_if, sizeof(req.ifr_name));
+ if (ioctl(sock, SIOCGIFADDR, &req) < 0) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: SIOCGIFADDR failed: %d (%s)",
+ errno, strerror(errno));
+ goto fail;
+ }
+ addr = (void *) &req.ifr_addr;
+ *ip_addr = addr->sin_addr.s_addr;
+ in_addr.s_addr = *ip_addr;
+ os_snprintf(*ip_addr_text, 16, "%s", inet_ntoa(in_addr));
+
+ os_strncpy(req.ifr_name, net_if, sizeof(req.ifr_name));
+ if (ioctl(sock, SIOCGIFHWADDR, &req) < 0) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: SIOCGIFHWADDR failed: "
+ "%d (%s)", errno, strerror(errno));
+ goto fail;
+ }
+ os_memcpy(mac, req.ifr_addr.sa_data, 6);
+ os_snprintf(*mac_addr_text, 18, MACSTR, MAC2STR(req.ifr_addr.sa_data));
+
+ close(sock);
+ return 0;
+
+fail:
+ if (sock >= 0)
+ close(sock);
+ os_free(*ip_addr_text);
+ *ip_addr_text = NULL;
+ os_free(*mac_addr_text);
+ *mac_addr_text = NULL;
+ return -1;
+}
+
+
+/**
+ * upnp_wps_device_stop - Stop WPS UPnP operations on an interface
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ */
+void upnp_wps_device_stop(struct upnp_wps_device_sm *sm)
+{
+ if (!sm || !sm->started)
+ return;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Stop device");
+ web_listener_stop(sm);
+ while (sm->web_connections)
+ web_connection_stop(sm->web_connections);
+ while (sm->msearch_replies)
+ msearchreply_state_machine_stop(sm->msearch_replies);
+ while (sm->subscriptions) {
+ struct subscription *s = sm->subscriptions;
+ subscription_unlink(s);
+ subscription_destroy(s);
+ }
+
+ advertisement_state_machine_stop(sm);
+ /* TODO: send byebye notifications */
+
+ event_send_stop_all(sm);
+ os_free(sm->wlanevent);
+ sm->wlanevent = NULL;
+ os_free(sm->net_if);
+ sm->net_if = NULL;
+ os_free(sm->mac_addr_text);
+ sm->mac_addr_text = NULL;
+ os_free(sm->ip_addr_text);
+ sm->ip_addr_text = NULL;
+ if (sm->multicast_sd >= 0)
+ close(sm->multicast_sd);
+ sm->multicast_sd = -1;
+ ssdp_listener_stop(sm);
+
+ sm->started = 0;
+}
+
+
+/**
+ * upnp_wps_device_start - Start WPS UPnP operations on an interface
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @net_if: Selected network interface name
+ * Returns: 0 on success, -1 on failure
+ */
+int upnp_wps_device_start(struct upnp_wps_device_sm *sm, char *net_if)
+{
+ if (!sm || !net_if)
+ return -1;
+
+ if (sm->started)
+ upnp_wps_device_stop(sm);
+
+ sm->net_if = strdup(net_if);
+ sm->multicast_sd = -1;
+ sm->ssdp_sd = -1;
+ sm->started = 1;
+ sm->advertise_count = 0;
+
+ /* Fix up linux multicast handling */
+ if (add_ssdp_network(net_if))
+ goto fail;
+
+ /* Determine which IP and mac address we're using */
+ if (get_netif_info(net_if,
+ &sm->ip_addr, &sm->ip_addr_text,
+ sm->mac_addr, &sm->mac_addr_text)) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Could not get IP/MAC address "
+ "for %s. Does it have IP address?", net_if);
+ goto fail;
+ }
+
+ /* Listen for incoming TCP connections so that others
+ * can fetch our "xml files" from us.
+ */
+ if (web_listener_start(sm))
+ goto fail;
+
+ /* Set up for receiving discovery (UDP) packets */
+ if (ssdp_listener_start(sm))
+ goto fail;
+
+ /* Set up for sending multicast */
+ if (ssdp_open_multicast(sm) < 0)
+ goto fail;
+
+ /*
+ * Broadcast NOTIFY messages to let the world know we exist.
+ * This is done via a state machine since the messages should not be
+ * all sent out at once.
+ */
+ if (advertisement_state_machine_start(sm))
+ goto fail;
+
+ return 0;
+
+fail:
+ upnp_wps_device_stop(sm);
+ return -1;
+}
+
+
+/**
+ * upnp_wps_device_deinit - Deinitialize WPS UPnP
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ */
+void upnp_wps_device_deinit(struct upnp_wps_device_sm *sm)
+{
+ if (!sm)
+ return;
+
+ upnp_wps_device_stop(sm);
+
+ if (sm->peer.wps)
+ wps_deinit(sm->peer.wps);
+ os_free(sm->root_dir);
+ os_free(sm->desc_url);
+ os_free(sm->ctx);
+ os_free(sm);
+}
+
+
+/**
+ * upnp_wps_device_init - Initialize WPS UPnP
+ * @ctx: callback table; we must eventually free it
+ * @wps: Pointer to longterm WPS context
+ * @priv: External context data that will be used in callbacks
+ * Returns: WPS UPnP state or %NULL on failure
+ */
+struct upnp_wps_device_sm *
+upnp_wps_device_init(struct upnp_wps_device_ctx *ctx, struct wps_context *wps,
+ void *priv)
+{
+ struct upnp_wps_device_sm *sm;
+
+ sm = os_zalloc(sizeof(*sm));
+ if (!sm) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: upnp_wps_device_init failed");
+ return NULL;
+ }
+
+ sm->ctx = ctx;
+ sm->wps = wps;
+ sm->priv = priv;
+
+ return sm;
+}
+
+
+/**
+ * upnp_wps_subscribers - Check whether there are any event subscribers
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 if no subscribers, 1 if subscribers
+ */
+int upnp_wps_subscribers(struct upnp_wps_device_sm *sm)
+{
+ return sm->subscriptions != NULL;
+}
diff --git a/src/wps/wps_upnp.h b/src/wps/wps_upnp.h
new file mode 100644
index 0000000..995a7f2
--- /dev/null
+++ b/src/wps/wps_upnp.h
@@ -0,0 +1,66 @@
+/*
+ * UPnP WPS Device
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#ifndef WPS_UPNP_H
+#define WPS_UPNP_H
+
+struct upnp_wps_device_sm;
+struct wps_context;
+struct wps_data;
+
+struct upnp_wps_peer {
+ struct wps_data *wps;
+};
+
+enum upnp_wps_wlanevent_type {
+ UPNP_WPS_WLANEVENT_TYPE_PROBE = 1,
+ UPNP_WPS_WLANEVENT_TYPE_EAP = 2
+};
+
+struct upnp_wps_device_ctx {
+ struct wpabuf * (*rx_req_get_device_info)(
+ void *priv, struct upnp_wps_peer *peer);
+ struct wpabuf * (*rx_req_put_message)(
+ void *priv, struct upnp_wps_peer *peer,
+ const struct wpabuf *msg);
+ struct wpabuf * (*rx_req_get_ap_settings)(void *priv,
+ const struct wpabuf *msg);
+ int (*rx_req_set_ap_settings)(void *priv, const struct wpabuf *msg);
+ int (*rx_req_del_ap_settings)(void *priv, const struct wpabuf *msg);
+ struct wpabuf * (*rx_req_get_sta_settings)(void *priv,
+ const struct wpabuf *msg);
+ int (*rx_req_set_sta_settings)(void *priv, const struct wpabuf *msg);
+ int (*rx_req_del_sta_settings)(void *priv, const struct wpabuf *msg);
+ int (*rx_req_put_wlan_event_response)(
+ void *priv, enum upnp_wps_wlanevent_type ev_type,
+ const u8 *mac_addr, const struct wpabuf *msg);
+ int (*rx_req_set_selected_registrar)(void *priv,
+ const struct wpabuf *msg);
+ int (*rx_req_reboot_ap)(void *priv, const struct wpabuf *msg);
+ int (*rx_req_reset_ap)(void *priv, const struct wpabuf *msg);
+ int (*rx_req_reboot_sta)(void *priv, const struct wpabuf *msg);
+ int (*rx_req_reset_sta)(void *priv, const struct wpabuf *msg);
+};
+
+struct upnp_wps_device_sm *
+upnp_wps_device_init(struct upnp_wps_device_ctx *ctx, struct wps_context *wps,
+ void *priv);
+void upnp_wps_device_deinit(struct upnp_wps_device_sm *sm);
+
+int upnp_wps_device_start(struct upnp_wps_device_sm *sm, char *net_if);
+void upnp_wps_device_stop(struct upnp_wps_device_sm *sm);
+
+int upnp_wps_device_send_wlan_event(struct upnp_wps_device_sm *sm,
+ const u8 from_mac_addr[ETH_ALEN],
+ enum upnp_wps_wlanevent_type ev_type,
+ const struct wpabuf *msg);
+int upnp_wps_subscribers(struct upnp_wps_device_sm *sm);
+
+#endif /* WPS_UPNP_H */
diff --git a/src/wps/wps_upnp_event.c b/src/wps/wps_upnp_event.c
new file mode 100644
index 0000000..7e7ce88
--- /dev/null
+++ b/src/wps/wps_upnp_event.c
@@ -0,0 +1,525 @@
+/*
+ * UPnP WPS Device - Event processing
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#include "includes.h"
+#include <assert.h>
+#include <fcntl.h>
+
+#include "common.h"
+#include "eloop.h"
+#include "uuid.h"
+#include "httpread.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+/*
+ * Event message generation (to subscribers)
+ *
+ * We make a separate copy for each message for each subscriber. This memory
+ * wasted could be limited (adding code complexity) by sharing copies, keeping
+ * a usage count and freeing when zero.
+ *
+ * Sending a message requires using a HTTP over TCP NOTIFY
+ * (like a PUT) which requires a number of states..
+ */
+
+#define MAX_EVENTS_QUEUED 20 /* How far behind queued events */
+#define EVENT_TIMEOUT_SEC 30 /* Drop sending event after timeout */
+
+/* How long to wait before sending event */
+#define EVENT_DELAY_SECONDS 0
+#define EVENT_DELAY_MSEC 0
+
+/*
+ * Event information that we send to each subscriber is remembered in this
+ * struct. The event cannot be sent by simple UDP; it has to be sent by a HTTP
+ * over TCP transaction which requires various states.. It may also need to be
+ * retried at a different address (if more than one is available).
+ *
+ * TODO: As an optimization we could share data between subscribers.
+ */
+struct wps_event_ {
+ struct wps_event_ *next;
+ struct wps_event_ *prev; /* double linked list */
+ struct subscription *s; /* parent */
+ unsigned subscriber_sequence; /* which event for this subscription*/
+ int retry; /* which retry */
+ struct subscr_addr *addr; /* address to connect to */
+ struct wpabuf *data; /* event data to send */
+ /* The following apply while we are sending an event message. */
+ int sd; /* -1 or socket descriptor for open connection */
+ int sd_registered; /* nonzero if we must cancel registration */
+ struct httpread *hread; /* NULL or open connection for event msg */
+};
+
+
+static void event_timeout_handler(void *eloop_data, void *user_ctx);
+
+/* event_clean -- clean sockets etc. of event
+ * Leaves data, retry count etc. alone.
+ */
+static void event_clean(struct wps_event_ *e)
+{
+ if (e->s->current_event == e) {
+ eloop_cancel_timeout(event_timeout_handler, NULL, e);
+ e->s->current_event = NULL;
+ }
+ if (e->sd_registered) {
+ eloop_unregister_sock(e->sd, EVENT_TYPE_WRITE);
+ e->sd_registered = 0;
+ }
+ if (e->sd != -1) {
+ close(e->sd);
+ e->sd = -1;
+ }
+ if (e->hread)
+ httpread_destroy(e->hread);
+ e->hread = NULL;
+}
+
+
+/* event_delete -- delete single unqueued event
+ * (be sure to dequeue first if need be)
+ */
+void event_delete(struct wps_event_ *e)
+{
+ event_clean(e);
+ wpabuf_free(e->data);
+ os_free(e);
+}
+
+
+/* event_dequeue -- get next event from the queue
+ * Returns NULL if empty.
+ */
+static struct wps_event_ *event_dequeue(struct subscription *s)
+{
+ struct wps_event_ **event_head = &s->event_queue;
+ struct wps_event_ *e = *event_head;
+ if (e == NULL)
+ return NULL;
+ e->next->prev = e->prev;
+ e->prev->next = e->next;
+ if (*event_head == e) {
+ if (e == e->next) {
+ /* last in queue */
+ *event_head = NULL;
+ } else {
+ *event_head = e->next;
+ }
+ }
+ s->n_queue--;
+ e->next = e->prev = NULL;
+ /* but parent "s" is still valid */
+ return e;
+}
+
+
+/* event_enqueue_at_end -- add event to end of queue */
+static void event_enqueue_at_end(struct subscription *s, struct wps_event_ *e)
+{
+ struct wps_event_ **event_head = &s->event_queue;
+ if (*event_head == NULL) {
+ *event_head = e->next = e->prev = e;
+ } else {
+ e->next = *event_head;
+ e->prev = e->next->prev;
+ e->prev->next = e;
+ e->next->prev = e;
+ }
+ s->n_queue++;
+}
+
+
+/* event_enqueue_at_begin -- add event to begin of queue
+ * (appropriate for retrying event only)
+ */
+static void event_enqueue_at_begin(struct subscription *s,
+ struct wps_event_ *e)
+{
+ struct wps_event_ **event_head = &s->event_queue;
+ if (*event_head == NULL) {
+ *event_head = e->next = e->prev = e;
+ } else {
+ e->prev = *event_head;
+ e->next = e->prev->next;
+ e->prev->next = e;
+ e->next->prev = e;
+ *event_head = e;
+ }
+ s->n_queue++;
+}
+
+
+/* event_delete_all -- delete entire event queue and current event */
+void event_delete_all(struct subscription *s)
+{
+ struct wps_event_ *e;
+ while ((e = event_dequeue(s)) != NULL)
+ event_delete(e);
+ if (s->current_event) {
+ event_delete(s->current_event);
+ /* will set: s->current_event = NULL; */
+ }
+}
+
+
+/**
+ * event_retry - Called when we had a failure delivering event msg
+ * @e: Event
+ * @do_next_address: skip address e.g. on connect fail
+ */
+static void event_retry(struct wps_event_ *e, int do_next_address)
+{
+ struct subscription *s = e->s;
+ struct upnp_wps_device_sm *sm = s->sm;
+
+ event_clean(e);
+ /* will set: s->current_event = NULL; */
+
+ if (do_next_address)
+ e->retry++;
+ if (e->retry >= s->n_addr) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Giving up on sending event");
+ return;
+ }
+ event_enqueue_at_begin(s, e);
+ event_send_all_later(sm);
+}
+
+
+/* called if the overall event-sending process takes too long */
+static void event_timeout_handler(void *eloop_data, void *user_ctx)
+{
+ struct wps_event_ *e = user_ctx;
+ struct subscription *s = e->s;
+
+ assert(e == s->current_event);
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Event send timeout");
+ event_retry(e, 1);
+}
+
+
+/* event_got_response_handler -- called back when http response is received. */
+static void event_got_response_handler(struct httpread *handle, void *cookie,
+ enum httpread_event en)
+{
+ struct wps_event_ *e = cookie;
+ struct subscription *s = e->s;
+ struct upnp_wps_device_sm *sm = s->sm;
+ struct httpread *hread = e->hread;
+ int reply_code = 0;
+
+ assert(e == s->current_event);
+ eloop_cancel_timeout(event_timeout_handler, NULL, e);
+
+ if (en == HTTPREAD_EVENT_FILE_READY) {
+ if (httpread_hdr_type_get(hread) == HTTPREAD_HDR_TYPE_REPLY) {
+ reply_code = httpread_reply_code_get(hread);
+ if (reply_code == HTTP_OK) {
+ wpa_printf(MSG_DEBUG,
+ "WPS UPnP: Got event reply OK");
+ event_delete(e);
+ goto send_more;
+ } else {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Got event "
+ "error reply code %d", reply_code);
+ goto bad;
+ }
+ } else {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Got bogus event "
+ "response %d", en);
+ }
+ } else {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Event response timeout/fail");
+ goto bad;
+ }
+ event_retry(e, 1);
+ goto send_more;
+
+send_more:
+ /* Schedule sending more if there is more to send */
+ if (s->event_queue)
+ event_send_all_later(sm);
+ return;
+
+bad:
+ /*
+ * If other side doesn't like what we say, forget about them.
+ * (There is no way to tell other side that we are dropping
+ * them...).
+ * Alternately, we could just do event_delete(e)
+ */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Deleting subscription due to errors");
+ subscription_unlink(s);
+ subscription_destroy(s);
+}
+
+
+/* event_send_tx_ready -- actually write event message
+ *
+ * Prequisite: subscription socket descriptor has become ready to
+ * write (because connection to subscriber has been made).
+ *
+ * It is also possible that we are called because the connect has failed;
+ * it is possible to test for this, or we can just go ahead and then
+ * the write will fail.
+ */
+static void event_send_tx_ready(int sock, void *eloop_ctx, void *sock_ctx)
+{
+ struct wps_event_ *e = sock_ctx;
+ struct subscription *s = e->s;
+ struct wpabuf *buf;
+ char *b;
+
+ assert(e == s->current_event);
+ assert(e->sd == sock);
+
+ buf = wpabuf_alloc(1000 + wpabuf_len(e->data));
+ if (buf == NULL) {
+ event_retry(e, 0);
+ goto bad;
+ }
+ wpabuf_printf(buf, "NOTIFY %s HTTP/1.1\r\n", e->addr->path);
+ wpabuf_put_str(buf, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n");
+ wpabuf_printf(buf, "HOST: %s\r\n", e->addr->domain_and_port);
+ wpabuf_put_str(buf, "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n"
+ "NT: upnp:event\r\n"
+ "NTS: upnp:propchange\r\n");
+ wpabuf_put_str(buf, "SID: uuid:");
+ b = wpabuf_put(buf, 0);
+ uuid_bin2str(s->uuid, b, 80);
+ wpabuf_put(buf, os_strlen(b));
+ wpabuf_put_str(buf, "\r\n");
+ wpabuf_printf(buf, "SEQ: %u\r\n", e->subscriber_sequence);
+ wpabuf_printf(buf, "CONTENT-LENGTH: %d\r\n",
+ (int) wpabuf_len(e->data));
+ wpabuf_put_str(buf, "\r\n"); /* terminating empty line */
+ wpabuf_put_buf(buf, e->data);
+
+ /* Since the message size is pretty small, we should be
+ * able to get the operating system to buffer what we give it
+ * and not have to come back again later to write more...
+ */
+#if 0
+ /* we could: Turn blocking back on? */
+ fcntl(e->sd, F_SETFL, 0);
+#endif
+ if (send_wpabuf(e->sd, buf) < 0) {
+ event_retry(e, 1);
+ goto bad;
+ }
+ wpabuf_free(buf);
+ buf = NULL;
+
+ if (e->sd_registered) {
+ e->sd_registered = 0;
+ eloop_unregister_sock(e->sd, EVENT_TYPE_WRITE);
+ }
+ /* Set up to read the reply */
+ e->hread = httpread_create(e->sd, event_got_response_handler,
+ e /* cookie */,
+ 0 /* no data expected */,
+ EVENT_TIMEOUT_SEC);
+ if (e->hread == NULL) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: httpread_create failed");
+ event_retry(e, 0);
+ goto bad;
+ }
+ return;
+
+bad:
+ /* Schedule sending more if there is more to send */
+ if (s->event_queue)
+ event_send_all_later(s->sm);
+ wpabuf_free(buf);
+}
+
+
+/* event_send_start -- prepare to send a event message to subscriber
+ *
+ * This gets complicated because:
+ * -- The message is sent via TCP and we have to keep the stream open
+ * for 30 seconds to get a response... then close it.
+ * -- But we might have other event happen in the meantime...
+ * we have to queue them, if we lose them then the subscriber will
+ * be forced to unsubscribe and subscribe again.
+ * -- If multiple URLs are provided then we are supposed to try successive
+ * ones after 30 second timeout.
+ * -- The URLs might use domain names instead of dotted decimal addresses,
+ * and resolution of those may cause unwanted sleeping.
+ * -- Doing the initial TCP connect can take a while, so we have to come
+ * back after connection and then send the data.
+ *
+ * Returns nonzero on error;
+ *
+ * Prerequisite: No current event send (s->current_event == NULL)
+ * and non-empty queue.
+ */
+static int event_send_start(struct subscription *s)
+{
+ struct wps_event_ *e;
+ int itry;
+
+ /*
+ * Assume we are called ONLY with no current event and ONLY with
+ * nonempty event queue and ONLY with at least one address to send to.
+ */
+ assert(s->addr_list != NULL);
+ assert(s->current_event == NULL);
+ assert(s->event_queue != NULL);
+
+ s->current_event = e = event_dequeue(s);
+
+ /* Use address acc. to no. of retries */
+ e->addr = s->addr_list;
+ for (itry = 0; itry < e->retry; itry++)
+ e->addr = e->addr->next;
+
+ e->sd = socket(AF_INET, SOCK_STREAM, 0);
+ if (e->sd < 0) {
+ event_retry(e, 0);
+ return -1;
+ }
+ /* set non-blocking so we don't sleep waiting for connection */
+ if (fcntl(e->sd, F_SETFL, O_NONBLOCK) != 0) {
+ event_retry(e, 0);
+ return -1;
+ }
+ /*
+ * Start the connect. It might succeed immediately but more likely will
+ * return errno EINPROGRESS.
+ */
+ if (connect(e->sd, (struct sockaddr *) &e->addr->saddr,
+ sizeof(e->addr->saddr))) {
+ if (errno == EINPROGRESS) {
+ } else {
+ event_retry(e, 1);
+ return -1;
+ }
+ }
+ /* Call back when ready for writing (or on failure...). */
+ if (eloop_register_sock(e->sd, EVENT_TYPE_WRITE, event_send_tx_ready,
+ NULL, e)) {
+ event_retry(e, 0);
+ return -1;
+ }
+ e->sd_registered = 1;
+ /* Don't wait forever! */
+ if (eloop_register_timeout(EVENT_TIMEOUT_SEC, 0, event_timeout_handler,
+ NULL, e)) {
+ event_retry(e, 0);
+ return -1;
+ }
+ return 0;
+}
+
+
+/* event_send_all_later_handler -- actually send events as needed */
+void event_send_all_later_handler(void *eloop_data, void *user_ctx)
+{
+ struct upnp_wps_device_sm *sm = user_ctx;
+ struct subscription *s;
+ struct subscription *s_old;
+ int nerrors = 0;
+
+ sm->event_send_all_queued = 0;
+ if ((s = sm->subscriptions) == NULL)
+ return;
+ do {
+ if (s->addr_list == NULL) {
+ /* if we've given up on all addresses */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Removing "
+ "subscription with no addresses");
+ s_old = s;
+ s = s_old->next;
+ subscription_unlink(s_old);
+ subscription_destroy(s_old);
+ } else {
+ if (s->current_event == NULL /* not busy */ &&
+ s->event_queue != NULL /* more to do */) {
+ if (event_send_start(s))
+ nerrors++;
+ }
+ s = s->next;
+ }
+ } while (sm->subscriptions != NULL && s != sm->subscriptions);
+
+ if (nerrors) {
+ /* Try again later */
+ event_send_all_later(sm);
+ }
+}
+
+
+/* event_send_all_later -- schedule sending events to all subscribers
+ * that need it.
+ * This avoids two problems:
+ * -- After getting a subscription, we should not send the first event
+ * until after our reply is fully queued to be sent back,
+ * -- Possible stack depth or infinite recursion issues.
+ */
+void event_send_all_later(struct upnp_wps_device_sm *sm)
+{
+ /*
+ * The exact time in the future isn't too important. Waiting a bit
+ * might let us do several together.
+ */
+ if (sm->event_send_all_queued)
+ return;
+ sm->event_send_all_queued = 1;
+ eloop_register_timeout(EVENT_DELAY_SECONDS, EVENT_DELAY_MSEC,
+ event_send_all_later_handler, NULL, sm);
+}
+
+
+/* event_send_stop_all -- cleanup */
+void event_send_stop_all(struct upnp_wps_device_sm *sm)
+{
+ if (sm->event_send_all_queued)
+ eloop_cancel_timeout(event_send_all_later_handler, NULL, sm);
+ sm->event_send_all_queued = 0;
+}
+
+
+/**
+ * event_add - Add a new event to a queue
+ * @s: Subscription
+ * @data: Event data (is copied; caller retains ownership)
+ * Returns: 0 on success, 1 on error
+ */
+int event_add(struct subscription *s, const struct wpabuf *data)
+{
+ struct wps_event_ *e;
+
+ if (s->n_queue >= MAX_EVENTS_QUEUED) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Too many events queued for "
+ "subscriber");
+ return 1;
+ }
+
+ e = os_zalloc(sizeof(*e));
+ if (e == NULL)
+ return 1;
+ e->s = s;
+ e->sd = -1;
+ e->data = wpabuf_dup(data);
+ if (e->data == NULL) {
+ os_free(e);
+ return 1;
+ }
+ e->subscriber_sequence = s->next_subscriber_sequence++;
+ if (s->next_subscriber_sequence == 0)
+ s->next_subscriber_sequence++;
+ event_enqueue_at_end(s, e);
+ event_send_all_later(s->sm);
+ return 0;
+}
diff --git a/src/wps/wps_upnp_i.h b/src/wps/wps_upnp_i.h
new file mode 100644
index 0000000..d4b6569
--- /dev/null
+++ b/src/wps/wps_upnp_i.h
@@ -0,0 +1,193 @@
+/*
+ * UPnP for WPS / internal definitions
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#ifndef WPS_UPNP_I_H
+#define WPS_UPNP_I_H
+
+#define UPNP_MULTICAST_ADDRESS "239.255.255.250" /* for UPnP multicasting */
+#define UPNP_MULTICAST_PORT 1900 /* UDP port to monitor for UPnP */
+
+/* min subscribe time per UPnP standard */
+#define UPNP_SUBSCRIBE_SEC_MIN 1800
+/* subscribe time we use */
+#define UPNP_SUBSCRIBE_SEC (UPNP_SUBSCRIBE_SEC_MIN + 1)
+
+/* "filenames" used in URLs that we service via our "web server": */
+#define UPNP_WPS_DEVICE_XML_FILE "wps_device.xml"
+#define UPNP_WPS_SCPD_XML_FILE "wps_scpd.xml"
+#define UPNP_WPS_DEVICE_CONTROL_FILE "wps_control"
+#define UPNP_WPS_DEVICE_EVENT_FILE "wps_event"
+
+
+struct web_connection;
+struct subscription;
+struct upnp_wps_device_sm;
+
+
+enum http_reply_code {
+ HTTP_OK = 200,
+ HTTP_BAD_REQUEST = 400,
+ UPNP_INVALID_ACTION = 401,
+ UPNP_INVALID_ARGS = 402,
+ HTTP_PRECONDITION_FAILED = 412,
+ HTTP_INTERNAL_SERVER_ERROR = 500,
+ HTTP_UNIMPLEMENTED = 501,
+ UPNP_ACTION_FAILED = 501,
+ UPNP_ARG_VALUE_INVALID = 600,
+ UPNP_ARG_VALUE_OUT_OF_RANGE = 601,
+ UPNP_OUT_OF_MEMORY = 603
+};
+
+
+enum advertisement_type_enum {
+ ADVERTISE_UP = 0,
+ ADVERTISE_DOWN = 1,
+ MSEARCH_REPLY = 2
+};
+
+/*
+ * Advertisements are broadcast via UDP NOTIFYs, and are also the essence of
+ * the reply to UDP M-SEARCH requests. This struct handles both cases.
+ *
+ * A state machine is needed because a number of variant forms must be sent in
+ * separate packets and spread out in time to avoid congestion.
+ */
+struct advertisement_state_machine {
+ /* double-linked list */
+ struct advertisement_state_machine *next;
+ struct advertisement_state_machine *prev;
+ struct upnp_wps_device_sm *sm; /* parent */
+ enum advertisement_type_enum type;
+ int state;
+ int nerrors;
+ struct sockaddr_in client; /* for M-SEARCH replies */
+};
+
+
+/*
+ * An address of a subscriber (who may have multiple addresses). We are
+ * supposed to send (via TCP) updates to each subscriber, trying each address
+ * for a subscriber until we find one that seems to work.
+ */
+struct subscr_addr {
+ /* double linked list */
+ struct subscr_addr *next;
+ struct subscr_addr *prev;
+ struct subscription *s; /* parent */
+ char *domain_and_port; /* domain and port part of url */
+ char *path; /* "filepath" part of url (from "mem") */
+ struct sockaddr_in saddr; /* address for doing connect */
+};
+
+
+/*
+ * Subscribers to our events are recorded in this struct. This includes a max
+ * of one outgoing connection (sending an "event message") per subscriber. We
+ * also have to age out subscribers unless they renew.
+ */
+struct subscription {
+ /* double linked list */
+ struct subscription *next;
+ struct subscription *prev;
+ struct upnp_wps_device_sm *sm; /* parent */
+ time_t timeout_time; /* when to age out the subscription */
+ unsigned next_subscriber_sequence; /* number our messages */
+ /*
+ * This uuid identifies the subscription and is randomly generated by
+ * us and given to the subscriber when the subscription is accepted;
+ * and is then included with each event sent to the subscriber.
+ */
+ u8 uuid[UUID_LEN];
+ /* Linked list of address alternatives (rotate through on failure) */
+ struct subscr_addr *addr_list;
+ int n_addr; /* Number of addresses in list */
+ struct wps_event_ *event_queue; /* Queued event messages. */
+ int n_queue; /* How many events are queued */
+ struct wps_event_ *current_event; /* non-NULL if being sent (not in q)
+ */
+};
+
+
+/*
+ * Our instance data corresponding to one WiFi network interface
+ * (multiple might share the same wired network interface!).
+ *
+ * This is known as an opaque struct declaration to users of the WPS UPnP code.
+ */
+struct upnp_wps_device_sm {
+ struct upnp_wps_device_ctx *ctx; /* callback table */
+ struct wps_context *wps;
+ void *priv;
+ char *root_dir;
+ char *desc_url;
+ int started; /* nonzero if we are active */
+ char *net_if; /* network interface we use */
+ char *mac_addr_text; /* mac addr of network i.f. we use */
+ u8 mac_addr[ETH_ALEN]; /* mac addr of network i.f. we use */
+ char *ip_addr_text; /* IP address of network i.f. we use */
+ unsigned ip_addr; /* IP address of network i.f. we use (host order) */
+ int multicast_sd; /* send multicast messages over this socket */
+ int ssdp_sd; /* receive discovery UPD packets on socket */
+ int ssdp_sd_registered; /* nonzero if we must unregister */
+ unsigned advertise_count; /* how many advertisements done */
+ struct advertisement_state_machine advertisement;
+ struct advertisement_state_machine *msearch_replies;
+ int n_msearch_replies; /* no. of pending M-SEARCH replies */
+ int web_port; /* our port that others get xml files from */
+ int web_sd; /* socket to listen for web requests */
+ int web_sd_registered; /* nonzero if we must cancel registration */
+ struct web_connection *web_connections; /* linked list */
+ int n_web_connections; /* no. of pending web connections */
+ /* Note: subscriptions are kept in expiry order */
+ struct subscription *subscriptions; /* linked list */
+ int n_subscriptions; /* no of current subscriptions */
+ int event_send_all_queued; /* if we are scheduled to send events soon
+ */
+
+ char *wlanevent; /* the last WLANEvent data */
+
+ /* FIX: maintain separate structures for each UPnP peer */
+ struct upnp_wps_peer peer;
+};
+
+/* wps_upnp.c */
+void format_date(struct wpabuf *buf);
+struct subscription * subscription_start(struct upnp_wps_device_sm *sm,
+ char *callback_urls);
+struct subscription * subscription_renew(struct upnp_wps_device_sm *sm,
+ const u8 uuid[UUID_LEN]);
+void subscription_unlink(struct subscription *s);
+void subscription_destroy(struct subscription *s);
+struct subscription * subscription_find(struct upnp_wps_device_sm *sm,
+ const u8 uuid[UUID_LEN]);
+int send_wpabuf(int fd, struct wpabuf *buf);
+
+/* wps_upnp_ssdp.c */
+void msearchreply_state_machine_stop(struct advertisement_state_machine *a);
+int advertisement_state_machine_start(struct upnp_wps_device_sm *sm);
+void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm);
+void ssdp_listener_stop(struct upnp_wps_device_sm *sm);
+int ssdp_listener_start(struct upnp_wps_device_sm *sm);
+int add_ssdp_network(char *net_if);
+int ssdp_open_multicast(struct upnp_wps_device_sm *sm);
+
+/* wps_upnp_web.c */
+void web_connection_stop(struct web_connection *c);
+int web_listener_start(struct upnp_wps_device_sm *sm);
+void web_listener_stop(struct upnp_wps_device_sm *sm);
+
+/* wps_upnp_event.c */
+int event_add(struct subscription *s, const struct wpabuf *data);
+void event_delete(struct wps_event_ *e);
+void event_delete_all(struct subscription *s);
+void event_send_all_later(struct upnp_wps_device_sm *sm);
+void event_send_stop_all(struct upnp_wps_device_sm *sm);
+
+#endif /* WPS_UPNP_I_H */
diff --git a/src/wps/wps_upnp_ssdp.c b/src/wps/wps_upnp_ssdp.c
new file mode 100644
index 0000000..71dcc96
--- /dev/null
+++ b/src/wps/wps_upnp_ssdp.c
@@ -0,0 +1,887 @@
+/*
+ * UPnP SSDP for WPS
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#include "includes.h"
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <net/route.h>
+
+#include "common.h"
+#include "uuid.h"
+#include "eloop.h"
+#include "wps.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+#define UPNP_CACHE_SEC (UPNP_CACHE_SEC_MIN + 1) /* cache time we use */
+#define UPNP_CACHE_SEC_MIN 1800 /* min cachable time per UPnP standard */
+#define UPNP_ADVERTISE_REPEAT 2 /* no more than 3 */
+#define MULTICAST_MAX_READ 1600 /* max bytes we'll read for UPD request */
+#define MAX_MSEARCH 20 /* max simultaneous M-SEARCH replies ongoing */
+#define SSDP_TARGET "239.0.0.0"
+#define SSDP_NETMASK "255.0.0.0"
+
+
+/* Check tokens for equality, where tokens consist of letters, digits,
+ * underscore and hyphen, and are matched case insensitive.
+ */
+static int token_eq(const char *s1, const char *s2)
+{
+ int c1;
+ int c2;
+ int end1 = 0;
+ int end2 = 0;
+ for (;;) {
+ c1 = *s1++;
+ c2 = *s2++;
+ if (isalpha(c1) && isupper(c1))
+ c1 = tolower(c1);
+ if (isalpha(c2) && isupper(c2))
+ c2 = tolower(c2);
+ end1 = !(isalnum(c1) || c1 == '_' || c1 == '-');
+ end2 = !(isalnum(c2) || c2 == '_' || c2 == '-');
+ if (end1 || end2 || c1 != c2)
+ break;
+ }
+ return (end1 && end2); /* reached end of both words? */
+}
+
+
+/* Return length of token (see above for definition of token) */
+static int token_length(const char *s)
+{
+ const char *begin = s;
+ for (;; s++) {
+ int c = *s;
+ int end = !(isalnum(c) || c == '_' || c == '-');
+ if (end)
+ break;
+ }
+ return s - begin;
+}
+
+
+/* return length of interword separation.
+ * This accepts only spaces/tabs and thus will not traverse a line
+ * or buffer ending.
+ */
+static int word_separation_length(const char *s)
+{
+ const char *begin = s;
+ for (;; s++) {
+ int c = *s;
+ if (c == ' ' || c == '\t')
+ continue;
+ break;
+ }
+ return s - begin;
+}
+
+
+/* No. of chars through (including) end of line */
+static int line_length(const char *l)
+{
+ const char *lp = l;
+ while (*lp && *lp != '\n')
+ lp++;
+ if (*lp == '\n')
+ lp++;
+ return lp - l;
+}
+
+
+/* No. of chars excluding trailing whitespace */
+static int line_length_stripped(const char *l)
+{
+ const char *lp = l + line_length(l);
+ while (lp > l && !isgraph(lp[-1]))
+ lp--;
+ return lp - l;
+}
+
+
+static int str_starts(const char *str, const char *start)
+{
+ return os_strncmp(str, start, os_strlen(start)) == 0;
+}
+
+
+/***************************************************************************
+ * Advertisements.
+ * These are multicast to the world to tell them we are here.
+ * The individual packets are spread out in time to limit loss,
+ * and then after a much longer period of time the whole sequence
+ * is repeated again (for NOTIFYs only).
+ **************************************************************************/
+
+/**
+ * next_advertisement - Build next message and advance the state machine
+ * @a: Advertisement state
+ * @islast: Buffer for indicating whether this is the last message (= 1)
+ * Returns: The new message (caller is responsible for freeing this)
+ *
+ * Note: next_advertisement is shared code with msearchreply_* functions
+ */
+static struct wpabuf *
+next_advertisement(struct advertisement_state_machine *a, int *islast)
+{
+ struct wpabuf *msg;
+ char *NTString = "";
+ char uuid_string[80];
+
+ *islast = 0;
+ uuid_bin2str(a->sm->wps->uuid, uuid_string, sizeof(uuid_string));
+ msg = wpabuf_alloc(800); /* more than big enough */
+ if (msg == NULL)
+ goto fail;
+ switch (a->type) {
+ case ADVERTISE_UP:
+ case ADVERTISE_DOWN:
+ NTString = "NT";
+ wpabuf_put_str(msg, "NOTIFY * HTTP/1.1\r\n");
+ wpabuf_printf(msg, "HOST: %s:%d\r\n",
+ UPNP_MULTICAST_ADDRESS, UPNP_MULTICAST_PORT);
+ wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
+ UPNP_CACHE_SEC);
+ wpabuf_printf(msg, "NTS: %s\r\n",
+ (a->type == ADVERTISE_UP ?
+ "ssdp:alive" : "ssdp:byebye"));
+ break;
+ case MSEARCH_REPLY:
+ NTString = "ST";
+ wpabuf_put_str(msg, "HTTP/1.1 200 OK\r\n");
+ wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
+ UPNP_CACHE_SEC);
+
+ wpabuf_put_str(msg, "DATE: ");
+ format_date(msg);
+ wpabuf_put_str(msg, "\r\n");
+
+ wpabuf_put_str(msg, "EXT:\r\n");
+ break;
+ }
+
+ if (a->type != ADVERTISE_DOWN) {
+ /* Where others may get our XML files from */
+ wpabuf_printf(msg, "LOCATION: http://%s:%d/%s\r\n",
+ a->sm->ip_addr_text, a->sm->web_port,
+ UPNP_WPS_DEVICE_XML_FILE);
+ }
+
+ /* The SERVER line has three comma-separated fields:
+ * operating system / version
+ * upnp version
+ * software package / version
+ * However, only the UPnP version is really required, the
+ * others can be place holders... for security reasons
+ * it is better to NOT provide extra information.
+ */
+ wpabuf_put_str(msg, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n");
+
+ switch (a->state / UPNP_ADVERTISE_REPEAT) {
+ case 0:
+ wpabuf_printf(msg, "%s: upnp:rootdevice\r\n", NTString);
+ wpabuf_printf(msg, "USN: uuid:%s::upnp:rootdevice\r\n",
+ uuid_string);
+ break;
+ case 1:
+ wpabuf_printf(msg, "%s: uuid:%s\r\n", NTString, uuid_string);
+ wpabuf_printf(msg, "USN: uuid:%s\r\n", uuid_string);
+ break;
+ case 2:
+ wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:device:"
+ "WFADevice:1\r\n", NTString);
+ wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
+ "org:device:WFADevice:1\r\n", uuid_string);
+ break;
+ case 3:
+ wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:service:"
+ "WFAWLANConfig:1\r\n", NTString);
+ wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
+ "org:service:WFAWLANConfig:1\r\n", uuid_string);
+ break;
+ }
+ wpabuf_put_str(msg, "\r\n");
+
+ if (a->state + 1 >= 4 * UPNP_ADVERTISE_REPEAT)
+ *islast = 1;
+
+ return msg;
+
+fail:
+ wpabuf_free(msg);
+ return NULL;
+}
+
+
+static void advertisement_state_machine_handler(void *eloop_data,
+ void *user_ctx);
+
+
+/**
+ * advertisement_state_machine_stop - Stop SSDP advertisements
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ */
+void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm)
+{
+ eloop_cancel_timeout(advertisement_state_machine_handler, NULL, sm);
+}
+
+
+static void advertisement_state_machine_handler(void *eloop_data,
+ void *user_ctx)
+{
+ struct upnp_wps_device_sm *sm = user_ctx;
+ struct advertisement_state_machine *a = &sm->advertisement;
+ struct wpabuf *msg;
+ int next_timeout_msec = 100;
+ int next_timeout_sec = 0;
+ struct sockaddr_in dest;
+ int islast = 0;
+
+ /*
+ * Each is sent twice (in case lost) w/ 100 msec delay between;
+ * spec says no more than 3 times.
+ * One pair for rootdevice, one pair for uuid, and a pair each for
+ * each of the two urns.
+ * The entire sequence must be repeated before cache control timeout
+ * (which is min 1800 seconds),
+ * recommend random portion of half of the advertised cache control age
+ * to ensure against loss... perhaps 1800/4 + rand*1800/4 ?
+ * Delay random interval < 100 msec prior to initial sending.
+ * TTL of 4
+ */
+
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: Advertisement state=%d", a->state);
+ msg = next_advertisement(a, &islast);
+ if (msg == NULL)
+ return;
+
+ os_memset(&dest, 0, sizeof(dest));
+ dest.sin_family = AF_INET;
+ dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
+ dest.sin_port = htons(UPNP_MULTICAST_PORT);
+
+ if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
+ (struct sockaddr *) &dest, sizeof(dest)) == -1) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: Advertisement sendto failed:"
+ "%d (%s)", errno, strerror(errno));
+ next_timeout_msec = 0;
+ next_timeout_sec = 10; /* ... later */
+ } else if (islast) {
+ a->state = 0; /* wrap around */
+ if (a->type == ADVERTISE_DOWN) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_DOWN->UP");
+ a->type = ADVERTISE_UP;
+ /* do it all over again right away */
+ } else {
+ u16 r;
+ /*
+ * Start over again after a long timeout
+ * (see notes above)
+ */
+ next_timeout_msec = 0;
+ os_get_random((void *) &r, sizeof(r));
+ next_timeout_sec = UPNP_CACHE_SEC / 4 +
+ (((UPNP_CACHE_SEC / 4) * r) >> 16);
+ sm->advertise_count++;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_UP (#%u); "
+ "next in %d sec",
+ sm->advertise_count, next_timeout_sec);
+ }
+ } else {
+ a->state++;
+ }
+
+ wpabuf_free(msg);
+
+ eloop_register_timeout(next_timeout_sec, next_timeout_msec,
+ advertisement_state_machine_handler, NULL, sm);
+}
+
+
+/**
+ * advertisement_state_machine_start - Start SSDP advertisements
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 on success, -1 on failure
+ */
+int advertisement_state_machine_start(struct upnp_wps_device_sm *sm)
+{
+ struct advertisement_state_machine *a = &sm->advertisement;
+ int next_timeout_msec;
+
+ advertisement_state_machine_stop(sm);
+
+ /*
+ * Start out advertising down, this automatically switches
+ * to advertising up which signals our restart.
+ */
+ a->type = ADVERTISE_DOWN;
+ a->state = 0;
+ a->sm = sm;
+ /* (other fields not used here) */
+
+ /* First timeout should be random interval < 100 msec */
+ next_timeout_msec = (100 * (os_random() & 0xFF)) >> 8;
+ return eloop_register_timeout(0, next_timeout_msec,
+ advertisement_state_machine_handler,
+ NULL, sm);
+}
+
+
+/***************************************************************************
+ * M-SEARCH replies
+ * These are very similar to the multicast advertisements, with some
+ * small changes in data content; and they are sent (UDP) to a specific
+ * unicast address instead of multicast.
+ * They are sent in response to a UDP M-SEARCH packet.
+ **************************************************************************/
+
+static void msearchreply_state_machine_handler(void *eloop_data,
+ void *user_ctx);
+
+
+/**
+ * msearchreply_state_machine_stop - Stop M-SEARCH reply state machine
+ * @a: Selected advertisement/reply state
+ */
+void msearchreply_state_machine_stop(struct advertisement_state_machine *a)
+{
+ struct upnp_wps_device_sm *sm = a->sm;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH stop");
+ if (a->next == a) {
+ sm->msearch_replies = NULL;
+ } else {
+ if (sm->msearch_replies == a) {
+ sm->msearch_replies = a->next;
+ }
+ a->next->prev = a->prev;
+ a->prev->next = a->next;
+ }
+ os_free(a);
+ sm->n_msearch_replies--;
+}
+
+
+static void msearchreply_state_machine_handler(void *eloop_data,
+ void *user_ctx)
+{
+ struct advertisement_state_machine *a = user_ctx;
+ struct upnp_wps_device_sm *sm = a->sm;
+ struct wpabuf *msg;
+ int next_timeout_msec = 100;
+ int next_timeout_sec = 0;
+ int islast = 0;
+
+ /*
+ * Each response is sent twice (in case lost) w/ 100 msec delay
+ * between; spec says no more than 3 times.
+ * One pair for rootdevice, one pair for uuid, and a pair each for
+ * each of the two urns.
+ */
+
+ /* TODO: should only send the requested response types */
+
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply state=%d (%s:%d)",
+ a->state, inet_ntoa(a->client.sin_addr),
+ ntohs(a->client.sin_port));
+ msg = next_advertisement(a, &islast);
+ if (msg == NULL)
+ return;
+
+ /*
+ * Send it on the multicast socket to avoid having to set up another
+ * socket.
+ */
+ if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
+ (struct sockaddr *) &a->client, sizeof(a->client)) < 0) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply sendto "
+ "errno %d (%s) for %s:%d",
+ errno, strerror(errno),
+ inet_ntoa(a->client.sin_addr),
+ ntohs(a->client.sin_port));
+ /* Ignore error and hope for the best */
+ }
+ wpabuf_free(msg);
+ if (islast) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply done");
+ msearchreply_state_machine_stop(a);
+ return;
+ }
+ a->state++;
+
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply in %d.%03d sec",
+ next_timeout_sec, next_timeout_msec);
+ eloop_register_timeout(next_timeout_sec, next_timeout_msec,
+ msearchreply_state_machine_handler, sm, a);
+}
+
+
+/**
+ * msearchreply_state_machine_start - Reply to M-SEARCH discovery request
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @client: Client address
+ * @mx: Maximum delay in seconds
+ *
+ * Use TTL of 4 (this was done when socket set up).
+ * A response should be given in randomized portion of min(MX,120) seconds
+ *
+ * UPnP-arch-DeviceArchitecture, 1.2.3:
+ * To be found, a device must send a UDP response to the source IP address and
+ * port that sent the request to the multicast channel. Devices respond if the
+ * ST header of the M-SEARCH request is "ssdp:all", "upnp:rootdevice", "uuid:"
+ * followed by a UUID that exactly matches one advertised by the device.
+ */
+static void msearchreply_state_machine_start(struct upnp_wps_device_sm *sm,
+ struct sockaddr_in *client,
+ int mx)
+{
+ struct advertisement_state_machine *a;
+ int next_timeout_sec;
+ int next_timeout_msec;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply start (%d "
+ "outstanding)", sm->n_msearch_replies);
+ if (sm->n_msearch_replies >= MAX_MSEARCH) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Too many outstanding "
+ "M-SEARCH replies");
+ return;
+ }
+
+ a = os_zalloc(sizeof(*a));
+ if (a == NULL)
+ return;
+ a->type = MSEARCH_REPLY;
+ a->state = 0;
+ a->sm = sm;
+ os_memcpy(&a->client, client, sizeof(client));
+ /* Wait time depending on MX value */
+ next_timeout_msec = (1000 * mx * (os_random() & 0xFF)) >> 8;
+ next_timeout_sec = next_timeout_msec / 1000;
+ next_timeout_msec = next_timeout_msec % 1000;
+ if (eloop_register_timeout(next_timeout_sec, next_timeout_msec,
+ msearchreply_state_machine_handler, sm,
+ a)) {
+ /* No way to recover (from malloc failure) */
+ goto fail;
+ }
+ /* Remember for future cleanup */
+ if (sm->msearch_replies) {
+ a->next = sm->msearch_replies;
+ a->prev = a->next->prev;
+ a->prev->next = a;
+ a->next->prev = a;
+ } else {
+ sm->msearch_replies = a->next = a->prev = a;
+ }
+ sm->n_msearch_replies++;
+ return;
+
+fail:
+ wpa_printf(MSG_INFO, "WPS UPnP: M-SEARCH reply failure!");
+ eloop_cancel_timeout(msearchreply_state_machine_handler, sm, a);
+ os_free(a);
+}
+
+
+/**
+ * ssdp_parse_msearch - Process a received M-SEARCH
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @client: Client address
+ * @data: NULL terminated M-SEARCH message
+ *
+ * Given that we have received a header w/ M-SEARCH, act upon it
+ *
+ * Format of M-SEARCH (case insensitive!):
+ *
+ * First line must be:
+ * M-SEARCH * HTTP/1.1
+ * Other lines in arbitrary order:
+ * HOST:239.255.255.250:1900
+ * ST:<varies -- must match>
+ * MAN:"ssdp:discover"
+ * MX:<varies>
+ *
+ * It should be noted that when Microsoft Vista is still learning its IP
+ * address, it sends out host lines like: HOST:[FF02::C]:1900
+ */
+static void ssdp_parse_msearch(struct upnp_wps_device_sm *sm,
+ struct sockaddr_in *client, const char *data)
+{
+ const char *start = data;
+ const char *end;
+ int got_host = 0;
+ int got_st = 0, st_match = 0;
+ int got_man = 0;
+ int got_mx = 0;
+ int mx = 0;
+
+ /*
+ * Skip first line M-SEARCH * HTTP/1.1
+ * (perhaps we should check remainder of the line for syntax)
+ */
+ data += line_length(data);
+
+ /* Parse remaining lines */
+ for (; *data != '\0'; data += line_length(data)) {
+ end = data + line_length_stripped(data);
+ if (token_eq(data, "host")) {
+ /* The host line indicates who the packet
+ * is addressed to... but do we really care?
+ * Note that Microsoft sometimes does funny
+ * stuff with the HOST: line.
+ */
+#if 0 /* could be */
+ data += token_length(data);
+ data += word_separation_length(data);
+ if (*data != ':')
+ goto bad;
+ data++;
+ data += word_separation_length(data);
+ /* UPNP_MULTICAST_ADDRESS */
+ if (!str_starts(data, "239.255.255.250"))
+ goto bad;
+ data += os_strlen("239.255.255.250");
+ if (*data == ':') {
+ if (!str_starts(data, ":1900"))
+ goto bad;
+ }
+#endif /* could be */
+ got_host = 1;
+ continue;
+ } else if (token_eq(data, "st")) {
+ /* There are a number of forms; we look
+ * for one that matches our case.
+ */
+ got_st = 1;
+ data += token_length(data);
+ data += word_separation_length(data);
+ if (*data != ':')
+ continue;
+ data++;
+ data += word_separation_length(data);
+ if (str_starts(data, "ssdp:all")) {
+ st_match = 1;
+ continue;
+ }
+ if (str_starts(data, "upnp:rootdevice")) {
+ st_match = 1;
+ continue;
+ }
+ if (str_starts(data, "uuid:")) {
+ char uuid_string[80];
+ data += os_strlen("uuid:");
+ uuid_bin2str(sm->wps->uuid, uuid_string,
+ sizeof(uuid_string));
+ if (str_starts(data, uuid_string))
+ st_match = 1;
+ continue;
+ }
+#if 0
+ /* FIX: should we really reply to IGD string? */
+ if (str_starts(data, "urn:schemas-upnp-org:device:"
+ "InternetGatewayDevice:1")) {
+ st_match = 1;
+ continue;
+ }
+#endif
+ if (str_starts(data, "urn:schemas-wifialliance-org:"
+ "service:WFAWLANConfig:1")) {
+ st_match = 1;
+ continue;
+ }
+ if (str_starts(data, "urn:schemas-wifialliance-org:"
+ "device:WFADevice:1")) {
+ st_match = 1;
+ continue;
+ }
+ continue;
+ } else if (token_eq(data, "man")) {
+ data += token_length(data);
+ data += word_separation_length(data);
+ if (*data != ':')
+ continue;
+ data++;
+ data += word_separation_length(data);
+ if (!str_starts(data, "\"ssdp:discover\"")) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Unexpected "
+ "M-SEARCH man-field");
+ goto bad;
+ }
+ got_man = 1;
+ continue;
+ } else if (token_eq(data, "mx")) {
+ data += token_length(data);
+ data += word_separation_length(data);
+ if (*data != ':')
+ continue;
+ data++;
+ data += word_separation_length(data);
+ mx = atol(data);
+ got_mx = 1;
+ continue;
+ }
+ /* ignore anything else */
+ }
+ if (!got_host || !got_st || !got_man || !got_mx || mx < 0) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid M-SEARCH: %d %d %d "
+ "%d mx=%d", got_host, got_st, got_man, got_mx, mx);
+ goto bad;
+ }
+ if (!st_match) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored M-SEARCH (no ST "
+ "match)");
+ return;
+ }
+ if (mx > 120)
+ mx = 120; /* UPnP-arch-DeviceArchitecture, 1.2.3 */
+ msearchreply_state_machine_start(sm, client, mx);
+ return;
+
+bad:
+ wpa_printf(MSG_INFO, "WPS UPnP: Failed to parse M-SEARCH");
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH data:\n%s", start);
+}
+
+
+/* Listening for (UDP) discovery (M-SEARCH) packets */
+
+/**
+ * ssdp_listener_stop - Stop SSDP listered
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ *
+ * This function stops the SSDP listerner that was started by calling
+ * ssdp_listener_start().
+ */
+void ssdp_listener_stop(struct upnp_wps_device_sm *sm)
+{
+ if (sm->ssdp_sd_registered) {
+ eloop_unregister_sock(sm->ssdp_sd, EVENT_TYPE_READ);
+ sm->ssdp_sd_registered = 0;
+ }
+
+ if (sm->ssdp_sd != -1) {
+ close(sm->ssdp_sd);
+ sm->ssdp_sd = -1;
+ }
+
+ eloop_cancel_timeout(msearchreply_state_machine_handler, sm,
+ ELOOP_ALL_CTX);
+}
+
+
+static void ssdp_listener_handler(int sd, void *eloop_ctx, void *sock_ctx)
+{
+ struct upnp_wps_device_sm *sm = sock_ctx;
+ struct sockaddr_in addr; /* client address */
+ socklen_t addr_len;
+ int nread;
+ char buf[MULTICAST_MAX_READ], *pos;
+
+ addr_len = sizeof(addr);
+ nread = recvfrom(sm->ssdp_sd, buf, sizeof(buf) - 1, 0,
+ (struct sockaddr *) &addr, &addr_len);
+ if (nread <= 0)
+ return;
+ buf[nread] = '\0'; /* need null termination for algorithm */
+
+ if (str_starts(buf, "NOTIFY ")) {
+ /*
+ * Silently ignore NOTIFYs to avoid filling debug log with
+ * unwanted messages.
+ */
+ return;
+ }
+
+ pos = os_strchr(buf, '\n');
+ if (pos)
+ *pos = '\0';
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: Received SSDP packet from %s:%d: "
+ "%s", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), buf);
+ if (pos)
+ *pos = '\n';
+
+ /* Parse first line */
+ if (os_strncasecmp(buf, "M-SEARCH", os_strlen("M-SEARCH")) == 0 &&
+ !isgraph(buf[strlen("M-SEARCH")])) {
+ ssdp_parse_msearch(sm, &addr, buf);
+ return;
+ }
+
+ /* Ignore anything else */
+}
+
+
+/**
+ * ssdp_listener_start - Set up for receiving discovery (UDP) packets
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 on success, -1 on failure
+ *
+ * The SSDP listerner is stopped by calling ssdp_listener_stop().
+ */
+int ssdp_listener_start(struct upnp_wps_device_sm *sm)
+{
+ int sd = -1;
+ struct sockaddr_in addr;
+ struct ip_mreq mcast_addr;
+ int on = 1;
+ /* per UPnP spec, keep IP packet time to live (TTL) small */
+ unsigned char ttl = 4;
+
+ sm->ssdp_sd = sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0)
+ goto fail;
+ if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0)
+ goto fail;
+ if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
+ goto fail;
+ os_memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(UPNP_MULTICAST_PORT);
+ if (bind(sd, (struct sockaddr *) &addr, sizeof(addr)))
+ goto fail;
+ os_memset(&mcast_addr, 0, sizeof(mcast_addr));
+ mcast_addr.imr_interface.s_addr = htonl(INADDR_ANY);
+ mcast_addr.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
+ if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ (char *) &mcast_addr, sizeof(mcast_addr)))
+ goto fail;
+ if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
+ &ttl, sizeof(ttl)))
+ goto fail;
+ if (eloop_register_sock(sd, EVENT_TYPE_READ, ssdp_listener_handler,
+ NULL, sm))
+ goto fail;
+ sm->ssdp_sd_registered = 1;
+ return 0;
+
+fail:
+ /* Error */
+ wpa_printf(MSG_ERROR, "WPS UPnP: ssdp_listener_start failed");
+ ssdp_listener_stop(sm);
+ return -1;
+}
+
+
+/**
+ * add_ssdp_network - Add routing entry for SSDP
+ * @net_if: Selected network interface name
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function assures that the multicast address will be properly
+ * handled by Linux networking code (by a modification to routing tables).
+ * This must be done per network interface. It really only needs to be done
+ * once after booting up, but it does not hurt to call this more frequently
+ * "to be safe".
+ */
+int add_ssdp_network(char *net_if)
+{
+ int ret = -1;
+ int sock = -1;
+ struct rtentry rt;
+ struct sockaddr_in *sin;
+
+ if (!net_if)
+ goto fail;
+
+ os_memset(&rt, 0, sizeof(rt));
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ goto fail;
+
+ rt.rt_dev = net_if;
+ sin = (struct sockaddr_in *) &rt.rt_dst;
+ sin->sin_family = AF_INET;
+ sin->sin_port = 0;
+ sin->sin_addr.s_addr = inet_addr(SSDP_TARGET);
+ sin = (struct sockaddr_in *) &rt.rt_genmask;
+ sin->sin_family = AF_INET;
+ sin->sin_port = 0;
+ sin->sin_addr.s_addr = inet_addr(SSDP_NETMASK);
+ rt.rt_flags = RTF_UP;
+ if (ioctl(sock, SIOCADDRT, &rt) < 0) {
+ if (errno == EPERM) {
+ wpa_printf(MSG_DEBUG, "add_ssdp_network: No "
+ "permissions to add routing table entry");
+ /* Continue to allow testing as non-root */
+ } else if (errno != EEXIST) {
+ wpa_printf(MSG_INFO, "add_ssdp_network() ioctl errno "
+ "%d (%s)", errno, strerror(errno));
+ goto fail;
+ }
+ }
+
+ ret = 0;
+
+fail:
+ if (sock >= 0)
+ close(sock);
+
+ return ret;
+}
+
+
+/**
+ * ssdp_open_multicast - Open socket for sending multicast SSDP messages
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 on success, -1 on failure
+ */
+int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
+{
+ int sd = -1;
+ /* per UPnP-arch-DeviceArchitecture, 1. Discovery, keep IP packet
+ * time to live (TTL) small */
+ unsigned char ttl = 4;
+
+ sm->multicast_sd = sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0)
+ return -1;
+
+#if 0 /* maybe ok if we sometimes block on writes */
+ if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0)
+ return -1;
+#endif
+
+ if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF,
+ &sm->ip_addr, sizeof(sm->ip_addr)))
+ return -1;
+ if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
+ &ttl, sizeof(ttl)))
+ return -1;
+
+#if 0 /* not needed, because we don't receive using multicast_sd */
+ {
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
+ mreq.imr_interface.s_addr = sm->ip_addr;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Multicast addr 0x%x if addr "
+ "0x%x",
+ mreq.imr_multiaddr.s_addr,
+ mreq.imr_interface.s_addr);
+ if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
+ sizeof(mreq))) {
+ wpa_printf(MSG_ERROR,
+ "WPS UPnP: setsockopt "
+ "IP_ADD_MEMBERSHIP errno %d (%s)",
+ errno, strerror(errno));
+ return -1;
+ }
+ }
+#endif /* not needed */
+
+ /*
+ * TODO: What about IP_MULTICAST_LOOP? It seems to be on by default?
+ * which aids debugging I suppose but isn't really necessary?
+ */
+
+ return 0;
+}
diff --git a/src/wps/wps_upnp_web.c b/src/wps/wps_upnp_web.c
new file mode 100644
index 0000000..2e0f296
--- /dev/null
+++ b/src/wps/wps_upnp_web.c
@@ -0,0 +1,1959 @@
+/*
+ * UPnP WPS Device - Web connections
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#include "includes.h"
+#include <fcntl.h>
+
+#include "common.h"
+#include "base64.h"
+#include "eloop.h"
+#include "uuid.h"
+#include "httpread.h"
+#include "wps_i.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+/***************************************************************************
+ * Web connections (we serve pages of info about ourselves, handle
+ * requests, etc. etc.).
+ **************************************************************************/
+
+#define WEB_CONNECTION_TIMEOUT_SEC 30 /* Drop web connection after t.o. */
+#define WEB_CONNECTION_MAX_READ 8000 /* Max we'll read for TCP request */
+#define MAX_WEB_CONNECTIONS 10 /* max simultaneous web connects */
+
+
+static const char *urn_wfawlanconfig =
+ "urn:schemas-wifialliance-org:service:WFAWLANConfig:1";
+static const char *http_server_hdr =
+ "Server: unspecified, UPnP/1.0, unspecified\r\n";
+static const char *http_connection_close =
+ "Connection: close\r\n";
+
+/*
+ * Incoming web connections are recorded in this struct.
+ * A web connection is a TCP connection to us, the server;
+ * it is called a "web connection" because we use http and serve
+ * data that looks like web pages.
+ * State information is need to track the connection until we figure
+ * out what they want and what we want to do about it.
+ */
+struct web_connection {
+ /* double linked list */
+ struct web_connection *next;
+ struct web_connection *prev;
+ struct upnp_wps_device_sm *sm; /* parent */
+ int sd; /* socket to read from */
+ int sd_registered; /* nonzero if we must cancel registration */
+ struct httpread *hread; /* state machine for reading socket */
+ int n_rcvd_data; /* how much data read so far */
+ int done; /* internal flag, set when we've finished */
+};
+
+
+/*
+ * XML parsing and formatting
+ *
+ * XML is a markup language based on unicode; usually (and in our case,
+ * always!) based on utf-8. utf-8 uses a variable number of bytes per
+ * character. utf-8 has the advantage that all non-ASCII unicode characters are
+ * represented by sequences of non-ascii (high bit set) bytes, whereas ASCII
+ * characters are single ascii bytes, thus we can use typical text processing.
+ *
+ * (One other interesting thing about utf-8 is that it is possible to look at
+ * any random byte and determine if it is the first byte of a character as
+ * versus a continuation byte).
+ *
+ * The base syntax of XML uses a few ASCII punctionation characters; any
+ * characters that would appear in the payload data are rewritten using
+ * sequences, e.g., &amp; for ampersand(&) and &lt for left angle bracket (<).
+ * Five such escapes total (more can be defined but that does not apply to our
+ * case). Thus we can safely parse for angle brackets etc.
+ *
+ * XML describes tree structures of tagged data, with each element beginning
+ * with an opening tag <label> and ending with a closing tag </label> with
+ * matching label. (There is also a self-closing tag <label/> which is supposed
+ * to be equivalent to <label></label>, i.e., no payload, but we are unlikely
+ * to see it for our purpose).
+ *
+ * Actually the opening tags are a little more complicated because they can
+ * contain "attributes" after the label (delimited by ascii space or tab chars)
+ * of the form attribute_label="value" or attribute_label='value'; as it turns
+ * out we do not have to read any of these attributes, just ignore them.
+ *
+ * Labels are any sequence of chars other than space, tab, right angle bracket
+ * (and ?), but may have an inner structure of <namespace><colon><plain_label>.
+ * As it turns out, we can ignore the namespaces, in fact we can ignore the
+ * entire tree hierarchy, because the plain labels we are looking for will be
+ * unique (not in general, but for this application). We do however have to be
+ * careful to skip over the namespaces.
+ *
+ * In generating XML we have to be more careful, but that is easy because
+ * everything we do is pretty canned. The only real care to take is to escape
+ * any special chars in our payload.
+ */
+
+/**
+ * xml_next_tag - Advance to next tag
+ * @in: Input
+ * @out: OUT: start of tag just after '<'
+ * @out_tagname: OUT: start of name of tag, skipping namespace
+ * @end: OUT: one after tag
+ * Returns: 0 on success, 1 on failure
+ *
+ * A tag has form:
+ * <left angle bracket><...><right angle bracket>
+ * Within the angle brackets, there is an optional leading forward slash (which
+ * makes the tag an ending tag), then an optional leading label (followed by
+ * colon) and then the tag name itself.
+ *
+ * Note that angle brackets present in the original data must have been encoded
+ * as &lt; and &gt; so they will not trouble us.
+ */
+static int xml_next_tag(char *in, char **out, char **out_tagname,
+ char **end)
+{
+ while (*in && *in != '<')
+ in++;
+ if (*in != '<')
+ return 1;
+ *out = ++in;
+ if (*in == '/')
+ in++;
+ *out_tagname = in; /* maybe */
+ while (isalnum(*in) || *in == '-')
+ in++;
+ if (*in == ':')
+ *out_tagname = ++in;
+ while (*in && *in != '>')
+ in++;
+ if (*in != '>')
+ return 1;
+ *end = ++in;
+ return 0;
+}
+
+
+/* xml_data_encode -- format data for xml file, escaping special characters.
+ *
+ * Note that we assume we are using utf8 both as input and as output!
+ * In utf8, characters may be classed as follows:
+ * 0xxxxxxx(2) -- 1 byte ascii char
+ * 11xxxxxx(2) -- 1st byte of multi-byte char w/ unicode value >= 0x80
+ * 110xxxxx(2) -- 1st byte of 2 byte sequence (5 payload bits here)
+ * 1110xxxx(2) -- 1st byte of 3 byte sequence (4 payload bits here)
+ * 11110xxx(2) -- 1st byte of 4 byte sequence (3 payload bits here)
+ * 10xxxxxx(2) -- extension byte (6 payload bits per byte)
+ * Some values implied by the above are however illegal because they
+ * do not represent unicode chars or are not the shortest encoding.
+ * Actually, we can almost entirely ignore the above and just do
+ * text processing same as for ascii text.
+ *
+ * XML is written with arbitrary unicode characters, except that five
+ * characters have special meaning and so must be escaped where they
+ * appear in payload data... which we do here.
+ */
+static void xml_data_encode(struct wpabuf *buf, const char *data, int len)
+{
+ int i;
+ for (i = 0; i < len; i++) {
+ u8 c = ((u8 *) data)[i];
+ if (c == '<') {
+ wpabuf_put_str(buf, "&lt;");
+ continue;
+ }
+ if (c == '>') {
+ wpabuf_put_str(buf, "&gt;");
+ continue;
+ }
+ if (c == '&') {
+ wpabuf_put_str(buf, "&amp;");
+ continue;
+ }
+ if (c == '\'') {
+ wpabuf_put_str(buf, "&apos;");
+ continue;
+ }
+ if (c == '"') {
+ wpabuf_put_str(buf, "&quot;");
+ continue;
+ }
+ /*
+ * We could try to represent control characters using the
+ * sequence: &#x; where x is replaced by a hex numeral, but not
+ * clear why we would do this.
+ */
+ wpabuf_put_u8(buf, c);
+ }
+}
+
+
+/* xml_add_tagged_data -- format tagged data as a new xml line.
+ *
+ * tag must not have any special chars.
+ * data may have special chars, which are escaped.
+ */
+static void xml_add_tagged_data(struct wpabuf *buf, const char *tag,
+ const char *data)
+{
+ wpabuf_printf(buf, "<%s>", tag);
+ xml_data_encode(buf, data, os_strlen(data));
+ wpabuf_printf(buf, "</%s>\n", tag);
+}
+
+
+/* A POST body looks something like (per upnp spec):
+ * <?xml version="1.0"?>
+ * <s:Envelope
+ * xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
+ * s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
+ * <s:Body>
+ * <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
+ * <argumentName>in arg value</argumentName>
+ * other in args and their values go here, if any
+ * </u:actionName>
+ * </s:Body>
+ * </s:Envelope>
+ *
+ * where :
+ * s: might be some other namespace name followed by colon
+ * u: might be some other namespace name followed by colon
+ * actionName will be replaced according to action requested
+ * schema following actionName will be WFA scheme instead
+ * argumentName will be actual argument name
+ * (in arg value) will be actual argument value
+ */
+static int
+upnp_get_first_document_item(char *doc, const char *item, char **value)
+{
+ const char *match = item;
+ int match_len = os_strlen(item);
+ char *tag;
+ char *tagname;
+ char *end;
+
+ *value = NULL; /* default, bad */
+
+ /*
+ * This is crude: ignore any possible tag name conflicts and go right
+ * to the first tag of this name. This should be ok for the limited
+ * domain of UPnP messages.
+ */
+ for (;;) {
+ if (xml_next_tag(doc, &tag, &tagname, &end))
+ return 1;
+ doc = end;
+ if (!os_strncasecmp(tagname, match, match_len) &&
+ *tag != '/' &&
+ (tagname[match_len] == '>' ||
+ !isgraph(tagname[match_len]) )) {
+ break;
+ }
+ }
+ end = doc;
+ while (*end && *end != '<')
+ end++;
+ *value = os_zalloc(1 + (end - doc));
+ if (*value == NULL)
+ return 1;
+ os_memcpy(*value, doc, end - doc);
+ return 0;
+}
+
+
+/*
+ * "Files" that we serve via HTTP. The format of these files is given by
+ * WFA WPS specifications. Extra white space has been removed to save space.
+ */
+
+static const char wps_scpd_xml[] =
+"<?xml version=\"1.0\"?>\n"
+"<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\n"
+"<specVersion><major>1</major><minor>0</minor></specVersion>\n"
+"<actionList>\n"
+"<action>\n"
+"<name>GetDeviceInfo</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewDeviceInfo</name>\n"
+"<direction>out</direction>\n"
+"<relatedStateVariable>DeviceInfo</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>PutMessage</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewInMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>InMessage</relatedStateVariable>\n"
+"</argument>\n"
+"<argument>\n"
+"<name>NewOutMessage</name>\n"
+"<direction>out</direction>\n"
+"<relatedStateVariable>OutMessage</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>GetAPSettings</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>Message</relatedStateVariable>\n"
+"</argument>\n"
+"<argument>\n"
+"<name>NewAPSettings</name>\n"
+"<direction>out</direction>\n"
+"<relatedStateVariable>APSettings</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>SetAPSettings</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>APSettings</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>APSettings</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>DelAPSettings</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewAPSettings</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>APSettings</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>GetSTASettings</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>Message</relatedStateVariable>\n"
+"</argument>\n"
+"<argument>\n"
+"<name>NewSTASettings</name>\n"
+"<direction>out</direction>\n"
+"<relatedStateVariable>STASettings</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>SetSTASettings</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewSTASettings</name>\n"
+"<direction>out</direction>\n"
+"<relatedStateVariable>STASettings</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>DelSTASettings</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewSTASettings</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>STASettings</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>PutWLANResponse</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>Message</relatedStateVariable>\n"
+"</argument>\n"
+"<argument>\n"
+"<name>NewWLANEventType</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>WLANEventType</relatedStateVariable>\n"
+"</argument>\n"
+"<argument>\n"
+"<name>NewWLANEventMAC</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>SetSelectedRegistrar</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>Message</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>RebootAP</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewAPSettings</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>APSettings</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>ResetAP</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>Message</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>RebootSTA</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewSTASettings</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>APSettings</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>ResetSTA</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>Message</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"</actionList>\n"
+"<serviceStateTable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>Message</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>InMessage</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>OutMessage</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>DeviceInfo</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>APSettings</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"yes\">\n"
+"<name>APStatus</name>\n"
+"<dataType>ui1</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>STASettings</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"yes\">\n"
+"<name>STAStatus</name>\n"
+"<dataType>ui1</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"yes\">\n"
+"<name>WLANEvent</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>WLANEventType</name>\n"
+"<dataType>ui1</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>WLANEventMAC</name>\n"
+"<dataType>string</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>WLANResponse</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"</serviceStateTable>\n"
+"</scpd>\n"
+;
+
+
+static const char *wps_device_xml_prefix =
+ "<?xml version=\"1.0\"?>\n"
+ "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
+ "<specVersion>\n"
+ "<major>1</major>\n"
+ "<minor>0</minor>\n"
+ "</specVersion>\n"
+ "<device>\n"
+ "<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1"
+ "</deviceType>\n";
+
+static const char *wps_device_xml_postfix =
+ "<serviceList>\n"
+ "<service>\n"
+ "<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1"
+ "</serviceType>\n"
+ "<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>"
+ "\n"
+ "<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n"
+ "<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n"
+ "<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n"
+ "</service>\n"
+ "</serviceList>\n"
+ "</device>\n"
+ "</root>\n";
+
+
+/* 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,
+ struct wpabuf *buf)
+{
+ const char *s;
+ char uuid_string[80];
+
+ wpabuf_put_str(buf, wps_device_xml_prefix);
+
+ /*
+ * Add required fields with default values if not configured. Add
+ * optional and recommended fields only if configured.
+ */
+ s = sm->wps->friendly_name;
+ s = ((s && *s) ? s : "WPS Access Point");
+ xml_add_tagged_data(buf, "friendlyName", s);
+
+ s = sm->wps->dev.manufacturer;
+ s = ((s && *s) ? s : "");
+ xml_add_tagged_data(buf, "manufacturer", s);
+
+ if (sm->wps->manufacturer_url)
+ xml_add_tagged_data(buf, "manufacturerURL",
+ sm->wps->manufacturer_url);
+
+ if (sm->wps->model_description)
+ xml_add_tagged_data(buf, "modelDescription",
+ sm->wps->model_description);
+
+ s = sm->wps->dev.model_name;
+ s = ((s && *s) ? s : "");
+ xml_add_tagged_data(buf, "modelName", s);
+
+ if (sm->wps->dev.model_number)
+ xml_add_tagged_data(buf, "modelNumber",
+ sm->wps->dev.model_number);
+
+ if (sm->wps->model_url)
+ xml_add_tagged_data(buf, "modelURL", sm->wps->model_url);
+
+ if (sm->wps->dev.serial_number)
+ xml_add_tagged_data(buf, "serialNumber",
+ sm->wps->dev.serial_number);
+
+ uuid_bin2str(sm->wps->uuid, uuid_string, sizeof(uuid_string));
+ s = uuid_string;
+ /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
+ * easily...
+ */
+ wpabuf_put_str(buf, "<UDN>uuid:");
+ 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);
+
+ wpabuf_put_str(buf, wps_device_xml_postfix);
+}
+
+
+void web_connection_stop(struct web_connection *c)
+{
+ struct upnp_wps_device_sm *sm = c->sm;
+
+ httpread_destroy(c->hread);
+ c->hread = NULL;
+ close(c->sd);
+ c->sd = -1;
+ if (c->next == c) {
+ sm->web_connections = NULL;
+ } else {
+ if (sm->web_connections == c)
+ sm->web_connections = c->next;
+ c->next->prev = c->prev;
+ c->prev->next = c->next;
+ }
+ os_free(c);
+ sm->n_web_connections--;
+}
+
+
+static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
+{
+ wpabuf_put_str(buf, "HTTP/1.1 ");
+ switch (code) {
+ case HTTP_OK:
+ wpabuf_put_str(buf, "200 OK\r\n");
+ break;
+ case HTTP_BAD_REQUEST:
+ wpabuf_put_str(buf, "400 Bad request\r\n");
+ break;
+ case HTTP_PRECONDITION_FAILED:
+ wpabuf_put_str(buf, "412 Precondition failed\r\n");
+ break;
+ case HTTP_UNIMPLEMENTED:
+ wpabuf_put_str(buf, "501 Unimplemented\r\n");
+ break;
+ case HTTP_INTERNAL_SERVER_ERROR:
+ default:
+ wpabuf_put_str(buf, "500 Internal server error\r\n");
+ break;
+ }
+}
+
+
+static void http_put_date(struct wpabuf *buf)
+{
+ wpabuf_put_str(buf, "Date: ");
+ format_date(buf);
+ wpabuf_put_str(buf, "\r\n");
+}
+
+
+static void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
+{
+ http_put_reply_code(buf, code);
+ wpabuf_put_str(buf, http_server_hdr);
+ wpabuf_put_str(buf, http_connection_close);
+ wpabuf_put_str(buf, "Content-Length: 0\r\n"
+ "\r\n");
+}
+
+
+/* Given that we have received a header w/ GET, act upon it
+ *
+ * Format of GET (case-insensitive):
+ *
+ * First line must be:
+ * GET /<file> HTTP/1.1
+ * Since we don't do anything fancy we just ignore other lines.
+ *
+ * Our response (if no error) which includes only required lines is:
+ * HTTP/1.1 200 OK
+ * Connection: close
+ * Content-Type: text/xml
+ * Date: <rfc1123-date>
+ *
+ * Header lines must end with \r\n
+ * Per RFC 2616, content-length: is not required but connection:close
+ * would appear to be required (given that we will be closing it!).
+ */
+static void web_connection_parse_get(struct web_connection *c, char *filename)
+{
+ struct upnp_wps_device_sm *sm = c->sm;
+ struct wpabuf *buf; /* output buffer, allocated */
+ char *put_length_here;
+ char *body_start;
+ enum {
+ GET_DEVICE_XML_FILE,
+ GET_SCPD_XML_FILE
+ } req;
+ size_t extra_len = 0;
+ int body_length;
+ char len_buf[10];
+
+ /*
+ * 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) {
+ 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);
+ } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
+ req = GET_SCPD_XML_FILE;
+ extra_len = os_strlen(wps_scpd_xml);
+ } else {
+ /* File not found */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: File not found: %s",
+ filename);
+ buf = wpabuf_alloc(200);
+ if (buf == NULL)
+ return;
+ wpabuf_put_str(buf,
+ "HTTP/1.1 404 Not Found\r\n"
+ "Connection: close\r\n");
+
+ http_put_date(buf);
+
+ /* terminating empty line */
+ wpabuf_put_str(buf, "\r\n");
+
+ goto send_buf;
+ }
+
+ buf = wpabuf_alloc(1000 + extra_len);
+ if (buf == NULL)
+ return;
+
+ wpabuf_put_str(buf,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/xml; charset=\"utf-8\"\r\n");
+ wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
+ wpabuf_put_str(buf, "Connection: close\r\n");
+ wpabuf_put_str(buf, "Content-Length: ");
+ /*
+ * We will paste the length in later, leaving some extra whitespace.
+ * HTTP code is supposed to be tolerant of extra whitespace.
+ */
+ put_length_here = wpabuf_put(buf, 0);
+ wpabuf_put_str(buf, " \r\n");
+
+ http_put_date(buf);
+
+ /* terminating empty line */
+ wpabuf_put_str(buf, "\r\n");
+
+ body_start = wpabuf_put(buf, 0);
+
+ switch (req) {
+ case GET_DEVICE_XML_FILE:
+ format_wps_device_xml(sm, buf);
+ break;
+ case GET_SCPD_XML_FILE:
+ wpabuf_put_str(buf, wps_scpd_xml);
+ break;
+ }
+
+ /* Now patch in the content length at the end */
+ body_length = (char *) wpabuf_put(buf, 0) - body_start;
+ os_snprintf(len_buf, 10, "%d", body_length);
+ os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
+
+send_buf:
+ send_wpabuf(c->sd, buf);
+ wpabuf_free(buf);
+}
+
+
+static struct wpabuf * web_get_item(char *data, const char *name,
+ enum http_reply_code *ret)
+{
+ char *msg;
+ struct wpabuf *buf;
+ unsigned char *decoded;
+ size_t len;
+
+ if (upnp_get_first_document_item(data, name, &msg)) {
+ *ret = UPNP_ARG_VALUE_INVALID;
+ return NULL;
+ }
+
+ decoded = base64_decode((unsigned char *) msg, os_strlen(msg), &len);
+ os_free(msg);
+ if (decoded == NULL) {
+ *ret = UPNP_OUT_OF_MEMORY;
+ return NULL;
+ }
+
+ buf = wpabuf_alloc_ext_data(decoded, len);
+ if (buf == NULL) {
+ os_free(decoded);
+ *ret = UPNP_OUT_OF_MEMORY;
+ return NULL;
+ }
+ return buf;
+}
+
+
+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";
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
+ if (sm->ctx->rx_req_get_device_info == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ *reply = sm->ctx->rx_req_get_device_info(sm->priv, &sm->peer);
+ if (*reply == NULL) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ *replyname = name;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ static const char *name = "NewOutMessage";
+ enum http_reply_code ret;
+
+ /*
+ * PutMessage is used by external UPnP-based Registrar to perform WPS
+ * operation with the access point itself; as compared with
+ * PutWLANResponse which is for proxying.
+ */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
+ if (sm->ctx->rx_req_put_message == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ msg = web_get_item(data, "NewInMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ *reply = sm->ctx->rx_req_put_message(sm->priv, &sm->peer, msg);
+ wpabuf_free(msg);
+ if (*reply == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ *replyname = name;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_get_ap_settings(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ static const char *name = "NewAPSettings";
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: GetAPSettings");
+ if (sm->ctx->rx_req_get_ap_settings == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ msg = web_get_item(data, "NewMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ *reply = sm->ctx->rx_req_get_ap_settings(sm->priv, msg);
+ wpabuf_free(msg);
+ if (*reply == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ *replyname = name;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_set_ap_settings(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: SetAPSettings");
+ msg = web_get_item(data, "NewAPSettings", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_set_ap_settings ||
+ sm->ctx->rx_req_set_ap_settings(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_del_ap_settings(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: DelAPSettings");
+ msg = web_get_item(data, "NewAPSettings", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_del_ap_settings ||
+ sm->ctx->rx_req_del_ap_settings(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_get_sta_settings(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ static const char *name = "NewSTASettings";
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: GetSTASettings");
+ if (sm->ctx->rx_req_get_sta_settings == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ msg = web_get_item(data, "NewMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ *reply = sm->ctx->rx_req_get_sta_settings(sm->priv, msg);
+ wpabuf_free(msg);
+ if (*reply == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ *replyname = name;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_set_sta_settings(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: SetSTASettings");
+ msg = web_get_item(data, "NewSTASettings", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_set_sta_settings ||
+ sm->ctx->rx_req_set_sta_settings(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_del_sta_settings(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: DelSTASettings");
+ msg = web_get_item(data, "NewSTASettings", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_del_sta_settings ||
+ sm->ctx->rx_req_del_sta_settings(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+ u8 macaddr[ETH_ALEN];
+ int ev_type;
+ char *val;
+
+ /*
+ * External UPnP-based Registrar is passing us a message to be proxied
+ * over to a Wi-Fi -based client of ours.
+ */
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
+ msg = web_get_item(data, "NewMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ if (upnp_get_first_document_item(data, "NewWLANEventType", &val)) {
+ wpabuf_free(msg);
+ return UPNP_ARG_VALUE_INVALID;
+ }
+ ev_type = atol(val);
+ os_free(val);
+ val = NULL;
+ if (upnp_get_first_document_item(data, "NewWLANEventMAC", &val) ||
+ hwaddr_aton(val, macaddr)) {
+ wpabuf_free(msg);
+ os_free(val);
+ return UPNP_ARG_VALUE_INVALID;
+ }
+ os_free(val);
+ if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
+ struct wps_parse_attr attr;
+ int type;
+ if (wps_parse_msg(msg, &attr) < 0 ||
+ attr.msg_type == NULL)
+ type = -1;
+ else
+ type = *attr.msg_type;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
+ }
+ if (!sm->ctx->rx_req_put_wlan_event_response ||
+ sm->ctx->rx_req_put_wlan_event_response(sm->priv, ev_type,
+ macaddr, msg)) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
+ "rx_req_put_wlan_event_response");
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_set_selected_registrar(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply,
+ const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
+ msg = web_get_item(data, "NewMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_set_selected_registrar ||
+ sm->ctx->rx_req_set_selected_registrar(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_reboot_ap(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: RebootAP");
+ msg = web_get_item(data, "NewAPSettings", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_reboot_ap ||
+ sm->ctx->rx_req_reboot_ap(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_reset_ap(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: ResetAP");
+ msg = web_get_item(data, "NewMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_reset_ap ||
+ sm->ctx->rx_req_reset_ap(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_reboot_sta(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: RebootSTA");
+ msg = web_get_item(data, "NewSTASettings", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_reboot_sta ||
+ sm->ctx->rx_req_reboot_sta(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_reset_sta(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: ResetSTA");
+ msg = web_get_item(data, "NewMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ if (!sm->ctx->rx_req_reset_sta ||
+ sm->ctx->rx_req_reset_sta(sm->priv, msg)) {
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static const char *soap_prefix =
+ "<?xml version=\"1.0\"?>\n"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+ "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
+ "<s:Body>\n";
+static const char *soap_postfix =
+ "</s:Body>\n</s:Envelope>\n";
+
+static const char *soap_error_prefix =
+ "<s:Fault>\n"
+ "<faultcode>s:Client</faultcode>\n"
+ "<faultstring>UPnPError</faultstring>\n"
+ "<detail>\n"
+ "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
+static const char *soap_error_postfix =
+ "<errorDescription>Error</errorDescription>\n"
+ "</UPnPError>\n"
+ "</detail>\n"
+ "</s:Fault>\n";
+
+static void web_connection_send_reply(struct web_connection *c,
+ enum http_reply_code ret,
+ const char *action, int action_len,
+ const struct wpabuf *reply,
+ const char *replyname)
+{
+ struct wpabuf *buf;
+ char *replydata;
+ char *put_length_here = NULL;
+ char *body_start = NULL;
+
+ if (reply) {
+ size_t len;
+ replydata = (char *) base64_encode(wpabuf_head(reply),
+ wpabuf_len(reply), &len);
+ } else
+ replydata = NULL;
+
+ /* Parameters of the response:
+ * action(action_len) -- action we are responding to
+ * replyname -- a name we need for the reply
+ * replydata -- NULL or null-terminated string
+ */
+ buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
+ (action_len > 0 ? action_len * 2 : 0));
+ if (buf == NULL) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
+ "POST");
+ wpabuf_free(buf);
+ os_free(replydata);
+ return;
+ }
+
+ /*
+ * Assuming we will be successful, put in the output header first.
+ * Note: we do not keep connections alive (and httpread does
+ * not support it)... therefore we must have Connection: close.
+ */
+ if (ret == HTTP_OK) {
+ wpabuf_put_str(buf,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/xml; "
+ "charset=\"utf-8\"\r\n");
+ } else {
+ wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
+ }
+ wpabuf_put_str(buf, http_connection_close);
+
+ wpabuf_put_str(buf, "Content-Length: ");
+ /*
+ * We will paste the length in later, leaving some extra whitespace.
+ * HTTP code is supposed to be tolerant of extra whitespace.
+ */
+ put_length_here = wpabuf_put(buf, 0);
+ wpabuf_put_str(buf, " \r\n");
+
+ http_put_date(buf);
+
+ /* terminating empty line */
+ wpabuf_put_str(buf, "\r\n");
+
+ body_start = wpabuf_put(buf, 0);
+
+ if (ret == HTTP_OK) {
+ wpabuf_put_str(buf, soap_prefix);
+ wpabuf_put_str(buf, "<u:");
+ wpabuf_put_data(buf, action, action_len);
+ wpabuf_put_str(buf, "Response xmlns:u=\"");
+ wpabuf_put_str(buf, urn_wfawlanconfig);
+ wpabuf_put_str(buf, "\">\n");
+ if (replydata && replyname) {
+ /* TODO: might possibly need to escape part of reply
+ * data? ...
+ * probably not, unlikely to have ampersand(&) or left
+ * angle bracket (<) in it...
+ */
+ wpabuf_printf(buf, "<%s>", replyname);
+ wpabuf_put_str(buf, replydata);
+ wpabuf_printf(buf, "</%s>\n", replyname);
+ }
+ wpabuf_put_str(buf, "</u:");
+ wpabuf_put_data(buf, action, action_len);
+ wpabuf_put_str(buf, "Response>\n");
+ wpabuf_put_str(buf, soap_postfix);
+ } else {
+ /* Error case */
+ wpabuf_put_str(buf, soap_prefix);
+ wpabuf_put_str(buf, soap_error_prefix);
+ wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
+ wpabuf_put_str(buf, soap_error_postfix);
+ wpabuf_put_str(buf, soap_postfix);
+ }
+ os_free(replydata);
+
+ /* Now patch in the content length at the end */
+ if (body_start && put_length_here) {
+ int body_length = (char *) wpabuf_put(buf, 0) - body_start;
+ char len_buf[10];
+ os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
+ os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
+ }
+
+ send_wpabuf(c->sd, buf);
+ wpabuf_free(buf);
+}
+
+
+static const char * web_get_action(struct web_connection *c,
+ const char *filename, size_t *action_len)
+{
+ const char *match;
+ int match_len;
+ char *b;
+ char *action;
+
+ *action_len = 0;
+ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
+ filename);
+ return NULL;
+ }
+ /* The SOAPAction line of the header tells us what we want to do */
+ b = httpread_hdr_line_get(c->hread, "SOAPAction:");
+ if (b == NULL)
+ return NULL;
+ if (*b == '"')
+ b++;
+ else
+ return NULL;
+ match = urn_wfawlanconfig;
+ match_len = os_strlen(urn_wfawlanconfig) - 1;
+ if (os_strncasecmp(b, match, match_len))
+ return NULL;
+ b += match_len;
+ /* skip over version */
+ while (isgraph(*b) && *b != '#')
+ b++;
+ if (*b != '#')
+ return NULL;
+ b++;
+ /* Following the sharp(#) should be the action and a double quote */
+ action = b;
+ while (isgraph(*b) && *b != '"')
+ b++;
+ if (*b != '"')
+ return NULL;
+ *action_len = b - action;
+ return action;
+}
+
+
+/* Given that we have received a header w/ POST, act upon it
+ *
+ * Format of POST (case-insensitive):
+ *
+ * First line must be:
+ * POST /<file> HTTP/1.1
+ * Since we don't do anything fancy we just ignore other lines.
+ *
+ * Our response (if no error) which includes only required lines is:
+ * HTTP/1.1 200 OK
+ * Connection: close
+ * Content-Type: text/xml
+ * Date: <rfc1123-date>
+ *
+ * Header lines must end with \r\n
+ * Per RFC 2616, content-length: is not required but connection:close
+ * would appear to be required (given that we will be closing it!).
+ */
+static void web_connection_parse_post(struct web_connection *c,
+ const char *filename)
+{
+ enum http_reply_code ret;
+ struct upnp_wps_device_sm *sm = c->sm;
+ char *data = httpread_data_get(c->hread); /* body of http msg */
+ const char *action;
+ size_t action_len;
+ const char *replyname = NULL; /* argument name for the reply */
+ struct wpabuf *reply = NULL; /* data for the reply */
+
+ ret = UPNP_INVALID_ACTION;
+ action = web_get_action(c, filename, &action_len);
+ if (action == NULL)
+ goto bad;
+
+ /*
+ * There are quite a few possible actions. Although we appear to
+ * support them all here, not all of them are necessarily supported by
+ * callbacks at higher levels.
+ */
+ if (!os_strncasecmp("GetDeviceInfo", action, action_len))
+ ret = web_process_get_device_info(sm, &reply, &replyname);
+ else if (!os_strncasecmp("PutMessage", action, action_len))
+ ret = web_process_put_message(sm, data, &reply, &replyname);
+ else if (!os_strncasecmp("GetAPSettings", action, action_len))
+ ret = web_process_get_ap_settings(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("SetAPSettings", action, action_len))
+ ret = web_process_set_ap_settings(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("DelAPSettings", action, action_len))
+ ret = web_process_del_ap_settings(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("GetSTASettings", action, action_len))
+ ret = web_process_get_sta_settings(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("SetSTASettings", action, action_len))
+ ret = web_process_set_sta_settings(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("DelSTASettings", action, action_len))
+ ret = web_process_del_sta_settings(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("PutWLANResponse", action, action_len))
+ ret = web_process_put_wlan_response(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
+ ret = web_process_set_selected_registrar(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("RebootAP", action, action_len))
+ ret = web_process_reboot_ap(sm, data, &reply, &replyname);
+ else if (!os_strncasecmp("ResetAP", action, action_len))
+ ret = web_process_reset_ap(sm, data, &reply, &replyname);
+ else if (!os_strncasecmp("RebootSTA", action, action_len))
+ ret = web_process_reboot_sta(sm, data, &reply, &replyname);
+ else if (!os_strncasecmp("ResetSTA", action, action_len))
+ ret = web_process_reset_sta(sm, data, &reply, &replyname);
+ else
+ wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
+
+bad:
+ if (ret != HTTP_OK)
+ wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
+ web_connection_send_reply(c, ret, action, action_len, reply,
+ replyname);
+ wpabuf_free(reply);
+}
+
+
+/* Given that we have received a header w/ SUBSCRIBE, act upon it
+ *
+ * Format of SUBSCRIBE (case-insensitive):
+ *
+ * First line must be:
+ * SUBSCRIBE /wps_event HTTP/1.1
+ *
+ * Our response (if no error) which includes only required lines is:
+ * HTTP/1.1 200 OK
+ * Server: xx, UPnP/1.0, xx
+ * SID: uuid:xxxxxxxxx
+ * Timeout: Second-<n>
+ * Content-Length: 0
+ * Date: xxxx
+ *
+ * Header lines must end with \r\n
+ * Per RFC 2616, content-length: is not required but connection:close
+ * would appear to be required (given that we will be closing it!).
+ */
+static void web_connection_parse_subscribe(struct web_connection *c,
+ const char *filename)
+{
+ struct upnp_wps_device_sm *sm = c->sm;
+ struct wpabuf *buf;
+ char *b;
+ char *hdr = httpread_hdr_get(c->hread);
+ char *h;
+ char *match;
+ int match_len;
+ char *end;
+ int len;
+ int got_nt = 0;
+ u8 uuid[UUID_LEN];
+ int got_uuid = 0;
+ char *callback_urls = NULL;
+ struct subscription *s = NULL;
+ enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
+
+ buf = wpabuf_alloc(1000);
+ if (buf == NULL)
+ return;
+
+ /* Parse/validate headers */
+ h = hdr;
+ /* First line: SUBSCRIBE /wps_event HTTP/1.1
+ * has already been parsed.
+ */
+ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto error;
+ }
+ end = os_strchr(h, '\n');
+
+ for (; end != NULL; h = end + 1) {
+ /* Option line by option line */
+ h = end + 1;
+ end = os_strchr(h, '\n');
+ if (end == NULL)
+ break; /* no unterminated lines allowed */
+
+ /* NT assures that it is our type of subscription;
+ * not used for a renewl.
+ **/
+ match = "NT:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ match = "upnp:event";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) != 0) {
+ ret = HTTP_BAD_REQUEST;
+ goto error;
+ }
+ got_nt = 1;
+ continue;
+ }
+ /* HOST should refer to us */
+#if 0
+ match = "HOST:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ .....
+ }
+#endif
+ /* CALLBACK gives one or more URLs for NOTIFYs
+ * to be sent as a result of the subscription.
+ * Each URL is enclosed in angle brackets.
+ */
+ match = "CALLBACK:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ len = end - h;
+ os_free(callback_urls);
+ callback_urls = os_malloc(len + 1);
+ if (callback_urls == NULL) {
+ ret = HTTP_INTERNAL_SERVER_ERROR;
+ goto error;
+ }
+ os_memcpy(callback_urls, h, len);
+ callback_urls[len] = 0;
+ continue;
+ }
+ /* SID is only for renewal */
+ match = "SID:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ match = "uuid:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) != 0) {
+ ret = HTTP_BAD_REQUEST;
+ goto error;
+ }
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ if (uuid_str2bin(h, uuid)) {
+ ret = HTTP_BAD_REQUEST;
+ goto error;
+ }
+ got_uuid = 1;
+ continue;
+ }
+ /* TIMEOUT is requested timeout, but apparently we can
+ * just ignore this.
+ */
+ }
+
+ if (got_uuid) {
+ /* renewal */
+ if (callback_urls) {
+ ret = HTTP_BAD_REQUEST;
+ goto error;
+ }
+ s = subscription_renew(sm, uuid);
+ if (s == NULL) {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto error;
+ }
+ } else if (callback_urls) {
+ if (!got_nt) {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto error;
+ }
+ s = subscription_start(sm, callback_urls);
+ if (s == NULL) {
+ ret = HTTP_INTERNAL_SERVER_ERROR;
+ goto error;
+ }
+ callback_urls = NULL; /* is now owned by subscription */
+ } else {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto error;
+ }
+
+ /* success */
+ http_put_reply_code(buf, HTTP_OK);
+ wpabuf_put_str(buf, http_server_hdr);
+ wpabuf_put_str(buf, http_connection_close);
+ wpabuf_put_str(buf, "Content-Length: 0\r\n");
+ wpabuf_put_str(buf, "SID: uuid:");
+ /* subscription id */
+ b = wpabuf_put(buf, 0);
+ uuid_bin2str(s->uuid, b, 80);
+ wpabuf_put(buf, os_strlen(b));
+ wpabuf_put_str(buf, "\r\n");
+ wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
+ http_put_date(buf);
+ /* And empty line to terminate header: */
+ wpabuf_put_str(buf, "\r\n");
+
+ wpa_hexdump_ascii(MSG_MSGDUMP, "WPS UPnP SUBSCRIBE response",
+ wpabuf_head(buf), wpabuf_len(buf));
+ send_wpabuf(c->sd, buf);
+ wpabuf_free(buf);
+ os_free(callback_urls);
+ return;
+
+error:
+ /* Per UPnP spec:
+ * Errors
+ * Incompatible headers
+ * 400 Bad Request. If SID header and one of NT or CALLBACK headers
+ * are present, the publisher must respond with HTTP error
+ * 400 Bad Request.
+ * Missing or invalid CALLBACK
+ * 412 Precondition Failed. If CALLBACK header is missing or does not
+ * contain a valid HTTP URL, the publisher must respond with HTTP
+ * error 412 Precondition Failed.
+ * Invalid NT
+ * 412 Precondition Failed. If NT header does not equal upnp:event,
+ * the publisher must respond with HTTP error 412 Precondition
+ * Failed.
+ * [For resubscription, use 412 if unknown uuid].
+ * Unable to accept subscription
+ * 5xx. If a publisher is not able to accept a subscription (such as
+ * due to insufficient resources), it must respond with a
+ * HTTP 500-series error code.
+ * 599 Too many subscriptions (not a standard HTTP error)
+ */
+ http_put_empty(buf, ret);
+ send_wpabuf(c->sd, buf);
+ wpabuf_free(buf);
+}
+
+
+/* Given that we have received a header w/ UNSUBSCRIBE, act upon it
+ *
+ * Format of UNSUBSCRIBE (case-insensitive):
+ *
+ * First line must be:
+ * UNSUBSCRIBE /wps_event HTTP/1.1
+ *
+ * Our response (if no error) which includes only required lines is:
+ * HTTP/1.1 200 OK
+ * Content-Length: 0
+ *
+ * Header lines must end with \r\n
+ * Per RFC 2616, content-length: is not required but connection:close
+ * would appear to be required (given that we will be closing it!).
+ */
+static void web_connection_parse_unsubscribe(struct web_connection *c,
+ const char *filename)
+{
+ struct upnp_wps_device_sm *sm = c->sm;
+ struct wpabuf *buf;
+ char *hdr = httpread_hdr_get(c->hread);
+ char *h;
+ char *match;
+ int match_len;
+ char *end;
+ u8 uuid[UUID_LEN];
+ int got_uuid = 0;
+ struct subscription *s = NULL;
+ enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
+
+ /* Parse/validate headers */
+ h = hdr;
+ /* First line: UNSUBSCRIBE /wps_event HTTP/1.1
+ * has already been parsed.
+ */
+ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto send_msg;
+ }
+ end = os_strchr(h, '\n');
+
+ for (; end != NULL; h = end + 1) {
+ /* Option line by option line */
+ h = end + 1;
+ end = os_strchr(h, '\n');
+ if (end == NULL)
+ break; /* no unterminated lines allowed */
+
+ /* HOST should refer to us */
+#if 0
+ match = "HOST:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ .....
+ }
+#endif
+ /* SID is only for renewal */
+ match = "SID:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ match = "uuid:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) != 0) {
+ ret = HTTP_BAD_REQUEST;
+ goto send_msg;
+ }
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ if (uuid_str2bin(h, uuid)) {
+ ret = HTTP_BAD_REQUEST;
+ goto send_msg;
+ }
+ got_uuid = 1;
+ continue;
+ }
+ }
+
+ if (got_uuid) {
+ s = subscription_find(sm, uuid);
+ if (s) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
+ s,
+ (s && s->addr_list &&
+ s->addr_list->domain_and_port) ?
+ s->addr_list->domain_and_port : "-null-");
+ subscription_unlink(s);
+ subscription_destroy(s);
+ }
+ } else {
+ wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
+ "found)");
+ ret = HTTP_PRECONDITION_FAILED;
+ goto send_msg;
+ }
+
+ ret = HTTP_OK;
+
+send_msg:
+ buf = wpabuf_alloc(200);
+ if (buf == NULL)
+ return;
+ http_put_empty(buf, ret);
+ send_wpabuf(c->sd, buf);
+ wpabuf_free(buf);
+}
+
+
+/* Send error in response to unknown requests */
+static void web_connection_unimplemented(struct web_connection *c)
+{
+ struct wpabuf *buf;
+ buf = wpabuf_alloc(200);
+ if (buf == NULL)
+ return;
+ http_put_empty(buf, HTTP_UNIMPLEMENTED);
+ send_wpabuf(c->sd, buf);
+ wpabuf_free(buf);
+}
+
+
+
+/* Called when we have gotten an apparently valid http request.
+ */
+static void web_connection_check_data(struct web_connection *c)
+{
+ struct httpread *hread = c->hread;
+ enum httpread_hdr_type htype = httpread_hdr_type_get(hread);
+ /* char *data = httpread_data_get(hread); */
+ char *filename = httpread_uri_get(hread);
+
+ c->done = 1;
+ if (!filename) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
+ return;
+ }
+ /* Trim leading slashes from filename */
+ while (*filename == '/')
+ filename++;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d", htype);
+
+ switch (htype) {
+ case HTTPREAD_HDR_TYPE_GET:
+ web_connection_parse_get(c, filename);
+ break;
+ case HTTPREAD_HDR_TYPE_POST:
+ web_connection_parse_post(c, filename);
+ break;
+ case HTTPREAD_HDR_TYPE_SUBSCRIBE:
+ web_connection_parse_subscribe(c, filename);
+ break;
+ case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
+ web_connection_parse_unsubscribe(c, filename);
+ break;
+ /* We are not required to support M-POST; just plain
+ * POST is supposed to work, so we only support that.
+ * If for some reason we need to support M-POST, it is
+ * mostly the same as POST, with small differences.
+ */
+ default:
+ /* Send 501 for anything else */
+ web_connection_unimplemented(c);
+ break;
+ }
+}
+
+
+
+/* called back when we have gotten request */
+static void web_connection_got_file_handler(struct httpread *handle,
+ void *cookie,
+ enum httpread_event en)
+{
+ struct web_connection *c = cookie;
+
+ if (en == HTTPREAD_EVENT_FILE_READY)
+ web_connection_check_data(c);
+ web_connection_stop(c);
+}
+
+
+/* web_connection_start - Start web connection
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @sd: Socket descriptor
+ * @ip_addr: of client, in host byte order
+ * @ip_port: of client, in native byte order
+ *
+ * The socket descriptor sd is handed over for ownership by the WPs UPnP
+ * state machine.
+ */
+static void web_connection_start(struct upnp_wps_device_sm *sm,
+ int sd, unsigned ip_addr, unsigned ip_port)
+{
+ struct web_connection *c = NULL;
+
+ /* if too many connections, bail */
+ if (sm->n_web_connections >= MAX_WEB_CONNECTIONS) {
+ close(sd);
+ return;
+ }
+
+ c = os_zalloc(sizeof(*c));
+ if (c == NULL)
+ return;
+ c->sm = sm;
+ c->sd = sd;
+#if 0
+ /*
+ * Setting non-blocking should not be necessary for read, and can mess
+ * up sending where blocking might be better.
+ */
+ if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0)
+ break;
+#endif
+ c->hread = httpread_create(c->sd, web_connection_got_file_handler,
+ c /* cookie */,
+ WEB_CONNECTION_MAX_READ,
+ WEB_CONNECTION_TIMEOUT_SEC);
+ if (c->hread == NULL)
+ goto fail;
+ if (sm->web_connections) {
+ c->next = sm->web_connections;
+ c->prev = c->next->prev;
+ c->prev->next = c;
+ c->next->prev = c;
+ } else {
+ sm->web_connections = c->next = c->prev = c;
+ }
+ sm->n_web_connections++;
+ return;
+
+fail:
+ if (c)
+ web_connection_stop(c);
+}
+
+
+/*
+ * Listening for web connections
+ * We have a single TCP listening port, and hand off connections as we get
+ * them.
+ */
+
+void web_listener_stop(struct upnp_wps_device_sm *sm)
+{
+ if (sm->web_sd_registered) {
+ sm->web_sd_registered = 0;
+ eloop_unregister_sock(sm->web_sd, EVENT_TYPE_READ);
+ }
+ if (sm->web_sd >= 0)
+ close(sm->web_sd);
+ sm->web_sd = -1;
+}
+
+
+static void web_listener_handler(int sd, void *eloop_ctx, void *sock_ctx)
+{
+ struct sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ struct upnp_wps_device_sm *sm = sock_ctx;
+ int new_sd;
+
+ /* Create state for new connection */
+ /* Remember so we can cancel if need be */
+ new_sd = accept(sm->web_sd, (struct sockaddr *) &addr, &addr_len);
+ if (new_sd < 0) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: web listener accept "
+ "errno=%d (%s) web_sd=%d",
+ errno, strerror(errno), sm->web_sd);
+ return;
+ }
+ web_connection_start(sm, new_sd, addr.sin_addr.s_addr,
+ htons(addr.sin_port));
+}
+
+
+int web_listener_start(struct upnp_wps_device_sm *sm)
+{
+ struct sockaddr_in addr;
+ int port;
+
+ sm->web_sd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sm->web_sd < 0)
+ goto fail;
+ if (fcntl(sm->web_sd, F_SETFL, O_NONBLOCK) != 0)
+ goto fail;
+ port = 49152; /* first non-reserved port */
+ for (;;) {
+ os_memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = sm->ip_addr;
+ addr.sin_port = htons(port);
+ if (bind(sm->web_sd, (struct sockaddr *) &addr,
+ sizeof(addr)) == 0)
+ break;
+ if (errno == EADDRINUSE) {
+ /* search for unused port */
+ if (++port == 65535)
+ goto fail;
+ continue;
+ }
+ goto fail;
+ }
+ if (listen(sm->web_sd, 10 /* max backlog */) != 0)
+ goto fail;
+ if (fcntl(sm->web_sd, F_SETFL, O_NONBLOCK) != 0)
+ goto fail;
+ if (eloop_register_sock(sm->web_sd, EVENT_TYPE_READ,
+ web_listener_handler, NULL, sm))
+ goto fail;
+ sm->web_sd_registered = 1;
+ sm->web_port = port;
+
+ return 0;
+
+fail:
+ /* Error */
+ web_listener_stop(sm);
+ return -1;
+}