Send connman mailing list submissions to
        [email protected]

To subscribe or unsubscribe via email, send a message with subject or
body 'help' to
        [email protected]

You can reach the person managing the list at
        [email protected]

When replying, please edit your Subject line so it is more specific
than "Re: Contents of connman digest..."

Today's Topics:

   1. Re: [PATCH v2 09/10] openvpn: Rewrite plugin to support VPN agent and 
encrypted private keys
      (Daniel Wagner)
   2. Re: [PATCH 0/5] Track VPN connection requests and reply only once to one
      (Daniel Wagner)
   3. Re: [PATCH 5/5] service: Clear VPN error and state if AutoConnect is set 
on
      (Daniel Wagner)
   4. Re: [PATCH v2 09/10] openvpn: Rewrite plugin to support VPN agent and 
encrypted private keys
      ([email protected])
   5. [PATCH v3 09/10] openvpn: Rewrite plugin to support VPN agent and 
encrypted private keys
      (Jussi Laakkonen)


----------------------------------------------------------------------

Date: Wed, 13 Nov 2019 10:18:46 +0100
From: Daniel Wagner <[email protected]>
Subject: Re: [PATCH v2 09/10] openvpn: Rewrite plugin to support VPN
        agent and encrypted private keys
To: Jussi Laakkonen <[email protected]>
Cc: [email protected], Matt Vogt <[email protected]>
Message-ID: <[email protected]>
Content-Type: text/plain; charset=us-ascii

On Tue, Nov 12, 2019 at 02:31:45PM +0200, Jussi Laakkonen wrote:
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -206,6 +206,7 @@ CLEANFILES = src/connman.conf $(BUILT_SOURCES) 
> $(service_files)
>  
>  statedir = $(runstatedir)/connman
>  vpn_statedir = $(runstatedir)/connman-vpn
> +tmpdir = "/tmp"

This is a fancy way to hard code it. I tried to read it up on this
topic but the autotools manual don't tell a lot. I assume the correct
way is just to honor the environment variable TMPDIR if it exists. If
not just use the fallback /tmp.

BTW, I'll give this a spin today. Haven't found the time yet.

Thanks,
Daniel

------------------------------

Date: Wed, 13 Nov 2019 10:29:04 +0100
From: Daniel Wagner <[email protected]>
Subject: Re: [PATCH 0/5] Track VPN connection requests and reply only
        once to one
To: Jussi Laakkonen <[email protected]>
Cc: [email protected]
Message-ID: <[email protected]>
Content-Type: text/plain; charset=us-ascii

Hi Jussi,

On Tue, Nov 12, 2019 at 04:00:50PM +0200, Jussi Laakkonen wrote:
> This set of patches addresses the issue of replying a VPN connection
> request twice. Also the VPN connection requests are limited to one per
> VPN. Connecting a previously failed VPN is also improved.
> 
> The plugins/vpn.c is enhanced to keep track of connection requests. If
> there is a pending call to connect the VPN report -EALREADY and handle
> that in provider.c. This must not create a state change indication but
> only inform that the connection is still in progress.
> 
> Each connection request must be replied only once and vpn-provider.c:
> do_connect() must not reply to the call immediately if -EINPROGRESS is
> returned. This is because vpn-provider.c:connect_cb() will send an
> appropriate reply later using the VPN plugin return code. If the VPN
> is in failure state or is disconnecting a direct reply must be sent as
> VPN plugin connect() is not called, thus connect_cb() is not reached.
> 
> Also, when a VPN settings are saved the previously set failure state
> must be changed to idle. This then follows same approach as it is used
> with error counters (reset when settings saved).
> 
> The error and state must be also reset, when user changes autoconnect
> value for the VPN to true value. This is similar to when connecting a
> VPN using D-Bus method Connect(). Without changing the state autoconnect
> for the VPN (service.c:do_auto_connect()) does not work as with
> CONNMAN_SERVICE_CONNET_REASON_AUTO a VPN in failure state should not
> be connected.

