X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=vpn%2Fplugins%2Fopenvpn.c;h=8c8d31625f97cae611a8f769c83193f79f197ae5;hb=5479dad2b3b5f342f51ca6e7ec8a5a501820bd55;hp=e226afd164a4932d4b44e580ff52502039ee8c53;hpb=9c99015013fcf0abde65abaf2203dde64c07a962;p=platform%2Fupstream%2Fconnman.git diff --git a/vpn/plugins/openvpn.c b/vpn/plugins/openvpn.c index e226afd..8c8d316 100755 --- 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 @@ -29,6 +30,10 @@ #include #include #include +#include +#include +#include +#include #include @@ -38,8 +43,12 @@ #include #include #include +#include +#include +#include #include "../vpn-provider.h" +#include "../vpn-agent.h" #include "vpn.h" @@ -56,7 +65,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 }, @@ -66,13 +75,63 @@ 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 }, { "OpenVPN.RemoteCertTls", "--remote-cert-tls", 1 }, { "OpenVPN.ConfigFile", "--config", 1 }, + { "OpenVPN.DeviceType", NULL, 1 }, + { "OpenVPN.Verb", "--verb", 1 }, + { "OpenVPN.Ping", "--ping", 1}, + { "OpenVPN.PingExit", "--ping-exit", 1}, + { "OpenVPN.RemapUsr1", "--remap-usr1", 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; @@ -95,8 +154,15 @@ static struct nameserver_entry *ov_append_dns_entries(const char *key, options[2]) { entry = g_try_new(struct nameserver_entry, 1); +#if defined TIZEN_EXT + if (!entry) { + g_strfreev(options); + return NULL; + } +#else if (!entry) return NULL; +#endif entry->nameserver = g_strdup(options[2]); entry->id = atoi(key + 15); /* foreign_option_XXX */ @@ -156,9 +222,10 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) { DBusMessageIter iter, dict; const char *reason, *key, *value; - char *address = NULL, *gateway = NULL, *peer = NULL; + 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); @@ -170,8 +237,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); @@ -192,6 +263,9 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) if (!strcmp(key, "ifconfig_local")) address = g_strdup(value); + if (!strcmp(key, "ifconfig_netmask")) + netmask = g_strdup(value); + if (!strcmp(key, "ifconfig_remote")) peer = g_strdup(value); @@ -218,12 +292,14 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) 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); + connman_ipaddress_set_p2p(ipaddress, true); vpn_provider_set_ipaddress(provider, ipaddress); if (nameserver_list) { @@ -256,8 +332,10 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider) g_free(address); g_free(gateway); g_free(peer); + g_free(netmask); connman_ipaddress_free(ipaddress); + ov_connect_done(data, 0); return VPN_STATE_CONNECT; } @@ -296,31 +374,73 @@ 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 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 void close_management_interface(struct ov_private_data *data) { - const char *option; - int err = 0, fd; + if (data->mgmt_path) { + if (unlink(data->mgmt_path) && errno != ENOENT) + connman_warn("Unable to unlink management socket %s: " + "%d", data->mgmt_path, errno); - option = vpn_provider_get_string(provider, "Host"); - if (!option) { - connman_error("Host not set; cannot enable VPN"); - return -EINVAL; + g_free(data->mgmt_path); + data->mgmt_path = NULL; } - task_append_config_data(provider, task); + if (data->mgmt_timer_id != 0) { + g_source_remove(data->mgmt_timer_id); + data->mgmt_timer_id = 0; + } + + if (data->mgmt_event_id) { + g_source_remove(data->mgmt_event_id); + data->mgmt_event_id = 0; + } + + 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 void ov_died(struct connman_task *task, int exit_code, void *user_data) +{ + struct ov_private_data *data = user_data; + + /* Cancel any pending agent requests */ + connman_agent_cancel(data->provider); + + close_management_interface(data); + + vpn_died(task, exit_code, data->provider); + + free_private_data(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 err = 0; option = vpn_provider_get_string(provider, "OpenVPN.ConfigFile"); if (!option) { @@ -341,6 +461,15 @@ 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"); @@ -361,8 +490,16 @@ 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-type", "tun"); + 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); + } else { + /* + * Default to tun for backwards compatibility. + */ + connman_task_add_argument(task, "--dev-type", "tun"); + } connman_task_add_argument(task, "--persist-tun", NULL); @@ -372,35 +509,706 @@ static int ov_connect(struct vpn_provider *provider, #endif /* - * Disable client restarts because we can't handle this at the - * moment. The problem is that when OpenVPN decides to switch + * Disable client restarts with TCP 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 + * 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"); + * + * 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")) { + option = vpn_provider_get_string(provider, "OpenVPN.PingExit"); + if (!option) + connman_task_add_argument(task, "--ping-restart", "0"); + + connman_task_add_argument(task, "--connect-retry-max", "1"); + /* Apply defaults for --ping and --ping-exit only with UDP protocol. */ + } else { + /* Apply default of 10 second interval for ping if omitted. */ + option = vpn_provider_get_string(provider, "OpenVPN.Ping"); + if (!option) + connman_task_add_argument(task, "--ping", "10"); - fd = fileno(stderr); - err = connman_task_run(task, vpn_died, provider, - NULL, &fd, &fd); + /* Apply default of 60 seconds for ping exit if omitted. */ + option = vpn_provider_get_string(provider, "OpenVPN.PingExit"); + if (!option) + connman_task_add_argument(task, "--ping-exit", "60"); + } + + 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); -done: - if (cb) - cb(provider, user_data, err); + if (vpn_provider_get_authentication_errors(data->provider)) + vpn_agent_append_auth_failure(&dict, data->provider, NULL); - return err; + /* 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; + } + + 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); + + vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT); +} + +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 int ov_route_env_parse(struct vpn_provider *provider, const char *key, + int *family, unsigned long *idx, + enum vpn_provider_route_type *type) +{ + char *end; + const char *start; + + if (g_str_has_prefix(key, "route_network_")) { + start = key + strlen("route_network_"); + *type = VPN_PROVIDER_ROUTE_TYPE_ADDR; + } else if (g_str_has_prefix(key, "route_netmask_")) { + start = key + strlen("route_netmask_"); + *type = VPN_PROVIDER_ROUTE_TYPE_MASK; + } else if (g_str_has_prefix(key, "route_gateway_")) { + start = key + strlen("route_gateway_"); + *type = VPN_PROVIDER_ROUTE_TYPE_GW; + } else + return -EINVAL; + + *family = AF_INET; + *idx = g_ascii_strtoull(start, &end, 10); + + return 0; } 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, }; static int openvpn_init(void)