3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-10-28 15:49:23 +01:00
iwd/src/network.c
Denis Kenzior 2a66b3bfe5 network: Move handshake parameter setup from station
Most parameters set into the handshake object are actually known by the
network object itself and not station.  This includes address
randomization settings, EAPoL settings, passphrase/psk/8021x settings,
etc.  Since the number of these settings will only keep growing, move
the handshake setup into network itself.  This also helps keep network
internals better encapsulated.
2021-07-14 09:55:49 -05:00

1747 lines
42 KiB
C

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2013-2019 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <alloca.h>
#include <linux/if_ether.h>
#include <ell/ell.h>
#include "ell/useful.h"
#include "src/missing.h"
#include "src/module.h"
#include "src/ie.h"
#include "src/crypto.h"
#include "src/iwd.h"
#include "src/common.h"
#include "src/storage.h"
#include "src/scan.h"
#include "src/dbus.h"
#include "src/agent.h"
#include "src/netdev.h"
#include "src/wiphy.h"
#include "src/station.h"
#include "src/eap.h"
#include "src/knownnetworks.h"
#include "src/network.h"
#include "src/blacklist.h"
#include "src/util.h"
#include "src/erp.h"
#include "src/handshake.h"
static uint32_t known_networks_watch;
static uint32_t anqp_watch;
struct network {
char ssid[33];
enum security security;
char *object_path;
struct station *station;
struct network_info *info;
unsigned char *psk;
char *passphrase;
unsigned int agent_request;
struct l_queue *bss_list;
struct l_settings *settings;
struct l_queue *secrets;
struct l_queue *blacklist; /* temporary blacklist for BSS's */
uint8_t hessid[6];
char **nai_realms;
uint8_t *rc_ie;
bool sync_settings:1; /* should settings be synced on connect? */
bool ask_passphrase:1; /* Whether we should force-ask agent */
bool is_hs20:1;
bool anqp_pending:1; /* Set if there is a pending ANQP request */
int rank;
/* Holds DBus Connect() message if it comes in before ANQP finishes */
struct l_dbus_message *connect_after_anqp;
};
static bool network_settings_load(struct network *network)
{
if (network->settings)
return true;
if (network->info)
network->settings = network_info_open_settings(network->info);
return network->settings != NULL;
}
static void network_reset_psk(struct network *network)
{
if (network->psk)
explicit_bzero(network->psk, 32);
l_free(network->psk);
network->psk = NULL;
}
static void network_reset_passphrase(struct network *network)
{
if (network->passphrase) {
explicit_bzero(network->passphrase,
strlen(network->passphrase));
l_free(network->passphrase);
network->passphrase = NULL;
}
}
static void network_settings_close(struct network *network)
{
if (!network->settings)
return;
network_reset_psk(network);
network_reset_passphrase(network);
l_settings_free(network->settings);
network->settings = NULL;
}
static bool network_secret_check_cacheable(void *data, void *user_data)
{
struct eap_secret_info *secret = data;
if (secret->cache_policy == EAP_CACHE_NEVER) {
eap_secret_info_free(secret);
return true;
}
return false;
}
void network_connected(struct network *network)
{
enum security security = network_get_security(network);
const char *ssid = network_get_ssid(network);
int err;
if (!network->info) {
/*
* This is an open network seen for the first time:
*
* Write a settings file to keep track of the
* last connected time. This will also make iwd autoconnect
* to this network in the future.
*/
if (!network->settings)
network->settings = l_settings_new();
storage_network_sync(security, ssid, network->settings);
} else {
err = network_info_touch(network->info);
if (err < 0)
l_error("Error %i touching network config", err);
/* Syncs frequencies of already known network*/
known_network_frequency_sync(network->info);
}
l_queue_foreach_remove(network->secrets,
network_secret_check_cacheable, network);
l_queue_clear(network->blacklist, NULL);
}
void network_disconnected(struct network *network)
{
network_settings_close(network);
l_queue_clear(network->blacklist, NULL);
}
/* First 64 entries calculated by 1 / pow(n, 0.3) for n >= 1 */
static const double rankmod_table[] = {
1.0000000000, 0.8122523964, 0.7192230933, 0.6597539554,
0.6170338627, 0.5841906811, 0.5577898253, 0.5358867313,
0.5172818580, 0.5011872336, 0.4870596972, 0.4745102806,
0.4632516708, 0.4530661223, 0.4437850034, 0.4352752816,
0.4274303178, 0.4201634287, 0.4134032816, 0.4070905315,
0.4011753236, 0.3956154062, 0.3903746872, 0.3854221125,
0.3807307877, 0.3762772797, 0.3720410580, 0.3680040435,
0.3641502401, 0.3604654325, 0.3569369365, 0.3535533906,
0.3503045821, 0.3471812999, 0.3441752105, 0.3412787518,
0.3384850430, 0.3357878061, 0.3331812996, 0.3306602598,
0.3282198502, 0.3258556179, 0.3235634544, 0.3213395618,
0.3191804229, 0.3170827751, 0.3150435863, 0.3130600345,
0.3111294892, 0.3092494947, 0.3074177553, 0.3056321221,
0.3038905808, 0.3021912409, 0.3005323264, 0.2989121662,
0.2973291870, 0.2957819051, 0.2942689208, 0.2927889114,
0.2913406263, 0.2899228820, 0.2885345572, 0.2871745887,
};
bool network_rankmod(const struct network *network, double *rankmod)
{
int n;
int nmax;
/*
* Current policy is that only networks successfully connected
* to at least once are autoconnectable. Known Networks that
* we have never connected to are not.
*/
if (!network->info || !network->info->connected_time)
return false;
n = known_network_offset(network->info);
if (n < 0)
return false;
nmax = L_ARRAY_SIZE(rankmod_table);
if (n >= nmax)
n = nmax - 1;
*rankmod = rankmod_table[n];
return true;
}
struct network *network_create(struct station *station, const char *ssid,
enum security security)
{
struct network *network;
network = l_new(struct network, 1);
network->station = station;
strcpy(network->ssid, ssid);
network->security = security;
network->info = known_networks_find(ssid, security);
if (network->info)
network->info->seen_count++;
network->bss_list = l_queue_new();
network->blacklist = l_queue_new();
return network;
}
const char *network_get_ssid(const struct network *network)
{
return network->ssid;
}
const char *network_get_path(const struct network *network)
{
return network->object_path;
}
enum security network_get_security(const struct network *network)
{
return network->security;
}
static const uint8_t *network_get_psk(struct network *network)
{
int r;
if (network->psk)
return network->psk;
network->psk = l_malloc(32);
if ((r = crypto_psk_from_passphrase(network->passphrase,
(unsigned char *)network->ssid,
strlen(network->ssid),
network->psk)) < 0) {
l_free(network->psk);
network->psk = NULL;
l_error("PSK generation failed: %s.", strerror(-r));
} else
network->sync_settings = true;
return network->psk;
}
static bool __network_set_passphrase(struct network *network,
const char *passphrase)
{
if (!passphrase || !crypto_passphrase_is_valid(passphrase))
return false;
network_reset_passphrase(network);
network->passphrase = l_strdup(passphrase);
network->sync_settings = true;
return true;
}
bool network_set_passphrase(struct network *network, const char *passphrase)
{
if (network_get_security(network) != SECURITY_PSK)
return false;
if (!network_settings_load(network))
network->settings = l_settings_new();
return __network_set_passphrase(network, passphrase);
}
struct l_queue *network_get_secrets(const struct network *network)
{
return network->secrets;
}
bool network_set_psk(struct network *network, const uint8_t *psk)
{
if (network_get_security(network) != SECURITY_PSK)
return false;
if (!network_settings_load(network))
network->settings = l_settings_new();
network_reset_psk(network);
network->psk = l_memdup(psk, 32);
return true;
}
int network_get_signal_strength(const struct network *network)
{
struct scan_bss *best_bss = l_queue_peek_head(network->bss_list);
return best_bss->signal_strength;
}
struct l_settings *network_get_settings(const struct network *network)
{
return network->settings;
}
static bool network_set_8021x_secrets(struct network *network)
{
const struct l_queue_entry *entry;
if (!network->settings)
return false;
for (entry = l_queue_get_entries(network->secrets); entry;
entry = entry->next) {
struct eap_secret_info *secret = entry->data;
char *setting;
switch (secret->type) {
case EAP_SECRET_LOCAL_PKEY_PASSPHRASE:
case EAP_SECRET_REMOTE_PASSWORD:
if (!l_settings_set_string(network->settings,
"Security", secret->id,
secret->value))
return false;
break;
case EAP_SECRET_REMOTE_USER_PASSWORD:
if (!l_settings_set_string(network->settings,
"Security", secret->id,
secret->value))
return false;
if (secret->id2)
setting = secret->id2;
else {
setting = alloca(strlen(secret->id) + 10);
sprintf(setting, "%s-Password", secret->id);
}
if (!l_settings_set_string(network->settings,
"Security", setting,
secret->value + 1 +
strlen(secret->value)))
return false;
break;
}
}
return true;
}
static int network_set_handshake_secrets_psk(struct network *network,
struct handshake_state *hs)
{
/* SAE will generate/set the PMK */
if (IE_AKM_IS_SAE(hs->akm_suite)) {
if (!network->passphrase)
return -ENOKEY;
handshake_state_set_passphrase(hs, network->passphrase);
} else {
const uint8_t *psk = network_get_psk(network);
if (!psk)
return -ENOKEY;
handshake_state_set_pmk(hs, psk, 32);
}
return 0;
}
int network_handshake_setup(struct network *network,
struct handshake_state *hs)
{
struct station *station = network->station;
struct wiphy *wiphy = station_get_wiphy(station);
struct l_settings *settings = network->settings;
uint32_t eapol_proto_version;
const char *value;
bool full_random;
bool override = false;
uint8_t new_addr[ETH_ALEN];
int r;
switch (network->security) {
case SECURITY_PSK:
r = network_set_handshake_secrets_psk(network, hs);
if (r < 0)
return r;
break;
case SECURITY_8021X:
handshake_state_set_8021x_config(hs, settings);
break;
case SECURITY_NONE:
break;
case SECURITY_WEP:
return -ENOTSUP;
}
handshake_state_set_ssid(hs, (void *) network->ssid,
strlen(network->ssid));
if (settings && l_settings_get_uint(settings, "EAPoL",
"ProtocolVersion",
&eapol_proto_version)) {
if (eapol_proto_version > 3) {
l_warn("Invalid ProtocolVersion value - should be 0-3");
eapol_proto_version = 0;
}
if (eapol_proto_version)
l_debug("Overriding EAPoL protocol version to: %u",
eapol_proto_version);
handshake_state_set_protocol_version(hs, eapol_proto_version);
}
/*
* We have three possible options here:
* 1. per-network MAC generation (default, no option in network config)
* 2. per-network full MAC randomization
* 3. per-network MAC override
*/
if (!l_settings_get_bool(settings, "Settings",
"AlwaysRandomizeAddress",
&full_random))
full_random = false;
value = l_settings_get_value(settings, "Settings",
"AddressOverride");
if (value) {
if (util_string_to_address(value, new_addr) &&
util_is_valid_sta_address(new_addr))
override = true;
else
l_warn("[Network].AddressOverride is not a valid "
"MAC address");
}
if (override && full_random) {
l_warn("Cannot use both AlwaysRandomizeAddress and "
"AddressOverride concurrently, defaulting to override");
full_random = false;
}
if (override)
handshake_state_set_supplicant_address(hs, new_addr);
else if (full_random) {
wiphy_generate_random_address(wiphy, new_addr);
handshake_state_set_supplicant_address(hs, new_addr);
}
return 0;
}
static int network_load_psk(struct network *network, bool need_passphrase)
{
const char *ssid = network_get_ssid(network);
enum security security = network_get_security(network);
size_t psk_len;
_auto_(l_free) uint8_t *psk =
l_settings_get_bytes(network->settings, "Security",
"PreSharedKey", &psk_len);
_auto_(l_free) char *passphrase =
l_settings_get_string(network->settings,
"Security", "Passphrase");
_auto_(l_free) char *path =
storage_get_network_file_path(security, ssid);
if (psk && psk_len != 32) {
l_error("%s: invalid PreSharedKey format", path);
l_free(psk);
psk = NULL;
psk_len = 0;
}
/* PSK can be generated from the passphrase but not the other way */
if (!psk || need_passphrase) {
if (!passphrase)
return -ENOKEY;
if (!crypto_passphrase_is_valid(passphrase)) {
l_error("%s: invalid Passphrase format", path);
return -ENOKEY;
}
}
network_reset_passphrase(network);
network_reset_psk(network);
network->passphrase = l_steal_ptr(passphrase);
network->psk = l_steal_ptr(psk);
return 0;
}
void network_sync_settings(struct network *network)
{
struct l_settings *settings = network->settings;
struct l_settings *fs_settings;
const char *ssid = network_get_ssid(network);
if (!network->sync_settings)
return;
network->sync_settings = false;
/*
* Re-open the settings from Disk, in case they were updated
* since we last opened them. We only update the [Security]
* bits here
*/
fs_settings = storage_network_open(SECURITY_PSK, ssid);
if (fs_settings)
settings = fs_settings;
l_settings_remove_group(settings, "Security");
if (network->psk)
l_settings_set_bytes(settings, "Security", "PreSharedKey",
network->psk, 32);
if (network->passphrase)
l_settings_set_string(settings, "Security", "Passphrase",
network->passphrase);
storage_network_sync(SECURITY_PSK, ssid, settings);
if (fs_settings)
l_settings_free(fs_settings);
}
const struct network_info *network_get_info(const struct network *network)
{
return network->info;
}
static void add_known_frequency(void *data, void *user_data)
{
struct scan_bss *bss = data;
struct network_info *info = user_data;
known_network_add_frequency(info, bss->frequency);
}
void network_set_info(struct network *network, struct network_info *info)
{
if (info) {
network->info = info;
network->info->seen_count++;
l_queue_foreach(network->bss_list, add_known_frequency, info);
} else {
network->info->seen_count--;
network->info = NULL;
}
l_dbus_property_changed(dbus_get_bus(), network_get_path(network),
IWD_NETWORK_INTERFACE, "KnownNetwork");
}
static inline bool __bss_is_sae(const struct scan_bss *bss,
const struct ie_rsn_info *rsn)
{
if (rsn->akm_suites & IE_RSN_AKM_SUITE_SAE_SHA256)
return true;
return false;
}
static bool bss_is_sae(const struct scan_bss *bss)
{
struct ie_rsn_info rsn;
memset(&rsn, 0, sizeof(rsn));
scan_bss_get_rsn_info(bss, &rsn);
return __bss_is_sae(bss, &rsn);
}
int network_autoconnect(struct network *network, struct scan_bss *bss)
{
struct station *station = network->station;
struct wiphy *wiphy = station_get_wiphy(station);
enum security security = network_get_security(network);
struct ie_rsn_info rsn;
bool is_rsn;
int ret;
/* already waiting for an agent request, connect in progress */
if (network->agent_request)
return -EALREADY;
switch (security) {
case SECURITY_NONE:
is_rsn = false;
break;
case SECURITY_PSK:
if (network->ask_passphrase)
return -ENOKEY;
/* Fall through */
case SECURITY_8021X:
is_rsn = true;
break;
default:
return -ENOTSUP;
}
if (!network_settings_load(network))
return -ENOKEY;
ret = -EPERM;
if (!network->info->is_autoconnectable)
goto close_settings;
if (!is_rsn)
goto done;
memset(&rsn, 0, sizeof(rsn));
scan_bss_get_rsn_info(bss, &rsn);
if (!wiphy_select_cipher(wiphy, rsn.pairwise_ciphers) ||
!wiphy_select_cipher(wiphy, rsn.group_cipher)) {
l_debug("Cipher mismatch");
ret = -ENETUNREACH;
goto close_settings;
}
if (security == SECURITY_PSK) {
ret = network_load_psk(network, __bss_is_sae(bss, &rsn));
if (ret < 0)
goto close_settings;
} else if (security == SECURITY_8021X) {
struct l_queue *missing_secrets = NULL;
ret = eap_check_settings(network->settings, network->secrets,
"EAP-", true, &missing_secrets);
if (ret < 0)
goto close_settings;
ret = -ENOKEY;
if (!l_queue_isempty(missing_secrets)) {
l_queue_destroy(missing_secrets, eap_secret_info_free);
goto close_settings;
}
if (!network_set_8021x_secrets(network))
goto close_settings;
}
done:
return __station_connect_network(station, network, bss);
close_settings:
network_settings_close(network);
return ret;
}
void network_connect_failed(struct network *network, bool in_handshake)
{
/*
* Connection failed during the handshake phase. If PSK try asking
* for the passphrase once more
*/
if (network_get_security(network) == SECURITY_PSK && in_handshake) {
network->sync_settings = false;
network->ask_passphrase = true;
}
l_queue_destroy(network->secrets, eap_secret_info_free);
network->secrets = NULL;
}
static bool hotspot_info_matches(struct network *network,
const struct network_info *info)
{
struct scan_bss *bss;
if (!network->is_hs20 || !info->is_hotspot)
return false;
bss = network_bss_select(network, true);
if (network_info_match_hessid(info, bss->hessid))
return true;
if (network_info_match_roaming_consortium(info, bss->rc_ie,
bss->rc_ie[1] + 2,
NULL))
return true;
return false;
}
static bool match_hotspot_network(const struct network_info *info,
void *user_data)
{
struct network *network = user_data;
if (!hotspot_info_matches(network, info))
return false;
network_set_info(network, (struct network_info *) info);
return true;
}
bool network_bss_add(struct network *network, struct scan_bss *bss)
{
if (!l_queue_insert(network->bss_list, bss, scan_bss_rank_compare,
NULL))
return false;
if (network->info)
known_network_add_frequency(network->info, bss->frequency);
/* Done if BSS is not HS20 or we already have network_info set */
if (!bss->hs20_capable)
return true;
else
network->is_hs20 = true;
if (network->info)
return true;
/* Set the network_info to a matching hotspot entry, if found */
known_networks_foreach(match_hotspot_network, network);
return true;
}
static bool match_addr(const void *a, const void *b)
{
const struct scan_bss *bss = a;
return memcmp(bss->addr, b, 6) == 0;
}
/*
* Replaces an old scan_bss (if exists) in the bss list with a new bss object.
* Note this BSS is *not* freed and must be by the caller. scan_bss objects are
* shared between network/station but station technically owns them.
*/
bool network_bss_update(struct network *network, struct scan_bss *bss)
{
l_queue_remove_if(network->bss_list, match_addr, bss->addr);
l_queue_insert(network->bss_list, bss, scan_bss_rank_compare, NULL);
/* Sync frequency for already known networks */
if (network->info) {
known_network_add_frequency(network->info, bss->frequency);
known_network_frequency_sync(network->info);
}
return true;
}
bool network_bss_list_isempty(struct network *network)
{
return l_queue_isempty(network->bss_list);
}
void network_bss_list_clear(struct network *network)
{
l_queue_destroy(network->bss_list, NULL);
network->bss_list = l_queue_new();
}
struct scan_bss *network_bss_list_pop(struct network *network)
{
return l_queue_pop_head(network->bss_list);
}
struct scan_bss *network_bss_find_by_addr(struct network *network,
const uint8_t *addr)
{
return l_queue_find(network->bss_list, match_addr, addr);
}
static bool match_bss(const void *a, const void *b)
{
return a == b;
}
bool network_has_erp_identity(struct network *network)
{
struct erp_cache_entry *cache;
struct l_settings *settings;
char *check_id;
const char *identity;
bool ret;
settings = network_get_settings(network);
if (!settings)
return false;
check_id = l_settings_get_string(settings, "Security", "EAP-Identity");
if (!check_id)
return false;
cache = erp_cache_get(network_get_ssid(network));
if (!cache) {
l_free(check_id);
return false;
}
identity = erp_cache_entry_get_identity(cache);
ret = strcmp(check_id, identity) == 0;
l_free(check_id);
erp_cache_put(cache);
/*
* The settings file must have change out from under us. In this
* case we want to remove the ERP entry because it is no longer
* valid.
*/
if (!ret)
erp_cache_remove(identity);
return ret;
}
const struct l_queue_entry *network_bss_list_get_entries(
struct network *network)
{
return l_queue_get_entries(network->bss_list);
}
struct scan_bss *network_bss_select(struct network *network,
bool fallback_to_blacklist)
{
struct l_queue *bss_list = network->bss_list;
struct wiphy *wiphy = station_get_wiphy(network->station);
const struct l_queue_entry *bss_entry;
struct scan_bss *candidate = NULL;
bool fils_hint = network_has_erp_identity(network);
for (bss_entry = l_queue_get_entries(bss_list); bss_entry;
bss_entry = bss_entry->next) {
struct scan_bss *bss = bss_entry->data;
switch (network_get_security(network)) {
case SECURITY_PSK:
case SECURITY_8021X:
if (!wiphy_can_connect(wiphy, bss, fils_hint))
continue;
/* fall through */
case SECURITY_NONE:
break;
default:
return NULL;
}
/*
* We only want to record the first (best) candidate. In case
* all our BSS's are blacklisted but we still want to connect
* we want to hold only this first candidate
*/
if (!candidate)
candidate = bss;
/* check if temporarily blacklisted */
if (l_queue_find(network->blacklist, match_bss, bss))
continue;
if (!blacklist_contains_bss(bss->addr))
return bss;
}
/*
* No BSS was found, but if we are falling back to blacklisted BSS's we
* can just use the first connectable candidate found above.
*/
if (fallback_to_blacklist)
return candidate;
return NULL;
}
static void passphrase_callback(enum agent_result result,
const char *passphrase,
struct l_dbus_message *message,
void *user_data)
{
struct network *network = user_data;
struct station *station = network->station;
struct scan_bss *bss;
l_debug("result %d", result);
network->agent_request = 0;
/*
* agent will release its reference to message after invoking this
* callback. So if we want this message, we need to take a reference
* to it
*/
l_dbus_message_ref(message);
if (result != AGENT_RESULT_OK) {
dbus_pending_reply(&message, dbus_error_aborted(message));
goto err;
}
bss = network_bss_select(network, true);
/* Did all good BSSes go away while we waited */
if (!bss) {
dbus_pending_reply(&message, dbus_error_failed(message));
goto err;
}
network_reset_psk(network);
if (!__network_set_passphrase(network, passphrase)) {
dbus_pending_reply(&message,
dbus_error_invalid_format(message));
goto err;
}
station_connect_network(station, network, bss, message);
l_dbus_message_unref(message);
return;
err:
network_settings_close(network);
}
static struct l_dbus_message *network_connect_psk(struct network *network,
struct scan_bss *bss,
struct l_dbus_message *message)
{
struct station *station = network->station;
/*
* A legacy psk file may only contain the PreSharedKey entry. For SAE
* networks the raw Passphrase is required. So in this case where
* the psk is found but no Passphrase, we ask the agent. The psk file
* will then be re-written to contain the raw passphrase.
*/
bool need_passphrase = bss_is_sae(bss);
if (!network_settings_load(network)) {
network->settings = l_settings_new();
network->ask_passphrase = true;
} else if (!network->ask_passphrase)
network->ask_passphrase =
network_load_psk(network, need_passphrase) < 0;
l_debug("ask_passphrase: %s",
network->ask_passphrase ? "true" : "false");
if (network->ask_passphrase) {
network->ask_passphrase = false;
network->agent_request =
agent_request_passphrase(network->object_path,
passphrase_callback,
message, network, NULL);
if (!network->agent_request)
return dbus_error_no_agent(message);
} else
station_connect_network(station, network, bss, message);
return NULL;
}
struct eap_secret_request {
struct network *network;
struct eap_secret_info *secret;
struct l_queue *pending_secrets;
void (*callback)(enum agent_result result,
struct l_dbus_message *message,
struct eap_secret_request *req);
};
static void eap_secret_request_free(void *data)
{
struct eap_secret_request *req = data;
eap_secret_info_free(req->secret);
l_queue_destroy(req->pending_secrets, eap_secret_info_free);
l_free(req);
}
static bool eap_secret_info_match_local(const void *a, const void *b)
{
const struct eap_secret_info *info = a;
return info->type == EAP_SECRET_LOCAL_PKEY_PASSPHRASE;
}
static void eap_password_callback(enum agent_result result, const char *value,
struct l_dbus_message *message,
void *user_data)
{
struct eap_secret_request *req = user_data;
req->network->agent_request = 0;
if (value) {
if (strlen(value) < IWD_MAX_PASSWORD_LEN)
req->secret->value = l_strdup(value);
else {
l_error("EAP password too long");
result = AGENT_RESULT_FAILED;
}
}
req->callback(result, message, req);
}
static void eap_user_password_callback(enum agent_result result,
const char *user, const char *passwd,
struct l_dbus_message *message,
void *user_data)
{
struct eap_secret_request *req = user_data;
req->network->agent_request = 0;
if (user && passwd) {
size_t len1 = strlen(user) + 1;
size_t len2 = strlen(passwd) + 1;
if (len2 > IWD_MAX_PASSWORD_LEN) {
l_error("EAP password too long");
result = AGENT_RESULT_FAILED;
goto done;
}
req->secret->value = l_malloc(len1 + len2);
memcpy(req->secret->value, user, len1);
memcpy(req->secret->value + len1, passwd, len2);
}
done:
req->callback(result, message, req);
}
static bool eap_send_agent_req(struct network *network,
struct l_queue *pending_secrets,
struct l_dbus_message *message,
void *callback)
{
struct eap_secret_request *req;
struct eap_secret_info *info;
/*
* Request the locally-verifiable data first, i.e.
* the private key encryption passphrases so that we don't bother
* asking for any other data if these passphrases turn out to
* be wrong.
*/
info = l_queue_remove_if(pending_secrets, eap_secret_info_match_local,
NULL);
if (!info)
info = l_queue_pop_head(pending_secrets);
req = l_new(struct eap_secret_request, 1);
req->network = network;
req->secret = info;
req->pending_secrets = pending_secrets;
req->callback = callback;
switch (info->type) {
case EAP_SECRET_LOCAL_PKEY_PASSPHRASE:
network->agent_request = agent_request_pkey_passphrase(
network->object_path,
eap_password_callback,
message, req,
eap_secret_request_free);
break;
case EAP_SECRET_REMOTE_PASSWORD:
network->agent_request = agent_request_user_password(
network->object_path,
info->parameter,
eap_password_callback,
message, req,
eap_secret_request_free);
break;
case EAP_SECRET_REMOTE_USER_PASSWORD:
network->agent_request = agent_request_user_name_password(
network->object_path,
eap_user_password_callback,
message, req,
eap_secret_request_free);
break;
}
if (network->agent_request)
return true;
eap_secret_request_free(req);
return false;
}
static struct l_dbus_message *network_connect_8021x(struct network *network,
struct scan_bss *bss,
struct l_dbus_message *message);
static void eap_secret_done(enum agent_result result,
struct l_dbus_message *message,
struct eap_secret_request *req)
{
struct network *network = req->network;
struct eap_secret_info *secret = req->secret;
struct l_queue *pending = req->pending_secrets;
struct scan_bss *bss;
l_debug("result %d", result);
/*
* Agent will release its reference to message after invoking this
* callback. So if we want this message, we need to take a reference
* to it.
*/
l_dbus_message_ref(message);
if (result != AGENT_RESULT_OK) {
dbus_pending_reply(&message, dbus_error_aborted(message));
goto err;
}
bss = network_bss_select(network, true);
/* Did all good BSSes go away while we waited */
if (!bss) {
dbus_pending_reply(&message, dbus_error_failed(message));
goto err;
}
if (!network->secrets)
network->secrets = l_queue_new();
l_queue_push_tail(network->secrets, secret);
req->secret = NULL;
/*
* If we have any other missing secrets in the queue, send the
* next request immediately unless we've just received a passphrase
* for a local private key. In that case we will first call
* network_connect_8021x to have it validate the new passphrase.
*/
if (secret->type == EAP_SECRET_LOCAL_PKEY_PASSPHRASE ||
l_queue_isempty(req->pending_secrets)) {
struct l_dbus_message *reply;
reply = network_connect_8021x(network, bss, message);
if (reply)
dbus_pending_reply(&message, reply);
else
l_dbus_message_unref(message);
return;
}
req->pending_secrets = NULL;
if (eap_send_agent_req(network, pending, message,
eap_secret_done)) {
l_dbus_message_unref(message);
return;
}
dbus_pending_reply(&message, dbus_error_no_agent(message));
err:
network_settings_close(network);
}
static struct l_dbus_message *network_connect_8021x(struct network *network,
struct scan_bss *bss,
struct l_dbus_message *message)
{
struct station *station = network->station;
int r;
struct l_queue *missing_secrets = NULL;
struct l_dbus_message *reply;
l_debug("");
r = eap_check_settings(network->settings, network->secrets, "EAP-",
true, &missing_secrets);
if (r) {
if (r == -EUNATCH)
reply = dbus_error_not_available(message);
else if (r == -ENOTSUP)
reply = dbus_error_not_supported(message);
else if (r == -EACCES)
reply = dbus_error_failed(message);
else
reply = dbus_error_not_configured(message);
goto error;
}
l_debug("supplied %u secrets, %u more needed for EAP",
l_queue_length(network->secrets),
l_queue_length(missing_secrets));
if (l_queue_isempty(missing_secrets)) {
if (!network_set_8021x_secrets(network)) {
reply = dbus_error_failed(message);
goto error;
}
station_connect_network(station, network, bss, message);
return NULL;
}
if (eap_send_agent_req(network, missing_secrets, message,
eap_secret_done))
return NULL;
reply = dbus_error_no_agent(message);
error:
network_settings_close(network);
l_queue_destroy(network->secrets, eap_secret_info_free);
network->secrets = NULL;
return reply;
}
static struct l_dbus_message *network_connect(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct network *network = user_data;
struct station *station = network->station;
struct scan_bss *bss;
l_debug("");
if (network == station_get_connected_network(station))
/*
* The requested network is already connected, return success.
*/
return l_dbus_message_new_method_return(message);
if (network->agent_request)
return dbus_error_busy(message);
/*
* Select the best BSS to use at this time. If we have to query the
* agent this may not be the final choice because BSS visibility can
* change while we wait for the agent.
*/
bss = network_bss_select(network, true);
/* None of the BSSes is compatible with our stack */
if (!bss)
return dbus_error_not_supported(message);
switch (network_get_security(network)) {
case SECURITY_PSK:
return network_connect_psk(network, bss, message);
case SECURITY_NONE:
station_connect_network(station, network, bss, message);
return NULL;
case SECURITY_8021X:
if (network->connect_after_anqp)
return dbus_error_busy(message);
/*
* If there is an ongoing ANQP request we must wait for that to
* finish. Save the message and wait for the ANQP watch to
* fire
*/
if (network->anqp_pending) {
network->connect_after_anqp =
l_dbus_message_ref(message);
l_debug("Pending ANQP request, delaying connect to %s",
network->ssid);
return NULL;
}
if (!network_settings_load(network))
return dbus_error_not_configured(message);
return network_connect_8021x(network, bss, message);
default:
return dbus_error_not_supported(message);
}
}
/*
* Returns an error message in case an error occurs. Otherwise this function
* returns NULL and takes a reference to message. Callers should unref
* their copy in this case
*/
struct l_dbus_message *network_connect_new_hidden_network(
struct network *network,
struct l_dbus_message *message)
{
struct station *station = network->station;
struct scan_bss *bss;
l_debug("");
if (network->agent_request)
return dbus_error_busy(message);
/*
* This is not a Known Network. If connection succeeds, either
* network_sync_settings or network_connected will save this network
* as hidden and trigger an update to the hidden networks count.
*/
bss = network_bss_select(network, true);
/* This should never happened for the hidden networks. */
if (!bss)
return dbus_error_not_supported(message);
network->settings = l_settings_new();
l_settings_set_bool(network->settings, "Settings", "Hidden", true);
switch (network_get_security(network)) {
case SECURITY_PSK:
return network_connect_psk(network, bss, message);
case SECURITY_NONE:
station_connect_network(station, network, bss, message);
return NULL;
default:
break;
}
return dbus_error_not_supported(message);
}
void network_blacklist_add(struct network *network, struct scan_bss *bss)
{
l_queue_push_head(network->blacklist, bss);
}
const struct iovec *network_get_extra_ies(struct network *network,
size_t *num_elems)
{
struct scan_bss *bss = network_bss_select(network, false);
return network_info_get_extra_ies(network->info, bss, num_elems);
}
static bool network_property_get_name(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network *network = user_data;
l_dbus_message_builder_append_basic(builder, 's',
network_get_ssid(network));
return true;
}
static bool network_property_is_connected(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network *network = user_data;
struct station *station = network->station;
bool connected;
connected = station_get_connected_network(station) == network;
l_dbus_message_builder_append_basic(builder, 'b', &connected);
return true;
}
static bool network_property_get_device(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network *network = user_data;
struct station *station = network->station;
struct netdev *netdev = station_get_netdev(station);
l_dbus_message_builder_append_basic(builder, 'o',
netdev_get_path(netdev));
return true;
}
static bool network_property_get_type(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network *network = user_data;
enum security security = network_get_security(network);
l_dbus_message_builder_append_basic(builder, 's',
security_to_str(security));
return true;
}
static bool network_property_get_known_network(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network *network = user_data;
if (!network->info)
return false;
l_dbus_message_builder_append_basic(builder, 'o',
network_info_get_path(network->info));
return true;
}
bool network_register(struct network *network, const char *path)
{
if (!l_dbus_object_add_interface(dbus_get_bus(), path,
IWD_NETWORK_INTERFACE, network)) {
l_info("Unable to register %s interface",
IWD_NETWORK_INTERFACE);
return false;
}
if (!l_dbus_object_add_interface(dbus_get_bus(), path,
L_DBUS_INTERFACE_PROPERTIES, network))
l_info("Unable to register %s interface",
L_DBUS_INTERFACE_PROPERTIES);
network->object_path = l_strdup(path);
return true;
}
static void network_unregister(struct network *network, int reason)
{
struct l_dbus *dbus = dbus_get_bus();
agent_request_cancel(network->agent_request, reason);
network_settings_close(network);
l_dbus_unregister_object(dbus, network->object_path);
l_free(network->object_path);
network->object_path = NULL;
}
void network_remove(struct network *network, int reason)
{
if (network->object_path)
network_unregister(network, reason);
l_queue_destroy(network->secrets, eap_secret_info_free);
network->secrets = NULL;
if (network->info)
network->info->seen_count -= 1;
l_queue_destroy(network->bss_list, NULL);
l_queue_destroy(network->blacklist, NULL);
if (network->nai_realms)
l_strv_free(network->nai_realms);
if (network->rc_ie)
l_free(network->rc_ie);
l_free(network);
}
int network_rank_compare(const void *a, const void *b, void *user)
{
const struct network *new_network = a;
const struct network *network = b;
return (network->rank > new_network->rank) ? 1 : -1;
}
void network_rank_update(struct network *network, bool connected)
{
static const double RANK_RSNE_FACTOR = 1.2;
static const double RANK_WPA_FACTOR = 1.0;
static const double RANK_OPEN_FACTOR = 0.5;
static const double RANK_NO_PRIVACY_FACTOR = 0.5;
/*
* Theoretically there may be difference between the BSS selection
* here and in network_bss_select but those should be rare cases.
*/
struct scan_bss *best_bss = l_queue_peek_head(network->bss_list);
/*
* The rank should separate networks into four groups that use
* non-overlapping ranges for:
* - current connected network,
* - other networks we've connected to before,
* - networks with preprovisioned settings file that we haven't
* used yet,
* - other networks.
*
* Within the 2nd group the last connection time is the main factor,
* for the other two groups it's the BSS rank - mainly signal strength.
*/
if (connected) {
network->rank = INT_MAX;
return;
}
if (!network->info) { /* Not known, assign negative rank */
network->rank = (int) best_bss->rank - USHRT_MAX;
return;
}
if (network->info->connected_time != 0) {
int n = known_network_offset(network->info);
L_WARN_ON(n < 0);
if (n >= (int) L_ARRAY_SIZE(rankmod_table))
n = L_ARRAY_SIZE(rankmod_table) - 1;
network->rank = rankmod_table[n] * best_bss->rank + USHRT_MAX;
} else
network->rank = best_bss->rank;
/*
* Prefer RSNE first, WPA second. Open networks are much less
* desirable.
*/
if (best_bss->rsne)
network->rank *= RANK_RSNE_FACTOR;
else if (best_bss->wpa)
network->rank *= RANK_WPA_FACTOR;
else
network->rank *= RANK_OPEN_FACTOR;
/* We prefer networks with CAP PRIVACY */
if (!(best_bss->capability & IE_BSS_CAP_PRIVACY))
network->rank *= RANK_NO_PRIVACY_FACTOR;
}
static void network_unset_hotspot(struct network *network, void *user_data)
{
struct network_info *info = user_data;
if (network->info != info)
return;
network_set_info(network, NULL);
}
static void emit_known_network_removed(struct station *station, void *user_data)
{
struct network_info *info = user_data;
bool was_hidden = info->is_hidden;
struct network *connected_network;
struct network *network = NULL;
/* Clear network info, as this network is no longer known */
if (info->is_hotspot)
station_network_foreach(station, network_unset_hotspot, info);
else {
network = station_network_find(station, info->ssid, info->type);
if (!network)
return;
network_set_info(network, NULL);
}
connected_network = station_get_connected_network(station);
if (connected_network && connected_network->info == NULL)
station_disconnect(station);
if (network && was_hidden)
station_hide_network(station, network);
}
static void network_update_hotspot(struct network *network, void *user_data)
{
struct network_info *info = user_data;
match_hotspot_network(info, network);
}
static void match_known_network(struct station *station, void *user_data)
{
struct network_info *info = user_data;
struct network *network;
if (!info->is_hotspot) {
network = station_network_find(station, info->ssid, info->type);
if (!network)
return;
network_set_info(network, info);
return;
}
/* This is a new hotspot network */
station_network_foreach(station, network_update_hotspot, info);
}
static void known_networks_changed(enum known_networks_event event,
const struct network_info *info,
void *user_data)
{
switch (event) {
case KNOWN_NETWORKS_EVENT_ADDED:
station_foreach(match_known_network, (void *) info);
/* Syncs frequencies of newly known network */
known_network_frequency_sync((struct network_info *)info);
break;
case KNOWN_NETWORKS_EVENT_REMOVED:
station_foreach(emit_known_network_removed, (void *) info);
break;
}
}
static void anqp_watch_changed(enum station_anqp_state state,
struct network *network, void *user_data)
{
network->anqp_pending = state == STATION_ANQP_STARTED;
if (state == STATION_ANQP_FINISHED && network->connect_after_anqp) {
struct l_dbus_message *reply;
l_debug("ANQP complete, resuming connect to %s", network->ssid);
if (!network_settings_load(network)) {
reply = dbus_error_not_configured(
network->connect_after_anqp);
dbus_pending_reply(&network->connect_after_anqp, reply);
return;
}
reply = network_connect_8021x(network,
network_bss_select(network, true),
network->connect_after_anqp);
if (reply)
l_dbus_send(dbus_get_bus(), reply);
l_dbus_message_unref(network->connect_after_anqp);
network->connect_after_anqp = NULL;
}
}
static void setup_network_interface(struct l_dbus_interface *interface)
{
l_dbus_interface_method(interface, "Connect", 0,
network_connect,
"", "");
l_dbus_interface_property(interface, "Name", 0, "s",
network_property_get_name, NULL);
l_dbus_interface_property(interface, "Connected", 0, "b",
network_property_is_connected,
NULL);
l_dbus_interface_property(interface, "Device", 0, "o",
network_property_get_device, NULL);
l_dbus_interface_property(interface, "Type", 0, "s",
network_property_get_type, NULL);
l_dbus_interface_property(interface, "KnownNetwork", 0, "o",
network_property_get_known_network, NULL);
}
static int network_init(void)
{
if (!l_dbus_register_interface(dbus_get_bus(), IWD_NETWORK_INTERFACE,
setup_network_interface, NULL, false))
l_error("Unable to register %s interface",
IWD_NETWORK_INTERFACE);
known_networks_watch =
known_networks_watch_add(known_networks_changed, NULL, NULL);
anqp_watch = station_add_anqp_watch(anqp_watch_changed, NULL, NULL);
return 0;
}
static void network_exit(void)
{
known_networks_watch_remove(known_networks_watch);
known_networks_watch = 0;
station_remove_anqp_watch(anqp_watch);
anqp_watch = 0;
l_dbus_unregister_interface(dbus_get_bus(), IWD_NETWORK_INTERFACE);
}
IWD_MODULE(network, network_init, network_exit)
IWD_MODULE_DEPENDS(network, known_networks)