I've applied patches 1-4. I reply on 5.

Thanks,
Daniel

------------------------------

Date: Wed, 13 Nov 2019 10:30:36 +0100
From: Daniel Wagner <[email protected]>
Subject: Re: [PATCH 5/5] service: Clear VPN error and state if
        AutoConnect is set on
To: Jussi Laakkonen <[email protected]>
Cc: [email protected]
Message-ID: <[email protected]>
Content-Type: text/plain; charset=us-ascii

Hi,

On Tue, Nov 12, 2019 at 04:00:55PM +0200, Jussi Laakkonen wrote:
> Implemented function to disable autoconnect from all but the selected
> service. This is required with VPNs as only one VPN should be
> automatically connected, hence each transport service can support one
> VPN only.

This makes sense if only a full VPN tunnel is configured. But what
about the case you have two splittet tunnels? Is this a realistic
scenario?

Thanks,
Daniel

------------------------------

Date: Wed, 13 Nov 2019 10:36:03 -0000
From: [email protected]
Subject: Re: [PATCH v2 09/10] openvpn: Rewrite plugin to support VPN
        agent and encrypted private keys
To: [email protected]
Message-ID: <[email protected]>
Content-Type: text/plain; charset="utf-8"

> This is a fancy way to hard code it. I tried to read it up on this
> topic but the autotools manual don't tell a lot. I assume the correct
> way is just to honor the environment variable TMPDIR if it exists. If
> not just use the fallback /tmp.
> 

On an afterthought, maybe this /tmp use should be handled differently.
Forcing to use a build-time directory on run-time may create some
trouble if the build system is different from the system vpnd is ran on.

Maybe it is enough to do the check within the plugin, and not to set that
during build.

Cheers,
 Jussi

------------------------------

Date: Wed, 13 Nov 2019 12:57:49 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH v3 09/10] openvpn: Rewrite plugin to support VPN agent
        and encrypted private keys
To: [email protected]
Cc: Matt Vogt <[email protected]>
Message-ID: <[email protected]>

Co-authored-by: Matt Vogt <[email protected]>
Co-authored-by: Slava Monich <[email protected]>

This OpenVPN plugin rewrite contains numerous amount of fixes. Most
importantly VPN agent is used to query credentials as well as the
password for the encrypted private key.

VPN agent support is done utilizing the management interface of OpenVPN.
The management interface is opened at each connection attempt to get
the potential requests for credentials, or encrypted private key
password. OpenVPN process is started with the stored information and if
there is some credential missing it will be queried via management
interface.

Each credential failure increases the authentication failed error
counter in vpn-provider.c but does not indicate it as an error to be
signaled. This is because the authentication failures are handled within
the plugin->openvpn process and the openvpn process does not die in
between. In case the credentials or the private key password is wrong
OpenVPN requests them again via management channel. If the error would
be signaled, connmand would have wrong indication of what is actually
happening and would attempt to disconnect the VPN in question.

The new VPN agent functionality is utilized to advise the VPN agent not
to store the encrypted private key password. Encrypted private key
password is kept in memory only, during the connman-vpnd lifetime. On
some systems VPN agents may store the credentials into files and, thus
it is imperative to not to save the encrypted private key password using
the VPN agent as it is bad practice to have both encrypted file and its
password stored on same storage space. Use of the
vpn_agent_append_keep_credentials() is also needed to indicate VPN agent
that the credentials should not be affected by the request to input
encrypted private key password. It may be that some VPN agents would
react to the storage and retrieval prevention values as the existing
values should be removed.

The private key password errors are not recorded as authentication
errors but are handled internally within the plugin. The rationale is
that since VPN agent is affected by the authentication errors and the
VpnAgent.AuthFailure is sent in such case, and VPN agent is advised not
to store the private key password, handling of the errors related to
private key password should happen within the plugin. If the private key
password stored in memory is wrong, it will be still attempted on first
try but OpenVPN will requests new one via management interface after a
failed attempt. The encrypted private key password failures are not
reported by OpenVPN (at least version <= 2.4.5) via management interface
and following patch is required in order for the failures to be
reported: https://git.sailfishos.org/mer-core/openvpn/blob/
4f4b4af116292a207416c8a990392e35a6fc41af/rpm/privatekey-passphrase-
handling.diff - a note about this is added to README.

