*
* ConnMan VPN daemon
*
- * Copyright (C) 2007-2012 Intel Corporation. All rights reserved.
+ * Copyright (C) 2007-2013 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
#include <config.h>
#endif
-#define _GNU_SOURCE
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/if_tun.h>
#include <net/if.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
#include <dbus/dbus.h>
#include "../vpn-provider.h"
#include "vpn.h"
+#include "../vpn.h"
struct vpn_data {
struct vpn_provider *provider;
char *if_name;
unsigned flags;
unsigned int watch;
- unsigned int state;
+ enum vpn_state state;
struct connman_task *task;
+ int tun_flags;
};
struct vpn_driver_data {
struct ifreq ifr;
int fd, err;
- if (data == NULL)
+ if (!data)
return -EINVAL;
name = vpn_provider_get_driver_name(provider);
- if (name == NULL)
+ if (!name)
return -EINVAL;
vpn_driver_data = g_hash_table_lookup(driver_hash, name);
- if (vpn_driver_data != NULL && vpn_driver_data->vpn_driver != NULL &&
- vpn_driver_data->vpn_driver->flags == VPN_FLAG_NO_TUN)
+ if (vpn_driver_data && vpn_driver_data->vpn_driver &&
+ vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN) {
+ vpn_driver_data->vpn_driver->disconnect(data->provider);
return 0;
+ }
memset(&ifr, 0, sizeof(ifr));
- ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
+ ifr.ifr_flags = data->tun_flags | IFF_NO_PI;
sprintf(ifr.ifr_name, "%s", data->if_name);
fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
DBG("provider %p data %p", provider, data);
- if (data == NULL)
+ if (!data)
goto vpn_exit;
+ /* The task may die after we have already started the new one */
+ if (data->task != task)
+ goto done;
+
state = data->state;
stop_vpn(provider);
vpn_provider_set_data(provider, NULL);
if (data->watch != 0) {
- vpn_provider_unref(provider);
vpn_rtnl_remove_watch(data->watch);
data->watch = 0;
+ vpn_provider_unref(provider);
}
vpn_exit:
struct vpn_driver_data *vpn_data = NULL;
name = vpn_provider_get_driver_name(provider);
- if (name != NULL)
+ if (name)
vpn_data = g_hash_table_lookup(driver_hash, name);
- if (vpn_data != NULL &&
- vpn_data->vpn_driver->error_code != NULL)
- ret = vpn_data->vpn_driver->error_code(exit_code);
+ if (vpn_data &&
+ vpn_data->vpn_driver->error_code)
+ ret = vpn_data->vpn_driver->error_code(provider,
+ exit_code);
else
ret = VPN_PROVIDER_ERROR_UNKNOWN;
vpn_provider_indicate_error(provider, ret);
-
- vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
} else
vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE);
vpn_provider_set_index(provider, -1);
- if (data != NULL) {
+ if (data) {
vpn_provider_unref(data->provider);
g_free(data->if_name);
g_free(data);
}
+done:
connman_task_destroy(task);
}
struct vpn_data *data = vpn_provider_get_data(provider);
int index;
- if (ifname == NULL || data == NULL)
+ if (!ifname || !data)
return -EIO;
index = connman_inet_ifindex(ifname);
if (index < 0)
return -EIO;
- if (data->if_name != NULL)
+ if (data->if_name)
g_free(data->if_name);
data->if_name = (char *)g_strdup(ifname);
return 0;
}
+static int vpn_set_state(struct vpn_provider *provider,
+ enum vpn_provider_state state)
+{
+ struct vpn_data *data = vpn_provider_get_data(provider);
+ if (!data)
+ return -EINVAL;
+
+ switch (state) {
+ case VPN_PROVIDER_STATE_UNKNOWN:
+ return -EINVAL;
+ case VPN_PROVIDER_STATE_IDLE:
+ data->state = VPN_STATE_IDLE;
+ break;
+ case VPN_PROVIDER_STATE_CONNECT:
+ case VPN_PROVIDER_STATE_READY:
+ data->state = VPN_STATE_CONNECT;
+ break;
+ case VPN_PROVIDER_STATE_DISCONNECT:
+ data->state = VPN_STATE_DISCONNECT;
+ break;
+ case VPN_PROVIDER_STATE_FAILURE:
+ data->state = VPN_STATE_FAILURE;
+ break;
+ }
+
+ return 0;
+}
+
static void vpn_newlink(unsigned flags, unsigned change, void *user_data)
{
struct vpn_provider *provider = user_data;
name = vpn_provider_get_driver_name(provider);
- if (name == NULL) {
+ if (!name) {
DBG("Cannot find VPN driver for provider %p", provider);
vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
return NULL;
}
vpn_driver_data = g_hash_table_lookup(driver_hash, name);
- if (vpn_driver_data == NULL) {
+ if (!vpn_driver_data) {
DBG("Cannot find VPN driver data for name %s", name);
vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
return NULL;
switch (state) {
case VPN_STATE_CONNECT:
case VPN_STATE_READY:
+ if (data->state == VPN_STATE_READY) {
+ /*
+ * This is the restart case, in which case we must
+ * just set the IP address.
+ *
+ * We need to remove first the old address, just
+ * replacing the old address will not work as expected
+ * because the old address will linger in the interface
+ * and not disappear so the clearing is needed here.
+ *
+ * Also the state must change, otherwise the routes
+ * will not be set properly.
+ */
+ vpn_provider_set_state(provider,
+ VPN_PROVIDER_STATE_CONNECT);
+
+ vpn_provider_clear_address(provider, AF_INET);
+ vpn_provider_clear_address(provider, AF_INET6);
+
+ vpn_provider_change_address(provider);
+ vpn_provider_set_state(provider,
+ VPN_PROVIDER_STATE_READY);
+ break;
+ }
+
index = vpn_provider_get_index(provider);
vpn_provider_ref(provider);
data->watch = vpn_rtnl_add_newlink_watch(index,
vpn_newlink, provider);
err = connman_inet_ifup(index);
if (err < 0) {
- if (err == -EALREADY)
+ if (err == -EALREADY) {
/*
* So the interface is up already, that is just
* great. Unfortunately in this case the
* newlink watch might not have been called at
* all. We must manually call it here so that
* the provider can go to ready state and the
- * routes are setup properly.
+ * routes are setup properly. Also reset flags
+ * so vpn_newlink() can handle the change.
*/
+ data->flags = 0;
vpn_newlink(IFF_UP, 0, provider);
- else
+ } else {
DBG("Cannot take interface %d up err %d/%s",
index, -err, strerror(-err));
+ }
}
break;
return NULL;
}
-static int vpn_create_tun(struct vpn_provider *provider)
+#if defined TIZEN_EXT
+static void vpn_event(struct vpn_provider *provider, int state)
+{
+ struct vpn_driver_data *vpn_driver_data;
+ const char *name;
+
+ name = vpn_provider_get_driver_name(provider);
+ if (!name) {
+ DBG("Cannot find VPN driver for provider %p", provider);
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
+ return;
+ }
+
+ vpn_driver_data = g_hash_table_lookup(driver_hash, name);
+ if (!vpn_driver_data) {
+ DBG("Cannot find VPN driver data for name %s", name);
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
+ return;
+ }
+
+ DBG("provider %p driver %s state %d", provider, name, state);
+
+ switch (state) {
+ case VPN_STATE_CONNECT:
+ vpn_provider_set_state(provider,
+ VPN_PROVIDER_STATE_CONNECT);
+ break;
+ case VPN_STATE_READY:
+ vpn_provider_set_state(provider,
+ VPN_PROVIDER_STATE_READY);
+ break;
+
+ case VPN_STATE_UNKNOWN:
+ case VPN_STATE_IDLE:
+ case VPN_STATE_DISCONNECT:
+ case VPN_STATE_FAILURE:
+ vpn_provider_set_state(provider,
+ VPN_PROVIDER_STATE_DISCONNECT);
+ break;
+
+ case VPN_STATE_AUTH_FAILURE:
+ vpn_provider_indicate_error(provider,
+ VPN_PROVIDER_ERROR_AUTH_FAILED);
+ break;
+ }
+
+ return;
+}
+#endif
+
+static int vpn_create_tun(struct vpn_provider *provider, int flags)
{
struct vpn_data *data = vpn_provider_get_data(provider);
struct ifreq ifr;
int i, fd, index;
int ret = 0;
- if (data == NULL)
+ if (!data)
return -EISCONN;
fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
}
memset(&ifr, 0, sizeof(ifr));
- ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
+ ifr.ifr_flags = flags | IFF_NO_PI;
for (i = 0; i < 256; i++) {
sprintf(ifr.ifr_name, "vpn%d", i);
goto exist_err;
}
+ data->tun_flags = flags;
+ g_free(data->if_name);
data->if_name = (char *)g_strdup(ifr.ifr_name);
- if (data->if_name == NULL) {
+ if (!data->if_name) {
connman_error("Failed to allocate memory");
close(fd);
ret = -ENOMEM;
return ret;
}
+static gboolean is_numeric(const char *str)
+{
+ gint i;
+
+ if(!str || !(*str))
+ return false;
+
+ for(i = 0; str[i] ; i++) {
+ if(!g_ascii_isdigit(str[i]))
+ return false;
+ }
+
+ return true;
+}
+
+static gint get_gid(const char *group_name)
+{
+ gint gid = -1;
+ struct group *grp;
+
+ if(!group_name || !(*group_name))
+ return gid;
+
+ if (is_numeric(group_name)) {
+ gid_t group_id = (gid_t)g_ascii_strtoull(group_name, NULL, 10);
+ grp = getgrgid(group_id);
+ } else {
+ grp = getgrnam(group_name);
+ }
+
+ if (grp)
+ gid = grp->gr_gid;
+
+ return gid;
+}
+
+static gint get_uid(const char *user_name)
+{
+ gint uid = -1;
+ struct passwd *pw;
+
+ if(!user_name || !(*user_name))
+ return uid;
+
+ if (is_numeric(user_name)) {
+ uid_t user_id = (uid_t)g_ascii_strtoull(user_name, NULL, 10);
+ pw = getpwuid(user_id);
+ } else {
+ pw = getpwnam(user_name);
+ }
+
+ if (pw)
+ uid = pw->pw_uid;
+
+ return uid;
+}
+
+static gint get_supplementary_gids(gchar **groups, gid_t **gid_list)
+{
+ gint group_count = 0;
+ gid_t *list = NULL;
+ int i;
+
+ if (groups) {
+ for(i = 0; groups[i]; i++) {
+ group_count++;
+
+ list = (gid_t*)g_try_realloc(list,
+ sizeof(gid_t) * group_count);
+
+ if (!list) {
+ DBG("cannot allocate supplementary group list");
+ break;
+ }
+
+ list[i] = get_gid(groups[i]);
+ }
+ }
+
+ *gid_list = list;
+
+ return group_count;
+}
+
+static void vpn_task_setup(gpointer user_data)
+{
+ struct vpn_plugin_data *data;
+ gint uid;
+ gint gid;
+ gid_t *gid_list = NULL;
+ size_t gid_list_size;
+ const gchar *user;
+ const gchar *group;
+ gchar **suppl_groups;
+
+ data = user_data;
+ user = vpn_settings_get_binary_user(data);
+ group = vpn_settings_get_binary_group(data);
+ suppl_groups = vpn_settings_get_binary_supplementary_groups(data);
+
+ uid = get_uid(user);
+ gid = get_gid(group);
+ gid_list_size = get_supplementary_gids(suppl_groups, &gid_list);
+
+ DBG("vpn_task_setup uid:%d gid:%d supplementary group list size:%zu",
+ uid, gid, gid_list_size);
+
+
+ /* Change group if proper group name was set, requires CAP_SETGID.*/
+ if (gid > 0 && setgid(gid))
+ connman_error("error setting gid %d %s", gid, strerror(errno));
+
+ /* Set the supplementary groups if list exists, requires CAP_SETGID. */
+ if (gid_list_size && gid_list && setgroups(gid_list_size, gid_list))
+ connman_error("error setting gid list %s", strerror(errno));
+
+ /* Change user for the task if set, requires CAP_SETUID */
+ if (uid > 0 && setuid(uid))
+ connman_error("error setting uid %d %s", uid, strerror(errno));
+}
+
+
+static gboolean update_provider_state(gpointer data)
+{
+ struct vpn_provider *provider = data;
+ struct vpn_data *vpn_data;
+ int index;
+
+ DBG("");
+
+ vpn_data = vpn_provider_get_data(provider);
+
+ index = vpn_provider_get_index(provider);
+ DBG("index to watch %d", index);
+ vpn_provider_ref(provider);
+ vpn_data->watch = vpn_rtnl_add_newlink_watch(index,
+ vpn_newlink, provider);
+ connman_inet_ifup(index);
+
+ return FALSE;
+}
+
static int vpn_connect(struct vpn_provider *provider,
- vpn_provider_connect_cb_t cb, void *user_data)
+ vpn_provider_connect_cb_t cb,
+ const char *dbus_sender, void *user_data)
{
struct vpn_data *data = vpn_provider_get_data(provider);
struct vpn_driver_data *vpn_driver_data;
+ struct vpn_plugin_data *vpn_plugin_data;
const char *name;
- int ret = 0;
+ int ret = 0, tun_flags = IFF_TUN;
+ enum vpn_state state = VPN_STATE_UNKNOWN;
- if (data != NULL)
- return -EISCONN;
+ if (data)
+ state = data->state;
- data = g_try_new0(struct vpn_data, 1);
- if (data == NULL)
- return -ENOMEM;
+ DBG("data %p state %d", data, state);
+
+ switch (state) {
+ case VPN_STATE_UNKNOWN:
+ data = g_try_new0(struct vpn_data, 1);
+ if (!data)
+ return -ENOMEM;
- data->provider = vpn_provider_ref(provider);
- data->watch = 0;
- data->flags = 0;
- data->task = NULL;
- data->state = VPN_STATE_IDLE;
+ data->provider = vpn_provider_ref(provider);
+ data->watch = 0;
+ data->flags = 0;
+ data->task = NULL;
- vpn_provider_set_data(provider, data);
+ vpn_provider_set_data(provider, data);
+ /* fall through */
+
+ case VPN_STATE_DISCONNECT:
+ case VPN_STATE_IDLE:
+ case VPN_STATE_FAILURE:
+ case VPN_STATE_AUTH_FAILURE:
+ data->state = VPN_STATE_IDLE;
+ break;
+
+ case VPN_STATE_CONNECT:
+ return -EINPROGRESS;
+
+ case VPN_STATE_READY:
+ return -EISCONN;
+ }
name = vpn_provider_get_driver_name(provider);
- if (name == NULL)
+ if (!name)
return -EINVAL;
vpn_driver_data = g_hash_table_lookup(driver_hash, name);
- if (vpn_driver_data == NULL || vpn_driver_data->vpn_driver == NULL) {
+ if (!vpn_driver_data || !vpn_driver_data->vpn_driver) {
ret = -EINVAL;
goto exist_err;
}
- if (vpn_driver_data->vpn_driver->flags != VPN_FLAG_NO_TUN) {
- ret = vpn_create_tun(provider);
+ if (!(vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN)) {
+ if (vpn_driver_data->vpn_driver->device_flags) {
+ tun_flags = vpn_driver_data->vpn_driver->device_flags(provider);
+ }
+ ret = vpn_create_tun(provider, tun_flags);
if (ret < 0)
goto exist_err;
}
- data->task = connman_task_create(vpn_driver_data->program);
- if (data->task == NULL) {
+ if (vpn_driver_data && vpn_driver_data->vpn_driver &&
+ vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_DAEMON) {
+
+ ret = vpn_driver_data->vpn_driver->connect(provider,
+ NULL, NULL, NULL, NULL, NULL);
+ if (ret) {
+ stop_vpn(provider);
+ goto exist_err;
+ }
+
+ DBG("%s started with dev %s",
+ vpn_driver_data->provider_driver.name, data->if_name);
+
+ data->state = VPN_STATE_CONNECT;
+
+ g_timeout_add(1, update_provider_state, provider);
+ return -EINPROGRESS;
+ }
+
+ vpn_plugin_data =
+ vpn_settings_get_vpn_plugin_config(vpn_driver_data->name);
+ data->task = connman_task_create(vpn_driver_data->program,
+ vpn_task_setup, vpn_plugin_data);
+
+ if (!data->task) {
ret = -ENOMEM;
stop_vpn(provider);
goto exist_err;
goto exist_err;
}
+
+#if defined TIZEN_EXT
+ if(vpn_driver_data->vpn_driver->set_event_cb)
+ vpn_driver_data->vpn_driver->set_event_cb(vpn_event, provider);
+#endif
+
ret = vpn_driver_data->vpn_driver->connect(provider, data->task,
- data->if_name, cb, user_data);
- if (ret < 0) {
+ data->if_name, cb, dbus_sender,
+ user_data);
+ if (ret < 0 && ret != -EINPROGRESS) {
stop_vpn(provider);
connman_task_destroy(data->task);
data->task = NULL;
DBG("disconnect provider %p:", provider);
- if (data == NULL)
+ if (!data)
return 0;
name = vpn_provider_get_driver_name(provider);
- if (name == NULL)
+ if (!name)
return 0;
vpn_driver_data = g_hash_table_lookup(driver_hash, name);
if (vpn_driver_data->vpn_driver->disconnect)
- vpn_driver_data->vpn_driver->disconnect();
+ vpn_driver_data->vpn_driver->disconnect(provider);
if (data->watch != 0) {
vpn_provider_unref(provider);
}
data->state = VPN_STATE_DISCONNECT;
- connman_task_stop(data->task);
+
+ if (!vpn_driver_data->vpn_driver->disconnect) {
+ DBG("Driver has no disconnect() implementation, set provider "
+ "state to disconnect.");
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT);
+ }
+
+ if (data->task)
+ connman_task_stop(data->task);
return 0;
}
static int vpn_remove(struct vpn_provider *provider)
{
struct vpn_data *data;
+ struct vpn_driver_data *driver_data;
+ const char *name;
+ int err = 0;
data = vpn_provider_get_data(provider);
- if (data == NULL)
- return 0;
+ name = vpn_provider_get_driver_name(provider);
+
+ if (!data)
+ goto call_remove;
if (data->watch != 0) {
vpn_provider_unref(provider);
data->watch = 0;
}
- connman_task_stop(data->task);
+ if (data->task)
+ connman_task_stop(data->task);
g_usleep(G_USEC_PER_SEC);
stop_vpn(provider);
- return 0;
+
+call_remove:
+ if (!name)
+ return 0;
+
+ driver_data = g_hash_table_lookup(driver_hash, name);
+
+ if (driver_data && driver_data->vpn_driver->remove)
+ err = driver_data->vpn_driver->remove(provider);
+
+ if (err)
+ DBG("%p vpn_driver->remove() returned %d", provider, err);
+
+ return err;
}
static int vpn_save(struct vpn_provider *provider, GKeyFile *keyfile)
name = vpn_provider_get_driver_name(provider);
vpn_driver_data = g_hash_table_lookup(driver_hash, name);
- if (vpn_driver_data != NULL &&
- vpn_driver_data->vpn_driver->save != NULL)
+ if (vpn_driver_data &&
+ vpn_driver_data->vpn_driver->save)
return vpn_driver_data->vpn_driver->save(provider, keyfile);
return 0;
}
+static int vpn_route_env_parse(struct vpn_provider *provider, const char *key,
+ int *family, unsigned long *idx,
+ enum vpn_provider_route_type *type)
+{
+ struct vpn_driver_data *vpn_driver_data = NULL;
+ const char *name = NULL;
+
+ if (!provider)
+ return -EINVAL;
+
+ name = vpn_provider_get_driver_name(provider);
+ vpn_driver_data = g_hash_table_lookup(driver_hash, name);
+
+ if (vpn_driver_data && vpn_driver_data->vpn_driver->route_env_parse)
+ return vpn_driver_data->vpn_driver->route_env_parse(provider, key,
+ family, idx, type);
+
+ return 0;
+}
+
int vpn_register(const char *name, struct vpn_driver *vpn_driver,
const char *program)
{
struct vpn_driver_data *data;
data = g_try_new0(struct vpn_driver_data, 1);
- if (data == NULL)
+ if (!data)
return -ENOMEM;
data->name = name;
data->program = program;
+ if (vpn_settings_parse_vpn_plugin_config(data->name) != 0)
+ DBG("No configuration provided for VPN plugin %s", data->name);
+
data->vpn_driver = vpn_driver;
data->provider_driver.name = name;
data->provider_driver.probe = vpn_probe;
data->provider_driver.remove = vpn_remove;
data->provider_driver.save = vpn_save;
+ data->provider_driver.set_state = vpn_set_state;
+ data->provider_driver.route_env_parse = vpn_route_env_parse;
- if (driver_hash == NULL)
+ if (!driver_hash)
driver_hash = g_hash_table_new_full(g_str_hash,
g_str_equal,
NULL, g_free);
- if (driver_hash == NULL) {
+ if (!driver_hash) {
connman_error("driver_hash not initialized for %s", name);
g_free(data);
return -ENOMEM;
struct vpn_driver_data *data;
data = g_hash_table_lookup(driver_hash, name);
- if (data == NULL)
+ if (!data)
return;
vpn_provider_driver_unregister(&data->provider_driver);
+ vpn_settings_delete_vpn_plugin_config(name);
g_hash_table_remove(driver_hash, name);