X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=vpn%2Fvpn-provider.c;h=5ce932872bbb304cfc6484d48cd090de9bdf37a1;hb=dd3cccc5e67548dcc2dd6c6254ed6c97859085d5;hp=dd54ac08a14cce639aa74f06686c533a4152446b;hpb=6b2381a2adabea7d8309ff158ef675ff88184305;p=platform%2Fupstream%2Fconnman.git diff --git a/vpn/vpn-provider.c b/vpn/vpn-provider.c index dd54ac0..5ce9328 100644 --- a/vpn/vpn-provider.c +++ b/vpn/vpn-provider.c @@ -3,6 +3,7 @@ * ConnMan VPN daemon * * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Jolla Ltd. 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 @@ -87,10 +88,14 @@ struct vpn_provider { bool immutable; struct connman_ipaddress *prev_ipv4_addr; struct connman_ipaddress *prev_ipv6_addr; + void *plugin_data; + unsigned int auth_error_counter; + unsigned int conn_error_counter; }; static void append_properties(DBusMessageIter *iter, struct vpn_provider *provider); +static int vpn_provider_save(struct vpn_provider *provider); static void free_route(gpointer data) { @@ -190,6 +195,39 @@ static int provider_routes_changed(struct vpn_provider *provider) return 0; } +/* + * Sort vpn_route struct based on (similarly to the route key in hash table): + * 1) IP protocol number + * 2) Network addresses + * 3) Netmask addresses + * 4) Gateway addresses + */ +static gint compare_route(gconstpointer a, gconstpointer b) +{ + const struct vpn_route *route_a = a; + const struct vpn_route *route_b = b; + int difference; + + /* If IP families differ, prefer IPv6 over IPv4 */ + if (route_a->family != route_b->family) { + if (route_a->family < route_b->family) + return -1; + + if (route_a->family > route_b->family) + return 1; + } + + /* If networks differ, return */ + if ((difference = g_strcmp0(route_a->network, route_b->network))) + return difference; + + /* If netmasks differ, return. */ + if ((difference = g_strcmp0(route_a->netmask, route_b->netmask))) + return difference; + + return g_strcmp0(route_a->gateway, route_b->gateway); +} + static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts) { DBusMessageIter dict, value, entry; @@ -258,9 +296,11 @@ static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts) } else { switch (family) { case '4': + case 4: family = AF_INET; break; case '6': + case 6: family = AF_INET6; break; default: @@ -274,7 +314,7 @@ static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts) route->netmask = g_strdup(netmask); route->gateway = g_strdup(gateway); - routes = g_slist_prepend(routes, route); + routes = g_slist_insert_sorted(routes, route, compare_route); return routes; } @@ -404,6 +444,189 @@ static DBusMessage *get_properties(DBusConnection *conn, return reply; } +/* True when lists are equal, false otherwise */ +static bool compare_network_lists(GSList *a, GSList *b) +{ + struct vpn_route *route_a, *route_b; + GSList *iter_a, *iter_b; + + if (!a && !b) + return true; + + /* + * If either of lists is NULL or the lists are of different size, the + * lists are not equal. + */ + if ((!a || !b) || (g_slist_length(a) != g_slist_length(b))) + return false; + + /* Routes are in sorted list so items can be compared in order. */ + for (iter_a = a, iter_b = b; iter_a && iter_b; + iter_a = iter_a->next, iter_b = iter_b->next) { + + route_a = iter_a->data; + route_b = iter_b->data; + + if (compare_route(route_a, route_b)) + return false; + } + + return true; +} + +static int set_provider_property(struct vpn_provider *provider, + const char *name, DBusMessageIter *value, int type) +{ + int err = 0; + + DBG("provider %p", provider); + + if (!provider || !name || !value) + return -EINVAL; + + if (g_str_equal(name, "UserRoutes")) { + GSList *networks; + + if (type != DBUS_TYPE_ARRAY) + return -EINVAL; + + networks = get_user_networks(value); + + if (compare_network_lists(provider->user_networks, networks)) { + g_slist_free_full(networks, free_route); + return -EALREADY; + } + + 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; + + if (type != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(value, &str); + + DBG("property %s value %s", name, str); + + /* Empty string clears the value, similar to ClearProperty. */ + err = vpn_provider_set_string(provider, name, + *str ? str : NULL); + } + + return err; +} + +static GString *append_to_gstring(GString *str, const char *value) +{ + if (!str) + return g_string_new(value); + + g_string_append_printf(str, ",%s", value); + + return str; +} + +static DBusMessage *set_properties(DBusMessageIter *iter, DBusMessage *msg, + void *data) +{ + struct vpn_provider *provider = data; + DBusMessageIter dict; + const char *key; + bool change = false; + GString *invalid = NULL; + GString *denied = NULL; + int type; + int err; + + for (dbus_message_iter_recurse(iter, &dict); + dbus_message_iter_get_arg_type(&dict) == + DBUS_TYPE_DICT_ENTRY; + dbus_message_iter_next(&dict)) { + DBusMessageIter entry, value; + + dbus_message_iter_recurse(&dict, &entry); + /* + * Ignore invalid types in order to process all values in the + * dict. If there is an invalid type in between the dict there + * may already be changes on some values and breaking out here + * would have the provider in an inconsistent state, leaving + * the rest, potentially correct property values untouched. + */ + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + continue; + + dbus_message_iter_get_basic(&entry, &key); + + DBG("key %s", key); + + dbus_message_iter_next(&entry); + /* Ignore and report back all non variant types. */ + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) { + invalid = append_to_gstring(invalid, key); + continue; + } + + dbus_message_iter_recurse(&entry, &value); + + type = dbus_message_iter_get_arg_type(&value); + /* Ignore and report back all invalid property types */ + if (type != DBUS_TYPE_STRING && type != DBUS_TYPE_ARRAY) { + invalid = append_to_gstring(invalid, key); + continue; + } + + err = set_provider_property(provider, key, &value, type); + switch (err) { + case 0: + change = true; + break; + case -EINVAL: + invalid = append_to_gstring(invalid, key); + break; + case -EPERM: + denied = append_to_gstring(denied, key); + break; + } + } + + if (change) + vpn_provider_save(provider); + + if (invalid || denied) { + DBusMessage *error; + char *invalid_str = g_string_free(invalid, FALSE); + char *denied_str = g_string_free(denied, FALSE); + + /* + * If there are both invalid and denied properties report + * back invalid arguments. Add also the failed properties to + * the error message. + */ + error = g_dbus_create_error(msg, (invalid ? + CONNMAN_ERROR_INTERFACE ".InvalidProperty" : + CONNMAN_ERROR_INTERFACE ".PermissionDenied"), + "%s %s%s%s", (invalid ? "Invalid properties" : + "Permission denied"), + (invalid ? invalid_str : ""), + (invalid && denied ? "," : ""), + (denied ? denied_str : "")); + + g_free(invalid_str); + g_free(denied_str); + + return error; + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, void *data) { @@ -411,6 +634,7 @@ static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, DBusMessageIter iter, value; const char *name; int type; + int err; DBG("conn %p", conn); @@ -432,28 +656,20 @@ static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, dbus_message_iter_recurse(&iter, &value); type = dbus_message_iter_get_arg_type(&value); + if (type == DBUS_TYPE_ARRAY && g_str_equal(name, "Properties")) + return set_properties(&value, msg, data); - 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); + err = set_provider_property(provider, name, &value, type); + switch (err) { + case 0: + vpn_provider_save(provider); + break; + case -EALREADY: + break; + case -EINVAL: + return __connman_error_invalid_property(msg); + default: + return __connman_error_failed(msg, -err); } return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); @@ -464,6 +680,8 @@ static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg, { struct vpn_provider *provider = data; const char *name; + bool change = false; + int err; DBG("conn %p", conn); @@ -474,16 +692,38 @@ static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg, DBUS_TYPE_INVALID); if (g_str_equal(name, "UserRoutes")) { + /* + * If either user_routes or user_networks has any entries + * there is a change that is to be written to settings file. + */ + if (g_hash_table_size(provider->user_routes) || + provider->user_networks) + change = true; + 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); + err = vpn_provider_set_string(provider, name, NULL); + switch (err) { + case 0: + change = true; + /* fall through */ + case -EALREADY: + break; + case -EINVAL: + return __connman_error_invalid_property(msg); + default: + return __connman_error_failed(msg, -err); + } } else { return __connman_error_invalid_property(msg); } + if (change) + vpn_provider_save(provider); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } @@ -496,7 +736,7 @@ static DBusMessage *do_connect(DBusConnection *conn, DBusMessage *msg, DBG("conn %p provider %p", conn, provider); err = __vpn_provider_connect(provider, msg); - if (err < 0) + if (err < 0 && err != -EINPROGRESS) return __connman_error_failed(msg, -err); return NULL; @@ -855,6 +1095,14 @@ static gchar **create_network_list(GSList *networks, gsize *count) return result; } +static void reset_error_counters(struct vpn_provider *provider) +{ + if (!provider) + return; + + provider->auth_error_counter = provider->conn_error_counter = 0; +} + static int vpn_provider_save(struct vpn_provider *provider) { GKeyFile *keyfile; @@ -862,6 +1110,11 @@ static int vpn_provider_save(struct vpn_provider *provider) DBG("provider %p immutable %s", provider, provider->immutable ? "yes" : "no"); + reset_error_counters(provider); + + if (provider->state == VPN_PROVIDER_STATE_FAILURE) + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE); + if (provider->immutable) { /* * Do not save providers that are provisioned via .config @@ -1085,20 +1338,111 @@ static void connect_cb(struct vpn_provider *provider, void *user_data, if (reply) g_dbus_send_message(connection, reply); - vpn_provider_indicate_error(provider, + switch (error) { + case EACCES: + vpn_provider_indicate_error(provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + break; + case ENOENT: + /* + * No reply, disconnect called by connmand because of + * connection timeout. + */ + break; + case ENOMSG: + /* fall through */ + case ETIMEDOUT: + /* No reply or timed out -> cancel the agent request */ + connman_agent_cancel(provider); + vpn_provider_indicate_error(provider, + VPN_PROVIDER_ERROR_UNKNOWN); + break; + case ECANCELED: + /* fall through */ + case ECONNABORTED: + /* + * This can be called in other situations than when + * VPN agent error checker is called. In such case + * react to both ECONNABORTED and ECANCELED as if the + * connection was called to terminate and do full + * disconnect -> idle cycle when being connected or + * ready. Setting the state also using the driver + * callback (vpn_set_state()) ensures that the driver is + * being disconnected as well and eventually the vpn + * process gets killed and vpn_died() is called to make + * the provider back to idle state. + */ + if (provider->state == VPN_PROVIDER_STATE_CONNECT || + provider->state == + VPN_PROVIDER_STATE_READY) { + if (provider->driver->set_state) + provider->driver->set_state(provider, + VPN_PROVIDER_STATE_DISCONNECT); + + vpn_provider_set_state(provider, + VPN_PROVIDER_STATE_DISCONNECT); + } + break; + default: + vpn_provider_indicate_error(provider, VPN_PROVIDER_ERROR_CONNECT_FAILED); - vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE); - } else + vpn_provider_set_state(provider, + VPN_PROVIDER_STATE_FAILURE); + } + } else { + reset_error_counters(provider); g_dbus_send_reply(connection, pending, DBUS_TYPE_INVALID); + } dbus_message_unref(pending); } int __vpn_provider_connect(struct vpn_provider *provider, DBusMessage *msg) { + DBusMessage *reply; int err; - DBG("provider %p", provider); + DBG("provider %p state %d", provider, provider->state); + + switch (provider->state) { + /* + * When previous connection has failed change state to idle and let + * the connmand to process this information as well. Return -EINPROGRESS + * to indicate that transition is in progress and next connection + * attempt will continue as normal. + */ + case VPN_PROVIDER_STATE_FAILURE: + if (provider->driver && provider->driver->set_state) + provider->driver->set_state(provider, + VPN_PROVIDER_STATE_IDLE); + + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE); + /* fall through */ + /* + * If re-using a provider and it is being disconnected let it finish + * the disconnect process in order to let vpn.c:vpn_died() to get + * processed and everything cleaned up. Otherwise the reference + * counters are not decreased properly causing the previous interface + * being left up and its routes will remain in routing table. Return + * -EINPROGRESS to indicate that transition is in progress. + */ + case VPN_PROVIDER_STATE_DISCONNECT: + /* + * Failure transition or disconnecting does not yield a + * message to be sent. Send in progress message to avoid + * D-Bus LimitsExceeded error message. + */ + reply = __connman_error_in_progress(msg); + if (reply) + g_dbus_send_message(connection, reply); + + return -EINPROGRESS; + case VPN_PROVIDER_STATE_UNKNOWN: + case VPN_PROVIDER_STATE_IDLE: + case VPN_PROVIDER_STATE_CONNECT: + case VPN_PROVIDER_STATE_READY: + break; + } if (provider->driver && provider->driver->connect) { const char *dbus_sender = dbus_message_get_sender(msg); @@ -1573,6 +1917,22 @@ int vpn_provider_set_state(struct vpn_provider *provider, return -EINVAL; } +void vpn_provider_add_error(struct vpn_provider *provider, + enum vpn_provider_error error) +{ + switch (error) { + case VPN_PROVIDER_ERROR_UNKNOWN: + break; + case VPN_PROVIDER_ERROR_CONNECT_FAILED: + ++provider->conn_error_counter; + break; + case VPN_PROVIDER_ERROR_LOGIN_FAILED: + case VPN_PROVIDER_ERROR_AUTH_FAILED: + ++provider->auth_error_counter; + break; + } +} + int vpn_provider_indicate_error(struct vpn_provider *provider, enum vpn_provider_error error) { @@ -1581,16 +1941,7 @@ int vpn_provider_indicate_error(struct vpn_provider *provider, 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; - } + vpn_provider_add_error(provider, error); if (provider->driver && provider->driver->set_state) provider->driver->set_state(provider, provider->state); @@ -1714,6 +2065,13 @@ static struct vpn_provider *vpn_provider_get(const char *identifier) return provider; } +static void vpn_provider_put(const char *identifier) +{ + configuration_count_del(); + + g_hash_table_remove(provider_hash, identifier); +} + static void provider_dbus_ident(char *ident) { int i, len = strlen(ident); @@ -2078,8 +2436,6 @@ int __vpn_provider_create_from_config(GHashTable *settings, provider->config_file = g_strdup(config_ident); provider->config_entry = g_strdup(config_entry); - provider_register(provider); - provider_resolv_host_addr(provider); } @@ -2114,6 +2470,7 @@ int __vpn_provider_create_from_config(GHashTable *settings, return 0; fail: + vpn_provider_put(ident); g_free(ident); g_slist_free_full(networks, free_route); @@ -2161,7 +2518,7 @@ DBusMessage *__vpn_provider_get_connections(DBusMessage *msg) return reply; } -const char *__vpn_provider_get_ident(struct vpn_provider *provider) +const char *vpn_provider_get_ident(struct vpn_provider *provider) { if (!provider) return NULL; @@ -2178,35 +2535,54 @@ static int set_string(struct vpn_provider *provider, hide_value ? "" : value); if (g_str_equal(key, "Type")) { + if (!g_strcmp0(provider->type, value)) + return -EALREADY; + 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")) { + if (!g_strcmp0(provider->name, value)) + return -EALREADY; + g_free(provider->name); provider->name = g_strdup(value); send_value(provider->path, "Name", provider->name); } else if (g_str_equal(key, "Host")) { + if (!g_strcmp0(provider->host, value)) + return -EALREADY; + 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")) { + if (!g_strcmp0(provider->domain, value)) + return -EALREADY; + g_free(provider->domain); provider->domain = g_strdup(value); send_value(provider->path, "Domain", provider->domain); } else { struct vpn_setting *setting; + bool replace = true; setting = g_hash_table_lookup(provider->setting_strings, key); - if (setting && !immutable && - setting->immutable) { - DBG("Trying to set immutable variable %s", key); - return -EPERM; - } + if (setting) { + if (!immutable && setting->immutable) { + DBG("Trying to set immutable variable %s", key); + return -EPERM; + } else if (!g_strcmp0(setting->value, value)) { + return -EALREADY; + } - setting = g_try_new0(struct vpn_setting, 1); - if (!setting) - return -ENOMEM; + g_free(setting->value); + replace = false; + } else { + setting = g_try_new0(struct vpn_setting, 1); + if (!setting) + return -ENOMEM; + } setting->value = g_strdup(value); setting->hide_value = hide_value; @@ -2217,8 +2593,9 @@ static int set_string(struct vpn_provider *provider, if (!hide_value) send_value(provider->path, key, setting->value); - g_hash_table_replace(provider->setting_strings, - g_strdup(key), setting); + if (replace) + g_hash_table_replace(provider->setting_strings, + g_strdup(key), setting); } return 0; @@ -2272,6 +2649,52 @@ const char *vpn_provider_get_string(struct vpn_provider *provider, return setting->value; } +bool vpn_provider_get_boolean(struct vpn_provider *provider, const char *key, + bool default_value) +{ + struct vpn_setting *setting; + + connman_info("provider %p key %s", provider, key); + + setting = g_hash_table_lookup(provider->setting_strings, key); + if (!setting || !setting->value) + return default_value; + + if (!g_strcmp0(setting->value, "true")) + return true; + + if (!g_strcmp0(setting->value, "false")) + return false; + + return default_value; +} + +bool vpn_provider_get_string_immutable(struct vpn_provider *provider, + const char *key) +{ + struct vpn_setting *setting; + + /* These values can be changed if the provider is not immutable */ + if (g_str_equal(key, "Type")) { + return provider->immutable; + } else if (g_str_equal(key, "Name")) { + return provider->immutable; + } else if (g_str_equal(key, "Host")) { + return provider->immutable; + } else if (g_str_equal(key, "HostIP")) { + return provider->immutable; + } else if (g_str_equal(key, "VPN.Domain") || + g_str_equal(key, "Domain")) { + return provider->immutable; + } + + setting = g_hash_table_lookup(provider->setting_strings, key); + if (!setting) + return true; /* Not found, regard as immutable - no changes */ + + return setting->immutable; +} + bool __vpn_provider_check_routes(struct vpn_provider *provider) { if (!provider) @@ -2298,6 +2721,16 @@ void vpn_provider_set_data(struct vpn_provider *provider, void *data) provider->driver_data = data; } +void *vpn_provider_get_plugin_data(struct vpn_provider *provider) +{ + return provider->plugin_data; +} + +void vpn_provider_set_plugin_data(struct vpn_provider *provider, void *data) +{ + provider->plugin_data = data; +} + void vpn_provider_set_index(struct vpn_provider *provider, int index) { DBG("index %d provider %p", index, provider); @@ -2306,7 +2739,7 @@ void vpn_provider_set_index(struct vpn_provider *provider, int index) provider->ipconfig_ipv4 = __vpn_ipconfig_create(index, AF_INET); if (!provider->ipconfig_ipv4) { - DBG("Couldnt create ipconfig for IPv4"); + DBG("Couldn't create ipconfig for IPv4"); goto done; } } @@ -2317,7 +2750,7 @@ void vpn_provider_set_index(struct vpn_provider *provider, int index) provider->ipconfig_ipv6 = __vpn_ipconfig_create(index, AF_INET6); if (!provider->ipconfig_ipv6) { - DBG("Couldnt create ipconfig for IPv6"); + DBG("Couldn't create ipconfig for IPv6"); goto done; } } @@ -2419,7 +2852,7 @@ int vpn_provider_set_nameservers(struct vpn_provider *provider, if (!nameservers) return 0; - provider->nameservers = g_strsplit(nameservers, " ", 0); + provider->nameservers = g_strsplit_set(nameservers, ", ", 0); return 0; } @@ -2545,7 +2978,6 @@ void vpn_provider_driver_unregister(struct vpn_provider_driver *driver) 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; @@ -2568,6 +3000,18 @@ const char *vpn_provider_get_path(struct vpn_provider *provider) return provider->path; } +unsigned int vpn_provider_get_authentication_errors( + struct vpn_provider *provider) +{ + return provider->auth_error_counter; +} + +unsigned int vpn_provider_get_connection_errors( + struct vpn_provider *provider) +{ + return provider->conn_error_counter; +} + void vpn_provider_change_address(struct vpn_provider *provider) { switch (provider->family) {