Since the management channel unix socket is to be used by both vpnd and
the OpenVPN process the socket is created under system temp (env
TMPDIR). If env TMPDIR is omitted or empty, /tmp is used instead.
---
Changes since v3:
 * Remove use of TMPDIR via makefile(s) and use getenv() in plugin to
   check if it is set. If TMPDIR is omitted or empty use /tmp instead as
   a fallback.

Changes since v2:
 * Remove hardcoded /tmp, add TMPDIR to makefile(s) and use it in plugin.
 * Update the OpenVPN patch link to permanent git repository link.
 * Add note about OpenVPN decryption failure reporting patch to README.
 * Move g_io_channel_read_line() outside condition check.
 * Cleanup code from unnecessary returns and other coding style violations.

 README                |   9 +
 vpn/plugins/openvpn.c | 774 +++++++++++++++++++++++++++++++++++++++---
 2 files changed, 733 insertions(+), 50 deletions(-)

diff --git a/README b/README
index f16b9ec0..d79a0bc7 100644
--- a/README
+++ b/README
@@ -368,6 +368,15 @@ routes will not be set by ConnMan if the uplink is a 
cellular
 network. While the same setup works well for a WiFi or ethernet
 uplink.
 
+Up to (at least) version 2.4.5 of OpenVPN getting information about
+private key decryption failures via management channel is missing. This
+will result in attempting with the invalid key over and over as the
+information about failed decryprion is not delivered to OpenVPN plugin.
+The following patch to OpenVPN is required for the private key
+decryption failures to be sent:
+https://git.sailfishos.org/mer-core/openvpn/blob/
+4f4b4af116292a207416c8a990392e35a6fc41af/rpm/privatekey-passphrase-
+handling.diff
 
 GnuTLS
 ======
diff --git a/vpn/plugins/openvpn.c b/vpn/plugins/openvpn.c
index 8e6421e0..0f15bc37 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 },
@@ -77,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;
@@ -163,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);
 
@@ -174,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);
 
@@ -267,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;
 }
 
@@ -305,67 +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);
 
-       if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP))
-               return FALSE;
+               g_free(data->mgmt_path);
+               data->mgmt_path = NULL;
+       }
 
-       g_io_channel_read_line(chan, &str, &size, NULL, NULL);
-       cbf(str);
-       g_free(str);
+       if (data->mgmt_timer_id != 0) {
+               g_source_remove(data->mgmt_timer_id);
+               data->mgmt_timer_id = 0;
+       }
 
-       return TRUE;
+       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 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;
 
-       task_append_config_data(provider, task);
-
        option = vpn_provider_get_string(provider, "OpenVPN.ConfigFile");
        if (!option) {
                /*
@@ -385,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",
@@ -403,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,20 +506,614 @@ static int ov_connect(struct vpn_provider *provider,
         */
        connman_task_add_argument(task, "--ping-restart", "0");
 
-       err = connman_task_run(task, vpn_died, provider,
-                       NULL, &stdout_fd, &stderr_fd);
+       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, EACCES);
+}
+
+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);
        }
 
-       err = setup_log_read(stdout_fd, stderr_fd);
-done:
-       if (cb)
-               cb(provider, user_data, err);
+       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;
 
-       return err;
+       connman_agent_cancel(provider);
 }
 
 static int ov_device_flags(struct vpn_provider *provider)
@@ -459,14 +1130,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;
@@ -492,6 +1165,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,
-- 
2.20.1

------------------------------

Subject: Digest Footer

_______________________________________________
connman mailing list -- [email protected]
To unsubscribe send an email to [email protected]


------------------------------

End of connman Digest, Vol 49, Issue 18
***************************************

Reply via email to