Imported Upstream version 1.35
[platform/upstream/connman.git] / vpn / plugins / openvpn.c
index dd65415..e339509 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  ConnMan VPN daemon
  *
- *  Copyright (C) 2010  BMW Car IT GmbH. All rights reserved.
+ *  Copyright (C) 2010-2014  BMW Car IT GmbH.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
 #include <config.h>
 #endif
 
+#include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <net/if.h>
+#include <linux/if_tun.h>
 
 #include <glib.h>
 
@@ -69,45 +71,97 @@ struct {
        { "OpenVPN.Auth", "--auth", 1 },
        { "OpenVPN.CompLZO", "--comp-lzo", 0 },
        { "OpenVPN.RemoteCertTls", "--remote-cert-tls", 1 },
+       { "OpenVPN.ConfigFile", "--config", 1 },
+       { "OpenVPN.DeviceType", NULL, 1 },
+       { "OpenVPN.Verb", "--verb", 1 },
 };
 
-static void ov_append_dns_entries(const char *key, const char *value,
-                                       char **dns_entries)
+struct nameserver_entry {
+       int id;
+       char *nameserver;
+};
+
+static struct nameserver_entry *ov_append_dns_entries(const char *key,
+                                               const char *value)
 {
+       struct nameserver_entry *entry = NULL;
        gchar **options;
 
-       if (g_str_has_prefix(key, "foreign_option_") == FALSE)
-               return;
+       if (!g_str_has_prefix(key, "foreign_option_"))
+               return NULL;
 
        options = g_strsplit(value, " ", 3);
-       if (options[0] != NULL &&
+       if (options[0] &&
                !strcmp(options[0], "dhcp-option") &&
-                       options[1] != NULL &&
+                       options[1] &&
                        !strcmp(options[1], "DNS") &&
-                               options[2] != NULL) {
+                               options[2]) {
 
-               if (*dns_entries != NULL) {
-                       char *tmp;
+               entry = g_try_new(struct nameserver_entry, 1);
+               if (!entry)
+                       return NULL;
 
-                       tmp = g_strjoin(" ", *dns_entries,
-                                               options[2], NULL);
-                       g_free(*dns_entries);
-                       *dns_entries = tmp;
-               } else {
-                       *dns_entries = g_strdup(options[2]);
-               }
+               entry->nameserver = g_strdup(options[2]);
+               entry->id = atoi(key + 15); /* foreign_option_XXX */
        }
 
        g_strfreev(options);
+
+       return entry;
+}
+
+static char *ov_get_domain_name(const char *key, const char *value)
+{
+       gchar **options;
+       char *domain = NULL;
+
+       if (!g_str_has_prefix(key, "foreign_option_"))
+               return NULL;
+
+       options = g_strsplit(value, " ", 3);
+       if (options[0] &&
+               !strcmp(options[0], "dhcp-option") &&
+                       options[1] &&
+                       !strcmp(options[1], "DOMAIN") &&
+                               options[2]) {
+
+               domain = g_strdup(options[2]);
+       }
+
+       g_strfreev(options);
+
+       return domain;
+}
+
+static gint cmp_ns(gconstpointer a, gconstpointer b)
+{
+       struct nameserver_entry *entry_a = (struct nameserver_entry *)a;
+       struct nameserver_entry *entry_b = (struct nameserver_entry *)b;
+
+       if (entry_a->id < entry_b->id)
+               return -1;
+
+       if (entry_a->id > entry_b->id)
+               return 1;
+
+       return 0;
+}
+
+static void free_ns_entry(gpointer data)
+{
+       struct nameserver_entry *entry = data;
+
+       g_free(entry->nameserver);
+       g_free(entry);
 }
 
 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;
+       char *address = NULL, *gateway = NULL, *peer = NULL, *netmask = NULL;
        struct connman_ipaddress *ipaddress;
+       GSList *nameserver_list = NULL;
 
        dbus_message_iter_init(msg, &iter);
 
@@ -125,6 +179,7 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider)
        dbus_message_iter_recurse(&iter, &dict);
 
        while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
+               struct nameserver_entry *ns_entry = NULL;
                DBusMessageIter entry;
 
                dbus_message_iter_recurse(&dict, &entry);
@@ -134,49 +189,81 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider)
 
                DBG("%s = %s", key, value);
 
