/* * * Connection Manager * * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "../src/connman.h" #include "vpn.h" enum what { REMOVE = 1, ADD = 2, }; struct vpn_config_provider { char *provider_identifier; char *ident; char *name; char *type; char *host; char *domain; char *networks; GHashTable *setting_strings; char *config_ident; /* file prefix */ char *config_entry; /* entry name */ }; struct vpn_config { char *ident; char *name; char *description; GHashTable *provider_table; }; static GHashTable *config_table = NULL; static bool cleanup = false; /* Definition of possible strings in the .config files */ #define CONFIG_KEY_NAME "Name" #define CONFIG_KEY_DESC "Description" static const char *config_possible_keys[] = { CONFIG_KEY_NAME, CONFIG_KEY_DESC, NULL, }; static void unregister_config(gpointer data) { struct vpn_config *config = data; connman_info("Removing configuration %s", config->ident); g_hash_table_destroy(config->provider_table); g_free(config->description); g_free(config->name); g_free(config->ident); g_free(config); } static void unregister_provider(gpointer data) { struct vpn_config_provider *config_provider = data; struct vpn_provider *provider; char *provider_id; if (cleanup) goto free_only; provider_id = config_provider->provider_identifier; connman_info("Removing provider configuration %s provider %s", config_provider->ident, provider_id); provider = __vpn_provider_lookup(provider_id); if (provider) __vpn_provider_delete(provider); else { if (!__connman_storage_remove_provider(provider_id)) DBG("Could not remove all files for provider %s", provider_id); } free_only: g_free(config_provider->ident); g_free(config_provider->type); g_free(config_provider->name); g_free(config_provider->host); g_free(config_provider->domain); g_free(config_provider->networks); g_hash_table_destroy(config_provider->setting_strings); g_free(config_provider->provider_identifier); g_free(config_provider->config_ident); g_free(config_provider->config_entry); g_free(config_provider); } static int set_string(struct vpn_config_provider *config_provider, const char *key, const char *value) { DBG("provider %p key %s value %s", config_provider, key, value); if (g_str_equal(key, "Type")) { g_free(config_provider->type); config_provider->type = g_strdup(value); } else if (g_str_equal(key, "Name")) { g_free(config_provider->name); config_provider->name = g_strdup(value); } else if (g_str_equal(key, "Host")) { g_free(config_provider->host); config_provider->host = g_strdup(value); } else if (g_str_equal(key, "Domain")) { g_free(config_provider->domain); config_provider->domain = g_strdup(value); } else if (g_str_equal(key, "Networks")) { g_free(config_provider->networks); config_provider->networks = g_strdup(value); } g_hash_table_replace(config_provider->setting_strings, g_strdup(key), g_strdup(value)); return 0; } static const char *get_string(struct vpn_config_provider *config_provider, const char *key) { DBG("provider %p key %s", config_provider, key); if (g_str_equal(key, "Type")) return config_provider->type; else if (g_str_equal(key, "Name")) return config_provider->name; else if (g_str_equal(key, "Host")) return config_provider->host; else if (g_str_equal(key, "Domain")) return config_provider->domain; else if (g_str_equal(key, "Networks")) return config_provider->networks; return g_hash_table_lookup(config_provider->setting_strings, key); } static void add_keys(struct vpn_config_provider *config_provider, GKeyFile *keyfile, const char *group) { char **avail_keys; gsize nb_avail_keys, i; avail_keys = g_key_file_get_keys(keyfile, group, &nb_avail_keys, NULL); if (!avail_keys) return; for (i = 0 ; i < nb_avail_keys; i++) { char *value = g_key_file_get_value(keyfile, group, avail_keys[i], NULL); if (!value) { connman_warn("Cannot find value for %s", avail_keys[i]); continue; } set_string(config_provider, avail_keys[i], value); g_free(value); } g_strfreev(avail_keys); } static int load_provider(GKeyFile *keyfile, const char *group, struct vpn_config *config, enum what action) { struct vpn_config_provider *config_provider; const char *ident, *host, *domain; int err; /* Strip off "provider_" prefix */ ident = group + 9; if (strlen(ident) < 1) return -EINVAL; config_provider = g_hash_table_lookup(config->provider_table, ident); if (config_provider) return -EALREADY; config_provider = g_try_new0(struct vpn_config_provider, 1); if (!config_provider) return -ENOMEM; config_provider->ident = g_strdup(ident); config_provider->setting_strings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); add_keys(config_provider, keyfile, group); host = get_string(config_provider, "Host"); domain = get_string(config_provider, "Domain"); if (host && domain) { char *id = __vpn_provider_create_identifier(host, domain); struct vpn_provider *provider; provider = __vpn_provider_lookup(id); if (provider) { if (action == REMOVE) { __vpn_provider_delete(provider); err = 0; } else { connman_warn("Provider configuration %s " "already exist", id); err = -EALREADY; } g_free(id); goto err; } config_provider->provider_identifier = id; DBG("provider identifier %s", id); } else { DBG("invalid values host %s domain %s", host, domain); err = -EINVAL; goto err; } config_provider->config_ident = g_strdup(config->ident); config_provider->config_entry = g_strdup_printf("provider_%s", config_provider->ident); g_hash_table_insert(config->provider_table, config_provider->ident, config_provider); err = __vpn_provider_create_from_config( config_provider->setting_strings, config_provider->config_ident, config_provider->config_entry); if (err != 0) { DBG("Cannot create provider from config file (%d/%s)", -err, strerror(-err)); goto err; } connman_info("Added provider configuration %s", config_provider->ident); return 0; err: g_free(config_provider->ident); g_free(config_provider->type); g_free(config_provider->name); g_free(config_provider->host); g_free(config_provider->domain); g_free(config_provider->networks); g_hash_table_destroy(config_provider->setting_strings); g_free(config_provider); return err; } static void check_keys(GKeyFile *keyfile, const char *group, const char **possible_keys) { char **avail_keys; gsize nb_avail_keys, i, j; avail_keys = g_key_file_get_keys(keyfile, group, &nb_avail_keys, NULL); if (!avail_keys) return; for (i = 0 ; i < nb_avail_keys; i++) { for (j = 0; possible_keys[j] ; j++) if (g_strcmp0(avail_keys[i], possible_keys[j]) == 0) break; if (!possible_keys[j]) connman_warn("Unknown configuration key %s in [%s]", avail_keys[i], group); } g_strfreev(avail_keys); } static int load_config(struct vpn_config *config, char *path, enum what action) { GKeyFile *keyfile; gsize length; char **groups; char *str; bool found = false; int i; DBG("config %p", config); keyfile = __connman_storage_load_provider_config(config->ident); if (!keyfile) return -EIO; /* Verify keys validity of the global section */ check_keys(keyfile, "global", config_possible_keys); str = __vpn_config_get_string(keyfile, "global", CONFIG_KEY_NAME, NULL); if (str) { g_free(config->name); config->name = str; } str = __vpn_config_get_string(keyfile, "global", CONFIG_KEY_DESC, NULL); if (str) { g_free(config->description); config->description = str; } groups = g_key_file_get_groups(keyfile, &length); for (i = 0; groups[i]; i++) { if (g_str_has_prefix(groups[i], "provider_")) { int ret = load_provider(keyfile, groups[i], config, action); if (ret == 0 || ret == -EALREADY) found = true; } } if (!found) connman_warn("Config file %s/%s.config does not contain any " "configuration that can be provisioned!", path, config->ident); g_strfreev(groups); g_key_file_free(keyfile); return 0; } static struct vpn_config *create_config(const char *ident) { struct vpn_config *config; DBG("ident %s", ident); if (g_hash_table_lookup(config_table, ident)) return NULL; config = g_try_new0(struct vpn_config, 1); if (!config) return NULL; config->ident = g_strdup(ident); config->provider_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, unregister_provider); g_hash_table_insert(config_table, config->ident, config); connman_info("Adding configuration %s", config->ident); return config; } static bool validate_ident(const char *ident) { unsigned int i; if (!ident) return false; for (i = 0; i < strlen(ident); i++) if (!g_ascii_isprint(ident[i])) return false; return true; } static char *get_dir(void) { return g_strdup_printf("%s", VPN_STORAGEDIR); } static int read_configs(void) { GDir *dir; char *path = get_dir(); DBG("path %s", path); dir = g_dir_open(path, 0, NULL); if (dir) { const gchar *file; while ((file = g_dir_read_name(dir))) { GString *str; gchar *ident; if (!g_str_has_suffix(file, ".config")) continue; ident = g_strrstr(file, ".config"); if (!ident) continue; str = g_string_new_len(file, ident - file); if (!str) continue; ident = g_string_free(str, FALSE); if (validate_ident(ident)) { struct vpn_config *config; config = create_config(ident); if (config) load_config(config, path, ADD); } else { connman_error("Invalid config ident %s", ident); } g_free(ident); } g_dir_close(dir); } g_free(path); return 0; } static void config_notify_handler(struct inotify_event *event, const char *ident) { char *ext; if (!ident) return; if (!g_str_has_suffix(ident, ".config")) return; ext = g_strrstr(ident, ".config"); if (!ext) return; *ext = '\0'; if (!validate_ident(ident)) { connman_error("Invalid config ident %s", ident); return; } if (event->mask & IN_CREATE) return; if (event->mask & IN_DELETE) { g_hash_table_remove(config_table, ident); return; } if (event->mask & IN_MODIFY) { struct vpn_config *config; char *path = get_dir(); config = g_hash_table_lookup(config_table, ident); if (config) { g_hash_table_remove_all(config->provider_table); load_config(config, path, REMOVE); /* Re-scan the config file for any changes */ g_hash_table_remove_all(config->provider_table); load_config(config, path, ADD); } else { /* * Inotify will send create event followed by modify * event for any config file that is copied to * monitored directory. So in practice we should just * ignore the create event and trust only the modify * one in order to avoid create/remove/create loop */ config = create_config(ident); if (config) load_config(config, path, ADD); } g_free(path); } } int __vpn_config_init(void) { char *dir = get_dir(); DBG(""); config_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, unregister_config); connman_inotify_register(dir, config_notify_handler); g_free(dir); return read_configs(); } void __vpn_config_cleanup(void) { char *dir = get_dir(); DBG(""); cleanup = true; connman_inotify_unregister(dir, config_notify_handler); g_free(dir); g_hash_table_destroy(config_table); config_table = NULL; cleanup = false; } char *__vpn_config_get_string(GKeyFile *key_file, const char *group_name, const char *key, GError **error) { char *str = g_key_file_get_string(key_file, group_name, key, error); if (!str) return NULL; return g_strchomp(str); } char **__vpn_config_get_string_list(GKeyFile *key_file, const char *group_name, const char *key, gsize *length, GError **error) { char **p; char **strlist = g_key_file_get_string_list(key_file, group_name, key, length, error); if (!strlist) return NULL; p = strlist; while (*p) { *p = g_strstrip(*p); p++; } return strlist; }