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 v3 4/4] tools: Remove GDateTime API usage (Daniel Wagner)
2. [PATCH v2 03/10] doc: Document VPN agent credential storage/retrieval
options
(Jussi Laakkonen)
3. [PATCH v2 05/10] doc: Add KeepCredentials documentation to VPN agent API
(Jussi Laakkonen)
4. [PATCH v2 09/10] openvpn: Rewrite plugin to support VPN agent and
encrypted private keys
(Jussi Laakkonen)
----------------------------------------------------------------------
Date: Tue, 12 Nov 2019 08:47:19 +0100
From: Daniel Wagner <[email protected]>
Subject: [PATCH v3 4/4] tools: Remove GDateTime API usage
To: [email protected]
Cc: Daniel Wagner <[email protected]>
Message-ID: <[email protected]>
Content-Type: text/plain; charset="us-ascii"
The g_date_time_new_from_iso8601() was introduced with GLib v2.56. We
don't want to update our version dependency. So let's use good old
POSIX APIs instead.
---
tools/stats-tool.c | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/tools/stats-tool.c b/tools/stats-tool.c
index 193eed24565f..105dc4973edb 100644
--- a/tools/stats-tool.c
+++ b/tools/stats-tool.c
@@ -108,18 +108,11 @@ static char *option_last_file_name = NULL;
static bool parse_start_ts(const char *key, const char *value,
gpointer user_data, GError **error)
{
- GTimeZone *tz;
- GDateTime *dt;
+ struct tm tm;
- tz = g_time_zone_new_local();
- dt = g_date_time_new_from_iso8601(value, tz);
- g_time_zone_unref(tz);
- if (!dt)
- return false;
+ strptime(value, "%FT%TZ", &tm);
+ option_start_ts = mktime(&tm);
- option_start_ts = g_date_time_get_second(dt);
-
- g_date_time_unref(dt);
return true;
}
--
2.23.0
_______________________________________________
connman mailing list -- [email protected]
To unsubscribe send an email to [email protected]
------------------------------
Date: Tue, 12 Nov 2019 14:29:09 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH v2 03/10] doc: Document VPN agent credential
storage/retrieval options
To: [email protected]
Cc: David Llewellyn-Jones <[email protected]>
Message-ID: <[email protected]>
From: David Llewellyn-Jones <[email protected]>
Document the AllowStoreCredentials and AllowRetrieveCredentials added
to VPN agent API.
---
Changes since v2:
* Correct intendation in the example
doc/vpn-agent-api.txt | 43 ++++++++++++++++++++++++++++++++++++++++---
1 file changed, 40 insertions(+), 3 deletions(-)
diff --git a/doc/vpn-agent-api.txt b/doc/vpn-agent-api.txt
index c27eddd5..4fc5bb5c 100644
--- a/doc/vpn-agent-api.txt
+++ b/doc/vpn-agent-api.txt
@@ -78,7 +78,7 @@ Fields string Username
string OpenConnect.ClientCert
Informational field containing a pkcs11 URL or a path
- name for the client certificate.
+ name for the client certificate.
string OpenConnect.Cookie
@@ -107,6 +107,22 @@ Fields string Username
Return the final VPN server to use after possible
web authentication logins, selections and redirections.
+ boolean AllowStoreCredentials
+
+ Indicates to the receiving UI whether the values
+ entered by the user can be stored for future use.
+ "Requirement" should be set to "control". A "Value"
+ of true indicates that the option to store the
+ credentials can be offered to the user, false
+ indicates that no such option should be presented.
+
+ boolean AllowRetrieveCredentials
+
+ Tells the receiving UI whether to attempt to retrieve
+ previously stored values. "Requirement" should be set
+ to "control". "Value" should be set to true if
+ previously stored values can be used, false otherwise.
+
string VpnAgent.AuthFailure
Informational field that can be used to indicate VPN
@@ -122,8 +138,8 @@ Arguments string Type
string Requirement
Contains the requirement option. Valid values are
- "mandatory", "optional", "alternate" or
- "informational".
+ "mandatory", "optional", "alternate", "informational"
+ and "control".
The "alternate" value specifies that this field can be
returned as an alternative to another one.
@@ -135,6 +151,11 @@ Arguments string Type
is here only to provide an information so a value is
attached to it.
+ A "control" argument is used to specify behaviour. The
+ effect will depend on the field name and value, but
+ control fields will not usually be presented directly
+ to the user, and are not expected to be returned.
+
array{string} Alternates
Contains the list of alternate field names this
@@ -174,3 +195,19 @@ Examples Requesting a username and password for L2TP
network
"Requirement" : "informational"
} }
==> { "OpenConnect.Cookie" : "0123456@adfsf@asasdf" }
+
+ Requesting a username and password but without allowing
+ the values entered by the user to be stored.
+
+ RequestInput("/vpn3",
+ { "Username" : { "Type" : "string",
+ "Requirement" : "mandatory"
+ } }
+ { "Password" : { "Type" : "password",
+ "Requirement" : "mandatory"
+ } }
+ { "AllowStoreCredentials" : { "Type" :
"boolean",
+ "Requirement" : "control",
+ "Value" : false
+ } }
+ ==> { "Username" : "foo", "Password" : "secret123" }
--
2.20.1
------------------------------
Date: Tue, 12 Nov 2019 14:30:12 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH v2 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.
Add documentation of KeepCredentials use with AllowStoreCredentials and
AllowRetrieveCredentials into vpn-overview.txt.
---
Changes since v2:
* Include changes to vpn-overview.txt to describe the use of the control
type values in VPN agent requests.
doc/vpn-agent-api.txt | 14 ++++++++++++++
doc/vpn-overview.txt | 39 +++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/doc/vpn-agent-api.txt b/doc/vpn-agent-api.txt
index 4fc5bb5c..58e525c0 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
diff --git a/doc/vpn-overview.txt b/doc/vpn-overview.txt
index 42b6e94a..3a08e451 100644
--- a/doc/vpn-overview.txt
+++ b/doc/vpn-overview.txt
@@ -58,3 +58,42 @@ After successfull connection, the relevant connection
properties are sent
by PropertyChanged signal; like IPv[4|6] information, the index of the
VPN tunneling interface (if there is any), nameserver information,
server specified routes etc.
+
+VPN agent interface
+===================
+
+VPN agent interface described in vpn-agent-api.txt is used for
+interaction between the connectivity UI and ConnMan. A VPN agent
+registered via Management interface gets requests from the VPN plugins
+to input credentials or other authentication information for the VPN
+connection and offers information about the VPN to be connected.
+
+In addition to basic credentials, there are additional types of optional
+and control parameters. The user can dictate whether to store the
+credentials with the optional SaveCredentials value. The VPN plugins can
+also define with the control values AllowStoreCredentials,
+AllowRetrieveCredentials and KeepCredentials how the VPN agent must
+handle the credentials. AllowStoreCredentials as false indicates that
+client cannot use SaveCredentials option. AllowRetrieveCredentials set
+as false, without AllowStoreCredentials set as false should not have
+that same effect and in that case user is allowed to save credentials.
+
+These three control values become useful when a VPN has two or more
+sets of authentication credentials, second of which can be requested
+when the VPN detects a need for them. The first, main credentials,
+would be requested without these control values, so user is able to
+select whether the credentials are saved or not with SaveCredentials
+value. After the VPN initializes the connection and, e.g., needs to
+decrypt a private key file, a new request is sent to VPN agent. In this
+new request both AllowStoreCredentials and AllowRetrieveCredentials are
+set as false indicating that in no circumstances existing credentials
+stored are to be used and neither there should be option visible for
+the user to select saving of the credentials. Depending on VPN agent
+implementation these values can be interpreted as clearing of all the
+existing credentials related to the VPN connection from the credential
+storage. By including the KeepCredentials as true value the VPN can,
+however, tell the VPN agent not to clear the credentials for this VPN
+connection. The KeepCredentials is used to inform the VPN agent that
+these new, second/third/etc. credentials are only to be queried from
+the user and forgotten after that, when used in conjunction with the
+AllowStoreCredentials and AllowRetrieveCredentials set as false.
--
2.20.1
------------------------------
Date: Tue, 12 Nov 2019 14:31:45 +0200
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH v2 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 it is to be created to /tmp. This commit adds a
TMPDIR to makefiles for such use.
---
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.
Makefile.am | 5 +-
Makefile.plugins | 3 +-
README | 9 +
vpn/plugins/openvpn.c | 768 +++++++++++++++++++++++++++++++++++++++---
4 files changed, 733 insertions(+), 52 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 5971ca9b..dbc214ca 100644
--- 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"
if VPN
vpn_plugindir = $(libdir)/connman/plugins-vpn
@@ -243,7 +244,8 @@ AM_CFLAGS = @DBUS_CFLAGS@ @GLIB_CFLAGS@ \
-DSCRIPTDIR=\""$(build_scriptdir)"\" \
-DSTORAGEDIR=\""$(storagedir)\"" \
-DVPN_STORAGEDIR=\""$(vpn_storagedir)\"" \
- -DCONFIGDIR=\""$(configdir)\""
+ -DCONFIGDIR=\""$(configdir)\"" \
+ -DTMPDIR=\""$(tmpdir)"\"
if VPN
AM_CPPFLAGS = -I$(builddir)/include -I$(srcdir)/gdbus
@@ -276,6 +278,7 @@ vpn_connman_vpnd_CFLAGS = @DBUS_CFLAGS@ @GLIB_CFLAGS@ \
-DSTORAGEDIR=\""$(storagedir)\"" \
-DVPN_STORAGEDIR=\""$(vpn_storagedir)\"" \
-DCONFIGDIR=\""$(configdir)\"" \
+ -DTMPDIR=\""$(tmpdir)"\" \
-I$(builddir)/vpn
endif
diff --git a/Makefile.plugins b/Makefile.plugins
index ab2bbe04..db5121ca 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -117,7 +117,8 @@ vpn_plugin_objects += $(plugins_openvpn_la_OBJECTS)
vpn_plugins_openvpn_la_SOURCES = vpn/plugins/openvpn.c
vpn_plugins_openvpn_la_CFLAGS = $(plugin_cflags) -DOPENVPN=\"@OPENVPN@\" \
-DVPN_STATEDIR=\""$(vpn_statedir)"\" \
- -DSCRIPTDIR=\""$(build_scriptdir)"\"
+ -DSCRIPTDIR=\""$(build_scriptdir)"\" \
+ -DTMPDIR=\""$(tmpdir)"\"
vpn_plugins_openvpn_la_LDFLAGS = $(plugin_ldflags)
endif
endif
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..70cd142b 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,608 @@ 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)
+{
+ 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(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 +1124,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 +1159,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 15
***************************************