diff options
-rw-r--r-- | Makefile.plugins | 2 | ||||
-rw-r--r-- | plugins/openconnect.c | 347 | ||||
-rw-r--r-- | plugins/vpn.c | 404 | ||||
-rw-r--r-- | plugins/vpn.h | 41 |
4 files changed, 479 insertions, 315 deletions
diff --git a/Makefile.plugins b/Makefile.plugins index e6b79b08..261417e8 100644 --- a/Makefile.plugins +++ b/Makefile.plugins @@ -109,7 +109,7 @@ endif if OPENCONNECT if OPENCONNECT_BUILTIN builtin_modules += openconnect -builtin_sources += plugins/openconnect.c +builtin_sources += plugins/vpn.c plugins/openconnect.c builtin_cflags += -DOPENCONNECT=\"@OPENCONNECT@\" else plugin_LTLIBRARIES += plugins/openconnect.la diff --git a/plugins/openconnect.c b/plugins/openconnect.c index 2ddb9046..42192f6a 100644 --- a/plugins/openconnect.c +++ b/plugins/openconnect.c @@ -24,143 +24,23 @@ #endif #include <string.h> -#include <fcntl.h> -#include <unistd.h> -#include <sys/stat.h> -#include <sys/wait.h> -#include <stdio.h> #include <errno.h> -#include <sys/ioctl.h> -#include <linux/if_tun.h> -#include <net/if.h> -#include <stdint.h> - -#include <glib/garray.h> -#include <glib/gerror.h> -#include <glib/gmain.h> -#include <glib/gspawn.h> +#include <unistd.h> #define CONNMAN_API_SUBJECT_TO_CHANGE #include <connman/plugin.h> -#include <connman/device.h> -#include <connman/element.h> #include <connman/provider.h> #include <connman/log.h> -#include <connman/element.h> -#include <connman/rtnl.h> #include <connman/task.h> -#include "inet.h" - -enum oc_state { - OC_STATE_UNKNOWN = 0, - OC_STATE_IDLE = 1, - OC_STATE_CONNECT = 2, - OC_STATE_READY = 3, - OC_STATE_DISCONNECT = 4, - OC_STATE_FAILURE = 5, -}; - -struct oc_data { - struct connman_provider *provider; - char *if_name; - unsigned flags; - unsigned int watch; - unsigned int state; - struct connman_task *task; -}; - -static int kill_tun(char *tun_name) -{ - struct ifreq ifr; - int fd, err; - - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_flags = IFF_TUN | IFF_NO_PI; - sprintf(ifr.ifr_name, "%s", tun_name); +#include "vpn.h" - fd = open("/dev/net/tun", O_RDWR); - if (fd < 0) { - err = -errno; - connman_error("Failed to open /dev/net/tun to device %s: %s", - tun_name, strerror(errno)); - return err; - } - - if (ioctl(fd, TUNSETIFF, (void *)&ifr)) { - err = -errno; - connman_error("Failed to TUNSETIFF for device %s to it: %s", - tun_name, strerror(errno)); - close(fd); - return err; - } - - if (ioctl(fd, TUNSETPERSIST, 0)) { - err = -errno; - connman_error("Failed to set tun device %s nonpersistent: %s", - tun_name, strerror(errno)); - close(fd); - return err; - } - close(fd); - DBG("Killed tun device %s", tun_name); - return 0; -} - -static void openconnect_died(struct connman_task *task, void *user_data) -{ - struct connman_provider *provider = user_data; - struct oc_data *data = connman_provider_get_data(provider); - int state = data->state; - - DBG("provider %p data %p", provider, data); - - if (!data) - goto oc_exit; - - kill_tun(data->if_name); - connman_provider_set_data(provider, NULL); - connman_rtnl_remove_watch(data->watch); - - oc_exit: - if (state != OC_STATE_READY && state != OC_STATE_DISCONNECT) - connman_provider_set_state(provider, - CONNMAN_PROVIDER_STATE_FAILURE); - else - connman_provider_set_state(provider, - CONNMAN_PROVIDER_STATE_IDLE); - - connman_provider_set_index(provider, -1); - connman_provider_unref(data->provider); - g_free(data); - - connman_task_destroy(task); -} - -static void vpn_newlink(unsigned flags, unsigned change, void *user_data) -{ - struct connman_provider *provider = user_data; - struct oc_data *data = connman_provider_get_data(provider); - - if ((data->flags & IFF_UP) != (flags & IFF_UP)) { - if (flags & IFF_UP) { - data->state = OC_STATE_READY; - connman_provider_set_state(provider, - CONNMAN_PROVIDER_STATE_READY); - } - } - data->flags = flags; -} - -static void openconnect_task_notify(struct connman_task *task, - DBusMessage *msg, void *user_data) +static int oc_notify(DBusMessage *msg, struct connman_provider *provider) { DBusMessageIter iter, dict; - struct connman_provider *provider = user_data; struct oc_data *data; const char *reason, *key, *value; const char *domain = NULL; - int index; dbus_message_iter_init(msg, &iter); @@ -169,20 +49,17 @@ static void openconnect_task_notify(struct connman_task *task, if (!provider) { connman_error("No provider found"); - return; + return VPN_STATE_FAILURE; } data = connman_provider_get_data(provider); if (!data) { DBG("provider %p no data", provider); - return; + return VPN_STATE_FAILURE; } - if (strcmp(reason, "connect")) { - connman_provider_set_state(provider, - CONNMAN_PROVIDER_STATE_DISCONNECT); - return; - } + if (strcmp(reason, "connect")) + return VPN_STATE_DISCONNECT; domain = connman_provider_get_string(provider, "VPN.Domain"); @@ -220,236 +97,78 @@ static void openconnect_task_notify(struct connman_task *task, dbus_message_iter_next(&dict); } - index = connman_provider_get_index(provider); connman_provider_set_string(provider, "Domain", domain); - data->watch = connman_rtnl_add_newlink_watch(index, - vpn_newlink, provider); - connman_inet_ifup(index); + return VPN_STATE_CONNECT; } -static int oc_connect(struct connman_provider *provider) +static int oc_connect(struct connman_provider *provider, + struct connman_task *task, const char *if_name) { - struct oc_data *data = connman_provider_get_data(provider); - struct ifreq ifr; - int oc_fd, fd, i, index; const char *vpnhost, *vpncookie, *cafile, *mtu; - int ret = 0; - - if (data != NULL) - return -EISCONN; - - data = g_try_new0(struct oc_data, 1); - if (data == NULL) - return -ENOMEM; - - data->provider = connman_provider_ref(provider); - data->watch = 0; - data->flags = 0; - data->task = NULL; - data->state = OC_STATE_IDLE; - - connman_provider_set_data(provider, data); + int fd, err; vpnhost = connman_provider_get_string(provider, "Host"); if (!vpnhost) { connman_error("Host not set; cannot enable VPN"); - ret = -EINVAL; - goto exist_err; + return -EINVAL; } vpncookie = connman_provider_get_string(provider, "OpenConnect.Cookie"); if (!vpncookie) { connman_error("OpenConnect.Cookie not set; cannot enable VPN"); - ret = -EINVAL; - goto exist_err; + return -EINVAL; } cafile = connman_provider_get_string(provider, "OpenConnect.CACert"); mtu = connman_provider_get_string(provider, "VPN.MTU"); - fd = open("/dev/net/tun", O_RDWR); - if (fd < 0) { - i = -errno; - connman_error("Failed to open /dev/net/tun: %s", - strerror(errno)); - ret = i; - goto exist_err; - } - - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_flags = IFF_TUN | IFF_NO_PI; - - for (i = 0; i < 256; i++) { - sprintf(ifr.ifr_name, "vpn%d", i); - - if (!ioctl(fd, TUNSETIFF, (void *)&ifr)) - break; - } - - if (i == 256) { - connman_error("Failed to find available tun device"); - close(fd); - ret = -ENODEV; - goto exist_err; - } - - data->if_name = (char *)g_strdup(ifr.ifr_name); - if (!data->if_name) { - ret = -ENOMEM; - goto exist_err; - } - - if (ioctl(fd, TUNSETPERSIST, 1)) { - i = -errno; - connman_error("Failed to set tun persistent: %s", - strerror(errno)); - close(fd); - ret = i; - goto exist_err; - } - - close(fd); - - index = connman_inet_ifindex(data->if_name); - if (index < 0) { - connman_error("Failed to get tun ifindex"); - kill_tun(data->if_name); - ret = -EIO; - goto exist_err; - } - connman_provider_set_index(provider, index); - - data->task = connman_task_create(OPENCONNECT); - - if (data->task == NULL) { - ret = -ENOMEM; - kill_tun(data->if_name); - goto exist_err; - } - - if (connman_task_set_notify(data->task, "notify", - openconnect_task_notify, provider)) { - ret = -ENOMEM; - kill_tun(data->if_name); - connman_task_destroy(data->task); - data->task = NULL; - goto exist_err; - } - if (cafile) - connman_task_add_argument(data->task, "--cafile", + connman_task_add_argument(task, "--cafile", (char *)cafile); if (mtu) - connman_task_add_argument(data->task, "--mtu", (char *)mtu); + connman_task_add_argument(task, "--mtu", (char *)mtu); - connman_task_add_argument(data->task, "--syslog", NULL); - connman_task_add_argument(data->task, "--cookie-on-stdin", NULL); + connman_task_add_argument(task, "--syslog", NULL); + connman_task_add_argument(task, "--cookie-on-stdin", NULL); - connman_task_add_argument(data->task, "--script", + connman_task_add_argument(task, "--script", SCRIPTDIR "/openconnect-script"); - connman_task_add_argument(data->task, "--interface", data->if_name); + connman_task_add_argument(task, "--interface", if_name); - connman_task_add_argument(data->task, (char *)vpnhost, NULL); + connman_task_add_argument(task, (char *)vpnhost, NULL); - ret = connman_task_run(data->task, openconnect_died, provider, - &oc_fd, NULL, NULL); - if (ret) { - connman_error("Openconnect failed to start"); - kill_tun(data->if_name); - ret = -EIO; - connman_task_destroy(data->task); - data->task = NULL; - goto exist_err; + err = connman_task_run(task, vpn_died, provider, + &fd, NULL, NULL); + if (err < 0) { + connman_error("openconnect failed to start"); + return -EIO; } - DBG("openconnect started with dev %s", data->if_name); - - if (write(oc_fd, vpncookie, strlen(vpncookie)) != - (ssize_t)strlen(vpncookie) || - write(oc_fd, "\n", 1) != 1) { + if (write(fd, vpncookie, strlen(vpncookie)) != + (ssize_t)strlen(vpncookie) || + write(fd, "\n", 1) != 1) { connman_error("openconnect failed to take cookie on stdin"); - connman_provider_set_data(provider, NULL); - connman_task_stop(data->task); - ret = -EIO; - goto exist_err; + return -EIO; } - data->state = OC_STATE_CONNECT; - - return -EINPROGRESS; - - exist_err: - connman_provider_set_index(provider, -1); - connman_provider_set_data(provider, NULL); - connman_provider_unref(data->provider); - g_free(data); - - return ret; -} - -static int oc_probe(struct connman_provider *provider) -{ return 0; } -static int oc_disconnect(struct connman_provider *provider) -{ - struct oc_data *data = connman_provider_get_data(provider); - - DBG("disconnect provider %p:", provider); - - if (data == NULL) - return 0; - - if (data->watch != 0) - connman_rtnl_remove_watch(data->watch); - - data->watch = 0; - data->state = OC_STATE_DISCONNECT; - connman_task_stop(data->task); - - return 0; -} - -static int oc_remove(struct connman_provider *provider) -{ - struct oc_data *data; - - data = connman_provider_get_data(provider); - connman_provider_set_data(provider, NULL); - if (data == NULL) - return 0; - - if (data->watch != 0) - connman_rtnl_remove_watch(data->watch); - data->watch = 0; - connman_task_stop(data->task); - - g_usleep(G_USEC_PER_SEC); - kill_tun(data->if_name); - return 0; -} - -static struct connman_provider_driver provider_driver = { - .name = "openconnect", - .disconnect = oc_disconnect, +static struct vpn_driver vpn_driver = { + .notify = oc_notify, .connect = oc_connect, - .probe = oc_probe, - .remove = oc_remove, }; static int openconnect_init(void) { - connman_provider_driver_register(&provider_driver); - - return 0; + return vpn_register("openconnect", &vpn_driver, OPENCONNECT); } static void openconnect_exit(void) { - connman_provider_driver_unregister(&provider_driver); + vpn_unregister("openconnect"); } CONNMAN_PLUGIN_DEFINE(openconnect, "OpenConnect VPN plugin", VERSION, diff --git a/plugins/vpn.c b/plugins/vpn.c new file mode 100644 index 00000000..b726e949 --- /dev/null +++ b/plugins/vpn.c @@ -0,0 +1,404 @@ +/* + * + * Connection Manager + * + * Copyright (C) 2007-2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <linux/if_tun.h> +#include <net/if.h> + +#include <dbus.h> + +#include <glib/ghash.h> +#include <glib/gprintf.h> + +#include <connman/provider.h> +#include <connman/log.h> +#include <connman/rtnl.h> +#include <connman/task.h> +#include <connman/inet.h> + +#include "vpn.h" + +struct vpn_data { + struct connman_provider *provider; + char *if_name; + unsigned flags; + unsigned int watch; + unsigned int state; + struct connman_task *task; +}; + +struct vpn_driver_data { + const char *name; + const char *program; + struct vpn_driver *vpn_driver; + struct connman_provider_driver provider_driver; +}; + +GHashTable *driver_hash = NULL; + +static int kill_tun(char *tun_name) +{ + struct ifreq ifr; + int fd, err; + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + sprintf(ifr.ifr_name, "%s", tun_name); + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) { + err = -errno; + connman_error("Failed to open /dev/net/tun to device %s: %s", + tun_name, strerror(errno)); + return err; + } + + if (ioctl(fd, TUNSETIFF, (void *)&ifr)) { + err = -errno; + connman_error("Failed to TUNSETIFF for device %s to it: %s", + tun_name, strerror(errno)); + close(fd); + return err; + } + + if (ioctl(fd, TUNSETPERSIST, 0)) { + err = -errno; + connman_error("Failed to set tun device %s nonpersistent: %s", + tun_name, strerror(errno)); + close(fd); + return err; + } + close(fd); + DBG("Killed tun device %s", tun_name); + return 0; +} + +void vpn_died(struct connman_task *task, void *user_data) +{ + struct connman_provider *provider = user_data; + struct vpn_data *data = connman_provider_get_data(provider); + int state = data->state; + + DBG("provider %p data %p", provider, data); + + if (!data) + goto vpn_exit; + + kill_tun(data->if_name); + connman_provider_set_data(provider, NULL); + connman_rtnl_remove_watch(data->watch); + +vpn_exit: + if (state != VPN_STATE_READY && state != VPN_STATE_DISCONNECT) + connman_provider_set_state(provider, + CONNMAN_PROVIDER_STATE_FAILURE); + else + connman_provider_set_state(provider, + CONNMAN_PROVIDER_STATE_IDLE); + + connman_provider_set_index(provider, -1); + connman_provider_unref(data->provider); + g_free(data); + + connman_task_destroy(task); +} + +static void vpn_newlink(unsigned flags, unsigned change, void *user_data) +{ + struct connman_provider *provider = user_data; + struct vpn_data *data = connman_provider_get_data(provider); + + if ((data->flags & IFF_UP) != (flags & IFF_UP)) { + if (flags & IFF_UP) { + data->state = VPN_STATE_READY; + connman_provider_set_state(provider, + CONNMAN_PROVIDER_STATE_READY); + } + } + data->flags = flags; +} + +static void vpn_notify(struct connman_task *task, + DBusMessage *msg, void *user_data) +{ + struct connman_provider *provider = user_data; + struct vpn_data *data; + struct vpn_driver_data *vpn_driver_data; + const char *name; + int state, index; + + data = connman_provider_get_data(provider); + + name = connman_provider_get_driver_name(provider); + vpn_driver_data = g_hash_table_lookup(driver_hash, name); + if (vpn_driver_data == NULL) + return; + + state = vpn_driver_data->vpn_driver->notify(msg, provider); + switch (state) { + case VPN_STATE_CONNECT: + case VPN_STATE_READY: + index = connman_provider_get_index(provider); + data->watch = connman_rtnl_add_newlink_watch(index, + vpn_newlink, provider); + connman_inet_ifup(index); + break; + + case VPN_STATE_UNKNOWN: + case VPN_STATE_IDLE: + case VPN_STATE_DISCONNECT: + case VPN_STATE_FAILURE: + connman_provider_set_state(provider, + CONNMAN_PROVIDER_STATE_DISCONNECT); + break; + } +} + +static int vpn_connect(struct connman_provider *provider) +{ + struct vpn_data *data = connman_provider_get_data(provider); + struct vpn_driver_data *vpn_driver_data; + struct ifreq ifr; + const char *name; + int i, fd, index; + int ret = 0; + + if (data != NULL) + return -EISCONN; + + data = g_try_new0(struct vpn_data, 1); + if (data == NULL) + return -ENOMEM; + + data->provider = connman_provider_ref(provider); + data->watch = 0; + data->flags = 0; + data->task = NULL; + data->state = VPN_STATE_IDLE; + + connman_provider_set_data(provider, data); + + name = connman_provider_get_driver_name(provider); + vpn_driver_data = g_hash_table_lookup(driver_hash, name); + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) { + i = -errno; + connman_error("Failed to open /dev/net/tun: %s", + strerror(errno)); + ret = i; + goto exist_err; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + + for (i = 0; i < 256; i++) { + sprintf(ifr.ifr_name, "vpn%d", i); + + if (!ioctl(fd, TUNSETIFF, (void *)&ifr)) + break; + } + + if (i == 256) { + connman_error("Failed to find available tun device"); + close(fd); + ret = -ENODEV; + goto exist_err; + } + + data->if_name = (char *)g_strdup(ifr.ifr_name); + if (!data->if_name) { + ret = -ENOMEM; + goto exist_err; + } + + if (ioctl(fd, TUNSETPERSIST, 1)) { + i = -errno; + connman_error("Failed to set tun persistent: %s", + strerror(errno)); + close(fd); + ret = i; + goto exist_err; + } + + close(fd); + + index = connman_inet_ifindex(data->if_name); + if (index < 0) { + connman_error("Failed to get tun ifindex"); + kill_tun(data->if_name); + ret = -EIO; + goto exist_err; + } + connman_provider_set_index(provider, index); + + data->task = connman_task_create(vpn_driver_data->program); + + if (data->task == NULL) { + ret = -ENOMEM; + kill_tun(data->if_name); + goto exist_err; + } + + if (connman_task_set_notify(data->task, "notify", + vpn_notify, provider)) { + ret = -ENOMEM; + kill_tun(data->if_name); + connman_task_destroy(data->task); + data->task = NULL; + goto exist_err; + } + + ret = vpn_driver_data->vpn_driver->connect(provider, data->task, + data->if_name); + if (ret < 0) { + kill_tun(data->if_name); + connman_task_destroy(data->task); + data->task = NULL; + goto exist_err; + } + + DBG("%s started with dev %s", + vpn_driver_data->provider_driver.name, data->if_name); + + data->state = VPN_STATE_CONNECT; + + return -EINPROGRESS; + +exist_err: + connman_provider_set_index(provider, -1); + connman_provider_set_data(provider, NULL); + connman_provider_unref(data->provider); + g_free(data); + + return ret; +} + +static int vpn_probe(struct connman_provider *provider) +{ + return 0; +} + +static int vpn_disconnect(struct connman_provider *provider) +{ + struct vpn_data *data = connman_provider_get_data(provider); + struct vpn_driver_data *vpn_driver_data; + const char *name; + + DBG("disconnect provider %p:", provider); + + if (data == NULL) + return 0; + + name = connman_provider_get_driver_name(provider); + vpn_driver_data = g_hash_table_lookup(driver_hash, name); + if (vpn_driver_data->vpn_driver->disconnect) + vpn_driver_data->vpn_driver->disconnect(); + + if (data->watch != 0) + connman_rtnl_remove_watch(data->watch); + + data->watch = 0; + data->state = VPN_STATE_DISCONNECT; + connman_task_stop(data->task); + + return 0; +} + +static int vpn_remove(struct connman_provider *provider) +{ + struct vpn_data *data; + + data = connman_provider_get_data(provider); + connman_provider_set_data(provider, NULL); + if (data == NULL) + return 0; + + if (data->watch != 0) + connman_rtnl_remove_watch(data->watch); + data->watch = 0; + connman_task_stop(data->task); + + g_usleep(G_USEC_PER_SEC); + kill_tun(data->if_name); + 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) + return -ENOMEM; + + data->name = name; + data->program = program; + + data->vpn_driver = vpn_driver; + + data->provider_driver.name = name; + data->provider_driver.disconnect = vpn_disconnect; + data->provider_driver.connect = vpn_connect; + data->provider_driver.probe = vpn_probe; + data->provider_driver.remove = vpn_remove; + + if (driver_hash == NULL) { + driver_hash = g_hash_table_new_full(g_str_hash, + g_str_equal, + NULL, g_free); + } + + g_hash_table_insert(driver_hash, (char *)name, data); + + connman_provider_driver_register(&data->provider_driver); + + return 0; +} + +void vpn_unregister(const char *name) +{ + struct vpn_driver_data *data; + + data = g_hash_table_lookup(driver_hash, name); + if (data == NULL) + return; + + connman_provider_driver_unregister(&data->provider_driver); + + g_hash_table_remove(driver_hash, name); + + if (g_hash_table_size(driver_hash) == 0) + g_hash_table_destroy(driver_hash); +} diff --git a/plugins/vpn.h b/plugins/vpn.h new file mode 100644 index 00000000..7f10150a --- /dev/null +++ b/plugins/vpn.h @@ -0,0 +1,41 @@ +/* + * + * Connection Manager + * + * Copyright (C) 2010 BMW Car IT GmbH. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +enum vpn_state { + VPN_STATE_UNKNOWN = 0, + VPN_STATE_IDLE = 1, + VPN_STATE_CONNECT = 2, + VPN_STATE_READY = 3, + VPN_STATE_DISCONNECT = 4, + VPN_STATE_FAILURE = 5, +}; + +struct vpn_driver { + int (*notify) (DBusMessage *msg, struct connman_provider *provider); + int (*connect) (struct connman_provider *provider, + struct connman_task *task, const char *if_name); + void (*disconnect) (void); +}; + +int vpn_register(const char *name, struct vpn_driver *driver, + const char *program); +void vpn_unregister(const char *provider_name); +void vpn_died(); |