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. [PATCH 06/10] vpn-provider: Expose __vpn_provider_get_ident() to plugins
(Jussi Laakkonen)
2. [PATCH 04/10] vpn-agent: Implement VPN agent setting to keep credentials
(Jussi Laakkonen)
3. [PATCH 05/10] doc: Add KeepCredentials documentation to VPN agent API
(Jussi Laakkonen)
4. [PATCH 07/10] vpn-provider: Add function to add errors without state
change
(Jussi Laakkonen)
5. [PATCH 08/10] vpn-provider: Handle ENOENT in connect_cb
(Jussi Laakkonen)
6. [PATCH 09/10] openvpn: Rewrite plugin to support VPN agent and encrypted
private keys
(Jussi Laakkonen)
----------------------------------------------------------------------
Date: Mon, 11 Nov 2019 16:01:50 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH 06/10] vpn-provider: Expose __vpn_provider_get_ident()
to plugins
To: [email protected]
Message-ID: <[email protected]>
It is wrong to use local functions in a plugin. This commit exposes
__vpn_provider_get_ident() for plugins to use.
Changed the openvpn.c to use the exposed vpn_provider_get_ident().
---
vpn/vpn-provider.c | 2 +-
vpn/vpn.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/vpn/vpn-provider.c b/vpn/vpn-provider.c
index 08792ecc..f29cbba0 100644
--- a/vpn/vpn-provider.c
+++ b/vpn/vpn-provider.c
@@ -2257,7 +2257,7 @@ DBusMessage *__vpn_provider_get_connections(DBusMessage
*msg)
return reply;
}
-const char *__vpn_provider_get_ident(struct vpn_provider *provider)
+const char *vpn_provider_get_ident(struct vpn_provider *provider)
{
if (!provider)
return NULL;
diff --git a/vpn/vpn.h b/vpn/vpn.h
index 126f5e10..45cf46dc 100644
--- a/vpn/vpn.h
+++ b/vpn/vpn.h
@@ -85,7 +85,7 @@ int __vpn_provider_create_from_config(GHashTable *settings,
int __vpn_provider_set_string_immutable(struct vpn_provider *provider,
const char *key, const char *value);
DBusMessage *__vpn_provider_get_connections(DBusMessage *msg);
-const char * __vpn_provider_get_ident(struct vpn_provider *provider);
+const char *vpn_provider_get_ident(struct vpn_provider *provider);
struct vpn_provider *__vpn_provider_lookup(const char *identifier);
int __vpn_provider_indicate_state(struct vpn_provider *provider,
enum vpn_provider_state state);
--
2.20.1
------------------------------
Date: Mon, 11 Nov 2019 16:01:48 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH 04/10] vpn-agent: Implement VPN agent setting to keep
credentials
To: [email protected]
Message-ID: <[email protected]>
Implement a setting "KeepCredentials" into vpn-agent.c. This boolean
value can be used the registered VPN agent to detect if the request
itself has storage and retrieval forbidden but still does not wish to
remove the credentials.
A example case of this is the OpenVPN Encrypted Private Key password
that is to be kept in memory only. But by disabling storage and
retrieval for the Private Key password request should propagate into
clearing the credentials for the connection.
---
vpn/vpn-agent.c | 7 +++++++
vpn/vpn-agent.h | 1 +
2 files changed, 8 insertions(+)
diff --git a/vpn/vpn-agent.c b/vpn/vpn-agent.c
index dbed17f5..ab6fea55 100644
--- a/vpn/vpn-agent.c
+++ b/vpn/vpn-agent.c
@@ -184,6 +184,13 @@ void
vpn_agent_append_allow_credential_retrieval(DBusMessageIter *iter,
GINT_TO_POINTER(allow));
}
+void vpn_agent_append_keep_credentials(DBusMessageIter *iter, bool allow)
+{
+ connman_dbus_dict_append_dict(iter, "KeepCredentials",
+ request_input_append_flag,
+ GINT_TO_POINTER(allow));
+}
+
struct failure_data {
struct vpn_provider *provider;
const char* type_str;
diff --git a/vpn/vpn-agent.h b/vpn/vpn-agent.h
index 89f4e81f..dc797665 100644
--- a/vpn/vpn-agent.h
+++ b/vpn/vpn-agent.h
@@ -42,6 +42,7 @@ void
vpn_agent_append_allow_credential_storage(DBusMessageIter *iter,
bool allow);
void vpn_agent_append_allow_credential_retrieval(DBusMessageIter *iter,
bool allow);
+void vpn_agent_append_keep_credentials(DBusMessageIter *iter, bool allow);
void vpn_agent_append_auth_failure(DBusMessageIter *iter,
struct vpn_provider *provider,
const char *information);
--
2.20.1
------------------------------
Date: Mon, 11 Nov 2019 16:01:49 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH 05/10] doc: Add KeepCredentials documentation to VPN
agent API
To: [email protected]
Message-ID: <[email protected]>
Describe use of KeepCredentials in VPN agent API documentation.
---
doc/vpn-agent-api.txt | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/doc/vpn-agent-api.txt b/doc/vpn-agent-api.txt
index 6b74cf83..318bfc18 100644
--- a/doc/vpn-agent-api.txt
+++ b/doc/vpn-agent-api.txt
@@ -123,6 +123,20 @@ Fields string Username
to "control". "Value" should be set to true if
previously stored values can be used, false otherwise.
+ boolean KeepCredentials
+
+ Indicates to the receiving UI whether to keep ("Value"
+ is set "true") or clear ("Value" is set "false") the
+ credentials or not. "Requirement" should be set to
+ "control". By default this is not required to be set
+ and is handled only when explicitely defined as "true".
+ This is useful in case of having both the
+ AllowStoreCredentials and the AllowRetrieveCredentials
+ set as "false", but clearing credentials is not
+ required. In such case the value can be explicitly set
+ to "true". An example case is when the password for
+ encrypted Private Key is requested.
+
string VpnAgent.AuthFailure
Informational field that can be used to indicate VPN
--
2.20.1
------------------------------
Date: Mon, 11 Nov 2019 16:01:51 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH 07/10] vpn-provider: Add function to add errors
without state change
To: [email protected]
Message-ID: <[email protected]>
A function to increase error counters without changing the state to
failure, as vpn_provider_indicate_error() does, is required by VPNs that
do handle the credential requests within their own process. If the
authentication error is added with vpn_provider_indicate_error() then
the state would be changed and change is signaled, creating confusion
between connmand and connman-vpnd.
---
vpn/vpn-provider.c | 21 +++++++++++++--------
vpn/vpn-provider.h | 2 ++
2 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/vpn/vpn-provider.c b/vpn/vpn-provider.c
index f29cbba0..06e4c3af 100644
--- a/vpn/vpn-provider.c
+++ b/vpn/vpn-provider.c
@@ -1661,26 +1661,31 @@ int vpn_provider_set_state(struct vpn_provider
*provider,
return -EINVAL;
}
-int vpn_provider_indicate_error(struct vpn_provider *provider,
- enum vpn_provider_error error)
+void vpn_provider_add_error(struct vpn_provider *provider,
+ enum vpn_provider_error error)
{
- DBG("provider %p id %s error %d", provider, provider->identifier,
- error);
-
- vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
-
switch (error) {
case VPN_PROVIDER_ERROR_UNKNOWN:
break;
case VPN_PROVIDER_ERROR_CONNECT_FAILED:
++provider->conn_error_counter;
break;
-
case VPN_PROVIDER_ERROR_LOGIN_FAILED:
case VPN_PROVIDER_ERROR_AUTH_FAILED:
++provider->auth_error_counter;
break;
}
+}
+
+int vpn_provider_indicate_error(struct vpn_provider *provider,
+ enum vpn_provider_error error)
+{
+ DBG("provider %p id %s error %d", provider, provider->identifier,
+ error);
+
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
+
+ vpn_provider_add_error(provider, error);
if (provider->driver && provider->driver->set_state)
provider->driver->set_state(provider, provider->state);
diff --git a/vpn/vpn-provider.h b/vpn/vpn-provider.h
index 36e16c35..af8c9778 100644
--- a/vpn/vpn-provider.h
+++ b/vpn/vpn-provider.h
@@ -94,6 +94,8 @@ bool vpn_provider_get_boolean(struct vpn_provider *provider,
const char *key,
int vpn_provider_set_state(struct vpn_provider *provider,
enum vpn_provider_state state);
+void vpn_provider_add_error(struct vpn_provider *provider,
+ enum vpn_provider_error error);
int vpn_provider_indicate_error(struct vpn_provider *provider,
enum vpn_provider_error error);
--
2.20.1
------------------------------
Date: Mon, 11 Nov 2019 16:01:52 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH 08/10] vpn-provider: Handle ENOENT in connect_cb
To: [email protected]
Message-ID: <[email protected]>
ENOENT error is not an error to be reacted on, it is received from VPNs
that are disconnected by connmand while waiting for VPN agent message,
which in that case returns no reply message.
---
vpn/vpn-provider.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/vpn/vpn-provider.c b/vpn/vpn-provider.c
index 06e4c3af..ad26c670 100644
--- a/vpn/vpn-provider.c
+++ b/vpn/vpn-provider.c
@@ -1107,6 +1107,12 @@ static void connect_cb(struct vpn_provider *provider,
void *user_data,
vpn_provider_indicate_error(provider,
VPN_PROVIDER_ERROR_AUTH_FAILED);
break;
+ case ENOENT:
+ /*
+ * No reply, disconnect called by connmand because of
+ * connection timeout.
+ */
+ break;
case ENOMSG:
/* fall through */
case ETIMEDOUT:
--
2.20.1
------------------------------
Date: Mon, 11 Nov 2019 16:01:53 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH 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 via management interface and following patch is
required in order for the failures to be reported:
https://sourceforge.net/p/openvpn/mailman/message/36789543/
---
vpn/plugins/openvpn.c | 764 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 714 insertions(+), 50 deletions(-)
diff --git a/vpn/plugins/openvpn.c b/vpn/plugins/openvpn.c
index 8e6421e0..8ca356cc 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,604 @@ 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, '"');
+
+ return;
+}
+
+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);
+ }
+
+ 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;
+ gboolean close = FALSE;
+
+ if ((condition & G_IO_IN) &&
+ g_io_channel_read_line(source, &str, NULL, NULL, NULL) ==
+ G_IO_STATUS_NORMAL) {
+ 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 a patch
+ * sent to upstream in order to work:
+ * https://sourceforge.net/p/openvpn/mailman/message/36789543/
+ */
+ } 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;
}
- err = setup_log_read(stdout_fd, stderr_fd);
-done:
- if (cb)
- cb(provider, user_data, err);
+ 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)
+{
+ 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.
+ */
+
+ /* Set up the path for the management interface */
+ data->mgmt_path = g_strconcat("/tmp/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 +1120,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 +1155,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 12
***************************************