diff options
author | Jukka Rissanen <jukka.rissanen@linux.intel.com> | 2012-11-12 14:07:21 +0200 |
---|---|---|
committer | Patrik Flykt <patrik.flykt@linux.intel.com> | 2012-11-23 12:58:50 +0200 |
commit | 4ba04eb6172f898402e0aa66f0dc8f564a12279f (patch) | |
tree | 7a67e489ea3de6f65e65d6034b2a0951db709cd0 /vpn | |
parent | a426464354273a5586612b6577288e3662e3f8ac (diff) | |
download | connman-4ba04eb6172f898402e0aa66f0dc8f564a12279f.tar.gz connman-4ba04eb6172f898402e0aa66f0dc8f564a12279f.tar.bz2 connman-4ba04eb6172f898402e0aa66f0dc8f564a12279f.zip |
vpn: New vpn daemon that handles vpn connections and clients
Diffstat (limited to 'vpn')
-rw-r--r-- | vpn/connman-vpn.service.in | 12 | ||||
-rw-r--r-- | vpn/main.c | 257 | ||||
-rw-r--r-- | vpn/net.connman.vpn.service.in | 5 | ||||
-rw-r--r-- | vpn/plugins/l2tp.c | 533 | ||||
-rw-r--r-- | vpn/plugins/openconnect.c | 282 | ||||
-rw-r--r-- | vpn/plugins/openvpn.c | 328 | ||||
-rw-r--r-- | vpn/plugins/pptp.c | 339 | ||||
-rw-r--r-- | vpn/plugins/vpn.c | 535 | ||||
-rw-r--r-- | vpn/plugins/vpn.h | 63 | ||||
-rw-r--r-- | vpn/plugins/vpnc.c | 345 | ||||
-rw-r--r-- | vpn/vpn-dbus.conf | 15 | ||||
-rw-r--r-- | vpn/vpn-ipconfig.c | 450 | ||||
-rw-r--r-- | vpn/vpn-manager.c | 142 | ||||
-rw-r--r-- | vpn/vpn-polkit.conf | 11 | ||||
-rw-r--r-- | vpn/vpn-polkit.policy | 29 | ||||
-rw-r--r-- | vpn/vpn-provider.c | 1680 | ||||
-rw-r--r-- | vpn/vpn-provider.h | 121 | ||||
-rw-r--r-- | vpn/vpn-rtnl.c | 1185 | ||||
-rw-r--r-- | vpn/vpn-rtnl.h | 65 | ||||
-rw-r--r-- | vpn/vpn.h | 98 | ||||
-rw-r--r-- | vpn/vpn.ver | 8 |
21 files changed, 6503 insertions, 0 deletions
diff --git a/vpn/connman-vpn.service.in b/vpn/connman-vpn.service.in new file mode 100644 index 00000000..ec02a867 --- /dev/null +++ b/vpn/connman-vpn.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=ConnMan VPN service +After=syslog.target + +[Service] +Type=dbus +BusName=net.connman.vpn +ExecStart=@prefix@/sbin/connman-vpnd -n +StandardOutput=null + +[Install] +WantedBy=multi-user.target diff --git a/vpn/main.c b/vpn/main.c new file mode 100644 index 00000000..35daca77 --- /dev/null +++ b/vpn/main.c @@ -0,0 +1,257 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2012 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 <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/signalfd.h> +#include <getopt.h> +#include <sys/stat.h> +#include <net/if.h> +#include <netdb.h> + +#include <gdbus.h> + +#include "../src/connman.h" +#include "vpn.h" + +#include "connman/vpn-dbus.h" + +static GMainLoop *main_loop = NULL; + +static unsigned int __terminated = 0; + +static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, + gpointer user_data) +{ + struct signalfd_siginfo si; + ssize_t result; + int fd; + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) + return FALSE; + + fd = g_io_channel_unix_get_fd(channel); + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return FALSE; + + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + if (__terminated == 0) { + connman_info("Terminating"); + g_main_loop_quit(main_loop); + } + + __terminated = 1; + break; + } + + return TRUE; +} + +static guint setup_signalfd(void) +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("Failed to set signal mask"); + return 0; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + perror("Failed to create signal descriptor"); + return 0; + } + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + signal_handler, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static void disconnect_callback(DBusConnection *conn, void *user_data) +{ + connman_error("D-Bus disconnect"); + + g_main_loop_quit(main_loop); +} + +static gchar *option_debug = NULL; +static gchar *option_plugin = NULL; +static gchar *option_noplugin = NULL; +static gboolean option_detach = TRUE; +static gboolean option_version = FALSE; + +static gboolean parse_debug(const char *key, const char *value, + gpointer user_data, GError **error) +{ + if (value) + option_debug = g_strdup(value); + else + option_debug = g_strdup("*"); + + return TRUE; +} + +static GOptionEntry options[] = { + { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, + G_OPTION_ARG_CALLBACK, parse_debug, + "Specify debug options to enable", "DEBUG" }, + { "plugin", 'p', 0, G_OPTION_ARG_STRING, &option_plugin, + "Specify plugins to load", "NAME,..." }, + { "noplugin", 'P', 0, G_OPTION_ARG_STRING, &option_noplugin, + "Specify plugins not to load", "NAME,..." }, + { "nodaemon", 'n', G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &option_detach, + "Don't fork daemon to background" }, + { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, + "Show version information and exit" }, + { NULL }, +}; + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + GError *error = NULL; + DBusConnection *conn; + DBusError err; + guint signal; + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (g_option_context_parse(context, &argc, &argv, &error) == FALSE) { + if (error != NULL) { + g_printerr("%s\n", error->message); + g_error_free(error); + } else + g_printerr("An unknown error occurred\n"); + exit(1); + } + + g_option_context_free(context); + + if (option_version == TRUE) { + printf("%s\n", VERSION); + exit(0); + } + + if (option_detach == TRUE) { + if (daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + } + + if (mkdir(STATEDIR, S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) { + if (errno != EEXIST) + perror("Failed to create state directory"); + } + + if (mkdir(STORAGEDIR, S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) { + if (errno != EEXIST) + perror("Failed to create storage directory"); + } + + umask(0077); + + main_loop = g_main_loop_new(NULL, FALSE); + + signal = setup_signalfd(); + + dbus_error_init(&err); + + conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, VPN_SERVICE, &err); + if (conn == NULL) { + if (dbus_error_is_set(&err) == TRUE) { + fprintf(stderr, "%s\n", err.message); + dbus_error_free(&err); + } else + fprintf(stderr, "Can't register with system bus\n"); + exit(1); + } + + g_dbus_set_disconnect_function(conn, disconnect_callback, NULL, NULL); + + __connman_log_init(argv[0], option_debug, option_detach, FALSE, + "Connection Manager VPN daemon", VERSION); + __connman_dbus_init(conn); + __vpn_provider_init(); + __vpn_manager_init(); + __vpn_ipconfig_init(); + __vpn_rtnl_init(); + __connman_task_init(); + __connman_plugin_init(option_plugin, option_noplugin); + + __vpn_rtnl_start(); + + g_free(option_plugin); + g_free(option_noplugin); + + g_main_loop_run(main_loop); + + g_source_remove(signal); + + __connman_task_cleanup(); + __vpn_rtnl_cleanup(); + __vpn_ipconfig_cleanup(); + __vpn_manager_cleanup(); + __vpn_provider_cleanup(); + __connman_dbus_cleanup(); + __connman_log_cleanup(FALSE); + + dbus_connection_unref(conn); + + g_main_loop_unref(main_loop); + + g_free(option_debug); + + return 0; +} diff --git a/vpn/net.connman.vpn.service.in b/vpn/net.connman.vpn.service.in new file mode 100644 index 00000000..fc9e9bf3 --- /dev/null +++ b/vpn/net.connman.vpn.service.in @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=net.connman.vpn +Exec=@prefix@/sbin/connman-vpnd -n +User=root +SystemdService=connman-vpn.service diff --git a/vpn/plugins/l2tp.c b/vpn/plugins/l2tp.c new file mode 100644 index 00000000..5a11e529 --- /dev/null +++ b/vpn/plugins/l2tp.c @@ -0,0 +1,533 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2010 BMW Car IT GmbH. All rights reserved. + * Copyright (C) 2012 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 <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <stdio.h> +#include <net/if.h> + +#include <dbus/dbus.h> +#include <glib.h> + +#define CONNMAN_API_SUBJECT_TO_CHANGE +#include <connman/plugin.h> +#include <connman/provider.h> +#include <connman/log.h> +#include <connman/task.h> +#include <connman/dbus.h> +#include <connman/inet.h> + +#include "vpn.h" + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +enum { + OPT_STRING = 1, + OPT_BOOL = 2, +}; + +enum { + OPT_ALL = 1, + OPT_L2G = 2, + OPT_L2 = 3, + OPT_PPPD = 4, +}; + +struct { + const char *cm_opt; + const char *pppd_opt; + int sub; + const char *vpn_default; + int type; +} pppd_options[] = { + { "L2TP.User", "name", OPT_ALL, NULL, OPT_STRING }, + { "L2TP.BPS", "bps", OPT_L2, NULL, OPT_STRING }, + { "L2TP.LengthBit", "length bit", OPT_L2, NULL, OPT_STRING }, + { "L2TP.Challenge", "challenge", OPT_L2, NULL, OPT_STRING }, + { "L2TP.DefaultRoute", "defaultroute", OPT_L2, NULL, OPT_STRING }, + { "L2TP.FlowBit", "flow bit", OPT_L2, NULL, OPT_STRING }, + { "L2TP.TunnelRWS", "tunnel rws", OPT_L2, NULL, OPT_STRING }, + { "L2TP.Exclusive", "exclusive", OPT_L2, NULL, OPT_STRING }, + { "L2TP.Autodial", "autodial", OPT_L2, "yes", OPT_STRING }, + { "L2TP.Redial", "redial", OPT_L2, "yes", OPT_STRING }, + { "L2TP.RedialTimeout", "redial timeout", OPT_L2, "10", OPT_STRING }, + { "L2TP.MaxRedials", "max redials", OPT_L2, NULL, OPT_STRING }, + { "L2TP.RequirePAP", "require pap", OPT_L2, "no", OPT_STRING }, + { "L2TP.RequireCHAP", "require chap", OPT_L2, "yes", OPT_STRING }, + { "L2TP.ReqAuth", "require authentication", OPT_L2, "no", OPT_STRING }, + { "L2TP.AccessControl", "access control", OPT_L2G, "yes", OPT_STRING }, + { "L2TP.AuthFile", "auth file", OPT_L2G, NULL, OPT_STRING }, + { "L2TP.ForceUserSpace", "force userspace", OPT_L2G, NULL, OPT_STRING }, + { "L2TP.ListenAddr", "listen-addr", OPT_L2G, NULL, OPT_STRING }, + { "L2TP.Rand Source", "rand source", OPT_L2G, NULL, OPT_STRING }, + { "L2TP.IPsecSaref", "ipsec saref", OPT_L2G, NULL, OPT_STRING }, + { "L2TP.Port", "port", OPT_L2G, NULL, OPT_STRING }, + { "L2TP.EchoFailure", "lcp-echo-failure", OPT_PPPD, "0", OPT_STRING }, + { "L2TP.EchoInterval", "lcp-echo-interval", OPT_PPPD, "0", OPT_STRING }, + { "L2TP.Debug", "debug", OPT_PPPD, NULL, OPT_STRING }, + { "L2TP.RefuseEAP", "refuse-eap", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.RefusePAP", "refuse-pap", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.RefuseCHAP", "refuse-chap", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.RefuseMSCHAP", "refuse-mschap", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.RefuseMSCHAP2", "refuse-mschapv2", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.NoBSDComp", "nobsdcomp", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.NoPcomp", "nopcomp", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.UseAccomp", "accomp", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.NoDeflate", "nodeflatey", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.ReqMPPE", "require-mppe", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.ReqMPPE40", "require-mppe-40", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.ReqMPPE128", "require-mppe-128", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.ReqMPPEStateful", "mppe-stateful", OPT_PPPD, NULL, OPT_BOOL }, + { "L2TP.NoVJ", "no-vj-comp", OPT_PPPD, NULL, OPT_BOOL }, +}; + +static DBusConnection *connection; + +static DBusMessage *l2tp_get_sec(struct connman_task *task, + DBusMessage *msg, void *user_data) +{ + const char *user, *passwd; + struct vpn_provider *provider = user_data; + + if (dbus_message_get_no_reply(msg) == FALSE) { + DBusMessage *reply; + + user = vpn_provider_get_string(provider, "L2TP.User"); + passwd = vpn_provider_get_string(provider, "L2TP.Password"); + + if (user == NULL || strlen(user) == 0 || + passwd == NULL || strlen(passwd) == 0) + return NULL; + + reply = dbus_message_new_method_return(msg); + if (reply == NULL) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &user, + DBUS_TYPE_STRING, &passwd, + DBUS_TYPE_INVALID); + + return reply; + } + + return NULL; +} + +static int l2tp_notify(DBusMessage *msg, struct vpn_provider *provider) +{ + DBusMessageIter iter, dict; + const char *reason, *key, *value; + char *addressv4 = NULL, *netmask = NULL, *gateway = NULL; + char *ifname = NULL, *nameservers = NULL; + struct connman_ipaddress *ipaddress = NULL; + + dbus_message_iter_init(msg, &iter); + + dbus_message_iter_get_basic(&iter, &reason); + dbus_message_iter_next(&iter); + + if (!provider) { + connman_error("No provider found"); + return VPN_STATE_FAILURE; + } + + if (strcmp(reason, "auth failed") == 0) + return VPN_STATE_AUTH_FAILURE; + + if (strcmp(reason, "connect")) + return VPN_STATE_DISCONNECT; + + dbus_message_iter_recurse(&iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + dbus_message_iter_get_basic(&entry, &value); + + DBG("%s = %s", key, value); + + if (!strcmp(key, "INTERNAL_IP4_ADDRESS")) { + vpn_provider_set_string(provider, "Address", value); + addressv4 = g_strdup(value); + } + + if (!strcmp(key, "INTERNAL_IP4_NETMASK")) { + vpn_provider_set_string(provider, "Netmask", value); + netmask = g_strdup(value); + } + + if (!strcmp(key, "INTERNAL_IP4_DNS")) { + vpn_provider_set_string(provider, "DNS", value); + nameservers = g_strdup(value); + } + + if (!strcmp(key, "INTERNAL_IFNAME")) + ifname = g_strdup(value); + + dbus_message_iter_next(&dict); + } + + if (vpn_set_ifname(provider, ifname) < 0) { + g_free(ifname); + g_free(addressv4); + g_free(netmask); + g_free(nameservers); + return VPN_STATE_FAILURE; + } + + if (addressv4 != NULL) + ipaddress = connman_ipaddress_alloc(AF_INET); + + g_free(ifname); + + if (ipaddress == NULL) { + connman_error("No IP address for provider"); + g_free(addressv4); + g_free(netmask); + g_free(nameservers); + return VPN_STATE_FAILURE; + } + + value = vpn_provider_get_string(provider, "HostIP"); + if (value != NULL) { + vpn_provider_set_string(provider, "Gateway", value); + gateway = g_strdup(value); + } + + if (addressv4 != NULL) + connman_ipaddress_set_ipv4(ipaddress, addressv4, netmask, + gateway); + + vpn_provider_set_ipaddress(provider, ipaddress); + vpn_provider_set_nameservers(provider, nameservers); + + g_free(addressv4); + g_free(netmask); + g_free(gateway); + g_free(nameservers); + connman_ipaddress_free(ipaddress); + + return VPN_STATE_CONNECT; +} + +static int l2tp_save(struct vpn_provider *provider, GKeyFile *keyfile) +{ + const char *option; + int i; + + for (i = 0; i < (int)ARRAY_SIZE(pppd_options); i++) { + if (strncmp(pppd_options[i].cm_opt, "L2TP.", 5) == 0) { + option = vpn_provider_get_string(provider, + pppd_options[i].cm_opt); + if (option == NULL) + continue; + + g_key_file_set_string(keyfile, + vpn_provider_get_save_group(provider), + pppd_options[i].cm_opt, option); + } + } + return 0; +} + +static ssize_t full_write(int fd, const void *buf, size_t len) +{ + ssize_t byte_write; + + while (len) { + byte_write = write(fd, buf, len); + if (byte_write < 0) { + connman_error("failed to write config to l2tp: %s\n", + strerror(errno)); + return byte_write; + } + len -= byte_write; + buf += byte_write; + } + + return 0; +} + +static ssize_t l2tp_write_bool_option(int fd, + const char *key, const char *value) +{ + gchar *buf; + ssize_t ret = 0; + + if (key != NULL && value != NULL) { + if (strcasecmp(value, "yes") == 0 || + strcasecmp(value, "true") == 0 || + strcmp(value, "1") == 0) { + buf = g_strdup_printf("%s\n", key); + ret = full_write(fd, buf, strlen(buf)); + + g_free(buf); + } + } + + return ret; +} + +static int l2tp_write_option(int fd, const char *key, const char *value) +{ + gchar *buf; + ssize_t ret = 0; + + if (key != NULL) { + if (value != NULL) + buf = g_strdup_printf("%s %s\n", key, value); + else + buf = g_strdup_printf("%s\n", key); + + ret = full_write(fd, buf, strlen(buf)); + + g_free(buf); + } + + return ret; +} + +static int l2tp_write_section(int fd, const char *key, const char *value) +{ + gchar *buf; + ssize_t ret = 0; + + if (key != NULL && value != NULL) { + buf = g_strdup_printf("%s = %s\n", key, value); + ret = full_write(fd, buf, strlen(buf)); + + g_free(buf); + } + + return ret; +} + +static int write_pppd_option(struct vpn_provider *provider, int fd) +{ + int i; + const char *opt_s; + + l2tp_write_option(fd, "nodetach", NULL); + l2tp_write_option(fd, "lock", NULL); + l2tp_write_option(fd, "usepeerdns", NULL); + l2tp_write_option(fd, "noipdefault", NULL); + l2tp_write_option(fd, "noauth", NULL); + l2tp_write_option(fd, "nodefaultroute", NULL); + l2tp_write_option(fd, "ipparam", "l2tp_plugin"); + + for (i = 0; i < (int)ARRAY_SIZE(pppd_options); i++) { + if (pppd_options[i].sub != OPT_ALL && + pppd_options[i].sub != OPT_PPPD) + continue; + + opt_s = vpn_provider_get_string(provider, + pppd_options[i].cm_opt); + if (!opt_s) + opt_s = pppd_options[i].vpn_default; + + if (!opt_s) + continue; + + if (pppd_options[i].type == OPT_STRING) + l2tp_write_option(fd, + pppd_options[i].pppd_opt, opt_s); + else if (pppd_options[i].type == OPT_BOOL) + l2tp_write_bool_option(fd, + pppd_options[i].pppd_opt, opt_s); + } + + l2tp_write_option(fd, "plugin", + SCRIPTDIR "/libppp-plugin.so"); + + return 0; +} + + +static int l2tp_write_fields(struct vpn_provider *provider, + int fd, int sub) +{ + int i; + const char *opt_s; + + for (i = 0; i < (int)ARRAY_SIZE(pppd_options); i++) { + if (pppd_options[i].sub != sub) + continue; + + opt_s = vpn_provider_get_string(provider, + pppd_options[i].cm_opt); + if (!opt_s) + opt_s = pppd_options[i].vpn_default; + + if (!opt_s) + continue; + + if (pppd_options[i].type == OPT_STRING) + l2tp_write_section(fd, + pppd_options[i].pppd_opt, opt_s); + else if (pppd_options[i].type == OPT_BOOL) + l2tp_write_bool_option(fd, + pppd_options[i].pppd_opt, opt_s); + } + + return 0; +} + +static int l2tp_write_config(struct vpn_provider *provider, + const char *pppd_name, int fd) +{ + const char *option; + + l2tp_write_option(fd, "[global]", NULL); + l2tp_write_fields(provider, fd, OPT_L2G); + + l2tp_write_option(fd, "[lac l2tp]", NULL); + + option = vpn_provider_get_string(provider, "Host"); + l2tp_write_option(fd, "lns =", option); + + l2tp_write_fields(provider, fd, OPT_ALL); + l2tp_write_fields(provider, fd, OPT_L2); + + l2tp_write_option(fd, "pppoptfile =", pppd_name); + + return 0; +} + +static void l2tp_died(struct connman_task *task, int exit_code, void *user_data) +{ + char *conf_file; + + vpn_died(task, exit_code, user_data); + + conf_file = g_strdup_printf("/var/run/connman/connman-xl2tpd.conf"); + unlink(conf_file); + g_free(conf_file); + + conf_file = g_strdup_printf("/var/run/connman/connman-ppp-option.conf"); + unlink(conf_file); + g_free(conf_file); +} + +static int l2tp_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name) +{ + const char *host; + char *l2tp_name, *pppd_name; + int l2tp_fd, pppd_fd; + int err; + + if (connman_task_set_notify(task, "getsec", + l2tp_get_sec, provider)) + return -ENOMEM; + + host = vpn_provider_get_string(provider, "Host"); + if (host == NULL) { + connman_error("Host not set; cannot enable VPN"); + return -EINVAL; + } + + l2tp_name = g_strdup_printf("/var/run/connman/connman-xl2tpd.conf"); + + l2tp_fd = open(l2tp_name, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR); + if (l2tp_fd < 0) { + g_free(l2tp_name); + connman_error("Error writing l2tp config"); + return -EIO; + } + + pppd_name = g_strdup_printf("/var/run/connman/connman-ppp-option.conf"); + + pppd_fd = open(pppd_name, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR); + if (pppd_fd < 0) { + connman_error("Error writing pppd config"); + g_free(l2tp_name); + g_free(pppd_name); + close(l2tp_fd); + return -EIO; + } + + l2tp_write_config(provider, pppd_name, l2tp_fd); + + write_pppd_option(provider, pppd_fd); + + connman_task_add_argument(task, "-D", NULL); + connman_task_add_argument(task, "-c", l2tp_name); + + g_free(l2tp_name); + g_free(pppd_name); + + err = connman_task_run(task, l2tp_died, provider, + NULL, NULL, NULL); + if (err < 0) { + connman_error("l2tp failed to start"); + return -EIO; + } + + return 0; +} + +static int l2tp_error_code(int exit_code) +{ + switch (exit_code) { + case 1: + return CONNMAN_PROVIDER_ERROR_CONNECT_FAILED; + default: + return CONNMAN_PROVIDER_ERROR_UNKNOWN; + } +} + +static struct vpn_driver vpn_driver = { + .flags = VPN_FLAG_NO_TUN, + .notify = l2tp_notify, + .connect = l2tp_connect, + .error_code = l2tp_error_code, + .save = l2tp_save, +}; + +static int l2tp_init(void) +{ + connection = connman_dbus_get_connection(); + + return vpn_register("l2tp", &vpn_driver, L2TP); +} + +static void l2tp_exit(void) +{ + vpn_unregister("l2tp"); + + dbus_connection_unref(connection); +} + +CONNMAN_PLUGIN_DEFINE(l2tp, "l2tp plugin", VERSION, + CONNMAN_PLUGIN_PRIORITY_DEFAULT, l2tp_init, l2tp_exit) diff --git a/vpn/plugins/openconnect.c b/vpn/plugins/openconnect.c new file mode 100644 index 00000000..0f54108c --- /dev/null +++ b/vpn/plugins/openconnect.c @@ -0,0 +1,282 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2007-2012 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 <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <net/if.h> + +#include <glib.h> + +#define CONNMAN_API_SUBJECT_TO_CHANGE +#include <connman/plugin.h> +#include <connman/log.h> +#include <connman/task.h> +#include <connman/ipconfig.h> + +#include "../vpn-provider.h" + +#include "vpn.h" + +static int oc_notify(DBusMessage *msg, struct vpn_provider *provider) +{ + DBusMessageIter iter, dict; + const char *reason, *key, *value; + const char *domain = NULL; + char *addressv4 = NULL, *addressv6 = NULL; + char *netmask = NULL, *gateway = NULL; + unsigned char prefix_len = 0; + struct connman_ipaddress *ipaddress; + + dbus_message_iter_init(msg, &iter); + + dbus_message_iter_get_basic(&iter, &reason); + dbus_message_iter_next(&iter); + + if (!provider) { + connman_error("No provider found"); + return VPN_STATE_FAILURE; + } + + if (strcmp(reason, "connect")) + return VPN_STATE_DISCONNECT; + + domain = vpn_provider_get_string(provider, "VPN.Domain"); + + dbus_message_iter_recurse(&iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + dbus_message_iter_get_basic(&entry, &value); + + if (strcmp(key, "CISCO_CSTP_OPTIONS")) + DBG("%s = %s", key, value); + + if (!strcmp(key, "VPNGATEWAY")) + gateway = g_strdup(value); + + if (!strcmp(key, "INTERNAL_IP4_ADDRESS")) + addressv4 = g_strdup(value); + + if (!strcmp(key, "INTERNAL_IP6_ADDRESS")) { + addressv6 = g_strdup(value); + prefix_len = 128; + } + + if (!strcmp(key, "INTERNAL_IP4_NETMASK")) + netmask = g_strdup(value); + + if (!strcmp(key, "INTERNAL_IP6_NETMASK")) { + char *sep; + + /* The netmask contains the address and the prefix */ + sep = strchr(value, '/'); + if (sep != NULL) { + unsigned char ip_len = sep - value; + + addressv6 = g_strndup(value, ip_len); + prefix_len = (unsigned char) + strtol(sep + 1, NULL, 10); + } + } + + if (!strcmp(key, "INTERNAL_IP4_DNS") || + !strcmp(key, "INTERNAL_IP6_DNS")) + vpn_provider_set_nameservers(provider, value); + + if (!strcmp(key, "CISCO_PROXY_PAC")) + vpn_provider_set_pac(provider, value); + + if (domain == NULL && !strcmp(key, "CISCO_DEF_DOMAIN")) + domain = value; + + if (g_str_has_prefix(key, "CISCO_SPLIT_INC") == TRUE || + g_str_has_prefix(key, "CISCO_IPV6_SPLIT_INC") == TRUE) + vpn_provider_append_route(provider, key, value); + + dbus_message_iter_next(&dict); + } + + DBG("%p %p", addressv4, addressv6); + + if (addressv4 != NULL) + ipaddress = connman_ipaddress_alloc(AF_INET); + else if (addressv6 != NULL) + ipaddress = connman_ipaddress_alloc(AF_INET6); + else + ipaddress = NULL; + + if (ipaddress == NULL) { + g_free(addressv4); + g_free(addressv6); + g_free(netmask); + g_free(gateway); + + return VPN_STATE_FAILURE; + } + + if (addressv4 != NULL) + connman_ipaddress_set_ipv4(ipaddress, addressv4, + netmask, gateway); + else + connman_ipaddress_set_ipv6(ipaddress, addressv6, + prefix_len, gateway); + vpn_provider_set_ipaddress(provider, ipaddress); + vpn_provider_set_domain(provider, domain); + + g_free(addressv4); + g_free(addressv6); + g_free(netmask); + g_free(gateway); + connman_ipaddress_free(ipaddress); + + return VPN_STATE_CONNECT; +} + +static int oc_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name) +{ + const char *vpnhost, *vpncookie, *cafile, *certsha1, *mtu; + int fd, err; + + vpnhost = vpn_provider_get_string(provider, "Host"); + if (!vpnhost) { + connman_error("Host not set; cannot enable VPN"); + return -EINVAL; + } + + vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie"); + if (!vpncookie) { + connman_error("OpenConnect.Cookie not set; cannot enable VPN"); + return -EINVAL; + } + + certsha1 = vpn_provider_get_string(provider, + "OpenConnect.ServerCert"); + if (certsha1) + connman_task_add_argument(task, "--servercert", + (char *)certsha1); + + cafile = vpn_provider_get_string(provider, "OpenConnect.CACert"); + mtu = vpn_provider_get_string(provider, "VPN.MTU"); + + if (cafile) + connman_task_add_argument(task, "--cafile", + (char *)cafile); + if (mtu) + connman_task_add_argument(task, "--mtu", (char *)mtu); + + connman_task_add_argument(task, "--syslog", NULL); + connman_task_add_argument(task, "--cookie-on-stdin", NULL); + + connman_task_add_argument(task, "--script", + SCRIPTDIR "/openconnect-script"); + + connman_task_add_argument(task, "--interface", if_name); + + connman_task_add_argument(task, (char *)vpnhost, NULL); + + err = connman_task_run(task, vpn_died, provider, + &fd, NULL, NULL); + if (err < 0) { + connman_error("openconnect failed to start"); + return -EIO; + } + + if (write(fd, vpncookie, strlen(vpncookie)) != + (ssize_t)strlen(vpncookie) || + write(fd, "\n", 1) != 1) { + connman_error("openconnect failed to take cookie on stdin"); + return -EIO; + } + + return 0; +} + +static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile) +{ + const char *setting; + + setting = vpn_provider_get_string(provider, + "OpenConnect.ServerCert"); + if (setting != NULL) + g_key_file_set_string(keyfile, + vpn_provider_get_save_group(provider), + "OpenConnect.ServerCert", setting); + + setting = vpn_provider_get_string(provider, + "OpenConnect.CACert"); + if (setting != NULL) + g_key_file_set_string(keyfile, + vpn_provider_get_save_group(provider), + "OpenConnect.CACert", setting); + + setting = vpn_provider_get_string(provider, + "VPN.MTU"); + if (setting != NULL) + g_key_file_set_string(keyfile, + vpn_provider_get_save_group(provider), + "VPN.MTU", setting); + + return 0; +} + +static int oc_error_code(int exit_code) +{ + + switch (exit_code) { + case 1: + return VPN_PROVIDER_ERROR_CONNECT_FAILED; + case 2: + return VPN_PROVIDER_ERROR_LOGIN_FAILED; + default: + return VPN_PROVIDER_ERROR_UNKNOWN; + } +} + +static struct vpn_driver vpn_driver = { + .notify = oc_notify, + .connect = oc_connect, + .error_code = oc_error_code, + .save = oc_save, +}; + +static int openconnect_init(void) +{ + return vpn_register("openconnect", &vpn_driver, OPENCONNECT); +} + +static void openconnect_exit(void) +{ + vpn_unregister("openconnect"); +} + +CONNMAN_PLUGIN_DEFINE(openconnect, "OpenConnect VPN plugin", VERSION, + CONNMAN_PLUGIN_PRIORITY_DEFAULT, openconnect_init, openconnect_exit) diff --git a/vpn/plugins/openvpn.c b/vpn/plugins/openvpn.c new file mode 100644 index 00000000..dd654156 --- /dev/null +++ b/vpn/plugins/openvpn.c @@ -0,0 +1,328 @@ +/* + * + * ConnMan VPN daemon + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <net/if.h> + +#include <glib.h> + +#define CONNMAN_API_SUBJECT_TO_CHANGE +#include <connman/plugin.h> +#include <connman/log.h> +#include <connman/task.h> +#include <connman/dbus.h> +#include <connman/ipconfig.h> + +#include "../vpn-provider.h" + +#include "vpn.h" + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +static DBusConnection *connection; + +struct { + const char *cm_opt; + const char *ov_opt; + char has_value; +} ov_options[] = { + { "Host", "--remote", 1 }, + { "OpenVPN.CACert", "--ca", 1 }, + { "OpenVPN.Cert", "--cert", 1 }, + { "OpenVPN.Key", "--key", 1 }, + { "OpenVPN.MTU", "--mtu", 1 }, + { "OpenVPN.NSCertType", "--ns-cert-type", 1 }, + { "OpenVPN.Proto", "--proto", 1 }, + { "OpenVPN.Port", "--port", 1 }, + { "OpenVPN.AuthUserPass", "--auth-user-pass", 1 }, + { "OpenVPN.AskPass", "--askpass", 1 }, + { "OpenVPN.AuthNoCache", "--auth-nocache", 0 }, + { "OpenVPN.TLSRemote", "--tls-remote", 1 }, + { "OpenVPN.TLSAuth", NULL, 1 }, + { "OpenVPN.TLSAuthDir", NULL, 1 }, + { "OpenVPN.Cipher", "--cipher", 1 }, + { "OpenVPN.Auth", "--auth", 1 }, + { "OpenVPN.CompLZO", "--comp-lzo", 0 }, + { "OpenVPN.RemoteCertTls", "--remote-cert-tls", 1 }, +}; + +static void ov_append_dns_entries(const char *key, const char *value, + char **dns_entries) +{ + gchar **options; + + if (g_str_has_prefix(key, "foreign_option_") == FALSE) + return; + + options = g_strsplit(value, " ", 3); + if (options[0] != NULL && + !strcmp(options[0], "dhcp-option") && + options[1] != NULL && + !strcmp(options[1], "DNS") && + options[2] != NULL) { + + if (*dns_entries != NULL) { + char *tmp; + + tmp = g_strjoin(" ", *dns_entries, + options[2], NULL); + g_free(*dns_entries); + *dns_entries = tmp; + } else { + *dns_entries = g_strdup(options[2]); + } + } + + g_strfreev(options); +} + +static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) +{ + DBusMessageIter iter, dict; + const char *reason, *key, *value; + char *nameservers = NULL; + char *address = NULL, *gateway = NULL, *peer = NULL; + struct connman_ipaddress *ipaddress; + + dbus_message_iter_init(msg, &iter); + + dbus_message_iter_get_basic(&iter, &reason); + dbus_message_iter_next(&iter); + + if (!provider) { + connman_error("No provider found"); + return VPN_STATE_FAILURE; + } + + if (strcmp(reason, "up")) + return VPN_STATE_DISCONNECT; + + dbus_message_iter_recurse(&iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + dbus_message_iter_get_basic(&entry, &value); + + DBG("%s = %s", key, value); + + if (!strcmp(key, "trusted_ip")) { + vpn_provider_set_string(provider, "Gateway", value); + gateway = g_strdup(value); + } + + if (!strcmp(key, "ifconfig_local")) { + vpn_provider_set_string(provider, "Address", value); + address = g_strdup(value); + } + + if (!strcmp(key, "ifconfig_remote")) { + vpn_provider_set_string(provider, "Peer", value); + peer = g_strdup(value); + } + + if (g_str_has_prefix(key, "route_") == TRUE) + vpn_provider_append_route(provider, key, value); + + ov_append_dns_entries(key, value, &nameservers); + + dbus_message_iter_next(&dict); + } + + ipaddress = connman_ipaddress_alloc(AF_INET); + if (ipaddress == NULL) { + g_free(nameservers); + g_free(address); + g_free(gateway); + g_free(peer); + + return VPN_STATE_FAILURE; + } + + connman_ipaddress_set_ipv4(ipaddress, address, NULL, gateway); + connman_ipaddress_set_peer(ipaddress, peer); + vpn_provider_set_ipaddress(provider, ipaddress); + + vpn_provider_set_nameservers(provider, nameservers); + + g_free(nameservers); + g_free(address); + g_free(gateway); + g_free(peer); + connman_ipaddress_free(ipaddress); + + return VPN_STATE_CONNECT; +} + +static int ov_save(struct vpn_provider *provider, GKeyFile *keyfile) +{ + const char *option; + int i; + + for (i = 0; i < (int)ARRAY_SIZE(ov_options); i++) { + if (strncmp(ov_options[i].cm_opt, "OpenVPN.", 8) == 0) { + option = vpn_provider_get_string(provider, + ov_options[i].cm_opt); + if (option == NULL) + continue; + + g_key_file_set_string(keyfile, + vpn_provider_get_save_group(provider), + ov_options[i].cm_opt, option); + } + } + return 0; +} + +static int task_append_config_data(struct vpn_provider *provider, + struct connman_task *task) +{ + const char *option; + int i; + + for (i = 0; i < (int)ARRAY_SIZE(ov_options); i++) { + if (ov_options[i].ov_opt == NULL) + continue; + + option = vpn_provider_get_string(provider, + ov_options[i].cm_opt); + if (option == NULL) + continue; + + if (connman_task_add_argument(task, + ov_options[i].ov_opt, + ov_options[i].has_value ? option : NULL) < 0) { + return -EIO; + } + } + + return 0; +} + +static int ov_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name) +{ + const char *option; + int err, fd; + + option = vpn_provider_get_string(provider, "Host"); + if (option == NULL) { + connman_error("Host not set; cannot enable VPN"); + return -EINVAL; + } + + task_append_config_data(provider, task); + + option = vpn_provider_get_string(provider, "OpenVPN.TLSAuth"); + if (option != NULL) { + connman_task_add_argument(task, "--tls-auth", option); + option = vpn_provider_get_string(provider, + "OpenVPN.TLSAuthDir"); + if (option != NULL) + connman_task_add_argument(task, option, NULL); + } + + connman_task_add_argument(task, "--syslog", NULL); + + connman_task_add_argument(task, "--script-security", "2"); + + connman_task_add_argument(task, "--up", + SCRIPTDIR "/openvpn-script"); + connman_task_add_argument(task, "--up-restart", NULL); + + connman_task_add_argument(task, "--setenv", NULL); + connman_task_add_argument(task, "CONNMAN_BUSNAME", + dbus_bus_get_unique_name(connection)); + + connman_task_add_argument(task, "--setenv", NULL); + connman_task_add_argument(task, "CONNMAN_INTERFACE", + CONNMAN_TASK_INTERFACE); + + connman_task_add_argument(task, "--setenv", NULL); + connman_task_add_argument(task, "CONNMAN_PATH", + connman_task_get_path(task)); + + connman_task_add_argument(task, "--dev", if_name); + connman_task_add_argument(task, "--dev-type", "tun"); + + connman_task_add_argument(task, "--tls-client", NULL); + connman_task_add_argument(task, "--nobind", NULL); + connman_task_add_argument(task, "--persist-key", NULL); + connman_task_add_argument(task, "--persist-tun", NULL); + + connman_task_add_argument(task, "--route-noexec", NULL); + connman_task_add_argument(task, "--ifconfig-noexec", NULL); + + /* + * Disable client restarts because we can't handle this at the + * moment. The problem is that when OpenVPN decides to switch + * from CONNECTED state to RECONNECTING and then to RESOLVE, + * it is not possible to do a DNS lookup. The DNS server is + * not accessable through the tunnel anymore and so we end up + * trying to resolve the OpenVPN servers address. + */ + connman_task_add_argument(task, "--ping-restart", "0"); + + connman_task_add_argument(task, "--client", NULL); + + fd = fileno(stderr); + err = connman_task_run(task, vpn_died, provider, + NULL, &fd, &fd); + if (err < 0) { + connman_error("openvpn failed to start"); + return -EIO; + } + + return 0; +} + +static struct vpn_driver vpn_driver = { + .notify = ov_notify, + .connect = ov_connect, + .save = ov_save, +}; + +static int openvpn_init(void) +{ + connection = connman_dbus_get_connection(); + + return vpn_register("openvpn", &vpn_driver, OPENVPN); +} + +static void openvpn_exit(void) +{ + vpn_unregister("openvpn"); + + dbus_connection_unref(connection); +} + +CONNMAN_PLUGIN_DEFINE(openvpn, "OpenVPN plugin", VERSION, + CONNMAN_PLUGIN_PRIORITY_DEFAULT, openvpn_init, openvpn_exit) diff --git a/vpn/plugins/pptp.c b/vpn/plugins/pptp.c new file mode 100644 index 00000000..f737c316 --- /dev/null +++ b/vpn/plugins/pptp.c @@ -0,0 +1,339 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2010 BMW Car IT GmbH. All rights reserved. + * Copyright (C) 2012 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 <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <net/if.h> + +#include <dbus/dbus.h> +#include <glib.h> + +#define CONNMAN_API_SUBJECT_TO_CHANGE +#include <connman/plugin.h> +#include <connman/provider.h> +#include <connman/log.h> +#include <connman/task.h> +#include <connman/dbus.h> +#include <connman/inet.h> + +#include "vpn.h" + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +enum { + OPT_STRING = 1, + OPT_BOOL = 2, +}; + +struct { + const char *cm_opt; + const char *pptp_opt; + const char *vpnc_default; + int type; +} pptp_options[] = { + { "PPTP.User", "user", NULL, OPT_STRING }, + { "PPTP.EchoFailure", "lcp-echo-failure", "0", OPT_STRING }, + { "PPTP.EchoInterval", "lcp-echo-interval", "0", OPT_STRING }, + { "PPTP.Debug", "debug", NULL, OPT_STRING }, + { "PPTP.RefuseEAP", "refuse-eap", NULL, OPT_BOOL }, + { "PPTP.RefusePAP", "refuse-pap", NULL, OPT_BOOL }, + { "PPTP.RefuseCHAP", "refuse-chap", NULL, OPT_BOOL }, + { "PPTP.RefuseMSCHAP", "refuse-mschap", NULL, OPT_BOOL }, + { "PPTP.RefuseMSCHAP2", "refuse-mschapv2", NULL, OPT_BOOL }, + { "PPTP.NoBSDComp", "nobsdcomp", NULL, OPT_BOOL }, + { "PPTP.NoDeflate", "nodeflate", NULL, OPT_BOOL }, + { "PPTP.RequirMPPE", "require-mppe", NULL, OPT_BOOL }, + { "PPTP.RequirMPPE40", "require-mppe-40", NULL, OPT_BOOL }, + { "PPTP.RequirMPPE128", "require-mppe-128", NULL, OPT_BOOL }, + { "PPTP.RequirMPPEStateful", "mppe-stateful", NULL, OPT_BOOL }, + { "PPTP.NoVJ", "no-vj-comp", NULL, OPT_BOOL }, +}; + +static DBusConnection *connection; + +static DBusMessage *pptp_get_sec(struct connman_task *task, + DBusMessage *msg, void *user_data) +{ + const char *user, *passwd; + struct vpn_provider *provider = user_data; + DBusMessage *reply; + + if (dbus_message_get_no_reply(msg) == TRUE) + return NULL; + + user = vpn_provider_get_string(provider, "PPTP.User"); + passwd = vpn_provider_get_string(provider, "PPTP.Password"); + if (user == NULL || strlen(user) == 0 || + passwd == NULL || strlen(passwd) == 0) + return NULL; + + reply = dbus_message_new_method_return(msg); + if (reply == NULL) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &user, + DBUS_TYPE_STRING, &passwd, + DBUS_TYPE_INVALID); + return reply; +} + +static int pptp_notify(DBusMessage *msg, struct vpn_provider *provider) +{ + DBusMessageIter iter, dict; + const char *reason, *key, *value; + char *addressv4 = NULL, *netmask = NULL, *gateway = NULL; + char *ifname = NULL, *nameservers = NULL; + struct connman_ipaddress *ipaddress = NULL; + + dbus_message_iter_init(msg, &iter); + + dbus_message_iter_get_basic(&iter, &reason); + dbus_message_iter_next(&iter); + + if (provider == NULL) { + connman_error("No provider found"); + return VPN_STATE_FAILURE; + } + + if (strcmp(reason, "auth failed") == 0) + return VPN_STATE_AUTH_FAILURE; + + if (strcmp(reason, "connect")) + return VPN_STATE_DISCONNECT; + + dbus_message_iter_recurse(&iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + dbus_message_iter_get_basic(&entry, &value); + + DBG("%s = %s", key, value); + + if (!strcmp(key, "INTERNAL_IP4_ADDRESS")) { + vpn_provider_set_string(provider, "Address", value); + addressv4 = g_strdup(value); + } + + if (!strcmp(key, "INTERNAL_IP4_NETMASK")) { + vpn_provider_set_string(provider, "Netmask", value); + netmask = g_strdup(value); + } + + if (!strcmp(key, "INTERNAL_IP4_DNS")) { + vpn_provider_set_string(provider, "DNS", value); + nameservers = g_strdup(value); + } + + if (!strcmp(key, "INTERNAL_IFNAME")) + ifname = g_strdup(value); + + dbus_message_iter_next(&dict); + } + + if (vpn_set_ifname(provider, ifname) < 0) { + g_free(ifname); + g_free(addressv4); + g_free(netmask); + g_free(nameservers); + return VPN_STATE_FAILURE; + } + + if (addressv4 != NULL) + ipaddress = connman_ipaddress_alloc(AF_INET); + + g_free(ifname); + + if (ipaddress == NULL) { + connman_error("No IP address for provider"); + g_free(addressv4); + g_free(netmask); + g_free(nameservers); + return VPN_STATE_FAILURE; + } + + value = vpn_provider_get_string(provider, "HostIP"); + if (value != NULL) { + vpn_provider_set_string(provider, "Gateway", value); + gateway = g_strdup(value); + } + + if (addressv4 != NULL) + connman_ipaddress_set_ipv4(ipaddress, addressv4, netmask, + gateway); + + vpn_provider_set_ipaddress(provider, ipaddress); + vpn_provider_set_nameservers(provider, nameservers); + + g_free(addressv4); + g_free(netmask); + g_free(gateway); + g_free(nameservers); + connman_ipaddress_free(ipaddress); + + return VPN_STATE_CONNECT; +} + +static int pptp_save(struct vpn_provider *provider, GKeyFile *keyfile) +{ + const char *option; + int i; + + for (i = 0; i < (int)ARRAY_SIZE(pptp_options); i++) { + if (strncmp(pptp_options[i].cm_opt, "PPTP.", 5) == 0) { + option = vpn_provider_get_string(provider, + pptp_options[i].cm_opt); + if (option == NULL) + continue; + + g_key_file_set_string(keyfile, + vpn_provider_get_save_group(provider), + pptp_options[i].cm_opt, option); + } + } + return 0; +} + +static void pptp_write_bool_option(struct connman_task *task, + const char *key, const char *value) +{ + if (key != NULL && value != NULL) { + if (strcasecmp(value, "yes") == 0 || + strcasecmp(value, "true") == 0 || + strcmp(value, "1") == 0) + connman_task_add_argument(task, key, NULL); + } +} + +static int pptp_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name) +{ + const char *opt_s, *host; + char *str; + int err, i; + + if (connman_task_set_notify(task, "getsec", + pptp_get_sec, provider)) + return -ENOMEM; + + host = vpn_provider_get_string(provider, "Host"); + if (host == NULL) { + connman_error("Host not set; cannot enable VPN"); + return -EINVAL; + } + + str = g_strdup_printf("%s %s --nolaunchpppd --loglevel 2", + PPTP, host); + if (str == NULL) { + connman_error("can not allocate memory"); + return -ENOMEM; + } + + connman_task_add_argument(task, "pty", str); + g_free(str); + + connman_task_add_argument(task, "nodetach", NULL); + connman_task_add_argument(task, "lock", NULL); + connman_task_add_argument(task, "usepeerdns", NULL); + connman_task_add_argument(task, "noipdefault", NULL); + connman_task_add_argument(task, "noauth", NULL); + connman_task_add_argument(task, "nodefaultroute", NULL); + connman_task_add_argument(task, "ipparam", "pptp_plugin"); + + for (i = 0; i < (int)ARRAY_SIZE(pptp_options); i++) { + opt_s = vpn_provider_get_string(provider, + pptp_options[i].cm_opt); + if (opt_s == NULL) + opt_s = pptp_options[i].vpnc_default; + + if (opt_s == NULL) + continue; + + if (pptp_options[i].type == OPT_STRING) + connman_task_add_argument(task, + pptp_options[i].pptp_opt, opt_s); + else if (pptp_options[i].type == OPT_BOOL) + pptp_write_bool_option(task, + pptp_options[i].pptp_opt, opt_s); + } + + connman_task_add_argument(task, "plugin", + SCRIPTDIR "/libppp-plugin.so"); + + err = connman_task_run(task, vpn_died, provider, + NULL, NULL, NULL); + if (err < 0) { + connman_error("pptp failed to start"); + return -EIO; + } + + return 0; +} + +static int pptp_error_code(int exit_code) +{ + + switch (exit_code) { + case 1: + return CONNMAN_PROVIDER_ERROR_CONNECT_FAILED; + case 2: + return CONNMAN_PROVIDER_ERROR_LOGIN_FAILED; + case 16: + return CONNMAN_PROVIDER_ERROR_AUTH_FAILED; + default: + return CONNMAN_PROVIDER_ERROR_UNKNOWN; + } +} + +static struct vpn_driver vpn_driver = { + .flags = VPN_FLAG_NO_TUN, + .notify = pptp_notify, + .connect = pptp_connect, + .error_code = pptp_error_code, + .save = pptp_save, +}; + +static int pptp_init(void) +{ + connection = connman_dbus_get_connection(); + + return vpn_register("pptp", &vpn_driver, PPPD); +} + +static void pptp_exit(void) +{ + vpn_unregister("pptp"); + + dbus_connection_unref(connection); +} + +CONNMAN_PLUGIN_DEFINE(pptp, "pptp plugin", VERSION, + CONNMAN_PLUGIN_PRIORITY_DEFAULT, pptp_init, pptp_exit) diff --git a/vpn/plugins/vpn.c b/vpn/plugins/vpn.c new file mode 100644 index 00000000..2a0fdaf7 --- /dev/null +++ b/vpn/plugins/vpn.c @@ -0,0 +1,535 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2007-2012 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 + +#define _GNU_SOURCE +#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/dbus.h> + +#include <glib/gprintf.h> + +#include <connman/log.h> +#include <connman/rtnl.h> +#include <connman/task.h> +#include <connman/inet.h> + +#include "../vpn-rtnl.h" +#include "../vpn-provider.h" + +#include "vpn.h" + +struct vpn_data { + struct vpn_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 vpn_provider_driver provider_driver; +}; + +GHashTable *driver_hash = NULL; + +static int stop_vpn(struct vpn_provider *provider) +{ + struct vpn_data *data = vpn_provider_get_data(provider); + struct vpn_driver_data *vpn_driver_data; + const char *name; + struct ifreq ifr; + int fd, err; + + if (data == NULL) + return -EINVAL; + + name = vpn_provider_get_driver_name(provider); + if (name == NULL) + 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) + return 0; + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + sprintf(ifr.ifr_name, "%s", data->if_name); + + fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC); + if (fd < 0) { + err = -errno; + connman_error("Failed to open /dev/net/tun to device %s: %s", + data->if_name, strerror(errno)); + return err; + } + + if (ioctl(fd, TUNSETIFF, (void *)&ifr)) { + err = -errno; + connman_error("Failed to TUNSETIFF for device %s to it: %s", + data->if_name, strerror(errno)); + close(fd); + return err; + } + + if (ioctl(fd, TUNSETPERSIST, 0)) { + err = -errno; + connman_error("Failed to set tun device %s nonpersistent: %s", + data->if_name, strerror(errno)); + close(fd); + return err; + } + close(fd); + DBG("Killed tun device %s", data->if_name); + return 0; +} + +void vpn_died(struct connman_task *task, int exit_code, void *user_data) +{ + struct vpn_provider *provider = user_data; + struct vpn_data *data = vpn_provider_get_data(provider); + int state = VPN_STATE_FAILURE; + enum vpn_provider_error ret; + + DBG("provider %p data %p", provider, data); + + if (data == NULL) + goto vpn_exit; + + 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_exit: + if (state != VPN_STATE_READY && state != VPN_STATE_DISCONNECT) { + const char *name; + struct vpn_driver_data *vpn_data = NULL; + + name = vpn_provider_get_driver_name(provider); + if (name != NULL) + 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); + else + ret = VPN_PROVIDER_ERROR_UNKNOWN; + + vpn_provider_indicate_error(provider, ret); + } else + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE); + + vpn_provider_set_index(provider, -1); + + if (data != NULL) { + vpn_provider_unref(data->provider); + g_free(data->if_name); + g_free(data); + } + + connman_task_destroy(task); +} + +int vpn_set_ifname(struct vpn_provider *provider, const char *ifname) +{ + struct vpn_data *data = vpn_provider_get_data(provider); + int index; + + if (ifname == NULL || data == NULL) + return -EIO; + + index = connman_inet_ifindex(ifname); + if (index < 0) + return -EIO; + + if (data->if_name != NULL) + g_free(data->if_name); + + data->if_name = (char *)g_strdup(ifname); + vpn_provider_set_index(provider, index); + + return 0; +} + +static void vpn_newlink(unsigned flags, unsigned change, void *user_data) +{ + struct vpn_provider *provider = user_data; + struct vpn_data *data = vpn_provider_get_data(provider); + + if ((data->flags & IFF_UP) != (flags & IFF_UP)) { + if (flags & IFF_UP) { + data->state = VPN_STATE_READY; + vpn_provider_set_state(provider, + VPN_PROVIDER_STATE_READY); + } + } + data->flags = flags; +} + +static DBusMessage *vpn_notify(struct connman_task *task, + DBusMessage *msg, void *user_data) +{ + struct vpn_provider *provider = user_data; + struct vpn_data *data; + struct vpn_driver_data *vpn_driver_data; + const char *name; + int state, index; + + data = vpn_provider_get_data(provider); + + name = vpn_provider_get_driver_name(provider); + if (name == NULL) + return NULL; + + vpn_driver_data = g_hash_table_lookup(driver_hash, name); + if (vpn_driver_data == NULL) + return NULL; + + state = vpn_driver_data->vpn_driver->notify(msg, provider); + switch (state) { + case VPN_STATE_CONNECT: + case VPN_STATE_READY: + index = vpn_provider_get_index(provider); + vpn_provider_ref(provider); + data->watch = vpn_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: + 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 NULL; +} + +static int vpn_create_tun(struct vpn_provider *provider) +{ + struct vpn_data *data = vpn_provider_get_data(provider); + struct ifreq ifr; + int i, fd, index; + int ret = 0; + + if (data == NULL) + return -EISCONN; + + fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC); + 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 == NULL) { + connman_error("Failed to allocate memory"); + close(fd); + 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"); + stop_vpn(provider); + ret = -EIO; + goto exist_err; + } + vpn_provider_set_index(provider, index); + + return 0; + +exist_err: + return ret; +} + +static int vpn_connect(struct vpn_provider *provider) +{ + struct vpn_data *data = vpn_provider_get_data(provider); + struct vpn_driver_data *vpn_driver_data; + const char *name; + int ret = 0; + + if (data != NULL) + return -EISCONN; + + data = g_try_new0(struct vpn_data, 1); + if (data == NULL) + return -ENOMEM; + + data->provider = vpn_provider_ref(provider); + data->watch = 0; + data->flags = 0; + data->task = NULL; + data->state = VPN_STATE_IDLE; + + vpn_provider_set_data(provider, data); + + name = vpn_provider_get_driver_name(provider); + if (name == NULL) + return -EINVAL; + + vpn_driver_data = g_hash_table_lookup(driver_hash, name); + + if (vpn_driver_data == NULL || vpn_driver_data->vpn_driver == NULL) { + ret = -EINVAL; + goto exist_err; + } + + if (vpn_driver_data->vpn_driver->flags != VPN_FLAG_NO_TUN) { + ret = vpn_create_tun(provider); + if (ret < 0) + goto exist_err; + } + + data->task = connman_task_create(vpn_driver_data->program); + + if (data->task == NULL) { + ret = -ENOMEM; + stop_vpn(provider); + goto exist_err; + } + + if (connman_task_set_notify(data->task, "notify", + vpn_notify, provider)) { + ret = -ENOMEM; + stop_vpn(provider); + 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) { + stop_vpn(provider); + 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: + vpn_provider_set_index(provider, -1); + vpn_provider_set_data(provider, NULL); + vpn_provider_unref(data->provider); + g_free(data->if_name); + g_free(data); + + return ret; +} + +static int vpn_probe(struct vpn_provider *provider) +{ + return 0; +} + +static int vpn_disconnect(struct vpn_provider *provider) +{ + struct vpn_data *data = vpn_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 = vpn_provider_get_driver_name(provider); + if (name == NULL) + 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(); + + if (data->watch != 0) { + vpn_provider_unref(provider); + vpn_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 vpn_provider *provider) +{ + struct vpn_data *data; + + data = vpn_provider_get_data(provider); + if (data == NULL) + return 0; + + if (data->watch != 0) { + vpn_provider_unref(provider); + vpn_rtnl_remove_watch(data->watch); + data->watch = 0; + } + + connman_task_stop(data->task); + + g_usleep(G_USEC_PER_SEC); + stop_vpn(provider); + return 0; +} + +static int vpn_save(struct vpn_provider *provider, GKeyFile *keyfile) +{ + struct vpn_driver_data *vpn_driver_data; + const char *name; + + 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) + return vpn_driver_data->vpn_driver->save(provider, keyfile); + + 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; + data->provider_driver.save = vpn_save; + + if (driver_hash == NULL) + driver_hash = g_hash_table_new_full(g_str_hash, + g_str_equal, + NULL, g_free); + + if (driver_hash == NULL) { + connman_error("driver_hash not initialized for %s", name); + g_free(data); + return -ENOMEM; + } + + g_hash_table_replace(driver_hash, (char *)name, data); + + vpn_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; + + vpn_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/vpn/plugins/vpn.h b/vpn/plugins/vpn.h new file mode 100644 index 00000000..6693cdba --- /dev/null +++ b/vpn/plugins/vpn.h @@ -0,0 +1,63 @@ +/* + * + * ConnMan VPN daemon + * + * 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 + * + */ + +#ifndef __CONNMAN_VPND_VPN_H +#define __CONNMAN_VPND_VPN_H + +#include "../vpn-provider.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VPN_FLAG_NO_TUN 1 + +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, + VPN_STATE_AUTH_FAILURE = 6, +}; + +struct vpn_driver { + int flags; + int (*notify) (DBusMessage *msg, struct vpn_provider *provider); + int (*connect) (struct vpn_provider *provider, + struct connman_task *task, const char *if_name); + void (*disconnect) (void); + int (*error_code) (int exit_code); + int (*save) (struct vpn_provider *provider, GKeyFile *keyfile); +}; + +int vpn_register(const char *name, struct vpn_driver *driver, + const char *program); +void vpn_unregister(const char *provider_name); +void vpn_died(struct connman_task *task, int exit_code, void *user_data); +int vpn_set_ifname(struct vpn_provider *provider, const char *ifname); + +#ifdef __cplusplus +} +#endif + +#endif /* __CONNMAN_VPND_VPN_H */ diff --git a/vpn/plugins/vpnc.c b/vpn/plugins/vpnc.c new file mode 100644 index 00000000..9fd1dec8 --- /dev/null +++ b/vpn/plugins/vpnc.c @@ -0,0 +1,345 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2010 BMW Car IT GmbH. All rights reserved. + * Copyright (C) 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 <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <net/if.h> + +#include <glib.h> + +#define CONNMAN_API_SUBJECT_TO_CHANGE +#include <connman/plugin.h> +#include <connman/log.h> +#include <connman/task.h> +#include <connman/ipconfig.h> +#include <connman/dbus.h> + +#include "../vpn-provider.h" + +#include "vpn.h" + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +static DBusConnection *connection; + +enum { + OPT_STRING = 1, + OPT_BOOLEAN = 2, +}; + +struct { + const char *cm_opt; + const char *vpnc_opt; + const char *vpnc_default; + int type; + connman_bool_t cm_save; +} vpnc_options[] = { + { "Host", "IPSec gateway", NULL, OPT_STRING, TRUE }, + { "VPNC.IPSec.ID", "IPSec ID", NULL, OPT_STRING, TRUE }, + { "VPNC.IPSec.Secret", "IPSec secret", NULL, OPT_STRING, FALSE }, + { "VPNC.Xauth.Username", "Xauth username", NULL, OPT_STRING, FALSE }, + { "VPNC.Xauth.Password", "Xauth password", NULL, OPT_STRING, FALSE }, + { "VPNC.IKE.Authmode", "IKE Authmode", NULL, OPT_STRING, TRUE }, + { "VPNC.IKE.DHGroup", "IKE DH Group", NULL, OPT_STRING, TRUE }, + { "VPNC.PFS", "Perfect Forward Secrecy", NULL, OPT_STRING, TRUE }, + { "VPNC.Domain", "Domain", NULL, OPT_STRING, TRUE }, + { "VPNC.Vendor", "Vendor", NULL, OPT_STRING, TRUE }, + { "VPNC.LocalPort", "Local Port", "0", OPT_STRING, TRUE, }, + { "VPNC.CiscoPort", "Cisco UDP Encapsulation Port", "0", OPT_STRING, + TRUE }, + { "VPNC.AppVersion", "Application Version", NULL, OPT_STRING, TRUE }, + { "VPNC.NATTMode", "NAT Traversal Mode", "cisco-udp", OPT_STRING, + TRUE }, + { "VPNC.DPDTimeout", "DPD idle timeout (our side)", NULL, OPT_STRING, + TRUE }, + { "VPNC.SingleDES", "Enable Single DES", NULL, OPT_BOOLEAN, TRUE }, + { "VPNC.NoEncryption", "Enable no encryption", NULL, OPT_BOOLEAN, + TRUE }, +}; + +static int vc_notify(DBusMessage *msg, struct vpn_provider *provider) +{ + DBusMessageIter iter, dict; + char *address = NULL, *netmask = NULL, *gateway = NULL; + struct connman_ipaddress *ipaddress; + const char *reason, *key, *value; + + dbus_message_iter_init(msg, &iter); + + dbus_message_iter_get_basic(&iter, &reason); + dbus_message_iter_next(&iter); + + if (!provider) { + connman_error("No provider found"); + return VPN_STATE_FAILURE; + } + + if (strcmp(reason, "connect")) + return VPN_STATE_DISCONNECT; + + dbus_message_iter_recurse(&iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + dbus_message_iter_get_basic(&entry, &value); + + DBG("%s = %s", key, value); + + if (!strcmp(key, "VPNGATEWAY")) + gateway = g_strdup(value); + + if (!strcmp(key, "INTERNAL_IP4_ADDRESS")) + address = g_strdup(value); + + if (!strcmp(key, "INTERNAL_IP4_NETMASK")) + netmask = g_strdup(value); + + if (!strcmp(key, "INTERNAL_IP4_DNS")) + vpn_provider_set_nameservers(provider, value); + + if (!strcmp(key, "CISCO_DEF_DOMAIN")) + vpn_provider_set_domain(provider, value); + + if (g_str_has_prefix(key, "CISCO_SPLIT_INC") == TRUE || + g_str_has_prefix(key, "CISCO_IPV6_SPLIT_INC") == TRUE) + vpn_provider_append_route(provider, key, value); + + dbus_message_iter_next(&dict); + } + + + ipaddress = connman_ipaddress_alloc(AF_INET); + if (ipaddress == NULL) { + g_free(address); + g_free(netmask); + g_free(gateway); + + return VPN_STATE_FAILURE; + } + + connman_ipaddress_set_ipv4(ipaddress, address, netmask, gateway); + vpn_provider_set_ipaddress(provider, ipaddress); + + g_free(address); + g_free(netmask); + g_free(gateway); + connman_ipaddress_free(ipaddress); + + return VPN_STATE_CONNECT; +} + +static ssize_t full_write(int fd, const void *buf, size_t len) +{ + ssize_t byte_write; + + while (len) { + byte_write = write(fd, buf, len); + if (byte_write < 0) { + connman_error("failed to write config to vpnc: %s\n", + strerror(errno)); + return byte_write; + } + len -= byte_write; + buf += byte_write; + } + + return 0; +} + +static ssize_t write_option(int fd, const char *key, const char *value) +{ + gchar *buf; + ssize_t ret = 0; + + if (key != NULL && value != NULL) { + buf = g_strdup_printf("%s %s\n", key, value); + ret = full_write(fd, buf, strlen(buf)); + + g_free(buf); + } + + return ret; +} + +static ssize_t write_bool_option(int fd, const char *key, const char *value) +{ + gchar *buf; + ssize_t ret = 0; + + if (key != NULL && value != NULL) { + if (strcasecmp(value, "yes") == 0 || + strcasecmp(value, "true") == 0 || + strcmp(value, "1") == 0) { + buf = g_strdup_printf("%s\n", key); + ret = full_write(fd, buf, strlen(buf)); + + g_free(buf); + } + } + + return ret; +} + +static int vc_write_config_data(struct vpn_provider *provider, int fd) +{ + const char *opt_s; + int i; + + for (i = 0; i < (int)ARRAY_SIZE(vpnc_options); i++) { + opt_s = vpn_provider_get_string(provider, + vpnc_options[i].cm_opt); + if (opt_s == FALSE) + opt_s = vpnc_options[i].vpnc_default; + + if (opt_s == FALSE) + continue; + + if (vpnc_options[i].type == OPT_STRING) { + if (write_option(fd, + vpnc_options[i].vpnc_opt, opt_s) < 0) + return -EIO; + } else if (vpnc_options[i].type == OPT_BOOLEAN) { + if (write_bool_option(fd, + vpnc_options[i].vpnc_opt, opt_s) < 0) + return -EIO; + } + + } + + return 0; +} + +static int vc_save(struct vpn_provider *provider, GKeyFile *keyfile) +{ + const char *option; + int i; + + for (i = 0; i < (int)ARRAY_SIZE(vpnc_options); i++) { + if (strncmp(vpnc_options[i].cm_opt, "VPNC.", 5) == 0) { + + if (vpnc_options[i].cm_save == FALSE) + continue; + + option = vpn_provider_get_string(provider, + vpnc_options[i].cm_opt); + if (option == NULL) + continue; + + g_key_file_set_string(keyfile, + vpn_provider_get_save_group(provider), + vpnc_options[i].cm_opt, option); + } + } + return 0; +} + +static int vc_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name) +{ + const char *option; + int err, fd; + + option = vpn_provider_get_string(provider, "Host"); + if (option == NULL) { + connman_error("Host not set; cannot enable VPN"); + return -EINVAL; + } + option = vpn_provider_get_string(provider, "VPNC.IPSec.ID"); + if (option == NULL) { + connman_error("Group not set; cannot enable VPN"); + return -EINVAL; + } + + connman_task_add_argument(task, "--non-inter", NULL); + connman_task_add_argument(task, "--no-detach", NULL); + + connman_task_add_argument(task, "--ifname", if_name); + connman_task_add_argument(task, "--ifmode", "tun"); + + connman_task_add_argument(task, "--script", + SCRIPTDIR "/openconnect-script"); + + option = vpn_provider_get_string(provider, "VPNC.Debug"); + if (option != NULL) + connman_task_add_argument(task, "--debug", option); + + connman_task_add_argument(task, "-", NULL); + + err = connman_task_run(task, vpn_died, provider, + &fd, NULL, NULL); + if (err < 0) { + connman_error("vpnc failed to start"); + return -EIO; + } + + err = vc_write_config_data(provider, fd); + + close(fd); + + return err; +} + +static int vc_error_code(int exit_code) +{ + switch (exit_code) { + case 1: + return VPN_PROVIDER_ERROR_CONNECT_FAILED; + case 2: + return VPN_PROVIDER_ERROR_LOGIN_FAILED; + default: + return VPN_PROVIDER_ERROR_UNKNOWN; + } +} + +static struct vpn_driver vpn_driver = { + .notify = vc_notify, + .connect = vc_connect, + .error_code = vc_error_code, + .save = vc_save, +}; + +static int vpnc_init(void) +{ + connection = connman_dbus_get_connection(); + + return vpn_register("vpnc", &vpn_driver, VPNC); +} + +static void vpnc_exit(void) +{ + vpn_unregister("vpnc"); + + dbus_connection_unref(connection); +} + +CONNMAN_PLUGIN_DEFINE(vpnc, "vpnc plugin", VERSION, + CONNMAN_PLUGIN_PRIORITY_DEFAULT, vpnc_init, vpnc_exit) diff --git a/vpn/vpn-dbus.conf b/vpn/vpn-dbus.conf new file mode 100644 index 00000000..0f0c8da4 --- /dev/null +++ b/vpn/vpn-dbus.conf @@ -0,0 +1,15 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <policy user="root"> + <allow own="net.connman.vpn"/> + <allow send_destination="net.connman.vpn"/> + <allow send_interface="net.connman.vpn.Agent"/> + </policy> + <policy at_console="true"> + <allow send_destination="net.connman.vpn"/> + </policy> + <policy context="default"> + <deny send_destination="net.connman.vpn"/> + </policy> +</busconfig> diff --git a/vpn/vpn-ipconfig.c b/vpn/vpn-ipconfig.c new file mode 100644 index 00000000..cb5167f0 --- /dev/null +++ b/vpn/vpn-ipconfig.c @@ -0,0 +1,450 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2012 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 <errno.h> +#include <stdio.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <linux/if_link.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <arpa/inet.h> + +#ifndef IFF_LOWER_UP +#define IFF_LOWER_UP 0x10000 +#endif + +#include <gdbus.h> + +#include "../src/connman.h" + +#include "vpn.h" + +struct vpn_ipconfig { + int refcount; + int index; + int family; + connman_bool_t enabled; + struct connman_ipaddress *address; + struct connman_ipaddress *system; +}; + +struct vpn_ipdevice { + int index; + char *ifname; + unsigned short type; + unsigned int flags; + char *address; + uint16_t mtu; + + GSList *address_list; + char *ipv4_gateway; + char *ipv6_gateway; + + char *pac; +}; + +static GHashTable *ipdevice_hash = NULL; + +unsigned char __vpn_ipconfig_netmask_prefix_len(const char *netmask) +{ + unsigned char bits; + in_addr_t mask; + in_addr_t host; + + if (netmask == NULL) + return 32; + + mask = inet_network(netmask); + host = ~mask; + + /* a valid netmask must be 2^n - 1 */ + if ((host & (host + 1)) != 0) + return -1; + + bits = 0; + for (; mask; mask <<= 1) + ++bits; + + return bits; +} + +const char *__vpn_ipconfig_get_peer(struct vpn_ipconfig *ipconfig) +{ + if (ipconfig->address == NULL) + return NULL; + + return ipconfig->address->peer; +} + +unsigned short __vpn_ipconfig_get_type_from_index(int index) +{ + struct vpn_ipdevice *ipdevice; + + ipdevice = g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index)); + if (ipdevice == NULL) + return ARPHRD_VOID; + + return ipdevice->type; +} + +unsigned int __vpn_ipconfig_get_flags_from_index(int index) +{ + struct vpn_ipdevice *ipdevice; + + ipdevice = g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index)); + if (ipdevice == NULL) + return 0; + + return ipdevice->flags; +} + +void __vpn_ipconfig_foreach(void (*function) (int index, + void *user_data), void *user_data) +{ + GList *list, *keys; + + keys = g_hash_table_get_keys(ipdevice_hash); + if (keys == NULL) + return; + + for (list = g_list_first(keys); list; list = g_list_next(list)) { + int index = GPOINTER_TO_INT(list->data); + + function(index, user_data); + } + + g_list_free(keys); +} + +void __vpn_ipconfig_set_local(struct vpn_ipconfig *ipconfig, + const char *address) +{ + if (ipconfig->address == NULL) + return; + + g_free(ipconfig->address->local); + ipconfig->address->local = g_strdup(address); +} + +const char *__vpn_ipconfig_get_local(struct vpn_ipconfig *ipconfig) +{ + if (ipconfig->address == NULL) + return NULL; + + return ipconfig->address->local; +} + +void __vpn_ipconfig_set_peer(struct vpn_ipconfig *ipconfig, + const char *address) +{ + if (ipconfig->address == NULL) + return; + + g_free(ipconfig->address->peer); + ipconfig->address->peer = g_strdup(address); +} + +void __vpn_ipconfig_set_broadcast(struct vpn_ipconfig *ipconfig, + const char *broadcast) +{ + if (ipconfig->address == NULL) + return; + + g_free(ipconfig->address->broadcast); + ipconfig->address->broadcast = g_strdup(broadcast); +} + +void __vpn_ipconfig_set_gateway(struct vpn_ipconfig *ipconfig, + const char *gateway) +{ + DBG(""); + + if (ipconfig->address == NULL) + return; + g_free(ipconfig->address->gateway); + ipconfig->address->gateway = g_strdup(gateway); +} + +const char * +__vpn_ipconfig_get_gateway(struct vpn_ipconfig *ipconfig) +{ + if (ipconfig->address == NULL) + return NULL; + + return ipconfig->address->gateway; +} + +void __vpn_ipconfig_set_prefixlen(struct vpn_ipconfig *ipconfig, + unsigned char prefixlen) +{ + if (ipconfig->address == NULL) + return; + + ipconfig->address->prefixlen = prefixlen; +} + +unsigned char +__vpn_ipconfig_get_prefixlen(struct vpn_ipconfig *ipconfig) +{ + if (ipconfig->address == NULL) + return 0; + + return ipconfig->address->prefixlen; +} + +int __vpn_ipconfig_address_add(struct vpn_ipconfig *ipconfig, int family) +{ + DBG("ipconfig %p family %d", ipconfig, family); + + if (ipconfig == NULL) + return -EINVAL; + + if (family == AF_INET) + return connman_inet_set_address(ipconfig->index, + ipconfig->address); + else if (family == AF_INET6) + return connman_inet_set_ipv6_address(ipconfig->index, + ipconfig->address); + + return 0; +} + +int __vpn_ipconfig_gateway_add(struct vpn_ipconfig *ipconfig, int family) +{ + DBG("ipconfig %p family %d", ipconfig, family); + + if (ipconfig == NULL || ipconfig->address == NULL) + return -EINVAL; + + DBG("family %d gw %s peer %s", family, + ipconfig->address->gateway, ipconfig->address->peer); + + if (family == AF_INET) + connman_inet_add_host_route(ipconfig->index, + ipconfig->address->gateway, + ipconfig->address->peer); + else if (family == AF_INET6) + connman_inet_add_ipv6_host_route(ipconfig->index, + ipconfig->address->gateway, + ipconfig->address->peer); + else + return -EINVAL; + + return 0; +} + +static struct vpn_ipconfig *create_ipv6config(int index) +{ + struct vpn_ipconfig *ipv6config; + + DBG("index %d", index); + + ipv6config = g_try_new0(struct vpn_ipconfig, 1); + if (ipv6config == NULL) + return NULL; + + ipv6config->refcount = 1; + + ipv6config->index = index; + ipv6config->enabled = FALSE; + ipv6config->family = AF_INET6; + + ipv6config->address = connman_ipaddress_alloc(AF_INET6); + if (ipv6config->address == NULL) { + g_free(ipv6config); + return NULL; + } + + ipv6config->system = connman_ipaddress_alloc(AF_INET6); + + DBG("ipconfig %p", ipv6config); + + return ipv6config; +} + +struct vpn_ipconfig *__vpn_ipconfig_create(int index, int family) +{ + struct vpn_ipconfig *ipconfig; + + if (family == AF_INET6) + return create_ipv6config(index); + + DBG("index %d", index); + + ipconfig = g_try_new0(struct vpn_ipconfig, 1); + if (ipconfig == NULL) + return NULL; + + ipconfig->refcount = 1; + + ipconfig->index = index; + ipconfig->enabled = FALSE; + ipconfig->family = family; + + ipconfig->address = connman_ipaddress_alloc(AF_INET); + if (ipconfig->address == NULL) { + g_free(ipconfig); + return NULL; + } + + ipconfig->system = connman_ipaddress_alloc(AF_INET); + + DBG("ipconfig %p", ipconfig); + + return ipconfig; +} + +void __vpn_ipconfig_set_index(struct vpn_ipconfig *ipconfig, int index) +{ + ipconfig->index = index; +} + +static const char *type2str(unsigned short type) +{ + switch (type) { + case ARPHRD_ETHER: + return "ETHER"; + case ARPHRD_LOOPBACK: + return "LOOPBACK"; + case ARPHRD_PPP: + return "PPP"; + case ARPHRD_NONE: + return "NONE"; + case ARPHRD_VOID: + return "VOID"; + } + + return ""; +} + +void __vpn_ipconfig_newlink(int index, unsigned short type, + unsigned int flags, + const char *address, + unsigned short mtu, + struct rtnl_link_stats *stats) +{ + struct vpn_ipdevice *ipdevice; + GString *str; + + if (type == ARPHRD_LOOPBACK) + return; + + ipdevice = g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index)); + if (ipdevice != NULL) + goto update; + + ipdevice = g_try_new0(struct vpn_ipdevice, 1); + if (ipdevice == NULL) + return; + + ipdevice->index = index; + ipdevice->ifname = connman_inet_ifname(index); + ipdevice->type = type; + + ipdevice->address = g_strdup(address); + + g_hash_table_insert(ipdevice_hash, GINT_TO_POINTER(index), ipdevice); + + connman_info("%s {create} index %d type %d <%s>", ipdevice->ifname, + index, type, type2str(type)); + +update: + ipdevice->mtu = mtu; + + if (flags == ipdevice->flags) + return; + + ipdevice->flags = flags; + + str = g_string_new(NULL); + if (str == NULL) + return; + + if (flags & IFF_UP) + g_string_append(str, "UP"); + else + g_string_append(str, "DOWN"); + + if (flags & IFF_RUNNING) + g_string_append(str, ",RUNNING"); + + if (flags & IFF_LOWER_UP) + g_string_append(str, ",LOWER_UP"); + + connman_info("%s {update} flags %u <%s>", ipdevice->ifname, + flags, str->str); + + g_string_free(str, TRUE); +} + +void __vpn_ipconfig_dellink(int index, struct rtnl_link_stats *stats) +{ + struct vpn_ipdevice *ipdevice; + + DBG("index %d", index); + + ipdevice = g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index)); + if (ipdevice == NULL) + return; + + g_hash_table_remove(ipdevice_hash, GINT_TO_POINTER(index)); +} + +static void free_ipdevice(gpointer data) +{ + struct vpn_ipdevice *ipdevice = data; + + connman_info("%s {remove} index %d", ipdevice->ifname, + ipdevice->index); + + g_free(ipdevice->ipv4_gateway); + g_free(ipdevice->ipv6_gateway); + g_free(ipdevice->pac); + + g_free(ipdevice->address); + + g_free(ipdevice->ifname); + g_free(ipdevice); +} + +int __vpn_ipconfig_init(void) +{ + DBG(""); + + ipdevice_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, free_ipdevice); + + return 0; +} + +void __vpn_ipconfig_cleanup(void) +{ + DBG(""); + + g_hash_table_destroy(ipdevice_hash); + ipdevice_hash = NULL; +} diff --git a/vpn/vpn-manager.c b/vpn/vpn-manager.c new file mode 100644 index 00000000..680f2fd9 --- /dev/null +++ b/vpn/vpn-manager.c @@ -0,0 +1,142 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2012 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 <errno.h> + +#include <gdbus.h> +#include <connman/log.h> + +#include "../src/connman.h" + +#include "vpn.h" +#include "connman/vpn-dbus.h" + +static int vpn_connect_count; +static DBusConnection *connection; + +static DBusMessage *create(DBusConnection *conn, DBusMessage *msg, void *data) +{ + int err; + + DBG("conn %p", conn); + + err = __vpn_provider_create_and_connect(msg); + if (err < 0) { + if (err == -EINPROGRESS) { + connman_error("Invalid return code (%d) " + "from connect", err); + err = -EINVAL; + } + + return __connman_error_failed(msg, -err); + } + + return NULL; +} + +static DBusMessage *remove(DBusConnection *conn, DBusMessage *msg, void *data) +{ + const char *path; + int err; + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + DBG("conn %p path %s", conn, path); + + err = __vpn_provider_remove(path); + if (err < 0) + return __connman_error_failed(msg, -err); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *get_connections(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBusMessage *reply; + + DBG("conn %p", conn); + + reply = __vpn_provider_get_connections(msg); + if (reply == NULL) + return __connman_error_failed(msg, -EINVAL); + + return reply; +} + +static const GDBusMethodTable manager_methods[] = { + { GDBUS_ASYNC_METHOD("Create", + GDBUS_ARGS({ "properties", "a{sv}" }), + GDBUS_ARGS({ "path", "o" }), + create) }, + { GDBUS_ASYNC_METHOD("Remove", + GDBUS_ARGS({ "identifier", "o" }), NULL, + remove) }, + { GDBUS_METHOD("GetConnections", NULL, + GDBUS_ARGS({ "connections", "a(oa{sv})" }), + get_connections) }, + { }, +}; + +static const GDBusSignalTable manager_signals[] = { + { GDBUS_SIGNAL("ConnectionAdded", + GDBUS_ARGS({ "identifier", "o" }, + { "properties", "a{sv}" })) }, + { GDBUS_SIGNAL("ConnectionRemoved", + GDBUS_ARGS({ "identifier", "o" })) }, + { }, +}; + +int __vpn_manager_init(void) +{ + DBG(""); + + connection = connman_dbus_get_connection(); + if (connection == NULL) + return -1; + + g_dbus_register_interface(connection, VPN_MANAGER_PATH, + VPN_MANAGER_INTERFACE, + manager_methods, + manager_signals, NULL, NULL, NULL); + + vpn_connect_count = 0; + + return 0; +} + +void __vpn_manager_cleanup(void) +{ + DBG(""); + + if (connection == NULL) + return; + + g_dbus_unregister_interface(connection, VPN_MANAGER_PATH, + VPN_MANAGER_INTERFACE); + + dbus_connection_unref(connection); +} diff --git a/vpn/vpn-polkit.conf b/vpn/vpn-polkit.conf new file mode 100644 index 00000000..a1dc6177 --- /dev/null +++ b/vpn/vpn-polkit.conf @@ -0,0 +1,11 @@ +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <policy user="root"> + <allow own="net.connman.vpn"/> + <allow send_interface="net.connman.vpn.Agent"/> + </policy> + <policy context="default"> + <allow send_destination="net.connman.vpn"/> + </policy> +</busconfig> diff --git a/vpn/vpn-polkit.policy b/vpn/vpn-polkit.policy new file mode 100644 index 00000000..0c427220 --- /dev/null +++ b/vpn/vpn-polkit.policy @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> + +<policyconfig> + + <vendor>Connection Manager VPN daemon</vendor> + <icon_name>network-wireless</icon_name> + + <action id="net.connman.vpn.modify"> + <description>Settings configuration</description> + <message>Policy prevents modification of settings</message> + <defaults> + <allow_inactive>no</allow_inactive> + <allow_active>auth_self_keep_session</allow_active> + </defaults> + </action> + + <action id="net.connman.vpn.secret"> + <description>Secrets configuration</description> + <message>Policy prevents modification of secrets</message> + <defaults> + <allow_inactive>no</allow_inactive> + <allow_active>auth_admin_keep_session</allow_active> + </defaults> + </action> + +</policyconfig> diff --git a/vpn/vpn-provider.c b/vpn/vpn-provider.c new file mode 100644 index 00000000..462dc26c --- /dev/null +++ b/vpn/vpn-provider.c @@ -0,0 +1,1680 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2012 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 <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <gdbus.h> +#include <connman/log.h> +#include <gweb/gresolv.h> + +#include "../src/connman.h" +#include "connman/vpn-dbus.h" +#include "vpn-provider.h" +#include "vpn.h" + +static DBusConnection *connection; +static GHashTable *provider_hash; +static GSList *driver_list; +static int configuration_count; + +struct vpn_route { + int family; + char *host; + char *netmask; + char *gateway; +}; + +struct vpn_provider { + int refcount; + int index; + int fd; + enum vpn_provider_state state; + char *path; + char *identifier; + char *name; + char *type; + char *host; + char *domain; + int family; + GHashTable *routes; + struct vpn_provider_driver *driver; + void *driver_data; + GHashTable *setting_strings; + GHashTable *user_routes; + gchar **user_networks; + gsize num_user_networks; + GResolv *resolv; + char **host_ip; + DBusMessage *pending_msg; + struct vpn_ipconfig *ipconfig_ipv4; + struct vpn_ipconfig *ipconfig_ipv6; + char **nameservers; +}; + +static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBG("conn %p", conn); + + // XXX: + + return NULL; +} + +static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBG("conn %p", conn); + + // XXX: + + return NULL; +} + +static DBusMessage *do_connect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct vpn_provider *provider = data; + int err; + + DBG("conn %p provider %p", conn, provider); + + err = __vpn_provider_connect(provider); + if (err < 0) + return __connman_error_failed(msg, -err); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *do_disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct vpn_provider *provider = data; + int err; + + DBG("conn %p provider %p", conn, provider); + + err = __vpn_provider_disconnect(provider); + if (err < 0) + return __connman_error_failed(msg, -err); + else + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static const GDBusMethodTable connection_methods[] = { + { GDBUS_METHOD("SetProperty", + GDBUS_ARGS({ "name", "s" }, { "value", "v" }), + NULL, set_property) }, + { GDBUS_METHOD("ClearProperty", + GDBUS_ARGS({ "name", "s" }), NULL, + clear_property) }, + { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, do_connect) }, + { GDBUS_METHOD("Disconnect", NULL, NULL, do_disconnect) }, + { }, +}; + +static const GDBusSignalTable connection_signals[] = { + { GDBUS_SIGNAL("PropertyChanged", + GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, + { }, +}; + +static void resolv_result(GResolvResultStatus status, + char **results, gpointer user_data) +{ + struct vpn_provider *provider = user_data; + + DBG("status %d", status); + + if (status == G_RESOLV_RESULT_STATUS_SUCCESS && results != NULL && + g_strv_length(results) > 0) + provider->host_ip = g_strdupv(results); + + vpn_provider_unref(provider); +} + +static void provider_resolv_host_addr(struct vpn_provider *provider) +{ + if (provider->host == NULL) + return; + + if (connman_inet_check_ipaddress(provider->host) > 0) + return; + + if (provider->host_ip != NULL) + return; + + /* + * If the hostname is not numeric, try to resolv it. We do not wait + * the result as it might take some time. We will get the result + * before VPN will feed routes to us because VPN client will need + * the IP address also before VPN connection can be established. + */ + provider->resolv = g_resolv_new(0); + if (provider->resolv == NULL) { + DBG("Cannot resolv %s", provider->host); + return; + } + + DBG("Trying to resolv %s", provider->host); + + vpn_provider_ref(provider); + + g_resolv_lookup_hostname(provider->resolv, provider->host, + resolv_result, provider); +} + +void __vpn_provider_append_properties(struct vpn_provider *provider, + DBusMessageIter *iter) +{ + if (provider->host != NULL) + connman_dbus_dict_append_basic(iter, "Host", + DBUS_TYPE_STRING, &provider->host); + + if (provider->domain != NULL) + connman_dbus_dict_append_basic(iter, "Domain", + DBUS_TYPE_STRING, &provider->domain); + + if (provider->type != NULL) + connman_dbus_dict_append_basic(iter, "Type", DBUS_TYPE_STRING, + &provider->type); +} + +int __vpn_provider_append_user_route(struct vpn_provider *provider, + int family, const char *network, const char *netmask) +{ + struct vpn_route *route; + char *key = g_strdup_printf("%d/%s/%s", family, network, netmask); + + DBG("family %d network %s netmask %s", family, network, netmask); + + route = g_hash_table_lookup(provider->user_routes, key); + if (route == NULL) { + route = g_try_new0(struct vpn_route, 1); + if (route == NULL) { + connman_error("out of memory"); + return -ENOMEM; + } + + route->family = family; + route->host = g_strdup(network); + route->netmask = g_strdup(netmask); + + g_hash_table_replace(provider->user_routes, key, route); + } else + g_free(key); + + return 0; +} + +static void set_user_networks(struct vpn_provider *provider, + char **networks) +{ + int i = 0; + + while (networks[i] != NULL) { + char **elems = g_strsplit(networks[i], "/", 0); + char *network, *netmask; + int family = PF_UNSPEC, ret; + + if (elems == NULL) + break; + + network = elems[0]; + if (network == NULL || *network == '\0') { + DBG("no network/netmask set"); + g_strfreev(elems); + break; + } + + netmask = elems[1]; + if (netmask != NULL && *netmask == '\0') { + DBG("no netmask set"); + g_strfreev(elems); + break; + } + + if (g_strrstr(network, ":") != NULL) + family = AF_INET6; + else if (g_strrstr(network, ".") != NULL) { + family = AF_INET; + + 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) + prefix_len = atoi(netmask); + + addr = 0xffffffff << (32 - prefix_len); + netmask_in.s_addr = htonl(addr); + netmask = inet_ntoa(netmask_in); + + DBG("network %s netmask %s", network, netmask); + } + } + + ret = __vpn_provider_append_user_route(provider, + family, network, netmask); + g_strfreev(elems); + + if (ret != 0) + break; + + i++; + } +} + +static int provider_load_from_keyfile(struct vpn_provider *provider, + GKeyFile *keyfile) +{ + gsize idx = 0; + gchar **settings; + gchar *key, *value; + gsize length; + + settings = g_key_file_get_keys(keyfile, provider->identifier, &length, + NULL); + if (settings == NULL) { + g_key_file_free(keyfile); + return -ENOENT; + } + + while (idx < length) { + 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, + provider->identifier, + key, + &provider->num_user_networks, + NULL); + } else { + value = g_key_file_get_string(keyfile, + provider->identifier, + key, NULL); + vpn_provider_set_string(provider, key, + value); + g_free(value); + } + } + idx += 1; + } + g_strfreev(settings); + + if (provider->user_networks != NULL) + set_user_networks(provider, provider->user_networks); + + return 0; +} + + +static int vpn_provider_load(struct vpn_provider *provider) +{ + GKeyFile *keyfile; + + DBG("provider %p", provider); + + keyfile = __connman_storage_load_provider(provider->identifier); + if (keyfile == NULL) + return -ENOENT; + + provider_load_from_keyfile(provider, keyfile); + + g_key_file_free(keyfile); + return 0; +} + +static int vpn_provider_save(struct vpn_provider *provider) +{ + GKeyFile *keyfile; + + DBG("provider %p", provider); + + keyfile = g_key_file_new(); + if (keyfile == NULL) + return -ENOMEM; + + g_key_file_set_string(keyfile, provider->identifier, + "Name", provider->name); + g_key_file_set_string(keyfile, provider->identifier, + "Type", provider->type); + g_key_file_set_string(keyfile, provider->identifier, + "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->driver != NULL && provider->driver->save != NULL) + provider->driver->save(provider, keyfile); + + __connman_storage_save_provider(keyfile, provider->identifier); + g_key_file_free(keyfile); + + return 0; +} + +static struct vpn_provider *vpn_provider_lookup(const char *identifier) +{ + struct vpn_provider *provider = NULL; + + provider = g_hash_table_lookup(provider_hash, identifier); + + return provider; +} + +static gboolean match_driver(struct vpn_provider *provider, + struct vpn_provider_driver *driver) +{ + if (g_strcmp0(driver->name, provider->type) == 0) + return TRUE; + + return FALSE; +} + +static int provider_probe(struct vpn_provider *provider) +{ + GSList *list; + + DBG("provider %p name %s", provider, provider->name); + + if (provider->driver != NULL) + return -EALREADY; + + for (list = driver_list; list; list = list->next) { + struct vpn_provider_driver *driver = list->data; + + if (match_driver(provider, driver) == FALSE) + continue; + + DBG("driver %p name %s", driver, driver->name); + + if (driver->probe != NULL && driver->probe(provider) == 0) { + provider->driver = driver; + break; + } + } + + if (provider->driver == NULL) + return -ENODEV; + + return 0; +} + +static void provider_remove(struct vpn_provider *provider) +{ + if (provider->driver != NULL) { + provider->driver->remove(provider); + provider->driver = NULL; + } +} + +static int provider_register(struct vpn_provider *provider) +{ + return provider_probe(provider); +} + +static void provider_unregister(struct vpn_provider *provider) +{ + provider_remove(provider); +} + +struct vpn_provider * +vpn_provider_ref_debug(struct vpn_provider *provider, + const char *file, int line, const char *caller) +{ + DBG("%p ref %d by %s:%d:%s()", provider, provider->refcount + 1, + file, line, caller); + + __sync_fetch_and_add(&provider->refcount, 1); + + return provider; +} + +static void provider_destruct(struct vpn_provider *provider) +{ + DBG("provider %p", provider); + + 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_strfreev(provider->nameservers); + g_hash_table_destroy(provider->routes); + g_hash_table_destroy(provider->user_routes); + g_hash_table_destroy(provider->setting_strings); + if (provider->resolv != NULL) { + g_resolv_unref(provider->resolv); + provider->resolv = NULL; + } + g_strfreev(provider->host_ip); + g_free(provider); +} + +void vpn_provider_unref_debug(struct vpn_provider *provider, + const char *file, int line, const char *caller) +{ + DBG("%p ref %d by %s:%d:%s()", provider, provider->refcount - 1, + file, line, caller); + + if (__sync_fetch_and_sub(&provider->refcount, 1) != 1) + return; + + provider_remove(provider); + + provider_destruct(provider); +} + +static void configuration_count_add(void) +{ + DBG("count %d", configuration_count + 1); + + __sync_fetch_and_add(&configuration_count, 1); +} + +static void configuration_count_del(void) +{ + DBG("count %d", configuration_count - 1); + + if (__sync_fetch_and_sub(&configuration_count, 1) != 1) + return; + + raise(SIGTERM); +} + +int __vpn_provider_disconnect(struct vpn_provider *provider) +{ + int err; + + DBG("provider %p", provider); + + if (provider->driver != NULL && provider->driver->disconnect != NULL) + err = provider->driver->disconnect(provider); + else + return -EOPNOTSUPP; + + if (err < 0) { + if (err != -EINPROGRESS) + return err; + + return -EINPROGRESS; + } + + return 0; +} + +int __vpn_provider_connect(struct vpn_provider *provider) +{ + int err; + + DBG("provider %p", provider); + + if (provider->driver != NULL && provider->driver->connect != NULL) + err = provider->driver->connect(provider); + else + return -EOPNOTSUPP; + + return err; +} + +int __vpn_provider_remove(const char *path) +{ + struct vpn_provider *provider; + + DBG("path %s", path); + + provider = vpn_provider_lookup(path); + if (provider != NULL) { + DBG("Removing VPN %s", provider->identifier); + + provider_unregister(provider); + g_hash_table_remove(provider_hash, provider->identifier); + return 0; + } + + return -ENXIO; +} + +static void append_ipv4(DBusMessageIter *iter, void *user_data) +{ + struct vpn_provider *provider = user_data; + const char *address, *gateway, *peer; + + address = __vpn_ipconfig_get_local(provider->ipconfig_ipv4); + if (address != NULL) { + in_addr_t addr; + struct in_addr netmask; + char *mask; + int prefixlen; + + prefixlen = __vpn_ipconfig_get_prefixlen( + provider->ipconfig_ipv4); + + addr = 0xffffffff << (32 - prefixlen); + netmask.s_addr = htonl(addr); + mask = inet_ntoa(netmask); + + connman_dbus_dict_append_basic(iter, "Address", + DBUS_TYPE_STRING, &address); + + connman_dbus_dict_append_basic(iter, "Netmask", + DBUS_TYPE_STRING, &mask); + } + + gateway = __vpn_ipconfig_get_gateway(provider->ipconfig_ipv4); + if (gateway != NULL) + connman_dbus_dict_append_basic(iter, "Gateway", + DBUS_TYPE_STRING, &gateway); + + peer = __vpn_ipconfig_get_peer(provider->ipconfig_ipv4); + if (peer != NULL) + connman_dbus_dict_append_basic(iter, "Peer", + DBUS_TYPE_STRING, &peer); +} + +static void append_ipv6(DBusMessageIter *iter, void *user_data) +{ + struct vpn_provider *provider = user_data; + const char *address, *gateway, *peer; + + address = __vpn_ipconfig_get_local(provider->ipconfig_ipv6); + if (address != NULL) { + unsigned char prefixlen; + + connman_dbus_dict_append_basic(iter, "Address", + DBUS_TYPE_STRING, &address); + + prefixlen = __vpn_ipconfig_get_prefixlen( + provider->ipconfig_ipv6); + + connman_dbus_dict_append_basic(iter, "PrefixLength", + DBUS_TYPE_BYTE, &prefixlen); + } + + gateway = __vpn_ipconfig_get_gateway(provider->ipconfig_ipv6); + if (gateway != NULL) + connman_dbus_dict_append_basic(iter, "Gateway", + DBUS_TYPE_STRING, &gateway); + + peer = __vpn_ipconfig_get_peer(provider->ipconfig_ipv6); + if (peer != NULL) + connman_dbus_dict_append_basic(iter, "Peer", + DBUS_TYPE_STRING, &peer); +} + +static const char *state2string(enum vpn_provider_state state) +{ + switch (state) { + case VPN_PROVIDER_STATE_UNKNOWN: + break; + case VPN_PROVIDER_STATE_IDLE: + return "idle"; + case VPN_PROVIDER_STATE_CONNECT: + return "configuration"; + case VPN_PROVIDER_STATE_READY: + return "ready"; + case VPN_PROVIDER_STATE_DISCONNECT: + return "disconnect"; + case VPN_PROVIDER_STATE_FAILURE: + return "failure"; + } + + return NULL; +} + +static int provider_indicate_state(struct vpn_provider *provider, + enum vpn_provider_state state) +{ + const char *str; + + DBG("provider %p state %d", provider, state); + + str = state2string(state); + if (str == NULL) + return -EINVAL; + + provider->state = state; + + if (state == VPN_PROVIDER_STATE_READY) { + connman_dbus_property_changed_basic(provider->path, + VPN_CONNECTION_INTERFACE, "Index", + DBUS_TYPE_INT32, &provider->index); + + if (provider->family == AF_INET) + connman_dbus_property_changed_dict(provider->path, + VPN_CONNECTION_INTERFACE, "IPv4", + append_ipv4, provider); + else if (provider->family == AF_INET6) + connman_dbus_property_changed_dict(provider->path, + VPN_CONNECTION_INTERFACE, "IPv6", + append_ipv6, provider); + } + + 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; + + if (provider->nameservers != NULL) + append_nameservers(iter, provider->nameservers); +} + +static void append_state(DBusMessageIter *iter, + struct vpn_provider *provider) +{ + char *str; + + switch (provider->state) { + case VPN_PROVIDER_STATE_UNKNOWN: + case VPN_PROVIDER_STATE_IDLE: + str = "idle"; + break; + case VPN_PROVIDER_STATE_CONNECT: + str = "configuration"; + break; + case VPN_PROVIDER_STATE_READY: + str = "ready"; + break; + case VPN_PROVIDER_STATE_DISCONNECT: + str = "disconnect"; + break; + case VPN_PROVIDER_STATE_FAILURE: + str = "failure"; + break; + } + + connman_dbus_dict_append_basic(iter, "State", + DBUS_TYPE_STRING, &str); +} + +static void append_properties(DBusMessageIter *iter, + struct vpn_provider *provider) +{ + DBusMessageIter dict; + + connman_dbus_dict_open(iter, &dict); + + append_state(&dict, provider); + + if (provider->type != NULL) + connman_dbus_dict_append_basic(&dict, "Type", + DBUS_TYPE_STRING, &provider->type); + + if (provider->name != NULL) + connman_dbus_dict_append_basic(&dict, "Name", + DBUS_TYPE_STRING, &provider->name); + + if (provider->host != NULL) + connman_dbus_dict_append_basic(&dict, "Host", + DBUS_TYPE_STRING, &provider->host); + if (provider->index >= 0) + connman_dbus_dict_append_basic(&dict, "Index", + DBUS_TYPE_INT32, &provider->index); + if (provider->domain != NULL) + connman_dbus_dict_append_basic(&dict, "Domain", + DBUS_TYPE_STRING, &provider->domain); + + if (provider->family == AF_INET) + connman_dbus_dict_append_dict(&dict, "IPv4", append_ipv4, + provider); + else if (provider->family == AF_INET6) + connman_dbus_dict_append_dict(&dict, "IPv6", append_ipv6, + provider); + + connman_dbus_dict_append_array(&dict, "Nameservers", + DBUS_TYPE_STRING, append_dns, provider); + + connman_dbus_dict_close(iter, &dict); +} + +static connman_bool_t check_host(char **hosts, char *host) +{ + int i; + + if (hosts == NULL) + return FALSE; + + for (i = 0; hosts[i] != NULL; i++) { + if (g_strcmp0(hosts[i], host) == 0) + return TRUE; + } + + return FALSE; +} + +static void provider_append_routes(gpointer key, gpointer value, + gpointer user_data) +{ + struct vpn_route *route = value; + struct vpn_provider *provider = user_data; + int index = provider->index; + + /* + * 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) { + DBG("Discarding VPN route to %s via %s at index %d", + route->host, 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, + route->gateway, + prefix_len); + } else { + connman_inet_add_network_route(index, route->host, + route->gateway, + route->netmask); + } +} + +static int set_connected(struct vpn_provider *provider, + connman_bool_t connected) +{ + struct vpn_ipconfig *ipconfig; + + DBG("provider %p id %s connected %d", provider, + provider->identifier, connected); + + if (connected == TRUE) { + if (provider->family == AF_INET6) + ipconfig = provider->ipconfig_ipv6; + else + ipconfig = provider->ipconfig_ipv4; + + __vpn_ipconfig_address_add(ipconfig, provider->family); + __vpn_ipconfig_gateway_add(ipconfig, provider->family); + + provider_indicate_state(provider, + VPN_PROVIDER_STATE_READY); + + g_hash_table_foreach(provider->routes, provider_append_routes, + provider); + + g_hash_table_foreach(provider->user_routes, + provider_append_routes, provider); + + } else { + provider_indicate_state(provider, + VPN_PROVIDER_STATE_DISCONNECT); + + provider_indicate_state(provider, + VPN_PROVIDER_STATE_IDLE); + } + + return 0; +} + +int vpn_provider_set_state(struct vpn_provider *provider, + enum vpn_provider_state state) +{ + if (provider == NULL) + return -EINVAL; + + switch (state) { + case VPN_PROVIDER_STATE_UNKNOWN: + return -EINVAL; + case VPN_PROVIDER_STATE_IDLE: + return set_connected(provider, FALSE); + case VPN_PROVIDER_STATE_CONNECT: + return provider_indicate_state(provider, state); + case VPN_PROVIDER_STATE_READY: + return set_connected(provider, TRUE); + case VPN_PROVIDER_STATE_DISCONNECT: + return provider_indicate_state(provider, state); + case VPN_PROVIDER_STATE_FAILURE: + return provider_indicate_state(provider, state); + } + return -EINVAL; +} + +int vpn_provider_indicate_error(struct vpn_provider *provider, + enum vpn_provider_error error) +{ + DBG("provider %p id %s error %d", provider, provider->identifier, + error); + + switch (error) { + case VPN_PROVIDER_ERROR_LOGIN_FAILED: + break; + case VPN_PROVIDER_ERROR_AUTH_FAILED: + break; + case VPN_PROVIDER_ERROR_CONNECT_FAILED: + break; + default: + break; + } + + 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; + + g_free(route->host); + g_free(route->netmask); + g_free(route->gateway); + g_free(route); +} + +static void provider_initialize(struct vpn_provider *provider) +{ + DBG("provider %p", provider); + + provider->index = 0; + provider->fd = -1; + provider->name = NULL; + provider->type = NULL; + provider->domain = NULL; + provider->identifier = NULL; + provider->user_networks = NULL; + provider->routes = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, destroy_route); + provider->user_routes = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, destroy_route); + provider->setting_strings = g_hash_table_new_full(g_str_hash, + g_str_equal, g_free, g_free); +} + +static struct vpn_provider *vpn_provider_new(void) +{ + struct vpn_provider *provider; + + provider = g_try_new0(struct vpn_provider, 1); + if (provider == NULL) + return NULL; + + provider->refcount = 1; + + DBG("provider %p", provider); + provider_initialize(provider); + + return provider; +} + +static struct vpn_provider *vpn_provider_get(const char *identifier) +{ + struct vpn_provider *provider; + + provider = g_hash_table_lookup(provider_hash, identifier); + if (provider != NULL) + return provider; + + provider = vpn_provider_new(); + if (provider == NULL) + return NULL; + + DBG("provider %p", provider); + + provider->identifier = g_strdup(identifier); + + g_hash_table_insert(provider_hash, provider->identifier, provider); + + configuration_count_add(); + + return provider; +} + +static void provider_dbus_ident(char *ident) +{ + int i, len = strlen(ident); + + for (i = 0; i < len; i++) { + if (ident[i] >= '0' && ident[i] <= '9') + continue; + if (ident[i] >= 'a' && ident[i] <= 'z') + continue; + if (ident[i] >= 'A' && ident[i] <= 'Z') + continue; + ident[i] = '_'; + } +} + +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) +{ + struct vpn_provider *provider; + + if (keyfile == NULL || ident == NULL) + return NULL; + + provider = vpn_provider_lookup(ident); + if (provider == NULL) { + provider = vpn_provider_get(ident); + if (provider == NULL) { + DBG("can not create provider"); + return NULL; + } + + provider_load_from_keyfile(provider, keyfile); + + if (provider->name == NULL || provider->host == NULL || + provider->domain == NULL) { + DBG("cannot get name, host or domain"); + vpn_provider_unref(provider); + return NULL; + } + + if (provider_register(provider) == 0) + connection_register(provider); + } + return provider; +} + +static void provider_create_all_from_type(const char *provider_type) +{ + unsigned int i; + char **providers; + char *id, *type; + GKeyFile *keyfile; + + DBG("provider type %s", provider_type); + + providers = __connman_storage_get_providers(); + + for (i = 0; providers[i] != NULL; i+=1) { + + if (strncmp(providers[i], "provider_", 9) != 0) + continue; + + id = providers[i] + 9; + keyfile = __connman_storage_load_provider(id); + + if (keyfile == NULL) + continue; + + type = g_key_file_get_string(keyfile, id, "Type", NULL); + + DBG("keyfile %p id %s type %s", keyfile, id, type); + + if (strcmp(provider_type, type) != 0) { + g_free(type); + g_key_file_free(keyfile); + continue; + } + + if (provider_create_from_keyfile(keyfile, id) == NULL) + DBG("could not create provider"); + + g_free(type); + g_key_file_free(keyfile); + } + g_strfreev(providers); +} + +static char **get_user_networks(DBusMessageIter *array, int *count) +{ + 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; + + for (l = list; l != NULL; l = g_slist_next(l)) + networks[--len] = l->data; + +out: + g_slist_free(list); + + return networks; +} + +int __vpn_provider_create_and_connect(DBusMessage *msg) +{ + struct vpn_provider *provider; + DBusMessageIter iter, array; + const char *type = NULL, *name = NULL; + const char *host = NULL, *domain = NULL; + char **networks = NULL; + char *ident; + int err, count = 0; + + dbus_message_iter_init(msg, &iter); + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + const char *key; + + dbus_message_iter_recurse(&array, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + switch (dbus_message_iter_get_arg_type(&value)) { + case DBUS_TYPE_STRING: + if (g_str_equal(key, "Type") == TRUE) + dbus_message_iter_get_basic(&value, &type); + else if (g_str_equal(key, "Name") == TRUE) + 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) + 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); + break; + } + + dbus_message_iter_next(&array); + } + + if (host == NULL || domain == NULL) + return -EINVAL; + + DBG("Type %s name %s networks %p", type, name, networks); + + if (type == NULL || name == NULL) + return -EOPNOTSUPP; + + ident = g_strdup_printf("%s_%s", host, domain); + provider_dbus_ident(ident); + + 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"); + g_free(ident); + return -EOPNOTSUPP; + } + + provider->host = g_strdup(host); + provider->domain = g_strdup(domain); + provider->name = g_strdup(name); + provider->type = g_strdup(type); + + if (provider_register(provider) == 0) + vpn_provider_load(provider); + + provider_resolv_host_addr(provider); + } + + if (networks != NULL) { + g_strfreev(provider->user_networks); + provider->user_networks = networks; + provider->num_user_networks = count; + set_user_networks(provider, provider->user_networks); + } + + dbus_message_iter_init(msg, &iter); + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + const char *key, *str; + + dbus_message_iter_recurse(&array, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + switch (dbus_message_iter_get_arg_type(&value)) { + case DBUS_TYPE_STRING: + dbus_message_iter_get_basic(&value, &str); + vpn_provider_set_string(provider, key, str); + break; + } + + dbus_message_iter_next(&array); + } + + g_free(ident); + + provider->pending_msg = dbus_message_ref(msg); + + DBG("provider %p pending %p", provider, provider->pending_msg); + + if (provider->index > 0) { + DBG("provider already connected"); + } else { + err = __vpn_provider_connect(provider); + if (err < 0 && err != -EINPROGRESS) + goto failed; + } + + vpn_provider_save(provider); + + return 0; + +failed: + DBG("Can not connect (%d), deleting provider %p %s", err, provider, + provider->identifier); + + vpn_provider_indicate_error(provider, + VPN_PROVIDER_ERROR_CONNECT_FAILED); + + g_hash_table_remove(provider_hash, provider->identifier); + + return err; +} + +static void append_connection_structs(DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter entry; + GHashTableIter hash; + gpointer value, key; + + g_hash_table_iter_init(&hash, provider_hash); + + while (g_hash_table_iter_next(&hash, &key, &value) == TRUE) { + struct vpn_provider *provider = value; + + DBG("path %s", provider->path); + + if (provider->identifier == NULL) + continue; + + dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, + NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, + &provider->path); + append_properties(&entry, provider); + dbus_message_iter_close_container(iter, &entry); + } +} + +DBusMessage *__vpn_provider_get_connections(DBusMessage *msg) +{ + DBusMessage *reply; + + DBG(""); + + reply = dbus_message_new_method_return(msg); + if (reply == NULL) + return NULL; + + __connman_dbus_append_objpath_dict_array(reply, + append_connection_structs, NULL); + + return reply; +} + +const char * __vpn_provider_get_ident(struct vpn_provider *provider) +{ + if (provider == NULL) + return NULL; + + return provider->identifier; +} + +int vpn_provider_set_string(struct vpn_provider *provider, + const char *key, const char *value) +{ + DBG("provider %p key %s value %s", provider, key, value); + + if (g_str_equal(key, "Type") == TRUE) { + g_free(provider->type); + provider->type = g_strdup(value); + } else if (g_str_equal(key, "Name") == TRUE) { + g_free(provider->name); + provider->name = g_strdup(value); + } 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) { + g_free(provider->domain); + provider->domain = g_strdup(value); + } else + g_hash_table_replace(provider->setting_strings, + g_strdup(key), g_strdup(value)); + return 0; +} + +const char *vpn_provider_get_string(struct vpn_provider *provider, + const char *key) +{ + DBG("provider %p key %s", provider, key); + + if (g_str_equal(key, "Type") == TRUE) + return provider->type; + else if (g_str_equal(key, "Name") == TRUE) + return provider->name; + else if (g_str_equal(key, "Host") == TRUE) + return provider->host; + else if (g_str_equal(key, "VPN.Domain") == TRUE) + return provider->domain; + + return g_hash_table_lookup(provider->setting_strings, key); +} + +connman_bool_t __vpn_provider_check_routes(struct vpn_provider *provider) +{ + if (provider == NULL) + return FALSE; + + if (provider->user_routes != NULL && + g_hash_table_size(provider->user_routes) > 0) + return TRUE; + + if (provider->routes != NULL && + g_hash_table_size(provider->routes) > 0) + return TRUE; + + return FALSE; +} + +void *vpn_provider_get_data(struct vpn_provider *provider) +{ + return provider->driver_data; +} + +void vpn_provider_set_data(struct vpn_provider *provider, void *data) +{ + provider->driver_data = 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; + } + + if (provider->ipconfig_ipv4 == NULL) { + provider->ipconfig_ipv4 = __vpn_ipconfig_create(index, + AF_INET); + if (provider->ipconfig_ipv4 == NULL) { + DBG("Couldnt create ipconfig for IPv4"); + goto done; + } + } + + __vpn_ipconfig_set_index(provider->ipconfig_ipv4, index); + + if (provider->ipconfig_ipv6 == NULL) { + provider->ipconfig_ipv6 = __vpn_ipconfig_create(index, + AF_INET6); + if (provider->ipconfig_ipv6 == NULL) { + DBG("Couldnt create ipconfig for IPv6"); + goto done; + } + } + + __vpn_ipconfig_set_index(provider->ipconfig_ipv6, index); + +done: + provider->index = index; +} + +int vpn_provider_get_index(struct vpn_provider *provider) +{ + return provider->index; +} + +int vpn_provider_set_ipaddress(struct vpn_provider *provider, + struct connman_ipaddress *ipaddress) +{ + struct vpn_ipconfig *ipconfig = NULL; + + switch (ipaddress->family) { + case AF_INET: + ipconfig = provider->ipconfig_ipv4; + break; + case AF_INET6: + ipconfig = provider->ipconfig_ipv6; + break; + default: + break; + } + + DBG("provider %p ipconfig %p family %d", provider, ipconfig, + ipaddress->family); + + if (ipconfig == NULL) + return -EINVAL; + + provider->family = ipaddress->family; + + __vpn_ipconfig_set_local(ipconfig, ipaddress->local); + __vpn_ipconfig_set_peer(ipconfig, ipaddress->peer); + __vpn_ipconfig_set_broadcast(ipconfig, ipaddress->broadcast); + __vpn_ipconfig_set_gateway(ipconfig, ipaddress->gateway); + __vpn_ipconfig_set_prefixlen(ipconfig, ipaddress->prefixlen); + + return 0; +} + +int vpn_provider_set_pac(struct vpn_provider *provider, + const char *pac) +{ + DBG("provider %p pac %s", provider, pac); + + return 0; +} + + +int vpn_provider_set_domain(struct vpn_provider *provider, + const char *domain) +{ + DBG("provider %p domain %s", provider, domain); + + g_free(provider->domain); + provider->domain = g_strdup(domain); + + return 0; +} + +int vpn_provider_set_nameservers(struct vpn_provider *provider, + const char *nameservers) +{ + DBG("provider %p nameservers %s", provider, nameservers); + + g_strfreev(provider->nameservers); + provider->nameservers = NULL; + + if (nameservers == NULL) + return 0; + + provider->nameservers = g_strsplit(nameservers, " ", 0); + + return 0; +} + +enum provider_route_type { + PROVIDER_ROUTE_TYPE_NONE = 0, + PROVIDER_ROUTE_TYPE_MASK = 1, + PROVIDER_ROUTE_TYPE_ADDR = 2, + PROVIDER_ROUTE_TYPE_GW = 3, +}; + +static int route_env_parse(struct vpn_provider *provider, const char *key, + int *family, unsigned long *idx, + enum provider_route_type *type) +{ + char *end; + const char *start; + + DBG("name %s", provider->name); + + if (!strcmp(provider->type, "openvpn")) { + if (g_str_has_prefix(key, "route_network_") == TRUE) { + start = key + strlen("route_network_"); + *type = PROVIDER_ROUTE_TYPE_ADDR; + } else if (g_str_has_prefix(key, "route_netmask_") == TRUE) { + start = key + strlen("route_netmask_"); + *type = PROVIDER_ROUTE_TYPE_MASK; + } else if (g_str_has_prefix(key, "route_gateway_") == TRUE) { + start = key + strlen("route_gateway_"); + *type = PROVIDER_ROUTE_TYPE_GW; + } else + return -EINVAL; + + *family = AF_INET; + *idx = g_ascii_strtoull(start, &end, 10); + + } else if (!strcmp(provider->type, "openconnect")) { + if (g_str_has_prefix(key, "CISCO_SPLIT_INC_") == TRUE) { + *family = AF_INET; + start = key + strlen("CISCO_SPLIT_INC_"); + } else if (g_str_has_prefix(key, + "CISCO_IPV6_SPLIT_INC_") == TRUE) { + *family = AF_INET6; + start = key + strlen("CISCO_IPV6_SPLIT_INC_"); + } else + return -EINVAL; + + *idx = g_ascii_strtoull(start, &end, 10); + + if (strncmp(end, "_ADDR", 5) == 0) + *type = PROVIDER_ROUTE_TYPE_ADDR; + else if (strncmp(end, "_MASK", 5) == 0) + *type = PROVIDER_ROUTE_TYPE_MASK; + else if (strncmp(end, "_MASKLEN", 8) == 0 && + *family == AF_INET6) { + *type = PROVIDER_ROUTE_TYPE_MASK; + } else + return -EINVAL; + } + + return 0; +} + +int vpn_provider_append_route(struct vpn_provider *provider, + const char *key, const char *value) +{ + struct vpn_route *route; + int ret, family = 0; + unsigned long idx = 0; + enum provider_route_type type = PROVIDER_ROUTE_TYPE_NONE; + + DBG("key %s value %s", key, value); + + ret = route_env_parse(provider, key, &family, &idx, &type); + if (ret < 0) + return ret; + + DBG("idx %lu family %d type %d", idx, family, type); + + route = g_hash_table_lookup(provider->routes, GINT_TO_POINTER(idx)); + if (route == NULL) { + route = g_try_new0(struct vpn_route, 1); + if (route == NULL) { + connman_error("out of memory"); + return -ENOMEM; + } + + route->family = family; + + g_hash_table_replace(provider->routes, GINT_TO_POINTER(idx), + route); + } + + switch (type) { + case PROVIDER_ROUTE_TYPE_NONE: + break; + case PROVIDER_ROUTE_TYPE_MASK: + route->netmask = g_strdup(value); + break; + case PROVIDER_ROUTE_TYPE_ADDR: + route->host = g_strdup(value); + break; + case PROVIDER_ROUTE_TYPE_GW: + route->gateway = g_strdup(value); + break; + } + + return 0; +} + +const char *vpn_provider_get_driver_name(struct vpn_provider *provider) +{ + if (provider->driver == NULL) + return NULL; + + return provider->driver->name; +} + +const char *vpn_provider_get_save_group(struct vpn_provider *provider) +{ + return provider->identifier; +} + +static gint compare_priority(gconstpointer a, gconstpointer b) +{ + return 0; +} + +static void clean_provider(gpointer key, gpointer value, gpointer user_data) +{ + struct vpn_provider *provider = value; + + if (provider->driver != NULL && provider->driver->remove) + provider->driver->remove(provider); + + connection_unregister(provider); +} + +int vpn_provider_driver_register(struct vpn_provider_driver *driver) +{ + DBG("driver %p name %s", driver, driver->name); + + driver_list = g_slist_insert_sorted(driver_list, driver, + compare_priority); + provider_create_all_from_type(driver->name); + return 0; +} + +void vpn_provider_driver_unregister(struct vpn_provider_driver *driver) +{ + DBG("driver %p name %s", driver, driver->name); + + driver_list = g_slist_remove(driver_list, driver); +} + +int __vpn_provider_init(void) +{ + DBG(""); + + connection = connman_dbus_get_connection(); + + provider_hash = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, unregister_provider); + + return 0; +} + +void __vpn_provider_cleanup(void) +{ + DBG(""); + + g_hash_table_foreach(provider_hash, clean_provider, NULL); + + g_hash_table_destroy(provider_hash); + provider_hash = NULL; + + dbus_connection_unref(connection); +} diff --git a/vpn/vpn-provider.h b/vpn/vpn-provider.h new file mode 100644 index 00000000..0f139b09 --- /dev/null +++ b/vpn/vpn-provider.h @@ -0,0 +1,121 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2007-2012 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 + * + */ + +#ifndef __VPN_PROVIDER_H +#define __VPN_PROVIDER_H + +#include <glib.h> +#include <connman/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * SECTION:provider + * @title: Provider premitives + * @short_description: Functions for handling providers + */ + +enum vpn_provider_type { + VPN_PROVIDER_TYPE_UNKNOWN = 0, + VPN_PROVIDER_TYPE_VPN = 1, +}; + +enum vpn_provider_state { + VPN_PROVIDER_STATE_UNKNOWN = 0, + VPN_PROVIDER_STATE_IDLE = 1, + VPN_PROVIDER_STATE_CONNECT = 2, + VPN_PROVIDER_STATE_READY = 3, + VPN_PROVIDER_STATE_DISCONNECT = 4, + VPN_PROVIDER_STATE_FAILURE = 5, +}; + +enum vpn_provider_error { + VPN_PROVIDER_ERROR_UNKNOWN = 0, + VPN_PROVIDER_ERROR_CONNECT_FAILED = 1, + VPN_PROVIDER_ERROR_LOGIN_FAILED = 2, + VPN_PROVIDER_ERROR_AUTH_FAILED = 3, +}; + +struct vpn_provider; +struct connman_ipaddress; + +#define vpn_provider_ref(provider) \ + vpn_provider_ref_debug(provider, __FILE__, __LINE__, __func__) + +#define vpn_provider_unref(provider) \ + vpn_provider_unref_debug(provider, __FILE__, __LINE__, __func__) + +struct vpn_provider * +vpn_provider_ref_debug(struct vpn_provider *provider, + const char *file, int line, const char *caller); +void vpn_provider_unref_debug(struct vpn_provider *provider, + const char *file, int line, const char *caller); + +int vpn_provider_set_string(struct vpn_provider *provider, + const char *key, const char *value); +const char *vpn_provider_get_string(struct vpn_provider *provider, + const char *key); + +int vpn_provider_set_state(struct vpn_provider *provider, + enum vpn_provider_state state); + +int vpn_provider_indicate_error(struct vpn_provider *provider, + enum vpn_provider_error error); + +void vpn_provider_set_index(struct vpn_provider *provider, int index); +int vpn_provider_get_index(struct vpn_provider *provider); + +void vpn_provider_set_data(struct vpn_provider *provider, void *data); +void *vpn_provider_get_data(struct vpn_provider *provider); +int vpn_provider_set_ipaddress(struct vpn_provider *provider, + struct connman_ipaddress *ipaddress); +int vpn_provider_set_pac(struct vpn_provider *provider, + const char *pac); +int vpn_provider_set_domain(struct vpn_provider *provider, + const char *domain); +int vpn_provider_set_nameservers(struct vpn_provider *provider, + const char *nameservers); +int vpn_provider_append_route(struct vpn_provider *provider, + const char *key, const char *value); + +const char *vpn_provider_get_driver_name(struct vpn_provider *provider); +const char *vpn_provider_get_save_group(struct vpn_provider *provider); + +struct vpn_provider_driver { + const char *name; + enum vpn_provider_type type; + int (*probe) (struct vpn_provider *provider); + int (*remove) (struct vpn_provider *provider); + int (*connect) (struct vpn_provider *provider); + int (*disconnect) (struct vpn_provider *provider); + int (*save) (struct vpn_provider *provider, GKeyFile *keyfile); +}; + +int vpn_provider_driver_register(struct vpn_provider_driver *driver); +void vpn_provider_driver_unregister(struct vpn_provider_driver *driver); + +#ifdef __cplusplus +} +#endif + +#endif /* __VPN_PROVIDER_H */ diff --git a/vpn/vpn-rtnl.c b/vpn/vpn-rtnl.c new file mode 100644 index 00000000..5ce14e42 --- /dev/null +++ b/vpn/vpn-rtnl.c @@ -0,0 +1,1185 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2012 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 <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <netinet/ether.h> +#include <netinet/icmp6.h> +#include <net/if_arp.h> +#include <linux/if.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/wireless.h> + +#include <glib.h> + +#include <connman/log.h> + +#include "vpn.h" + +#include "vpn-rtnl.h" + +#ifndef ARPHDR_PHONET_PIPE +#define ARPHDR_PHONET_PIPE (821) +#endif + +#define print(arg...) do { if (0) connman_info(arg); } while (0) +#define debug(arg...) do { if (0) DBG(arg); } while (0) + +struct watch_data { + unsigned int id; + int index; + vpn_rtnl_link_cb_t newlink; + void *user_data; +}; + +static GSList *watch_list = NULL; +static unsigned int watch_id = 0; + +static GSList *update_list = NULL; +static guint update_interval = G_MAXUINT; +static guint update_timeout = 0; + +struct interface_data { + int index; + char *name; + char *ident; +}; + +static GHashTable *interface_list = NULL; + +static void free_interface(gpointer data) +{ + struct interface_data *interface = data; + + g_free(interface->ident); + g_free(interface->name); + g_free(interface); +} + +/** + * vpn_rtnl_add_newlink_watch: + * @index: network device index + * @callback: callback function + * @user_data: callback data; + * + * Add a new RTNL watch for newlink events + * + * Returns: %0 on failure and a unique id on success + */ +unsigned int vpn_rtnl_add_newlink_watch(int index, + vpn_rtnl_link_cb_t callback, void *user_data) +{ + struct watch_data *watch; + + watch = g_try_new0(struct watch_data, 1); + if (watch == NULL) + return 0; + + watch->id = ++watch_id; + watch->index = index; + + watch->newlink = callback; + watch->user_data = user_data; + + watch_list = g_slist_prepend(watch_list, watch); + + DBG("id %d", watch->id); + + if (callback) { + unsigned int flags = __vpn_ipconfig_get_flags_from_index(index); + + if (flags > 0) + callback(flags, 0, user_data); + } + + return watch->id; +} + +/** + * vpn_rtnl_remove_watch: + * @id: watch identifier + * + * Remove the RTNL watch for the identifier + */ +void vpn_rtnl_remove_watch(unsigned int id) +{ + GSList *list; + + DBG("id %d", id); + + if (id == 0) + return; + + for (list = watch_list; list; list = list->next) { + struct watch_data *watch = list->data; + + if (watch->id == id) { + watch_list = g_slist_remove(watch_list, watch); + g_free(watch); + break; + } + } +} + +static void trigger_rtnl(int index, void *user_data) +{ + struct vpn_rtnl *rtnl = user_data; + + if (rtnl->newlink) { + unsigned short type = __vpn_ipconfig_get_type_from_index(index); + unsigned int flags = __vpn_ipconfig_get_flags_from_index(index); + + rtnl->newlink(type, index, flags, 0); + } +} + +static GSList *rtnl_list = NULL; + +static gint compare_priority(gconstpointer a, gconstpointer b) +{ + const struct vpn_rtnl *rtnl1 = a; + const struct vpn_rtnl *rtnl2 = b; + + return rtnl2->priority - rtnl1->priority; +} + +/** + * vpn_rtnl_register: + * @rtnl: RTNL module + * + * Register a new RTNL module + * + * Returns: %0 on success + */ +int vpn_rtnl_register(struct vpn_rtnl *rtnl) +{ + DBG("rtnl %p name %s", rtnl, rtnl->name); + + rtnl_list = g_slist_insert_sorted(rtnl_list, rtnl, + compare_priority); + + __vpn_ipconfig_foreach(trigger_rtnl, rtnl); + + return 0; +} + +/** + * vpn_rtnl_unregister: + * @rtnl: RTNL module + * + * Remove a previously registered RTNL module + */ +void vpn_rtnl_unregister(struct vpn_rtnl *rtnl) +{ + DBG("rtnl %p name %s", rtnl, rtnl->name); + + rtnl_list = g_slist_remove(rtnl_list, rtnl); +} + +static const char *operstate2str(unsigned char operstate) +{ + switch (operstate) { + case IF_OPER_UNKNOWN: + return "UNKNOWN"; + case IF_OPER_NOTPRESENT: + return "NOT-PRESENT"; + case IF_OPER_DOWN: + return "DOWN"; + case IF_OPER_LOWERLAYERDOWN: + return "LOWER-LAYER-DOWN"; + case IF_OPER_TESTING: + return "TESTING"; + case IF_OPER_DORMANT: + return "DORMANT"; + case IF_OPER_UP: + return "UP"; + } + + return ""; +} + +static void extract_link(struct ifinfomsg *msg, int bytes, + struct ether_addr *address, const char **ifname, + unsigned int *mtu, unsigned char *operstate, + struct rtnl_link_stats *stats) +{ + struct rtattr *attr; + + for (attr = IFLA_RTA(msg); RTA_OK(attr, bytes); + attr = RTA_NEXT(attr, bytes)) { + switch (attr->rta_type) { + case IFLA_ADDRESS: + if (address != NULL) + memcpy(address, RTA_DATA(attr), ETH_ALEN); + break; + case IFLA_IFNAME: + if (ifname != NULL) + *ifname = RTA_DATA(attr); + break; + case IFLA_MTU: + if (mtu != NULL) + *mtu = *((unsigned int *) RTA_DATA(attr)); + break; + case IFLA_STATS: + if (stats != NULL) + memcpy(stats, RTA_DATA(attr), + sizeof(struct rtnl_link_stats)); + break; + case IFLA_OPERSTATE: + if (operstate != NULL) + *operstate = *((unsigned char *) RTA_DATA(attr)); + break; + case IFLA_LINKMODE: + break; + } + } +} + +static void process_newlink(unsigned short type, int index, unsigned flags, + unsigned change, struct ifinfomsg *msg, int bytes) +{ + struct ether_addr address = {{ 0, 0, 0, 0, 0, 0 }}; + struct ether_addr compare = {{ 0, 0, 0, 0, 0, 0 }}; + struct rtnl_link_stats stats; + unsigned char operstate = 0xff; + struct interface_data *interface; + const char *ifname = NULL; + unsigned int mtu = 0; + char ident[13], str[18]; + GSList *list; + + memset(&stats, 0, sizeof(stats)); + extract_link(msg, bytes, &address, &ifname, &mtu, &operstate, &stats); + + snprintf(ident, 13, "%02x%02x%02x%02x%02x%02x", + address.ether_addr_octet[0], + address.ether_addr_octet[1], + address.ether_addr_octet[2], + address.ether_addr_octet[3], + address.ether_addr_octet[4], + address.ether_addr_octet[5]); + + snprintf(str, 18, "%02X:%02X:%02X:%02X:%02X:%02X", + address.ether_addr_octet[0], + address.ether_addr_octet[1], + address.ether_addr_octet[2], + address.ether_addr_octet[3], + address.ether_addr_octet[4], + address.ether_addr_octet[5]); + + switch (type) { + case ARPHRD_ETHER: + case ARPHRD_LOOPBACK: + case ARPHDR_PHONET_PIPE: + case ARPHRD_NONE: + __vpn_ipconfig_newlink(index, type, flags, + str, mtu, &stats); + break; + } + + if (memcmp(&address, &compare, ETH_ALEN) != 0) + connman_info("%s {newlink} index %d address %s mtu %u", + ifname, index, str, mtu); + + if (operstate != 0xff) + connman_info("%s {newlink} index %d operstate %u <%s>", + ifname, index, operstate, + operstate2str(operstate)); + + interface = g_hash_table_lookup(interface_list, GINT_TO_POINTER(index)); + if (interface == NULL) { + interface = g_new0(struct interface_data, 1); + interface->index = index; + interface->name = g_strdup(ifname); + interface->ident = g_strdup(ident); + + g_hash_table_insert(interface_list, + GINT_TO_POINTER(index), interface); + } + + for (list = rtnl_list; list; list = list->next) { + struct vpn_rtnl *rtnl = list->data; + + if (rtnl->newlink) + rtnl->newlink(type, index, flags, change); + } + + for (list = watch_list; list; list = list->next) { + struct watch_data *watch = list->data; + + if (watch->index != index) + continue; + + if (watch->newlink) + watch->newlink(flags, change, watch->user_data); + } +} + +static void process_dellink(unsigned short type, int index, unsigned flags, + unsigned change, struct ifinfomsg *msg, int bytes) +{ + struct rtnl_link_stats stats; + unsigned char operstate = 0xff; + const char *ifname = NULL; + GSList *list; + + memset(&stats, 0, sizeof(stats)); + extract_link(msg, bytes, NULL, &ifname, NULL, &operstate, &stats); + + if (operstate != 0xff) + connman_info("%s {dellink} index %d operstate %u <%s>", + ifname, index, operstate, + operstate2str(operstate)); + + for (list = rtnl_list; list; list = list->next) { + struct vpn_rtnl *rtnl = list->data; + + if (rtnl->dellink) + rtnl->dellink(type, index, flags, change); + } + + switch (type) { + case ARPHRD_ETHER: + case ARPHRD_LOOPBACK: + case ARPHRD_NONE: + __vpn_ipconfig_dellink(index, &stats); + break; + } + + g_hash_table_remove(interface_list, GINT_TO_POINTER(index)); +} + +static void extract_ipv4_route(struct rtmsg *msg, int bytes, int *index, + struct in_addr *dst, + struct in_addr *gateway) +{ + struct rtattr *attr; + + for (attr = RTM_RTA(msg); RTA_OK(attr, bytes); + attr = RTA_NEXT(attr, bytes)) { + switch (attr->rta_type) { + case RTA_DST: + if (dst != NULL) + *dst = *((struct in_addr *) RTA_DATA(attr)); + break; + case RTA_GATEWAY: + if (gateway != NULL) + *gateway = *((struct in_addr *) RTA_DATA(attr)); + break; + case RTA_OIF: + if (index != NULL) + *index = *((int *) RTA_DATA(attr)); + break; + } + } +} + +static void extract_ipv6_route(struct rtmsg *msg, int bytes, int *index, + struct in6_addr *dst, + struct in6_addr *gateway) +{ + struct rtattr *attr; + + for (attr = RTM_RTA(msg); RTA_OK(attr, bytes); + attr = RTA_NEXT(attr, bytes)) { + switch (attr->rta_type) { + case RTA_DST: + if (dst != NULL) + *dst = *((struct in6_addr *) RTA_DATA(attr)); + break; + case RTA_GATEWAY: + if (gateway != NULL) + *gateway = + *((struct in6_addr *) RTA_DATA(attr)); + break; + case RTA_OIF: + if (index != NULL) + *index = *((int *) RTA_DATA(attr)); + break; + } + } +} + +static void process_newroute(unsigned char family, unsigned char scope, + struct rtmsg *msg, int bytes) +{ + GSList *list; + char dststr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN]; + int index = -1; + + if (family == AF_INET) { + struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY }; + + extract_ipv4_route(msg, bytes, &index, &dst, &gateway); + + inet_ntop(family, &dst, dststr, sizeof(dststr)); + inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr)); + + /* skip host specific routes */ + if (scope != RT_SCOPE_UNIVERSE && + !(scope == RT_SCOPE_LINK && dst.s_addr == INADDR_ANY)) + return; + + if (dst.s_addr != INADDR_ANY) + return; + + } else if (family == AF_INET6) { + struct in6_addr dst = IN6ADDR_ANY_INIT, + gateway = IN6ADDR_ANY_INIT; + + extract_ipv6_route(msg, bytes, &index, &dst, &gateway); + + inet_ntop(family, &dst, dststr, sizeof(dststr)); + inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr)); + + /* skip host specific routes */ + if (scope != RT_SCOPE_UNIVERSE && + !(scope == RT_SCOPE_LINK && + IN6_IS_ADDR_UNSPECIFIED(&dst))) + return; + + if (!IN6_IS_ADDR_UNSPECIFIED(&dst)) + return; + } else + return; + + for (list = rtnl_list; list; list = list->next) { + struct vpn_rtnl *rtnl = list->data; + + if (rtnl->newgateway) + rtnl->newgateway(index, gatewaystr); + } +} + +static void process_delroute(unsigned char family, unsigned char scope, + struct rtmsg *msg, int bytes) +{ + GSList *list; + char dststr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN]; + int index = -1; + + if (family == AF_INET) { + struct in_addr dst = { INADDR_ANY }, gateway = { INADDR_ANY }; + + extract_ipv4_route(msg, bytes, &index, &dst, &gateway); + + inet_ntop(family, &dst, dststr, sizeof(dststr)); + inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr)); + + /* skip host specific routes */ + if (scope != RT_SCOPE_UNIVERSE && + !(scope == RT_SCOPE_LINK && dst.s_addr == INADDR_ANY)) + return; + + if (dst.s_addr != INADDR_ANY) + return; + + } else if (family == AF_INET6) { + struct in6_addr dst = IN6ADDR_ANY_INIT, + gateway = IN6ADDR_ANY_INIT; + + extract_ipv6_route(msg, bytes, &index, &dst, &gateway); + + inet_ntop(family, &dst, dststr, sizeof(dststr)); + inet_ntop(family, &gateway, gatewaystr, sizeof(gatewaystr)); + + /* skip host specific routes */ + if (scope != RT_SCOPE_UNIVERSE && + !(scope == RT_SCOPE_LINK && + IN6_IS_ADDR_UNSPECIFIED(&dst))) + return; + + if (!IN6_IS_ADDR_UNSPECIFIED(&dst)) + return; + } else + return; + + for (list = rtnl_list; list; list = list->next) { + struct vpn_rtnl *rtnl = list->data; + + if (rtnl->delgateway) + rtnl->delgateway(index, gatewaystr); + } +} + +static inline void print_ether(struct rtattr *attr, const char *name) +{ + int len = (int) RTA_PAYLOAD(attr); + + if (len == ETH_ALEN) { + struct ether_addr eth; + memcpy(ð, RTA_DATA(attr), ETH_ALEN); + print(" attr %s (len %d) %s\n", name, len, ether_ntoa(ð)); + } else + print(" attr %s (len %d)\n", name, len); +} + +static inline void print_inet(struct rtattr *attr, const char *name, + unsigned char family) +{ + int len = (int) RTA_PAYLOAD(attr); + + if (family == AF_INET && len == sizeof(struct in_addr)) { + struct in_addr addr; + addr = *((struct in_addr *) RTA_DATA(attr)); + print(" attr %s (len %d) %s\n", name, len, inet_ntoa(addr)); + } else + print(" attr %s (len %d)\n", name, len); +} + +static inline void print_string(struct rtattr *attr, const char *name) +{ + print(" attr %s (len %d) %s\n", name, (int) RTA_PAYLOAD(attr), + (char *) RTA_DATA(attr)); +} + +static inline void print_byte(struct rtattr *attr, const char *name) +{ + print(" attr %s (len %d) 0x%02x\n", name, (int) RTA_PAYLOAD(attr), + *((unsigned char *) RTA_DATA(attr))); +} + +static inline void print_integer(struct rtattr *attr, const char *name) +{ + print(" attr %s (len %d) %d\n", name, (int) RTA_PAYLOAD(attr), + *((int *) RTA_DATA(attr))); +} + +static inline void print_attr(struct rtattr *attr, const char *name) +{ + int len = (int) RTA_PAYLOAD(attr); + + if (name && len > 0) + print(" attr %s (len %d)\n", name, len); + else + print(" attr %d (len %d)\n", attr->rta_type, len); +} + +static void rtnl_link(struct nlmsghdr *hdr) +{ + struct ifinfomsg *msg; + struct rtattr *attr; + int bytes; + + msg = (struct ifinfomsg *) NLMSG_DATA(hdr); + bytes = IFLA_PAYLOAD(hdr); + + print("ifi_index %d ifi_flags 0x%04x", msg->ifi_index, msg->ifi_flags); + + for (attr = IFLA_RTA(msg); RTA_OK(attr, bytes); + attr = RTA_NEXT(attr, bytes)) { + switch (attr->rta_type) { + case IFLA_ADDRESS: + print_ether(attr, "address"); + break; + case IFLA_BROADCAST: + print_ether(attr, "broadcast"); + break; + case IFLA_IFNAME: + print_string(attr, "ifname"); + break; + case IFLA_MTU: + print_integer(attr, "mtu"); + break; + case IFLA_LINK: + print_attr(attr, "link"); + break; + case IFLA_QDISC: + print_attr(attr, "qdisc"); + break; + case IFLA_STATS: + print_attr(attr, "stats"); + break; + case IFLA_COST: + print_attr(attr, "cost"); + break; + case IFLA_PRIORITY: + print_attr(attr, "priority"); + break; + case IFLA_MASTER: + print_attr(attr, "master"); + break; + case IFLA_WIRELESS: + print_attr(attr, "wireless"); + break; + case IFLA_PROTINFO: + print_attr(attr, "protinfo"); + break; + case IFLA_TXQLEN: + print_integer(attr, "txqlen"); + break; + case IFLA_MAP: + print_attr(attr, "map"); + break; + case IFLA_WEIGHT: + print_attr(attr, "weight"); + break; + case IFLA_OPERSTATE: + print_byte(attr, "operstate"); + break; + case IFLA_LINKMODE: + print_byte(attr, "linkmode"); + break; + default: + print_attr(attr, NULL); + break; + } + } +} + +static void rtnl_newlink(struct nlmsghdr *hdr) +{ + struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr); + + rtnl_link(hdr); + + process_newlink(msg->ifi_type, msg->ifi_index, msg->ifi_flags, + msg->ifi_change, msg, IFA_PAYLOAD(hdr)); +} + +static void rtnl_dellink(struct nlmsghdr *hdr) +{ + struct ifinfomsg *msg = (struct ifinfomsg *) NLMSG_DATA(hdr); + + rtnl_link(hdr); + + process_dellink(msg->ifi_type, msg->ifi_index, msg->ifi_flags, + msg->ifi_change, msg, IFA_PAYLOAD(hdr)); +} + +static void rtnl_route(struct nlmsghdr *hdr) +{ + struct rtmsg *msg; + struct rtattr *attr; + int bytes; + + msg = (struct rtmsg *) NLMSG_DATA(hdr); + bytes = RTM_PAYLOAD(hdr); + + print("rtm_family %d rtm_table %d rtm_protocol %d", + msg->rtm_family, msg->rtm_table, msg->rtm_protocol); + print("rtm_scope %d rtm_type %d rtm_flags 0x%04x", + msg->rtm_scope, msg->rtm_type, msg->rtm_flags); + + for (attr = RTM_RTA(msg); RTA_OK(attr, bytes); + attr = RTA_NEXT(attr, bytes)) { + switch (attr->rta_type) { + case RTA_DST: + print_inet(attr, "dst", msg->rtm_family); + break; + case RTA_SRC: + print_inet(attr, "src", msg->rtm_family); + break; + case RTA_IIF: + print_string(attr, "iif"); + break; + case RTA_OIF: + print_integer(attr, "oif"); + break; + case RTA_GATEWAY: + print_inet(attr, "gateway", msg->rtm_family); + break; + case RTA_PRIORITY: + print_attr(attr, "priority"); + break; + case RTA_PREFSRC: + print_inet(attr, "prefsrc", msg->rtm_family); + break; + case RTA_METRICS: + print_attr(attr, "metrics"); + break; + case RTA_TABLE: + print_integer(attr, "table"); + break; + default: + print_attr(attr, NULL); + break; + } + } +} + +static connman_bool_t is_route_rtmsg(struct rtmsg *msg) +{ + + if (msg->rtm_table != RT_TABLE_MAIN) + return FALSE; + + if (msg->rtm_protocol != RTPROT_BOOT && + msg->rtm_protocol != RTPROT_KERNEL) + return FALSE; + + if (msg->rtm_type != RTN_UNICAST) + return FALSE; + + return TRUE; +} + +static void rtnl_newroute(struct nlmsghdr *hdr) +{ + struct rtmsg *msg = (struct rtmsg *) NLMSG_DATA(hdr); + + rtnl_route(hdr); + + if (is_route_rtmsg(msg)) + process_newroute(msg->rtm_family, msg->rtm_scope, + msg, RTM_PAYLOAD(hdr)); +} + +static void rtnl_delroute(struct nlmsghdr *hdr) +{ + struct rtmsg *msg = (struct rtmsg *) NLMSG_DATA(hdr); + + rtnl_route(hdr); + + if (is_route_rtmsg(msg)) + process_delroute(msg->rtm_family, msg->rtm_scope, + msg, RTM_PAYLOAD(hdr)); +} + +static const char *type2string(uint16_t type) +{ + switch (type) { + case NLMSG_NOOP: + return "NOOP"; + case NLMSG_ERROR: + return "ERROR"; + case NLMSG_DONE: + return "DONE"; + case NLMSG_OVERRUN: + return "OVERRUN"; + case RTM_GETLINK: + return "GETLINK"; + case RTM_NEWLINK: + return "NEWLINK"; + case RTM_DELLINK: + return "DELLINK"; + case RTM_NEWADDR: + return "NEWADDR"; + case RTM_DELADDR: + return "DELADDR"; + case RTM_GETROUTE: + return "GETROUTE"; + case RTM_NEWROUTE: + return "NEWROUTE"; + case RTM_DELROUTE: + return "DELROUTE"; + case RTM_NEWNDUSEROPT: + return "NEWNDUSEROPT"; + default: + return "UNKNOWN"; + } +} + +static GIOChannel *channel = NULL; + +struct rtnl_request { + struct nlmsghdr hdr; + struct rtgenmsg msg; +}; +#define RTNL_REQUEST_SIZE (sizeof(struct nlmsghdr) + sizeof(struct rtgenmsg)) + +static GSList *request_list = NULL; +static guint32 request_seq = 0; + +static struct rtnl_request *find_request(guint32 seq) +{ + GSList *list; + + for (list = request_list; list; list = list->next) { + struct rtnl_request *req = list->data; + + if (req->hdr.nlmsg_seq == seq) + return req; + } + + return NULL; +} + +static int send_request(struct rtnl_request *req) +{ + struct sockaddr_nl addr; + int sk; + + debug("%s len %d type %d flags 0x%04x seq %d", + type2string(req->hdr.nlmsg_type), + req->hdr.nlmsg_len, req->hdr.nlmsg_type, + req->hdr.nlmsg_flags, req->hdr.nlmsg_seq); + + sk = g_io_channel_unix_get_fd(channel); + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + return sendto(sk, req, req->hdr.nlmsg_len, 0, + (struct sockaddr *) &addr, sizeof(addr)); +} + +static int queue_request(struct rtnl_request *req) +{ + request_list = g_slist_append(request_list, req); + + if (g_slist_length(request_list) > 1) + return 0; + + return send_request(req); +} + +static int process_response(guint32 seq) +{ + struct rtnl_request *req; + + debug("seq %d", seq); + + req = find_request(seq); + if (req != NULL) { + request_list = g_slist_remove(request_list, req); + g_free(req); + } + + req = g_slist_nth_data(request_list, 0); + if (req == NULL) + return 0; + + return send_request(req); +} + +static void rtnl_message(void *buf, size_t len) +{ + debug("buf %p len %zd", buf, len); + + while (len > 0) { + struct nlmsghdr *hdr = buf; + struct nlmsgerr *err; + + if (!NLMSG_OK(hdr, len)) + break; + + debug("%s len %d type %d flags 0x%04x seq %d pid %d", + type2string(hdr->nlmsg_type), + hdr->nlmsg_len, hdr->nlmsg_type, + hdr->nlmsg_flags, hdr->nlmsg_seq, + hdr->nlmsg_pid); + + switch (hdr->nlmsg_type) { + case NLMSG_NOOP: + case NLMSG_OVERRUN: + return; + case NLMSG_DONE: + process_response(hdr->nlmsg_seq); + return; + case NLMSG_ERROR: + err = NLMSG_DATA(hdr); + DBG("error %d (%s)", -err->error, + strerror(-err->error)); + return; + case RTM_NEWLINK: + rtnl_newlink(hdr); + break; + case RTM_DELLINK: + rtnl_dellink(hdr); + break; + case RTM_NEWADDR: + break; + case RTM_DELADDR: + break; + case RTM_NEWROUTE: + rtnl_newroute(hdr); + break; + case RTM_DELROUTE: + rtnl_delroute(hdr); + break; + } + + len -= hdr->nlmsg_len; + buf += hdr->nlmsg_len; + } +} + +static gboolean netlink_event(GIOChannel *chan, + GIOCondition cond, gpointer data) +{ + unsigned char buf[4096]; + struct sockaddr_nl nladdr; + socklen_t addr_len = sizeof(nladdr); + ssize_t status; + int fd; + + if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) + return FALSE; + + memset(buf, 0, sizeof(buf)); + memset(&nladdr, 0, sizeof(nladdr)); + + fd = g_io_channel_unix_get_fd(chan); + + status = recvfrom(fd, buf, sizeof(buf), 0, + (struct sockaddr *) &nladdr, &addr_len); + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + return TRUE; + + return FALSE; + } + + if (status == 0) + return FALSE; + + if (nladdr.nl_pid != 0) { /* not sent by kernel, ignore */ + DBG("Received msg from %u, ignoring it", nladdr.nl_pid); + return TRUE; + } + + rtnl_message(buf, status); + + return TRUE; +} + +static int send_getlink(void) +{ + struct rtnl_request *req; + + debug(""); + + req = g_try_malloc0(RTNL_REQUEST_SIZE); + if (req == NULL) + return -ENOMEM; + + req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; + req->hdr.nlmsg_type = RTM_GETLINK; + req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req->hdr.nlmsg_pid = 0; + req->hdr.nlmsg_seq = request_seq++; + req->msg.rtgen_family = AF_INET; + + return queue_request(req); +} + +static int send_getaddr(void) +{ + struct rtnl_request *req; + + debug(""); + + req = g_try_malloc0(RTNL_REQUEST_SIZE); + if (req == NULL) + return -ENOMEM; + + req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; + req->hdr.nlmsg_type = RTM_GETADDR; + req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req->hdr.nlmsg_pid = 0; + req->hdr.nlmsg_seq = request_seq++; + req->msg.rtgen_family = AF_INET; + + return queue_request(req); +} + +static int send_getroute(void) +{ + struct rtnl_request *req; + + debug(""); + + req = g_try_malloc0(RTNL_REQUEST_SIZE); + if (req == NULL) + return -ENOMEM; + + req->hdr.nlmsg_len = RTNL_REQUEST_SIZE; + req->hdr.nlmsg_type = RTM_GETROUTE; + req->hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req->hdr.nlmsg_pid = 0; + req->hdr.nlmsg_seq = request_seq++; + req->msg.rtgen_family = AF_INET; + + return queue_request(req); +} + +static gboolean update_timeout_cb(gpointer user_data) +{ + __vpn_rtnl_request_update(); + + return TRUE; +} + +static void update_interval_callback(guint min) +{ + if (update_timeout > 0) + g_source_remove(update_timeout); + + if (min < G_MAXUINT) { + update_interval = min; + update_timeout = g_timeout_add_seconds(update_interval, + update_timeout_cb, NULL); + } else { + update_timeout = 0; + update_interval = G_MAXUINT; + } +} + +static gint compare_interval(gconstpointer a, gconstpointer b) +{ + guint val_a = GPOINTER_TO_UINT(a); + guint val_b = GPOINTER_TO_UINT(b); + + return val_a - val_b; +} + +unsigned int __vpn_rtnl_update_interval_add(unsigned int interval) +{ + guint min; + + if (interval == 0) + return 0; + + update_list = g_slist_insert_sorted(update_list, + GUINT_TO_POINTER(interval), compare_interval); + + min = GPOINTER_TO_UINT(g_slist_nth_data(update_list, 0)); + if (min < update_interval) { + update_interval_callback(min); + __vpn_rtnl_request_update(); + } + + return update_interval; +} + +unsigned int __vpn_rtnl_update_interval_remove(unsigned int interval) +{ + guint min = G_MAXUINT; + + if (interval == 0) + return 0; + + update_list = g_slist_remove(update_list, GINT_TO_POINTER(interval)); + + if (update_list != NULL) + min = GPOINTER_TO_UINT(g_slist_nth_data(update_list, 0)); + + if (min > update_interval) + update_interval_callback(min); + + return min; +} + +int __vpn_rtnl_request_update(void) +{ + return send_getlink(); +} + +int __vpn_rtnl_init(void) +{ + struct sockaddr_nl addr; + int sk; + + DBG(""); + + interface_list = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, free_interface); + + sk = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE); + if (sk < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE | + RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE | + (1<<(RTNLGRP_ND_USEROPT-1)); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return -1; + } + + channel = g_io_channel_unix_new(sk); + g_io_channel_set_close_on_unref(channel, TRUE); + + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, + netlink_event, NULL); + + return 0; +} + +void __vpn_rtnl_start(void) +{ + DBG(""); + + send_getlink(); + send_getaddr(); + send_getroute(); +} + +void __vpn_rtnl_cleanup(void) +{ + GSList *list; + + DBG(""); + + for (list = watch_list; list; list = list->next) { + struct watch_data *watch = list->data; + + DBG("removing watch %d", watch->id); + + g_free(watch); + list->data = NULL; + } + + g_slist_free(watch_list); + watch_list = NULL; + + g_slist_free(update_list); + update_list = NULL; + + for (list = request_list; list; list = list->next) { + struct rtnl_request *req = list->data; + + debug("%s len %d type %d flags 0x%04x seq %d", + type2string(req->hdr.nlmsg_type), + req->hdr.nlmsg_len, req->hdr.nlmsg_type, + req->hdr.nlmsg_flags, req->hdr.nlmsg_seq); + + g_free(req); + list->data = NULL; + } + + g_slist_free(request_list); + request_list = NULL; + + g_io_channel_shutdown(channel, TRUE, NULL); + g_io_channel_unref(channel); + + channel = NULL; + + g_hash_table_destroy(interface_list); +} diff --git a/vpn/vpn-rtnl.h b/vpn/vpn-rtnl.h new file mode 100644 index 00000000..aa640a66 --- /dev/null +++ b/vpn/vpn-rtnl.h @@ -0,0 +1,65 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2012 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 + * + */ + +#ifndef __VPN_RTNL_H +#define __VPN_RTNL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * SECTION:rtnl + * @title: RTNL premitives + * @short_description: Functions for registering RTNL modules + */ + +typedef void (* vpn_rtnl_link_cb_t) (unsigned flags, unsigned change, + void *user_data); + +unsigned int vpn_rtnl_add_newlink_watch(int index, + vpn_rtnl_link_cb_t callback, void *user_data); + +void vpn_rtnl_remove_watch(unsigned int id); + +#define VPN_RTNL_PRIORITY_LOW -100 +#define VPN_RTNL_PRIORITY_DEFAULT 0 +#define VPN_RTNL_PRIORITY_HIGH 100 + +struct vpn_rtnl { + const char *name; + int priority; + void (*newlink) (unsigned short type, int index, + unsigned flags, unsigned change); + void (*dellink) (unsigned short type, int index, + unsigned flags, unsigned change); + void (*newgateway) (int index, const char *gateway); + void (*delgateway) (int index, const char *gateway); +}; + +int vpn_rtnl_register(struct vpn_rtnl *rtnl); +void vpn_rtnl_unregister(struct vpn_rtnl *rtnl); + +#ifdef __cplusplus +} +#endif + +#endif /* __VPN_RTNL_H */ diff --git a/vpn/vpn.h b/vpn/vpn.h new file mode 100644 index 00000000..10672e02 --- /dev/null +++ b/vpn/vpn.h @@ -0,0 +1,98 @@ +/* + * + * ConnMan VPN daemon + * + * Copyright (C) 2012 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 + * + */ + +#include <glib.h> + +#define VPN_API_SUBJECT_TO_CHANGE + +#include <connman/dbus.h> +#include <connman/types.h> + +int __vpn_manager_init(void); +void __vpn_manager_cleanup(void); + +struct vpn_ipconfig; + +unsigned char __vpn_ipconfig_netmask_prefix_len(const char *netmask); +unsigned short __vpn_ipconfig_get_type_from_index(int index); +unsigned int __vpn_ipconfig_get_flags_from_index(int index); +void __vpn_ipconfig_foreach(void (*function) (int index, + void *user_data), void *user_data); +void __vpn_ipconfig_set_local(struct vpn_ipconfig *ipconfig, + const char *address); +const char *__vpn_ipconfig_get_local(struct vpn_ipconfig *ipconfig); +void __vpn_ipconfig_set_peer(struct vpn_ipconfig *ipconfig, + const char *address); +const char *__vpn_ipconfig_get_peer(struct vpn_ipconfig *ipconfig); +void __vpn_ipconfig_set_broadcast(struct vpn_ipconfig *ipconfig, + const char *broadcast); +void __vpn_ipconfig_set_gateway(struct vpn_ipconfig *ipconfig, + const char *gateway); +const char *__vpn_ipconfig_get_gateway(struct vpn_ipconfig *ipconfig); +void __vpn_ipconfig_set_prefixlen(struct vpn_ipconfig *ipconfig, + unsigned char prefixlen); +unsigned char __vpn_ipconfig_get_prefixlen(struct vpn_ipconfig *ipconfig); +int __vpn_ipconfig_address_add(struct vpn_ipconfig *ipconfig, int family); +int __vpn_ipconfig_gateway_add(struct vpn_ipconfig *ipconfig, int family); +struct vpn_ipconfig *__vpn_ipconfig_create(int index, int family); +void __vpn_ipconfig_set_index(struct vpn_ipconfig *ipconfig, + int index); +struct rtnl_link_stats; + +void __vpn_ipconfig_newlink(int index, unsigned short type, + unsigned int flags, const char *address, + unsigned short mtu, + struct rtnl_link_stats *stats); +void __vpn_ipconfig_dellink(int index, struct rtnl_link_stats *stats); +int __vpn_ipconfig_init(void); +void __vpn_ipconfig_cleanup(void); + +#include "vpn-provider.h" + +connman_bool_t __vpn_provider_check_routes(struct vpn_provider *provider); +int __vpn_provider_append_user_route(struct vpn_provider *provider, + int family, const char *network, const char *netmask); +void __vpn_provider_append_properties(struct vpn_provider *provider, DBusMessageIter *iter); +void __vpn_provider_list(DBusMessageIter *iter, void *user_data); +int __vpn_provider_create_and_connect(DBusMessage *msg); +DBusMessage *__vpn_provider_get_connections(DBusMessage *msg); +const char * __vpn_provider_get_ident(struct vpn_provider *provider); +int __vpn_provider_indicate_state(struct vpn_provider *provider, + enum vpn_provider_state state); +int __vpn_provider_indicate_error(struct vpn_provider *provider, + enum vpn_provider_error error); +int __vpn_provider_connect(struct vpn_provider *provider); +int __vpn_provider_connect_path(const char *path); +int __vpn_provider_disconnect(struct vpn_provider *provider); +int __vpn_provider_remove(const char *path); +void __vpn_provider_cleanup(void); +int __vpn_provider_init(void); + +#include "vpn-rtnl.h" + +int __vpn_rtnl_init(void); +void __vpn_rtnl_start(void); +void __vpn_rtnl_cleanup(void); + +unsigned int __vpn_rtnl_update_interval_add(unsigned int interval); +unsigned int __vpn_rtnl_update_interval_remove(unsigned int interval); +int __vpn_rtnl_request_update(void); +int __vpn_rtnl_send(const void *buf, size_t len); diff --git a/vpn/vpn.ver b/vpn/vpn.ver new file mode 100644 index 00000000..b8877064 --- /dev/null +++ b/vpn/vpn.ver @@ -0,0 +1,8 @@ +{ + global: + connman_*; + vpn_*; + g_dbus_*; + local: + *; +}; |