summaryrefslogtreecommitdiff
path: root/vpn
diff options
context:
space:
mode:
authorJukka Rissanen <jukka.rissanen@linux.intel.com>2012-11-12 14:07:21 +0200
committerPatrik Flykt <patrik.flykt@linux.intel.com>2012-11-23 12:58:50 +0200
commit4ba04eb6172f898402e0aa66f0dc8f564a12279f (patch)
tree7a67e489ea3de6f65e65d6034b2a0951db709cd0 /vpn
parenta426464354273a5586612b6577288e3662e3f8ac (diff)
downloadconnman-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.in12
-rw-r--r--vpn/main.c257
-rw-r--r--vpn/net.connman.vpn.service.in5
-rw-r--r--vpn/plugins/l2tp.c533
-rw-r--r--vpn/plugins/openconnect.c282
-rw-r--r--vpn/plugins/openvpn.c328
-rw-r--r--vpn/plugins/pptp.c339
-rw-r--r--vpn/plugins/vpn.c535
-rw-r--r--vpn/plugins/vpn.h63
-rw-r--r--vpn/plugins/vpnc.c345
-rw-r--r--vpn/vpn-dbus.conf15
-rw-r--r--vpn/vpn-ipconfig.c450
-rw-r--r--vpn/vpn-manager.c142
-rw-r--r--vpn/vpn-polkit.conf11
-rw-r--r--vpn/vpn-polkit.policy29
-rw-r--r--vpn/vpn-provider.c1680
-rw-r--r--vpn/vpn-provider.h121
-rw-r--r--vpn/vpn-rtnl.c1185
-rw-r--r--vpn/vpn-rtnl.h65
-rw-r--r--vpn/vpn.h98
-rw-r--r--vpn/vpn.ver8
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(&eth, RTA_DATA(attr), ETH_ALEN);
+ print(" attr %s (len %d) %s\n", name, len, ether_ntoa(&eth));
+ } 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:
+ *;
+};