WPS: Add support for external Registrars using UPnP transport

This adds mostly feature complete external Registrar support with the
main missing part being proper support for multiple external Registrars
working at the same time and processing of concurrent registrations when
using an external Registrar.

This code is based on Sony/Saice implementation
(https://www.saice-wpsnfc.bz/) and the changes made by Ted Merrill
(Atheros) to make it more suitable for hostapd design and embedded
systems. Some of the UPnP code is based on Intel's libupnp. Copyrights
and licensing are explained in src/wps/wps_upnp.c in more detail.
This commit is contained in:
Jouni Malinen 2009-01-29 18:47:02 +02:00 committed by Jouni Malinen
parent 39034ce80f
commit f620268f13
23 changed files with 6273 additions and 8 deletions

View File

@ -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)

View File

@ -311,6 +311,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

View File

@ -2234,6 +2234,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 "
@ -2441,6 +2458,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 */
}

View File

@ -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 */
};

View File

@ -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

View File

@ -25,6 +25,7 @@
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;
@ -96,6 +97,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 */
};

View File

@ -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 */

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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 */

858
src/wps/httpread.c Normal file
View File

@ -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++;
}
}

123
src/wps/httpread.h Normal file
View File

@ -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 */

View File

@ -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,
@ -419,6 +427,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)
@ -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);

View File

@ -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);

View File

@ -101,6 +101,8 @@ struct wps_data {
* config_error - Configuration Error value to be used in NACK
*/
u16 config_error;
int ext_reg;
};

View File

@ -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;
}

1056
src/wps/wps_upnp.c Normal file

File diff suppressed because it is too large Load Diff

66
src/wps/wps_upnp.h Normal file
View File

@ -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 */

525
src/wps/wps_upnp_event.c Normal file
View File

@ -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;
}

193
src/wps/wps_upnp_i.h Normal file
View File

@ -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 */

887
src/wps/wps_upnp_ssdp.c Normal file
View File

@ -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;
}

1959
src/wps/wps_upnp_web.c Normal file

File diff suppressed because it is too large Load Diff