vpn-provider: Send domain name to connman when connection is ready
[platform/upstream/connman.git] / vpn / vpn-provider.c
index 462dc26..513d926 100644 (file)
 #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"
@@ -40,14 +42,21 @@ static DBusConnection *connection;
 static GHashTable *provider_hash;
 static GSList *driver_list;
 static int configuration_count;
+static gboolean handle_routes;
 
 struct vpn_route {
        int family;
-       char *host;
+       char *network;
        char *netmask;
        char *gateway;
 };
 
+struct vpn_setting {
+       gboolean hide_value;
+       gboolean immutable;
+       char *value;
+};
+
 struct vpn_provider {
        int refcount;
        int index;
@@ -65,34 +74,411 @@ struct vpn_provider {
        void *driver_data;
        GHashTable *setting_strings;
        GHashTable *user_routes;
-       gchar **user_networks;
-       gsize num_user_networks;
+       GSList *user_networks;
        GResolv *resolv;
        char **host_ip;
-       DBusMessage *pending_msg;
        struct vpn_ipconfig *ipconfig_ipv4;
        struct vpn_ipconfig *ipconfig_ipv6;
        char **nameservers;
+       guint notify_id;
+       char *config_file;
+       char *config_entry;
+       connman_bool_t immutable;
 };
 
+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 == NULL)
+               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 != NULL)
+               connman_dbus_dict_append_basic(&item, "Network",
+                                       DBUS_TYPE_STRING, &route->network);
+
+       if (route->netmask != NULL)
+               connman_dbus_dict_append_basic(&item, "Netmask",
+                                       DBUS_TYPE_STRING, &route->netmask);
+
+       if (route->gateway != NULL)
+               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 == NULL) {
+               append_route(iter, NULL);
+               return;
+       }
+
+       g_hash_table_iter_init(&hash, routes);
+
+       while (g_hash_table_iter_next(&hash, &key, &value) == TRUE) {
+               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_STRING:
+                       if (g_str_equal(key, "ProtocolFamily") == TRUE)
+                               dbus_message_iter_get_basic(&value, &family);
+                       else if (g_str_equal(key, "Network") == TRUE)
+                               dbus_message_iter_get_basic(&value, &network);
+                       else if (g_str_equal(key, "Netmask") == TRUE)
+                               dbus_message_iter_get_basic(&value, &netmask);
+                       else if (g_str_equal(key, "Gateway") == TRUE)
+                               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 == NULL || netmask == NULL) {
+               DBG("Ignoring route as network/netmask is missing");
+               return routes;
+       }
+
+       route = g_try_new(struct vpn_route, 1);
+       if (route == NULL) {
+               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 != NULL; 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 == TRUE && g_hash_table_iter_next(&hash,
+                                               &key, &value) == TRUE) {
+               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 != NULL)
+               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 == NULL)
+               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);
 
-       // XXX:
+       if (provider->immutable == TRUE)
+               return __connman_error_not_supported(msg);
 
-       return NULL;
+       if (dbus_message_iter_init(msg, &iter) == FALSE)
+               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") == TRUE) {
+               GSList *networks;
+
+               if (type != DBUS_TYPE_ARRAY)
+                       return __connman_error_invalid_arguments(msg);
+
+               networks = get_user_networks(&value);
+               if (networks != NULL) {
+                       del_routes(provider);
+                       provider->user_networks = networks;
+                       set_user_networks(provider, provider->user_networks);
+
+                       if (handle_routes == FALSE)
+                               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);
 
-       // XXX:
+       if (provider->immutable == TRUE)
+               return __connman_error_not_supported(msg);
 
