diff options
Diffstat (limited to 'vpn/plugins/openvpn.c')
-rw-r--r-- | vpn/plugins/openvpn.c | 798 |
1 files changed, 741 insertions, 57 deletions
diff --git a/vpn/plugins/openvpn.c b/vpn/plugins/openvpn.c index f38c0c36..bc0303c2 100644 --- a/vpn/plugins/openvpn.c +++ b/vpn/plugins/openvpn.c @@ -3,6 +3,7 @@ * ConnMan VPN daemon * * Copyright (C) 2010-2014 BMW Car IT GmbH. + * Copyright (C) 2016-2019 Jolla Ltd. * * 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 @@ -30,6 +31,9 @@ #include <stdio.h> #include <net/if.h> #include <linux/if_tun.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> #include <glib.h> @@ -39,10 +43,15 @@ #include <connman/task.h> #include <connman/dbus.h> #include <connman/ipconfig.h> +#include <connman/agent.h> +#include <connman/setting.h> +#include <connman/vpn-dbus.h> #include "../vpn-provider.h" +#include "../vpn-agent.h" #include "vpn.h" +#include "../vpn.h" #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) @@ -57,7 +66,7 @@ struct { { "OpenVPN.CACert", "--ca", 1 }, { "OpenVPN.Cert", "--cert", 1 }, { "OpenVPN.Key", "--key", 1 }, - { "OpenVPN.MTU", "--mtu", 1 }, + { "OpenVPN.MTU", "--tun-mtu", 1 }, { "OpenVPN.NSCertType", "--ns-cert-type", 1 }, { "OpenVPN.Proto", "--proto", 1 }, { "OpenVPN.Port", "--port", 1 }, @@ -67,6 +76,7 @@ struct { { "OpenVPN.TLSRemote", "--tls-remote", 1 }, { "OpenVPN.TLSAuth", NULL, 1 }, { "OpenVPN.TLSAuthDir", NULL, 1 }, + { "OpenVPN.TLSCipher", "--tls-cipher", 1}, { "OpenVPN.Cipher", "--cipher", 1 }, { "OpenVPN.Auth", "--auth", 1 }, { "OpenVPN.CompLZO", "--comp-lzo", 0 }, @@ -76,6 +86,50 @@ struct { { "OpenVPN.Verb", "--verb", 1 }, }; +struct ov_private_data { + struct vpn_provider *provider; + struct connman_task *task; + char *dbus_sender; + char *if_name; + vpn_provider_connect_cb_t cb; + void *user_data; + char *mgmt_path; + guint mgmt_timer_id; + guint mgmt_event_id; + GIOChannel *mgmt_channel; + int connect_attempts; + int failed_attempts_privatekey; +}; + +static void ov_connect_done(struct ov_private_data *data, int err) +{ + if (data && data->cb) { + vpn_provider_connect_cb_t cb = data->cb; + void *user_data = data->user_data; + + /* Make sure we don't invoke this callback twice */ + data->cb = NULL; + data->user_data = NULL; + cb(data->provider, user_data, err); + } + + if (!err) + data->failed_attempts_privatekey = 0; +} + +static void free_private_data(struct ov_private_data *data) +{ + if (vpn_provider_get_plugin_data(data->provider) == data) + vpn_provider_set_plugin_data(data->provider, NULL); + + ov_connect_done(data, EIO); + vpn_provider_unref(data->provider); + g_free(data->dbus_sender); + g_free(data->if_name); + g_free(data->mgmt_path); + g_free(data); +} + struct nameserver_entry { int id; char *nameserver; @@ -162,6 +216,7 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) char *address = NULL, *gateway = NULL, *peer = NULL, *netmask = NULL; struct connman_ipaddress *ipaddress; GSList *nameserver_list = NULL; + struct ov_private_data *data = vpn_provider_get_plugin_data(provider); dbus_message_iter_init(msg, &iter); @@ -173,8 +228,12 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) return VPN_STATE_FAILURE; } - if (strcmp(reason, "up")) + DBG("%p %s", vpn_provider_get_name(provider), reason); + + if (strcmp(reason, "up")) { + ov_connect_done(data, EIO); return VPN_STATE_DISCONNECT; + } dbus_message_iter_recurse(&iter, &dict); @@ -266,6 +325,7 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) g_free(netmask); connman_ipaddress_free(ipaddress); + ov_connect_done(data, 0); return VPN_STATE_CONNECT; } @@ -304,73 +364,74 @@ static int task_append_config_data(struct vpn_provider *provider, if (!option) continue; + /* + * If the AuthUserPass option is "-", provide the input + * via management interface + */ + if (!strcmp(ov_options[i].cm_opt, "OpenVPN.AuthUserPass") && + !strcmp(option, "-")) + option = NULL; + if (connman_task_add_argument(task, ov_options[i].ov_opt, - ov_options[i].has_value ? option : NULL) < 0) { + ov_options[i].has_value ? option : NULL) < 0) return -EIO; - } + } return 0; } -static gboolean can_read_data(GIOChannel *chan, - GIOCondition cond, gpointer data) +static void close_management_interface(struct ov_private_data *data) { - void (*cbf)(const char *format, ...) = data; - gchar *str; - gsize size; + if (data->mgmt_path) { + if (unlink(data->mgmt_path) && errno != ENOENT) + connman_warn("Unable to unlink management socket %s: " + "%d", data->mgmt_path, errno); + + g_free(data->mgmt_path); + data->mgmt_path = NULL; + } - if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) - return FALSE; + if (data->mgmt_timer_id != 0) { + g_source_remove(data->mgmt_timer_id); + data->mgmt_timer_id = 0; + } - g_io_channel_read_line(chan, &str, &size, NULL, NULL); - cbf(str); - g_free(str); + if (data->mgmt_event_id) { + g_source_remove(data->mgmt_event_id); + data->mgmt_event_id = 0; + } - return TRUE; + if (data->mgmt_channel) { + g_io_channel_shutdown(data->mgmt_channel, FALSE, NULL); + g_io_channel_unref(data->mgmt_channel); + data->mgmt_channel = NULL; + } } -static int setup_log_read(int stdout_fd, int stderr_fd) +static void ov_died(struct connman_task *task, int exit_code, void *user_data) { - GIOChannel *chan; - int watch; + struct ov_private_data *data = user_data; - 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); + /* Cancel any pending agent requests */ + connman_agent_cancel(data->provider); - if (watch == 0) - return -EIO; + close_management_interface(data); - 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); + vpn_died(task, exit_code, data->provider); - return watch == 0? -EIO : 0; + free_private_data(data); } -static int ov_connect(struct vpn_provider *provider, - struct connman_task *task, const char *if_name, - vpn_provider_connect_cb_t cb, const char *dbus_sender, - void *user_data) +static int run_connect(struct ov_private_data *data, + vpn_provider_connect_cb_t cb, void *user_data) { + struct vpn_provider *provider = data->provider; + struct connman_task *task = data->task; const char *option; - int stdout_fd, stderr_fd; int err = 0; - option = vpn_provider_get_string(provider, "Host"); - 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) { /* @@ -390,6 +451,17 @@ static int ov_connect(struct vpn_provider *provider, connman_task_add_argument(task, "--client", NULL); } + if (data->mgmt_path) { + connman_task_add_argument(task, "--management", NULL); + connman_task_add_argument(task, data->mgmt_path, NULL); + connman_task_add_argument(task, "unix", NULL); + connman_task_add_argument(task, "--management-query-passwords", + NULL); + connman_task_add_argument(task, "--auth-retry", "interact"); + } + + connman_task_add_argument(task, "--syslog", NULL); + connman_task_add_argument(task, "--script-security", "2"); connman_task_add_argument(task, "--up", @@ -408,7 +480,7 @@ static int ov_connect(struct vpn_provider *provider, 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", data->if_name); option = vpn_provider_get_string(provider, "OpenVPN.DeviceType"); if (option) { connman_task_add_argument(task, "--dev-type", option); @@ -429,25 +501,634 @@ static int ov_connect(struct vpn_provider *provider, * 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 + * not accessible through the tunnel anymore and so we end up * trying to resolve the OpenVPN servers address. */ connman_task_add_argument(task, "--ping-restart", "0"); - err = connman_task_run(task, vpn_died, provider, - NULL, &stdout_fd, &stderr_fd); + /* + * Disable connetion retrying when OpenVPN is connected over TCP. + * With TCP OpenVPN attempts to handle reconnection silently without + * reporting the error back when establishing a connection or + * reconnecting as succesful one. The latter causes trouble if the + * retries are not limited to 1 (no retry) as the interface is up and + * connman regards it as the default route and network ceases to work, + * including DNS. + */ + option = vpn_provider_get_string(provider, "OpenVPN.Proto"); + if (option && g_str_has_prefix(option, "tcp")) + connman_task_add_argument(task, "--connect-retry-max", "1"); + + err = connman_task_run(task, ov_died, data, NULL, NULL, NULL); if (err < 0) { + data->cb = NULL; + data->user_data = NULL; connman_error("openvpn failed to start"); - err = -EIO; - goto done; + return -EIO; + } else { + /* This lets the caller know that the actual result of + * the operation will be reported to the callback */ + return -EINPROGRESS; + } +} + +static void ov_quote_credential(GString *line, const char *cred) +{ + if (!line) + return; + + g_string_append_c(line, '"'); + + while (*cred != '\0') { + + switch (*cred) { + case ' ': + case '"': + case '\\': + g_string_append_c(line, '\\'); + break; + default: + break; + } + + g_string_append_c(line, *cred++); + } + + g_string_append_c(line, '"'); +} + +static void ov_return_credentials(struct ov_private_data *data, + const char *username, const char *password) +{ + GString *reply_string; + gchar *reply; + gsize len; + + reply_string = g_string_new(NULL); + + g_string_append(reply_string, "username \"Auth\" "); + ov_quote_credential(reply_string, username); + g_string_append_c(reply_string, '\n'); + + g_string_append(reply_string, "password \"Auth\" "); + ov_quote_credential(reply_string, password); + g_string_append_c(reply_string, '\n'); + + len = reply_string->len; + reply = g_string_free(reply_string, FALSE); + + g_io_channel_write_chars(data->mgmt_channel, reply, len, NULL, NULL); + g_io_channel_flush(data->mgmt_channel, NULL); + + memset(reply, 0, len); + g_free(reply); +} + +static void ov_return_private_key_password(struct ov_private_data *data, + const char *privatekeypass) +{ + GString *reply_string; + gchar *reply; + gsize len; + + reply_string = g_string_new(NULL); + + g_string_append(reply_string, "password \"Private Key\" "); + ov_quote_credential(reply_string, privatekeypass); + g_string_append_c(reply_string, '\n'); + + len = reply_string->len; + reply = g_string_free(reply_string, FALSE); + + g_io_channel_write_chars(data->mgmt_channel, reply, len, NULL, NULL); + g_io_channel_flush(data->mgmt_channel, NULL); + + memset(reply, 0, len); + g_free(reply); +} + +static void request_input_append_informational(DBusMessageIter *iter, + void *user_data) +{ + char *str = "string"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "informational"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); +} + +static void request_input_append_mandatory(DBusMessageIter *iter, + void *user_data) +{ + char *str = "string"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "mandatory"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); +} + +static void request_input_append_password(DBusMessageIter *iter, + void *user_data) +{ + char *str = "password"; + + connman_dbus_dict_append_basic(iter, "Type", + DBUS_TYPE_STRING, &str); + str = "mandatory"; + connman_dbus_dict_append_basic(iter, "Requirement", + DBUS_TYPE_STRING, &str); +} + +static void request_input_credentials_reply(DBusMessage *reply, + void *user_data) +{ + struct ov_private_data *data = user_data; + char *username = NULL; + char *password = NULL; + char *key; + DBusMessageIter iter, dict; + DBusError error; + int err = 0; + + connman_info("provider %p", data->provider); + + /* + * When connmand calls disconnect because of connection timeout no + * reply is received. + */ + if (!reply) { + err = ENOENT; + goto err; + } + + dbus_error_init(&error); + + err = vpn_agent_check_and_process_reply_error(reply, data->provider, + data->task, data->cb, data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + return; + } + + if (!vpn_agent_check_reply_has_dict(reply)) { + err = ENOENT; + goto err; + } + + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &dict); + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic(&entry, &key); + + if (g_str_equal(key, "OpenVPN.Password")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &password); + vpn_provider_set_string_hide_value(data->provider, + key, password); + + } else if (g_str_equal(key, "OpenVPN.Username")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &username); + vpn_provider_set_string_hide_value(data->provider, + key, username); + } + + dbus_message_iter_next(&dict); + } + + if (!password || !username) { + vpn_provider_indicate_error(data->provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + err = EACCES; + goto err; + } + + ov_return_credentials(data, username, password); + + return; + +err: + ov_connect_done(data, err); +} + +static int request_credentials_input(struct ov_private_data *data) +{ + DBusMessage *message; + const char *path, *agent_sender, *agent_path; + DBusMessageIter iter; + DBusMessageIter dict; + int err; + void *agent; + + agent = connman_agent_get_info(data->dbus_sender, &agent_sender, + &agent_path); + if (!agent || !agent_path) + return -ESRCH; + + message = dbus_message_new_method_call(agent_sender, agent_path, + VPN_AGENT_INTERFACE, + "RequestInput"); + if (!message) + return -ENOMEM; + + dbus_message_iter_init_append(message, &iter); + + path = vpn_provider_get_path(data->provider); + dbus_message_iter_append_basic(&iter, + DBUS_TYPE_OBJECT_PATH, &path); + + connman_dbus_dict_open(&iter, &dict); + + if (vpn_provider_get_authentication_errors(data->provider)) + vpn_agent_append_auth_failure(&dict, data->provider, NULL); + + /* Request temporary properties to pass on to openvpn */ + connman_dbus_dict_append_dict(&dict, "OpenVPN.Username", + request_input_append_mandatory, NULL); + + connman_dbus_dict_append_dict(&dict, "OpenVPN.Password", + request_input_append_password, NULL); + + vpn_agent_append_host_and_name(&dict, data->provider); + + connman_dbus_dict_close(&iter, &dict); + + err = connman_agent_queue_message(data->provider, message, + connman_timeout_input_request(), + request_input_credentials_reply, data, agent); + + if (err < 0 && err != -EBUSY) { + connman_error("error %d sending agent request", err); + dbus_message_unref(message); + + return err; + } + + dbus_message_unref(message); + + return -EINPROGRESS; +} + +static void request_input_private_key_reply(DBusMessage *reply, + void *user_data) +{ + struct ov_private_data *data = user_data; + const char *privatekeypass = NULL; + const char *key; + DBusMessageIter iter, dict; + DBusError error; + int err = 0; + + connman_info("provider %p", data->provider); + + /* + * When connmand calls disconnect because of connection timeout no + * reply is received. + */ + if (!reply) { + err = ENOENT; + goto err; + } + + dbus_error_init(&error); + + err = vpn_agent_check_and_process_reply_error(reply, data->provider, + data->task, data->cb, data->user_data); + if (err) { + /* Ensure cb is called only once */ + data->cb = NULL; + data->user_data = NULL; + return; + } + + if (!vpn_agent_check_reply_has_dict(reply)) { + err = ENOENT; + goto err; + } + + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &dict); + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic(&entry, &key); + + if (g_str_equal(key, "OpenVPN.PrivateKeyPassword")) { + dbus_message_iter_next(&entry); + if (dbus_message_iter_get_arg_type(&entry) + != DBUS_TYPE_VARIANT) + break; + dbus_message_iter_recurse(&entry, &value); + if (dbus_message_iter_get_arg_type(&value) + != DBUS_TYPE_STRING) + break; + dbus_message_iter_get_basic(&value, &privatekeypass); + vpn_provider_set_string_hide_value(data->provider, + key, privatekeypass); + + } + + dbus_message_iter_next(&dict); + } + + if (!privatekeypass) { + vpn_provider_indicate_error(data->provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + + err = EACCES; + goto err; + } + + ov_return_private_key_password(data, privatekeypass); + + return; + +err: + ov_connect_done(data, err); +} + +static int request_private_key_input(struct ov_private_data *data) +{ + DBusMessage *message; + const char *path, *agent_sender, *agent_path; + const char *privatekeypass; + DBusMessageIter iter; + DBusMessageIter dict; + int err; + void *agent; + + /* + * First check if this is the second attempt to get the key within + * this connection. In such case there has been invalid Private Key + * Password and it must be reset, and queried from user. + */ + if (data->failed_attempts_privatekey) { + vpn_provider_set_string_hide_value(data->provider, + "OpenVPN.PrivateKeyPassword", NULL); + } else { + /* If the encrypted Private key password is kept in memory and + * use it first. If authentication fails this is cleared, + * likewise it is when connman-vpnd is restarted. + */ + privatekeypass = vpn_provider_get_string(data->provider, + "OpenVPN.PrivateKeyPassword"); + if (privatekeypass) { + ov_return_private_key_password(data, privatekeypass); + goto out; + } + } + + agent = connman_agent_get_info(data->dbus_sender, &agent_sender, + &agent_path); + if (!agent || !agent_path) + return -ESRCH; + + message = dbus_message_new_method_call(agent_sender, agent_path, + VPN_AGENT_INTERFACE, + "RequestInput"); + if (!message) + return -ENOMEM; + + dbus_message_iter_init_append(message, &iter); + + path = vpn_provider_get_path(data->provider); + dbus_message_iter_append_basic(&iter, + DBUS_TYPE_OBJECT_PATH, &path); + + connman_dbus_dict_open(&iter, &dict); + + connman_dbus_dict_append_dict(&dict, "OpenVPN.PrivateKeyPassword", + request_input_append_password, NULL); + + vpn_agent_append_host_and_name(&dict, data->provider); + + /* Do not allow to store or retrieve the encrypted Private Key pass */ + vpn_agent_append_allow_credential_storage(&dict, false); + vpn_agent_append_allow_credential_retrieval(&dict, false); + + /* + * Indicate to keep credentials, the enc Private Key password should + * not affect the credential storing. + */ + vpn_agent_append_keep_credentials(&dict, true); + + connman_dbus_dict_append_dict(&dict, "Enter Private Key password", + request_input_append_informational, NULL); + + connman_dbus_dict_close(&iter, &dict); + + err = connman_agent_queue_message(data->provider, message, + connman_timeout_input_request(), + request_input_private_key_reply, data, agent); + + if (err < 0 && err != -EBUSY) { + connman_error("error %d sending agent request", err); + dbus_message_unref(message); + + return err; + } + + dbus_message_unref(message); + +out: + return -EINPROGRESS; +} + +static gboolean ov_management_handle_input(GIOChannel *source, + GIOCondition condition, gpointer user_data) +{ + struct ov_private_data *data = user_data; + char *str = NULL; + int err = 0; + bool close = false; + + if (condition & G_IO_IN) { + /* + * Just return if line is not read and str is not allocated. + * Condition check handles closing of the channel later. + */ + if (g_io_channel_read_line(source, &str, NULL, NULL, NULL) != + G_IO_STATUS_NORMAL) + return true; + + str[strlen(str) - 1] = '\0'; + connman_warn("openvpn request %s", str); + + if (g_str_has_prefix(str, ">PASSWORD:Need 'Auth'")) { + /* + * Request credentials from the user + */ + err = request_credentials_input(data); + if (err != -EINPROGRESS) + close = true; + } else if (g_str_has_prefix(str, + ">PASSWORD:Need 'Private Key'")) { + err = request_private_key_input(data); + if (err != -EINPROGRESS) + close = true; + } else if (g_str_has_prefix(str, + ">PASSWORD:Verification Failed: 'Auth'")) { + /* + * Add error only, state change indication causes + * signal to be sent, which is not desired when + * OpenVPN is in interactive mode. + */ + vpn_provider_add_error(data->provider, + VPN_PROVIDER_ERROR_AUTH_FAILED); + /* + * According to the OpenVPN manual about management interface + * https://openvpn.net/community-resources/management-interface/ + * this should be received but it does not seem to be reported + * when decrypting private key fails. This requires following + * patch for OpenVPN (at least <= 2.4.5) in order to work: + * https://git.sailfishos.org/mer-core/openvpn/blob/ + * 4f4b4af116292a207416c8a990392e35a6fc41af/rpm/privatekey- + * passphrase-handling.diff + */ + } else if (g_str_has_prefix(str, ">PASSWORD:Verification " + "Failed: 'Private Key'")) { + data->failed_attempts_privatekey++; + } + + g_free(str); + } else if (condition & (G_IO_ERR | G_IO_HUP)) { + connman_warn("Management channel termination"); + close = true; } - err = setup_log_read(stdout_fd, stderr_fd); -done: - if (cb) - cb(provider, user_data, err); + if (close) + close_management_interface(data); + + return true; +} + +static int ov_management_connect_timer_cb(gpointer user_data) +{ + struct ov_private_data *data = user_data; + + if (!data->mgmt_channel) { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd >= 0) { + struct sockaddr_un remote; + int err; + + memset(&remote, 0, sizeof(remote)); + remote.sun_family = AF_UNIX; + g_strlcpy(remote.sun_path, data->mgmt_path, + sizeof(remote.sun_path)); + + err = connect(fd, (struct sockaddr *)&remote, + sizeof(remote)); + if (err == 0) { + data->mgmt_channel = g_io_channel_unix_new(fd); + data->mgmt_event_id = + g_io_add_watch(data->mgmt_channel, + G_IO_IN | G_IO_ERR | G_IO_HUP, + ov_management_handle_input, + data); + + connman_warn("Connected management socket"); + data->mgmt_timer_id = 0; + return G_SOURCE_REMOVE; + } + close(fd); + } + } + + data->connect_attempts++; + if (data->connect_attempts > 30) { + connman_warn("Unable to connect management socket"); + data->mgmt_timer_id = 0; + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static int ov_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name, + vpn_provider_connect_cb_t cb, const char *dbus_sender, + void *user_data) +{ + const char *tmpdir; + struct ov_private_data *data; + + data = g_try_new0(struct ov_private_data, 1); + if (!data) + return -ENOMEM; + + vpn_provider_set_plugin_data(provider, data); + data->provider = vpn_provider_ref(provider); + data->task = task; + data->dbus_sender = g_strdup(dbus_sender); + data->if_name = g_strdup(if_name); + data->cb = cb; + data->user_data = user_data; + + /* + * We need to use the management interface to provide + * the user credentials and password for decrypting private key. + */ + + /* Use env TMPDIR for creating management socket, fall back to /tmp */ + tmpdir = getenv("TMPDIR"); + if (!tmpdir || !*tmpdir) + tmpdir = "/tmp"; + + /* Set up the path for the management interface */ + data->mgmt_path = g_strconcat(tmpdir, "/connman-vpn-management-", + vpn_provider_get_ident(provider), NULL); + if (unlink(data->mgmt_path) != 0 && errno != ENOENT) { + connman_warn("Unable to unlink management socket %s: %d", + data->mgmt_path, errno); + } + + data->mgmt_timer_id = g_timeout_add(200, + ov_management_connect_timer_cb, data); + + task_append_config_data(provider, task); + + return run_connect(data, cb, user_data); +} + +static void ov_disconnect(struct vpn_provider *provider) +{ + if (!provider) + return; + + connman_agent_cancel(provider); - return err; + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT); } static int ov_device_flags(struct vpn_provider *provider) @@ -464,14 +1145,16 @@ static int ov_device_flags(struct vpn_provider *provider) } if (!g_str_equal(option, "tun")) { - connman_warn("bad OpenVPN.DeviceType value, falling back to tun"); + connman_warn("bad OpenVPN.DeviceType value " + "falling back to tun"); } return IFF_TUN; } static int ov_route_env_parse(struct vpn_provider *provider, const char *key, - int *family, unsigned long *idx, enum vpn_provider_route_type *type) + int *family, unsigned long *idx, + enum vpn_provider_route_type *type) { char *end; const char *start; @@ -497,6 +1180,7 @@ static int ov_route_env_parse(struct vpn_provider *provider, const char *key, static struct vpn_driver vpn_driver = { .notify = ov_notify, .connect = ov_connect, + .disconnect = ov_disconnect, .save = ov_save, .device_flags = ov_device_flags, .route_env_parse = ov_route_env_parse, |