| /* |
| * |
| * ConnMan VPN daemon |
| * |
| * 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 <config.h> |
| #endif |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <gdbus.h> |
| #include <connman/log.h> |
| #include <gweb/gresolv.h> |
| #include <netdb.h> |
| |
| #include "../src/connman.h" |
| #include "connman/agent.h" |
| #include "connman/vpn-dbus.h" |
| #include "vpn-provider.h" |
| #include "vpn.h" |
| |
| static DBusConnection *connection; |
| static GHashTable *provider_hash; |
| static GSList *driver_list; |
| static int configuration_count; |
| static bool handle_routes; |
| |
| struct vpn_route { |
| int family; |
| char *network; |
| char *netmask; |
| char *gateway; |
| }; |
| |
| struct vpn_setting { |
| bool hide_value; |
| bool immutable; |
| char *value; |
| }; |
| |
| struct vpn_provider { |
| int refcount; |
| int index; |
| int fd; |
| enum vpn_provider_state state; |
| char *path; |
| char *identifier; |
| char *name; |
| char *type; |
| char *host; |
| char *domain; |
| int family; |
| GHashTable *routes; |
| struct vpn_provider_driver *driver; |
| void *driver_data; |
| GHashTable *setting_strings; |
| GHashTable *user_routes; |
| GSList *user_networks; |
| GResolv *resolv; |
| char **host_ip; |
| struct vpn_ipconfig *ipconfig_ipv4; |
| struct vpn_ipconfig *ipconfig_ipv6; |
| char **nameservers; |
| guint notify_id; |
| char *config_file; |
| char *config_entry; |
| bool immutable; |
| struct connman_ipaddress *prev_ipv4_addr; |
| struct connman_ipaddress *prev_ipv6_addr; |
| }; |
| |
| static void append_properties(DBusMessageIter *iter, |
| struct vpn_provider *provider); |
| |
| static void free_route(gpointer data) |
| { |
| struct vpn_route *route = data; |
| |
| g_free(route->network); |
| g_free(route->netmask); |
| g_free(route->gateway); |
| |
| g_free(route); |
| } |
| |
| static void free_setting(gpointer data) |
| { |
| struct vpn_setting *setting = data; |
| |
| g_free(setting->value); |
| g_free(setting); |
| } |
| |
| static void append_route(DBusMessageIter *iter, void *user_data) |
| { |
| struct vpn_route *route = user_data; |
| DBusMessageIter item; |
| int family = 0; |
| |
| connman_dbus_dict_open(iter, &item); |
| |
| if (!route) |
| goto empty_dict; |
| |
| if (route->family == AF_INET) |
| family = 4; |
| else if (route->family == AF_INET6) |
| family = 6; |
| |
| if (family != 0) |
| connman_dbus_dict_append_basic(&item, "ProtocolFamily", |
| DBUS_TYPE_INT32, &family); |
| |
| if (route->network) |
| connman_dbus_dict_append_basic(&item, "Network", |
| DBUS_TYPE_STRING, &route->network); |
| |
| if (route->netmask) |
| connman_dbus_dict_append_basic(&item, "Netmask", |
| DBUS_TYPE_STRING, &route->netmask); |
| |
| if (route->gateway) |
| connman_dbus_dict_append_basic(&item, "Gateway", |
| DBUS_TYPE_STRING, &route->gateway); |
| |
| empty_dict: |
| connman_dbus_dict_close(iter, &item); |
| } |
| |
| static void append_routes(DBusMessageIter *iter, void *user_data) |
| { |
| GHashTable *routes = user_data; |
| GHashTableIter hash; |
| gpointer value, key; |
| |
| if (!routes) { |
| append_route(iter, NULL); |
| return; |
| } |
| |
| g_hash_table_iter_init(&hash, routes); |
| |
| while (g_hash_table_iter_next(&hash, &key, &value)) { |
| DBusMessageIter dict; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, |
| &dict); |
| append_route(&dict, value); |
| dbus_message_iter_close_container(iter, &dict); |
| } |
| } |
| |
| static void send_routes(struct vpn_provider *provider, GHashTable *routes, |
| const char *name) |
| { |
| connman_dbus_property_changed_array(provider->path, |
| VPN_CONNECTION_INTERFACE, |
| name, |
| DBUS_TYPE_DICT_ENTRY, |
| append_routes, |
| routes); |
| } |
| |
| static int provider_routes_changed(struct vpn_provider *provider) |
| { |
| DBG("provider %p", provider); |
| |
| send_routes(provider, provider->routes, "ServerRoutes"); |
| |
| return 0; |
| } |
| |
| static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts) |
| { |
| DBusMessageIter dict, value, entry; |
| const char *network, *netmask, *gateway; |
| struct vpn_route *route; |
| int family, type; |
| const char *key; |
| |
| dbus_message_iter_recurse(dicts, &entry); |
| |
| network = netmask = gateway = NULL; |
| family = PF_UNSPEC; |
| |
| while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_DICT_ENTRY) { |
| |
| dbus_message_iter_recurse(&entry, &dict); |
| dbus_message_iter_get_basic(&dict, &key); |
| |
| dbus_message_iter_next(&dict); |
| dbus_message_iter_recurse(&dict, &value); |
| |
| type = dbus_message_iter_get_arg_type(&value); |
| |
| switch (type) { |
| case DBUS_TYPE_INT32: |
| if (g_str_equal(key, "ProtocolFamily")) |
| dbus_message_iter_get_basic(&value, &family); |
| break; |
| |
| case DBUS_TYPE_STRING: |
| if (g_str_equal(key, "Network")) |
| dbus_message_iter_get_basic(&value, &network); |
| else if (g_str_equal(key, "Netmask")) |
| dbus_message_iter_get_basic(&value, &netmask); |
| else if (g_str_equal(key, "Gateway")) |
| dbus_message_iter_get_basic(&value, &gateway); |
| break; |
| } |
| |
| dbus_message_iter_next(&entry); |
| } |
| |
| DBG("family %d network %s netmask %s gateway %s", family, |
| network, netmask, gateway); |
| |
| if (!network || !netmask) { |
| DBG("Ignoring route as network/netmask is missing"); |
| return routes; |
| } |
| |
| route = g_try_new(struct vpn_route, 1); |
| if (!route) { |
| g_slist_free_full(routes, free_route); |
| return NULL; |
| } |
| |
| if (family == PF_UNSPEC) { |
| family = connman_inet_check_ipaddress(network); |
| if (family < 0) { |
| DBG("Cannot get address family of %s (%d/%s)", network, |
| family, gai_strerror(family)); |
| |
| g_free(route); |
| return routes; |
| } |
| } else { |
| switch (family) { |
| case '4': |
| family = AF_INET; |
| break; |
| case '6': |
| family = AF_INET6; |
| break; |
| default: |
| family = PF_UNSPEC; |
| break; |
| } |
| } |
| |
| route->family = family; |
| route->network = g_strdup(network); |
| route->netmask = g_strdup(netmask); |
| route->gateway = g_strdup(gateway); |
| |
| routes = g_slist_prepend(routes, route); |
| return routes; |
| } |
| |
| static GSList *get_user_networks(DBusMessageIter *array) |
| { |
| DBusMessageIter entry; |
| GSList *list = NULL; |
| |
| while (dbus_message_iter_get_arg_type(array) == DBUS_TYPE_ARRAY) { |
| |
| dbus_message_iter_recurse(array, &entry); |
| |
| while (dbus_message_iter_get_arg_type(&entry) == |
| DBUS_TYPE_STRUCT) { |
| DBusMessageIter dicts; |
| |
| dbus_message_iter_recurse(&entry, &dicts); |
| |
| while (dbus_message_iter_get_arg_type(&dicts) == |
| DBUS_TYPE_ARRAY) { |
| |
| list = read_route_dict(list, &dicts); |
| dbus_message_iter_next(&dicts); |
| } |
| |
| dbus_message_iter_next(&entry); |
| } |
| |
| dbus_message_iter_next(array); |
| } |
| |
| return list; |
| } |
| |
| static void set_user_networks(struct vpn_provider *provider, GSList *networks) |
| { |
| GSList *list; |
| |
| for (list = networks; list; list = g_slist_next(list)) { |
| struct vpn_route *route = list->data; |
| |
| if (__vpn_provider_append_user_route(provider, |
| route->family, route->network, |
| route->netmask, route->gateway) != 0) |
| break; |
| } |
| } |
| |
| static void del_routes(struct vpn_provider *provider) |
| { |
| GHashTableIter hash; |
| gpointer value, key; |
| |
| g_hash_table_iter_init(&hash, provider->user_routes); |
| while (handle_routes && g_hash_table_iter_next(&hash, |
| &key, &value)) { |
| struct vpn_route *route = value; |
| if (route->family == AF_INET6) { |
| unsigned char prefixlen = atoi(route->netmask); |
| connman_inet_del_ipv6_network_route(provider->index, |
| route->network, |
| prefixlen); |
| } else |
| connman_inet_del_host_route(provider->index, |
| route->network); |
| } |
| |
| g_hash_table_remove_all(provider->user_routes); |
| g_slist_free_full(provider->user_networks, free_route); |
| provider->user_networks = NULL; |
| } |
| |
| static void send_value(const char *path, const char *key, const char *value) |
| { |
| const char *empty = ""; |
| const char *str; |
| |
| if (value) |
| str = value; |
| else |
| str = empty; |
| |
| connman_dbus_property_changed_basic(path, |
| VPN_CONNECTION_INTERFACE, |
| key, |
| DBUS_TYPE_STRING, |
| &str); |
| } |
| |
| static gboolean provider_send_changed(gpointer data) |
| { |
| struct vpn_provider *provider = data; |
| |
| provider_routes_changed(provider); |
| |
| provider->notify_id = 0; |
| |
| return FALSE; |
| } |
| |
| static void provider_schedule_changed(struct vpn_provider *provider) |
| { |
| if (provider->notify_id != 0) |
| g_source_remove(provider->notify_id); |
| |
| provider->notify_id = g_timeout_add(100, provider_send_changed, |
| provider); |
| } |
| |
| static DBusMessage *get_properties(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct vpn_provider *provider = data; |
| DBusMessage *reply; |
| DBusMessageIter array; |
| |
| DBG("provider %p", provider); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| dbus_message_iter_init_append(reply, &array); |
| |
| append_properties(&array, provider); |
| |
| return reply; |
| } |
| |
| static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| struct vpn_provider *provider = data; |
| DBusMessageIter iter, value; |
| const char *name; |
| int type; |
| |
| DBG("conn %p", conn); |
| |
| if (provider->immutable) |
| return __connman_error_not_supported(msg); |
| |
| if (!dbus_message_iter_init(msg, &iter)) |
| return __connman_error_invalid_arguments(msg); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) |
| return __connman_error_invalid_arguments(msg); |
| |
| dbus_message_iter_get_basic(&iter, &name); |
| dbus_message_iter_next(&iter); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) |
| return __connman_error_invalid_arguments(msg); |
| |
| dbus_message_iter_recurse(&iter, &value); |
| |
| type = dbus_message_iter_get_arg_type(&value); |
| |
| if (g_str_equal(name, "UserRoutes")) { |
| GSList *networks; |
| |
| if (type != DBUS_TYPE_ARRAY) |
| return __connman_error_invalid_arguments(msg); |
| |
| networks = get_user_networks(&value); |
| if (networks) { |
| del_routes(provider); |
| provider->user_networks = networks; |
| set_user_networks(provider, provider->user_networks); |
| |
| if (!handle_routes) |
| send_routes(provider, provider->user_routes, |
| "UserRoutes"); |
| } |
| } else { |
| const char *str; |
| |
| dbus_message_iter_get_basic(&value, &str); |
| vpn_provider_set_string(provider, name, str); |
| } |
| |
| return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); |
| } |
| |
| static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| struct vpn_provider *provider = data; |
| const char *name; |
| |
| DBG("conn %p", conn); |
| |
| if (provider->immutable) |
| return __connman_error_not_supported(msg); |
| |
| dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, |
| DBUS_TYPE_INVALID); |
| |
| if (g_str_equal(name, "UserRoutes")) { |
| del_routes(provider); |
| |
| if (!handle_routes) |
| send_routes(provider, provider->user_routes, name); |
| } else if (vpn_provider_get_string(provider, name)) { |
| vpn_provider_set_string(provider, name, NULL); |
| } else { |
| return __connman_error_invalid_property(msg); |
| } |
| |
| return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); |
| } |
| |
| static DBusMessage *do_connect(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| struct vpn_provider *provider = data; |
| int err; |
| |
| DBG("conn %p provider %p", conn, provider); |
| |
| err = __vpn_provider_connect(provider, msg); |
| if (err < 0) |
| return __connman_error_failed(msg, -err); |
| |
| return NULL; |
| } |
| |
| static DBusMessage *do_disconnect(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| struct vpn_provider *provider = data; |
| int err; |
| |
| DBG("conn %p provider %p", conn, provider); |
| |
| err = __vpn_provider_disconnect(provider); |
| if (err < 0 && err != -EINPROGRESS) |
| return __connman_error_failed(msg, -err); |
| |
| return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); |
| } |
| |
| static const GDBusMethodTable connection_methods[] = { |
| { GDBUS_METHOD("GetProperties", |
| NULL, GDBUS_ARGS({ "properties", "a{sv}" }), |
| get_properties) }, |
| { GDBUS_METHOD("SetProperty", |
| GDBUS_ARGS({ "name", "s" }, { "value", "v" }), |
| NULL, set_property) }, |
| { GDBUS_METHOD("ClearProperty", |
| GDBUS_ARGS({ "name", "s" }), NULL, |
| clear_property) }, |
| { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, do_connect) }, |
| { GDBUS_METHOD("Disconnect", NULL, NULL, do_disconnect) }, |
| { }, |
| }; |
| |
| static const GDBusSignalTable connection_signals[] = { |
| { GDBUS_SIGNAL("PropertyChanged", |
| GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, |
| { }, |
| }; |
| |
| static void resolv_result(GResolvResultStatus status, |
| char **results, gpointer user_data) |
| { |
| struct vpn_provider *provider = user_data; |
| |
| DBG("status %d", status); |
| |
| if (status == G_RESOLV_RESULT_STATUS_SUCCESS && results && |
| g_strv_length(results) > 0) |
| provider->host_ip = g_strdupv(results); |
| |
| vpn_provider_unref(provider); |
| } |
| |
| static void provider_resolv_host_addr(struct vpn_provider *provider) |
| { |
| if (!provider->host) |
| return; |
| |
| if (connman_inet_check_ipaddress(provider->host) > 0) |
| return; |
| |
| if (provider->host_ip) |
| return; |
| |
| /* |
| * If the hostname is not numeric, try to resolv it. We do not wait |
| * the result as it might take some time. We will get the result |
| * before VPN will feed routes to us because VPN client will need |
| * the IP address also before VPN connection can be established. |
| */ |
| provider->resolv = g_resolv_new(0); |
| if (!provider->resolv) { |
| DBG("Cannot resolv %s", provider->host); |
| return; |
| } |
| |
| DBG("Trying to resolv %s", provider->host); |
| |
| vpn_provider_ref(provider); |
| |
| g_resolv_lookup_hostname(provider->resolv, provider->host, |
| resolv_result, provider); |
| } |
| |
| void __vpn_provider_append_properties(struct vpn_provider *provider, |
| DBusMessageIter *iter) |
| { |
| if (provider->host) |
| connman_dbus_dict_append_basic(iter, "Host", |
| DBUS_TYPE_STRING, &provider->host); |
| |
| if (provider->domain) |
| connman_dbus_dict_append_basic(iter, "Domain", |
| DBUS_TYPE_STRING, &provider->domain); |
| |
| if (provider->type) |
| connman_dbus_dict_append_basic(iter, "Type", DBUS_TYPE_STRING, |
| &provider->type); |
| } |
| |
| int __vpn_provider_append_user_route(struct vpn_provider *provider, |
| int family, const char *network, |
| const char *netmask, const char *gateway) |
| { |
| struct vpn_route *route; |
| char *key = g_strdup_printf("%d/%s/%s/%s", family, network, |
| netmask, gateway ? gateway : ""); |
| |
| DBG("family %d network %s netmask %s gw %s", family, network, |
| netmask, gateway); |
| |
| route = g_hash_table_lookup(provider->user_routes, key); |
| if (!route) { |
| route = g_try_new0(struct vpn_route, 1); |
| if (!route) { |
| connman_error("out of memory"); |
| return -ENOMEM; |
| } |
| |
| route->family = family; |
| route->network = g_strdup(network); |
| route->netmask = g_strdup(netmask); |
| route->gateway = g_strdup(gateway); |
| |
| g_hash_table_replace(provider->user_routes, key, route); |
| } else |
| g_free(key); |
| |
| return 0; |
| } |
| |
| static struct vpn_route *get_route(char *route_str) |
| { |
| char **elems = g_strsplit(route_str, "/", 0); |
| char *network, *netmask, *gateway, *family_str; |
| int family = PF_UNSPEC; |
| struct vpn_route *route = NULL; |
| |
| if (!elems) |
| return NULL; |
| |
| family_str = elems[0]; |
| |
| network = elems[1]; |
| if (!network || network[0] == '\0') |
| goto out; |
| |
| netmask = elems[2]; |
| if (!netmask || netmask[0] == '\0') |
| goto out; |
| |
| gateway = elems[3]; |
| |
| route = g_try_new0(struct vpn_route, 1); |
| if (!route) |
| goto out; |
| |
| if (family_str[0] == '\0' || atoi(family_str) == 0) { |
| family = PF_UNSPEC; |
| } else { |
| switch (family_str[0]) { |
| case '4': |
| family = AF_INET; |
| break; |
| case '6': |
| family = AF_INET6; |
| break; |
| } |
| } |
| |
| if (g_strrstr(network, ":")) { |
| if (family != PF_UNSPEC && family != AF_INET6) |
| DBG("You have IPv6 address but you have non IPv6 route"); |
| } else if (g_strrstr(network, ".")) { |
| if (family != PF_UNSPEC && family != AF_INET) |
| DBG("You have IPv4 address but you have non IPv4 route"); |
| |
| if (!g_strrstr(netmask, ".")) { |
| /* We have netmask length */ |
| in_addr_t addr; |
| struct in_addr netmask_in; |
| unsigned char prefix_len = 32; |
| |
| if (netmask) { |
| char *ptr; |
| long int value = strtol(netmask, &ptr, 10); |
| if (ptr != netmask && *ptr == '\0' && |
| value <= 32) |
| prefix_len = value; |
| } |
| |
| addr = 0xffffffff << (32 - prefix_len); |
| netmask_in.s_addr = htonl(addr); |
| netmask = inet_ntoa(netmask_in); |
| |
| DBG("network %s netmask %s", network, netmask); |
| } |
| } |
| |
| if (family == PF_UNSPEC) { |
| family = connman_inet_check_ipaddress(network); |
| if (family < 0 || family == PF_UNSPEC) |
| goto out; |
| } |
| |
| route->family = family; |
| route->network = g_strdup(network); |
| route->netmask = g_strdup(netmask); |
| route->gateway = g_strdup(gateway); |
| |
| out: |
| g_strfreev(elems); |
| return route; |
| } |
| |
| static GSList *get_routes(gchar **networks) |
| { |
| struct vpn_route *route; |
| GSList *routes = NULL; |
| int i; |
| |
| for (i = 0; networks[i]; i++) { |
| route = get_route(networks[i]); |
| if (route) |
| routes = g_slist_prepend(routes, route); |
| } |
| |
| return routes; |
| } |
| |
| static int provider_load_from_keyfile(struct vpn_provider *provider, |
| GKeyFile *keyfile) |
| { |
| gsize idx = 0; |
| gchar **settings; |
| gchar *key, *value; |
| gsize length, num_user_networks; |
| gchar **networks = NULL; |
| |
| settings = g_key_file_get_keys(keyfile, provider->identifier, &length, |
| NULL); |
| if (!settings) { |
| g_key_file_free(keyfile); |
| return -ENOENT; |
| } |
| |
| while (idx < length) { |
| key = settings[idx]; |
| if (key) { |
| if (g_str_equal(key, "Networks")) { |
| networks = __vpn_config_get_string_list(keyfile, |
| provider->identifier, |
| key, |
| &num_user_networks, |
| NULL); |
| provider->user_networks = get_routes(networks); |
| |
| } else { |
| value = __vpn_config_get_string(keyfile, |
| provider->identifier, |
| key, NULL); |
| vpn_provider_set_string(provider, key, |
| value); |
| g_free(value); |
| } |
| } |
| idx += 1; |
| } |
| g_strfreev(settings); |
| g_strfreev(networks); |
| |
| if (provider->user_networks) |
| set_user_networks(provider, provider->user_networks); |
| |
| return 0; |
| } |
| |
| |
| static int vpn_provider_load(struct vpn_provider *provider) |
| { |
| GKeyFile *keyfile; |
| |
| DBG("provider %p", provider); |
| |
| keyfile = __connman_storage_load_provider(provider->identifier); |
| if (!keyfile) |
| return -ENOENT; |
| |
| provider_load_from_keyfile(provider, keyfile); |
| |
| g_key_file_free(keyfile); |
| return 0; |
| } |
| |
| static gchar **create_network_list(GSList *networks, gsize *count) |
| { |
| GSList *list; |
| gchar **result = NULL; |
| unsigned int num_elems = 0; |
| |
| for (list = networks; list; list = g_slist_next(list)) { |
| struct vpn_route *route = list->data; |
| int family; |
| |
| result = g_try_realloc(result, |
| (num_elems + 1) * sizeof(gchar *)); |
| if (!result) |
| return NULL; |
| |
| switch (route->family) { |
| case AF_INET: |
| family = 4; |
| break; |
| case AF_INET6: |
| family = 6; |
| break; |
| default: |
| family = 0; |
| break; |
| } |
| |
| result[num_elems] = g_strdup_printf("%d/%s/%s/%s", |
| family, route->network, route->netmask, |
| !route->gateway ? "" : route->gateway); |
| |
| num_elems++; |
| } |
| |
| result = g_try_realloc(result, (num_elems + 1) * sizeof(gchar *)); |
| if (!result) |
| return NULL; |
| |
| result[num_elems] = NULL; |
| *count = num_elems; |
| return result; |
| } |
| |
| static int vpn_provider_save(struct vpn_provider *provider) |
| { |
| GKeyFile *keyfile; |
| |
| DBG("provider %p immutable %s", provider, |
| provider->immutable ? "yes" : "no"); |
| |
| if (provider->immutable) { |
| /* |
| * Do not save providers that are provisioned via .config |
| * file. |
| */ |
| return -EPERM; |
| } |
| |
| keyfile = g_key_file_new(); |
| if (!keyfile) |
| return -ENOMEM; |
| |
| g_key_file_set_string(keyfile, provider->identifier, |
| "Name", provider->name); |
| g_key_file_set_string(keyfile, provider->identifier, |
| "Type", provider->type); |
| g_key_file_set_string(keyfile, provider->identifier, |
| "Host", provider->host); |
| g_key_file_set_string(keyfile, provider->identifier, |
| "VPN.Domain", provider->domain); |
| if (provider->user_networks) { |
| gchar **networks; |
| gsize network_count; |
| |
| networks = create_network_list(provider->user_networks, |
| &network_count); |
| if (networks) { |
| g_key_file_set_string_list(keyfile, |
| provider->identifier, |
| "Networks", |
| (const gchar ** const)networks, |
| network_count); |
| g_strfreev(networks); |
| } |
| } |
| |
| if (provider->config_file && strlen(provider->config_file) > 0) |
| g_key_file_set_string(keyfile, provider->identifier, |
| "Config.file", provider->config_file); |
| |
| if (provider->config_entry && |
| strlen(provider->config_entry) > 0) |
| g_key_file_set_string(keyfile, provider->identifier, |
| "Config.ident", provider->config_entry); |
| |
| if (provider->driver && provider->driver->save) |
| provider->driver->save(provider, keyfile); |
| |
| __connman_storage_save_provider(keyfile, provider->identifier); |
| g_key_file_free(keyfile); |
| |
| return 0; |
| } |
| |
| struct vpn_provider *__vpn_provider_lookup(const char *identifier) |
| { |
| struct vpn_provider *provider = NULL; |
| |
| provider = g_hash_table_lookup(provider_hash, identifier); |
| |
| return provider; |
| } |
| |
| static bool match_driver(struct vpn_provider *provider, |
| struct vpn_provider_driver *driver) |
| { |
| if (g_strcmp0(driver->name, provider->type) == 0) |
| return true; |
| |
| return false; |
| } |
| |
| static int provider_probe(struct vpn_provider *provider) |
| { |
| GSList *list; |
| |
| DBG("provider %p driver %p name %s", provider, provider->driver, |
| provider->name); |
| |
| if (provider->driver) |
| return -EALREADY; |
| |
| for (list = driver_list; list; list = list->next) { |
| struct vpn_provider_driver *driver = list->data; |
| |
| if (!match_driver(provider, driver)) |
| continue; |
| |
| DBG("driver %p name %s", driver, driver->name); |
| |
| if (driver->probe && driver->probe(provider) == 0) { |
| provider->driver = driver; |
| break; |
| } |
| } |
| |
| if (!provider->driver) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| static void provider_remove(struct vpn_provider *provider) |
| { |
| if (provider->driver) { |
| provider->driver->remove(provider); |
| provider->driver = NULL; |
| } |
| } |
| |
| static int provider_register(struct vpn_provider *provider) |
| { |
| return provider_probe(provider); |
| } |
| |
| static void provider_unregister(struct vpn_provider *provider) |
| { |
| provider_remove(provider); |
| } |
| |
| struct vpn_provider * |
| vpn_provider_ref_debug(struct vpn_provider *provider, |
| const char *file, int line, const char *caller) |
| { |
| DBG("%p ref %d by %s:%d:%s()", provider, provider->refcount + 1, |
| file, line, caller); |
| |
| __sync_fetch_and_add(&provider->refcount, 1); |
| |
| return provider; |
| } |
| |
| static void provider_destruct(struct vpn_provider *provider) |
| { |
| DBG("provider %p", provider); |
| |
| if (provider->notify_id != 0) |
| g_source_remove(provider->notify_id); |
| |
| g_free(provider->name); |
| g_free(provider->type); |
| g_free(provider->host); |
| g_free(provider->domain); |
| g_free(provider->identifier); |
| g_free(provider->path); |
| g_slist_free_full(provider->user_networks, free_route); |
| g_strfreev(provider->nameservers); |
| g_hash_table_destroy(provider->routes); |
| g_hash_table_destroy(provider->user_routes); |
| g_hash_table_destroy(provider->setting_strings); |
| if (provider->resolv) { |
| g_resolv_unref(provider->resolv); |
| provider->resolv = NULL; |
| } |
| __vpn_ipconfig_unref(provider->ipconfig_ipv4); |
| __vpn_ipconfig_unref(provider->ipconfig_ipv6); |
| |
| g_strfreev(provider->host_ip); |
| g_free(provider->config_file); |
| g_free(provider->config_entry); |
| connman_ipaddress_free(provider->prev_ipv4_addr); |
| connman_ipaddress_free(provider->prev_ipv6_addr); |
| g_free(provider); |
| } |
| |
| void vpn_provider_unref_debug(struct vpn_provider *provider, |
| const char *file, int line, const char *caller) |
| { |
| DBG("%p ref %d by %s:%d:%s()", provider, provider->refcount - 1, |
| file, line, caller); |
| |
| if (__sync_fetch_and_sub(&provider->refcount, 1) != 1) |
| return; |
| |
| provider_remove(provider); |
| |
| provider_destruct(provider); |
| } |
| |
| static void configuration_count_add(void) |
| { |
| DBG("count %d", configuration_count + 1); |
| |
| __sync_fetch_and_add(&configuration_count, 1); |
| } |
| |
| static void configuration_count_del(void) |
| { |
| DBG("count %d", configuration_count - 1); |
| |
| if (__sync_fetch_and_sub(&configuration_count, 1) != 1) |
| return; |
| } |
| |
| int __vpn_provider_disconnect(struct vpn_provider *provider) |
| { |
| int err; |
| |
| DBG("provider %p", provider); |
| |
| if (provider->driver && provider->driver->disconnect) |
| err = provider->driver->disconnect(provider); |
| else |
| return -EOPNOTSUPP; |
| |
| if (err == -EINPROGRESS) |
| vpn_provider_set_state(provider, VPN_PROVIDER_STATE_CONNECT); |
| |
| return err; |
| } |
| |
| static void connect_cb(struct vpn_provider *provider, void *user_data, |
| int error) |
| { |
| DBusMessage *pending = user_data; |
| |
| DBG("provider %p user %p error %d", provider, user_data, error); |
| |
| if (error != 0) { |
| DBusMessage *reply = __connman_error_failed(pending, error); |
| if (reply) |
| g_dbus_send_message(connection, reply); |
| |
| vpn_provider_indicate_error(provider, |
| VPN_PROVIDER_ERROR_CONNECT_FAILED); |
| vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE); |
| } else |
| g_dbus_send_reply(connection, pending, DBUS_TYPE_INVALID); |
| |
| dbus_message_unref(pending); |
| } |
| |
| int __vpn_provider_connect(struct vpn_provider *provider, DBusMessage *msg) |
| { |
| int err; |
| |
| DBG("provider %p", provider); |
| |
| if (provider->driver && provider->driver->connect) { |
| dbus_message_ref(msg); |
| err = provider->driver->connect(provider, connect_cb, |
| dbus_message_get_sender(msg), |
| msg); |
| } else |
| return -EOPNOTSUPP; |
| |
| if (err == -EINPROGRESS) |
| vpn_provider_set_state(provider, VPN_PROVIDER_STATE_CONNECT); |
| |
| return err; |
| } |
| |
| static void connection_removed_signal(struct vpn_provider *provider) |
| { |
| DBusMessage *signal; |
| DBusMessageIter iter; |
| |
| signal = dbus_message_new_signal(VPN_MANAGER_PATH, |
| VPN_MANAGER_INTERFACE, "ConnectionRemoved"); |
| if (!signal) |
| return; |
| |
| dbus_message_iter_init_append(signal, &iter); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, |
| &provider->path); |
| dbus_connection_send(connection, signal, NULL); |
| dbus_message_unref(signal); |
| } |
| |
| static char *get_ident(const char *path) |
| { |
| char *pos; |
| |
| if (*path != '/') |
| return NULL; |
| |
| pos = strrchr(path, '/'); |
| if (!pos) |
| return NULL; |
| |
| return pos + 1; |
| } |
| |
| int __vpn_provider_remove(const char *path) |
| { |
| struct vpn_provider *provider; |
| char *ident; |
| |
| DBG("path %s", path); |
| |
| ident = get_ident(path); |
| |
| provider = __vpn_provider_lookup(ident); |
| if (provider) |
| return __vpn_provider_delete(provider); |
| |
| return -ENXIO; |
| } |
| |
| int __vpn_provider_delete(struct vpn_provider *provider) |
| { |
| DBG("Deleting VPN %s", provider->identifier); |
| |
| connection_removed_signal(provider); |
| |
| provider_unregister(provider); |
| |
| __connman_storage_remove_provider(provider->identifier); |
| |
| g_hash_table_remove(provider_hash, provider->identifier); |
| |
| return 0; |
| } |
| |
| static void append_ipv4(DBusMessageIter *iter, void *user_data) |
| { |
| struct vpn_provider *provider = user_data; |
| const char *address, *gateway, *peer; |
| |
| address = __vpn_ipconfig_get_local(provider->ipconfig_ipv4); |
| if (address) { |
| in_addr_t addr; |
| struct in_addr netmask; |
| char *mask; |
| int prefixlen; |
| |
| prefixlen = __vpn_ipconfig_get_prefixlen( |
| provider->ipconfig_ipv4); |
| |
| addr = 0xffffffff << (32 - prefixlen); |
| netmask.s_addr = htonl(addr); |
| mask = inet_ntoa(netmask); |
| |
| connman_dbus_dict_append_basic(iter, "Address", |
| DBUS_TYPE_STRING, &address); |
| |
| connman_dbus_dict_append_basic(iter, "Netmask", |
| DBUS_TYPE_STRING, &mask); |
| } |
| |
| gateway = __vpn_ipconfig_get_gateway(provider->ipconfig_ipv4); |
| if (gateway) |
| connman_dbus_dict_append_basic(iter, "Gateway", |
| DBUS_TYPE_STRING, &gateway); |
| |
| peer = __vpn_ipconfig_get_peer(provider->ipconfig_ipv4); |
| if (peer) |
| connman_dbus_dict_append_basic(iter, "Peer", |
| DBUS_TYPE_STRING, &peer); |
| } |
| |
| static void append_ipv6(DBusMessageIter *iter, void *user_data) |
| { |
| struct vpn_provider *provider = user_data; |
| const char *address, *gateway, *peer; |
| |
| address = __vpn_ipconfig_get_local(provider->ipconfig_ipv6); |
| if (address) { |
| unsigned char prefixlen; |
| |
| connman_dbus_dict_append_basic(iter, "Address", |
| DBUS_TYPE_STRING, &address); |
| |
| prefixlen = __vpn_ipconfig_get_prefixlen( |
| provider->ipconfig_ipv6); |
| |
| connman_dbus_dict_append_basic(iter, "PrefixLength", |
| DBUS_TYPE_BYTE, &prefixlen); |
| } |
| |
| gateway = __vpn_ipconfig_get_gateway(provider->ipconfig_ipv6); |
| if (gateway) |
| connman_dbus_dict_append_basic(iter, "Gateway", |
| DBUS_TYPE_STRING, &gateway); |
| |
| peer = __vpn_ipconfig_get_peer(provider->ipconfig_ipv6); |
| if (peer) |
| connman_dbus_dict_append_basic(iter, "Peer", |
| DBUS_TYPE_STRING, &peer); |
| } |
| |
| static const char *state2string(enum vpn_provider_state state) |
| { |
| switch (state) { |
| case VPN_PROVIDER_STATE_UNKNOWN: |
| break; |
| case VPN_PROVIDER_STATE_IDLE: |
| return "idle"; |
| case VPN_PROVIDER_STATE_CONNECT: |
| return "configuration"; |
| case VPN_PROVIDER_STATE_READY: |
| return "ready"; |
| case VPN_PROVIDER_STATE_DISCONNECT: |
| return "disconnect"; |
| case VPN_PROVIDER_STATE_FAILURE: |
| return "failure"; |
| } |
| |
| return NULL; |
| } |
| |
| static void append_nameservers(DBusMessageIter *iter, char **servers) |
| { |
| int i; |
| |
| DBG("%p", servers); |
| |
| for (i = 0; servers[i]; i++) { |
| DBG("servers[%d] %s", i, servers[i]); |
| dbus_message_iter_append_basic(iter, |
| DBUS_TYPE_STRING, &servers[i]); |
| } |
| } |
| |
| static void append_dns(DBusMessageIter *iter, void *user_data) |
| { |
| struct vpn_provider *provider = user_data; |
| |
| if (provider->nameservers) |
| append_nameservers(iter, provider->nameservers); |
| } |
| |
| static int provider_indicate_state(struct vpn_provider *provider, |
| enum vpn_provider_state state) |
| { |
| const char *str; |
| enum vpn_provider_state old_state; |
| |
| str = state2string(state); |
| DBG("provider %p state %s/%d", provider, str, state); |
| if (!str) |
| return -EINVAL; |
| |
| old_state = provider->state; |
| provider->state = state; |
| |
| if (state == VPN_PROVIDER_STATE_READY) { |
| connman_dbus_property_changed_basic(provider->path, |
| VPN_CONNECTION_INTERFACE, "Index", |
| DBUS_TYPE_INT32, &provider->index); |
| |
| if (provider->family == AF_INET) |
| connman_dbus_property_changed_dict(provider->path, |
| VPN_CONNECTION_INTERFACE, "IPv4", |
| append_ipv4, provider); |
| else if (provider->family == AF_INET6) |
| connman_dbus_property_changed_dict(provider->path, |
| VPN_CONNECTION_INTERFACE, "IPv6", |
| append_ipv6, provider); |
| |
| connman_dbus_property_changed_array(provider->path, |
| VPN_CONNECTION_INTERFACE, |
| "Nameservers", |
| DBUS_TYPE_STRING, |
| append_dns, provider); |
| |
| if (provider->domain) |
| connman_dbus_property_changed_basic(provider->path, |
| VPN_CONNECTION_INTERFACE, |
| "Domain", |
| DBUS_TYPE_STRING, |
| &provider->domain); |
| } |
| |
| if (old_state != state) |
| connman_dbus_property_changed_basic(provider->path, |
| VPN_CONNECTION_INTERFACE, "State", |
| DBUS_TYPE_STRING, &str); |
| |
| return 0; |
| } |
| |
| static void append_state(DBusMessageIter *iter, |
| struct vpn_provider *provider) |
| { |
| char *str; |
| |
| switch (provider->state) { |
| case VPN_PROVIDER_STATE_UNKNOWN: |
| case VPN_PROVIDER_STATE_IDLE: |
| str = "idle"; |
| break; |
| case VPN_PROVIDER_STATE_CONNECT: |
| str = "configuration"; |
| break; |
| case VPN_PROVIDER_STATE_READY: |
| str = "ready"; |
| break; |
| case VPN_PROVIDER_STATE_DISCONNECT: |
| str = "disconnect"; |
| break; |
| case VPN_PROVIDER_STATE_FAILURE: |
| str = "failure"; |
| break; |
| } |
| |
| connman_dbus_dict_append_basic(iter, "State", |
| DBUS_TYPE_STRING, &str); |
| } |
| |
| static void append_properties(DBusMessageIter *iter, |
| struct vpn_provider *provider) |
| { |
| DBusMessageIter dict; |
| GHashTableIter hash; |
| gpointer value, key; |
| dbus_bool_t immutable; |
| |
| connman_dbus_dict_open(iter, &dict); |
| |
| append_state(&dict, provider); |
| |
| if (provider->type) |
| connman_dbus_dict_append_basic(&dict, "Type", |
| DBUS_TYPE_STRING, &provider->type); |
| |
| if (provider->name) |
| connman_dbus_dict_append_basic(&dict, "Name", |
| DBUS_TYPE_STRING, &provider->name); |
| |
| if (provider->host) |
| connman_dbus_dict_append_basic(&dict, "Host", |
| DBUS_TYPE_STRING, &provider->host); |
| if (provider->index >= 0) |
| connman_dbus_dict_append_basic(&dict, "Index", |
| DBUS_TYPE_INT32, &provider->index); |
| if (provider->domain) |
| connman_dbus_dict_append_basic(&dict, "Domain", |
| DBUS_TYPE_STRING, &provider->domain); |
| |
| immutable = provider->immutable; |
| connman_dbus_dict_append_basic(&dict, "Immutable", DBUS_TYPE_BOOLEAN, |
| &immutable); |
| |
| if (provider->family == AF_INET) |
| connman_dbus_dict_append_dict(&dict, "IPv4", append_ipv4, |
| provider); |
| else if (provider->family == AF_INET6) |
| connman_dbus_dict_append_dict(&dict, "IPv6", append_ipv6, |
| provider); |
| |
| connman_dbus_dict_append_array(&dict, "Nameservers", |
| DBUS_TYPE_STRING, append_dns, provider); |
| |
| connman_dbus_dict_append_array(&dict, "UserRoutes", |
| DBUS_TYPE_DICT_ENTRY, append_routes, |
| provider->user_routes); |
| |
| connman_dbus_dict_append_array(&dict, "ServerRoutes", |
| DBUS_TYPE_DICT_ENTRY, append_routes, |
| provider->routes); |
| |
| if (provider->setting_strings) { |
| g_hash_table_iter_init(&hash, provider->setting_strings); |
| |
| while (g_hash_table_iter_next(&hash, &key, &value)) { |
| struct vpn_setting *setting = value; |
| |
| if (!setting->hide_value && |
| setting->value) |
| connman_dbus_dict_append_basic(&dict, key, |
| DBUS_TYPE_STRING, |
| &setting->value); |
| } |
| } |
| |
| connman_dbus_dict_close(iter, &dict); |
| } |
| |
| static void connection_added_signal(struct vpn_provider *provider) |
| { |
| DBusMessage *signal; |
| DBusMessageIter iter; |
| |
| signal = dbus_message_new_signal(VPN_MANAGER_PATH, |
| VPN_MANAGER_INTERFACE, "ConnectionAdded"); |
| if (!signal) |
| return; |
| |
| dbus_message_iter_init_append(signal, &iter); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, |
| &provider->path); |
| append_properties(&iter, provider); |
| |
| dbus_connection_send(connection, signal, NULL); |
| dbus_message_unref(signal); |
| } |
| |
| static bool check_host(char **hosts, char *host) |
| { |
| int i; |
| |
| if (!hosts) |
| return false; |
| |
| for (i = 0; hosts[i]; i++) { |
| if (g_strcmp0(hosts[i], host) == 0) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void provider_append_routes(gpointer key, gpointer value, |
| gpointer user_data) |
| { |
| struct vpn_route *route = value; |
| struct vpn_provider *provider = user_data; |
| int index = provider->index; |
| |
| if (!handle_routes) |
| return; |
| |
| /* |
| * If the VPN administrator/user has given a route to |
| * VPN server, then we must discard that because the |
| * server cannot be contacted via VPN tunnel. |
| */ |
| if (check_host(provider->host_ip, route->network)) { |
| DBG("Discarding VPN route to %s via %s at index %d", |
| route->network, route->gateway, index); |
| return; |
| } |
| |
| if (route->family == AF_INET6) { |
| unsigned char prefix_len = atoi(route->netmask); |
| |
| connman_inet_add_ipv6_network_route(index, route->network, |
| route->gateway, |
| prefix_len); |
| } else { |
| connman_inet_add_network_route(index, route->network, |
| route->gateway, |
| route->netmask); |
| } |
| } |
| |
| static int set_connected(struct vpn_provider *provider, |
| bool connected) |
| { |
| struct vpn_ipconfig *ipconfig; |
| |
| DBG("provider %p id %s connected %d", provider, |
| provider->identifier, connected); |
| |
| if (connected) { |
| if (provider->family == AF_INET6) |
| ipconfig = provider->ipconfig_ipv6; |
| else |
| ipconfig = provider->ipconfig_ipv4; |
| |
| __vpn_ipconfig_address_add(ipconfig, provider->family); |
| |
| if (handle_routes) |
| __vpn_ipconfig_gateway_add(ipconfig, provider->family); |
| |
| provider_indicate_state(provider, |
| VPN_PROVIDER_STATE_READY); |
| |
| g_hash_table_foreach(provider->routes, provider_append_routes, |
| provider); |
| |
| g_hash_table_foreach(provider->user_routes, |
| provider_append_routes, provider); |
| |
| } else { |
| provider_indicate_state(provider, |
| VPN_PROVIDER_STATE_DISCONNECT); |
| |
| provider_indicate_state(provider, |
| VPN_PROVIDER_STATE_IDLE); |
| } |
| |
| return 0; |
| } |
| |
| int vpn_provider_set_state(struct vpn_provider *provider, |
| enum vpn_provider_state state) |
| { |
| if (!provider) |
| return -EINVAL; |
| |
| switch (state) { |
| case VPN_PROVIDER_STATE_UNKNOWN: |
| return -EINVAL; |
| case VPN_PROVIDER_STATE_IDLE: |
| return set_connected(provider, false); |
| case VPN_PROVIDER_STATE_CONNECT: |
| return provider_indicate_state(provider, state); |
| case VPN_PROVIDER_STATE_READY: |
| return set_connected(provider, true); |
| case VPN_PROVIDER_STATE_DISCONNECT: |
| return provider_indicate_state(provider, state); |
| case VPN_PROVIDER_STATE_FAILURE: |
| return provider_indicate_state(provider, state); |
| } |
| return -EINVAL; |
| } |
| |
| int vpn_provider_indicate_error(struct vpn_provider *provider, |
| enum vpn_provider_error error) |
| { |
| DBG("provider %p id %s error %d", provider, provider->identifier, |
| error); |
| |
| vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE); |
| |
| switch (error) { |
| case VPN_PROVIDER_ERROR_UNKNOWN: |
| case VPN_PROVIDER_ERROR_CONNECT_FAILED: |
| break; |
| |
| case VPN_PROVIDER_ERROR_LOGIN_FAILED: |
| case VPN_PROVIDER_ERROR_AUTH_FAILED: |
| vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int connection_unregister(struct vpn_provider *provider) |
| { |
| DBG("provider %p path %s", provider, provider->path); |
| |
| if (!provider->path) |
| return -EALREADY; |
| |
| g_dbus_unregister_interface(connection, provider->path, |
| VPN_CONNECTION_INTERFACE); |
| |
| g_free(provider->path); |
| provider->path = NULL; |
| |
| return 0; |
| } |
| |
| static int connection_register(struct vpn_provider *provider) |
| { |
| DBG("provider %p path %s", provider, provider->path); |
| |
| if (provider->path) |
| return -EALREADY; |
| |
| provider->path = g_strdup_printf("%s/connection/%s", VPN_PATH, |
| provider->identifier); |
| |
| g_dbus_register_interface(connection, provider->path, |
| VPN_CONNECTION_INTERFACE, |
| connection_methods, connection_signals, |
| NULL, provider, NULL); |
| |
| return 0; |
| } |
| |
| static void unregister_provider(gpointer data) |
| { |
| struct vpn_provider *provider = data; |
| |
| configuration_count_del(); |
| |
| connection_unregister(provider); |
| |
| vpn_provider_unref(provider); |
| } |
| |
| static void provider_initialize(struct vpn_provider *provider) |
| { |
| DBG("provider %p", provider); |
| |
| provider->index = 0; |
| provider->fd = -1; |
| provider->name = NULL; |
| provider->type = NULL; |
| provider->domain = NULL; |
| provider->identifier = NULL; |
| provider->immutable = false; |
| provider->user_networks = NULL; |
| provider->routes = g_hash_table_new_full(g_direct_hash, g_direct_equal, |
| NULL, free_route); |
| provider->user_routes = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, free_route); |
| provider->setting_strings = g_hash_table_new_full(g_str_hash, |
| g_str_equal, g_free, free_setting); |
| } |
| |
| static struct vpn_provider *vpn_provider_new(void) |
| { |
| struct vpn_provider *provider; |
| |
| provider = g_try_new0(struct vpn_provider, 1); |
| if (!provider) |
| return NULL; |
| |
| provider->refcount = 1; |
| |
| DBG("provider %p", provider); |
| provider_initialize(provider); |
| |
| return provider; |
| } |
| |
| static struct vpn_provider *vpn_provider_get(const char *identifier) |
| { |
| struct vpn_provider *provider; |
| |
| provider = g_hash_table_lookup(provider_hash, identifier); |
| if (provider) |
| return provider; |
| |
| provider = vpn_provider_new(); |
| if (!provider) |
| return NULL; |
| |
| DBG("provider %p", provider); |
| |
| provider->identifier = g_strdup(identifier); |
| |
| g_hash_table_insert(provider_hash, provider->identifier, provider); |
| |
| configuration_count_add(); |
| |
| return provider; |
| } |
| |
| static void provider_dbus_ident(char *ident) |
| { |
| int i, len = strlen(ident); |
| |
| for (i = 0; i < len; i++) { |
| if (ident[i] >= '0' && ident[i] <= '9') |
| continue; |
| if (ident[i] >= 'a' && ident[i] <= 'z') |
| continue; |
| if (ident[i] >= 'A' && ident[i] <= 'Z') |
| continue; |
| ident[i] = '_'; |
| } |
| } |
| |
| static struct vpn_provider *provider_create_from_keyfile(GKeyFile *keyfile, |
| const char *ident) |
| { |
| struct vpn_provider *provider; |
| |
| if (!keyfile || !ident) |
| return NULL; |
| |
| provider = __vpn_provider_lookup(ident); |
| if (!provider) { |
| provider = vpn_provider_get(ident); |
| if (!provider) { |
| DBG("can not create provider"); |
| return NULL; |
| } |
| |
| provider_load_from_keyfile(provider, keyfile); |
| |
| if (!provider->name || !provider->host || |
| !provider->domain) { |
| DBG("cannot get name, host or domain"); |
| vpn_provider_unref(provider); |
| return NULL; |
| } |
| |
| if (provider_register(provider) == 0) |
| connection_register(provider); |
| } |
| return provider; |
| } |
| |
| static void provider_create_all_from_type(const char *provider_type) |
| { |
| unsigned int i; |
| char **providers; |
| char *id, *type; |
| GKeyFile *keyfile; |
| |
| DBG("provider type %s", provider_type); |
| |
| providers = __connman_storage_get_providers(); |
| |
| if (!providers) |
| return; |
| |
| for (i = 0; providers[i]; i += 1) { |
| |
| if (strncmp(providers[i], "provider_", 9) != 0) |
| continue; |
| |
| id = providers[i] + 9; |
| keyfile = __connman_storage_load_provider(id); |
| |
| if (!keyfile) |
| continue; |
| |
| type = __vpn_config_get_string(keyfile, id, "Type", NULL); |
| |
| DBG("keyfile %p id %s type %s", keyfile, id, type); |
| |
| if (strcmp(provider_type, type) != 0) { |
| g_free(type); |
| g_key_file_free(keyfile); |
| continue; |
| } |
| |
| if (!provider_create_from_keyfile(keyfile, id)) |
| DBG("could not create provider"); |
| |
| g_free(type); |
| g_key_file_free(keyfile); |
| } |
| g_strfreev(providers); |
| } |
| |
| char *__vpn_provider_create_identifier(const char *host, const char *domain) |
| { |
| char *ident; |
| |
| ident = g_strdup_printf("%s_%s", host, domain); |
| if (!ident) |
| return NULL; |
| |
| provider_dbus_ident(ident); |
| |
| return ident; |
| } |
| |
| int __vpn_provider_create(DBusMessage *msg) |
| { |
| struct vpn_provider *provider; |
| DBusMessageIter iter, array; |
| const char *type = NULL, *name = NULL; |
| const char *host = NULL, *domain = NULL; |
| GSList *networks = NULL; |
| char *ident; |
| int err; |
| |
| dbus_message_iter_init(msg, &iter); |
| dbus_message_iter_recurse(&iter, &array); |
| |
| while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key; |
| |
| dbus_message_iter_recurse(&array, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| switch (dbus_message_iter_get_arg_type(&value)) { |
| case DBUS_TYPE_STRING: |
| if (g_str_equal(key, "Type")) |
| dbus_message_iter_get_basic(&value, &type); |
| else if (g_str_equal(key, "Name")) |
| dbus_message_iter_get_basic(&value, &name); |
| else if (g_str_equal(key, "Host")) |
| dbus_message_iter_get_basic(&value, &host); |
| else if (g_str_equal(key, "VPN.Domain") || |
| g_str_equal(key, "Domain")) |
| dbus_message_iter_get_basic(&value, &domain); |
| break; |
| case DBUS_TYPE_ARRAY: |
| if (g_str_equal(key, "UserRoutes")) |
| networks = get_user_networks(&value); |
| break; |
| } |
| |
| dbus_message_iter_next(&array); |
| } |
| |
| if (!host || !domain) |
| return -EINVAL; |
| |
| DBG("Type %s name %s networks %p", type, name, networks); |
| |
| if (!type || !name) |
| return -EOPNOTSUPP; |
| |
| ident = __vpn_provider_create_identifier(host, domain); |
| DBG("ident %s", ident); |
| |
| provider = __vpn_provider_lookup(ident); |
| if (!provider) { |
| provider = vpn_provider_get(ident); |
| if (!provider) { |
| DBG("can not create provider"); |
| g_free(ident); |
| return -EOPNOTSUPP; |
| } |
| |
| provider->host = g_strdup(host); |
| provider->domain = g_strdup(domain); |
| provider->name = g_strdup(name); |
| provider->type = g_strdup(type); |
| |
| if (provider_register(provider) == 0) |
| vpn_provider_load(provider); |
| |
| provider_resolv_host_addr(provider); |
| } |
| |
| if (networks) { |
| g_slist_free_full(provider->user_networks, free_route); |
| provider->user_networks = networks; |
| set_user_networks(provider, provider->user_networks); |
| } |
| |
| dbus_message_iter_init(msg, &iter); |
| dbus_message_iter_recurse(&iter, &array); |
| |
| while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key, *str; |
| |
| dbus_message_iter_recurse(&array, &entry); |
| dbus_message_iter_get_basic(&entry, &key); |
| |
| dbus_message_iter_next(&entry); |
| dbus_message_iter_recurse(&entry, &value); |
| |
| switch (dbus_message_iter_get_arg_type(&value)) { |
| case DBUS_TYPE_STRING: |
| dbus_message_iter_get_basic(&value, &str); |
| vpn_provider_set_string(provider, key, str); |
| break; |
| } |
| |
| dbus_message_iter_next(&array); |
| } |
| |
| g_free(ident); |
| |
| vpn_provider_save(provider); |
| |
| err = provider_register(provider); |
| if (err != 0 && err != -EALREADY) |
| return err; |
| |
| connection_register(provider); |
| |
| DBG("provider %p index %d path %s", provider, provider->index, |
| provider->path); |
| |
| g_dbus_send_reply(connection, msg, |
| DBUS_TYPE_OBJECT_PATH, &provider->path, |
| DBUS_TYPE_INVALID); |
| |
| connection_added_signal(provider); |
| |
| return 0; |
| } |
| |
| static const char *get_string(GHashTable *settings, const char *key) |
| { |
| DBG("settings %p key %s", settings, key); |
| |
| return g_hash_table_lookup(settings, key); |
| } |
| |
| static GSList *parse_user_networks(const char *network_str) |
| { |
| GSList *networks = NULL; |
| char **elems; |
| int i = 0; |
| |
| if (!network_str) |
| return NULL; |
| |
| elems = g_strsplit(network_str, ",", 0); |
| if (!elems) |
| return NULL; |
| |
| while (elems[i]) { |
| struct vpn_route *vpn_route; |
| char *network, *netmask, *gateway; |
| int family; |
| char **route; |
| |
| route = g_strsplit(elems[i], "/", 0); |
| if (!route) |
| goto next; |
| |
| network = route[0]; |
| if (!network || network[0] == '\0') |
| goto next; |
| |
| family = connman_inet_check_ipaddress(network); |
| if (family < 0) { |
| DBG("Cannot get address family of %s (%d/%s)", network, |
| family, gai_strerror(family)); |
| |
| goto next; |
| } |
| |
| switch (family) { |
| case AF_INET: |
| break; |
| case AF_INET6: |
| break; |
| default: |
| DBG("Unsupported address family %d", family); |
| goto next; |
| } |
| |
| netmask = route[1]; |
| if (!netmask || netmask[0] == '\0') |
| goto next; |
| |
| gateway = route[2]; |
| |
| vpn_route = g_try_new0(struct vpn_route, 1); |
| if (!vpn_route) { |
| g_strfreev(route); |
| break; |
| } |
| |
| vpn_route->family = family; |
| vpn_route->network = g_strdup(network); |
| vpn_route->netmask = g_strdup(netmask); |
| vpn_route->gateway = g_strdup(gateway); |
| |
| DBG("route %s/%s%s%s", network, netmask, |
| gateway ? " via " : "", gateway ? gateway : ""); |
| |
| networks = g_slist_prepend(networks, vpn_route); |
| |
| next: |
| g_strfreev(route); |
| i++; |
| } |
| |
| g_strfreev(elems); |
| |
| return g_slist_reverse(networks); |
| } |
| |
| int __vpn_provider_create_from_config(GHashTable *settings, |
| const char *config_ident, |
| const char *config_entry) |
| { |
| struct vpn_provider *provider; |
| const char *type, *name, *host, *domain, *networks_str; |
| GSList *networks; |
| char *ident = NULL; |
| GHashTableIter hash; |
| gpointer value, key; |
| int err; |
| |
| type = get_string(settings, "Type"); |
| name = get_string(settings, "Name"); |
| host = get_string(settings, "Host"); |
| domain = get_string(settings, "Domain"); |
| networks_str = get_string(settings, "Networks"); |
| networks = parse_user_networks(networks_str); |
| |
| if (!host || !domain) { |
| err = -EINVAL; |
| goto fail; |
| } |
| |
| DBG("type %s name %s networks %s", type, name, networks_str); |
| |
| if (!type || !name) { |
| err = -EOPNOTSUPP; |
| goto fail; |
| } |
| |
| ident = __vpn_provider_create_identifier(host, domain); |
| DBG("ident %s", ident); |
| |
| provider = __vpn_provider_lookup(ident); |
| if (!provider) { |
| provider = vpn_provider_get(ident); |
| if (!provider) { |
| DBG("can not create provider"); |
| err = -EOPNOTSUPP; |
| goto fail; |
| } |
| |
| provider->host = g_strdup(host); |
| provider->domain = g_strdup(domain); |
| provider->name = g_strdup(name); |
| provider->type = g_ascii_strdown(type, -1); |
| |
| provider->config_file = g_strdup(config_ident); |
| provider->config_entry = g_strdup(config_entry); |
| |
| provider_register(provider); |
| |
| provider_resolv_host_addr(provider); |
| } |
| |
| if (networks) { |
| g_slist_free_full(provider->user_networks, free_route); |
| provider->user_networks = networks; |
| set_user_networks(provider, provider->user_networks); |
| } |
| |
| g_hash_table_iter_init(&hash, settings); |
| |
| while (g_hash_table_iter_next(&hash, &key, &value)) |
| __vpn_provider_set_string_immutable(provider, key, value); |
| |
| provider->immutable = true; |
| |
| vpn_provider_save(provider); |
| |
| err = provider_register(provider); |
| if (err != 0 && err != -EALREADY) |
| goto fail; |
| |
| connection_register(provider); |
| |
| DBG("provider %p index %d path %s", provider, provider->index, |
| provider->path); |
| |
| connection_added_signal(provider); |
| |
| g_free(ident); |
| |
| return 0; |
| |
| fail: |
| g_free(ident); |
| g_slist_free_full(networks, free_route); |
| |
| return err; |
| } |
| |
| static void append_connection_structs(DBusMessageIter *iter, void *user_data) |
| { |
| DBusMessageIter entry; |
| GHashTableIter hash; |
| gpointer value, key; |
| |
| g_hash_table_iter_init(&hash, provider_hash); |
| |
| while (g_hash_table_iter_next(&hash, &key, &value)) { |
| struct vpn_provider *provider = value; |
| |
| DBG("path %s", provider->path); |
| |
| if (!provider->identifier) |
| continue; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, |
| NULL, &entry); |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, |
| &provider->path); |
| append_properties(&entry, provider); |
| dbus_message_iter_close_container(iter, &entry); |
| } |
| } |
| |
| DBusMessage *__vpn_provider_get_connections(DBusMessage *msg) |
| { |
| DBusMessage *reply; |
| |
| DBG(""); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return NULL; |
| |
| __connman_dbus_append_objpath_dict_array(reply, |
| append_connection_structs, NULL); |
| |
| return reply; |
| } |
| |
| const char *__vpn_provider_get_ident(struct vpn_provider *provider) |
| { |
| if (!provider) |
| return NULL; |
| |
| return provider->identifier; |
| } |
| |
| static int set_string(struct vpn_provider *provider, |
| const char *key, const char *value, |
| bool hide_value, bool immutable) |
| { |
| DBG("provider %p key %s immutable %s value %s", provider, key, |
| immutable ? "yes" : "no", |
| hide_value ? "<not printed>" : value); |
| |
| if (g_str_equal(key, "Type")) { |
| g_free(provider->type); |
| provider->type = g_ascii_strdown(value, -1); |
| send_value(provider->path, "Type", provider->type); |
| } else if (g_str_equal(key, "Name")) { |
| g_free(provider->name); |
| provider->name = g_strdup(value); |
| send_value(provider->path, "Name", provider->name); |
| } else if (g_str_equal(key, "Host")) { |
| g_free(provider->host); |
| provider->host = g_strdup(value); |
| send_value(provider->path, "Host", provider->host); |
| } else if (g_str_equal(key, "VPN.Domain") || |
| g_str_equal(key, "Domain")) { |
| g_free(provider->domain); |
| provider->domain = g_strdup(value); |
| send_value(provider->path, "Domain", provider->domain); |
| } else { |
| struct vpn_setting *setting; |
| |
| setting = g_hash_table_lookup(provider->setting_strings, key); |
| if (setting && !immutable && |
| setting->immutable) { |
| DBG("Trying to set immutable variable %s", key); |
| return -EPERM; |
| } |
| |
| setting = g_try_new0(struct vpn_setting, 1); |
| if (!setting) |
| return -ENOMEM; |
| |
| setting->value = g_strdup(value); |
| setting->hide_value = hide_value; |
| |
| if (immutable) |
| setting->immutable = true; |
| |
| if (!hide_value) |
| send_value(provider->path, key, setting->value); |
| |
| g_hash_table_replace(provider->setting_strings, |
| g_strdup(key), setting); |
| } |
| |
| return 0; |
| } |
| |
| int vpn_provider_set_string(struct vpn_provider *provider, |
| const char *key, const char *value) |
| { |
| return set_string(provider, key, value, false, false); |
| } |
| |
| int vpn_provider_set_string_hide_value(struct vpn_provider *provider, |
| const char *key, const char *value) |
| { |
| return set_string(provider, key, value, true, false); |
| } |
| |
| int __vpn_provider_set_string_immutable(struct vpn_provider *provider, |
| const char *key, const char *value) |
| { |
| return set_string(provider, key, value, false, true); |
| } |
| |
| const char *vpn_provider_get_string(struct vpn_provider *provider, |
| const char *key) |
| { |
| struct vpn_setting *setting; |
| |
| DBG("provider %p key %s", provider, key); |
| |
| if (g_str_equal(key, "Type")) |
| return provider->type; |
| else if (g_str_equal(key, "Name")) |
| return provider->name; |
| else if (g_str_equal(key, "Host")) |
| return provider->host; |
| else if (g_str_equal(key, "HostIP")) { |
| if (!provider->host_ip || |
| !provider->host_ip[0]) |
| return provider->host; |
| else |
| return provider->host_ip[0]; |
| } else if (g_str_equal(key, "VPN.Domain") || |
| g_str_equal(key, "Domain")) |
| return provider->domain; |
| |
| setting = g_hash_table_lookup(provider->setting_strings, key); |
| if (!setting) |
| return NULL; |
| |
| return setting->value; |
| } |
| |
| bool __vpn_provider_check_routes(struct vpn_provider *provider) |
| { |
| if (!provider) |
| return false; |
| |
| if (provider->user_routes && |
| g_hash_table_size(provider->user_routes) > 0) |
| return true; |
| |
| if (provider->routes && |
| g_hash_table_size(provider->routes) > 0) |
| return true; |
| |
| return false; |
| } |
| |
| void *vpn_provider_get_data(struct vpn_provider *provider) |
| { |
| return provider->driver_data; |
| } |
| |
| void vpn_provider_set_data(struct vpn_provider *provider, void *data) |
| { |
| provider->driver_data = data; |
| } |
| |
| void vpn_provider_set_index(struct vpn_provider *provider, int index) |
| { |
| DBG("index %d provider %p", index, provider); |
| |
| if (!provider->ipconfig_ipv4) { |
| provider->ipconfig_ipv4 = __vpn_ipconfig_create(index, |
| AF_INET); |
| if (!provider->ipconfig_ipv4) { |
| DBG("Couldnt create ipconfig for IPv4"); |
| goto done; |
| } |
| } |
| |
| __vpn_ipconfig_set_index(provider->ipconfig_ipv4, index); |
| |
| if (!provider->ipconfig_ipv6) { |
| provider->ipconfig_ipv6 = __vpn_ipconfig_create(index, |
| AF_INET6); |
| if (!provider->ipconfig_ipv6) { |
| DBG("Couldnt create ipconfig for IPv6"); |
| goto done; |
| } |
| } |
| |
| __vpn_ipconfig_set_index(provider->ipconfig_ipv6, index); |
| |
| done: |
| provider->index = index; |
| } |
| |
| int vpn_provider_get_index(struct vpn_provider *provider) |
| { |
| return provider->index; |
| } |
| |
| int vpn_provider_set_ipaddress(struct vpn_provider *provider, |
| struct connman_ipaddress *ipaddress) |
| { |
| struct vpn_ipconfig *ipconfig = NULL; |
| |
| switch (ipaddress->family) { |
| case AF_INET: |
| ipconfig = provider->ipconfig_ipv4; |
| break; |
| case AF_INET6: |
| ipconfig = provider->ipconfig_ipv6; |
| break; |
| default: |
| break; |
| } |
| |
| DBG("provider %p state %d ipconfig %p family %d", provider, |
| provider->state, ipconfig, ipaddress->family); |
| |
| if (!ipconfig) |
| return -EINVAL; |
| |
| provider->family = ipaddress->family; |
| |
| if (provider->state == VPN_PROVIDER_STATE_CONNECT || |
| provider->state == VPN_PROVIDER_STATE_READY) { |
| struct connman_ipaddress *addr = |
| __vpn_ipconfig_get_address(ipconfig); |
| |
| /* |
| * Remember the old address so that we can remove it in notify |
| * function in plugins/vpn.c if we ever restart |
| */ |
| if (ipaddress->family == AF_INET6) { |
| connman_ipaddress_free(provider->prev_ipv6_addr); |
| provider->prev_ipv6_addr = |
| connman_ipaddress_copy(addr); |
| } else { |
| connman_ipaddress_free(provider->prev_ipv4_addr); |
| provider->prev_ipv4_addr = |
| connman_ipaddress_copy(addr); |
| } |
| } |
| |
| if (ipaddress->local) { |
| __vpn_ipconfig_set_local(ipconfig, ipaddress->local); |
| __vpn_ipconfig_set_peer(ipconfig, ipaddress->peer); |
| __vpn_ipconfig_set_broadcast(ipconfig, ipaddress->broadcast); |
| __vpn_ipconfig_set_gateway(ipconfig, ipaddress->gateway); |
| __vpn_ipconfig_set_prefixlen(ipconfig, ipaddress->prefixlen); |
| } |
| |
| return 0; |
| } |
| |
| int vpn_provider_set_pac(struct vpn_provider *provider, |
| const char *pac) |
| { |
| DBG("provider %p pac %s", provider, pac); |
| |
| return 0; |
| } |
| |
| |
| int vpn_provider_set_domain(struct vpn_provider *provider, |
| const char *domain) |
| { |
| DBG("provider %p domain %s", provider, domain); |
| |
| g_free(provider->domain); |
| provider->domain = g_strdup(domain); |
| |
| return 0; |
| } |
| |
| int vpn_provider_set_nameservers(struct vpn_provider *provider, |
| const char *nameservers) |
| { |
| DBG("provider %p nameservers %s", provider, nameservers); |
| |
| g_strfreev(provider->nameservers); |
| provider->nameservers = NULL; |
| |
| if (!nameservers) |
| return 0; |
| |
| provider->nameservers = g_strsplit(nameservers, " ", 0); |
| |
| return 0; |
| } |
| |
| enum provider_route_type { |
| PROVIDER_ROUTE_TYPE_NONE = 0, |
| PROVIDER_ROUTE_TYPE_MASK = 1, |
| PROVIDER_ROUTE_TYPE_ADDR = 2, |
| PROVIDER_ROUTE_TYPE_GW = 3, |
| }; |
| |
| static int route_env_parse(struct vpn_provider *provider, const char *key, |
| int *family, unsigned long *idx, |
| enum provider_route_type *type) |
| { |
| char *end; |
| const char *start; |
| |
| DBG("name %s", provider->name); |
| |
| if (!strcmp(provider->type, "openvpn")) { |
| if (g_str_has_prefix(key, "route_network_")) { |
| start = key + strlen("route_network_"); |
| *type = PROVIDER_ROUTE_TYPE_ADDR; |
| } else if (g_str_has_prefix(key, "route_netmask_")) { |
| start = key + strlen("route_netmask_"); |
| *type = PROVIDER_ROUTE_TYPE_MASK; |
| } else if (g_str_has_prefix(key, "route_gateway_")) { |
| start = key + strlen("route_gateway_"); |
| *type = PROVIDER_ROUTE_TYPE_GW; |
| } else |
| return -EINVAL; |
| |
| *family = AF_INET; |
| *idx = g_ascii_strtoull(start, &end, 10); |
| |
| } else if (!strcmp(provider->type, "openconnect")) { |
| if (g_str_has_prefix(key, "CISCO_SPLIT_INC_")) { |
| *family = AF_INET; |
| start = key + strlen("CISCO_SPLIT_INC_"); |
| } else if (g_str_has_prefix(key, |
| "CISCO_IPV6_SPLIT_INC_")) { |
| *family = AF_INET6; |
| start = key + strlen("CISCO_IPV6_SPLIT_INC_"); |
| } else |
| return -EINVAL; |
| |
| *idx = g_ascii_strtoull(start, &end, 10); |
| |
| if (strncmp(end, "_ADDR", 5) == 0) |
| *type = PROVIDER_ROUTE_TYPE_ADDR; |
| else if (strncmp(end, "_MASK", 5) == 0) |
| *type = PROVIDER_ROUTE_TYPE_MASK; |
| else if (strncmp(end, "_MASKLEN", 8) == 0 && |
| *family == AF_INET6) { |
| *type = PROVIDER_ROUTE_TYPE_MASK; |
| } else |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int vpn_provider_append_route(struct vpn_provider *provider, |
| const char *key, const char *value) |
| { |
| struct vpn_route *route; |
| int ret, family = 0; |
| unsigned long idx = 0; |
| enum provider_route_type type = PROVIDER_ROUTE_TYPE_NONE; |
| |
| DBG("key %s value %s", key, value); |
| |
| ret = route_env_parse(provider, key, &family, &idx, &type); |
| if (ret < 0) |
| return ret; |
| |
| DBG("idx %lu family %d type %d", idx, family, type); |
| |
| route = g_hash_table_lookup(provider->routes, GINT_TO_POINTER(idx)); |
| if (!route) { |
| route = g_try_new0(struct vpn_route, 1); |
| if (!route) { |
| connman_error("out of memory"); |
| return -ENOMEM; |
| } |
| |
| route->family = family; |
| |
| g_hash_table_replace(provider->routes, GINT_TO_POINTER(idx), |
| route); |
| } |
| |
| switch (type) { |
| case PROVIDER_ROUTE_TYPE_NONE: |
| break; |
| case PROVIDER_ROUTE_TYPE_MASK: |
| route->netmask = g_strdup(value); |
| break; |
| case PROVIDER_ROUTE_TYPE_ADDR: |
| route->network = g_strdup(value); |
| break; |
| case PROVIDER_ROUTE_TYPE_GW: |
| route->gateway = g_strdup(value); |
| break; |
| } |
| |
| if (!handle_routes) { |
| if (route->netmask && route->gateway && |
| route->network) |
| provider_schedule_changed(provider); |
| } |
| |
| return 0; |
| } |
| |
| const char *vpn_provider_get_driver_name(struct vpn_provider *provider) |
| { |
| if (!provider->driver) |
| return NULL; |
| |
| return provider->driver->name; |
| } |
| |
| const char *vpn_provider_get_save_group(struct vpn_provider *provider) |
| { |
| return provider->identifier; |
| } |
| |
| static gint compare_priority(gconstpointer a, gconstpointer b) |
| { |
| return 0; |
| } |
| |
| static void clean_provider(gpointer key, gpointer value, gpointer user_data) |
| { |
| struct vpn_provider *provider = value; |
| |
| if (provider->driver && provider->driver->remove) |
| provider->driver->remove(provider); |
| |
| connection_unregister(provider); |
| } |
| |
| int vpn_provider_driver_register(struct vpn_provider_driver *driver) |
| { |
| DBG("driver %p name %s", driver, driver->name); |
| |
| driver_list = g_slist_insert_sorted(driver_list, driver, |
| compare_priority); |
| provider_create_all_from_type(driver->name); |
| return 0; |
| } |
| |
| void vpn_provider_driver_unregister(struct vpn_provider_driver *driver) |
| { |
| GHashTableIter iter; |
| gpointer value, key; |
| |
| DBG("driver %p name %s", driver, driver->name); |
| |
| driver_list = g_slist_remove(driver_list, driver); |
| |
| g_hash_table_iter_init(&iter, provider_hash); |
| while (g_hash_table_iter_next(&iter, &key, &value)) { |
| struct vpn_provider *provider = value; |
| |
| if (provider && provider->driver && |
| provider->driver->type == driver->type && |
| g_strcmp0(provider->driver->name, |
| driver->name) == 0) { |
| provider->driver = NULL; |
| } |
| } |
| } |
| |
| const char *vpn_provider_get_name(struct vpn_provider *provider) |
| { |
| return provider->name; |
| } |
| |
| const char *vpn_provider_get_host(struct vpn_provider *provider) |
| { |
| return provider->host; |
| } |
| |
| const char *vpn_provider_get_path(struct vpn_provider *provider) |
| { |
| return provider->path; |
| } |
| |
| void vpn_provider_change_address(struct vpn_provider *provider) |
| { |
| switch (provider->family) { |
| case AF_INET: |
| connman_inet_set_address(provider->index, |
| __vpn_ipconfig_get_address(provider->ipconfig_ipv4)); |
| break; |
| case AF_INET6: |
| connman_inet_set_ipv6_address(provider->index, |
| __vpn_ipconfig_get_address(provider->ipconfig_ipv6)); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void vpn_provider_clear_address(struct vpn_provider *provider, int family) |
| { |
| const char *address; |
| unsigned char len; |
| |
| DBG("provider %p family %d ipv4 %p ipv6 %p", provider, family, |
| provider->prev_ipv4_addr, provider->prev_ipv6_addr); |
| |
| switch (family) { |
| case AF_INET: |
| if (provider->prev_ipv4_addr) { |
| connman_ipaddress_get_ip(provider->prev_ipv4_addr, |
| &address, &len); |
| |
| DBG("ipv4 %s/%d", address, len); |
| |
| connman_inet_clear_address(provider->index, |
| provider->prev_ipv4_addr); |
| connman_ipaddress_free(provider->prev_ipv4_addr); |
| provider->prev_ipv4_addr = NULL; |
| } |
| break; |
| case AF_INET6: |
| if (provider->prev_ipv6_addr) { |
| connman_ipaddress_get_ip(provider->prev_ipv6_addr, |
| &address, &len); |
| |
| DBG("ipv6 %s/%d", address, len); |
| |
| connman_inet_clear_ipv6_address(provider->index, |
| address, len); |
| |
| connman_ipaddress_free(provider->prev_ipv6_addr); |
| provider->prev_ipv6_addr = NULL; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static int agent_probe(struct connman_agent *agent) |
| { |
| DBG("agent %p", agent); |
| return 0; |
| } |
| |
| static void agent_remove(struct connman_agent *agent) |
| { |
| DBG("agent %p", agent); |
| } |
| |
| static struct connman_agent_driver agent_driver = { |
| .name = "vpn", |
| .interface = VPN_AGENT_INTERFACE, |
| .probe = agent_probe, |
| .remove = agent_remove, |
| }; |
| |
| static void remove_unprovisioned_providers(void) |
| { |
| gchar **providers; |
| GKeyFile *keyfile, *configkeyfile; |
| char *file, *section; |
| int i = 0; |
| |
| providers = __connman_storage_get_providers(); |
| if (!providers) |
| return; |
| |
| for (; providers[i]; i++) { |
| char *group = providers[i] + sizeof("provider_") - 1; |
| file = section = NULL; |
| keyfile = configkeyfile = NULL; |
| |
| keyfile = __connman_storage_load_provider(group); |
| if (!keyfile) |
| continue; |
| |
| file = __vpn_config_get_string(keyfile, group, |
| "Config.file", NULL); |
| if (!file) |
| goto next; |
| |
| section = __vpn_config_get_string(keyfile, group, |
| "Config.ident", NULL); |
| if (!section) |
| goto next; |
| |
| configkeyfile = __connman_storage_load_provider_config(file); |
| if (!configkeyfile) { |
| /* |
| * Config file is missing, remove the provisioned |
| * service. |
| */ |
| __connman_storage_remove_provider(group); |
| goto next; |
| } |
| |
| if (!g_key_file_has_group(configkeyfile, section)) |
| /* |
| * Config section is missing, remove the provisioned |
| * service. |
| */ |
| __connman_storage_remove_provider(group); |
| |
| next: |
| if (keyfile) |
| g_key_file_free(keyfile); |
| |
| if (configkeyfile) |
| g_key_file_free(configkeyfile); |
| |
| g_free(section); |
| g_free(file); |
| } |
| |
| g_strfreev(providers); |
| } |
| |
| int __vpn_provider_init(bool do_routes) |
| { |
| int err; |
| |
| DBG(""); |
| |
| handle_routes = do_routes; |
| |
| err = connman_agent_driver_register(&agent_driver); |
| if (err < 0) { |
| connman_error("Cannot register agent driver for %s", |
| agent_driver.name); |
| return err; |
| } |
| |
| connection = connman_dbus_get_connection(); |
| |
| remove_unprovisioned_providers(); |
| |
| provider_hash = g_hash_table_new_full(g_str_hash, g_str_equal, |
| NULL, unregister_provider); |
| return 0; |
| } |
| |
| void __vpn_provider_cleanup(void) |
| { |
| DBG(""); |
| |
| connman_agent_driver_unregister(&agent_driver); |
| |
| g_hash_table_foreach(provider_hash, clean_provider, NULL); |
| |
| g_hash_table_destroy(provider_hash); |
| provider_hash = NULL; |
| |
| dbus_connection_unref(connection); |
| } |