Imported Upstream version 1.38
[platform/upstream/connman.git] / vpn / vpn-provider.c
index dd54ac0..5ce9328 100644 (file)
@@ -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 ? "<not printed>" : 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) {