-               if (!strcmp(key, "trusted_ip")) {
-                       vpn_provider_set_string(provider, "Gateway", value);
+               if (!strcmp(key, "trusted_ip"))
                        gateway = g_strdup(value);
-               }
 
-               if (!strcmp(key, "ifconfig_local")) {
-                       vpn_provider_set_string(provider, "Address", value);
+               if (!strcmp(key, "ifconfig_local"))
                        address = g_strdup(value);
-               }
 
-               if (!strcmp(key, "ifconfig_remote")) {
-                       vpn_provider_set_string(provider, "Peer", value);
+               if (!strcmp(key, "ifconfig_netmask"))
+                       netmask = g_strdup(value);
+
+               if (!strcmp(key, "ifconfig_remote"))
                        peer = g_strdup(value);
-               }
 
-               if (g_str_has_prefix(key, "route_") == TRUE)
+               if (g_str_has_prefix(key, "route_"))
                        vpn_provider_append_route(provider, key, value);
 
-               ov_append_dns_entries(key, value, &nameservers);
+               if ((ns_entry = ov_append_dns_entries(key, value)))
+                       nameserver_list = g_slist_prepend(nameserver_list,
+                                                       ns_entry);
+               else {
+                       char *domain = ov_get_domain_name(key, value);
+                       if (domain) {
+                               vpn_provider_set_domain(provider, domain);
+                               g_free(domain);
+                       }
+               }
 
                dbus_message_iter_next(&dict);
        }
 
        ipaddress = connman_ipaddress_alloc(AF_INET);
