diff options
Diffstat (limited to 'src/supplicant')
| -rw-r--r-- | src/supplicant/nm-supplicant-config.c | 1246 | ||||
| -rw-r--r-- | src/supplicant/nm-supplicant-config.h | 79 | ||||
| -rw-r--r-- | src/supplicant/nm-supplicant-interface.c | 1681 | ||||
| -rw-r--r-- | src/supplicant/nm-supplicant-interface.h | 122 | ||||
| -rw-r--r-- | src/supplicant/nm-supplicant-manager.c | 413 | ||||
| -rw-r--r-- | src/supplicant/nm-supplicant-manager.h | 45 | ||||
| -rw-r--r-- | src/supplicant/nm-supplicant-settings-verify.c | 281 | ||||
| -rw-r--r-- | src/supplicant/nm-supplicant-settings-verify.h | 38 | ||||
| -rw-r--r-- | src/supplicant/nm-supplicant-types.h | 58 | ||||
| -rw-r--r-- | src/supplicant/tests/certs/test-ca-cert.pem | 27 | ||||
| -rw-r--r-- | src/supplicant/tests/certs/test-cert.p12 | bin | 0 -> 4092 bytes | |||
| -rw-r--r-- | src/supplicant/tests/test-supplicant-config.c | 614 |
12 files changed, 4604 insertions, 0 deletions
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c new file mode 100644 index 00000000..8f766d7c --- /dev/null +++ b/src/supplicant/nm-supplicant-config.c @@ -0,0 +1,1246 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2012 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-supplicant-config.h" + +#include <string.h> +#include <stdlib.h> + +#include "nm-supplicant-settings-verify.h" +#include "nm-setting.h" +#include "NetworkManagerUtils.h" +#include "nm-utils.h" + +typedef struct { + char *value; + guint32 len; + OptType type; +} ConfigOption; + +/*****************************************************************************/ + +typedef struct { + GHashTable *config; + GHashTable *blobs; + guint32 ap_scan; + gboolean fast_required; + gboolean dispose_has_run; +} NMSupplicantConfigPrivate; + +struct _NMSupplicantConfig { + GObject parent; + NMSupplicantConfigPrivate _priv; +}; + +struct _NMSupplicantConfigClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE (NMSupplicantConfig, nm_supplicant_config, G_TYPE_OBJECT) + +#define NM_SUPPLICANT_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantConfig, NM_IS_SUPPLICANT_CONFIG) + +/*****************************************************************************/ + +NMSupplicantConfig * +nm_supplicant_config_new (void) +{ + return g_object_new (NM_TYPE_SUPPLICANT_CONFIG, NULL); +} + +static void +config_option_free (ConfigOption *opt) +{ + g_free (opt->value); + g_slice_free (ConfigOption, opt); +} + +static void +blob_free (GByteArray *array) +{ + g_byte_array_free (array, TRUE); +} + +static void +nm_supplicant_config_init (NMSupplicantConfig * self) +{ + NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self); + + priv->config = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) config_option_free); + + priv->blobs = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) blob_free); + + priv->ap_scan = 1; + priv->dispose_has_run = FALSE; +} + +static gboolean +nm_supplicant_config_add_option_with_type (NMSupplicantConfig *self, + const char *key, + const char *value, + gint32 len, + OptType opt_type, + const char *hidden, + GError **error) +{ + NMSupplicantConfigPrivate *priv; + ConfigOption *old_opt; + ConfigOption *opt; + OptType type; + + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE); + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + nm_assert (!error || !*error); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self); + + if (len < 0) + len = strlen (value); + + if (opt_type != TYPE_INVALID) + type = opt_type; + else { + type = nm_supplicant_settings_verify_setting (key, value, len); + if (type == TYPE_INVALID) { + char buf[255]; + memset (&buf[0], 0, sizeof (buf)); + memcpy (&buf[0], value, len > 254 ? 254 : len); + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "key '%s' and/or value '%s' invalid", key, hidden ? hidden : buf); + return FALSE; + } + } + + old_opt = (ConfigOption *) g_hash_table_lookup (priv->config, key); + if (old_opt) { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "key '%s' already configured", key); + return FALSE; + } + + opt = g_slice_new0 (ConfigOption); + opt->value = g_malloc (len + 1); + memcpy (opt->value, value, len); + opt->value[len] = '\0'; + + opt->len = len; + opt->type = type; + + { + char buf[255]; + memset (&buf[0], 0, sizeof (buf)); + memcpy (&buf[0], opt->value, opt->len > 254 ? 254 : opt->len); + nm_log_info (LOGD_SUPPLICANT, "Config: added '%s' value '%s'", key, hidden ? hidden : &buf[0]); + } + + g_hash_table_insert (priv->config, g_strdup (key), opt); + + return TRUE; +} + +static gboolean +nm_supplicant_config_add_option (NMSupplicantConfig *self, + const char *key, + const char *value, + gint32 len, + const char *hidden, + GError **error) +{ + return nm_supplicant_config_add_option_with_type (self, key, value, len, TYPE_INVALID, hidden, error); +} + +static gboolean +nm_supplicant_config_add_blob (NMSupplicantConfig *self, + const char *key, + GBytes *value, + const char *blobid, + GError **error) +{ + NMSupplicantConfigPrivate *priv; + ConfigOption *old_opt; + ConfigOption *opt; + OptType type; + GByteArray *blob; + const guint8 *data; + gsize data_len; + + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE); + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + g_return_val_if_fail (blobid != NULL, FALSE); + + data = g_bytes_get_data (value, &data_len); + g_return_val_if_fail (data_len > 0, FALSE); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self); + + type = nm_supplicant_settings_verify_setting (key, (const char *) data, data_len); + if (type == TYPE_INVALID) { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "key '%s' and/or its contained value is invalid", key); + return FALSE; + } + + old_opt = (ConfigOption *) g_hash_table_lookup (priv->config, key); + if (old_opt) { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "key '%s' already configured", key); + return FALSE; + } + + blob = g_byte_array_sized_new (data_len); + g_byte_array_append (blob, data, data_len); + + opt = g_slice_new0 (ConfigOption); + opt->value = g_strdup_printf ("blob://%s", blobid); + opt->len = strlen (opt->value); + opt->type = type; + + nm_log_info (LOGD_SUPPLICANT, "Config: added '%s' value '%s'", key, opt->value); + + g_hash_table_insert (priv->config, g_strdup (key), opt); + g_hash_table_insert (priv->blobs, g_strdup (blobid), blob); + + return TRUE; +} + +static gboolean +nm_supplicant_config_add_blob_for_connection (NMSupplicantConfig *self, + GBytes *field, + const char *name, + const char *con_uid, + GError **error) +{ + if (field && g_bytes_get_size (field)) { + gs_free char *uid = NULL; + char *p; + + uid = g_strdup_printf ("%s-%s", con_uid, name); + for (p = uid; *p; p++) { + if (*p == '/') + *p = '-'; + } + if (!nm_supplicant_config_add_blob (self, name, field, uid, error)) + return FALSE; + } + return TRUE; +} + +static void +nm_supplicant_config_finalize (GObject *object) +{ + NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE ((NMSupplicantConfig *) object); + + g_hash_table_destroy (priv->config); + g_hash_table_destroy (priv->blobs); + + G_OBJECT_CLASS (nm_supplicant_config_parent_class)->finalize (object); +} + + +static void +nm_supplicant_config_class_init (NMSupplicantConfigClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = nm_supplicant_config_finalize; +} + +guint32 +nm_supplicant_config_get_ap_scan (NMSupplicantConfig * self) +{ + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), 1); + + return NM_SUPPLICANT_CONFIG_GET_PRIVATE (self)->ap_scan; +} + +gboolean +nm_supplicant_config_fast_required (NMSupplicantConfig *self) +{ + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE); + + return NM_SUPPLICANT_CONFIG_GET_PRIVATE (self)->fast_required; +} + +GVariant * +nm_supplicant_config_to_variant (NMSupplicantConfig *self) +{ + NMSupplicantConfigPrivate *priv; + GVariantBuilder builder; + GHashTableIter iter; + ConfigOption *option; + const char *key; + + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), NULL); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self); + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + + g_hash_table_iter_init (&iter, priv->config); + while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &option)) { + switch (option->type) { + case TYPE_INT: + g_variant_builder_add (&builder, "{sv}", key, g_variant_new_int32 (atoi (option->value))); + break; + case TYPE_BYTES: + case TYPE_UTF8: + g_variant_builder_add (&builder, "{sv}", + key, + g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + option->value, option->len, 1)); + break; + case TYPE_KEYWORD: + case TYPE_STRING: + g_variant_builder_add (&builder, "{sv}", key, g_variant_new_string (option->value)); + break; + default: + break; + } + } + + return g_variant_builder_end (&builder); +} + +GHashTable * +nm_supplicant_config_get_blobs (NMSupplicantConfig * self) +{ + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), NULL); + + return NM_SUPPLICANT_CONFIG_GET_PRIVATE (self)->blobs; +} + +static const char * +wifi_freqs_to_string (gboolean bg_band) +{ + static const char *str_2ghz = NULL; + static const char *str_5ghz = NULL; + const char *str; + + str = bg_band ? str_2ghz : str_5ghz; + + if (G_UNLIKELY (str == NULL)) { + GString *tmp; + const guint *freqs; + int i; + + freqs = bg_band ? nm_utils_wifi_2ghz_freqs () : nm_utils_wifi_5ghz_freqs (); + tmp = g_string_sized_new (bg_band ? 70 : 225); + for (i = 0; freqs[i]; i++) + g_string_append_printf (tmp, i == 0 ? "%d" : " %d", freqs[i]); + str = g_string_free (tmp, FALSE); + if (bg_band) + str_2ghz = str; + else + str_5ghz = str; + } + return str; +} + +gboolean +nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self, + NMSettingMacsec * setting, + GError **error) +{ + NMSupplicantConfigPrivate *priv; + gs_unref_bytes GBytes *bytes = NULL; + const char *value; + char buf[32]; + int port; + + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE); + g_return_val_if_fail (setting != NULL, FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self); + + if (!nm_supplicant_config_add_option (self, "macsec_policy", "1", -1, NULL, error)) + return FALSE; + + value = nm_setting_macsec_get_encrypt (setting) ? "0" : "1"; + if (!nm_supplicant_config_add_option (self, "macsec_integ_only", value, -1, NULL, error)) + return FALSE; + + port = nm_setting_macsec_get_port (setting); + if (port > 0 && port < 65534) { + snprintf (buf, sizeof (buf), "%d", port); + if (!nm_supplicant_config_add_option (self, "macsec_port", buf, -1, NULL, error)) + return FALSE; + } + + if (nm_setting_macsec_get_mode (setting) == NM_SETTING_MACSEC_MODE_PSK) { + if (!nm_supplicant_config_add_option (self, "key_mgmt", "NONE", -1, NULL, error)) + return FALSE; + + /* CAK */ + value = nm_setting_macsec_get_mka_cak (setting); + if (!value) { + g_set_error_literal (error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "missing MKA CAK"); + return FALSE; + } + + bytes = nm_utils_hexstr2bin (value); + if (!nm_supplicant_config_add_option (self, + "mka_cak", + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + "<hidden>", + error)) + return FALSE; + + /* CKN */ + value = nm_setting_macsec_get_mka_ckn (setting); + if (!value) { + g_set_error_literal (error, + NM_SUPPLICANT_ERROR, + NM_SUPPLICANT_ERROR_CONFIG, + "missing MKA CKN"); + return FALSE; + } + + bytes = nm_utils_hexstr2bin (value); + if (!nm_supplicant_config_add_option (self, + "mka_ckn", + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + NULL, + error)) + return FALSE; + } + + return TRUE; +} + +gboolean +nm_supplicant_config_add_setting_wireless (NMSupplicantConfig * self, + NMSettingWireless * setting, + guint32 fixed_freq, + GError **error) +{ + NMSupplicantConfigPrivate *priv; + gboolean is_adhoc, is_ap; + const char *mode, *band; + guint32 channel; + GBytes *ssid; + const char *bssid; + + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE); + g_return_val_if_fail (setting != NULL, FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self); + + mode = nm_setting_wireless_get_mode (setting); + is_adhoc = (mode && !strcmp (mode, "adhoc")) ? TRUE : FALSE; + is_ap = (mode && !strcmp (mode, "ap")) ? TRUE : FALSE; + if (is_adhoc || is_ap) + priv->ap_scan = 2; + else + priv->ap_scan = 1; + + ssid = nm_setting_wireless_get_ssid (setting); + if (!nm_supplicant_config_add_option (self, "ssid", + (char *) g_bytes_get_data (ssid, NULL), + g_bytes_get_size (ssid), + NULL, + error)) + return FALSE; + + if (is_adhoc) { + if (!nm_supplicant_config_add_option (self, "mode", "1", -1, NULL, error)) + return FALSE; + } + + if (is_ap) { + if (!nm_supplicant_config_add_option (self, "mode", "2", -1, NULL, error)) + return FALSE; + } + + if ((is_adhoc || is_ap) && fixed_freq) { + gs_free char *str_freq = NULL; + + str_freq = g_strdup_printf ("%u", fixed_freq); + if (!nm_supplicant_config_add_option (self, "frequency", str_freq, -1, NULL, error)) + return FALSE; + } + + /* Except for Ad-Hoc and Hotspot, request that the driver probe for the + * specific SSID we want to associate with. + */ + if (!(is_adhoc || is_ap)) { + if (!nm_supplicant_config_add_option (self, "scan_ssid", "1", -1, NULL, error)) + return FALSE; + } + + bssid = nm_setting_wireless_get_bssid (setting); + if (bssid) { + if (!nm_supplicant_config_add_option (self, "bssid", + bssid, strlen (bssid), + NULL, + error)) + return FALSE; + } + + band = nm_setting_wireless_get_band (setting); + channel = nm_setting_wireless_get_channel (setting); + if (band) { + if (channel) { + guint32 freq; + gs_free char *str_freq = NULL; + + freq = nm_utils_wifi_channel_to_freq (channel, band); + str_freq = g_strdup_printf ("%u", freq); + if (!nm_supplicant_config_add_option (self, "freq_list", str_freq, -1, NULL, error)) + return FALSE; + } else { + const char *freqs = NULL; + + if (!strcmp (band, "a")) + freqs = wifi_freqs_to_string (FALSE); + else if (!strcmp (band, "bg")) + freqs = wifi_freqs_to_string (TRUE); + + if (freqs && !nm_supplicant_config_add_option (self, "freq_list", freqs, strlen (freqs), NULL, error)) + return FALSE; + } + } + + return TRUE; +} + +static gboolean +add_string_val (NMSupplicantConfig *self, + const char *field, + const char *name, + gboolean ucase, + const char *hidden, + GError **error) +{ + + if (field) { + gs_free char *value = NULL; + + if (ucase) { + value = g_ascii_strup (field, -1); + field = value; + } + return nm_supplicant_config_add_option (self, name, field, strlen (field), hidden, error); + } + return TRUE; +} + +#define ADD_STRING_LIST_VAL(self, setting, setting_name, field, field_plural, name, separator, ucase, hidden, error) \ + ({ \ + typeof (*(setting)) *_setting = (setting); \ + gboolean _success = TRUE; \ + \ + if (nm_setting_##setting_name##_get_num_##field_plural (_setting)) { \ + const char _separator = (separator); \ + GString *_str = g_string_new (NULL); \ + guint _k, _n; \ + \ + _n = nm_setting_##setting_name##_get_num_##field_plural (_setting); \ + for (_k = 0; _k < _n; _k++) { \ + const char *item = nm_setting_##setting_name##_get_##field (_setting, _k); \ + \ + if (!_str->len) { \ + g_string_append (_str, item); \ + } else { \ + g_string_append_c (_str, _separator); \ + g_string_append (_str, item); \ + } \ + } \ + if ((ucase)) \ + g_string_ascii_up (_str); \ + if (_str->len) { \ + if (!nm_supplicant_config_add_option ((self), (name), _str->str, -1, (hidden), (error))) \ + _success = FALSE; \ + } \ + g_string_free (_str, TRUE); \ + } \ + _success; \ + }) + +static void +wep128_passphrase_hash (const char *input, + size_t input_len, + guint8 *out_digest, + size_t *out_digest_len) +{ + GChecksum *sum; + guint8 data[64]; + int i; + + g_return_if_fail (out_digest != NULL); + g_return_if_fail (out_digest_len != NULL); + g_return_if_fail (*out_digest_len >= 16); + + /* Get at least 64 bytes by repeating the passphrase into the buffer */ + for (i = 0; i < sizeof (data); i++) + data[i] = input[i % input_len]; + + sum = g_checksum_new (G_CHECKSUM_MD5); + g_assert (sum); + g_checksum_update (sum, data, sizeof (data)); + g_checksum_get_digest (sum, out_digest, out_digest_len); + g_checksum_free (sum); + + g_assert (*out_digest_len == 16); + /* WEP104 keys are 13 bytes in length (26 hex characters) */ + *out_digest_len = 13; +} + +static gboolean +add_wep_key (NMSupplicantConfig *self, + const char *key, + const char *name, + NMWepKeyType wep_type, + GError **error) +{ + size_t key_len = key ? strlen (key) : 0; + + if (!key || !key_len) + return TRUE; + + if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) { + if (nm_utils_wep_key_valid (key, NM_WEP_KEY_TYPE_KEY)) + wep_type = NM_WEP_KEY_TYPE_KEY; + else if (nm_utils_wep_key_valid (key, NM_WEP_KEY_TYPE_PASSPHRASE)) + wep_type = NM_WEP_KEY_TYPE_PASSPHRASE; + } + + if ( (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) + || (wep_type == NM_WEP_KEY_TYPE_KEY)) { + if ((key_len == 10) || (key_len == 26)) { + gs_unref_bytes GBytes *bytes = NULL; + + bytes = nm_utils_hexstr2bin (key); + if (!bytes) { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "cannot add wep-key %s to suplicant config because key is not hex", + name); + return FALSE; + } + if (!nm_supplicant_config_add_option (self, + name, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + "<hidden>", + error)) + return FALSE; + } else if ((key_len == 5) || (key_len == 13)) { + if (!nm_supplicant_config_add_option (self, name, key, key_len, "<hidden>", error)) + return FALSE; + } else { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "Cannot add wep-key %s to suplicant config because key-length %u is invalid", + name, (guint) key_len); + return FALSE; + } + } else if (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE) { + guint8 digest[16]; + size_t digest_len = sizeof (digest); + + wep128_passphrase_hash (key, key_len, digest, &digest_len); + if (!nm_supplicant_config_add_option (self, name, (const char *) digest, digest_len, "<hidden>", error)) + return FALSE; + } + + return TRUE; +} + +gboolean +nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self, + NMSettingWirelessSecurity *setting, + NMSetting8021x *setting_8021x, + const char *con_uuid, + guint32 mtu, + GError **error) +{ + const char *key_mgmt, *auth_alg; + const char *psk; + + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE); + g_return_val_if_fail (setting != NULL, FALSE); + g_return_val_if_fail (con_uuid != NULL, FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + + key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting); + if (!add_string_val (self, key_mgmt, "key_mgmt", TRUE, NULL, error)) + return FALSE; + + auth_alg = nm_setting_wireless_security_get_auth_alg (setting); + if (!add_string_val (self, auth_alg, "auth_alg", TRUE, NULL, error)) + return FALSE; + + psk = nm_setting_wireless_security_get_psk (setting); + if (psk) { + size_t psk_len = strlen (psk); + + if (psk_len == 64) { + gs_unref_bytes GBytes *bytes = NULL; + + /* Hex PSK */ + bytes = nm_utils_hexstr2bin (psk); + if (!bytes) { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "Cannot add psk to supplicant config due to invalid hex"); + return FALSE; + } + + if (!nm_supplicant_config_add_option (self, + "psk", + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + "<hidden>", + error)) + return FALSE; + } else if (psk_len >= 8 && psk_len <= 63) { + /* Use TYPE_STRING here so that it gets pushed to the + * supplicant as a string, and therefore gets quoted, + * and therefore the supplicant will interpret it as a + * passphrase and not a hex key. + */ + if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, TYPE_STRING, "<hidden>", error)) + return FALSE; + } else { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "Cannot add psk to supplicant config due to invalid PSK length %u (not between 8 and 63 characters)", + (guint) psk_len); + return FALSE; + } + } + + /* Only WPA-specific things when using WPA */ + if ( !strcmp (key_mgmt, "wpa-none") + || !strcmp (key_mgmt, "wpa-psk") + || !strcmp (key_mgmt, "wpa-eap")) { + if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, proto, protos, "proto", ' ', TRUE, NULL, error)) + return FALSE; + if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, pairwise, pairwise, "pairwise", ' ', TRUE, NULL, error)) + return FALSE; + if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, group, groups, "group", ' ', TRUE, NULL, error)) + return FALSE; + } + + /* WEP keys if required */ + if (!strcmp (key_mgmt, "none")) { + NMWepKeyType wep_type = nm_setting_wireless_security_get_wep_key_type (setting); + const char *wep0 = nm_setting_wireless_security_get_wep_key (setting, 0); + const char *wep1 = nm_setting_wireless_security_get_wep_key (setting, 1); + const char *wep2 = nm_setting_wireless_security_get_wep_key (setting, 2); + const char *wep3 = nm_setting_wireless_security_get_wep_key (setting, 3); + + if (!add_wep_key (self, wep0, "wep_key0", wep_type, error)) + return FALSE; + if (!add_wep_key (self, wep1, "wep_key1", wep_type, error)) + return FALSE; + if (!add_wep_key (self, wep2, "wep_key2", wep_type, error)) + return FALSE; + if (!add_wep_key (self, wep3, "wep_key3", wep_type, error)) + return FALSE; + + if (wep0 || wep1 || wep2 || wep3) { + gs_free char *value = NULL; + + value = g_strdup_printf ("%d", nm_setting_wireless_security_get_wep_tx_keyidx (setting)); + if (!nm_supplicant_config_add_option (self, "wep_tx_keyidx", value, -1, NULL, error)) + return FALSE; + } + } + + if (auth_alg && !strcmp (auth_alg, "leap")) { + /* LEAP */ + if (!strcmp (key_mgmt, "ieee8021x")) { + const char *tmp; + + tmp = nm_setting_wireless_security_get_leap_username (setting); + if (!add_string_val (self, tmp, "identity", FALSE, NULL, error)) + return FALSE; + + tmp = nm_setting_wireless_security_get_leap_password (setting); + if (!add_string_val (self, tmp, "password", FALSE, "<hidden>", error)) + return FALSE; + + if (!add_string_val (self, "leap", "eap", TRUE, NULL, error)) + return FALSE; + } else { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "Invalid key-mgmt \"%s\" for leap", key_mgmt); + return FALSE; + } + } else { + /* 802.1x for Dynamic WEP and WPA-Enterprise */ + if (!strcmp (key_mgmt, "ieee8021x") || !strcmp (key_mgmt, "wpa-eap")) { + if (!setting_8021x) { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "Cannot set key-mgmt %s with missing 8021x setting", key_mgmt); + return FALSE; + } + if (!nm_supplicant_config_add_setting_8021x (self, setting_8021x, con_uuid, mtu, FALSE, error)) + return FALSE; + } + + if (!strcmp (key_mgmt, "wpa-eap")) { + /* If using WPA Enterprise, enable optimized background scanning + * to ensure roaming within an ESS works well. + */ + if (!nm_supplicant_config_add_option (self, "bgscan", "simple:30:-65:300", -1, NULL, error)) + return FALSE; + + /* When using WPA-Enterprise, we want to use Proactive Key Caching (also + * called Opportunistic Key Caching) to avoid full EAP exchanges when + * roaming between access points in the same mobility group. + */ + if (!nm_supplicant_config_add_option (self, "proactive_key_caching", "1", -1, NULL, error)) + return FALSE; + } + } + + return TRUE; +} + +gboolean +nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self, + NMSetting8021x *setting, + const char *con_uuid, + guint32 mtu, + gboolean wired, + GError **error) +{ + NMSupplicantConfigPrivate *priv; + char *tmp; + const char *peapver, *value, *path; + gboolean added; + GString *phase1, *phase2; + GBytes *bytes; + gboolean fast = FALSE; + guint32 i, num_eap; + gboolean fast_provisoning_allowed = FALSE; + const char *ca_path_override = NULL, *ca_cert_override = NULL; + guint32 frag, hdrs; + gs_free char *frag_str = NULL; + + g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE); + g_return_val_if_fail (setting != NULL, FALSE); + g_return_val_if_fail (con_uuid != NULL, FALSE); + + priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self); + + value = nm_setting_802_1x_get_password (setting); + if (value) { + if (!add_string_val (self, value, "password", FALSE, "<hidden>", error)) + return FALSE; + } else { + bytes = nm_setting_802_1x_get_password_raw (setting); + if (bytes) { + if (!nm_supplicant_config_add_option (self, + "password", + (const char *) g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + "<hidden>", + error)) + return FALSE; + } + } + value = nm_setting_802_1x_get_pin (setting); + if (!add_string_val (self, value, "pin", FALSE, "<hidden>", error)) + return FALSE; + + if (wired) { + if (!add_string_val (self, "IEEE8021X", "key_mgmt", FALSE, NULL, error)) + return FALSE; + /* Wired 802.1x must always use eapol_flags=0 */ + if (!add_string_val (self, "0", "eapol_flags", FALSE, NULL, error)) + return FALSE; + priv->ap_scan = 0; + } + + if (!ADD_STRING_LIST_VAL (self, setting, 802_1x, eap_method, eap_methods, "eap", ' ', TRUE, NULL, error)) + return FALSE; + + /* Check EAP method for special handling: PEAP + GTC, FAST */ + num_eap = nm_setting_802_1x_get_num_eap_methods (setting); + for (i = 0; i < num_eap; i++) { + const char *method = nm_setting_802_1x_get_eap_method (setting, i); + + if (method && (strcasecmp (method, "fast") == 0)) { + fast = TRUE; + priv->fast_required = TRUE; + } + } + + /* Adjust the fragment size according to MTU, but do not set it higher than 1280-14 + * for better compatibility */ + hdrs = 14; /* EAPOL + EAP-TLS */ + frag = 1280 - hdrs; + if (mtu > hdrs) + frag = CLAMP (mtu - hdrs, 100, frag); + frag_str = g_strdup_printf ("%u", frag); + + if (!nm_supplicant_config_add_option (self, "fragment_size", frag_str, -1, NULL, error)) + return FALSE; + + phase1 = g_string_new (NULL); + peapver = nm_setting_802_1x_get_phase1_peapver (setting); + if (peapver) { + if (!strcmp (peapver, "0")) + g_string_append (phase1, "peapver=0"); + else if (!strcmp (peapver, "1")) + g_string_append (phase1, "peapver=1"); + } + + if (nm_setting_802_1x_get_phase1_peaplabel (setting)) { + if (phase1->len) + g_string_append_c (phase1, ' '); + g_string_append_printf (phase1, "peaplabel=%s", nm_setting_802_1x_get_phase1_peaplabel (setting)); + } + + value = nm_setting_802_1x_get_phase1_fast_provisioning (setting); + if (value) { + if (phase1->len) + g_string_append_c (phase1, ' '); + g_string_append_printf (phase1, "fast_provisioning=%s", value); + + if (strcmp (value, "0") != 0) + fast_provisoning_allowed = TRUE; + } + + if (phase1->len) { + if (!add_string_val (self, phase1->str, "phase1", FALSE, NULL, error)) { + g_string_free (phase1, TRUE); + return FALSE; + } + } + g_string_free (phase1, TRUE); + + phase2 = g_string_new (NULL); + if (nm_setting_802_1x_get_phase2_auth (setting) && !fast_provisoning_allowed) { + tmp = g_ascii_strup (nm_setting_802_1x_get_phase2_auth (setting), -1); + g_string_append_printf (phase2, "auth=%s", tmp); + g_free (tmp); + } + + if (nm_setting_802_1x_get_phase2_autheap (setting)) { + if (phase2->len) + g_string_append_c (phase2, ' '); + tmp = g_ascii_strup (nm_setting_802_1x_get_phase2_autheap (setting), -1); + g_string_append_printf (phase2, "autheap=%s", tmp); + g_free (tmp); + } + + if (phase2->len) { + if (!add_string_val (self, phase2->str, "phase2", FALSE, NULL, error)) { + g_string_free (phase2, TRUE); + return FALSE; + } + } + g_string_free (phase2, TRUE); + + /* PAC file */ + path = nm_setting_802_1x_get_pac_file (setting); + if (path) { + if (!add_string_val (self, path, "pac_file", FALSE, NULL, error)) + return FALSE; + } else { + /* PAC file is not specified. + * If provisioning is allowed, use an blob format. + */ + if (fast_provisoning_allowed) { + gs_free char *blob_name = NULL; + + blob_name = g_strdup_printf ("blob://pac-blob-%s", con_uuid); + if (!add_string_val (self, blob_name, "pac_file", FALSE, NULL, error)) + return FALSE; + } else { + /* This is only error for EAP-FAST; don't disturb other methods. */ + if (fast) { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "EAP-FAST error: no PAC file provided and " + "automatic PAC provisioning is disabled"); + return FALSE; + } + } + } + + /* If user wants to use system CA certs, either populate ca_path (if the path + * is a directory) or ca_cert (the path is a file name) */ + if (nm_setting_802_1x_get_system_ca_certs (setting)) { + if (g_file_test (SYSTEM_CA_PATH, G_FILE_TEST_IS_DIR)) + ca_path_override = SYSTEM_CA_PATH; + else + ca_cert_override = SYSTEM_CA_PATH; + } + + /* CA path */ + path = nm_setting_802_1x_get_ca_path (setting); + path = ca_path_override ? ca_path_override : path; + if (path) { + if (!add_string_val (self, path, "ca_path", FALSE, NULL, error)) + return FALSE; + } + + /* Phase2 CA path */ + path = nm_setting_802_1x_get_phase2_ca_path (setting); + path = ca_path_override ? ca_path_override : path; + if (path) { + if (!add_string_val (self, path, "ca_path2", FALSE, NULL, error)) + return FALSE; + } + + /* CA certificate */ + if (ca_cert_override) { + if (!add_string_val (self, ca_cert_override, "ca_cert", FALSE, NULL, error)) + return FALSE; + } else { + switch (nm_setting_802_1x_get_ca_cert_scheme (setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_ca_cert_blob (setting); + if (!nm_supplicant_config_add_blob_for_connection (self, bytes, "ca_cert", con_uuid, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_ca_cert_path (setting); + if (!add_string_val (self, path, "ca_cert", FALSE, NULL, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + path = nm_setting_802_1x_get_ca_cert_uri (setting); + if (!add_string_val (self, path, "ca_cert", FALSE, NULL, error)) + return FALSE; + break; + default: + break; + } + } + + /* Phase 2 CA certificate */ + if (ca_cert_override) { + if (!add_string_val (self, ca_cert_override, "ca_cert2", FALSE, NULL, error)) + return FALSE; + } else { + switch (nm_setting_802_1x_get_phase2_ca_cert_scheme (setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_phase2_ca_cert_blob (setting); + if (!nm_supplicant_config_add_blob_for_connection (self, bytes, "ca_cert2", con_uuid, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_phase2_ca_cert_path (setting); + if (!add_string_val (self, path, "ca_cert2", FALSE, NULL, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + path = nm_setting_802_1x_get_phase2_ca_cert_uri (setting); + if (!add_string_val (self, path, "ca_cert2", FALSE, NULL, error)) + return FALSE; + break; + default: + break; + } + } + + /* Subject match */ + value = nm_setting_802_1x_get_subject_match (setting); + if (!add_string_val (self, value, "subject_match", FALSE, NULL, error)) + return FALSE; + value = nm_setting_802_1x_get_phase2_subject_match (setting); + if (!add_string_val (self, value, "subject_match2", FALSE, NULL, error)) + return FALSE; + + /* altSubjectName match */ + if (!ADD_STRING_LIST_VAL (self, setting, 802_1x, altsubject_match, altsubject_matches, "altsubject_match", ';', FALSE, NULL, error)) + return FALSE; + if (!ADD_STRING_LIST_VAL (self, setting, 802_1x, phase2_altsubject_match, phase2_altsubject_matches, "altsubject_match2", ';', FALSE, NULL, error)) + return FALSE; + + /* Domain suffix match */ + value = nm_setting_802_1x_get_domain_suffix_match (setting); + if (!add_string_val (self, value, "domain_suffix_match", FALSE, NULL, error)) + return FALSE; + value = nm_setting_802_1x_get_phase2_domain_suffix_match (setting); + if (!add_string_val (self, value, "domain_suffix_match2", FALSE, NULL, error)) + return FALSE; + + /* Private key */ + added = FALSE; + switch (nm_setting_802_1x_get_private_key_scheme (setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_private_key_blob (setting); + if (!nm_supplicant_config_add_blob_for_connection (self, bytes, "private_key", con_uuid, error)) + return FALSE; + added = TRUE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_private_key_path (setting); + if (!add_string_val (self, path, "private_key", FALSE, NULL, error)) + return FALSE; + added = TRUE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + path = nm_setting_802_1x_get_private_key_uri (setting); + if (!add_string_val (self, path, "private_key", FALSE, NULL, error)) + return FALSE; + added = TRUE; + break; + default: + break; + } + + if (added) { + NMSetting8021xCKFormat format; + NMSetting8021xCKScheme scheme; + + format = nm_setting_802_1x_get_private_key_format (setting); + scheme = nm_setting_802_1x_get_private_key_scheme (setting); + + if ( scheme == NM_SETTING_802_1X_CK_SCHEME_PATH + || format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + /* Only add the private key password for PKCS#12 blobs and + * all path schemes, since in both of these cases the private key + * isn't decrypted at all. + */ + value = nm_setting_802_1x_get_private_key_password (setting); + if (!add_string_val (self, value, "private_key_passwd", FALSE, "<hidden>", error)) + return FALSE; + } + + if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + /* Only add the client cert if the private key is not PKCS#12, as + * wpa_supplicant configuration directs us to do. + */ + switch (nm_setting_802_1x_get_client_cert_scheme (setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_client_cert_blob (setting); + if (!nm_supplicant_config_add_blob_for_connection (self, bytes, "client_cert", con_uuid, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_client_cert_path (setting); + if (!add_string_val (self, path, "client_cert", FALSE, NULL, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + path = nm_setting_802_1x_get_client_cert_uri (setting); + if (!add_string_val (self, path, "client_cert", FALSE, NULL, error)) + return FALSE; + break; + default: + break; + } + } + } + + /* Phase 2 private key */ + added = FALSE; + switch (nm_setting_802_1x_get_phase2_private_key_scheme (setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_phase2_private_key_blob (setting); + if (!nm_supplicant_config_add_blob_for_connection (self, bytes, "private_key2", con_uuid, error)) + return FALSE; + added = TRUE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_phase2_private_key_path (setting); + if (!add_string_val (self, path, "private_key2", FALSE, NULL, error)) + return FALSE; + added = TRUE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + path = nm_setting_802_1x_get_phase2_private_key_uri (setting); + if (!add_string_val (self, path, "private_key2", FALSE, NULL, error)) + return FALSE; + added = TRUE; + break; + default: + break; + } + + if (added) { + NMSetting8021xCKFormat format; + NMSetting8021xCKScheme scheme; + + format = nm_setting_802_1x_get_phase2_private_key_format (setting); + scheme = nm_setting_802_1x_get_phase2_private_key_scheme (setting); + + if ( scheme == NM_SETTING_802_1X_CK_SCHEME_PATH + || format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + /* Only add the private key password for PKCS#12 blobs and + * all path schemes, since in both of these cases the private key + * isn't decrypted at all. + */ + value = nm_setting_802_1x_get_phase2_private_key_password (setting); + if (!add_string_val (self, value, "private_key2_passwd", FALSE, "<hidden>", error)) + return FALSE; + } + + if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + /* Only add the client cert if the private key is not PKCS#12, as + * wpa_supplicant configuration directs us to do. + */ + switch (nm_setting_802_1x_get_phase2_client_cert_scheme (setting)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + bytes = nm_setting_802_1x_get_phase2_client_cert_blob (setting); + if (!nm_supplicant_config_add_blob_for_connection (self, bytes, "client_cert2", con_uuid, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = nm_setting_802_1x_get_phase2_client_cert_path (setting); + if (!add_string_val (self, path, "client_cert2", FALSE, NULL, error)) + return FALSE; + break; + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + path = nm_setting_802_1x_get_phase2_client_cert_uri (setting); + if (!add_string_val (self, path, "client_cert2", FALSE, NULL, error)) + return FALSE; + break; + default: + break; + } + } + } + + value = nm_setting_802_1x_get_identity (setting); + if (!add_string_val (self, value, "identity", FALSE, NULL, error)) + return FALSE; + value = nm_setting_802_1x_get_anonymous_identity (setting); + if (!add_string_val (self, value, "anonymous_identity", FALSE, NULL, error)) + return FALSE; + + return TRUE; +} + +gboolean +nm_supplicant_config_add_no_security (NMSupplicantConfig *self, GError **error) +{ + return nm_supplicant_config_add_option (self, "key_mgmt", "NONE", -1, NULL, error); +} + diff --git a/src/supplicant/nm-supplicant-config.h b/src/supplicant/nm-supplicant-config.h new file mode 100644 index 00000000..40fca61b --- /dev/null +++ b/src/supplicant/nm-supplicant-config.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2012 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_CONFIG_H__ +#define __NETWORKMANAGER_SUPPLICANT_CONFIG_H__ + +#include <nm-setting-macsec.h> +#include <nm-setting-wireless.h> +#include <nm-setting-wireless-security.h> +#include <nm-setting-8021x.h> + +#include "nm-supplicant-types.h" + +#define NM_TYPE_SUPPLICANT_CONFIG (nm_supplicant_config_get_type ()) +#define NM_SUPPLICANT_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfig)) +#define NM_SUPPLICANT_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfigClass)) +#define NM_IS_SUPPLICANT_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SUPPLICANT_CONFIG)) +#define NM_IS_SUPPLICANT_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SUPPLICANT_CONFIG)) +#define NM_SUPPLICANT_CONFIG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfigClass)) + +typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass; + +GType nm_supplicant_config_get_type (void); + +NMSupplicantConfig *nm_supplicant_config_new (void); + +guint32 nm_supplicant_config_get_ap_scan (NMSupplicantConfig *self); + +gboolean nm_supplicant_config_fast_required (NMSupplicantConfig *self); + +GVariant *nm_supplicant_config_to_variant (NMSupplicantConfig *self); + +GHashTable *nm_supplicant_config_get_blobs (NMSupplicantConfig *self); + +gboolean nm_supplicant_config_add_setting_wireless (NMSupplicantConfig *self, + NMSettingWireless *setting, + guint32 fixed_freq, + GError **error); + +gboolean nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self, + NMSettingWirelessSecurity *setting, + NMSetting8021x *setting_8021x, + const char *con_uuid, + guint32 mtu, + GError **error); + +gboolean nm_supplicant_config_add_no_security (NMSupplicantConfig *self, + GError **error); + +gboolean nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self, + NMSetting8021x *setting, + const char *con_uuid, + guint32 mtu, + gboolean wired, + GError **error); + +gboolean nm_supplicant_config_add_setting_macsec (NMSupplicantConfig *self, + NMSettingMacsec *setting, + GError **error); + +#endif /* __NETWORKMANAGER_SUPPLICANT_CONFIG_H__ */ diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c new file mode 100644 index 00000000..6fc85535 --- /dev/null +++ b/src/supplicant/nm-supplicant-interface.c @@ -0,0 +1,1681 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2012 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-supplicant-interface.h" + +#include <stdio.h> +#include <string.h> + +#include "NetworkManagerUtils.h" +#include "nm-supplicant-config.h" +#include "nm-core-internal.h" +#include "nm-dbus-compat.h" + +#define WPAS_DBUS_IFACE_INTERFACE WPAS_DBUS_INTERFACE ".Interface" +#define WPAS_DBUS_IFACE_BSS WPAS_DBUS_INTERFACE ".BSS" +#define WPAS_DBUS_IFACE_NETWORK WPAS_DBUS_INTERFACE ".Network" +#define WPAS_ERROR_INVALID_IFACE WPAS_DBUS_INTERFACE ".InvalidInterface" +#define WPAS_ERROR_EXISTS_ERROR WPAS_DBUS_INTERFACE ".InterfaceExists" + +/*****************************************************************************/ + +enum { + STATE, /* change in the interface's state */ + REMOVED, /* interface was removed by the supplicant */ + NEW_BSS, /* interface saw a new access point from a scan */ + BSS_UPDATED, /* a BSS property changed */ + BSS_REMOVED, /* supplicant removed BSS from its scan list */ + SCAN_DONE, /* wifi scan is complete */ + CONNECTION_ERROR, /* an error occurred during a connection request */ + CREDENTIALS_REQUEST, /* 802.1x identity or password requested */ + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface, + PROP_IFACE, + PROP_SCANNING, + PROP_CURRENT_BSS, + PROP_DRIVER, + PROP_FAST_SUPPORTED, + PROP_AP_SUPPORT, +); + +typedef struct { + char * dev; + NMSupplicantDriver driver; + bool fast_supported; + gboolean has_credreq; /* Whether querying 802.1x credentials is supported */ + NMSupplicantFeature ap_support; /* Lightweight AP mode support */ + NMSupplicantFeature mac_randomization_support; + guint32 max_scan_ssids; + guint32 ready_count; + + char * object_path; + guint32 state; + int disconnect_reason; + + gboolean scanning; + + GDBusProxy * wpas_proxy; + GCancellable * init_cancellable; + GDBusProxy * iface_proxy; + GCancellable * other_cancellable; + GCancellable * assoc_cancellable; + char * net_path; + guint32 blobs_left; + GHashTable * bss_proxies; + char * current_bss; + + gint32 last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_s() */ + + NMSupplicantConfig *cfg; +} NMSupplicantInterfacePrivate; + +struct _NMSupplicantInterface { + GObject parent; + NMSupplicantInterfacePrivate _priv; +}; + +struct _NMSupplicantInterfaceClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE (NMSupplicantInterface, nm_supplicant_interface, G_TYPE_OBJECT) + +#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantInterface, NM_IS_SUPPLICANT_INTERFACE) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SUPPLICANT +#define _NMLOG_PREFIX_NAME "sup-iface" +#define _NMLOG(level, ...) \ + G_STMT_START { \ + char _sbuf[64]; \ + \ + nm_log ((level), _NMLOG_DOMAIN, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + ((self) \ + ? nm_sprintf_buf (_sbuf, \ + "[%p,%s]", \ + (self), \ + NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev) \ + : "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } G_STMT_END + +/*****************************************************************************/ + +static void +emit_error_helper (NMSupplicantInterface *self, GError *error) +{ + char *name = NULL; + + if (g_dbus_error_is_remote_error (error)) + name = g_dbus_error_get_remote_error (error); + + g_signal_emit (self, signals[CONNECTION_ERROR], 0, name, error->message); + g_free (name); +} + +static void +bss_props_changed_cb (GDBusProxy *proxy, + GVariant *changed_properties, + char **invalidated_properties, + gpointer user_data) +{ + NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (priv->scanning) + priv->last_scan = nm_utils_get_monotonic_timestamp_s (); + + g_signal_emit (self, signals[BSS_UPDATED], 0, + g_dbus_proxy_get_object_path (proxy), + changed_properties); +} + +static GVariant * +_get_bss_proxy_properties (NMSupplicantInterface *self, GDBusProxy *proxy) +{ + gs_strfreev char **properties = NULL; + GVariantBuilder builder; + char **iter; + + iter = properties = g_dbus_proxy_get_cached_property_names (proxy); + if (!iter) + return NULL; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + while (*iter) { + GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter); + + g_variant_builder_add (&builder, "{sv}", *iter++, copy); + g_variant_unref (copy); + } + + return g_variant_builder_end (&builder); +} + +#define BSS_PROXY_INITED "bss-proxy-inited" + +static void +on_bss_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *props = NULL; + + if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + self = NM_SUPPLICANT_INTERFACE (user_data); + _LOGD ("failed to acquire BSS proxy: (%s)", error->message); + g_hash_table_remove (NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->bss_proxies, + g_dbus_proxy_get_object_path (proxy)); + } + return; + } + + self = NM_SUPPLICANT_INTERFACE (user_data); + props = _get_bss_proxy_properties (self, proxy); + if (!props) + return; + + g_object_set_data (G_OBJECT (proxy), BSS_PROXY_INITED, GUINT_TO_POINTER (TRUE)); + + g_signal_emit (self, signals[NEW_BSS], 0, + g_dbus_proxy_get_object_path (proxy), + g_variant_ref_sink (props)); +} + +static void +handle_new_bss (NMSupplicantInterface *self, const char *object_path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + GDBusProxy *bss_proxy; + + g_return_if_fail (object_path != NULL); + + if (g_hash_table_lookup (priv->bss_proxies, object_path)) + return; + + bss_proxy = g_object_new (G_TYPE_DBUS_PROXY, + "g-bus-type", G_BUS_TYPE_SYSTEM, + "g-flags", G_DBUS_PROXY_FLAGS_NONE, + "g-name", WPAS_DBUS_SERVICE, + "g-object-path", object_path, + "g-interface-name", WPAS_DBUS_IFACE_BSS, + NULL); + g_hash_table_insert (priv->bss_proxies, + (char *) g_dbus_proxy_get_object_path (bss_proxy), + bss_proxy); + g_signal_connect (bss_proxy, "g-properties-changed", G_CALLBACK (bss_props_changed_cb), self); + g_async_initable_init_async (G_ASYNC_INITABLE (bss_proxy), + G_PRIORITY_DEFAULT, + priv->other_cancellable, + (GAsyncReadyCallback) on_bss_proxy_acquired, + self); +} + +static void +set_state (NMSupplicantInterface *self, guint32 new_state) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + guint32 old_state = priv->state; + + g_return_if_fail (new_state < NM_SUPPLICANT_INTERFACE_STATE_LAST); + + if (new_state == priv->state) + return; + + /* DOWN is a terminal state */ + g_return_if_fail (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN); + + /* Cannot regress to READY, STARTING, or INIT from higher states */ + if (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY) + g_return_if_fail (new_state > NM_SUPPLICANT_INTERFACE_STATE_READY); + + if (new_state == NM_SUPPLICANT_INTERFACE_STATE_READY) { + if (priv->other_cancellable) { + g_warn_if_fail (priv->other_cancellable == NULL); + g_cancellable_cancel (priv->other_cancellable); + g_clear_object (&priv->other_cancellable); + } + priv->other_cancellable = g_cancellable_new (); + } else if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + if (priv->init_cancellable) + g_cancellable_cancel (priv->init_cancellable); + g_clear_object (&priv->init_cancellable); + + if (priv->other_cancellable) + g_cancellable_cancel (priv->other_cancellable); + g_clear_object (&priv->other_cancellable); + + if (priv->iface_proxy) + g_signal_handlers_disconnect_by_data (priv->iface_proxy, self); + } + + priv->state = new_state; + + if ( priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING + || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING) + priv->last_scan = nm_utils_get_monotonic_timestamp_s (); + + /* Disconnect reason is no longer relevant when not in the DISCONNECTED state */ + if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED) + priv->disconnect_reason = 0; + + g_signal_emit (self, signals[STATE], 0, + priv->state, + old_state, + priv->disconnect_reason); +} + +static int +wpas_state_string_to_enum (const char *str_state) +{ + if (!strcmp (str_state, "interface_disabled")) + return NM_SUPPLICANT_INTERFACE_STATE_DISABLED; + else if (!strcmp (str_state, "disconnected")) + return NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED; + else if (!strcmp (str_state, "inactive")) + return NM_SUPPLICANT_INTERFACE_STATE_INACTIVE; + else if (!strcmp (str_state, "scanning")) + return NM_SUPPLICANT_INTERFACE_STATE_SCANNING; + else if (!strcmp (str_state, "authenticating")) + return NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING; + else if (!strcmp (str_state, "associating")) + return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING; + else if (!strcmp (str_state, "associated")) + return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED; + else if (!strcmp (str_state, "4way_handshake")) + return NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE; + else if (!strcmp (str_state, "group_handshake")) + return NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE; + else if (!strcmp (str_state, "completed")) + return NM_SUPPLICANT_INTERFACE_STATE_COMPLETED; + + return -1; +} + +static void +set_state_from_string (NMSupplicantInterface *self, const char *new_state) +{ + int state; + + state = wpas_state_string_to_enum (new_state); + if (state == -1) { + _LOGW ("unknown supplicant state '%s'", new_state); + return; + } + set_state (self, (guint32) state); +} + +static void +set_scanning (NMSupplicantInterface *self, gboolean new_scanning) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (priv->scanning != new_scanning) { + priv->scanning = new_scanning; + + /* Cache time of last scan completion */ + if (priv->scanning == FALSE) + priv->last_scan = nm_utils_get_monotonic_timestamp_s (); + + _notify (self, PROP_SCANNING); + } +} + +gboolean +nm_supplicant_interface_get_scanning (NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_val_if_fail (self != NULL, FALSE); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + if (priv->scanning) + return TRUE; + if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING) + return TRUE; + return FALSE; +} + +const char * +nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_val_if_fail (self != NULL, FALSE); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + return priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY ? priv->current_bss : NULL; +} + +gint32 +nm_supplicant_interface_get_last_scan_time (NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->last_scan; +} + +#define MATCH_PROPERTY(p, n, v, t) (!strcmp (p, n) && g_variant_is_of_type (v, t)) + +static void +parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + gboolean have_active = FALSE, have_ssid = FALSE; + gint32 max_scan_ssids = -1; + const char **array; + + g_return_if_fail (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT)); + + if ( g_variant_lookup (capabilities, "Scan", "^a&s", &array) + && array) { + if (g_strv_contains (array, "active")) + have_active = TRUE; + if (g_strv_contains (array, "ssid")) + have_ssid = TRUE; + g_free (array); + } + + if (g_variant_lookup (capabilities, "MaxScanSSID", "i", &max_scan_ssids)) { + /* We need active scan and SSID probe capabilities to care about MaxScanSSIDs */ + if (max_scan_ssids > 0 && have_active && have_ssid) { + /* wpa_supplicant's WPAS_MAX_SCAN_SSIDS value is 16, but for speed + * and to ensure we don't disclose too many SSIDs from the hidden + * list, we'll limit to 5. + */ + priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, 5); + _LOGI ("supports %d scan SSIDs", priv->max_scan_ssids); + } + } +} + +static void +iface_check_ready (NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (priv->ready_count && priv->state < NM_SUPPLICANT_INTERFACE_STATE_READY) { + priv->ready_count--; + if (priv->ready_count == 0) + set_state (self, NM_SUPPLICANT_INTERFACE_STATE_READY); + } +} + +gboolean +nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self, + const char *field, + const char *value, + GError **error) +{ + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *reply = NULL; + + g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE); + g_return_val_if_fail (field != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + g_return_val_if_fail (priv->has_credreq == TRUE, FALSE); + + /* Need a network block object path */ + g_return_val_if_fail (priv->net_path, FALSE); + reply = g_dbus_proxy_call_sync (priv->iface_proxy, + "NetworkReply", + g_variant_new ("(oss)", + priv->net_path, + field, + value), + G_DBUS_CALL_FLAGS_NONE, + 5000, + NULL, + error); + if (error && *error) + g_dbus_error_strip_remote_error (*error); + + return !!reply; +} + +static void +iface_check_netreply_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + /* We know NetworkReply is supported if the NetworkReply method returned + * successfully (which is unexpected since we sent a bogus network + * object path) or if we got an "InvalidArgs" (which indicates NetworkReply + * is supported). We know it's not supported if we get an + * "UnknownMethod" error. + */ + + variant = g_dbus_proxy_call_finish (proxy, result, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (variant || _nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.InvalidArgs")) + priv->has_credreq = TRUE; + + _LOGD ("supplicant %s network credentials requests", + priv->has_credreq ? "supports" : "does not support"); + + iface_check_ready (self); +} + +NMSupplicantFeature +nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ap_support; +} + +void +nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self, + NMSupplicantFeature ap_support) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + /* Use the best indicator of support between the supplicant global + * Capabilities property and the interface's introspection data. + */ + if (ap_support > priv->ap_support) + priv->ap_support = ap_support; +} + +static void +set_preassoc_scan_mac_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + variant = _nm_dbus_proxy_call_finish (proxy, result, + G_VARIANT_TYPE ("()"), + &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + if (error) + _LOGW ("failed to enable scan MAC address randomization (%s)", error->message); + iface_check_ready (self); +} + +static void +iface_introspect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + const char *data; + + variant = _nm_dbus_proxy_call_finish (proxy, result, + G_VARIANT_TYPE ("(s)"), + &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (variant) { + g_variant_get (variant, "(&s)", &data); + + /* The ProbeRequest method only exists if AP mode has been enabled */ + if (strstr (data, "ProbeRequest")) + priv->ap_support = NM_SUPPLICANT_FEATURE_YES; + + if (strstr (data, "PreassocMacAddr")) { + priv->mac_randomization_support = NM_SUPPLICANT_FEATURE_YES; + + /* Turn on MAC randomization during scans by default */ + priv->ready_count++; + g_dbus_proxy_call (priv->iface_proxy, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new ("(ssv)", + WPAS_DBUS_IFACE_INTERFACE, + "PreassocMacAddr", + g_variant_new_string ("0")), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->init_cancellable, + (GAsyncReadyCallback) set_preassoc_scan_mac_cb, + self); + } + } + + iface_check_ready (self); +} + +static void +wpas_iface_scan_done (GDBusProxy *proxy, + gboolean success, + gpointer user_data) +{ + NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + GVariant *props; + GHashTableIter iter; + char *bss_path; + GDBusProxy *bss_proxy; + + /* Cache last scan completed time */ + priv->last_scan = nm_utils_get_monotonic_timestamp_s (); + + g_signal_emit (self, signals[SCAN_DONE], 0, success); + + /* Emit NEW_BSS so that wifi device has the APs (in case it removed them) */ + g_hash_table_iter_init (&iter, priv->bss_proxies); + while (g_hash_table_iter_next (&iter, (gpointer) &bss_path, (gpointer) &bss_proxy)) { + if (g_object_get_data (G_OBJECT (bss_proxy), BSS_PROXY_INITED)) { + props = _get_bss_proxy_properties (self, bss_proxy); + if (props) { + g_signal_emit (self, signals[NEW_BSS], 0, + bss_path, + g_variant_ref_sink (props)); + g_variant_unref (props); + } + } + } +} + +static void +wpas_iface_bss_added (GDBusProxy *proxy, + const char *path, + GVariant *props, + gpointer user_data) +{ + NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (priv->scanning) + priv->last_scan = nm_utils_get_monotonic_timestamp_s (); + + handle_new_bss (self, path); +} + +static void +wpas_iface_bss_removed (GDBusProxy *proxy, + const char *path, + gpointer user_data) +{ + NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + g_signal_emit (self, signals[BSS_REMOVED], 0, path); + g_hash_table_remove (priv->bss_proxies, path); +} + +static void +wpas_iface_network_request (GDBusProxy *proxy, + const char *path, + const char *field, + const char *message, + gpointer user_data) +{ + NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (priv->has_credreq && priv->net_path && !g_strcmp0 (path, priv->net_path)) + g_signal_emit (self, signals[CREDENTIALS_REQUEST], 0, field, message); +} + +static void +props_changed_cb (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + gpointer user_data) +{ + NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data); + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + const char *s, **array, **iter; + gboolean b = FALSE; + gint32 i32; + GVariant *v; + + g_object_freeze_notify (G_OBJECT (self)); + + if (g_variant_lookup (changed_properties, "Scanning", "b", &b)) + set_scanning (self, b); + + if ( g_variant_lookup (changed_properties, "State", "&s", &s) + && priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY) { + /* Only transition to actual wpa_supplicant interface states (ie, + * anything > READY) after the NMSupplicantInterface has had a + * chance to initialize, which is signalled by entering the READY + * state. + */ + set_state_from_string (self, s); + } + + if (g_variant_lookup (changed_properties, "BSSs", "^a&o", &array)) { + iter = array; + while (*iter) + handle_new_bss (self, *iter++); + g_free (array); + } + + if (g_variant_lookup (changed_properties, "CurrentBSS", "&o", &s)) { + if (strcmp (s, "/") == 0) + s = NULL; + if (g_strcmp0 (s, priv->current_bss) != 0) { + g_free (priv->current_bss); + priv->current_bss = g_strdup (s); + _notify (self, PROP_CURRENT_BSS); + } + } + + v = g_variant_lookup_value (changed_properties, "Capabilities", G_VARIANT_TYPE_VARDICT); + if (v) { + parse_capabilities (self, v); + g_variant_unref (v); + } + + if (g_variant_lookup (changed_properties, "DisconnectReason", "i", &i32)) { + /* Disconnect reason is currently only given for deauthentication events, + * not disassociation; currently they are IEEE 802.11 "reason codes", + * defined by (IEEE 802.11-2007, 7.3.1.7, Table 7-22). Any locally caused + * deauthentication will be negative, while authentications caused by the + * AP will be positive. + */ + priv->disconnect_reason = i32; + if (priv->disconnect_reason != 0) + _LOGW ("connection disconnected (reason %d)", priv->disconnect_reason); + } + + g_object_thaw_notify (G_OBJECT (self)); +} + +static void +on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_free_error GError *error = NULL; + + if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + self = NM_SUPPLICANT_INTERFACE (user_data); + _LOGW ("failed to acquire wpa_supplicant interface proxy: (%s)", error->message); + set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN); + } + return; + } + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + _nm_dbus_signal_connect (priv->iface_proxy, "ScanDone", G_VARIANT_TYPE ("(b)"), + G_CALLBACK (wpas_iface_scan_done), self); + _nm_dbus_signal_connect (priv->iface_proxy, "BSSAdded", G_VARIANT_TYPE ("(oa{sv})"), + G_CALLBACK (wpas_iface_bss_added), self); + _nm_dbus_signal_connect (priv->iface_proxy, "BSSRemoved", G_VARIANT_TYPE ("(o)"), + G_CALLBACK (wpas_iface_bss_removed), self); + _nm_dbus_signal_connect (priv->iface_proxy, "NetworkRequest", G_VARIANT_TYPE ("(oss)"), + G_CALLBACK (wpas_iface_network_request), self); + + /* Scan result aging parameters */ + g_dbus_proxy_call (priv->iface_proxy, + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + WPAS_DBUS_IFACE_INTERFACE, + "BSSExpireAge", + g_variant_new_uint32 (250)), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->init_cancellable, + NULL, + NULL); + g_dbus_proxy_call (priv->iface_proxy, + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + WPAS_DBUS_IFACE_INTERFACE, + "BSSExpireCount", + g_variant_new_uint32 (2)), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->init_cancellable, + NULL, + NULL); + + /* Check whether NetworkReply and AP mode are supported */ + priv->ready_count = 1; + g_dbus_proxy_call (priv->iface_proxy, + "NetworkReply", + g_variant_new ("(oss)", + "/fff", + "foobar", + "foobar"), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->init_cancellable, + (GAsyncReadyCallback) iface_check_netreply_cb, + self); + + if (priv->ap_support == NM_SUPPLICANT_FEATURE_UNKNOWN || + priv->mac_randomization_support == NM_SUPPLICANT_FEATURE_UNKNOWN) { + /* If the global supplicant capabilities property is not present, we can + * fall back to checking whether the ProbeRequest method is supported. If + * neither of these works we have no way of determining if AP mode is + * supported or not. hostap 1.0 and earlier don't support either of these. + */ + priv->ready_count++; + g_dbus_proxy_call (priv->iface_proxy, + DBUS_INTERFACE_INTROSPECTABLE ".Introspect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->init_cancellable, + (GAsyncReadyCallback) iface_introspect_cb, + self); + } +} + +static void +interface_add_done (NMSupplicantInterface *self, const char *path) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + _LOGD ("interface added to supplicant"); + + priv->object_path = g_strdup (path); + priv->iface_proxy = g_object_new (G_TYPE_DBUS_PROXY, + "g-bus-type", G_BUS_TYPE_SYSTEM, + "g-flags", G_DBUS_PROXY_FLAGS_NONE, + "g-name", WPAS_DBUS_SERVICE, + "g-object-path", priv->object_path, + "g-interface-name", WPAS_DBUS_IFACE_INTERFACE, + NULL); + g_signal_connect (priv->iface_proxy, "g-properties-changed", G_CALLBACK (props_changed_cb), self); + g_async_initable_init_async (G_ASYNC_INITABLE (priv->iface_proxy), + G_PRIORITY_DEFAULT, + priv->init_cancellable, + (GAsyncReadyCallback) on_iface_proxy_acquired, + self); +} + +static void +interface_get_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + const char *path; + + variant = _nm_dbus_proxy_call_finish (proxy, result, + G_VARIANT_TYPE ("(o)"), + &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (variant) { + g_variant_get (variant, "(&o)", &path); + interface_add_done (self, path); + } else { + g_dbus_error_strip_remote_error (error); + _LOGE ("error getting interface: %s", error->message); + set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN); + } +} + +static void +interface_add_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *variant = NULL; + const char *path; + + variant = _nm_dbus_proxy_call_finish (proxy, result, + G_VARIANT_TYPE ("(o)"), + &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (variant) { + g_variant_get (variant, "(&o)", &path); + interface_add_done (self, path); + } else if (_nm_dbus_error_has_name (error, WPAS_ERROR_EXISTS_ERROR)) { + /* Interface already added, just get its object path */ + g_dbus_proxy_call (priv->wpas_proxy, + "GetInterface", + g_variant_new ("(s)", priv->dev), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->init_cancellable, + (GAsyncReadyCallback) interface_get_cb, + self); + } else if ( g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) + || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED) + || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED) + || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED) + || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT) + || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY) + || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMED_OUT) + || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) { + /* Supplicant wasn't running and could not be launched via service + * activation. Wait for it to start by moving back to the INIT + * state. + */ + g_dbus_error_strip_remote_error (error); + _LOGD ("failed to activate supplicant: %s", error->message); + set_state (self, NM_SUPPLICANT_INTERFACE_STATE_INIT); + } else { + g_dbus_error_strip_remote_error (error); + _LOGE ("error adding interface: %s", error->message); + set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN); + } +} + +#if HAVE_WEXT +#define DEFAULT_WIFI_DRIVER "nl80211,wext" +#else +#define DEFAULT_WIFI_DRIVER "nl80211" +#endif + +static void +on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy *wpas_proxy; + GVariantBuilder props; + const char *driver_name = NULL; + + wpas_proxy = g_dbus_proxy_new_for_bus_finish (result, &error); + if (!wpas_proxy) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + self = NM_SUPPLICANT_INTERFACE (user_data); + _LOGW ("failed to acquire wpa_supplicant proxy: (%s)", error->message); + set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN); + } + return; + } + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + priv->wpas_proxy = wpas_proxy; + + /* Try to add the interface to the supplicant. If the supplicant isn't + * running, this will start it via D-Bus activation and return the response + * when the supplicant has started. + */ + + switch (priv->driver) { + case NM_SUPPLICANT_DRIVER_WIRELESS: + driver_name = DEFAULT_WIFI_DRIVER; + break; + case NM_SUPPLICANT_DRIVER_WIRED: + driver_name = "wired"; + break; + case NM_SUPPLICANT_DRIVER_MACSEC: + driver_name = "macsec_linux"; + break; + } + + g_return_if_fail (driver_name); + + g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&props, "{sv}", + "Driver", + g_variant_new_string (driver_name)); + g_variant_builder_add (&props, "{sv}", + "Ifname", + g_variant_new_string (priv->dev)); + + g_dbus_proxy_call (priv->wpas_proxy, + "CreateInterface", + g_variant_new ("(a{sv})", &props), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->init_cancellable, + (GAsyncReadyCallback) interface_add_cb, + self); +} + +static void +interface_add (NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + /* Can only start the interface from INIT state */ + g_return_if_fail (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT); + + _LOGD ("adding interface to supplicant"); + + /* Move to starting to prevent double-calls of interface_add() */ + set_state (self, NM_SUPPLICANT_INTERFACE_STATE_STARTING); + + g_warn_if_fail (priv->init_cancellable == NULL); + g_clear_object (&priv->init_cancellable); + priv->init_cancellable = g_cancellable_new (); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + WPAS_DBUS_SERVICE, + WPAS_DBUS_PATH, + WPAS_DBUS_INTERFACE, + priv->init_cancellable, + (GAsyncReadyCallback) on_wpas_proxy_acquired, + self); +} + +void +nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self, + gboolean available) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self)); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (available) { + /* This can happen if the supplicant couldn't be activated but + * for some reason was started after the activation failure. + */ + if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT) + interface_add (self); + } else { + /* The supplicant stopped; so we must tear down the interface */ + set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN); + } +} + +static void +log_result_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish (proxy, result, &error); + if ( !reply + && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) + && !strstr (error->message, "fi.w1.wpa_supplicant1.NotConnected")) { + g_dbus_error_strip_remote_error (error); + nm_log_warn (_NMLOG_DOMAIN, "%s: failed to %s: %s", + _NMLOG_PREFIX_NAME, (const char *) user_data, error->message); + } +} + +void +nm_supplicant_interface_disconnect (NMSupplicantInterface * self) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self)); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + /* Cancel all pending calls related to a prior connection attempt */ + if (priv->assoc_cancellable) { + g_cancellable_cancel (priv->assoc_cancellable); + g_clear_object (&priv->assoc_cancellable); + } + + /* Don't do anything if there is no connection to the supplicant yet. */ + if (!priv->iface_proxy) + return; + + /* Disconnect from the current AP */ + if ( (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_SCANNING) + && (priv->state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)) { + g_dbus_proxy_call (priv->iface_proxy, + "Disconnect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) log_result_cb, + "disconnect"); + } + + /* Remove any network that was added by NetworkManager */ + if (priv->net_path) { + g_dbus_proxy_call (priv->iface_proxy, + "RemoveNetwork", + g_variant_new ("(o)", priv->net_path), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->other_cancellable, + (GAsyncReadyCallback) log_result_cb, + "remove network"); + g_free (priv->net_path); + priv->net_path = NULL; + } +} + +static void +select_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish (proxy, result, &error); + if ( !reply + && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + self = NM_SUPPLICANT_INTERFACE (user_data); + g_dbus_error_strip_remote_error (error); + _LOGW ("couldn't select network config: %s", error->message); + emit_error_helper (self, error); + } +} + +static void +call_select_network (NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + /* We only select the network after all blobs (if any) have been set */ + if (priv->blobs_left == 0) { + g_dbus_proxy_call (priv->iface_proxy, + "SelectNetwork", + g_variant_new ("(o)", priv->net_path), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->assoc_cancellable, + (GAsyncReadyCallback) select_network_cb, + self); + } +} + +static void +add_blob_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish (proxy, result, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + priv->blobs_left--; + if (reply) + call_select_network (self); + else { + g_dbus_error_strip_remote_error (error); + _LOGW ("couldn't set network certificates: %s", error->message); + emit_error_helper (self, error); + } +} + +static void +add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + GHashTable *blobs; + GHashTableIter iter; + const char *blob_name; + GByteArray *blob_data; + + reply = _nm_dbus_proxy_call_finish (proxy, result, + G_VARIANT_TYPE ("(o)"), + &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + g_free (priv->net_path); + priv->net_path = NULL; + + if (error) { + g_dbus_error_strip_remote_error (error); + _LOGW ("adding network to supplicant failed: %s", error->message); + emit_error_helper (self, error); + return; + } + + g_variant_get (reply, "(o)", &priv->net_path); + + /* Send blobs first; otherwise jump to selecting the network */ + blobs = nm_supplicant_config_get_blobs (priv->cfg); + priv->blobs_left = g_hash_table_size (blobs); + + g_hash_table_iter_init (&iter, blobs); + while (g_hash_table_iter_next (&iter, (gpointer) &blob_name, (gpointer) &blob_data)) { + g_dbus_proxy_call (priv->iface_proxy, + "AddBlob", + g_variant_new ("(s@ay)", + blob_name, + g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + blob_data->data, blob_data->len, 1)), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->assoc_cancellable, + (GAsyncReadyCallback) add_blob_cb, + self); + } + + call_select_network (self); +} + +static void +add_network (NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + g_dbus_proxy_call (priv->iface_proxy, + "AddNetwork", + g_variant_new ("(@a{sv})", nm_supplicant_config_to_variant (priv->cfg)), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->assoc_cancellable, + (GAsyncReadyCallback) add_network_cb, + self); +} + +static void +set_mac_randomization_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish (proxy, result, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (!reply) { + g_dbus_error_strip_remote_error (error); + _LOGW ("couldn't send MAC randomization mode to the supplicant interface: %s", + error->message); + emit_error_helper (self, error); + return; + } + + _LOGT ("config: set MAC randomization to 0"); + add_network (self); +} + +static void +set_ap_scan_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + NMSupplicantInterfacePrivate *priv; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish (proxy, result, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + if (!reply) { + g_dbus_error_strip_remote_error (error); + _LOGW ("couldn't send AP scan mode to the supplicant interface: %s", + error->message); + emit_error_helper (self, error); + return; + } + + _LOGI ("config: set interface ap_scan to %d", + nm_supplicant_config_get_ap_scan (priv->cfg)); + + if (priv->mac_randomization_support == NM_SUPPLICANT_FEATURE_YES) { + /* Enable/disable association MAC address randomization */ + g_dbus_proxy_call (priv->iface_proxy, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new ("(ssv)", + WPAS_DBUS_IFACE_INTERFACE, + "MacAddr", + g_variant_new_string ("0")), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->assoc_cancellable, + (GAsyncReadyCallback) set_mac_randomization_cb, + self); + } else + add_network (self); +} + +gboolean +nm_supplicant_interface_set_config (NMSupplicantInterface *self, + NMSupplicantConfig *cfg, + GError **error) +{ + NMSupplicantInterfacePrivate *priv; + + g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + nm_supplicant_interface_disconnect (self); + + /* Make sure the supplicant supports EAP-FAST before trying to send + * it an EAP-FAST configuration. + */ + if (nm_supplicant_config_fast_required (cfg) && !priv->fast_supported) { + g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG, + "EAP-FAST is not supported by the supplicant"); + return FALSE; + } + + g_clear_object (&priv->cfg); + if (cfg) { + priv->assoc_cancellable = g_cancellable_new (); + priv->cfg = g_object_ref (cfg); + g_dbus_proxy_call (priv->iface_proxy, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new ("(ssv)", + WPAS_DBUS_IFACE_INTERFACE, + "ApScan", + g_variant_new_uint32 (nm_supplicant_config_get_ap_scan (priv->cfg))), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->assoc_cancellable, + (GAsyncReadyCallback) set_ap_scan_cb, + self); + } + return TRUE; +} + +static void +scan_request_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantInterface *self; + gs_unref_variant GVariant *reply = NULL; + gs_free_error GError *error = NULL; + + reply = g_dbus_proxy_call_finish (proxy, result, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = NM_SUPPLICANT_INTERFACE (user_data); + + if (error) { + if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError")) + _LOGD ("could not get scan request result: %s", error->message); + else { + g_dbus_error_strip_remote_error (error); + _LOGW ("could not get scan request result: %s", error->message); + } + } + g_signal_emit (self, signals[SCAN_DONE], 0, error ? FALSE : TRUE); +} + +gboolean +nm_supplicant_interface_request_scan (NMSupplicantInterface *self, const GPtrArray *ssids) +{ + NMSupplicantInterfacePrivate *priv; + GVariantBuilder builder; + guint i; + + g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE); + + priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + /* Scan parameters */ + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&builder, "{sv}", "Type", g_variant_new_string ("active")); + if (ssids) { + GVariantBuilder ssids_builder; + + g_variant_builder_init (&ssids_builder, G_VARIANT_TYPE_BYTESTRING_ARRAY); + for (i = 0; i < ssids->len; i++) { + GByteArray *ssid = g_ptr_array_index (ssids, i); + g_variant_builder_add (&ssids_builder, "@ay", + g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + ssid->data, ssid->len, 1)); + } + g_variant_builder_add (&builder, "{sv}", "SSIDs", g_variant_builder_end (&ssids_builder)); + } + + g_dbus_proxy_call (priv->iface_proxy, + "Scan", + g_variant_new ("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->other_cancellable, + (GAsyncReadyCallback) scan_request_cb, + self); + return TRUE; +} + +guint32 +nm_supplicant_interface_get_state (NMSupplicantInterface * self) +{ + g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NM_SUPPLICANT_INTERFACE_STATE_DOWN); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->state; +} + +const char * +nm_supplicant_interface_state_to_string (guint32 state) +{ + switch (state) { + case NM_SUPPLICANT_INTERFACE_STATE_INIT: + return "init"; + case NM_SUPPLICANT_INTERFACE_STATE_STARTING: + return "starting"; + case NM_SUPPLICANT_INTERFACE_STATE_READY: + return "ready"; + case NM_SUPPLICANT_INTERFACE_STATE_DISABLED: + return "disabled"; + case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED: + return "disconnected"; + case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE: + return "inactive"; + case NM_SUPPLICANT_INTERFACE_STATE_SCANNING: + return "scanning"; + case NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING: + return "authenticating"; + case NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING: + return "associating"; + case NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED: + return "associated"; + case NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE: + return "4-way handshake"; + case NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE: + return "group handshake"; + case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED: + return "completed"; + case NM_SUPPLICANT_INTERFACE_STATE_DOWN: + return "down"; + default: + break; + } + return "unknown"; +} + +const char * +nm_supplicant_interface_get_object_path (NMSupplicantInterface *self) +{ + g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->object_path; +} + +const char * +nm_supplicant_interface_get_ifname (NMSupplicantInterface *self) +{ + g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev; +} + +guint +nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self) +{ + g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), 0); + + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->max_scan_ssids; +} + +/*****************************************************************************/ + +NMSupplicantInterface * +nm_supplicant_interface_new (const char *ifname, + NMSupplicantDriver driver, + gboolean fast_supported, + NMSupplicantFeature ap_support) +{ + g_return_val_if_fail (ifname != NULL, NULL); + + return g_object_new (NM_TYPE_SUPPLICANT_INTERFACE, + NM_SUPPLICANT_INTERFACE_IFACE, ifname, + NM_SUPPLICANT_INTERFACE_DRIVER, (guint) driver, + NM_SUPPLICANT_INTERFACE_FAST_SUPPORTED, fast_supported, + NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support, + NULL); +} + +static void +nm_supplicant_interface_init (NMSupplicantInterface * self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + priv->state = NM_SUPPLICANT_INTERFACE_STATE_INIT; + priv->bss_proxies = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object); + + switch (prop_id) { + case PROP_IFACE: + /* construct-only */ + priv->dev = g_value_dup_string (value); + g_return_if_fail (priv->dev); + break; + case PROP_DRIVER: + /* construct-only */ + priv->driver = g_value_get_uint (value); + break; + case PROP_FAST_SUPPORTED: + /* construct-only */ + priv->fast_supported = g_value_get_boolean (value); + break; + case PROP_AP_SUPPORT: + /* construct-only */ + priv->ap_support = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object); + + switch (prop_id) { + case PROP_SCANNING: + g_value_set_boolean (value, priv->scanning); + break; + case PROP_CURRENT_BSS: + g_value_set_string (value, priv->current_bss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object); + + if (priv->iface_proxy) + g_signal_handlers_disconnect_by_data (priv->iface_proxy, object); + g_clear_object (&priv->iface_proxy); + + nm_clear_g_cancellable (&priv->init_cancellable); + nm_clear_g_cancellable (&priv->other_cancellable); + nm_clear_g_cancellable (&priv->assoc_cancellable); + + g_clear_object (&priv->wpas_proxy); + g_clear_pointer (&priv->bss_proxies, (GDestroyNotify) g_hash_table_destroy); + + g_clear_pointer (&priv->net_path, g_free); + g_clear_pointer (&priv->dev, g_free); + g_clear_pointer (&priv->object_path, g_free); + g_clear_pointer (&priv->current_bss, g_free); + + g_clear_object (&priv->cfg); + + /* Chain up to the parent class */ + G_OBJECT_CLASS (nm_supplicant_interface_parent_class)->dispose (object); +} + +static void +nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dispose; + object_class->set_property = set_property; + object_class->get_property = get_property; + + obj_properties[PROP_SCANNING] = + g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_SCANNING, "", "", + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_CURRENT_BSS] = + g_param_spec_string (NM_SUPPLICANT_INTERFACE_CURRENT_BSS, "", "", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_IFACE] = + g_param_spec_string (NM_SUPPLICANT_INTERFACE_IFACE, "", "", + NULL, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_DRIVER] = + g_param_spec_uint (NM_SUPPLICANT_INTERFACE_DRIVER, "", "", + 0, G_MAXUINT, NM_SUPPLICANT_DRIVER_WIRELESS, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_FAST_SUPPORTED] = + g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_FAST_SUPPORTED, "", "", + TRUE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + obj_properties[PROP_AP_SUPPORT] = + g_param_spec_int (NM_SUPPLICANT_INTERFACE_AP_SUPPORT, "", "", + NM_SUPPLICANT_FEATURE_UNKNOWN, + NM_SUPPLICANT_FEATURE_YES, + NM_SUPPLICANT_FEATURE_UNKNOWN, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[STATE] = + g_signal_new (NM_SUPPLICANT_INTERFACE_STATE, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT); + + signals[REMOVED] = + g_signal_new (NM_SUPPLICANT_INTERFACE_REMOVED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[NEW_BSS] = + g_signal_new (NM_SUPPLICANT_INTERFACE_NEW_BSS, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT); + + signals[BSS_UPDATED] = + g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_UPDATED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT); + + signals[BSS_REMOVED] = + g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_REMOVED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[SCAN_DONE] = + g_signal_new (NM_SUPPLICANT_INTERFACE_SCAN_DONE, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + signals[CONNECTION_ERROR] = + g_signal_new (NM_SUPPLICANT_INTERFACE_CONNECTION_ERROR, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + + signals[CREDENTIALS_REQUEST] = + g_signal_new (NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); +} + diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h new file mode 100644 index 00000000..5ab66d5d --- /dev/null +++ b/src/supplicant/nm-supplicant-interface.h @@ -0,0 +1,122 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2010 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_INTERFACE_H__ +#define __NETWORKMANAGER_SUPPLICANT_INTERFACE_H__ + +#include "nm-supplicant-types.h" + +/* + * Supplicant interface states + * A mix of wpa_supplicant interface states and internal states. + */ +enum { + NM_SUPPLICANT_INTERFACE_STATE_INIT = 0, + NM_SUPPLICANT_INTERFACE_STATE_STARTING, + NM_SUPPLICANT_INTERFACE_STATE_READY, + NM_SUPPLICANT_INTERFACE_STATE_DISABLED, + NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, + NM_SUPPLICANT_INTERFACE_STATE_INACTIVE, + NM_SUPPLICANT_INTERFACE_STATE_SCANNING, + NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING, + NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING, + NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED, + NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE, + NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, + NM_SUPPLICANT_INTERFACE_STATE_COMPLETED, + NM_SUPPLICANT_INTERFACE_STATE_DOWN, + NM_SUPPLICANT_INTERFACE_STATE_LAST +}; + +#define NM_TYPE_SUPPLICANT_INTERFACE (nm_supplicant_interface_get_type ()) +#define NM_SUPPLICANT_INTERFACE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterface)) +#define NM_SUPPLICANT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass)) +#define NM_IS_SUPPLICANT_INTERFACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SUPPLICANT_INTERFACE)) +#define NM_IS_SUPPLICANT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SUPPLICANT_INTERFACE)) +#define NM_SUPPLICANT_INTERFACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass)) + +/* Properties */ +#define NM_SUPPLICANT_INTERFACE_IFACE "iface" +#define NM_SUPPLICANT_INTERFACE_SCANNING "scanning" +#define NM_SUPPLICANT_INTERFACE_CURRENT_BSS "current-bss" +#define NM_SUPPLICANT_INTERFACE_DRIVER "driver" +#define NM_SUPPLICANT_INTERFACE_FAST_SUPPORTED "fast-supported" +#define NM_SUPPLICANT_INTERFACE_AP_SUPPORT "ap-support" + +/* Signals */ +#define NM_SUPPLICANT_INTERFACE_STATE "state" +#define NM_SUPPLICANT_INTERFACE_REMOVED "removed" +#define NM_SUPPLICANT_INTERFACE_NEW_BSS "new-bss" +#define NM_SUPPLICANT_INTERFACE_BSS_UPDATED "bss-updated" +#define NM_SUPPLICANT_INTERFACE_BSS_REMOVED "bss-removed" +#define NM_SUPPLICANT_INTERFACE_SCAN_DONE "scan-done" +#define NM_SUPPLICANT_INTERFACE_CONNECTION_ERROR "connection-error" +#define NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST "credentials-request" + +typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass; + +GType nm_supplicant_interface_get_type (void); + +NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname, + NMSupplicantDriver driver, + gboolean fast_supported, + NMSupplicantFeature ap_support); + +void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self, + gboolean available); + +gboolean nm_supplicant_interface_set_config (NMSupplicantInterface * iface, + NMSupplicantConfig * cfg, + GError **error); + +void nm_supplicant_interface_disconnect (NMSupplicantInterface * iface); + +const char *nm_supplicant_interface_get_object_path (NMSupplicantInterface * iface); + +gboolean nm_supplicant_interface_request_scan (NMSupplicantInterface * self, const GPtrArray *ssids); + +guint32 nm_supplicant_interface_get_state (NMSupplicantInterface * self); + +const char *nm_supplicant_interface_state_to_string (guint32 state); + +gboolean nm_supplicant_interface_get_scanning (NMSupplicantInterface *self); + +const char *nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self); + +gint32 nm_supplicant_interface_get_last_scan_time (NMSupplicantInterface *self); + +const char *nm_supplicant_interface_get_ifname (NMSupplicantInterface *self); + +guint nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self); + +gboolean nm_supplicant_interface_get_has_credentials_request (NMSupplicantInterface *self); + +gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self, + const char *field, + const char *value, + GError **error); + +NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self); + +void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self, + NMSupplicantFeature apmode); + +#endif /* NM_SUPPLICANT_INTERFACE_H */ diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c new file mode 100644 index 00000000..2fbfa391 --- /dev/null +++ b/src/supplicant/nm-supplicant-manager.c @@ -0,0 +1,413 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2010 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "nm-default.h" + +#include "nm-supplicant-manager.h" + +#include <string.h> + +#include "nm-supplicant-interface.h" +#include "nm-supplicant-types.h" +#include "nm-core-internal.h" + +/*****************************************************************************/ + +typedef struct { + GDBusProxy * proxy; + GCancellable * cancellable; + gboolean running; + + GSList *ifaces; + gboolean fast_supported; + NMSupplicantFeature ap_support; + guint die_count_reset_id; + guint die_count; +} NMSupplicantManagerPrivate; + +struct _NMSupplicantManager { + GObject parent; + NMSupplicantManagerPrivate _priv; +}; + +struct _NMSupplicantManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE (NMSupplicantManager, nm_supplicant_manager, G_TYPE_OBJECT) + +#define NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantManager, NM_IS_SUPPLICANT_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_SUPPLICANT +#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "supplicant", __VA_ARGS__) + +/*****************************************************************************/ + +G_DEFINE_QUARK (nm-supplicant-error-quark, nm_supplicant_error); + +/*****************************************************************************/ + +static inline gboolean +die_count_exceeded (guint32 count) +{ + return count > 2; +} + +static gboolean +is_available (NMSupplicantManager *self) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + + return priv->running + && !die_count_exceeded (priv->die_count); +} + +/*****************************************************************************/ + +static void +_sup_iface_last_ref (gpointer data, + GObject *object, + gboolean is_last_ref) +{ + NMSupplicantManager *self = data; + NMSupplicantManagerPrivate *priv; + NMSupplicantInterface *sup_iface = (NMSupplicantInterface *) object; + const char *op; + + g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self)); + g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (sup_iface)); + g_return_if_fail (is_last_ref); + + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + + if (!g_slist_find (priv->ifaces, sup_iface)) + g_return_if_reached (); + + /* Ask wpa_supplicant to remove this interface */ + if ( priv->running + && priv->proxy + && (op = nm_supplicant_interface_get_object_path (sup_iface))) { + g_dbus_proxy_call (priv->proxy, + "RemoveInterface", + g_variant_new ("(o)", op), + G_DBUS_CALL_FLAGS_NONE, + 3000, + NULL, + NULL, + NULL); + } + + priv->ifaces = g_slist_remove (priv->ifaces, sup_iface); + g_object_remove_toggle_ref ((GObject *) sup_iface, _sup_iface_last_ref, self); +} + +/** + * nm_supplicant_manager_create_interface: + * @self: the #NMSupplicantManager + * @ifname: the interface for which to obtain the supplicant interface + * @is_wireless: whether the interface is supposed to be wireless. + * + * Note: the manager owns a reference to the instance and the only way to + * get the manager to release it, is by dropping all other references + * to the supplicant-interface (or destroying the manager). + * + * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL. + * Must be unrefed at the end. + * */ +NMSupplicantInterface * +nm_supplicant_manager_create_interface (NMSupplicantManager *self, + const char *ifname, + NMSupplicantDriver driver) +{ + NMSupplicantManagerPrivate *priv; + NMSupplicantInterface *iface; + GSList *ifaces; + + g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL); + g_return_val_if_fail (ifname != NULL, NULL); + + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + + _LOGD ("(%s): creating new supplicant interface", ifname); + + /* assert against not requesting duplicate interfaces. */ + for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) { + if (g_strcmp0 (nm_supplicant_interface_get_ifname (ifaces->data), ifname) == 0) + g_return_val_if_reached (NULL); + } + + iface = nm_supplicant_interface_new (ifname, + driver, + priv->fast_supported, + priv->ap_support); + + priv->ifaces = g_slist_prepend (priv->ifaces, iface); + g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self); + + /* If we're making the supplicant take a time out for a bit, don't + * let the supplicant interface start immediately, just let it hang + * around in INIT state until we're ready to talk to the supplicant + * again. + */ + if (is_available (self)) + nm_supplicant_interface_set_supplicant_available (iface, TRUE); + + return iface; +} + +static void +update_capabilities (NMSupplicantManager *self) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + GSList *ifaces; + const char **array; + GVariant *value; + + /* The supplicant only advertises global capabilities if the following + * commit has been applied: + * + * commit 1634ac0654eba8d458640a115efc0a6cde3bac4d + * Author: Dan Williams <dcbw@redhat.com> + * Date: Sat Sep 29 19:06:30 2012 +0300 + * + * dbus: Add global capabilities property + */ + priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN; + + value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities"); + if (value) { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) { + array = g_variant_get_strv (value, NULL); + priv->ap_support = NM_SUPPLICANT_FEATURE_NO; + if (array) { + if (g_strv_contains (array, "ap")) + priv->ap_support = NM_SUPPLICANT_FEATURE_YES; + g_free (array); + } + } + g_variant_unref (value); + } + + /* Tell all interfaces about results of the AP check */ + for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) + nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support); + + _LOGD ("AP mode is %ssupported", + (priv->ap_support == NM_SUPPLICANT_FEATURE_YES) ? "" : + (priv->ap_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly "); + + /* EAP-FAST */ + priv->fast_supported = FALSE; + value = g_dbus_proxy_get_cached_property (priv->proxy, "EapMethods"); + if (value) { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) { + array = g_variant_get_strv (value, NULL); + if (array) { + if (g_strv_contains (array, "fast")) + priv->fast_supported = TRUE; + g_free (array); + } + } + g_variant_unref (value); + } + + _LOGD ("EAP-FAST is %ssupported", priv->fast_supported ? "" : "not "); +} + +static void +availability_changed (NMSupplicantManager *self, gboolean available) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + GSList *ifaces, *iter; + + if (!priv->ifaces) + return; + + /* setting the supplicant as unavailable might cause the caller to unref + * the supplicant (and thus remove the instance from the list of interfaces. + * Delay that by taking an additional reference first. */ + ifaces = g_slist_copy (priv->ifaces); + for (iter = ifaces; iter; iter = iter->next) + g_object_ref (iter->data); + for (iter = ifaces; iter; iter = iter->next) + nm_supplicant_interface_set_supplicant_available (iter->data, available); + g_slist_free_full (ifaces, g_object_unref); +} + +static void +set_running (NMSupplicantManager *self, gboolean now_running) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + gboolean old_available = is_available (self); + gboolean new_available; + + priv->running = now_running; + new_available = is_available (self); + if (old_available != new_available) + availability_changed (self, new_available); +} + +static void +set_die_count (NMSupplicantManager *self, guint new_die_count) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + gboolean old_available = is_available (self); + gboolean new_available; + + priv->die_count = new_die_count; + new_available = is_available (self); + if (old_available != new_available) + availability_changed (self, new_available); +} + +static gboolean +wpas_die_count_reset_cb (gpointer user_data) +{ + NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data); + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + + /* Reset the die count back to zero, which allows use of the supplicant again */ + priv->die_count_reset_id = 0; + set_die_count (self, 0); + _LOGI ("wpa_supplicant die count reset"); + return FALSE; +} + +static void +name_owner_cb (GDBusProxy *proxy, GParamSpec *pspec, gpointer user_data) +{ + NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data); + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + char *owner; + + g_return_if_fail (proxy == priv->proxy); + + owner = g_dbus_proxy_get_name_owner (proxy); + _LOGI ("wpa_supplicant %s", owner ? "running" : "stopped"); + + if (owner) { + set_running (self, TRUE); + update_capabilities (self); + } else if (priv->running) { + /* Reschedule the die count reset timeout. Every time the supplicant + * dies we wait 10 seconds before resetting the counter. If the + * supplicant died more than twice before the timer is reset, then + * we don't try to talk to the supplicant for a while. + */ + if (priv->die_count_reset_id) + g_source_remove (priv->die_count_reset_id); + priv->die_count_reset_id = g_timeout_add_seconds (10, wpas_die_count_reset_cb, self); + set_die_count (self, priv->die_count + 1); + + if (die_count_exceeded (priv->die_count)) { + _LOGI ("wpa_supplicant die count %d; ignoring for 10 seconds", + priv->die_count); + } + + set_running (self, FALSE); + + priv->fast_supported = FALSE; + } + + g_free (owner); +} + +static void +on_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data) +{ + NMSupplicantManager *self; + NMSupplicantManagerPrivate *priv; + GError *error = NULL; + GDBusProxy *proxy; + + proxy = g_dbus_proxy_new_for_bus_finish (result, &error); + if (!proxy) { + _LOGW ("failed to acquire wpa_supplicant proxy: Wi-Fi and 802.1x will not be available (%s)", + error->message); + g_clear_error (&error); + return; + } + + self = NM_SUPPLICANT_MANAGER (user_data); + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + + priv->proxy = proxy; + g_signal_connect (priv->proxy, "notify::g-name-owner", G_CALLBACK (name_owner_cb), self); + name_owner_cb (priv->proxy, NULL, self); +} + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER (NMSupplicantManager, nm_supplicant_manager_get, NM_TYPE_SUPPLICANT_MANAGER); + +static void +nm_supplicant_manager_init (NMSupplicantManager *self) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + + priv->cancellable = g_cancellable_new (); + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + WPAS_DBUS_SERVICE, + WPAS_DBUS_PATH, + WPAS_DBUS_INTERFACE, + priv->cancellable, + (GAsyncReadyCallback) on_proxy_acquired, + self); +} + +static void +dispose (GObject *object) +{ + NMSupplicantManager *self = (NMSupplicantManager *) object; + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + GSList *ifaces; + + nm_clear_g_source (&priv->die_count_reset_id); + + if (priv->cancellable) { + g_cancellable_cancel (priv->cancellable); + g_clear_object (&priv->cancellable); + } + + if (priv->ifaces) { + for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) + g_object_remove_toggle_ref (ifaces->data, _sup_iface_last_ref, self); + g_slist_free (priv->ifaces); + priv->ifaces = NULL; + } + + g_clear_object (&priv->proxy); + + G_OBJECT_CLASS (nm_supplicant_manager_parent_class)->dispose (object); +} + +static void +nm_supplicant_manager_class_init (NMSupplicantManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dispose; +} + diff --git a/src/supplicant/nm-supplicant-manager.h b/src/supplicant/nm-supplicant-manager.h new file mode 100644 index 00000000..8928cf20 --- /dev/null +++ b/src/supplicant/nm-supplicant-manager.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2008 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ +#define __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ + +#include "nm-supplicant-types.h" +#include "devices/nm-device.h" + +#define NM_TYPE_SUPPLICANT_MANAGER (nm_supplicant_manager_get_type ()) +#define NM_SUPPLICANT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManager)) +#define NM_SUPPLICANT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManagerClass)) +#define NM_IS_SUPPLICANT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SUPPLICANT_MANAGER)) +#define NM_IS_SUPPLICANT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SUPPLICANT_MANAGER)) +#define NM_SUPPLICANT_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManagerClass)) + +typedef struct _NMSupplicantManagerClass NMSupplicantManagerClass; + +GType nm_supplicant_manager_get_type (void); + +NMSupplicantManager *nm_supplicant_manager_get (void); + +NMSupplicantInterface *nm_supplicant_manager_create_interface (NMSupplicantManager *mgr, + const char *ifname, + NMSupplicantDriver driver); + +#endif /* __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ */ diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c new file mode 100644 index 00000000..9e220808 --- /dev/null +++ b/src/supplicant/nm-supplicant-settings-verify.c @@ -0,0 +1,281 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2012 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-supplicant-settings-verify.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +struct Opt { + const char * key; + const OptType type; + const gint32 int_low; /* Inclusive */ + const gint32 int_high; /* Inclusive; max length for strings */ + const gboolean str_allowed_multiple; + const char ** str_allowed; +}; + + +static gboolean validate_type_int (const struct Opt * opt, + const char * value, + const guint32 len); + +static gboolean validate_type_bytes (const struct Opt * opt, + const char * value, + const guint32 len); + +static gboolean validate_type_utf8 (const struct Opt *opt, + const char * value, + const guint32 len); + +static gboolean validate_type_keyword (const struct Opt * opt, + const char * value, + const guint32 len); + +typedef gboolean (*validate_func)(const struct Opt *, const char *, const guint32); + +struct validate_entry { + const OptType type; + const validate_func func; +}; + +static const struct validate_entry validate_table[] = { + { TYPE_INT, validate_type_int }, + { TYPE_BYTES, validate_type_bytes }, + { TYPE_UTF8, validate_type_utf8 }, + { TYPE_KEYWORD, validate_type_keyword }, +}; + + +const char * pairwise_allowed[] = { "CCMP", "TKIP", "NONE", NULL }; +const char * group_allowed[] = { "CCMP", "TKIP", "WEP104", "WEP40", NULL }; +const char * proto_allowed[] = { "WPA", "RSN", NULL }; +const char * key_mgmt_allowed[] = { "WPA-PSK", "WPA-EAP", "IEEE8021X", "WPA-NONE", + "NONE", NULL }; +const char * auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL }; +const char * eap_allowed[] = { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM", + "PSK", "FAST", "PWD", NULL }; + +const char * phase1_allowed[] = {"peapver=0", "peapver=1", "peaplabel=1", + "peap_outer_success=0", "include_tls_length=1", + "sim_min_num_chal=3", "fast_provisioning=0", + "fast_provisioning=1", "fast_provisioning=2", + "fast_provisioning=3", NULL }; +const char * phase2_allowed[] = {"auth=PAP", "auth=CHAP", "auth=MSCHAP", + "auth=MSCHAPV2", "auth=GTC", "auth=OTP", + "auth=MD5", "auth=TLS", "autheap=MD5", + "autheap=MSCHAPV2", "autheap=OTP", + "autheap=GTC", "autheap=TLS", NULL }; + +static const struct Opt opt_table[] = { + { "ssid", TYPE_BYTES, 0, 32,FALSE, NULL }, + { "bssid", TYPE_KEYWORD, 0, 0, FALSE, NULL }, + { "scan_ssid", TYPE_INT, 0, 1, FALSE, NULL }, + { "mode", TYPE_INT, 0, 2, FALSE, NULL }, + { "frequency", TYPE_INT, 2412, 5825, FALSE, NULL }, + { "auth_alg", TYPE_KEYWORD, 0, 0, FALSE, auth_alg_allowed }, + { "psk", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "pairwise", TYPE_KEYWORD, 0, 0, FALSE, pairwise_allowed }, + { "group", TYPE_KEYWORD, 0, 0, FALSE, group_allowed }, + { "proto", TYPE_KEYWORD, 0, 0, FALSE, proto_allowed }, + { "key_mgmt", TYPE_KEYWORD, 0, 0, FALSE, key_mgmt_allowed }, + { "wep_key0", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "wep_key1", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "wep_key2", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "wep_key3", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "wep_tx_keyidx", TYPE_INT, 0, 3, FALSE, NULL }, + { "eapol_flags", TYPE_INT, 0, 3, FALSE, NULL }, + { "eap", TYPE_KEYWORD, 0, 0, FALSE, eap_allowed }, + { "identity", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "password", TYPE_UTF8, 0, 0, FALSE, NULL }, + { "ca_path", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "subject_match", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "altsubject_match", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "domain_suffix_match",TYPE_BYTES, 0, 0, FALSE, NULL }, + { "ca_cert", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "client_cert", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "private_key", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "private_key_passwd", TYPE_BYTES, 0, 1024, FALSE, NULL }, + { "phase1", TYPE_KEYWORD, 0, 0, TRUE, phase1_allowed }, + { "phase2", TYPE_KEYWORD, 0, 0, TRUE, phase2_allowed }, + { "anonymous_identity", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "ca_path2", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "subject_match2", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "altsubject_match2", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "domain_suffix_match2", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "ca_cert2", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "client_cert2", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "private_key2", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "private_key2_passwd",TYPE_BYTES, 0, 1024, FALSE, NULL }, + { "pin", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "pcsc", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "nai", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "eappsk", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "pac_file", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "engine", TYPE_INT, 0, 1, FALSE, NULL }, + { "engine_id", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "key_id", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "fragment_size", TYPE_INT, 1, 2000, FALSE, NULL }, + { "proactive_key_caching", TYPE_INT, 0, 1, FALSE, NULL }, + { "bgscan", TYPE_BYTES, 0, 0, FALSE, NULL }, + { "pac_file", TYPE_BYTES, 0, 1024, FALSE, NULL }, + { "freq_list", TYPE_KEYWORD, 0, 0, FALSE, NULL }, + { "macsec_policy", TYPE_INT, 0, 1, FALSE, NULL }, + { "macsec_integ_only", TYPE_INT, 0, 1, FALSE, NULL }, + { "mka_cak", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "mka_ckn", TYPE_BYTES, 0, 65536, FALSE, NULL }, + { "macsec_port", TYPE_INT, 1, 65534, FALSE, NULL }, +}; + + +static gboolean +validate_type_int (const struct Opt * opt, + const char * value, + const guint32 len) +{ + long int intval; + + g_return_val_if_fail (opt != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + errno = 0; + intval = strtol (value, NULL, 10); + if (errno != 0) + return FALSE; + + /* strtol returns a long, but we are dealing with ints */ + if (intval > INT_MAX || intval < INT_MIN) + return FALSE; + if (intval > opt->int_high || intval < opt->int_low) + return FALSE; + + return TRUE; +} + +static gboolean +validate_type_bytes (const struct Opt * opt, + const char * value, + const guint32 len) +{ + guint32 check_len; + + g_return_val_if_fail (opt != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + check_len = opt->int_high ? opt->int_high : 255; + if (len > check_len) + return FALSE; + + return TRUE; +} + +static gboolean +validate_type_utf8 (const struct Opt *opt, + const char * value, + const guint32 len) +{ + guint32 check_len; + + g_return_val_if_fail (opt != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + check_len = opt->int_high ? opt->int_high : 255; + /* Note that we deliberately don't validate the UTF-8, because + some "UTF-8" fields, such as 8021x.password, do not actually + have to be valid UTF-8 */ + if (g_utf8_strlen (value, len) > check_len) + return FALSE; + + return TRUE; +} + +static gboolean +validate_type_keyword (const struct Opt * opt, + const char * value, + const guint32 len) +{ + char ** allowed; + gchar ** candidates = NULL; + char ** candidate; + gboolean found = FALSE; + + g_return_val_if_fail (opt != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + /* Allow everything */ + if (!opt->str_allowed) + return TRUE; + + candidates = g_strsplit (value, " ", 0); + if (!candidates) + goto out; + + /* validate each space-separated word in 'value' */ + for (candidate = candidates; *candidate; candidate++) { + found = FALSE; + for (allowed = (char **) opt->str_allowed; *allowed; allowed++) { + if (strcmp (*candidate, *allowed) == 0) { + found = TRUE; + break; + } + } + if (!found) + break; + } + +out: + g_strfreev (candidates); + return found; +} + +OptType +nm_supplicant_settings_verify_setting (const char * key, + const char * value, + const guint32 len) +{ + OptType type = TYPE_INVALID; + int opt_count = sizeof (opt_table) / sizeof (opt_table[0]); + int val_count = sizeof (validate_table) / sizeof (validate_table[0]); + int i, j; + + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + for (i = 0; i < opt_count; i++) { + if (strcmp (opt_table[i].key, key) != 0) + continue; + + for (j = 0; j < val_count; j++) { + if (validate_table[j].type == opt_table[i].type) { + if ((*(validate_table[j].func))(&opt_table[i], value, len)) { + type = opt_table[i].type; + break; + } + } + } + } + + return type; +} + diff --git a/src/supplicant/nm-supplicant-settings-verify.h b/src/supplicant/nm-supplicant-settings-verify.h new file mode 100644 index 00000000..920343ba --- /dev/null +++ b/src/supplicant/nm-supplicant-settings-verify.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2008 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__ +#define __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__ + +typedef enum { + TYPE_INVALID = 0, + TYPE_INT, + TYPE_BYTES, + TYPE_UTF8, + TYPE_KEYWORD, + TYPE_STRING +} OptType; + +OptType nm_supplicant_settings_verify_setting (const char * key, + const char * value, + const guint32 len); + + +#endif /* __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__ */ diff --git a/src/supplicant/nm-supplicant-types.h b/src/supplicant/nm-supplicant-types.h new file mode 100644 index 00000000..f75827ec --- /dev/null +++ b/src/supplicant/nm-supplicant-types.h @@ -0,0 +1,58 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2006 - 2008 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_SUPPLICANT_TYPES_H__ +#define __NETWORKMANAGER_SUPPLICANT_TYPES_H__ + +#define WPAS_DBUS_SERVICE "fi.w1.wpa_supplicant1" +#define WPAS_DBUS_PATH "/fi/w1/wpa_supplicant1" +#define WPAS_DBUS_INTERFACE "fi.w1.wpa_supplicant1" + +typedef struct _NMSupplicantManager NMSupplicantManager; +typedef struct _NMSupplicantInterface NMSupplicantInterface; +typedef struct _NMSupplicantConfig NMSupplicantConfig; + +typedef enum { + NM_SUPPLICANT_FEATURE_UNKNOWN = 0, /* Can't detect whether supported or not */ + NM_SUPPLICANT_FEATURE_NO = 1, /* Feature definitely not supported */ + NM_SUPPLICANT_FEATURE_YES = 2, /* Feature definitely supported */ +} NMSupplicantFeature; + +/** + * NMSupplicantError: + * @NM_SUPPLICANT_ERROR_UNKNOWN: unknown or unclassified error + * @NM_SUPPLICANT_ERROR_CONFIG: a failure constructing the + * wpa-supplicant configuration. + */ +typedef enum { + NM_SUPPLICANT_ERROR_UNKNOWN = 0, /*< nick=Unknown >*/ + NM_SUPPLICANT_ERROR_CONFIG = 1, /*< nick=Config >*/ +} NMSupplicantError; + +typedef enum { + NM_SUPPLICANT_DRIVER_WIRELESS, + NM_SUPPLICANT_DRIVER_WIRED, + NM_SUPPLICANT_DRIVER_MACSEC, +} NMSupplicantDriver; + +#define NM_SUPPLICANT_ERROR (nm_supplicant_error_quark ()) +GQuark nm_supplicant_error_quark (void); + +#endif /* NM_SUPPLICANT_TYPES_H */ diff --git a/src/supplicant/tests/certs/test-ca-cert.pem b/src/supplicant/tests/certs/test-ca-cert.pem new file mode 100644 index 00000000..ef1be20d --- /dev/null +++ b/src/supplicant/tests/certs/test-ca-cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD +VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw +FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE +AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx +NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ +QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55 +IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3 +DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU +xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc +Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3 +FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4 +HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm +J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P +T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF +ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO +BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL +EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl +c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC +AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc +JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT +p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t +qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn +B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp +3nniskIjbH0xjgZf/nVMyLnjxg== +-----END CERTIFICATE----- diff --git a/src/supplicant/tests/certs/test-cert.p12 b/src/supplicant/tests/certs/test-cert.p12 new file mode 100644 index 00000000..ae4a6830 --- /dev/null +++ b/src/supplicant/tests/certs/test-cert.p12 Binary files differdiff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c new file mode 100644 index 00000000..fd91e921 --- /dev/null +++ b/src/supplicant/tests/test-supplicant-config.c @@ -0,0 +1,614 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "nm-core-internal.h" + +#include "supplicant/nm-supplicant-config.h" +#include "supplicant/nm-supplicant-settings-verify.h" + +#include "nm-test-utils-core.h" + +static gboolean +validate_opt (const char *detail, + GVariant *config, + const char *key, + OptType val_type, + gconstpointer expected, + size_t expected_len) +{ + char *config_key; + GVariant *config_value; + gboolean found = FALSE; + const guint8 *bytes; + gsize len; + const char *s; + const unsigned char *expected_array = expected; + GVariantIter iter; + + g_assert (g_variant_is_of_type (config, G_VARIANT_TYPE_VARDICT)); + + g_variant_iter_init (&iter, config); + while (g_variant_iter_next (&iter, "{&sv}", (gpointer) &config_key, (gpointer) &config_value)) { + if (!strcmp (key, config_key)) { + found = TRUE; + switch (val_type) { + case TYPE_INT: + g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_INT32)); + g_assert_cmpint (g_variant_get_int32 (config_value), ==, GPOINTER_TO_INT (expected)); + break; + case TYPE_BYTES: + g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_BYTESTRING)); + bytes = g_variant_get_fixed_array (config_value, &len, 1); + g_assert_cmpint (len, ==, expected_len); + g_assert (memcmp (bytes, expected_array, expected_len) == 0); + break; + case TYPE_KEYWORD: + case TYPE_STRING: + g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_STRING)); + if (expected_len == -1) + expected_len = strlen ((const char *) expected); + s = g_variant_get_string (config_value, NULL); + g_assert_cmpint (strlen (s), ==, expected_len); + g_assert_cmpstr (s, ==, expected); + break; + default: + g_assert_not_reached (); + break; + } + } + g_variant_unref (config_value); + } + + return found; +} + +static void +test_wifi_open (void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMSupplicantConfig *config = NULL; + gs_unref_variant GVariant *config_dict = NULL; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + NMSettingIPConfig *s_ip4; + char *uuid; + gboolean success; + GError *error = NULL; + GBytes *ssid; + const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 }; + const char *bssid_str = "11:22:33:44:55:66"; + + connection = nm_simple_connection_new (); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new (); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + uuid = nm_utils_uuid_generate (); + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, "Test Wifi Open", + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + g_free (uuid); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + + ssid = g_bytes_new (ssid_data, sizeof (ssid_data)); + + g_object_set (s_wifi, + NM_SETTING_WIRELESS_SSID, ssid, + NM_SETTING_WIRELESS_BSSID, bssid_str, + NM_SETTING_WIRELESS_MODE, "infrastructure", + NM_SETTING_WIRELESS_BAND, "bg", + NULL); + + g_bytes_unref (ssid); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip4)); + + g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + success = nm_connection_verify (connection, &error); + g_assert_no_error (error); + g_assert (success); + + config = nm_supplicant_config_new (); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'ssid' value 'Test SSID'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'scan_ssid' value '1'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'bssid' value '11:22:33:44:55:66'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'freq_list' value *"); + g_assert (nm_supplicant_config_add_setting_wireless (config, + s_wifi, + 0, + &error)); + g_assert_no_error (error); + g_test_assert_expected_messages (); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'key_mgmt' value 'NONE'"); + g_assert (nm_supplicant_config_add_no_security (config, &error)); + g_assert_no_error (error); + g_test_assert_expected_messages (); + + config_dict = nm_supplicant_config_to_variant (config); + g_assert (config_dict); + + validate_opt ("wifi-open", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1); + validate_opt ("wifi-open", config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data)); + validate_opt ("wifi-open", config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1); + validate_opt ("wifi-open", config_dict, "key_mgmt", TYPE_KEYWORD, "NONE", -1); +} + +static void +test_wifi_wep_key (const char *detail, + NMWepKeyType wep_type, + const char *key_data, + const unsigned char *expected, + size_t expected_size) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMSupplicantConfig *config = NULL; + gs_unref_variant GVariant *config_dict = NULL; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig *s_ip4; + char *uuid; + gboolean success; + GError *error = NULL; + GBytes *ssid; + const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 }; + const char *bssid_str = "11:22:33:44:55:66"; + + connection = nm_simple_connection_new (); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new (); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + uuid = nm_utils_uuid_generate (); + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, "Test Wifi WEP Key", + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + g_free (uuid); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + + ssid = g_bytes_new (ssid_data, sizeof (ssid_data)); + + g_object_set (s_wifi, + NM_SETTING_WIRELESS_SSID, ssid, + NM_SETTING_WIRELESS_BSSID, bssid_str, + NM_SETTING_WIRELESS_MODE, "infrastructure", + NM_SETTING_WIRELESS_BAND, "bg", + NULL); + + g_bytes_unref (ssid); + + /* Wifi Security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wsec)); + + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, wep_type, + NULL); + nm_setting_wireless_security_set_wep_key (s_wsec, 0, key_data); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip4)); + + g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + success = nm_connection_verify (connection, &error); + g_assert_no_error (error); + g_assert (success); + + config = nm_supplicant_config_new (); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'ssid' value 'Test SSID'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'scan_ssid' value '1'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'bssid' value '11:22:33:44:55:66'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'freq_list' value *"); + g_assert (nm_supplicant_config_add_setting_wireless (config, + s_wifi, + 0, + &error)); + g_assert_no_error (error); + g_test_assert_expected_messages (); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'key_mgmt' value 'NONE'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'wep_key0' value *"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'wep_tx_keyidx' value '0'"); + g_assert (nm_supplicant_config_add_setting_wireless_security (config, + s_wsec, + NULL, + "376aced7-b28c-46be-9a62-fcdf072571da", + 1500, + &error)); + g_assert_no_error (error); + g_test_assert_expected_messages (); + + config_dict = nm_supplicant_config_to_variant (config); + g_assert (config_dict); + + validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1); + validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data)); + validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1); + validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "NONE", -1); + validate_opt (detail, config_dict, "wep_tx_keyidx", TYPE_INT, GINT_TO_POINTER (0), -1); + validate_opt (detail, config_dict, "wep_key0", TYPE_BYTES, expected, expected_size); +} + +static void +test_wifi_wep (void) +{ + const char *key1 = "12345"; + const unsigned char key1_expected[] = { 0x31, 0x32, 0x33, 0x34, 0x35 }; + const char *key2 = "ascii test$$$"; + const unsigned char key2_expected[] = { 0x61, 0x73, 0x63, 0x69, 0x69, 0x20, 0x74, 0x65, 0x73, 0x74, 0x24, 0x24, 0x24 }; + const char *key3 = "abcdef1234"; + const unsigned char key3_expected[] = { 0xab, 0xcd, 0xef, 0x12, 0x34 }; + const char *key4 = "96aec785c6392675f87f592972"; + const unsigned char key4_expected[] = { 0x96, 0xae, 0xc7, 0x85, 0xc6, 0x39, 0x26, 0x75, 0xf8, 0x7f, 0x59, 0x29, 0x72 }; + const char *key5 = "r34lly l33t w3p p4ssphr4s3 for t3st1ng"; + const unsigned char key5_expected[] = { 0xce, 0x68, 0x8b, 0x35, 0xf6, 0x0a, 0x2b, 0xbf, 0xc9, 0x8f, 0xed, 0x10, 0xda }; + + test_wifi_wep_key ("wifi-wep-ascii-40", NM_WEP_KEY_TYPE_KEY, key1, key1_expected, sizeof (key1_expected)); + test_wifi_wep_key ("wifi-wep-ascii-104", NM_WEP_KEY_TYPE_KEY, key2, key2_expected, sizeof (key2_expected)); + test_wifi_wep_key ("wifi-wep-hex-40", NM_WEP_KEY_TYPE_KEY, key3, key3_expected, sizeof (key3_expected)); + test_wifi_wep_key ("wifi-wep-hex-104", NM_WEP_KEY_TYPE_KEY, key4, key4_expected, sizeof (key4_expected)); + test_wifi_wep_key ("wifi-wep-passphrase-104", NM_WEP_KEY_TYPE_PASSPHRASE, key5, key5_expected, sizeof (key5_expected)); + + test_wifi_wep_key ("wifi-wep-old-hex-104", NM_WEP_KEY_TYPE_UNKNOWN, key4, key4_expected, sizeof (key4_expected)); +} + +static void +test_wifi_wpa_psk (const char *detail, + OptType key_type, + const char *key_data, + const unsigned char *expected, + size_t expected_size) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMSupplicantConfig *config = NULL; + gs_unref_variant GVariant *config_dict = NULL; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSettingIPConfig *s_ip4; + char *uuid; + gboolean success; + GError *error = NULL; + GBytes *ssid; + const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 }; + const char *bssid_str = "11:22:33:44:55:66"; + + connection = nm_simple_connection_new (); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new (); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + uuid = nm_utils_uuid_generate (); + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, "Test Wifi WEP Key", + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + g_free (uuid); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + + ssid = g_bytes_new (ssid_data, sizeof (ssid_data)); + + g_object_set (s_wifi, + NM_SETTING_WIRELESS_SSID, ssid, + NM_SETTING_WIRELESS_BSSID, bssid_str, + NM_SETTING_WIRELESS_MODE, "infrastructure", + NM_SETTING_WIRELESS_BAND, "bg", + NULL); + + g_bytes_unref (ssid); + + /* Wifi Security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wsec)); + + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_PSK, key_data, + NULL); + + nm_setting_wireless_security_add_proto (s_wsec, "wpa"); + nm_setting_wireless_security_add_proto (s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise (s_wsec, "tkip"); + nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp"); + nm_setting_wireless_security_add_group (s_wsec, "tkip"); + nm_setting_wireless_security_add_group (s_wsec, "ccmp"); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip4)); + + g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + success = nm_connection_verify (connection, &error); + g_assert_no_error (error); + g_assert (success); + + config = nm_supplicant_config_new (); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'ssid' value 'Test SSID'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'scan_ssid' value '1'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'bssid' value '11:22:33:44:55:66'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'freq_list' value *"); + g_assert (nm_supplicant_config_add_setting_wireless (config, + s_wifi, + 0, + &error)); + g_assert_no_error (error); + g_test_assert_expected_messages (); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'key_mgmt' value 'WPA-PSK'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'psk' value *"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'proto' value 'WPA RSN'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'pairwise' value 'TKIP CCMP'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'group' value 'TKIP CCMP'"); + g_assert (nm_supplicant_config_add_setting_wireless_security (config, + s_wsec, + NULL, + "376aced7-b28c-46be-9a62-fcdf072571da", + 1500, + &error)); + g_assert_no_error (error); + g_test_assert_expected_messages (); + + config_dict = nm_supplicant_config_to_variant (config); + g_assert (config_dict); + + validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1); + validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data)); + validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1); + validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-PSK", -1); + validate_opt (detail, config_dict, "proto", TYPE_KEYWORD, "WPA RSN", -1); + validate_opt (detail, config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP", -1); + validate_opt (detail, config_dict, "group", TYPE_KEYWORD, "TKIP CCMP", -1); + validate_opt (detail, config_dict, "psk", key_type, expected, expected_size); +} + +static void +test_wifi_wpa_psk_types (void) +{ + const char *key1 = "d4721e911461d3cdef9793858e977fcda091779243abb7316c2f11605a160893"; + const unsigned char key1_expected[] = { 0xd4, 0x72, 0x1e, 0x91, 0x14, 0x61, 0xd3, 0xcd, + 0xef, 0x97, 0x93, 0x85, 0x8e, 0x97, 0x7f, 0xcd, + 0xa0, 0x91, 0x77, 0x92, 0x43, 0xab, 0xb7, 0x31, + 0x6c, 0x2f, 0x11, 0x60, 0x5a, 0x16, 0x08, 0x93 }; + const char *key2 = "r34lly l33t wp4 p4ssphr4s3 for t3st1ng"; + + test_wifi_wpa_psk ("wifi-wpa-psk-hex", TYPE_BYTES, key1, key1_expected, sizeof (key1_expected)); + test_wifi_wpa_psk ("wifi-wep-psk-passphrase", TYPE_STRING, key2, (gconstpointer) key2, strlen (key2)); +} + +static void +test_wifi_eap (void) +{ + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMSupplicantConfig *config = NULL; + gs_unref_variant GVariant *config_dict = NULL; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + NMSetting8021x *s_8021x; + NMSettingIPConfig *s_ip4; + char *uuid; + gboolean success; + GError *error = NULL; + GBytes *ssid; + const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 }; + const char *bssid_str = "11:22:33:44:55:66"; + guint32 mtu = 1100; + + connection = nm_simple_connection_new (); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new (); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + uuid = nm_utils_uuid_generate (); + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, "Test Wifi EAP-TLS", + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + g_free (uuid); + + /* Wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + + ssid = g_bytes_new (ssid_data, sizeof (ssid_data)); + + g_object_set (s_wifi, + NM_SETTING_WIRELESS_SSID, ssid, + NM_SETTING_WIRELESS_BSSID, bssid_str, + NM_SETTING_WIRELESS_MODE, "infrastructure", + NM_SETTING_WIRELESS_BAND, "bg", + NULL); + + g_bytes_unref (ssid); + + /* Wifi Security setting */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wsec)); + + g_object_set (s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", + NULL); + + nm_setting_wireless_security_add_proto (s_wsec, "wpa"); + nm_setting_wireless_security_add_proto (s_wsec, "rsn"); + nm_setting_wireless_security_add_pairwise (s_wsec, "tkip"); + nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp"); + nm_setting_wireless_security_add_group (s_wsec, "tkip"); + nm_setting_wireless_security_add_group (s_wsec, "ccmp"); + + /* 802-1X setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new (); + nm_connection_add_setting (connection, NM_SETTING (s_8021x)); + nm_setting_802_1x_add_eap_method (s_8021x, "tls"); + nm_setting_802_1x_set_client_cert (s_8021x, TEST_CERT_DIR "/test-cert.p12", NM_SETTING_802_1X_CK_SCHEME_PATH, NULL, NULL); + nm_setting_802_1x_set_ca_cert (s_8021x, TEST_CERT_DIR "/test-ca-cert.pem", NM_SETTING_802_1X_CK_SCHEME_PATH, NULL, NULL); + nm_setting_802_1x_set_private_key (s_8021x, TEST_CERT_DIR "/test-cert.p12", NULL, NM_SETTING_802_1X_CK_SCHEME_PATH, NULL, NULL); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip4)); + + g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + success = nm_connection_verify (connection, &error); + g_assert_no_error (error); + g_assert (success); + + config = nm_supplicant_config_new (); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'ssid' value 'Test SSID'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'scan_ssid' value '1'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'bssid' value '11:22:33:44:55:66'*"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'freq_list' value *"); + g_assert (nm_supplicant_config_add_setting_wireless (config, + s_wifi, + 0, + &error)); + g_assert_no_error (error); + g_test_assert_expected_messages (); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'key_mgmt' value 'WPA-EAP'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'proto' value 'WPA RSN'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'pairwise' value 'TKIP CCMP'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*added 'group' value 'TKIP CCMP'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*Config: added 'eap' value 'TLS'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*Config: added 'fragment_size' value '1086'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "* Config: added 'ca_cert' value '*/test-ca-cert.pem'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "* Config: added 'private_key' value '*/test-cert.p12'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*Config: added 'bgscan' value 'simple:30:-65:300'"); + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, + "*Config: added 'proactive_key_caching' value '1'"); + g_assert (nm_supplicant_config_add_setting_wireless_security (config, + s_wsec, + s_8021x, + "d5b488af-9cab-41ed-bad4-97709c58430f", + mtu, + &error)); + g_assert_no_error (error); + g_test_assert_expected_messages (); + + config_dict = nm_supplicant_config_to_variant (config); + g_assert (config_dict); + + validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1); + validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data)); + validate_opt ("wifi-eap", config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1); + validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP", -1); + validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS", -1); + validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN", -1); + validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP", -1); + validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP", -1); + validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14), -1); +} + +NMTST_DEFINE (); + +int main (int argc, char **argv) +{ + nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT"); + + g_test_add_func ("/supplicant-config/wifi-open", test_wifi_open); + g_test_add_func ("/supplicant-config/wifi-wep", test_wifi_wep); + g_test_add_func ("/supplicant-config/wifi-wpa-psk-types", test_wifi_wpa_psk_types); + g_test_add_func ("/supplicant-config/wifi-eap", test_wifi_eap); + + return g_test_run (); +} + |