diff options
Diffstat (limited to 'libnm-util/nm-utils.c')
| -rw-r--r-- | libnm-util/nm-utils.c | 2104 |
1 files changed, 2104 insertions, 0 deletions
diff --git a/libnm-util/nm-utils.c b/libnm-util/nm-utils.c new file mode 100644 index 00000000..ddb02a22 --- /dev/null +++ b/libnm-util/nm-utils.c @@ -0,0 +1,2104 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ + +/* NetworkManager -- Network link manager + * + * Ray Strode <rstrode@redhat.com> + * Dan Williams <dcbw@redhat.com> + * Tambet Ingo <tambet@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2005 - 2010 Red Hat, Inc. + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <arpa/inet.h> + +#include "wireless-helper.h" + +#include <glib.h> +#include <glib-object.h> +#include <glib/gi18n.h> +#include <dbus/dbus-glib.h> +#include <uuid/uuid.h> + +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "NetworkManager.h" +#include "nm-dbus-glib-types.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "crypto.h" + +/** + * SECTION:nm-utils + * @short_description: Utility functions + * @include: nm-utils.h + * + * A collection of utility functions for working SSIDs, IP addresses, WiFi + * access points and devices, among other things. + */ + +struct EncodingTriplet +{ + const char *encoding1; + const char *encoding2; + const char *encoding3; +}; + +struct IsoLangToEncodings +{ + const char * lang; + struct EncodingTriplet encodings; +}; + +/* 5-letter language codes */ +static const struct IsoLangToEncodings isoLangEntries5[] = +{ + /* Simplified Chinese */ + { "zh_cn", {"euc-cn", "gb2312", "gb18030"} }, /* PRC */ + { "zh_sg", {"euc-cn", "gb2312", "gb18030"} }, /* Singapore */ + + /* Traditional Chinese */ + { "zh_tw", {"big5", "euc-tw", NULL} }, /* Taiwan */ + { "zh_hk", {"big5", "euc-tw", "big5-hkcs"} },/* Hong Kong */ + { "zh_mo", {"big5", "euc-tw", NULL} }, /* Macau */ + + /* Table end */ + { NULL, {NULL, NULL, NULL} } +}; + +/* 2-letter language codes; we don't care about the other 3 in this table */ +static const struct IsoLangToEncodings isoLangEntries2[] = +{ + /* Japanese */ + { "ja", {"euc-jp", "shift_jis", "iso-2022-jp"} }, + + /* Korean */ + { "ko", {"euc-kr", "iso-2022-kr", "johab"} }, + + /* Thai */ + { "th", {"iso-8859-11","windows-874", NULL} }, + + /* Central European */ + { "hu", {"iso-8859-2", "windows-1250", NULL} }, /* Hungarian */ + { "cs", {"iso-8859-2", "windows-1250", NULL} }, /* Czech */ + { "hr", {"iso-8859-2", "windows-1250", NULL} }, /* Croatian */ + { "pl", {"iso-8859-2", "windows-1250", NULL} }, /* Polish */ + { "ro", {"iso-8859-2", "windows-1250", NULL} }, /* Romanian */ + { "sk", {"iso-8859-2", "windows-1250", NULL} }, /* Slovakian */ + { "sl", {"iso-8859-2", "windows-1250", NULL} }, /* Slovenian */ + { "sh", {"iso-8859-2", "windows-1250", NULL} }, /* Serbo-Croatian */ + + /* Cyrillic */ + { "ru", {"koi8-r", "windows-1251", "iso-8859-5"} }, /* Russian */ + { "be", {"koi8-r", "windows-1251", "iso-8859-5"} }, /* Belorussian */ + { "bg", {"windows-1251","koi8-r", "iso-8859-5"} }, /* Bulgarian */ + { "mk", {"koi8-r", "windows-1251", "iso-8859-5"} }, /* Macedonian */ + { "sr", {"koi8-r", "windows-1251", "iso-8859-5"} }, /* Serbian */ + { "uk", {"koi8-u", "koi8-r", "windows-1251"} }, /* Ukranian */ + + /* Arabic */ + { "ar", {"iso-8859-6", "windows-1256", NULL} }, + + /* Balitc */ + { "et", {"iso-8859-4", "windows-1257", NULL} }, /* Estonian */ + { "lt", {"iso-8859-4", "windows-1257", NULL} }, /* Lithuanian */ + { "lv", {"iso-8859-4", "windows-1257", NULL} }, /* Latvian */ + + /* Greek */ + { "el", {"iso-8859-7", "windows-1253", NULL} }, + + /* Hebrew */ + { "he", {"iso-8859-8", "windows-1255", NULL} }, + { "iw", {"iso-8859-8", "windows-1255", NULL} }, + + /* Turkish */ + { "tr", {"iso-8859-9", "windows-1254", NULL} }, + + /* Table end */ + { NULL, {NULL, NULL, NULL} } +}; + + +static GHashTable * langToEncodings5 = NULL; +static GHashTable * langToEncodings2 = NULL; + +static void +init_lang_to_encodings_hash (void) +{ + struct IsoLangToEncodings *enc; + + if (G_UNLIKELY (langToEncodings5 == NULL)) { + /* Five-letter codes */ + enc = (struct IsoLangToEncodings *) &isoLangEntries5[0]; + langToEncodings5 = g_hash_table_new (g_str_hash, g_str_equal); + while (enc->lang) { + g_hash_table_insert (langToEncodings5, (gpointer) enc->lang, + (gpointer) &enc->encodings); + enc++; + } + } + + if (G_UNLIKELY (langToEncodings2 == NULL)) { + /* Two-letter codes */ + enc = (struct IsoLangToEncodings *) &isoLangEntries2[0]; + langToEncodings2 = g_hash_table_new (g_str_hash, g_str_equal); + while (enc->lang) { + g_hash_table_insert (langToEncodings2, (gpointer) enc->lang, + (gpointer) &enc->encodings); + enc++; + } + } +} + + +static gboolean +get_encodings_for_lang (const char *lang, + char **encoding1, + char **encoding2, + char **encoding3) +{ + struct EncodingTriplet * encodings; + gboolean success = FALSE; + char * tmp_lang; + + g_return_val_if_fail (lang != NULL, FALSE); + g_return_val_if_fail (encoding1 != NULL, FALSE); + g_return_val_if_fail (encoding2 != NULL, FALSE); + g_return_val_if_fail (encoding3 != NULL, FALSE); + + *encoding1 = "iso-8859-1"; + *encoding2 = "windows-1251"; + *encoding3 = NULL; + + init_lang_to_encodings_hash (); + + tmp_lang = g_strdup (lang); + if ((encodings = g_hash_table_lookup (langToEncodings5, tmp_lang))) + { + *encoding1 = (char *) encodings->encoding1; + *encoding2 = (char *) encodings->encoding2; + *encoding3 = (char *) encodings->encoding3; + success = TRUE; + } + + /* Truncate tmp_lang to length of 2 */ + if (strlen (tmp_lang) > 2) + tmp_lang[2] = '\0'; + if (!success && (encodings = g_hash_table_lookup (langToEncodings2, tmp_lang))) + { + *encoding1 = (char *) encodings->encoding1; + *encoding2 = (char *) encodings->encoding2; + *encoding3 = (char *) encodings->encoding3; + success = TRUE; + } + + g_free (tmp_lang); + return success; +} + +static char * +string_to_utf8 (const char *str, gsize len) +{ + char *converted = NULL; + char *lang, *e1 = NULL, *e2 = NULL, *e3 = NULL; + + g_return_val_if_fail (str != NULL, NULL); + + if (g_utf8_validate (str, len, NULL)) + return g_strdup (str); + + /* LANG may be a good encoding hint */ + g_get_charset ((const char **)(&e1)); + if ((lang = getenv ("LANG"))) { + char * dot; + + lang = g_ascii_strdown (lang, -1); + if ((dot = strchr (lang, '.'))) + *dot = '\0'; + + get_encodings_for_lang (lang, &e1, &e2, &e3); + g_free (lang); + } + + converted = g_convert (str, len, "UTF-8", e1, NULL, NULL, NULL); + if (!converted && e2) + converted = g_convert (str, len, "UTF-8", e2, NULL, NULL, NULL); + + if (!converted && e3) + converted = g_convert (str, len, "UTF-8", e3, NULL, NULL, NULL); + + if (!converted) { + converted = g_convert_with_fallback (str, len, "UTF-8", e1, + "?", NULL, NULL, NULL); + } + + return converted; +} + +/* init, deinit for libnm_util */ + +static gboolean initialized = FALSE; + +/** + * nm_utils_init: + * @error: location to store error, or %NULL + * + * Initializes libnm-util; should be called when starting and program that + * uses libnm-util. Sets up an atexit() handler to ensure de-initialization + * is performed, but calling nm_utils_deinit() to explicitly deinitialize + * libnm-util can also be done. This function can be called more than once. + * + * Returns: TRUE if the initialization was successful, FALSE on failure. + **/ +gboolean +nm_utils_init (GError **error) +{ + if (!initialized) { + if (!crypto_init (error)) + return FALSE; + + _nm_utils_register_value_transformations (); + + atexit (nm_utils_deinit); + initialized = TRUE; + } + return TRUE; +} + +/** + * nm_utils_deinit: + * + * Frees all resources used internally by libnm-util. This function is called + * from an atexit() handler, set up by nm_utils_init(), but is safe to be called + * more than once. Subsequent calls have no effect until nm_utils_init() is + * called again. + **/ +void +nm_utils_deinit (void) +{ + if (initialized) { + crypto_deinit (); + initialized = FALSE; + } +} + +/* ssid helpers */ + +/** + * nm_utils_ssid_to_utf8: + * @ssid: pointer to a buffer containing the SSID data + * @len: length of the SSID data in @ssid + * + * WiFi SSIDs are byte arrays, they are _not_ strings. Thus, an SSID may + * contain embedded NULLs and other unprintable characters. Often it is + * useful to print the SSID out for debugging purposes, but that should be the + * _only_ use of this function. Do not use this function for any persistent + * storage of the SSID, since the printable SSID returned from this function + * cannot be converted back into the real SSID of the access point. + * + * This function does almost everything humanly possible to convert the input + * into a printable UTF-8 string, using roughly the following procedure: + * + * 1) if the input data is already UTF-8 safe, no conversion is performed + * 2) attempts to get the current system language from the LANG environment + * variable, and depending on the language, uses a table of alternative + * encodings to try. For example, if LANG=hu_HU, the table may first try + * the ISO-8859-2 encoding, and if that fails, try the Windows-1250 encoding. + * If all fallback encodings fail, replaces non-UTF-8 characters with '?'. + * 3) If the system language was unable to be determined, falls back to the + * ISO-8859-1 encoding, then to the Windows-1251 encoding. + * 4) If step 3 fails, replaces non-UTF-8 characters with '?'. + * + * Again, this function should be used for debugging and display purposes + * _only_. + * + * Returns: an allocated string containing a UTF-8 representation of the + * SSID, which must be freed by the caller using g_free(). Returns NULL + * on errors. + **/ +char * +nm_utils_ssid_to_utf8 (const char *ssid, guint32 len) +{ + char *converted = NULL, *buf; + gsize buflen = MIN (IW_ESSID_MAX_SIZE, (gsize) len); + + g_return_val_if_fail (ssid != NULL, NULL); + + /* New buffer to ensure NULL-termination of SSID */ + buf = g_malloc0 (IW_ESSID_MAX_SIZE + 1); + memcpy (buf, ssid, buflen); + converted = string_to_utf8 (buf, buflen); + g_free (buf); + return converted; +} + +/* Shamelessly ripped from the Linux kernel ieee80211 stack */ +/** + * nm_utils_is_empty_ssid: + * @ssid: pointer to a buffer containing the SSID data + * @len: length of the SSID data in @ssid + * + * Different manufacturers use different mechanisms for not broadcasting the + * AP's SSID. This function attempts to detect blank/empty SSIDs using a + * number of known SSID-cloaking methods. + * + * Returns: TRUE if the SSID is "empty", FALSE if it is not + **/ +gboolean +nm_utils_is_empty_ssid (const guint8 * ssid, int len) +{ + /* Single white space is for Linksys APs */ + if (len == 1 && ssid[0] == ' ') + return TRUE; + + /* Otherwise, if the entire ssid is 0, we assume it is hidden */ + while (len--) { + if (ssid[len] != '\0') + return FALSE; + } + return TRUE; +} + +/** + * nm_utils_escape_ssid: + * @ssid: pointer to a buffer containing the SSID data + * @len: length of the SSID data in @ssid + * + * This function does a quick printable character conversion of the SSID, simply + * replacing embedded NULLs and non-printable characters with the hexadecimal + * representation of that character. Intended for debugging only, should not + * be used for display of SSIDs. + * + * Returns: pointer to the escaped SSID, which uses an internal static buffer + * and will be overwritten by subsequent calls to this function + **/ +const char * +nm_utils_escape_ssid (const guint8 * ssid, guint32 len) +{ + static char escaped[IW_ESSID_MAX_SIZE * 2 + 1]; + const guint8 *s = ssid; + char *d = escaped; + + if (nm_utils_is_empty_ssid (ssid, len)) { + memcpy (escaped, "<hidden>", sizeof ("<hidden>")); + return escaped; + } + + len = MIN (len, (guint32) IW_ESSID_MAX_SIZE); + while (len--) { + if (*s == '\0') { + *d++ = '\\'; + *d++ = '0'; + s++; + } else { + *d++ = *s++; + } + } + *d = '\0'; + return escaped; +} + +/** + * nm_utils_same_ssid: + * @ssid1: first SSID data to compare + * @ssid2: second SSID data to compare + * @ignore_trailing_null: TRUE to ignore one trailing NULL byte + * + * Earlier versions of the Linux kernel added a NULL byte to the end of the + * SSID to enable easy printing of the SSID on the console or in a terminal, + * but this behavior was problematic (SSIDs are simply byte arrays, not strings) + * and thus was changed. This function compensates for that behavior at the + * cost of some compatibility with odd SSIDs that may legitimately have trailing + * NULLs, even though that is functionally pointless. + * + * Returns: TRUE if the SSIDs are the same, FALSE if they are not + **/ +gboolean +nm_utils_same_ssid (const GByteArray * ssid1, + const GByteArray * ssid2, + gboolean ignore_trailing_null) +{ + guint32 ssid1_len, ssid2_len; + + if (ssid1 == ssid2) + return TRUE; + if ((ssid1 && !ssid2) || (!ssid1 && ssid2)) + return FALSE; + + ssid1_len = ssid1->len; + ssid2_len = ssid2->len; + if (ssid1_len && ssid2_len && ignore_trailing_null) { + if (ssid1->data[ssid1_len - 1] == '\0') + ssid1_len--; + if (ssid2->data[ssid2_len - 1] == '\0') + ssid2_len--; + } + + if (ssid1_len != ssid2_len) + return FALSE; + + return memcmp (ssid1->data, ssid2->data, ssid1_len) == 0 ? TRUE : FALSE; +} + +static void +value_destroy (gpointer data) +{ + GValue *value = (GValue *) data; + + g_value_unset (value); + g_slice_free (GValue, value); +} + +static void +value_dup (gpointer key, gpointer val, gpointer user_data) +{ + GHashTable *table = (GHashTable *) user_data; + GValue *value = (GValue *) val; + GValue *dup_value; + + dup_value = g_slice_new0 (GValue); + g_value_init (dup_value, G_VALUE_TYPE (val)); + g_value_copy (value, dup_value); + + g_hash_table_insert (table, g_strdup ((char *) key), dup_value); +} + +/** + * nm_utils_gvalue_hash_dup: + * @hash: a #GHashTable mapping string:GValue + * + * Utility function to duplicate a hash table of GValues. + * + * Returns: a newly allocated duplicated #GHashTable, caller must free the + * returned hash with g_hash_table_unref() or g_hash_table_destroy() + **/ +GHashTable * +nm_utils_gvalue_hash_dup (GHashTable *hash) +{ + GHashTable *table; + + g_return_val_if_fail (hash != NULL, NULL); + + table = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + value_destroy); + + g_hash_table_foreach (hash, value_dup, table); + + return table; +} + +/** + * nm_utils_slist_free: + * @list: a #GSList + * @elem_destroy_fn: user function called for each element in @list + * + * Utility function to free a #GSList. + **/ +void +nm_utils_slist_free (GSList *list, GDestroyNotify elem_destroy_fn) +{ + if (!list) + return; + + if (elem_destroy_fn) + g_slist_foreach (list, (GFunc) elem_destroy_fn, NULL); + + g_slist_free (list); +} + +gboolean +_nm_utils_string_in_list (const char *str, const char **valid_strings) +{ + int i; + + for (i = 0; valid_strings[i]; i++) + if (strcmp (str, valid_strings[i]) == 0) + break; + + return valid_strings[i] != NULL; +} + +gboolean +_nm_utils_string_slist_validate (GSList *list, const char **valid_values) +{ + GSList *iter; + + for (iter = list; iter; iter = iter->next) { + if (!_nm_utils_string_in_list ((char *) iter->data, valid_values)) + return FALSE; + } + + return TRUE; +} + +static void +nm_utils_convert_strv_to_slist (const GValue *src_value, GValue *dest_value) +{ + char **str; + GSList *list = NULL; + guint i = 0; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), G_TYPE_STRV)); + + str = (char **) g_value_get_boxed (src_value); + + while (str && str[i]) + list = g_slist_prepend (list, g_strdup (str[i++])); + + g_value_take_boxed (dest_value, g_slist_reverse (list)); +} + +static void +nm_utils_convert_strv_to_string (const GValue *src_value, GValue *dest_value) +{ + GSList *strings; + GString *printable; + GSList *iter; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_LIST_OF_STRING)); + + strings = (GSList *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + for (iter = strings; iter; iter = g_slist_next (iter)) { + if (iter != strings) + g_string_append (printable, ", '"); + else + g_string_append_c (printable, '\''); + g_string_append (printable, iter->data); + g_string_append_c (printable, '\''); + } + g_string_append_c (printable, ']'); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +static void +nm_utils_convert_uint_array_to_string (const GValue *src_value, GValue *dest_value) +{ + GArray *array; + GString *printable; + guint i = 0; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_UINT_ARRAY)); + + array = (GArray *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + while (array && (i < array->len)) { + char buf[INET_ADDRSTRLEN + 1]; + struct in_addr addr; + + if (i > 0) + g_string_append (printable, ", "); + + memset (buf, 0, sizeof (buf)); + addr.s_addr = g_array_index (array, guint32, i++); + if (!inet_ntop (AF_INET, &addr, buf, INET_ADDRSTRLEN)) + nm_warning ("%s: error converting IP4 address 0x%X", + __func__, ntohl (addr.s_addr)); + g_string_append_printf (printable, "%u (%s)", addr.s_addr, buf); + } + g_string_append_c (printable, ']'); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +static void +nm_utils_convert_ip4_addr_route_struct_array_to_string (const GValue *src_value, GValue *dest_value) +{ + GPtrArray *ptr_array; + GString *printable; + guint i = 0; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT)); + + ptr_array = (GPtrArray *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + while (ptr_array && (i < ptr_array->len)) { + GArray *array; + char buf[INET_ADDRSTRLEN + 1]; + struct in_addr addr; + gboolean is_addr; /* array contains address x route */ + + if (i > 0) + g_string_append (printable, ", "); + + g_string_append (printable, "{ "); + array = (GArray *) g_ptr_array_index (ptr_array, i++); + if (array->len < 2) { + g_string_append (printable, "invalid"); + continue; + } + is_addr = (array->len < 4); + + memset (buf, 0, sizeof (buf)); + addr.s_addr = g_array_index (array, guint32, 0); + if (!inet_ntop (AF_INET, &addr, buf, INET_ADDRSTRLEN)) + nm_warning ("%s: error converting IP4 address 0x%X", + __func__, ntohl (addr.s_addr)); + if (is_addr) + g_string_append_printf (printable, "ip = %s", buf); + else + g_string_append_printf (printable, "dst = %s", buf); + g_string_append (printable, ", "); + + memset (buf, 0, sizeof (buf)); + g_string_append_printf (printable, "px = %u", + g_array_index (array, guint32, 1)); + + if (array->len > 2) { + g_string_append (printable, ", "); + + memset (buf, 0, sizeof (buf)); + addr.s_addr = g_array_index (array, guint32, 2); + if (!inet_ntop (AF_INET, &addr, buf, INET_ADDRSTRLEN)) + nm_warning ("%s: error converting IP4 address 0x%X", + __func__, ntohl (addr.s_addr)); + if (is_addr) + g_string_append_printf (printable, "gw = %s", buf); + else + g_string_append_printf (printable, "nh = %s", buf); + } + + if (array->len > 3) { + g_string_append (printable, ", "); + + memset (buf, 0, sizeof (buf)); + g_string_append_printf (printable, "mt = %u", + g_array_index (array, guint32, 3)); + } + + g_string_append (printable, " }"); + } + g_string_append_c (printable, ']'); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +static void +convert_one_gvalue_hash_entry (gpointer key, gpointer value, gpointer user_data) +{ + GString *printable = (GString *) user_data; + char *value_as_string; + + value_as_string = g_strdup_value_contents ((GValue *) value); + g_string_append_printf (printable, " { '%s': %s },", (const char *) key, value_as_string); + g_free (value_as_string); +} + +static void +nm_utils_convert_gvalue_hash_to_string (const GValue *src_value, GValue *dest_value) +{ + GHashTable *hash; + GString *printable; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_MAP_OF_VARIANT)); + + hash = (GHashTable *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + g_hash_table_foreach (hash, convert_one_gvalue_hash_entry, printable); + g_string_append (printable, " ]"); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +static void +convert_one_string_hash_entry (gpointer key, gpointer value, gpointer user_data) +{ + GString *printable = (GString *) user_data; + + g_string_append_printf (printable, " { '%s': %s },", (const char *) key, (const char *) value); +} + +static void +nm_utils_convert_string_hash_to_string (const GValue *src_value, GValue *dest_value) +{ + GHashTable *hash; + GString *printable; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_MAP_OF_STRING)); + + hash = (GHashTable *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + if (hash) + g_hash_table_foreach (hash, convert_one_string_hash_entry, printable); + g_string_append (printable, " ]"); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +static void +nm_utils_convert_byte_array_to_string (const GValue *src_value, GValue *dest_value) +{ + GArray *array; + GString *printable; + guint i = 0; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_UCHAR_ARRAY)); + + array = (GArray *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + if (array) { + while (i < MIN (array->len, 35)) { + if (i > 0) + g_string_append_c (printable, ' '); + g_string_append_printf (printable, "0x%02X", + g_array_index (array, unsigned char, i++)); + } + if (i < array->len) + g_string_append (printable, " ... "); + } + g_string_append_c (printable, ']'); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +static gboolean +nm_utils_inet6_ntop (struct in6_addr *addr, char *buf) +{ + if (!inet_ntop (AF_INET6, addr, buf, INET6_ADDRSTRLEN)) { + int i; + GString *ip6_str = g_string_new (NULL); + g_string_append_printf (ip6_str, "%02X", addr->s6_addr[0]); + for (i = 1; i < 16; i++) + g_string_append_printf (ip6_str, " %02X", addr->s6_addr[i]); + nm_warning ("%s: error converting IP6 address %s", + __func__, ip6_str->str); + g_string_free (ip6_str, TRUE); + return FALSE; + } + return TRUE; +} + +static void +nm_utils_convert_ip6_dns_array_to_string (const GValue *src_value, GValue *dest_value) +{ + GPtrArray *ptr_array; + GString *printable; + guint i = 0; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UCHAR)); + + ptr_array = (GPtrArray *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + while (ptr_array && (i < ptr_array->len)) { + GByteArray *bytearray; + char buf[INET6_ADDRSTRLEN]; + struct in6_addr *addr; + + if (i > 0) + g_string_append (printable, ", "); + + bytearray = (GByteArray *) g_ptr_array_index (ptr_array, i++); + if (bytearray->len != 16) { + g_string_append (printable, "invalid"); + continue; + } + addr = (struct in6_addr *) bytearray->data; + memset (buf, 0, sizeof (buf)); + nm_utils_inet6_ntop (addr, buf); + g_string_append_printf (printable, "%s", buf); + } + g_string_append_c (printable, ']'); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +static void +nm_utils_convert_ip6_addr_struct_array_to_string (const GValue *src_value, GValue *dest_value) +{ + GPtrArray *ptr_array; + GString *printable; + guint i = 0; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_ARRAY_OF_IP6_ADDRESS)); + + ptr_array = (GPtrArray *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + while (ptr_array && (i < ptr_array->len)) { + GValueArray *elements; + GValue *tmp; + GByteArray *ba_addr; + char buf[INET6_ADDRSTRLEN]; + struct in6_addr *addr; + guint32 prefix; + + if (i > 0) + g_string_append (printable, ", "); + + g_string_append (printable, "{ "); + elements = (GValueArray *) g_ptr_array_index (ptr_array, i++); + if ( (elements->n_values != 3) + || (G_VALUE_TYPE (g_value_array_get_nth (elements, 0)) != DBUS_TYPE_G_UCHAR_ARRAY) + || (G_VALUE_TYPE (g_value_array_get_nth (elements, 1)) != G_TYPE_UINT) + || (G_VALUE_TYPE (g_value_array_get_nth (elements, 2)) != DBUS_TYPE_G_UCHAR_ARRAY)) { + g_string_append (printable, "invalid }"); + continue; + } + + /* IPv6 address */ + tmp = g_value_array_get_nth (elements, 0); + ba_addr = g_value_get_boxed (tmp); + if (ba_addr->len != 16) { + g_string_append (printable, "invalid }"); + continue; + } + addr = (struct in6_addr *) ba_addr->data; + memset (buf, 0, sizeof (buf)); + nm_utils_inet6_ntop (addr, buf); + g_string_append_printf (printable, "ip = %s", buf); + g_string_append (printable, ", "); + + /* Prefix */ + tmp = g_value_array_get_nth (elements, 1); + prefix = g_value_get_uint (tmp); + if (prefix > 128) { + g_string_append (printable, "invalid }"); + continue; + } + g_string_append_printf (printable, "px = %u", prefix); + g_string_append (printable, ", "); + + /* IPv6 Gateway */ + tmp = g_value_array_get_nth (elements, 2); + ba_addr = g_value_get_boxed (tmp); + if (ba_addr->len != 16) { + g_string_append (printable, "invalid }"); + continue; + } + addr = (struct in6_addr *) ba_addr->data; + memset (buf, 0, sizeof (buf)); + nm_utils_inet6_ntop (addr, buf); + g_string_append_printf (printable, "gw = %s", buf); + g_string_append (printable, " }"); + } + g_string_append_c (printable, ']'); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +static void +nm_utils_convert_ip6_route_struct_array_to_string (const GValue *src_value, GValue *dest_value) +{ + GPtrArray *ptr_array; + GString *printable; + guint i = 0; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), DBUS_TYPE_G_ARRAY_OF_IP6_ROUTE)); + + ptr_array = (GPtrArray *) g_value_get_boxed (src_value); + + printable = g_string_new ("["); + while (ptr_array && (i < ptr_array->len)) { + GValueArray *elements; + GValue *tmp; + GByteArray *ba_addr; + char buf[INET6_ADDRSTRLEN]; + struct in6_addr *addr; + guint32 prefix, metric; + + if (i > 0) + g_string_append (printable, ", "); + + g_string_append (printable, "{ "); + elements = (GValueArray *) g_ptr_array_index (ptr_array, i++); + if ( (elements->n_values != 4) + || (G_VALUE_TYPE (g_value_array_get_nth (elements, 0)) != DBUS_TYPE_G_UCHAR_ARRAY) + || (G_VALUE_TYPE (g_value_array_get_nth (elements, 1)) != G_TYPE_UINT) + || (G_VALUE_TYPE (g_value_array_get_nth (elements, 2)) != DBUS_TYPE_G_UCHAR_ARRAY) + || (G_VALUE_TYPE (g_value_array_get_nth (elements, 3)) != G_TYPE_UINT)) { + g_string_append (printable, "invalid"); + continue; + } + + /* Destination address */ + tmp = g_value_array_get_nth (elements, 0); + ba_addr = g_value_get_boxed (tmp); + if (ba_addr->len != 16) { + g_string_append (printable, "invalid"); + continue; + } + addr = (struct in6_addr *) ba_addr->data; + memset (buf, 0, sizeof (buf)); + nm_utils_inet6_ntop (addr, buf); + g_string_append_printf (printable, "dst = %s", buf); + g_string_append (printable, ", "); + + /* Prefix */ + tmp = g_value_array_get_nth (elements, 1); + prefix = g_value_get_uint (tmp); + if (prefix > 128) { + g_string_append (printable, "invalid"); + continue; + } + g_string_append_printf (printable, "px = %u", prefix); + g_string_append (printable, ", "); + + /* Next hop addresses */ + tmp = g_value_array_get_nth (elements, 2); + ba_addr = g_value_get_boxed (tmp); + if (ba_addr->len != 16) { + g_string_append (printable, "invalid"); + continue; + } + addr = (struct in6_addr *) ba_addr->data; + memset (buf, 0, sizeof (buf)); + nm_utils_inet6_ntop (addr, buf); + g_string_append_printf (printable, "nh = %s", buf); + g_string_append (printable, ", "); + + /* Metric */ + tmp = g_value_array_get_nth (elements, 3); + metric = g_value_get_uint (tmp); + g_string_append_printf (printable, "mt = %u", metric); + + g_string_append (printable, " }"); + } + g_string_append_c (printable, ']'); + + g_value_take_string (dest_value, printable->str); + g_string_free (printable, FALSE); +} + +#define OLD_DBUS_TYPE_G_IP6_ADDRESS (dbus_g_type_get_struct ("GValueArray", DBUS_TYPE_G_UCHAR_ARRAY, G_TYPE_UINT, G_TYPE_INVALID)) +#define OLD_DBUS_TYPE_G_ARRAY_OF_IP6_ADDRESS (dbus_g_type_get_collection ("GPtrArray", OLD_DBUS_TYPE_G_IP6_ADDRESS)) + +static void +nm_utils_convert_old_ip6_addr_array (const GValue *src_value, GValue *dst_value) +{ + GPtrArray *src_outer_array; + GPtrArray *dst_outer_array; + guint i; + + g_return_if_fail (g_type_is_a (G_VALUE_TYPE (src_value), OLD_DBUS_TYPE_G_ARRAY_OF_IP6_ADDRESS)); + + src_outer_array = (GPtrArray *) g_value_get_boxed (src_value); + dst_outer_array = g_ptr_array_new (); + + for (i = 0; src_outer_array && (i < src_outer_array->len); i++) { + GValueArray *src_addr_array; + GValueArray *dst_addr_array; + GValue element = {0, }; + GValue *src_addr, *src_prefix; + GByteArray *ba; + + src_addr_array = (GValueArray *) g_ptr_array_index (src_outer_array, i); + + if ( (src_addr_array->n_values != 2) + || (G_VALUE_TYPE (g_value_array_get_nth (src_addr_array, 0)) != DBUS_TYPE_G_UCHAR_ARRAY) + || (G_VALUE_TYPE (g_value_array_get_nth (src_addr_array, 1)) != G_TYPE_UINT)) { + g_warning ("%s: invalid old IPv6 address type", __func__); + return; + } + + dst_addr_array = g_value_array_new (3); + + src_addr = g_value_array_get_nth (src_addr_array, 0); + g_value_array_append (dst_addr_array, src_addr); + src_prefix = g_value_array_get_nth (src_addr_array, 1); + g_value_array_append (dst_addr_array, src_prefix); + + /* Blank Gateway */ + g_value_init (&element, DBUS_TYPE_G_UCHAR_ARRAY); + ba = g_byte_array_new (); + g_byte_array_append (ba, (guint8 *) "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16); + g_value_take_boxed (&element, ba); + g_value_array_append (dst_addr_array, &element); + g_value_unset (&element); + + g_ptr_array_add (dst_outer_array, dst_addr_array); + } + + g_value_take_boxed (dst_value, dst_outer_array); +} + +void +_nm_utils_register_value_transformations (void) +{ + static gboolean registered = FALSE; + + if (G_UNLIKELY (!registered)) { + g_value_register_transform_func (G_TYPE_STRV, + DBUS_TYPE_G_LIST_OF_STRING, + nm_utils_convert_strv_to_slist); + g_value_register_transform_func (DBUS_TYPE_G_LIST_OF_STRING, + G_TYPE_STRING, + nm_utils_convert_strv_to_string); + g_value_register_transform_func (DBUS_TYPE_G_UINT_ARRAY, + G_TYPE_STRING, + nm_utils_convert_uint_array_to_string); + g_value_register_transform_func (DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT, + G_TYPE_STRING, + nm_utils_convert_ip4_addr_route_struct_array_to_string); + g_value_register_transform_func (DBUS_TYPE_G_MAP_OF_VARIANT, + G_TYPE_STRING, + nm_utils_convert_gvalue_hash_to_string); + g_value_register_transform_func (DBUS_TYPE_G_MAP_OF_STRING, + G_TYPE_STRING, + nm_utils_convert_string_hash_to_string); + g_value_register_transform_func (DBUS_TYPE_G_UCHAR_ARRAY, + G_TYPE_STRING, + nm_utils_convert_byte_array_to_string); + g_value_register_transform_func (DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UCHAR, + G_TYPE_STRING, + nm_utils_convert_ip6_dns_array_to_string); + g_value_register_transform_func (DBUS_TYPE_G_ARRAY_OF_IP6_ADDRESS, + G_TYPE_STRING, + nm_utils_convert_ip6_addr_struct_array_to_string); + g_value_register_transform_func (DBUS_TYPE_G_ARRAY_OF_IP6_ROUTE, + G_TYPE_STRING, + nm_utils_convert_ip6_route_struct_array_to_string); + g_value_register_transform_func (OLD_DBUS_TYPE_G_ARRAY_OF_IP6_ADDRESS, + DBUS_TYPE_G_ARRAY_OF_IP6_ADDRESS, + nm_utils_convert_old_ip6_addr_array); + registered = TRUE; + } +} + +static gboolean +device_supports_ap_ciphers (guint32 dev_caps, + guint32 ap_flags, + gboolean static_wep) +{ + gboolean have_pair = FALSE; + gboolean have_group = FALSE; + /* Device needs to support at least one pairwise and one group cipher */ + + /* Pairwise */ + if (static_wep) { + /* Static WEP only uses group ciphers */ + have_pair = TRUE; + } else { + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_WEP40) + if (ap_flags & NM_802_11_AP_SEC_PAIR_WEP40) + have_pair = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_WEP104) + if (ap_flags & NM_802_11_AP_SEC_PAIR_WEP104) + have_pair = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP) + if (ap_flags & NM_802_11_AP_SEC_PAIR_TKIP) + have_pair = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP) + if (ap_flags & NM_802_11_AP_SEC_PAIR_CCMP) + have_pair = TRUE; + } + + /* Group */ + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_WEP40) + if (ap_flags & NM_802_11_AP_SEC_GROUP_WEP40) + have_group = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_WEP104) + if (ap_flags & NM_802_11_AP_SEC_GROUP_WEP104) + have_group = TRUE; + if (!static_wep) { + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP) + if (ap_flags & NM_802_11_AP_SEC_GROUP_TKIP) + have_group = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP) + if (ap_flags & NM_802_11_AP_SEC_GROUP_CCMP) + have_group = TRUE; + } + + return (have_pair && have_group); +} + +/** + * nm_utils_security_valid: + * @type: the security type to check AP flags and device capabilties against, + * e.g. #NMU_SEC_STATIC_WEP + * @wifi_caps: bitfield of the capabilities of the specific WiFi device, e.g. + * #NM_WIFI_DEVICE_CAP_CIPHER_WEP40 + * @have_ap: whether the @ap_flags, @ap_wpa, and @ap_rsn arguments are valid + * @adhoc: whether the capabilities being tested are from an Ad-Hoc AP (IBSS) + * @ap_flags: bitfield of AP capabilities, e.g. #NM_802_11_AP_FLAGS_PRIVACY + * @ap_wpa: bitfield of AP capabilties derived from the AP's WPA beacon, + * e.g. (#NM_802_11_AP_SEC_PAIR_TKIP | #NM_802_11_AP_SEC_KEY_MGMT_PSK) + * @ap_rsn: bitfield of AP capabilties derived from the AP's RSN/WPA2 beacon, + * e.g. (#NM_802_11_AP_SEC_PAIR_CCMP | #NM_802_11_AP_SEC_PAIR_TKIP) + * + * Given a set of device capabilities, and a desired security type to check + * against, determines whether the combination of device, desired security + * type, and AP capabilities intersect. + * + * Returns: TRUE if the device capabilities and AP capabilties intersect and are + * compatible with the desired @type, FALSE if they are not + **/ +gboolean +nm_utils_security_valid (NMUtilsSecurityType type, + guint32 wifi_caps, + gboolean have_ap, + gboolean adhoc, + guint32 ap_flags, + guint32 ap_wpa, + guint32 ap_rsn) +{ + gboolean good = TRUE; + + if (!have_ap) { + if (type == NMU_SEC_NONE) + return TRUE; + if ( (type == NMU_SEC_STATIC_WEP) + || ((type == NMU_SEC_DYNAMIC_WEP) && !adhoc) + || ((type == NMU_SEC_LEAP) && !adhoc)) { + if (wifi_caps & (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104)) + return TRUE; + } + } + + switch (type) { + case NMU_SEC_NONE: + g_assert (have_ap); + if (ap_flags & NM_802_11_AP_FLAGS_PRIVACY) + return FALSE; + if (ap_wpa || ap_rsn) + return FALSE; + break; + case NMU_SEC_LEAP: /* require PRIVACY bit for LEAP? */ + if (adhoc) + return FALSE; + /* Fall through */ + case NMU_SEC_STATIC_WEP: + g_assert (have_ap); + if (!(ap_flags & NM_802_11_AP_FLAGS_PRIVACY)) + return FALSE; + if (ap_wpa || ap_rsn) { + if (!device_supports_ap_ciphers (wifi_caps, ap_wpa, TRUE)) + if (!device_supports_ap_ciphers (wifi_caps, ap_rsn, TRUE)) + return FALSE; + } + break; + case NMU_SEC_DYNAMIC_WEP: + if (adhoc) + return FALSE; + g_assert (have_ap); + if (ap_rsn || !(ap_flags & NM_802_11_AP_FLAGS_PRIVACY)) + return FALSE; + /* Some APs broadcast minimal WPA-enabled beacons that must be handled */ + if (ap_wpa) { + if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return FALSE; + if (!device_supports_ap_ciphers (wifi_caps, ap_wpa, FALSE)) + return FALSE; + } + break; + case NMU_SEC_WPA_PSK: + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_WPA)) + return FALSE; + if (have_ap) { + /* Ad-Hoc WPA APs won't necessarily have the PSK flag set */ + if ((ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_PSK) || adhoc) { + if ( (ap_wpa & NM_802_11_AP_SEC_PAIR_TKIP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP)) + return TRUE; + if ( (ap_wpa & NM_802_11_AP_SEC_PAIR_CCMP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + return TRUE; + } + return FALSE; + } + break; + case NMU_SEC_WPA2_PSK: + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN)) + return FALSE; + if (have_ap) { + /* Ad-Hoc WPA APs won't necessarily have the PSK flag set */ + if ((ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_PSK) || adhoc) { + if ( (ap_rsn & NM_802_11_AP_SEC_PAIR_TKIP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP)) + return TRUE; + if ( (ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + return TRUE; + } + return FALSE; + } + break; + case NMU_SEC_WPA_ENTERPRISE: + if (adhoc) + return FALSE; + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_WPA)) + return FALSE; + if (have_ap) { + if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return FALSE; + /* Ensure at least one WPA cipher is supported */ + if (!device_supports_ap_ciphers (wifi_caps, ap_wpa, FALSE)) + return FALSE; + } + break; + case NMU_SEC_WPA2_ENTERPRISE: + if (adhoc) + return FALSE; + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN)) + return FALSE; + if (have_ap) { + if (!(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return FALSE; + /* Ensure at least one WPA cipher is supported */ + if (!device_supports_ap_ciphers (wifi_caps, ap_rsn, FALSE)) + return FALSE; + } + break; + default: + good = FALSE; + break; + } + + return good; +} + +/** + * nm_utils_ip4_addresses_from_gvalue: + * @value: gvalue containing a GPtrArray of GArrays of guint32s + * + * Utility function to convert a #GPtrArray of #GArrays of guint32s representing + * a list of NetworkManager IPv4 addresses (which is a tuple of address, gateway, + * and prefix) into a GSList of #NMIP4Address objects. The specific format of + * this serialization is not guaranteed to be stable and the #GArray may be + * extended in the future. + * + * Returns: a newly allocated #GSList of #NMIP4Address objects + **/ +GSList * +nm_utils_ip4_addresses_from_gvalue (const GValue *value) +{ + GPtrArray *addresses; + int i; + GSList *list = NULL; + + addresses = (GPtrArray *) g_value_get_boxed (value); + for (i = 0; addresses && (i < addresses->len); i++) { + GArray *array = (GArray *) g_ptr_array_index (addresses, i); + NMIP4Address *addr; + + if (array->len < 3) { + nm_warning ("Ignoring invalid IP4 address"); + continue; + } + + addr = nm_ip4_address_new (); + nm_ip4_address_set_address (addr, g_array_index (array, guint32, 0)); + nm_ip4_address_set_prefix (addr, g_array_index (array, guint32, 1)); + nm_ip4_address_set_gateway (addr, g_array_index (array, guint32, 2)); + list = g_slist_prepend (list, addr); + } + + return g_slist_reverse (list); +} + +/** + * nm_utils_ip4_addresses_to_gvalue: + * @list: a list of #NMIP4Address objects + * @value: a pointer to a #GValue into which to place the converted addresses, + * which should be unset by the caller (when no longer needed) with + * g_value_unset(). + * + * Utility function to convert a #GSList of #NMIP4Address objects into a + * GPtrArray of GArrays of guint32s representing a list of NetworkManager IPv4 + * addresses (which is a tuple of address, gateway, and prefix). The specific + * format of this serialization is not guaranteed to be stable and may be + * extended in the future. + **/ +void +nm_utils_ip4_addresses_to_gvalue (GSList *list, GValue *value) +{ + GPtrArray *addresses; + GSList *iter; + + addresses = g_ptr_array_new (); + + for (iter = list; iter; iter = iter->next) { + NMIP4Address *addr = (NMIP4Address *) iter->data; + GArray *array; + guint32 tmp; + + array = g_array_sized_new (FALSE, TRUE, sizeof (guint32), 3); + + tmp = nm_ip4_address_get_address (addr); + g_array_append_val (array, tmp); + + tmp = nm_ip4_address_get_prefix (addr); + g_array_append_val (array, tmp); + + tmp = nm_ip4_address_get_gateway (addr); + g_array_append_val (array, tmp); + + g_ptr_array_add (addresses, array); + } + + g_value_take_boxed (value, addresses); +} + +/** + * nm_utils_ip4_routes_from_gvalue: + * @value: gvalue containing a GPtrArray of GArrays of guint32s + * + * Utility function to convert a GPtrArray of GArrays of guint32s representing + * a list of NetworkManager IPv4 routes (which is a tuple of route, next hop, + * prefix, and metric) into a GSList of #NMIP4Route objects. The specific + * format of this serialization is not guaranteed to be stable and may be + * extended in the future. + * + * Returns: a newly allocated #GSList of #NMIP4Route objects + **/ +GSList * +nm_utils_ip4_routes_from_gvalue (const GValue *value) +{ + GPtrArray *routes; + int i; + GSList *list = NULL; + + routes = (GPtrArray *) g_value_get_boxed (value); + for (i = 0; routes && (i < routes->len); i++) { + GArray *array = (GArray *) g_ptr_array_index (routes, i); + NMIP4Route *route; + + if (array->len < 4) { + nm_warning ("Ignoring invalid IP4 route"); + continue; + } + + route = nm_ip4_route_new (); + nm_ip4_route_set_dest (route, g_array_index (array, guint32, 0)); + nm_ip4_route_set_prefix (route, g_array_index (array, guint32, 1)); + nm_ip4_route_set_next_hop (route, g_array_index (array, guint32, 2)); + nm_ip4_route_set_metric (route, g_array_index (array, guint32, 3)); + list = g_slist_prepend (list, route); + } + + return g_slist_reverse (list); +} + +/** + * nm_utils_ip4_routes_to_gvalue: + * @list: a list of #NMIP4Route objects + * @value: a pointer to a #GValue into which to place the converted routes, + * which should be unset by the caller (when no longer needed) with + * g_value_unset(). + * + * Utility function to convert a #GSList of #NMIP4Route objects into a + * GPtrArray of GArrays of guint32s representing a list of NetworkManager IPv4 + * routes (which is a tuple of route, next hop, prefix, and metric). The + * specific format of this serialization is not guaranteed to be stable and may + * be extended in the future. + **/ +void +nm_utils_ip4_routes_to_gvalue (GSList *list, GValue *value) +{ + GPtrArray *routes; + GSList *iter; + + routes = g_ptr_array_new (); + + for (iter = list; iter; iter = iter->next) { + NMIP4Route *route = (NMIP4Route *) iter->data; + GArray *array; + guint32 tmp; + + array = g_array_sized_new (FALSE, TRUE, sizeof (guint32), 3); + + tmp = nm_ip4_route_get_dest (route); + g_array_append_val (array, tmp); + + tmp = nm_ip4_route_get_prefix (route); + g_array_append_val (array, tmp); + + tmp = nm_ip4_route_get_next_hop (route); + g_array_append_val (array, tmp); + + tmp = nm_ip4_route_get_metric (route); + g_array_append_val (array, tmp); + + g_ptr_array_add (routes, array); + } + + g_value_take_boxed (value, routes); +} + +/** + * nm_utils_ip4_netmask_to_prefix: + * @netmask: an IPv4 netmask in network byte order + * + * Returns: the CIDR prefix represented by the netmask + **/ +guint32 +nm_utils_ip4_netmask_to_prefix (guint32 netmask) +{ + guchar *p, *end; + guint32 prefix = 0; + + p = (guchar *) &netmask; + end = p + sizeof (guint32); + + while ((*p == 0xFF) && p < end) { + prefix += 8; + p++; + } + + if (p < end) { + guchar v = *p; + + while (v) { + prefix++; + v <<= 1; + } + } + + return prefix; +} + +/** + * nm_utils_ip4_prefix_to_netmask: + * @prefix: a CIDR prefix + * + * Returns: the netmask represented by the prefix + **/ +guint32 +nm_utils_ip4_prefix_to_netmask (guint32 prefix) +{ + guint32 msk = 0x80000000; + guint32 netmask = 0; + + while (prefix > 0) { + netmask |= msk; + msk >>= 1; + prefix--; + } + + return (guint32) htonl (netmask); +} + + +/** + * nm_utils_ip4_get_default_prefix: + * @ip: an IPv4 address (in network byte order) + * + * When the Internet was originally set up, various ranges of IP addresses were + * segmented into three network classes: A, B, and C. This function will return + * a prefix that is associated with the IP address specified defining where it + * falls in the predefined classes. + * + * Returns: the default class prefix for the given IP + **/ +/* The function is originally from ipcalc.c of Red Hat's initscripts. */ +guint32 +nm_utils_ip4_get_default_prefix (guint32 ip) +{ + if (((ntohl (ip) & 0xFF000000) >> 24) <= 127) + return 8; /* Class A - 255.0.0.0 */ + else if (((ntohl (ip) & 0xFF000000) >> 24) <= 191) + return 16; /* Class B - 255.255.0.0 */ + + return 24; /* Class C - 255.255.255.0 */ +} + +/** + * nm_utils_ip6_addresses_from_gvalue: + * @value: gvalue containing a GPtrArray of GValueArrays of (GArray of guchars) and guint32 + * + * Utility function to convert a #GPtrArray of #GValueArrays of (#GArray of guchars) and guint32 + * representing a list of NetworkManager IPv6 addresses (which is a tuple of address, + * prefix, and gateway), into a GSList of #NMIP6Address objects. The specific format of + * this serialization is not guaranteed to be stable and the #GValueArray may be + * extended in the future. + * + * Returns: a newly allocated #GSList of #NMIP6Address objects + **/ +GSList * +nm_utils_ip6_addresses_from_gvalue (const GValue *value) +{ + GPtrArray *addresses; + int i; + GSList *list = NULL; + + addresses = (GPtrArray *) g_value_get_boxed (value); + + for (i = 0; addresses && (i < addresses->len); i++) { + GValueArray *elements = (GValueArray *) g_ptr_array_index (addresses, i); + GValue *tmp; + GByteArray *ba_addr; + GByteArray *ba_gw = NULL; + NMIP6Address *addr; + guint32 prefix; + + if (elements->n_values < 2 || elements->n_values > 3) { + nm_warning ("%s: ignoring invalid IP6 address structure", __func__); + continue; + } + + if ( (G_VALUE_TYPE (g_value_array_get_nth (elements, 0)) != DBUS_TYPE_G_UCHAR_ARRAY) + || (G_VALUE_TYPE (g_value_array_get_nth (elements, 1)) != G_TYPE_UINT)) { + nm_warning ("%s: ignoring invalid IP6 address structure", __func__); + continue; + } + + /* Check optional 3rd element (gateway) */ + if ( elements->n_values == 3 + && (G_VALUE_TYPE (g_value_array_get_nth (elements, 2)) != DBUS_TYPE_G_UCHAR_ARRAY)) { + nm_warning ("%s: ignoring invalid IP6 address structure", __func__); + continue; + } + + tmp = g_value_array_get_nth (elements, 0); + ba_addr = g_value_get_boxed (tmp); + if (ba_addr->len != 16) { + nm_warning ("%s: ignoring invalid IP6 address of length %d", + __func__, ba_addr->len); + continue; + } + + tmp = g_value_array_get_nth (elements, 1); + prefix = g_value_get_uint (tmp); + if (prefix > 128) { + nm_warning ("%s: ignoring invalid IP6 prefix %d", + __func__, prefix); + continue; + } + + if (elements->n_values == 3) { + tmp = g_value_array_get_nth (elements, 2); + ba_gw = g_value_get_boxed (tmp); + if (ba_gw->len != 16) { + nm_warning ("%s: ignoring invalid IP6 gateway address of length %d", + __func__, ba_gw->len); + continue; + } + } + + addr = nm_ip6_address_new (); + nm_ip6_address_set_prefix (addr, prefix); + nm_ip6_address_set_address (addr, (const struct in6_addr *) ba_addr->data); + if (ba_gw) + nm_ip6_address_set_gateway (addr, (const struct in6_addr *) ba_gw->data); + + list = g_slist_prepend (list, addr); + } + + return g_slist_reverse (list); +} + +/** + * nm_utils_ip6_addresses_to_gvalue: + * @list: a list of #NMIP6Address objects + * @value: a pointer to a #GValue into which to place the converted addresses, + * which should be unset by the caller (when no longer needed) with + * g_value_unset(). + * + * Utility function to convert a #GSList of #NMIP6Address objects into a + * GPtrArray of GValueArrays representing a list of NetworkManager IPv6 addresses + * (which is a tuple of address, prefix, and gateway). The specific format of + * this serialization is not guaranteed to be stable and may be extended in the + * future. + **/ +void +nm_utils_ip6_addresses_to_gvalue (GSList *list, GValue *value) +{ + GPtrArray *addresses; + GSList *iter; + + addresses = g_ptr_array_new (); + + for (iter = list; iter; iter = iter->next) { + NMIP6Address *addr = (NMIP6Address *) iter->data; + GValueArray *array; + GValue element = {0, }; + GByteArray *ba; + + array = g_value_array_new (3); + + /* IP address */ + g_value_init (&element, DBUS_TYPE_G_UCHAR_ARRAY); + ba = g_byte_array_new (); + g_byte_array_append (ba, (guint8 *) nm_ip6_address_get_address (addr), 16); + g_value_take_boxed (&element, ba); + g_value_array_append (array, &element); + g_value_unset (&element); + + /* Prefix */ + g_value_init (&element, G_TYPE_UINT); + g_value_set_uint (&element, nm_ip6_address_get_prefix (addr)); + g_value_array_append (array, &element); + g_value_unset (&element); + + /* Gateway */ + g_value_init (&element, DBUS_TYPE_G_UCHAR_ARRAY); + ba = g_byte_array_new (); + g_byte_array_append (ba, (guint8 *) nm_ip6_address_get_gateway (addr), 16); + g_value_take_boxed (&element, ba); + g_value_array_append (array, &element); + g_value_unset (&element); + + g_ptr_array_add (addresses, array); + } + + g_value_take_boxed (value, addresses); +} + +/** + * nm_utils_ip6_routes_from_gvalue: + * @value: gvalue containing a GPtrArray of GValueArrays of (GArray or guchars), guint32, + * (GArray of guchars), and guint32 + * + * Utility function GPtrArray of GValueArrays of (GArray or guchars), guint32, + * (GArray of guchars), and guint32 representing a list of NetworkManager IPv6 + * routes (which is a tuple of destination, prefix, next hop, and metric) + * into a GSList of #NMIP6Route objects. The specific format of this serialization + * is not guaranteed to be stable and may be extended in the future. + * + * Returns: a newly allocated #GSList of #NMIP6Route objects + **/ +GSList * +nm_utils_ip6_routes_from_gvalue (const GValue *value) +{ + GPtrArray *routes; + int i; + GSList *list = NULL; + + routes = (GPtrArray *) g_value_get_boxed (value); + for (i = 0; routes && (i < routes->len); i++) { + GValueArray *route_values = (GValueArray *) g_ptr_array_index (routes, i); + GByteArray *dest, *next_hop; + guint prefix, metric; + NMIP6Route *route; + + if ( (route_values->n_values != 4) + || (G_VALUE_TYPE (g_value_array_get_nth (route_values, 0)) != DBUS_TYPE_G_UCHAR_ARRAY) + || (G_VALUE_TYPE (g_value_array_get_nth (route_values, 1)) != G_TYPE_UINT) + || (G_VALUE_TYPE (g_value_array_get_nth (route_values, 2)) != DBUS_TYPE_G_UCHAR_ARRAY) + || (G_VALUE_TYPE (g_value_array_get_nth (route_values, 3)) != G_TYPE_UINT)) { + nm_warning ("Ignoring invalid IP6 route"); + continue; + } + + dest = g_value_get_boxed (g_value_array_get_nth (route_values, 0)); + if (dest->len != 16) { + nm_warning ("%s: ignoring invalid IP6 dest address of length %d", + __func__, dest->len); + continue; + } + + prefix = g_value_get_uint (g_value_array_get_nth (route_values, 1)); + + next_hop = g_value_get_boxed (g_value_array_get_nth (route_values, 2)); + if (next_hop->len != 16) { + nm_warning ("%s: ignoring invalid IP6 next_hop address of length %d", + __func__, next_hop->len); + continue; + } + + metric = g_value_get_uint (g_value_array_get_nth (route_values, 3)); + + route = nm_ip6_route_new (); + nm_ip6_route_set_dest (route, (struct in6_addr *)dest->data); + nm_ip6_route_set_prefix (route, prefix); + nm_ip6_route_set_next_hop (route, (struct in6_addr *)next_hop->data); + nm_ip6_route_set_metric (route, metric); + list = g_slist_prepend (list, route); + } + + return g_slist_reverse (list); +} + +/** + * nm_utils_ip6_routes_to_gvalue: + * @list: a list of #NMIP6Route objects + * @value: a pointer to a #GValue into which to place the converted routes, + * which should be unset by the caller (when no longer needed) with + * g_value_unset(). + * + * Utility function to convert a #GSList of #NMIP6Route objects into a GPtrArray of + * GValueArrays of (GArray or guchars), guint32, (GArray of guchars), and guint32 + * representing a list of NetworkManager IPv6 routes (which is a tuple of destination, + * prefix, next hop, and metric). The specific format of this serialization is not + * guaranteed to be stable and may be extended in the future. + **/ +void +nm_utils_ip6_routes_to_gvalue (GSList *list, GValue *value) +{ + GPtrArray *routes; + GSList *iter; + + routes = g_ptr_array_new (); + + for (iter = list; iter; iter = iter->next) { + NMIP6Route *route = (NMIP6Route *) iter->data; + GValueArray *array; + const struct in6_addr *addr; + GByteArray *ba; + GValue element = {0, }; + + array = g_value_array_new (4); + + g_value_init (&element, DBUS_TYPE_G_UCHAR_ARRAY); + addr = nm_ip6_route_get_dest (route); + ba = g_byte_array_new (); + g_byte_array_append (ba, (guchar *)addr, sizeof (*addr)); + g_value_take_boxed (&element, ba); + g_value_array_append (array, &element); + g_value_unset (&element); + + g_value_init (&element, G_TYPE_UINT); + g_value_set_uint (&element, nm_ip6_route_get_prefix (route)); + g_value_array_append (array, &element); + g_value_unset (&element); + + g_value_init (&element, DBUS_TYPE_G_UCHAR_ARRAY); + addr = nm_ip6_route_get_next_hop (route); + ba = g_byte_array_new (); + g_byte_array_append (ba, (guchar *)addr, sizeof (*addr)); + g_value_take_boxed (&element, ba); + g_value_array_append (array, &element); + g_value_unset (&element); + + g_value_init (&element, G_TYPE_UINT); + g_value_set_uint (&element, nm_ip6_route_get_metric (route)); + g_value_array_append (array, &element); + g_value_unset (&element); + + g_ptr_array_add (routes, array); + } + + g_value_take_boxed (value, routes); +} + +GSList * +nm_utils_ip6_dns_from_gvalue (const GValue *value) +{ + GPtrArray *dns; + int i; + GSList *list = NULL; + + dns = (GPtrArray *) g_value_get_boxed (value); + for (i = 0; dns && (i < dns->len); i++) { + GByteArray *bytearray = (GByteArray *) g_ptr_array_index (dns, i); + struct in6_addr *addr; + + if (bytearray->len != 16) { + nm_warning ("%s: ignoring invalid IP6 address of length %d", + __func__, bytearray->len); + continue; + } + + addr = g_malloc0 (sizeof (struct in6_addr)); + memcpy (addr->s6_addr, bytearray->data, bytearray->len); + list = g_slist_prepend (list, addr); + } + + return g_slist_reverse (list); +} + +void +nm_utils_ip6_dns_to_gvalue (GSList *list, GValue *value) +{ + GPtrArray *dns; + GSList *iter; + + dns = g_ptr_array_new (); + + for (iter = list; iter; iter = iter->next) { + struct in6_addr *addr = (struct in6_addr *) iter->data; + GByteArray *bytearray; + + bytearray = g_byte_array_sized_new (16); + g_byte_array_append (bytearray, (guint8 *) addr->s6_addr, 16); + g_ptr_array_add (dns, bytearray); + } + + g_value_take_boxed (value, dns); +} + +/** + * nm_utils_uuid_generate: + * + * Returns: a newly allocated UUID suitable for use as the #NMSettingConnection + * object's #NMSettingConnection:id: property. Should be freed with g_free() + **/ +char * +nm_utils_uuid_generate (void) +{ + uuid_t uuid; + char *buf; + + buf = g_malloc0 (37); + uuid_generate_random (uuid); + uuid_unparse_lower (uuid, &buf[0]); + return buf; +} + +/** + * nm_utils_uuid_generate_from_string: + * @s: a string to use as the seed for the UUID + * + * For a given @s, this function will always return the same UUID. + * + * Returns: a newly allocated UUID suitable for use as the #NMSettingConnection + * object's #NMSettingConnection:id: property + **/ +char * +nm_utils_uuid_generate_from_string (const char *s) +{ + GError *error = NULL; + uuid_t *uuid; + char *buf = NULL; + + if (!nm_utils_init (&error)) { + nm_warning ("error initializing crypto: (%d) %s", + error ? error->code : 0, + error ? error->message : "unknown"); + if (error) + g_error_free (error); + return NULL; + } + + uuid = g_malloc0 (sizeof (*uuid)); + if (!crypto_md5_hash (NULL, 0, s, strlen (s), (char *) uuid, sizeof (*uuid), &error)) { + nm_warning ("error generating UUID: (%d) %s", + error ? error->code : 0, + error ? error->message : "unknown"); + if (error) + g_error_free (error); + goto out; + } + + buf = g_malloc0 (37); + uuid_unparse_lower (*uuid, &buf[0]); + +out: + g_free (uuid); + return buf; +} + +static char * +make_key (const char *salt, + const gsize salt_len, + const char *password, + gsize *out_len, + GError **error) +{ + char *key; + guint32 digest_len = 24; /* DES-EDE3-CBC */ + + g_return_val_if_fail (salt != NULL, NULL); + g_return_val_if_fail (salt_len >= 8, NULL); + g_return_val_if_fail (password != NULL, NULL); + g_return_val_if_fail (out_len != NULL, NULL); + + key = g_malloc0 (digest_len + 1); + if (!key) { + g_set_error (error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Not enough memory to make encryption key.")); + return NULL; + } + + if (!crypto_md5_hash (salt, salt_len, password, strlen (password), key, digest_len, error)) { + *out_len = 0; + memset (key, 0, digest_len); + g_free (key); + key = NULL; + } else + *out_len = digest_len; + + return key; +} + +/* + * utils_bin2hexstr + * + * Convert a byte-array into a hexadecimal string. + * + * Code originally by Alex Larsson <alexl@redhat.com> and + * copyright Red Hat, Inc. under terms of the LGPL. + * + */ +static char * +utils_bin2hexstr (const char *bytes, int len, int final_len) +{ + static char hex_digits[] = "0123456789abcdef"; + char *result; + int i; + gsize buflen = (len * 2) + 1; + + g_return_val_if_fail (bytes != NULL, NULL); + g_return_val_if_fail (len > 0, NULL); + g_return_val_if_fail (len < 4096, NULL); /* Arbitrary limit */ + if (final_len > -1) + g_return_val_if_fail (final_len < buflen, NULL); + + result = g_malloc0 (buflen); + for (i = 0; i < len; i++) + { + result[2*i] = hex_digits[(bytes[i] >> 4) & 0xf]; + result[2*i+1] = hex_digits[bytes[i] & 0xf]; + } + /* Cut converted key off at the correct length for this cipher type */ + if (final_len > -1) + result[final_len] = '\0'; + else + result[buflen - 1] = '\0'; + + return result; +} + +/** + * nm_utils_rsa_key_encrypt: + * @data: RSA private key data to be encrypted + * @in_password: existing password to use, if any + * @out_password: if @in_password was NULL, a random password will be generated + * and returned in this argument + * @error: detailed error information on return, if an error occurred + * + * Encrypts the given RSA private key data with the given password (or generates + * a password if no password was given) and converts the data to PEM format + * suitable for writing to a file. + * + * Returns: on success, PEM-formatted data suitable for writing to a PEM-formatted + * certificate/private key file. + **/ +GByteArray * +nm_utils_rsa_key_encrypt (const GByteArray *data, + const char *in_password, + char **out_password, + GError **error) +{ + char salt[8]; + char *key = NULL, *enc = NULL, *pw_buf[32]; + gsize key_len = 0, enc_len = 0; + GString *pem = NULL; + char *tmp, *tmp_password = NULL; + int left; + const char *p; + GByteArray *ret = NULL; + + g_return_val_if_fail (data != NULL, NULL); + g_return_val_if_fail (data->len > 0, NULL); + if (out_password) + g_return_val_if_fail (*out_password == NULL, NULL); + + /* Make the password if needed */ + if (!in_password) { + if (!crypto_randomize (pw_buf, sizeof (pw_buf), error)) + return NULL; + in_password = tmp_password = utils_bin2hexstr ((const char *) pw_buf, sizeof (pw_buf), -1); + } + + if (!crypto_randomize (salt, sizeof (salt), error)) + goto out; + + key = make_key (&salt[0], sizeof (salt), in_password, &key_len, error); + if (!key) + goto out; + + enc = crypto_encrypt (CIPHER_DES_EDE3_CBC, data, salt, sizeof (salt), key, key_len, &enc_len, error); + if (!enc) + goto out; + + pem = g_string_sized_new (enc_len * 2 + 100); + if (!pem) { + g_set_error_literal (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for PEM file creation.")); + goto out; + } + + g_string_append (pem, "-----BEGIN RSA PRIVATE KEY-----\n"); + g_string_append (pem, "Proc-Type: 4,ENCRYPTED\n"); + + /* Convert the salt to a hex string */ + tmp = utils_bin2hexstr ((const char *) salt, sizeof (salt), 16); + if (!tmp) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for writing IV to PEM file.")); + goto out; + } + + g_string_append_printf (pem, "DEK-Info: DES-EDE3-CBC,%s\n\n", tmp); + g_free (tmp); + + /* Convert the encrypted key to a base64 string */ + p = tmp = g_base64_encode ((const guchar *) enc, enc_len); + if (!tmp) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for writing encrypted key to PEM file.")); + goto out; + } + + left = strlen (tmp); + while (left > 0) { + g_string_append_len (pem, p, (left < 64) ? left : 64); + g_string_append_c (pem, '\n'); + left -= 64; + p += 64; + } + g_free (tmp); + + g_string_append (pem, "-----END RSA PRIVATE KEY-----\n"); + + ret = g_byte_array_sized_new (pem->len); + if (!ret) { + g_set_error (error, NM_CRYPTO_ERROR, + NM_CRYPTO_ERR_OUT_OF_MEMORY, + _("Could not allocate memory for PEM file data.")); + goto out; + } + g_byte_array_append (ret, (const unsigned char *) pem->str, pem->len); + if (tmp_password && out_password) + *out_password = g_strdup (tmp_password); + +out: + if (key) { + memset (key, 0, key_len); + g_free (key); + } + if (enc) { + memset (enc, 0, enc_len); + g_free (enc); + } + if (pem) + g_string_free (pem, TRUE); + + if (tmp_password) { + memset (tmp_password, 0, strlen (tmp_password)); + g_free (tmp_password); + } + + return ret; +} + |