*
* 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>
{ "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);
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);
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;
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,
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,
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.ConfigFile");
- if (option == NULL) {
+ if (!option) {
/*
* Set some default options if user has no config file.
*/
option = vpn_provider_get_string(provider, "OpenVPN.TLSAuth");
- if (option != NULL) {
+ if (option) {
connman_task_add_argument(task, "--tls-auth", option);
option = vpn_provider_get_string(provider,
"OpenVPN.TLSAuthDir");
- if (option != NULL)
+ if (option)
connman_task_add_argument(task, option, NULL);
}
connman_task_add_argument(task, "--client", NULL);
}
- connman_task_add_argument(task, "--syslog", NULL);
-
connman_task_add_argument(task, "--script-security", "2");
connman_task_add_argument(task, "--up",
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, "--persist-tun", NULL);
+#if !defined TIZEN_EXT
connman_task_add_argument(task, "--route-noexec", NULL);
connman_task_add_argument(task, "--ifconfig-noexec", NULL);
+#endif
/*
* Disable client restarts because we can't handle this at the
*/
connman_task_add_argument(task, "--ping-restart", "0");
- 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)