-       return NULL;
+       dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name,
+                                                       DBUS_TYPE_INVALID);
+
+       if (g_str_equal(name, "UserRoutes") == TRUE) {
+               del_routes(provider);
+
+               if (handle_routes == FALSE)
+                       send_routes(provider, provider->user_routes, name);
+       } else if (vpn_provider_get_string(provider, name) != NULL) {
+               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,
@@ -103,11 +489,11 @@ static DBusMessage *do_connect(DBusConnection *conn, DBusMessage *msg,
 
        DBG("conn %p provider %p", conn, provider);
 
-       err = __vpn_provider_connect(provider);
+       err = __vpn_provider_connect(provider, msg);
        if (err < 0)
                return __connman_error_failed(msg, -err);
 
-       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+       return NULL;
 }
 
 static DBusMessage *do_disconnect(DBusConnection *conn, DBusMessage *msg,
@@ -119,13 +505,16 @@ static DBusMessage *do_disconnect(DBusConnection *conn, DBusMessage *msg,
        DBG("conn %p provider %p", conn, provider);
 
        err = __vpn_provider_disconnect(provider);
-       if (err < 0)
+       if (err < 0 && err != -EINPROGRESS)
                return __connman_error_failed(msg, -err);
-       else
-               return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+
+       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) },
@@ -205,12 +594,15 @@ void __vpn_provider_append_properties(struct vpn_provider *provider,
 }
 
 int __vpn_provider_append_user_route(struct vpn_provider *provider,
-                       int family, const char *network, const char *netmask)
+                               int family, const char *network,
+                               const char *netmask, const char *gateway)
 {
        struct vpn_route *route;
-       char *key = g_strdup_printf("%d/%s/%s", family, network, netmask);
+       char *key = g_strdup_printf("%d/%s/%s/%s", family, network,
+                               netmask, gateway != NULL ? gateway : "");
 
-       DBG("family %d network %s netmask %s", family, network, netmask);
+       DBG("family %d network %s netmask %s gw %s", family, network,
+                                                       netmask, gateway);
 
        route = g_hash_table_lookup(provider->user_routes, key);
        if (route == NULL) {
@@ -221,8 +613,9 @@ int __vpn_provider_append_user_route(struct vpn_provider *provider,
                }
 
                route->family = family;
-               route->host = g_strdup(network);
+               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
@@ -231,64 +624,103 @@ int __vpn_provider_append_user_route(struct vpn_provider *provider,
        return 0;
 }
 
-static void set_user_networks(struct vpn_provider *provider,
-                                                       char **networks)
+static struct vpn_route *get_route(char *route_str)
 {
-       int i = 0;
+       char **elems = g_strsplit(route_str, "/", 0);
+       char *network, *netmask, *gateway, *family_str;
+       int family = PF_UNSPEC;
+       struct vpn_route *route = NULL;
 
-       while (networks[i] != NULL) {
-               char **elems = g_strsplit(networks[i], "/", 0);
-               char *network, *netmask;
-               int family = PF_UNSPEC, ret;
+       if (elems == NULL)
+               return NULL;
 
-               if (elems == NULL)
-                       break;
+       family_str = elems[0];
 
-               network = elems[0];
-               if (network == NULL || *network == '\0') {
-                       DBG("no network/netmask set");
-                       g_strfreev(elems);
-                       break;
-               }
+       network = elems[1];
+       if (network == NULL || network[0] == '\0')
+               goto out;
+
+       netmask = elems[2];
+       if (netmask == NULL || netmask[0] == '\0')
+               goto out;
 
-               netmask = elems[1];
-               if (netmask != NULL && *netmask == '\0') {
-                       DBG("no netmask set");
-                       g_strfreev(elems);
+       gateway = elems[3];
+
+       route = g_try_new0(struct vpn_route, 1);
+       if (route == NULL)
+               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, ":") != NULL)
-                       family = AF_INET6;
-               else if (g_strrstr(network, ".") != NULL) {
-                       family = AF_INET;
+       if (g_strrstr(network, ":") != NULL) {
+               if (family != PF_UNSPEC && family != AF_INET6)
+                       DBG("You have IPv6 address but you have non IPv6 route");
+       } else if (g_strrstr(network, ".") != NULL) {
+               if (family != PF_UNSPEC && family != AF_INET)
+                       DBG("You have IPv4 address but you have non IPv4 route");
+
+               if (g_strrstr(netmask, ".") == NULL) {
+                       /* We have netmask length */
+                       in_addr_t addr;
+                       struct in_addr netmask_in;
+                       unsigned char prefix_len = 32;
+
+                       if (netmask != NULL) {
+                               char *ptr;
+                               long int value = strtol(netmask, &ptr, 10);
+                               if (ptr != netmask && *ptr == '\0' &&
+                                                               value <= 32)
+                                       prefix_len = value;
+                       }
 
-                       if (g_strrstr(netmask, ".") == NULL) {
-                               /* We have netmask length */
-                               in_addr_t addr;
-                               struct in_addr netmask_in;
-                               unsigned char prefix_len = 32;
+                       addr = 0xffffffff << (32 - prefix_len);
+                       netmask_in.s_addr = htonl(addr);
+                       netmask = inet_ntoa(netmask_in);
 
-                               if (netmask != NULL)
-                                       prefix_len = atoi(netmask);
+                       DBG("network %s netmask %s", network, netmask);
+               }
+       }
 
-                               addr = 0xffffffff << (32 - prefix_len);
-                               netmask_in.s_addr = htonl(addr);
-                               netmask = inet_ntoa(netmask_in);
+       if (family == PF_UNSPEC) {
+               family = connman_inet_check_ipaddress(network);
+               if (family < 0 || family == PF_UNSPEC)
+                       goto out;
+       }
 
-                               DBG("network %s netmask %s", network, netmask);
-                       }
-               }
+       route->family = family;
+       route->network = g_strdup(network);
+       route->netmask = g_strdup(netmask);
+       route->gateway = g_strdup(gateway);
 
-               ret = __vpn_provider_append_user_route(provider,
-                                               family, network, netmask);
-               g_strfreev(elems);
+out:
+       g_strfreev(elems);
+       return route;
+}
 
-               if (ret != 0)
-                       break;
+static GSList *get_routes(gchar **networks)
+{
+       struct vpn_route *route;
+       GSList *routes = NULL;
+       int i;
 
-               i++;
+       for (i = 0; networks[i] != NULL; i++) {
+               route = get_route(networks[i]);
+               if (route != NULL)
+                       routes = g_slist_prepend(routes, route);
        }
+
+       return routes;
 }
 
 static int provider_load_from_keyfile(struct vpn_provider *provider,
@@ -297,7 +729,8 @@ static int provider_load_from_keyfile(struct vpn_provider *provider,
        gsize idx = 0;
        gchar **settings;
        gchar *key, *value;
-       gsize length;
+       gsize length, num_user_networks;
+       gchar **networks = NULL;
 
        settings = g_key_file_get_keys(keyfile, provider->identifier, &length,
                                NULL);
@@ -310,13 +743,13 @@ static int provider_load_from_keyfile(struct vpn_provider *provider,
                key = settings[idx];
                if (key != NULL) {
                        if (g_str_equal(key, "Networks") == TRUE) {
-                               g_strfreev(provider->user_networks);
-                               provider->user_networks =
-                                       g_key_file_get_string_list(keyfile,
+                               networks = g_key_file_get_string_list(keyfile,
                                                provider->identifier,
                                                key,
-                                               &provider->num_user_networks,
+                                               &num_user_networks,
                                                NULL);
+                               provider->user_networks = get_routes(networks);
+
                        } else {
                                value = g_key_file_get_string(keyfile,
                                                        provider->identifier,
@@ -329,6 +762,7 @@ static int provider_load_from_keyfile(struct vpn_provider *provider,
                idx += 1;
        }
        g_strfreev(settings);
+       g_strfreev(networks);
 
        if (provider->user_networks != NULL)
                set_user_networks(provider, provider->user_networks);
@@ -353,11 +787,63 @@ static int vpn_provider_load(struct vpn_provider *provider)
        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 != NULL; 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 == NULL)
+                       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 == NULL ? "" : route->gateway);
+
+               num_elems++;
+       }
+
+       result = g_try_realloc(result, (num_elems + 1) * sizeof(gchar *));
+       if (result == NULL)
+               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", provider);
+       DBG("provider %p immutable %s", provider,
+                                       provider->immutable ? "yes" : "no");
+
+       if (provider->immutable == TRUE) {
+               /*
+                * Do not save providers that are provisioned via .config
+                * file.
+                */
+               return -EPERM;
+       }
 
        keyfile = g_key_file_new();
        if (keyfile == NULL)
@@ -371,11 +857,30 @@ static int vpn_provider_save(struct vpn_provider *provider)
                        "Host", provider->host);
        g_key_file_set_string(keyfile, provider->identifier,
                        "VPN.Domain", provider->domain);
-       if (provider->user_networks != NULL)
-               g_key_file_set_string_list(keyfile, provider->identifier,
-                               "Networks",
-                               (const gchar **)provider->user_networks,
-                               provider->num_user_networks);
+       if (provider->user_networks != NULL) {
+               gchar **networks;
+               gsize network_count;
+
+               networks = create_network_list(provider->user_networks,
+                                                       &network_count);
+               if (networks != NULL) {
+                       g_key_file_set_string_list(keyfile,
+                                               provider->identifier,
+                                               "Networks",
+                                               (const gchar ** const)networks,
+                                               network_count);
+                       g_strfreev(networks);
+               }
+       }
+
+       if (provider->config_file != NULL && strlen(provider->config_file) > 0)
+               g_key_file_set_string(keyfile, provider->identifier,
+                               "Config.file", provider->config_file);
+
+       if (provider->config_entry != NULL &&
+                                       strlen(provider->config_entry) > 0)
+               g_key_file_set_string(keyfile, provider->identifier,
+                               "Config.ident", provider->config_entry);
 
        if (provider->driver != NULL && provider->driver->save != NULL)
                provider->driver->save(provider, keyfile);
@@ -386,7 +891,7 @@ static int vpn_provider_save(struct vpn_provider *provider)
        return 0;
 }
 
-static struct vpn_provider *vpn_provider_lookup(const char *identifier)
+struct vpn_provider *__vpn_provider_lookup(const char *identifier)
 {
        struct vpn_provider *provider = NULL;
 
@@ -408,7 +913,8 @@ static int provider_probe(struct vpn_provider *provider)
 {
        GSList *list;
 
-       DBG("provider %p name %s", provider, provider->name);
+       DBG("provider %p driver %p name %s", provider, provider->driver,
+                                               provider->name);
 
        if (provider->driver != NULL)
                return -EALREADY;
@@ -467,13 +973,16 @@ 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_strfreev(provider->user_networks);
+       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);
@@ -482,7 +991,12 @@ static void provider_destruct(struct vpn_provider *provider)
                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);
        g_free(provider);
 }
 
@@ -513,8 +1027,6 @@ static void configuration_count_del(void)
 
        if (__sync_fetch_and_sub(&configuration_count, 1) != 1)
                return;
-
-       raise(SIGTERM);
 }
 
 int __vpn_provider_disconnect(struct vpn_provider *provider)
@@ -528,48 +1040,113 @@ int __vpn_provider_disconnect(struct vpn_provider *provider)
        else
                return -EOPNOTSUPP;
 
-       if (err < 0) {
-               if (err != -EINPROGRESS)
-                       return err;
+       if (err == -EINPROGRESS)
+               vpn_provider_set_state(provider, VPN_PROVIDER_STATE_CONNECT);
 
-               return -EINPROGRESS;
-       }
+       return err;
+}
 
-       return 0;
+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 != NULL)
+                       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)
+int __vpn_provider_connect(struct vpn_provider *provider, DBusMessage *msg)
 {
        int err;
 
        DBG("provider %p", provider);
 
-       if (provider->driver != NULL && provider->driver->connect != NULL)
-               err = provider->driver->connect(provider);
-       else
+       if (provider->driver != NULL && provider->driver->connect != NULL) {
+               dbus_message_ref(msg);
+               err = provider->driver->connect(provider, connect_cb, 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 == NULL)
+               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 == NULL)
+               return NULL;
+
+       return pos + 1;
+}
+
 int __vpn_provider_remove(const char *path)
 {
        struct vpn_provider *provider;
+       char *ident;
 
        DBG("path %s", path);
 
-       provider = vpn_provider_lookup(path);
-       if (provider != NULL) {
-               DBG("Removing VPN %s", provider->identifier);
+       ident = get_ident(path);
 
-               provider_unregister(provider);
-               g_hash_table_remove(provider_hash, provider->identifier);
-               return 0;
-       }
+       provider = __vpn_provider_lookup(ident);
+       if (provider != NULL)
+               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;
@@ -657,17 +1234,39 @@ static const char *state2string(enum vpn_provider_state state)
        return NULL;
 }
 