-       if (ipaddress == NULL) {
-               g_free(nameservers);
+       if (!ipaddress) {
+               g_slist_free_full(nameserver_list, free_ns_entry);
                g_free(address);
                g_free(gateway);
                g_free(peer);
+               g_free(netmask);
 
                return VPN_STATE_FAILURE;
        }
 
-       connman_ipaddress_set_ipv4(ipaddress, address, NULL, gateway);
+       connman_ipaddress_set_ipv4(ipaddress, address, netmask, gateway);
        connman_ipaddress_set_peer(ipaddress, peer);
        vpn_provider_set_ipaddress(provider, ipaddress);
 
-       vpn_provider_set_nameservers(provider, nameservers);
+       if (nameserver_list) {
+               char *nameservers = NULL;
+               GSList *tmp;
+
+               nameserver_list = g_slist_sort(nameserver_list, cmp_ns);
+               for (tmp = nameserver_list; tmp;
+                                               tmp = g_slist_next(tmp)) {
+                       struct nameserver_entry *ns = tmp->data;
+
+                       if (!nameservers) {
+                               nameservers = g_strdup(ns->nameserver);
+                       } else {
+                               char *str;
+                               str = g_strjoin(" ", nameservers,
+                                               ns->nameserver, NULL);
+                               g_free(nameservers);
+                               nameservers = str;
+                       }
+               }
+
+               g_slist_free_full(nameserver_list, free_ns_entry);
+
+               vpn_provider_set_nameservers(provider, nameservers);
+
+               g_free(nameservers);
+       }
 
-       g_free(nameservers);
        g_free(address);
        g_free(gateway);
        g_free(peer);
+       g_free(netmask);
        connman_ipaddress_free(ipaddress);
 
        return VPN_STATE_CONNECT;
@@ -191,7 +278,7 @@ static int ov_save(struct vpn_provider *provider, GKeyFile *keyfile)
                if (strncmp(ov_options[i].cm_opt, "OpenVPN.", 8) == 0) {
                        option = vpn_provider_get_string(provider,
                                                        ov_options[i].cm_opt);
-                       if (option == NULL)
+                       if (!option)
                                continue;
 
                        g_key_file_set_string(keyfile,
@@ -209,12 +296,12 @@ static int task_append_config_data(struct vpn_provider *provider,
        int i;
 
        for (i = 0; i < (int)ARRAY_SIZE(ov_options); i++) {
-               if (ov_options[i].ov_opt == NULL)
+               if (!ov_options[i].ov_opt)
                        continue;
 
                option = vpn_provider_get_string(provider,
                                        ov_options[i].cm_opt);
-               if (option == NULL)
+               if (!option)
                        continue;
 
                if (connman_task_add_argument(task,
@@ -227,30 +314,81 @@ static int task_append_config_data(struct vpn_provider *provider,
        return 0;
 }
 
+static gboolean can_read_data(GIOChannel *chan,
+                                GIOCondition cond, gpointer data)
+{
+       void (*cbf)(const char *format, ...) = data;
+       gchar *str;
+       gsize size;
+
+       if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP))
+               return FALSE;
+
+       g_io_channel_read_line(chan, &str, &size, NULL, NULL);
+       cbf(str);
+       g_free(str);
+
+       return TRUE;
+}
+
+static int setup_log_read(int stdout_fd, int stderr_fd)
+{
+       GIOChannel *chan;
+       int watch;
+
+       chan = g_io_channel_unix_new(stdout_fd);
+       g_io_channel_set_close_on_unref(chan, TRUE);
+       watch = g_io_add_watch(chan, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
+                              can_read_data, connman_debug);
+       g_io_channel_unref(chan);
+
+       if (watch == 0)
+               return -EIO;
+
+       chan = g_io_channel_unix_new(stderr_fd);
+       g_io_channel_set_close_on_unref(chan, TRUE);
+       watch = g_io_add_watch(chan, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
+                              can_read_data, connman_error);
+       g_io_channel_unref(chan);
+
+       return watch == 0? -EIO : 0;
+}
+
 static int ov_connect(struct vpn_provider *provider,
-               struct connman_task *task, const char *if_name)
+                       struct connman_task *task, const char *if_name,
+                       vpn_provider_connect_cb_t cb, const char *dbus_sender,
+                       void *user_data)
 {
        const char *option;
-       int err, fd;
+       int stdout_fd, stderr_fd;
+       int err = 0;
 
        option = vpn_provider_get_string(provider, "Host");
-       if (option == NULL) {
+       if (!option) {
                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);
-       }
+       option = vpn_provider_get_string(provider, "OpenVPN.ConfigFile");
+       if (!option) {
+               /*
+                * Set some default options if user has no config file.
+                */
+               option = vpn_provider_get_string(provider, "OpenVPN.TLSAuth");
+               if (option) {
+                       connman_task_add_argument(task, "--tls-auth", option);
+                       option = vpn_provider_get_string(provider,
+                                                       "OpenVPN.TLSAuthDir");
+                       if (option)
+                               connman_task_add_argument(task, option, NULL);
+               }
 
-       connman_task_add_argument(task, "--syslog", NULL);
+               connman_task_add_argument(task, "--nobind", NULL);
+               connman_task_add_argument(task, "--persist-key", NULL);
+               connman_task_add_argument(task, "--client", NULL);
+       }
 
        connman_task_add_argument(task, "--script-security", "2");
 
@@ -271,11 +409,16 @@ static int ov_connect(struct vpn_provider *provider,
                                        connman_task_get_path(task));
 
        connman_task_add_argument(task, "--dev", if_name);
-       connman_task_add_argument(task, "--dev-type", "tun");
+       option = vpn_provider_get_string(provider, "OpenVPN.DeviceType");
+       if (option) {
+               connman_task_add_argument(task, "--dev-type", option);
+       } else {
+               /*
+                * Default to tun for backwards compatibility.
+                */
+               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);
@@ -291,23 +434,47 @@ static int ov_connect(struct vpn_provider *provider,
         */
        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);
+                       NULL, &stdout_fd, &stderr_fd);
        if (err < 0) {
                connman_error("openvpn failed to start");
-               return -EIO;
+               err = -EIO;
+               goto done;
        }
 
-       return 0;
+       err = setup_log_read(stdout_fd, stderr_fd);
+done:
+       if (cb)
+               cb(provider, user_data, err);
+
+       return err;
+}
+
+static int ov_device_flags(struct vpn_provider *provider)
+{
+       const char *option;
+
+       option = vpn_provider_get_string(provider, "OpenVPN.DeviceType");
+       if (!option) {
+               return IFF_TUN;
+       }
+
+       if (g_str_equal(option, "tap")) {
+               return IFF_TAP;
+       }
+
+       if (!g_str_equal(option, "tun")) {
+               connman_warn("bad OpenVPN.DeviceType value, falling back to tun");
+       }
+
+       return IFF_TUN;
 }
 
 static struct vpn_driver vpn_driver = {
        .notify = ov_notify,
        .connect        = ov_connect,
        .save           = ov_save,
+       .device_flags = ov_device_flags,
 };
 
 static int openvpn_init(void)