+static void append_nameservers(DBusMessageIter *iter, char **servers)
+{
+       int i;
+
+       DBG("%p", servers);
+
+       for (i = 0; servers[i] != NULL; 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 != NULL)
+               append_nameservers(iter, provider->nameservers);
+}
+
 static int provider_indicate_state(struct vpn_provider *provider,
                                enum vpn_provider_state state)
 {
        const char *str;
-
-       DBG("provider %p state %d", provider, state);
+       enum vpn_provider_state old_state;
 
        str = state2string(state);
+       DBG("provider %p state %s/%d", provider, str, state);
        if (str == NULL)
                return -EINVAL;
 
+       old_state = provider->state;
        provider->state = state;
 
        if (state == VPN_PROVIDER_STATE_READY) {
@@ -683,33 +1282,34 @@ static int provider_indicate_state(struct vpn_provider *provider,
                        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 != NULL)
+                       connman_dbus_property_changed_basic(provider->path,
+                                               VPN_CONNECTION_INTERFACE,
+                                               "Domain",
+                                               DBUS_TYPE_STRING,
+                                               &provider->domain);
        }
 
-       connman_dbus_property_changed_basic(provider->path,
+       if (old_state != state)
+               connman_dbus_property_changed_basic(provider->path,
                                        VPN_CONNECTION_INTERFACE, "State",
                                        DBUS_TYPE_STRING, &str);
-       return 0;
-}
-
-static void append_nameservers(DBusMessageIter *iter, char **servers)
-{
-       int i;
-
-       DBG("%p", servers);
-
-       for (i = 0; servers[i] != NULL; 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;
+       /*
+        * We do not stay in failure state as clients like connmand can
+        * get confused about our current state.
+        */
+       if (provider->state == VPN_PROVIDER_STATE_FAILURE)
+               provider->state = VPN_PROVIDER_STATE_IDLE;
 
-       if (provider->nameservers != NULL)
-               append_nameservers(iter, provider->nameservers);
+       return 0;
 }
 
 static void append_state(DBusMessageIter *iter,
@@ -744,6 +1344,8 @@ static void append_properties(DBusMessageIter *iter,
                                        struct vpn_provider *provider)
 {
        DBusMessageIter dict;
+       GHashTableIter hash;
+       gpointer value, key;
 
        connman_dbus_dict_open(iter, &dict);
 
@@ -767,6 +1369,9 @@ static void append_properties(DBusMessageIter *iter,
                connman_dbus_dict_append_basic(&dict, "Domain",
                                        DBUS_TYPE_STRING, &provider->domain);
 
+       connman_dbus_dict_append_basic(&dict, "Immutable", DBUS_TYPE_BOOLEAN,
+                                       &provider->immutable);
+
        if (provider->family == AF_INET)
                connman_dbus_dict_append_dict(&dict, "IPv4", append_ipv4,
                                                provider);
@@ -777,9 +1382,50 @@ static void append_properties(DBusMessageIter *iter,
        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 != NULL) {
+               g_hash_table_iter_init(&hash, provider->setting_strings);
+
+               while (g_hash_table_iter_next(&hash, &key, &value) == TRUE) {
+                       struct vpn_setting *setting = value;
+
+                       if (setting->hide_value == FALSE &&
+                                                       setting->value != NULL)
+                               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 == NULL)
+               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 connman_bool_t check_host(char **hosts, char *host)
 {
        int i;
@@ -802,25 +1448,28 @@ static void provider_append_routes(gpointer key, gpointer value,
        struct vpn_provider *provider = user_data;
        int index = provider->index;
 
+       if (handle_routes == FALSE)
+               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->host) == TRUE) {
+       if (check_host(provider->host_ip, route->network) == TRUE) {
                DBG("Discarding VPN route to %s via %s at index %d",
-                       route->host, route->gateway, index);
+                       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->host,
+               connman_inet_add_ipv6_network_route(index, route->network,
                                                        route->gateway,
                                                        prefix_len);
        } else {
-               connman_inet_add_network_route(index, route->host,
+               connman_inet_add_network_route(index, route->network,
                                                route->gateway,
                                                route->netmask);
        }
@@ -841,7 +1490,9 @@ static int set_connected(struct vpn_provider *provider,
                        ipconfig = provider->ipconfig_ipv4;
 
                __vpn_ipconfig_address_add(ipconfig, provider->family);
-               __vpn_ipconfig_gateway_add(ipconfig, provider->family);
+
+               if (handle_routes == TRUE)
+                       __vpn_ipconfig_gateway_add(ipconfig, provider->family);
 
                provider_indicate_state(provider,
                                        VPN_PROVIDER_STATE_READY);
@@ -896,6 +1547,7 @@ int vpn_provider_indicate_error(struct vpn_provider *provider,
        case VPN_PROVIDER_ERROR_LOGIN_FAILED:
                break;
        case VPN_PROVIDER_ERROR_AUTH_FAILED:
+               vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
                break;
        case VPN_PROVIDER_ERROR_CONNECT_FAILED:
                break;
@@ -906,23 +1558,49 @@ int vpn_provider_indicate_error(struct vpn_provider *provider,
        return 0;
 }
 
+static int connection_unregister(struct vpn_provider *provider)
+{
+       DBG("provider %p path %s", provider, provider->path);
+
+       if (provider->path == NULL)
+               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 != NULL)
+               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();
 
-       vpn_provider_unref(provider);
-}
-
-static void destroy_route(gpointer user_data)
-{
-       struct vpn_route *route = user_data;
+       connection_unregister(provider);
 
-       g_free(route->host);
-       g_free(route->netmask);
-       g_free(route->gateway);
-       g_free(route);
+       vpn_provider_unref(provider);
 }
 
 static void provider_initialize(struct vpn_provider *provider)
@@ -935,13 +1613,14 @@ static void provider_initialize(struct vpn_provider *provider)
        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, destroy_route);
+                                       NULL, free_route);
        provider->user_routes = g_hash_table_new_full(g_str_hash, g_str_equal,
-                                       g_free, destroy_route);
+                                       g_free, free_route);
        provider->setting_strings = g_hash_table_new_full(g_str_hash,
-                                               g_str_equal, g_free, g_free);
+                                       g_str_equal, g_free, free_setting);
 }
 
 static struct vpn_provider *vpn_provider_new(void)
@@ -998,38 +1677,6 @@ static void provider_dbus_ident(char *ident)
        }
 }
 
-static int connection_unregister(struct vpn_provider *provider)
-{
-       if (provider->path == NULL)
-               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 != NULL)
-               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 struct vpn_provider *provider_create_from_keyfile(GKeyFile *keyfile,
                const char *ident)
 {
@@ -1038,7 +1685,7 @@ static struct vpn_provider *provider_create_from_keyfile(GKeyFile *keyfile,
        if (keyfile == NULL || ident == NULL)
                return NULL;
 
-       provider = vpn_provider_lookup(ident);
+       provider = __vpn_provider_lookup(ident);
        if (provider == NULL) {
                provider = vpn_provider_get(ident);
                if (provider == NULL) {
@@ -1072,6 +1719,9 @@ static void provider_create_all_from_type(const char *provider_type)
 
        providers = __connman_storage_get_providers();
 
+       if (providers == NULL)
+               return;
+
        for (i = 0; providers[i] != NULL; i+=1) {
 
                if (strncmp(providers[i], "provider_", 9) != 0)
@@ -1102,52 +1752,28 @@ static void provider_create_all_from_type(const char *provider_type)
        g_strfreev(providers);
 }
 
-static char **get_user_networks(DBusMessageIter *array, int *count)
+char *__vpn_provider_create_identifier(const char *host, const char *domain)
 {
-       DBusMessageIter entry;
-       char **networks = NULL;
-       GSList *list = NULL, *l;
-       int len;
-
-       dbus_message_iter_recurse(array, &entry);
-
-       while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) {
-               const char *val;
-               dbus_message_iter_get_basic(&entry, &val);
-
-               list = g_slist_prepend(list, g_strdup(val));
-               dbus_message_iter_next(&entry);
-       }
-
-       len = g_slist_length(list);
-       if (len == 0)
-               goto out;
-
-       networks = g_try_new(char *, len + 1);
-       if (networks == NULL)
-               goto out;
-
-       *count = len;
-       networks[len] = 0;
+       char *ident;
 
-       for (l = list; l != NULL; l = g_slist_next(l))
-               networks[--len] = l->data;
+       ident = g_strdup_printf("%s_%s", host, domain);
+       if (ident == NULL)
+               return NULL;
 
-out:
-       g_slist_free(list);
+       provider_dbus_ident(ident);
 
-       return networks;
+       return ident;
 }
 
-int __vpn_provider_create_and_connect(DBusMessage *msg)
+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;
-       char **networks = NULL;
+       GSList *networks = NULL;
        char *ident;
-       int err, count = 0;
+       int err;
 
        dbus_message_iter_init(msg, &iter);
        dbus_message_iter_recurse(&iter, &array);
@@ -1170,12 +1796,13 @@ int __vpn_provider_create_and_connect(DBusMessage *msg)
                                dbus_message_iter_get_basic(&value, &name);
                        else if (g_str_equal(key, "Host") == TRUE)
                                dbus_message_iter_get_basic(&value, &host);
-                       else if (g_str_equal(key, "VPN.Domain") == TRUE)
+                       else if (g_str_equal(key, "VPN.Domain") == TRUE ||
+                                       g_str_equal(key, "Domain") == TRUE)
                                dbus_message_iter_get_basic(&value, &domain);
                        break;
                case DBUS_TYPE_ARRAY:
-                       if (g_str_equal(key, "Networks") == TRUE)
-                               networks = get_user_networks(&value, &count);
+                       if (g_str_equal(key, "UserRoutes") == TRUE)
+                               networks = get_user_networks(&value);
                        break;
                }
 
@@ -1190,12 +1817,10 @@ int __vpn_provider_create_and_connect(DBusMessage *msg)
        if (type == NULL || name == NULL)
                return -EOPNOTSUPP;
 
-       ident = g_strdup_printf("%s_%s", host, domain);
-       provider_dbus_ident(ident);
-
+       ident = __vpn_provider_create_identifier(host, domain);
        DBG("ident %s", ident);
 
-       provider = vpn_provider_lookup(ident);
+       provider = __vpn_provider_lookup(ident);
        if (provider == NULL) {
                provider = vpn_provider_get(ident);
                if (provider == NULL) {
@@ -1216,9 +1841,8 @@ int __vpn_provider_create_and_connect(DBusMessage *msg)
        }
 
        if (networks != NULL) {
-               g_strfreev(provider->user_networks);
+               g_slist_free_full(provider->user_networks, free_route);
                provider->user_networks = networks;
-               provider->num_user_networks = count;
                set_user_networks(provider, provider->user_networks);
        }
 
@@ -1247,30 +1871,199 @@ int __vpn_provider_create_and_connect(DBusMessage *msg)
 
        g_free(ident);
 
-       provider->pending_msg = dbus_message_ref(msg);
+       vpn_provider_save(provider);
+
+       err = provider_register(provider);
+       if (err != 0 && err != -EALREADY)
+               return err;
 
-       DBG("provider %p pending %p", provider, provider->pending_msg);
+       connection_register(provider);
 
-       if (provider->index > 0) {
-               DBG("provider already connected");
-       } else {
-               err = __vpn_provider_connect(provider);
-               if (err < 0 && err != -EINPROGRESS)
-                       goto failed;
+       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 == NULL)
+               return NULL;
+
+       elems = g_strsplit(network_str, ",", 0);
+       if (elems == NULL)
+               return NULL;
+
+       while (elems[i] != NULL) {
+               struct vpn_route *vpn_route;
+               char *network, *netmask, *gateway;
+               int family;
+               char **route;
+
+               route = g_strsplit(elems[i], "/", 0);
+               if (route == NULL)
+                       goto next;
+
+               network = route[0];
+               if (network == NULL || 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 == NULL || netmask[0] == '\0')
+                       goto next;
+
+               gateway = route[2];
+
+               vpn_route = g_try_new0(struct vpn_route, 1);
+               if (vpn_route == NULL) {
+                       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 == NULL || domain == NULL) {
+               err = -EINVAL;
+               goto fail;
+       }
+
+       DBG("type %s name %s networks %s", type, name, networks_str);
+
+       if (type == NULL || name == NULL) {
+               err = -EOPNOTSUPP;
+               goto fail;
+       }
+
+       ident = __vpn_provider_create_identifier(host, domain);
+       DBG("ident %s", ident);
+
+       provider = __vpn_provider_lookup(ident);
+       if (provider == NULL) {
+               provider = vpn_provider_get(ident);
+               if (provider == NULL) {
+                       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 != NULL) {
+               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) == TRUE)
+               __vpn_provider_set_string_immutable(provider, key, value);
+
+       provider->immutable = TRUE;
+
        vpn_provider_save(provider);
 
-       return 0;
+       err = provider_register(provider);
+       if (err != 0 && err != -EALREADY)
+               goto fail;
 
-failed:
-       DBG("Can not connect (%d), deleting provider %p %s", err, provider,
-               provider->identifier);
+       connection_register(provider);
 
-       vpn_provider_indicate_error(provider,
-                               VPN_PROVIDER_ERROR_CONNECT_FAILED);
+       DBG("provider %p index %d path %s", provider, provider->index,
+                                                       provider->path);
 
-       g_hash_table_remove(provider_hash, provider->identifier);
+       connection_added_signal(provider);
+
+       g_free(ident);
+
+       return 0;
+
+fail:
+       g_free(ident);
+       g_slist_free_full(networks, free_route);
 
        return err;
 }
@@ -1324,32 +2117,84 @@ const char * __vpn_provider_get_ident(struct vpn_provider *provider)
        return provider->identifier;
 }
 
-int vpn_provider_set_string(struct vpn_provider *provider,
-                                       const char *key, const char *value)
+static int set_string(struct vpn_provider *provider,
+                       const char *key, const char *value,
+                       gboolean hide_value, gboolean immutable)
 {
-       DBG("provider %p key %s value %s", provider, key, value);
+       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") == TRUE) {
                g_free(provider->type);
-               provider->type = g_strdup(value);
+               provider->type = g_ascii_strdown(value, -1);
+               send_value(provider->path, "Type", provider->type);
        } else if (g_str_equal(key, "Name") == TRUE) {
                g_free(provider->name);
                provider->name = g_strdup(value);
+               send_value(provider->path, "Name", provider->name);
        } else if (g_str_equal(key, "Host") == TRUE) {
                g_free(provider->host);
                provider->host = g_strdup(value);
-       } else if (g_str_equal(key, "VPN.Domain") == TRUE) {
+               send_value(provider->path, "Host", provider->host);
+       } else if (g_str_equal(key, "VPN.Domain") == TRUE ||
+                       g_str_equal(key, "Domain") == TRUE) {
                g_free(provider->domain);
                provider->domain = g_strdup(value);
-       } else
+               send_value(provider->path, "Domain", provider->domain);
+       } else {
+               struct vpn_setting *setting;
+
+               setting = g_hash_table_lookup(provider->setting_strings, key);
+               if (setting != NULL && immutable == FALSE &&
+                                               setting->immutable == TRUE) {
+                       DBG("Trying to set immutable variable %s", key);
+                       return -EPERM;
+               }
+
+               setting = g_try_new0(struct vpn_setting, 1);
+               if (setting == NULL)
+                       return -ENOMEM;
+
+               setting->value = g_strdup(value);
+               setting->hide_value = hide_value;
+
+               if (immutable == TRUE)
+                       setting->immutable = TRUE;
+
+               if (hide_value == FALSE)
+                       send_value(provider->path, key, setting->value);
+
                g_hash_table_replace(provider->setting_strings,
-                               g_strdup(key), g_strdup(value));
+                               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") == TRUE)
@@ -1358,10 +2203,21 @@ const char *vpn_provider_get_string(struct vpn_provider *provider,
                return provider->name;
        else if (g_str_equal(key, "Host") == TRUE)
                return provider->host;
-       else if (g_str_equal(key, "VPN.Domain") == TRUE)
+       else if (g_str_equal(key, "HostIP") == TRUE) {
+               if (provider->host_ip == NULL ||
+                               provider->host_ip[0] == NULL)
+                       return provider->host;
+               else
+                       return provider->host_ip[0];
+       } else if (g_str_equal(key, "VPN.Domain") == TRUE ||
+                       g_str_equal(key, "Domain") == TRUE)
                return provider->domain;
 
-       return g_hash_table_lookup(provider->setting_strings, key);
+       setting = g_hash_table_lookup(provider->setting_strings, key);
+       if (setting == NULL)
+               return NULL;
+
+       return setting->value;
 }
 
 connman_bool_t __vpn_provider_check_routes(struct vpn_provider *provider)
@@ -1392,17 +2248,7 @@ void vpn_provider_set_data(struct vpn_provider *provider, void *data)
 
 void vpn_provider_set_index(struct vpn_provider *provider, int index)
 {
-       DBG("index %d provider %p pending %p", index, provider,
-               provider->pending_msg);
-
-       if (provider->pending_msg != NULL) {
-               g_dbus_send_reply(connection, provider->pending_msg,
-                               DBUS_TYPE_STRING, &provider->identifier,
-                               DBUS_TYPE_INT32, &index,
-                               DBUS_TYPE_INVALID);
-               dbus_message_unref(provider->pending_msg);
-               provider->pending_msg = NULL;
-       }
+       DBG("index %d provider %p", index, provider);
 
        if (provider->ipconfig_ipv4 == NULL) {
                provider->ipconfig_ipv4 = __vpn_ipconfig_create(index,
@@ -1600,13 +2446,19 @@ int vpn_provider_append_route(struct vpn_provider *provider,
                route->netmask = g_strdup(value);
                break;
        case PROVIDER_ROUTE_TYPE_ADDR:
-               route->host = g_strdup(value);
+               route->network = g_strdup(value);
                break;
        case PROVIDER_ROUTE_TYPE_GW:
                route->gateway = g_strdup(value);
                break;
        }
 
+       if (handle_routes == FALSE) {
+               if (route->netmask != NULL && route->gateway != NULL &&
+                                                       route->network != NULL)
+                       provider_schedule_changed(provider);
+       }
+
        return 0;
 }
 
@@ -1650,20 +2502,141 @@ int vpn_provider_driver_register(struct vpn_provider_driver *driver)
 
 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) == TRUE) {
+               struct vpn_provider *provider = value;
+
+               if (provider != NULL && provider->driver != NULL &&
+                               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;
+}
+
+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);
 }
 
-int __vpn_provider_init(void)
+static struct connman_agent_driver agent_driver = {
+       .name           = "vpn",
+       .interface      = VPN_AGENT_INTERFACE,
+       .probe          = agent_probe,
+       .remove         = agent_remove,
+};
+
+static void remove_unprovisioned_providers()
+{
+       gchar **providers;
+       GKeyFile *keyfile, *configkeyfile;
+       char *file, *section;
+       int i = 0;
+
+       providers = __connman_storage_get_providers();
+       if (providers == NULL)
+               return;
+
+       for (; providers[i] != NULL; i++) {
+               char *group = providers[i] + sizeof("provider_") - 1;
+               file = section = NULL;
+               keyfile = configkeyfile = NULL;
+
+               keyfile = __connman_storage_load_provider(group);
+               if (keyfile == NULL)
+                       continue;
+
+               file = g_key_file_get_string(keyfile, group,
+                                       "Config.file", NULL);
+               if (file == NULL)
+                       goto next;
+
+               section = g_key_file_get_string(keyfile, group,
+                                       "Config.ident", NULL);
+               if (section == NULL)
+                       goto next;
+
+               configkeyfile = __connman_storage_load_provider_config(file);
+               if (configkeyfile == NULL) {
+                       /*
+                        * Config file is missing, remove the provisioned
+                        * service.
+                        */
+                       __connman_storage_remove_provider(group);
+                       goto next;
+               }
+
+               if (g_key_file_has_group(configkeyfile, section) == FALSE)
+                       /*
+                        * Config section is missing, remove the provisioned
+                        * service.
+                        */
+                       __connman_storage_remove_provider(group);
+
+       next:
+               if (keyfile != NULL)
+                       g_key_file_free(keyfile);
+
+               if (configkeyfile != NULL)
+                       g_key_file_free(configkeyfile);
+
+               g_free(section);
+               g_free(file);
+       }
+
+       g_strfreev(providers);
+}
+
+int __vpn_provider_init(gboolean 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;
 }
 
@@ -1676,5 +2649,7 @@ void __vpn_provider_cleanup(void)
        g_hash_table_destroy(provider_hash);
        provider_hash = NULL;
 
+       connman_agent_driver_unregister(&agent_driver);
+
        dbus_connection_unref(connection);
 }