summaryrefslogtreecommitdiff
path: root/vpn
diff options
context:
space:
mode:
authorNiraj Kumar Goit <niraj.g@samsung.com>2020-12-01 18:14:19 +0530
committerNiraj Kumar Goit <niraj.g@samsung.com>2021-01-04 05:50:23 +0000
commitc647a4b6f1132684c9d8b8ad71ec38d81147b278 (patch)
treeb346bee32f204af1f3b13cfacb2ad3caa9a9c484 /vpn
parent04d1dbacf6aabbb44f16f6776496192964d460d8 (diff)
downloadconnman-c647a4b6f1132684c9d8b8ad71ec38d81147b278.tar.gz
connman-c647a4b6f1132684c9d8b8ad71ec38d81147b278.tar.bz2
connman-c647a4b6f1132684c9d8b8ad71ec38d81147b278.zip
Imported Upstream connman version 1.38
Change-Id: I9e650762f3b2b2a31945b66e044e67a77e3b4b12 Signed-off-by: Niraj Kumar Goit <niraj.g@samsung.com>
Diffstat (limited to 'vpn')
-rwxr-xr-xvpn/main.c7
-rwxr-xr-xvpn/plugins/l2tp.c45
-rw-r--r--vpn/plugins/libwireguard.c998
-rwxr-xr-xvpn/plugins/openconnect.c1318
-rwxr-xr-xvpn/plugins/openvpn.c798
-rwxr-xr-xvpn/plugins/pptp.c39
-rwxr-xr-xvpn/plugins/vpn.c197
-rwxr-xr-xvpn/plugins/vpn.h3
-rwxr-xr-xvpn/plugins/vpnc.c564
-rw-r--r--vpn/plugins/wireguard.c404
-rw-r--r--vpn/plugins/wireguard.h103
-rwxr-xr-xvpn/vpn-agent.c146
-rwxr-xr-xvpn/vpn-agent.h12
-rwxr-xr-xvpn/vpn-config.c7
-rwxr-xr-xvpn/vpn-provider.c554
-rwxr-xr-xvpn/vpn-provider.h21
-rw-r--r--vpn/vpn-settings.c253
-rwxr-xr-xvpn/vpn.h18
18 files changed, 5138 insertions, 349 deletions
diff --git a/vpn/main.c b/vpn/main.c
index 474c62fb..ae9c945b 100755
--- a/vpn/main.c
+++ b/vpn/main.c
@@ -260,7 +260,7 @@ unsigned int *connman_setting_get_uint_list(const char *key)
*/
unsigned int connman_timeout_input_request(void)
{
- return connman_vpn_settings.timeout_inputreq;
+ return __vpn_settings_get_timeout_inputreq();
}
unsigned int connman_timeout_browser_launch(void)
@@ -354,9 +354,9 @@ int main(int argc, char *argv[])
__connman_dbus_init(conn);
if (!option_config)
- config_init(CONFIGMAINFILE);
+ __vpn_settings_init(CONFIGMAINFILE);
else
- config_init(option_config);
+ __vpn_settings_init(option_config);
__connman_inotify_init();
__connman_agent_init();
@@ -388,6 +388,7 @@ int main(int argc, char *argv[])
__connman_inotify_cleanup();
__connman_dbus_cleanup();
__connman_log_cleanup(false);
+ __vpn_settings_cleanup();
dbus_connection_unref(conn);
diff --git a/vpn/plugins/l2tp.c b/vpn/plugins/l2tp.c
index 5d83eb88..48894aa5 100755
--- a/vpn/plugins/l2tp.c
+++ b/vpn/plugins/l2tp.c
@@ -65,6 +65,7 @@ enum {
OPT_L2G = 2,
OPT_L2 = 3,
OPT_PPPD = 4,
+ OPT_L2LNS = 5,
};
struct {
@@ -83,7 +84,7 @@ struct {
{ "L2TP.DefaultRoute", "defaultroute", OPT_L2, NULL, OPT_STRING },
{ "L2TP.FlowBit", "flow bit", OPT_L2, NULL, OPT_STRING },
{ "L2TP.TunnelRWS", "tunnel rws", OPT_L2, NULL, OPT_STRING },
- { "L2TP.Exclusive", "exclusive", OPT_L2, NULL, OPT_STRING },
+ { "L2TP.Exclusive", "exclusive", OPT_L2LNS, NULL, OPT_STRING },
{ "L2TP.Autodial", "autodial", OPT_L2, "yes", OPT_STRING },
{ "L2TP.Redial", "redial", OPT_L2, "yes", OPT_STRING },
{ "L2TP.RedialTimeout", "redial timeout", OPT_L2, "10", OPT_STRING },
@@ -96,7 +97,7 @@ struct {
{ "L2TP.ForceUserSpace", "force userspace", OPT_L2G, NULL, OPT_STRING },
{ "L2TP.ListenAddr", "listen-addr", OPT_L2G, NULL, OPT_STRING },
{ "L2TP.Rand Source", "rand source", OPT_L2G, NULL, OPT_STRING },
- { "L2TP.IPsecSaref", "ipsec saref", OPT_L2G, NULL, OPT_STRING },
+ { "L2TP.IPsecSaref", "ipsec saref", OPT_L2G, "no", OPT_STRING },
{ "L2TP.Port", "port", OPT_L2G, NULL, OPT_STRING },
{ "PPPD.EchoFailure", "lcp-echo-failure", OPT_PPPD, "0", OPT_STRING },
{ "PPPD.EchoInterval", "lcp-echo-interval", OPT_PPPD, "0", OPT_STRING },
@@ -178,7 +179,8 @@ static int l2tp_notify(DBusMessage *msg, struct vpn_provider *provider)
DBG("authentication failure");
vpn_provider_set_string(provider, "L2TP.User", NULL);
- vpn_provider_set_string(provider, "L2TP.Password", NULL);
+ vpn_provider_set_string_hide_value(provider, "L2TP.Password",
+ NULL);
return VPN_STATE_AUTH_FAILURE;
}
@@ -454,6 +456,9 @@ static int l2tp_write_config(struct vpn_provider *provider,
l2tp_write_option(fd, "[global]", NULL);
l2tp_write_fields(provider, fd, OPT_L2G);
+ l2tp_write_option(fd, "[lns default]", NULL);
+ l2tp_write_fields(provider, fd, OPT_L2LNS);
+
l2tp_write_option(fd, "[lac l2tp]", NULL);
option = vpn_provider_get_string(provider, "Host");
@@ -491,16 +496,28 @@ struct request_input_reply {
static void request_input_reply(DBusMessage *reply, void *user_data)
{
struct request_input_reply *l2tp_reply = user_data;
+ struct l2tp_private_data *data;
const char *error = NULL;
char *username = NULL, *password = NULL;
char *key;
DBusMessageIter iter, dict;
+ int err;
DBG("provider %p", l2tp_reply->provider);
- if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
- if (reply)
- error = dbus_message_get_error_name(reply);
+ if (!reply)
+ goto done;
+
+ data = l2tp_reply->user_data;
+
+ err = vpn_agent_check_and_process_reply_error(reply,
+ l2tp_reply->provider, data->task, data->cb,
+ data->user_data);
+ if (err) {
+ /* Ensure cb is called only once */
+ data->cb = NULL;
+ data->user_data = NULL;
+ error = dbus_message_get_error_name(reply);
goto done;
}
@@ -593,6 +610,9 @@ static int request_input(struct vpn_provider *provider,
connman_dbus_dict_open(&iter, &dict);
+ if (vpn_provider_get_authentication_errors(provider))
+ vpn_agent_append_auth_failure(&dict, provider, NULL);
+
vpn_agent_append_user_info(&dict, provider, "L2TP.User");
vpn_agent_append_host_and_name(&dict, provider);
@@ -633,7 +653,7 @@ static int run_connect(struct vpn_provider *provider,
int l2tp_fd, pppd_fd;
int err;
- if (!username || !password) {
+ if (!username || !*username || !password || !*password) {
DBG("Cannot connect username %s password %p",
username, password);
err = -EINVAL;
@@ -704,7 +724,7 @@ static void request_input_cb(struct vpn_provider *provider,
{
struct l2tp_private_data *data = user_data;
- if (!username || !password)
+ if (!username || !*username || !password || !*password)
DBG("Requesting username %s or password failed, error %s",
username, error);
else if (error)
@@ -739,7 +759,7 @@ static int l2tp_connect(struct vpn_provider *provider,
DBG("user %s password %p", username, password);
- if (!username || !password) {
+ if (!username || !*username || !password || !*password) {
struct l2tp_private_data *data;
data = g_try_new0(struct l2tp_private_data, 1);
@@ -783,7 +803,12 @@ static int l2tp_error_code(struct vpn_provider *provider, int exit_code)
static void l2tp_disconnect(struct vpn_provider *provider)
{
- vpn_provider_set_string(provider, "L2TP.Password", NULL);
+ if (!provider)
+ return;
+
+ vpn_provider_set_string_hide_value(provider, "L2TP.Password", NULL);
+
+ connman_agent_cancel(provider);
}
static struct vpn_driver vpn_driver = {
diff --git a/vpn/plugins/libwireguard.c b/vpn/plugins/libwireguard.c
new file mode 100644
index 00000000..c1f55a72
--- /dev/null
+++ b/vpn/plugins/libwireguard.c
@@ -0,0 +1,998 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ * Copyright (C) 2008-2012 Pablo Neira Ayuso <pablo@netfilter.org>.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/genetlink.h>
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include <libmnl/libmnl.h>
+
+#include "src/shared/mnlg.h"
+#include "wireguard.h"
+
+/* wireguard.h netlink uapi: */
+
+#define WG_GENL_NAME "wireguard"
+#define WG_GENL_VERSION 1
+
+enum wg_cmd {
+ WG_CMD_GET_DEVICE,
+ WG_CMD_SET_DEVICE,
+ __WG_CMD_MAX
+};
+
+enum wgdevice_flag {
+ WGDEVICE_F_REPLACE_PEERS = 1U << 0
+};
+enum wgdevice_attribute {
+ WGDEVICE_A_UNSPEC,
+ WGDEVICE_A_IFINDEX,
+ WGDEVICE_A_IFNAME,
+ WGDEVICE_A_PRIVATE_KEY,
+ WGDEVICE_A_PUBLIC_KEY,
+ WGDEVICE_A_FLAGS,
+ WGDEVICE_A_LISTEN_PORT,
+ WGDEVICE_A_FWMARK,
+ WGDEVICE_A_PEERS,
+ __WGDEVICE_A_LAST
+};
+
+enum wgpeer_flag {
+ WGPEER_F_REMOVE_ME = 1U << 0,
+ WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1
+};
+enum wgpeer_attribute {
+ WGPEER_A_UNSPEC,
+ WGPEER_A_PUBLIC_KEY,
+ WGPEER_A_PRESHARED_KEY,
+ WGPEER_A_FLAGS,
+ WGPEER_A_ENDPOINT,
+ WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
+ WGPEER_A_LAST_HANDSHAKE_TIME,
+ WGPEER_A_RX_BYTES,
+ WGPEER_A_TX_BYTES,
+ WGPEER_A_ALLOWEDIPS,
+ WGPEER_A_PROTOCOL_VERSION,
+ __WGPEER_A_LAST
+};
+
+enum wgallowedip_attribute {
+ WGALLOWEDIP_A_UNSPEC,
+ WGALLOWEDIP_A_FAMILY,
+ WGALLOWEDIP_A_IPADDR,
+ WGALLOWEDIP_A_CIDR_MASK,
+ __WGALLOWEDIP_A_LAST
+};
+
+/* wireguard-specific parts: */
+
+struct inflatable_buffer {
+ char *buffer;
+ char *next;
+ bool good;
+ size_t len;
+ size_t pos;
+};
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
+{
+ size_t len, expand_to;
+ char *new_buffer;
+
+ if (!buffer->good || !buffer->next) {
+ free(buffer->next);
+ buffer->good = false;
+ return 0;
+ }
+
+ len = strlen(buffer->next) + 1;
+
+ if (len == 1) {
+ free(buffer->next);
+ buffer->good = false;
+ return 0;
+ }
+
+ if (buffer->len - buffer->pos <= len) {
+ expand_to = max(buffer->len * 2, buffer->len + len + 1);
+ new_buffer = realloc(buffer->buffer, expand_to);
+ if (!new_buffer) {
+ free(buffer->next);
+ buffer->good = false;
+ return -errno;
+ }
+ memset(&new_buffer[buffer->len], 0, expand_to - buffer->len);
+ buffer->buffer = new_buffer;
+ buffer->len = expand_to;
+ }
+ memcpy(&buffer->buffer[buffer->pos], buffer->next, len);
+ free(buffer->next);
+ buffer->good = false;
+ buffer->pos += len;
+ return 0;
+}
+
+static int parse_linkinfo(const struct nlattr *attr, void *data)
+{
+ struct inflatable_buffer *buffer = data;
+
+ if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr)))
+ buffer->good = true;
+ return MNL_CB_OK;
+}
+
+static int parse_infomsg(const struct nlattr *attr, void *data)
+{
+ struct inflatable_buffer *buffer = data;
+
+ if (mnl_attr_get_type(attr) == IFLA_LINKINFO)
+ return mnl_attr_parse_nested(attr, parse_linkinfo, data);
+ else if (mnl_attr_get_type(attr) == IFLA_IFNAME)
+ buffer->next = strdup(mnl_attr_get_str(attr));
+ return MNL_CB_OK;
+}
+
+static int read_devices_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct inflatable_buffer *buffer = data;
+ int ret;
+
+ buffer->good = false;
+ buffer->next = NULL;
+ ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, data);
+ if (ret != MNL_CB_OK)
+ return ret;
+ ret = add_next_to_inflatable_buffer(buffer);
+ if (ret < 0)
+ return ret;
+ if (nlh->nlmsg_type != NLMSG_DONE)
+ return MNL_CB_OK + 1;
+ return MNL_CB_OK;
+}
+
+static int fetch_device_names(struct inflatable_buffer *buffer)
+{
+ struct mnl_socket *nl = NULL;
+ char *rtnl_buffer = NULL;
+ size_t message_len;
+ unsigned int portid, seq;
+ ssize_t len;
+ int ret = 0;
+ struct nlmsghdr *nlh;
+ struct ifinfomsg *ifm;
+
+ ret = -ENOMEM;
+ rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);
+ if (!rtnl_buffer)
+ goto cleanup;
+
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (!nl) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ seq = time(NULL);
+ portid = mnl_socket_get_portid(nl);
+ nlh = mnl_nlmsg_put_header(rtnl_buffer);
+ nlh->nlmsg_type = RTM_GETLINK;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+ nlh->nlmsg_seq = seq;
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifi_family = AF_UNSPEC;
+ message_len = nlh->nlmsg_len;
+
+ if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+another:
+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, buffer)) < 0) {
+ /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed
+ * during the dump. That's unfortunate, but is pretty common on busy
+ * systems that are adding and removing tunnels all the time. Rather
+ * than retrying, potentially indefinitely, we just work with the
+ * partial results. */
+ if (errno != EINTR) {
+ ret = -errno;
+ goto cleanup;
+ }
+ }
+ if (len == MNL_CB_OK + 1)
+ goto another;
+ ret = 0;
+
+cleanup:
+ free(rtnl_buffer);
+ if (nl)
+ mnl_socket_close(nl);
+ return ret;
+}
+
+static int add_del_iface(const char *ifname, bool add)
+{
+ struct mnl_socket *nl = NULL;
+ char *rtnl_buffer;
+ ssize_t len;
+ int ret;
+ struct nlmsghdr *nlh;
+ struct ifinfomsg *ifm;
+ struct nlattr *nest;
+
+ rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);
+ if (!rtnl_buffer) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (!nl) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ nlh = mnl_nlmsg_put_header(rtnl_buffer);
+ nlh->nlmsg_type = add ? RTM_NEWLINK : RTM_DELLINK;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | (add ? NLM_F_CREATE | NLM_F_EXCL : 0);
+ nlh->nlmsg_seq = time(NULL);
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifi_family = AF_UNSPEC;
+ mnl_attr_put_strz(nlh, IFLA_IFNAME, ifname);
+ nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
+ mnl_attr_put_strz(nlh, IFLA_INFO_KIND, WG_GENL_NAME);
+ mnl_attr_nest_end(nlh, nest);
+
+ if (mnl_socket_sendto(nl, rtnl_buffer, nlh->nlmsg_len) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ if (mnl_cb_run(rtnl_buffer, len, nlh->nlmsg_seq, mnl_socket_get_portid(nl), NULL, NULL) < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+ ret = 0;
+
+cleanup:
+ free(rtnl_buffer);
+ if (nl)
+ mnl_socket_close(nl);
+ return ret;
+}
+
+int wg_set_device(wg_device *dev)
+{
+ int ret = 0;
+ wg_peer *peer = NULL;
+ wg_allowedip *allowedip = NULL;
+ struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest;
+ struct nlmsghdr *nlh;
+ struct mnlg_socket *nlg;
+
+ nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+ if (!nlg)
+ return -errno;
+
+again:
+ nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK);
+ mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name);
+
+ if (!peer) {
+ uint32_t flags = 0;
+
+ if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
+ mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key);
+ if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+ mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port);
+ if (dev->flags & WGDEVICE_HAS_FWMARK)
+ mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark);
+ if (dev->flags & WGDEVICE_REPLACE_PEERS)
+ flags |= WGDEVICE_F_REPLACE_PEERS;
+ if (flags)
+ mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags);
+ }
+ if (!dev->first_peer)
+ goto send;
+ peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL;
+ peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS);
+ for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
+ uint32_t flags = 0;
+
+ peer_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0);
+ if (!peer_nest)
+ goto toobig_peers;
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key))
+ goto toobig_peers;
+ if (peer->flags & WGPEER_REMOVE_ME)
+ flags |= WGPEER_F_REMOVE_ME;
+ if (!allowedip) {
+ if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+ flags |= WGPEER_F_REPLACE_ALLOWEDIPS;
+ if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))
+ goto toobig_peers;
+ }
+ if (peer->endpoint.addr.sa_family == AF_INET) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))
+ goto toobig_peers;
+ } else if (peer->endpoint.addr.sa_family == AF_INET6) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))
+ goto toobig_peers;
+ }
+ if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
+ if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
+ goto toobig_peers;
+ }
+ }
+ if (flags) {
+ if (!mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_FLAGS, flags))
+ goto toobig_peers;
+ }
+ if (peer->first_allowedip) {
+ if (!allowedip)
+ allowedip = peer->first_allowedip;
+ allowedips_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS);
+ if (!allowedips_nest)
+ goto toobig_allowedips;
+ for (; allowedip; allowedip = allowedip->next_allowedip) {
+ allowedip_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0);
+ if (!allowedip_nest)
+ goto toobig_allowedips;
+ if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family))
+ goto toobig_allowedips;
+ if (allowedip->family == AF_INET) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))
+ goto toobig_allowedips;
+ } else if (allowedip->family == AF_INET6) {
+ if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
+ goto toobig_allowedips;
+ }
+ if (!mnl_attr_put_u8_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
+ goto toobig_allowedips;
+ mnl_attr_nest_end(nlh, allowedip_nest);
+ allowedip_nest = NULL;
+ }
+ mnl_attr_nest_end(nlh, allowedips_nest);
+ allowedips_nest = NULL;
+ }
+
+ mnl_attr_nest_end(nlh, peer_nest);
+ peer_nest = NULL;
+ }
+ mnl_attr_nest_end(nlh, peers_nest);
+ peers_nest = NULL;
+ goto send;
+toobig_allowedips:
+ if (allowedip_nest)
+ mnl_attr_nest_cancel(nlh, allowedip_nest);
+ if (allowedips_nest)
+ mnl_attr_nest_end(nlh, allowedips_nest);
+ mnl_attr_nest_end(nlh, peer_nest);
+ mnl_attr_nest_end(nlh, peers_nest);
+ goto send;
+toobig_peers:
+ if (peer_nest)
+ mnl_attr_nest_cancel(nlh, peer_nest);
+ mnl_attr_nest_end(nlh, peers_nest);
+ goto send;
+send:
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {
+ ret = errno ? -errno : -EINVAL;
+ goto out;
+ }
+ if (peer)
+ goto again;
+
+out:
+ mnlg_socket_close(nlg);
+ errno = -ret;
+ return ret;
+}
+
+static int parse_allowedip(const struct nlattr *attr, void *data)
+{
+ wg_allowedip *allowedip = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGALLOWEDIP_A_UNSPEC:
+ break;
+ case WGALLOWEDIP_A_FAMILY:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ allowedip->family = mnl_attr_get_u16(attr);
+ break;
+ case WGALLOWEDIP_A_IPADDR:
+ if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4))
+ memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4));
+ else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6))
+ memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6));
+ break;
+ case WGALLOWEDIP_A_CIDR_MASK:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U8))
+ allowedip->cidr = mnl_attr_get_u8(attr);
+ break;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int parse_allowedips(const struct nlattr *attr, void *data)
+{
+ wg_peer *peer = data;
+ wg_allowedip *new_allowedip = calloc(1, sizeof(wg_allowedip));
+ int ret;
+
+ if (!new_allowedip)
+ return MNL_CB_ERROR;
+ if (!peer->first_allowedip)
+ peer->first_allowedip = peer->last_allowedip = new_allowedip;
+ else {
+ peer->last_allowedip->next_allowedip = new_allowedip;
+ peer->last_allowedip = new_allowedip;
+ }
+ ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip);
+ if (!ret)
+ return ret;
+ if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128))) {
+ errno = EAFNOSUPPORT;
+ return MNL_CB_ERROR;
+ }
+ return MNL_CB_OK;
+}
+
+bool wg_key_is_zero(const wg_key key)
+{
+ volatile uint8_t acc = 0;
+ unsigned int i;
+
+ for (i = 0; i < sizeof(wg_key); ++i) {
+ acc |= key[i];
+ __asm__ ("" : "=r" (acc) : "0" (acc));
+ }
+ return 1 & ((acc - 1) >> 8);
+}
+
+static int parse_peer(const struct nlattr *attr, void *data)
+{
+ wg_peer *peer = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGPEER_A_UNSPEC:
+ break;
+ case WGPEER_A_PUBLIC_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) {
+ memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key));
+ peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+ }
+ break;
+ case WGPEER_A_PRESHARED_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) {
+ memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key));
+ if (!wg_key_is_zero(peer->preshared_key))
+ peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+ }
+ break;
+ case WGPEER_A_ENDPOINT: {
+ struct sockaddr *addr;
+
+ if (mnl_attr_get_payload_len(attr) < sizeof(*addr))
+ break;
+ addr = mnl_attr_get_payload(attr);
+ if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr4))
+ memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4));
+ else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6))
+ memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6));
+ break;
+ }
+ case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);
+ break;
+ case WGPEER_A_LAST_HANDSHAKE_TIME:
+ if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time))
+ memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time));
+ break;
+ case WGPEER_A_RX_BYTES:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+ peer->rx_bytes = mnl_attr_get_u64(attr);
+ break;
+ case WGPEER_A_TX_BYTES:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+ peer->tx_bytes = mnl_attr_get_u64(attr);
+ break;
+ case WGPEER_A_ALLOWEDIPS:
+ return mnl_attr_parse_nested(attr, parse_allowedips, peer);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int parse_peers(const struct nlattr *attr, void *data)
+{
+ wg_device *device = data;
+ wg_peer *new_peer = calloc(1, sizeof(wg_peer));
+ int ret;
+
+ if (!new_peer)
+ return MNL_CB_ERROR;
+ if (!device->first_peer)
+ device->first_peer = device->last_peer = new_peer;
+ else {
+ device->last_peer->next_peer = new_peer;
+ device->last_peer = new_peer;
+ }
+ ret = mnl_attr_parse_nested(attr, parse_peer, new_peer);
+ if (!ret)
+ return ret;
+ if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY)) {
+ errno = ENXIO;
+ return MNL_CB_ERROR;
+ }
+ return MNL_CB_OK;
+}
+
+static int parse_device(const struct nlattr *attr, void *data)
+{
+ wg_device *device = data;
+
+ switch (mnl_attr_get_type(attr)) {
+ case WGDEVICE_A_UNSPEC:
+ break;
+ case WGDEVICE_A_IFINDEX:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+ device->ifindex = mnl_attr_get_u32(attr);
+ break;
+ case WGDEVICE_A_IFNAME:
+ if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) {
+ strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1);
+ device->name[sizeof(device->name) - 1] = '\0';
+ }
+ break;
+ case WGDEVICE_A_PRIVATE_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) {
+ memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key));
+ device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+ }
+ break;
+ case WGDEVICE_A_PUBLIC_KEY:
+ if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) {
+ memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key));
+ device->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+ }
+ break;
+ case WGDEVICE_A_LISTEN_PORT:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+ device->listen_port = mnl_attr_get_u16(attr);
+ break;
+ case WGDEVICE_A_FWMARK:
+ if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+ device->fwmark = mnl_attr_get_u32(attr);
+ break;
+ case WGDEVICE_A_PEERS:
+ return mnl_attr_parse_nested(attr, parse_peers, device);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int read_device_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data);
+}
+
+static void coalesce_peers(wg_device *device)
+{
+ wg_peer *old_next_peer, *peer = device->first_peer;
+
+ while (peer && peer->next_peer) {
+ if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(wg_key))) {
+ peer = peer->next_peer;
+ continue;
+ }
+ if (!peer->first_allowedip) {
+ peer->first_allowedip = peer->next_peer->first_allowedip;
+ peer->last_allowedip = peer->next_peer->last_allowedip;
+ } else {
+ peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip;
+ peer->last_allowedip = peer->next_peer->last_allowedip;
+ }
+ old_next_peer = peer->next_peer;
+ peer->next_peer = old_next_peer->next_peer;
+ free(old_next_peer);
+ }
+}
+
+int wg_get_device(wg_device **device, const char *device_name)
+{
+ int ret = 0;
+ struct nlmsghdr *nlh;
+ struct mnlg_socket *nlg;
+
+try_again:
+ *device = calloc(1, sizeof(wg_device));
+ if (!*device)
+ return -errno;
+
+ nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+ if (!nlg) {
+ wg_free_device(*device);
+ *device = NULL;
+ return -errno;
+ }
+
+ nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+ mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, device_name);
+ if (mnlg_socket_send(nlg, nlh) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ errno = 0;
+ if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) {
+ ret = errno ? -errno : -EINVAL;
+ goto out;
+ }
+ coalesce_peers(*device);
+
+out:
+ if (nlg)
+ mnlg_socket_close(nlg);
+ if (ret) {
+ wg_free_device(*device);
+ if (ret == -EINTR)
+ goto try_again;
+ *device = NULL;
+ }
+ errno = -ret;
+ return ret;
+}
+
+/* first\0second\0third\0forth\0last\0\0 */
+char *wg_list_device_names(void)
+{
+ struct inflatable_buffer buffer = { .len = MNL_SOCKET_BUFFER_SIZE };
+ int ret;
+
+ ret = -ENOMEM;
+ buffer.buffer = calloc(1, buffer.len);
+ if (!buffer.buffer)
+ goto err;
+
+ ret = fetch_device_names(&buffer);
+err:
+ errno = -ret;
+ if (errno) {
+ free(buffer.buffer);
+ return NULL;
+ }
+ return buffer.buffer;
+}
+
+int wg_add_device(const char *device_name)
+{
+ return add_del_iface(device_name, true);
+}
+
+int wg_del_device(const char *device_name)
+{
+ return add_del_iface(device_name, false);
+}
+
+void wg_free_device(wg_device *dev)
+{
+ wg_peer *peer, *np;
+ wg_allowedip *allowedip, *na;
+
+ if (!dev)
+ return;
+ for (peer = dev->first_peer, np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) {
+ for (allowedip = peer->first_allowedip, na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL)
+ free(allowedip);
+ free(peer);
+ }
+ free(dev);
+}
+
+static void encode_base64(char dest[static 4], const uint8_t src[static 3])
+{
+ const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 };
+ unsigned int i;
+
+ for (i = 0; i < 4; ++i)
+ dest[i] = input[i] + 'A'
+ + (((25 - input[i]) >> 8) & 6)
+ - (((51 - input[i]) >> 8) & 75)
+ - (((61 - input[i]) >> 8) & 15)
+ + (((62 - input[i]) >> 8) & 3);
+
+}
+
+void wg_key_to_base64(wg_key_b64_string base64, const wg_key key)
+{
+ unsigned int i;
+
+ for (i = 0; i < 32 / 3; ++i)
+ encode_base64(&base64[i * 4], &key[i * 3]);
+ encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 });
+ base64[sizeof(wg_key_b64_string) - 2] = '=';
+ base64[sizeof(wg_key_b64_string) - 1] = '\0';
+}
+
+static int decode_base64(const char src[static 4])
+{
+ int val = 0;
+ unsigned int i;
+
+ for (i = 0; i < 4; ++i)
+ val |= (-1
+ + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))
+ + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))
+ + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))
+ + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)
+ + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)
+ ) << (18 - 6 * i);
+ return val;
+}
+
+int wg_key_from_base64(wg_key key, const wg_key_b64_string base64)
+{
+ unsigned int i;
+ int val;
+ volatile uint8_t ret = 0;
+
+ if (strlen(base64) != sizeof(wg_key_b64_string) - 1 || base64[sizeof(wg_key_b64_string) - 2] != '=') {
+ errno = EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < 32 / 3; ++i) {
+ val = decode_base64(&base64[i * 4]);
+ ret |= (uint32_t)val >> 31;
+ key[i * 3 + 0] = (val >> 16) & 0xff;
+ key[i * 3 + 1] = (val >> 8) & 0xff;
+ key[i * 3 + 2] = val & 0xff;
+ }
+ val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });
+ ret |= ((uint32_t)val >> 31) | (val & 0xff);
+ key[i * 3 + 0] = (val >> 16) & 0xff;
+ key[i * 3 + 1] = (val >> 8) & 0xff;
+ errno = EINVAL & ~((ret - 1) >> 8);
+out:
+ return -errno;
+}
+
+typedef int64_t fe[16];
+
+static __attribute__((noinline)) void memzero_explicit(void *s, size_t count)
+{
+ memset(s, 0, count);
+ __asm__ __volatile__("": :"r"(s) :"memory");
+}
+
+static void carry(fe o)
+{
+ int i;
+
+ for (i = 0; i < 16; ++i) {
+ o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16);
+ o[i] &= 0xffff;
+ }
+}
+
+static void cswap(fe p, fe q, int b)
+{
+ int i;
+ int64_t t, c = ~(b - 1);
+
+ for (i = 0; i < 16; ++i) {
+ t = c & (p[i] ^ q[i]);
+ p[i] ^= t;
+ q[i] ^= t;
+ }
+
+ memzero_explicit(&t, sizeof(t));
+ memzero_explicit(&c, sizeof(c));
+ memzero_explicit(&b, sizeof(b));
+}
+
+static void pack(uint8_t *o, const fe n)
+{
+ int i, j, b;
+ fe m, t;
+
+ memcpy(t, n, sizeof(t));
+ carry(t);
+ carry(t);
+ carry(t);
+ for (j = 0; j < 2; ++j) {
+ m[0] = t[0] - 0xffed;
+ for (i = 1; i < 15; ++i) {
+ m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
+ m[i - 1] &= 0xffff;
+ }
+ m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
+ b = (m[15] >> 16) & 1;
+ m[14] &= 0xffff;
+ cswap(t, m, 1 - b);
+ }
+ for (i = 0; i < 16; ++i) {
+ o[2 * i] = t[i] & 0xff;
+ o[2 * i + 1] = t[i] >> 8;
+ }
+
+ memzero_explicit(m, sizeof(m));
+ memzero_explicit(t, sizeof(t));
+ memzero_explicit(&b, sizeof(b));
+}
+
+static void add(fe o, const fe a, const fe b)
+{
+ int i;
+
+ for (i = 0; i < 16; ++i)
+ o[i] = a[i] + b[i];
+}
+
+static void subtract(fe o, const fe a, const fe b)
+{
+ int i;
+
+ for (i = 0; i < 16; ++i)
+ o[i] = a[i] - b[i];
+}
+
+static void multmod(fe o, const fe a, const fe b)
+{
+ int i, j;
+ int64_t t[31] = { 0 };
+
+ for (i = 0; i < 16; ++i) {
+ for (j = 0; j < 16; ++j)
+ t[i + j] += a[i] * b[j];
+ }
+ for (i = 0; i < 15; ++i)
+ t[i] += 38 * t[i + 16];
+ memcpy(o, t, sizeof(fe));
+ carry(o);
+ carry(o);
+
+ memzero_explicit(t, sizeof(t));
+}
+
+static void invert(fe o, const fe i)
+{
+ fe c;
+ int a;
+
+ memcpy(c, i, sizeof(c));
+ for (a = 253; a >= 0; --a) {
+ multmod(c, c, c);
+ if (a != 2 && a != 4)
+ multmod(c, c, i);
+ }
+ memcpy(o, c, sizeof(fe));
+
+ memzero_explicit(c, sizeof(c));
+}
+
+static void clamp_key(uint8_t *z)
+{
+ z[31] = (z[31] & 127) | 64;
+ z[0] &= 248;
+}
+
+void wg_generate_public_key(wg_key public_key, const wg_key private_key)
+{
+ int i, r;
+ uint8_t z[32];
+ fe a = { 1 }, b = { 9 }, c = { 0 }, d = { 1 }, e, f;
+
+ memcpy(z, private_key, sizeof(z));
+ clamp_key(z);
+
+ for (i = 254; i >= 0; --i) {
+ r = (z[i >> 3] >> (i & 7)) & 1;
+ cswap(a, b, r);
+ cswap(c, d, r);
+ add(e, a, c);
+ subtract(a, a, c);
+ add(c, b, d);
+ subtract(b, b, d);
+ multmod(d, e, e);
+ multmod(f, a, a);
+ multmod(a, c, a);
+ multmod(c, b, e);
+ add(e, a, c);
+ subtract(a, a, c);
+ multmod(b, a, a);
+ subtract(c, d, f);
+ multmod(a, c, (const fe){ 0xdb41, 1 });
+ add(a, a, d);
+ multmod(c, c, a);
+ multmod(a, d, f);
+ multmod(d, b, (const fe){ 9 });
+ multmod(b, e, e);
+ cswap(a, b, r);
+ cswap(c, d, r);
+ }
+ invert(c, c);
+ multmod(a, a, c);
+ pack(public_key, a);
+
+ memzero_explicit(&r, sizeof(r));
+ memzero_explicit(z, sizeof(z));
+ memzero_explicit(a, sizeof(a));
+ memzero_explicit(b, sizeof(b));
+ memzero_explicit(c, sizeof(c));
+ memzero_explicit(d, sizeof(d));
+ memzero_explicit(e, sizeof(e));
+ memzero_explicit(f, sizeof(f));
+}
+
+void wg_generate_private_key(wg_key private_key)
+{
+ wg_generate_preshared_key(private_key);
+ clamp_key(private_key);
+}
+
+void wg_generate_preshared_key(wg_key preshared_key)
+{
+ ssize_t ret;
+ size_t i;
+ int fd;
+#if defined(__OpenBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)))
+ if (!getentropy(preshared_key, sizeof(wg_key)))
+ return;
+#endif
+#if defined(__NR_getrandom) && defined(__linux__)
+ if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == sizeof(wg_key))
+ return;
+#endif
+ fd = open("/dev/urandom", O_RDONLY);
+ assert(fd >= 0);
+ for (i = 0; i < sizeof(wg_key); i += ret) {
+ ret = read(fd, preshared_key + i, sizeof(wg_key) - i);
+ assert(ret > 0);
+ }
+ close(fd);
+}
diff --git a/vpn/plugins/openconnect.c b/vpn/plugins/openconnect.c
index 8e74479f..d600e61e 100755
--- a/vpn/plugins/openconnect.c
+++ b/vpn/plugins/openconnect.c
@@ -3,6 +3,7 @@
* ConnMan VPN daemon
*
* Copyright (C) 2007-2013 Intel Corporation. All rights reserved.
+ * Copyright (C) 2019 Jolla Ltd. All rights reserved.
*
* 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
@@ -47,25 +48,137 @@
#include "vpn.h"
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
+#define OC_MAX_READBUF_LEN 128
+
+enum opt_type {
+ OPT_STRING = 0,
+ OPT_BOOL = 1,
+};
struct {
- const char *cm_opt;
- const char *oc_opt;
- char has_value;
+ const char *cm_opt;
+ const char *oc_opt;
+ bool has_value;
+ bool enabled; // Use as task parameter
+ enum opt_type type;
} oc_options[] = {
- { "OpenConnect.NoCertCheck", "--no-cert-check", 0 },
+ { "OpenConnect.AllowSelfSignedCert", NULL, 1, 0, OPT_BOOL},
+ { "OpenConnect.AuthType", NULL, 1, 0, OPT_STRING},
+ { "OpenConnect.CACert", "--cafile", 1, 1, OPT_STRING},
+ { "OpenConnect.ClientCert", NULL, 1, 0, OPT_STRING},
+ { "OpenConnect.DisableIPv6", "--disable-ipv6", 1, 1, OPT_BOOL},
+ { "OpenConnect.PKCSClientCert", NULL, 1, 0, OPT_STRING},
+ { "OpenConnect.Protocol", "--protocol", 1, 1, OPT_STRING},
+ /* --no-cert-check is disabled in openconnect 8.02 */
+ { "OpenConnect.NoCertCheck", "--no-cert-check", 0, 0, OPT_BOOL},
+ { "OpenConnect.NoHTTPKeepalive", "--no-http-keepalive", 1, 1, OPT_BOOL},
+ { "OpenConnect.NoDTLS", "--no-dtls", 1, 1, OPT_BOOL},
+ { "OpenConnect.ServerCert", "--servercert", 1, 1, OPT_STRING},
+ { "OpenConnect.Usergroup", "--usergroup", 1, 1, OPT_STRING},
+ { "OpenConnect.UserPrivateKey", NULL, 1, 0, OPT_STRING},
+ { "VPN.MTU", "--base-mtu", 1, 1, OPT_STRING},
+};
+
+enum oc_connect_type {
+ OC_CONNECT_COOKIE = 0,
+ OC_CONNECT_COOKIE_WITH_USERPASS,
+ OC_CONNECT_USERPASS,
+ OC_CONNECT_PUBLICKEY,
+ OC_CONNECT_PKCS,
};
+static const char *connect_types[] = {"cookie", "cookie_with_userpass",
+ "userpass", "publickey", "pkcs", NULL};
+static const char *protocols[] = { "anyconnect", "nc", "gp", NULL};
+
struct oc_private_data {
struct vpn_provider *provider;
struct connman_task *task;
char *if_name;
+ char *dbus_sender;
vpn_provider_connect_cb_t cb;
void *user_data;
+ int fd_in;
+ int out_ch_id;
+ int err_ch_id;
+ GIOChannel *out_ch;
+ GIOChannel *err_ch;
+ enum oc_connect_type connect_type;
+ bool interactive;
};
+static bool is_valid_protocol(const char* protocol)
+{
+ if (!protocol || !*protocol)
+ return false;
+
+ return g_strv_contains(protocols, protocol);
+}
+
+static void oc_connect_done(struct oc_private_data *data, int err)
+{
+ connman_info("data %p err %d/%s", data, err, strerror(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);
+ }
+}
+
+static void close_io_channel(struct oc_private_data *data, GIOChannel *channel)
+{
+ int id = 0;
+
+ connman_info("data %p channel %p", data, channel);
+
+ if (!data || !channel)
+ return;
+
+ if (data->out_ch == channel) {
+ id = data->out_ch_id;
+ data->out_ch = NULL;
+ data->out_ch_id = 0;
+ } else if (data->err_ch == channel) {
+ id = data->err_ch_id;
+ data->err_ch = NULL;
+ data->err_ch_id = 0;
+ } else {
+ return;
+ }
+
+ if (id)
+ g_source_remove(id);
+
+ g_io_channel_shutdown(channel, FALSE, NULL);
+ g_io_channel_unref(channel);
+}
+
static void free_private_data(struct oc_private_data *data)
{
+ connman_info("data %p", data);
+
+ if (!data || !data->provider)
+ return;
+
+ connman_info("provider %p", data->provider);
+
+ if (vpn_provider_get_plugin_data(data->provider) == data)
+ vpn_provider_set_plugin_data(data->provider, NULL);
+
+ vpn_provider_unref(data->provider);
+
+ if (data->fd_in > 0)
+ close(data->fd_in);
+ data->fd_in = -1;
+ close_io_channel(data, data->out_ch);
+ close_io_channel(data, data->err_ch);
+
+ g_free(data->dbus_sender);
g_free(data->if_name);
g_free(data);
}
@@ -73,17 +186,51 @@ static void free_private_data(struct oc_private_data *data)
static int task_append_config_data(struct vpn_provider *provider,
struct connman_task *task)
{
- const char *option;
+ const char *option = NULL;
int i;
for (i = 0; i < (int)ARRAY_SIZE(oc_options); i++) {
- if (!oc_options[i].oc_opt)
+ if (!oc_options[i].oc_opt || !oc_options[i].enabled)
continue;
- option = vpn_provider_get_string(provider,
- oc_options[i].cm_opt);
- if (!option)
- continue;
+ if (oc_options[i].has_value) {
+ option = vpn_provider_get_string(provider,
+ oc_options[i].cm_opt);
+ if (!option)
+ continue;
+
+ /* Add boolean type values only if set as true. */
+ if (oc_options[i].type == OPT_BOOL) {
+ if (!vpn_provider_get_boolean(provider,
+ oc_options[i].cm_opt,
+ false))
+ continue;
+
+ /* No option is set for boolean type values. */
+ option = NULL;
+ }
+
+ /* Skip protocol if it is invalid. */
+ if (!g_strcmp0(oc_options[i].cm_opt,
+ "OpenConnect.Protocol")) {
+ if (!is_valid_protocol(option))
+ continue;
+ }
+ }
+
+ /*
+ * Add server certificate fingerprint only when self signed
+ * certificates are explicitly allowed. Using --servercert as
+ * parameter will accept any server with matching fingerprint,
+ * which would disregard the setting of AllowSelfSignedCert.
+ */
+ if (!g_strcmp0(oc_options[i].cm_opt,
+ "OpenConnect.ServerCert")) {
+ if (!vpn_provider_get_boolean(provider,
+ "OpenConnect.AllowSelfSignedCert",
+ false))
+ continue;
+ }
if (connman_task_add_argument(task,
oc_options[i].oc_opt,
@@ -103,6 +250,11 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider)
char *netmask = NULL, *gateway = NULL;
unsigned char prefix_len = 0;
struct connman_ipaddress *ipaddress;
+ struct oc_private_data *data;
+
+ connman_info("provider %p", provider);
+
+ data = vpn_provider_get_plugin_data(provider);
dbus_message_iter_init(msg, &iter);
@@ -111,6 +263,7 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider)
if (!provider) {
connman_error("No provider found");
+ oc_connect_done(data, ENOENT);
return VPN_STATE_FAILURE;
}
@@ -214,110 +367,770 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider)
g_free(domain);
connman_ipaddress_free(ipaddress);
+ oc_connect_done(data, 0);
return VPN_STATE_CONNECT;
}
-static int run_connect(struct vpn_provider *provider,
- struct connman_task *task, const char *if_name,
- vpn_provider_connect_cb_t cb, void *user_data)
+static ssize_t full_write(int fd, const void *buf, size_t len)
{
- const char *vpnhost, *vpncookie, *servercert, *mtu;
- int fd, err = 0, len;
+ ssize_t byte_write;
+
+ while (len) {
+ byte_write = write(fd, buf, len);
+ if (byte_write < 0) {
+ connman_error("failed to write config to openconnect: "
+ " %s\n", strerror(errno));
+ return byte_write;
+ }
+ len -= byte_write;
+ buf += byte_write;
+ }
- vpnhost = vpn_provider_get_string(provider, "OpenConnect.VPNHost");
- if (!vpnhost)
- vpnhost = vpn_provider_get_string(provider, "Host");
- vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie");
- servercert = vpn_provider_get_string(provider,
- "OpenConnect.ServerCert");
+ return len;
+}
- if (!vpncookie || !servercert) {
- err = -EINVAL;
- goto done;
+static ssize_t write_data(int fd, const char *data)
+{
+ gchar *buf;
+ ssize_t len;
+
+ if (!data || !*data)
+ return -1;
+
+ buf = g_strdup_printf("%s\n", data);
+
+ len = full_write(fd, buf, strlen(buf));
+
+ g_free(buf);
+
+ return len;
+}
+
+static void oc_died(struct connman_task *task, int exit_code, void *user_data)
+{
+ struct oc_private_data *data = user_data;
+
+ connman_info("task %p data %p exit_code %d user_data %p", task, data,
+ exit_code, user_data);
+
+ if (!data)
+ return;
+
+ if (data->provider) {
+ connman_agent_cancel(data->provider);
+
+ if (task)
+ vpn_died(task, exit_code, data->provider);
}
- task_append_config_data(provider, task);
+ free_private_data(data);
+}
- connman_task_add_argument(task, "--servercert", servercert);
+static gboolean io_channel_out_cb(GIOChannel *source, GIOCondition condition,
+ gpointer user_data)
+{
+ struct oc_private_data *data;
+ char *str;
+
+ data = user_data;
- mtu = vpn_provider_get_string(provider, "VPN.MTU");
+ if (data->out_ch != source)
+ return G_SOURCE_REMOVE;
- if (mtu)
- connman_task_add_argument(task, "--mtu", (char *)mtu);
+ if ((condition & G_IO_IN) &&
+ g_io_channel_read_line(source, &str, NULL, NULL, NULL) ==
+ G_IO_STATUS_NORMAL) {
- connman_task_add_argument(task, "--syslog", NULL);
- connman_task_add_argument(task, "--cookie-on-stdin", NULL);
+ g_strchomp(str);
- connman_task_add_argument(task, "--script",
- SCRIPTDIR "/openconnect-script");
+ /* Only cookie is printed to stdout */
+ vpn_provider_set_string_hide_value(data->provider,
+ "OpenConnect.Cookie", str);
- connman_task_add_argument(task, "--interface", if_name);
+ g_free(str);
+ } else if (condition & (G_IO_ERR | G_IO_HUP)) {
+ connman_info("Out channel termination");
+ close_io_channel(data, source);
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static bool strv_contains_prefix(const char *strv[], const char *str)
+{
+ int i;
+
+ if (!strv || !str || !*str)
+ return false;
+
+ for (i = 0; strv[i]; i++) {
+ if (g_str_has_prefix(str, strv[i]))
+ return true;
+ }
+
+ return false;
+}
+
+static void clear_provider_credentials(struct vpn_provider *provider)
+{
+ const char *keys[] = { "OpenConnect.Username",
+ "OpenConnect.Password",
+ "OpenConnect.PKCSPassword",
+ "OpenConnect.Cookie",
+ NULL
+ };
+ int i;
+
+ connman_info("provider %p", provider);
+
+ for (i = 0; keys[i]; i++) {
+ if (!vpn_provider_get_string_immutable(provider, keys[i]))
+ vpn_provider_set_string_hide_value(provider, keys[i],
+ "-");
+ }
+}
+
+typedef void (* request_input_reply_cb_t) (DBusMessage *reply,
+ void *user_data);
+
+static int request_input_credentials(struct oc_private_data *data,
+ request_input_reply_cb_t cb);
+
+
+static void request_input_pkcs_reply(DBusMessage *reply, void *user_data)
+{
+ struct oc_private_data *data = user_data;
+ const char *password = NULL;
+ const char *key;
+ DBusMessageIter iter, dict;
+ int err;
+
+ connman_info("provider %p", data->provider);
+
+ if (!reply)
+ goto err;
+
+ 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;
+ goto err;
+ }
+
+ if (!vpn_agent_check_reply_has_dict(reply))
+ 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, "OpenConnect.PKCSPassword")) {
+ 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);
+ }
+
+ dbus_message_iter_next(&dict);
+ }
+
+ if (data->connect_type != OC_CONNECT_PKCS || !password)
+ goto err;
+
+ if (write_data(data->fd_in, password) != 0) {
+ connman_error("openconnect failed to take PKCS pass phrase on"
+ " stdin");
+ goto err;
+ }
+
+ clear_provider_credentials(data->provider);
+
+ return;
+err:
+ oc_connect_done(data, EACCES);
+}
+
+static gboolean io_channel_err_cb(GIOChannel *source, GIOCondition condition,
+ gpointer user_data)
+{
+ struct oc_private_data *data;
+ const char *auth_failures[] = {
+ /* Login failed */
+ "Got HTTP response: HTTP/1.1 401 Unauthorized",
+ "Failed to obtain WebVPN cookie",
+ /* Cookie not valid */
+ "Got inappropriate HTTP CONNECT response: "
+ "HTTP/1.1 401 Unauthorized",
+ /* Invalid cookie */
+ "VPN service unavailable",
+ /* Problem with certificates */
+ "SSL connection failure",
+ "Creating SSL connection failed",
+ "SSL connection cancelled",
+ NULL
+ };
+ const char *conn_failures[] = {
+ "Failed to connect to",
+ "Failed to open HTTPS connection to",
+ NULL
+ };
+ /* Handle both PKCS#12 and PKCS#8 failures */
+ const char *pkcs_failures[] = {
+ "Failed to decrypt PKCS#12 certificate file",
+ "Failed to decrypt PKCS#8 certificate file",
+ NULL
+ };
+ /* Handle both PKCS#12 and PKCS#8 requests */
+ const char *pkcs_requests[] = {
+ "Enter PKCS#12 pass phrase",
+ "Enter PKCS#8 pass phrase",
+ NULL
+ };
+ const char *server_key_hash = " --servercert";
+ char *str;
+ bool close = false;
+ int err = 0;
+
+ data = user_data;
+
+ if (!data)
+ return G_SOURCE_REMOVE;
+
+ if (source && data->err_ch != source)
+ return G_SOURCE_REMOVE;
+
+ if ((condition & G_IO_IN)) {
+ gsize len;
+ int pos;
+
+ if (!data->interactive) {
+ if (g_io_channel_read_line(source, &str, &len, NULL,
+ NULL) != G_IO_STATUS_NORMAL)
+ err = EIO;
+ else
+ str[len - 1] = '\0';
+ } else {
+ GIOStatus status;
+ str = g_try_new0(char, OC_MAX_READBUF_LEN);
+ if (!str)
+ return G_SOURCE_REMOVE;
+
+ for (pos = 0; pos < OC_MAX_READBUF_LEN - 1 ; ++pos) {
+ status = g_io_channel_read_chars(source,
+ str+pos, 1, &len, NULL);
+
+ if (status == G_IO_STATUS_EOF) {
+ break;
+ } else if (status != G_IO_STATUS_NORMAL) {
+ err = EIO;
+ break;
+ }
+
+ /* Ignore control chars and digits at start */
+ if (!pos && (g_ascii_iscntrl(str[pos]) ||
+ g_ascii_isdigit(str[pos])))
+ --pos;
+
+ /* Read zero length or no more to read */
+ if (!len || g_io_channel_get_buffer_condition(
+ source) != G_IO_IN ||
+ str[pos] == '\n')
+ break;
+ }
+
+ /*
+ * When self signed certificates are allowed and server
+ * SHA1 fingerprint is printed to stderr there is a
+ * newline char at the end of SHA1 fingerprint.
+ */
+ if (str[pos] == '\n')
+ str[pos] = '\0';
+ }
+
+ connman_info("openconnect: %s", str);
+
+ if (err || !str || !*str) {
+ connman_info("error reading from openconnect");
+ } else if (g_str_has_prefix(str, server_key_hash)) {
+ const char *fingerprint;
+ int position;
+ bool allow_self_signed;
+
+ allow_self_signed = vpn_provider_get_boolean(
+ data->provider,
+ "OpenConnect.AllowSelfSignedCert",
+ false);
+
+ if (allow_self_signed) {
+ position = strlen(server_key_hash) + 1;
+ fingerprint = g_strstrip(str + position);
+
+ connman_info("Set server key hash: \"%s\"",
+ fingerprint);
+
+ vpn_provider_set_string(data->provider,
+ "OpenConnect.ServerCert",
+ fingerprint);
+
+ /*
+ * OpenConnect waits for "yes" or "no" as
+ * response to certificate acceptance request.
+ */
+ if (write_data(data->fd_in, "yes") != 0)
+ connman_error("openconnect: cannot "
+ "write answer to certificate "
+ "accept request");
+
+ } else {
+ connman_warn("Self signed certificate is not "
+ " allowed");
+
+ /*
+ * Close IO channel to avoid deadlock as an
+ * answer is expected for the certificate
+ * accept request.
+ */
+ close = true;
+ err = ECONNREFUSED;
+ }
+ } else if (strv_contains_prefix(pkcs_failures, str)) {
+ connman_warn("PKCS failure: %s", str);
+ close = true;
+ err = EACCES;
+ } else if (strv_contains_prefix(pkcs_requests, str)) {
+ connman_info("PKCS file pass phrase request: %s", str);
+ err = request_input_credentials(data,
+ request_input_pkcs_reply);
+
+ if (err != -EINPROGRESS) {
+ err = EACCES;
+ close = true;
+ } else {
+ err = 0;
+ }
+ } else if (strv_contains_prefix(auth_failures, str)) {
+ connman_warn("authentication failed: %s", str);
+ err = EACCES;
+ } else if (strv_contains_prefix(conn_failures, str)) {
+ connman_warn("connection failed: %s", str);
+ err = ECONNREFUSED;
+ }
+
+ g_free(str);
+ } else if (condition & (G_IO_ERR | G_IO_HUP)) {
+ connman_info("Err channel termination");
+ close = true;
+ }
+
+ if (err) {
+ switch (err) {
+ case EACCES:
+ clear_provider_credentials(data->provider);
+ break;
+ case ECONNREFUSED:
+ /*
+ * This will trigger VPN_PROVIDER_ERROR_CONNECT_FAILED
+ * in vpn-provider.c:connect_cb().
+ */
+ default:
+ break;
+ }
+
+ oc_connect_done(data, err);
+ }
+
+ if (close) {
+ close_io_channel(data, source);
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static int run_connect(struct oc_private_data *data)
+{
+ struct vpn_provider *provider;
+ struct connman_task *task;
+ const char *vpnhost;
+ const char *vpncookie = NULL;
+ const char *username;
+ const char *password = NULL;
+ const char *certificate = NULL;
+ const char *private_key;
+ const char *setting_str;
+ bool setting;
+ bool use_stdout = false;
+ int fd_out = -1;
+ int fd_err;
+ int err = 0;
+
+ if (!data)
+ return -EINVAL;
+
+ provider = data->provider;
+ task = data->task;
+
+ connman_info("provider %p task %p", provider, task);
+
+ switch (data->connect_type) {
+ case OC_CONNECT_COOKIE:
+ vpncookie = vpn_provider_get_string(provider,
+ "OpenConnect.Cookie");
+ if (!vpncookie || !g_strcmp0(vpncookie, "-")) {
+ err = -EACCES;
+ goto done;
+ }
+
+ connman_task_add_argument(task, "--cookie-on-stdin", NULL);
+ break;
+ case OC_CONNECT_COOKIE_WITH_USERPASS:
+ vpncookie = vpn_provider_get_string(provider,
+ "OpenConnect.Cookie");
+ /* No cookie set yet, username and password used first */
+ if (!vpncookie || !g_strcmp0(vpncookie, "-")) {
+ username = vpn_provider_get_string(provider,
+ "OpenConnect.Username");
+ password = vpn_provider_get_string(provider,
+ "OpenConnect.Password");
+ if (!username || !password ||
+ !g_strcmp0(username, "-") ||
+ !g_strcmp0(password, "-")) {
+ err = -EACCES;
+ goto done;
+ }
+
+ connman_task_add_argument(task, "--cookieonly", NULL);
+ connman_task_add_argument(task, "--user", username);
+ connman_task_add_argument(task, "--passwd-on-stdin",
+ NULL);
+
+ /* Use stdout only when cookie is to be read. */
+ use_stdout = true;
+ } else {
+ connman_task_add_argument(task, "--cookie-on-stdin",
+ NULL);
+ }
+
+ break;
+ case OC_CONNECT_USERPASS:
+ username = vpn_provider_get_string(provider,
+ "OpenConnect.Username");
+ password = vpn_provider_get_string(provider,
+ "OpenConnect.Password");
+ if (!username || !password || !g_strcmp0(username, "-") ||
+ !g_strcmp0(password, "-")) {
+ err = -EACCES;
+ goto done;
+ }
+
+ connman_task_add_argument(task, "--user", username);
+ connman_task_add_argument(task, "--passwd-on-stdin", NULL);
+ break;
+ case OC_CONNECT_PUBLICKEY:
+ certificate = vpn_provider_get_string(provider,
+ "OpenConnect.ClientCert");
+ private_key = vpn_provider_get_string(provider,
+ "OpenConnect.UserPrivateKey");
+
+ if (!certificate || !private_key) {
+ err = -EACCES;
+ goto done;
+ }
+
+ connman_task_add_argument(task, "--certificate", certificate);
+ connman_task_add_argument(task, "--sslkey", private_key);
+ break;
+ case OC_CONNECT_PKCS:
+ certificate = vpn_provider_get_string(provider,
+ "OpenConnect.PKCSClientCert");
+ if (!certificate) {
+ err = -EACCES;
+ goto done;
+ }
+
+ connman_task_add_argument(task, "--certificate", certificate);
+
+ password = vpn_provider_get_string(data->provider,
+ "OpenConnect.PKCSPassword");
+ /* Add password only if it is has been set */
+ if (!password || !g_strcmp0(password, "-"))
+ break;
+
+ connman_task_add_argument(task, "--passwd-on-stdin", NULL);
+ break;
+ }
+
+ vpnhost = vpn_provider_get_string(provider, "OpenConnect.VPNHost");
+ if (!vpnhost || !*vpnhost)
+ vpnhost = vpn_provider_get_string(provider, "Host");
+
+ task_append_config_data(provider, task);
+
+ /*
+ * To clarify complex situation, if cookie is expected to be printed
+ * to stdout all other output must go to syslog. But with PKCS all
+ * output must be caught in order to get message about file decryption
+ * error. For this reason, the mode has to be interactive as well.
+ */
+ switch (data->connect_type) {
+ case OC_CONNECT_COOKIE:
+ /* fall through */
+ case OC_CONNECT_COOKIE_WITH_USERPASS:
+ /* fall through */
+ case OC_CONNECT_USERPASS:
+ /* fall through */
+ case OC_CONNECT_PUBLICKEY:
+ connman_task_add_argument(task, "--syslog", NULL);
+
+ setting = vpn_provider_get_boolean(provider,
+ "OpenConnect.AllowSelfSignedCert",
+ false);
+ setting_str = vpn_provider_get_string(provider,
+ "OpenConnect.ServerCert");
+
+ /*
+ * Run in interactive mode if self signed certificates are
+ * allowed and there is no set server SHA1 fingerprint.
+ */
+ if (setting_str || !setting)
+ connman_task_add_argument(task, "--non-inter", NULL);
+ else
+ data->interactive = true;
+ break;
+ case OC_CONNECT_PKCS:
+ data->interactive = true;
+ break;
+ }
+
+ connman_task_add_argument(task, "--script", SCRIPTDIR "/vpn-script");
+
+ connman_task_add_argument(task, "--interface", data->if_name);
connman_task_add_argument(task, (char *)vpnhost, NULL);
- err = connman_task_run(task, vpn_died, provider,
- &fd, NULL, NULL);
+ err = connman_task_run(task, oc_died, data, &data->fd_in, use_stdout ?
+ &fd_out : NULL, &fd_err);
if (err < 0) {
- connman_error("openconnect failed to start");
err = -EIO;
goto done;
}
- len = strlen(vpncookie);
- if (write(fd, vpncookie, len) != (ssize_t)len ||
- write(fd, "\n", 1) != 1) {
- connman_error("openconnect failed to take cookie on stdin");
- err = -EIO;
+ switch (data->connect_type) {
+ case OC_CONNECT_COOKIE:
+ if (write_data(data->fd_in, vpncookie) != 0) {
+ connman_error("openconnect failed to take cookie on "
+ "stdin");
+ err = -EIO;
+ }
+
+ break;
+ case OC_CONNECT_USERPASS:
+ if (write_data(data->fd_in, password) != 0) {
+ connman_error("openconnect failed to take password on "
+ "stdin");
+ err = -EIO;
+ }
+
+ break;
+ case OC_CONNECT_COOKIE_WITH_USERPASS:
+ if (!vpncookie || !g_strcmp0(vpncookie, "-")) {
+ if (write_data(data->fd_in, password) != 0) {
+ connman_error("openconnect failed to take "
+ "password on stdin");
+ err = -EIO;
+ }
+ } else {
+ if (write_data(data->fd_in, vpncookie) != 0) {
+ connman_error("openconnect failed to take "
+ "cookie on stdin");
+ err = -EIO;
+ }
+ }
+
+ break;
+ case OC_CONNECT_PUBLICKEY:
+ break;
+ case OC_CONNECT_PKCS:
+ if (!password || !g_strcmp0(password, "-"))
+ break;
+
+ if (write_data(data->fd_in, password) != 0) {
+ connman_error("openconnect failed to take PKCS "
+ "pass phrase on stdin");
+ err = -EIO;
+ }
+
+ break;
+ }
+
+ if (err) {
+ if (fd_out > 0)
+ close(fd_out);
+
+ if (fd_err > 0)
+ close(fd_err);
+
goto done;
}
+ err = -EINPROGRESS;
+
+ if (use_stdout) {
+ data->out_ch = g_io_channel_unix_new(fd_out);
+
+ /* Use ASCII encoding only */
+ if (g_io_channel_set_encoding(data->out_ch, NULL, NULL) !=
+ G_IO_STATUS_NORMAL) {
+ close_io_channel(data, data->out_ch);
+ err = -EIO;
+ } else {
+ data->out_ch_id = g_io_add_watch(data->out_ch,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ (GIOFunc)io_channel_out_cb,
+ data);
+ }
+ }
+
+ data->err_ch = g_io_channel_unix_new(fd_err);
+
+ /* Use ASCII encoding only */
+ if (g_io_channel_set_encoding(data->err_ch, NULL, NULL) !=
+ G_IO_STATUS_NORMAL) {
+ close_io_channel(data, data->err_ch);
+ err = -EIO;
+ } else {
+ data->err_ch_id = g_io_add_watch(data->err_ch,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ (GIOFunc)io_channel_err_cb, data);
+ }
+
done:
- if (cb)
- cb(provider, user_data, err);
+ clear_provider_credentials(data->provider);
return err;
}
-static void request_input_append_informational(DBusMessageIter *iter,
- void *user_data)
+static void request_input_append(DBusMessageIter *iter,
+ const char *str_type, const char *str, void *user_data)
{
- const char *str;
+ const char *string;
- str = "string";
- connman_dbus_dict_append_basic(iter, "Type", DBUS_TYPE_STRING, &str);
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str_type);
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
- str = "informational";
- connman_dbus_dict_append_basic(iter, "Requirement", DBUS_TYPE_STRING,
- &str);
+ if (!user_data)
+ return;
+
+ string = user_data;
+ connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING,
+ &string);
+}
- str = user_data;
- connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str);
+static void request_input_append_informational(DBusMessageIter *iter,
+ void *user_data)
+{
+ request_input_append(iter, "string", "informational", user_data);
}
static void request_input_append_mandatory(DBusMessageIter *iter,
void *user_data)
{
- char *str = "string";
+ request_input_append(iter, "string", "mandatory", user_data);
+}
- 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_optional(DBusMessageIter *iter,
+ void *user_data)
+{
+ request_input_append(iter, "string", "optional", user_data);
+}
+
+static void request_input_append_password(DBusMessageIter *iter,
+ void *user_data)
+{
+ request_input_append(iter, "password", "mandatory", user_data);
+}
+
+static void request_input_append_to_dict(struct vpn_provider *provider,
+ DBusMessageIter *dict,
+ connman_dbus_append_cb_t function_cb, const char *key)
+{
+ const char *str;
+ bool immutable = false;
+
+ if (!provider || !dict || !function_cb || !key)
+ return;
+
+ str = vpn_provider_get_string(provider, key);
+ /* Ignore empty informational content */
+ if (!str && function_cb == request_input_append_informational)
+ return;
+
+ /* If value is "-", it is cleared by VPN agent */
+ if (!g_strcmp0(str, "-"))
+ str = NULL;
+
+ if (str)
+ immutable = vpn_provider_get_string_immutable(provider, key);
+
+ if (immutable) {
+ /* Hide immutable password types */
+ if (function_cb == request_input_append_password)
+ str = "********";
+
+ /* Send immutable as informational */
+ function_cb = request_input_append_informational;
+ }
+
+ connman_dbus_dict_append_dict(dict, key, function_cb,
+ str ? (void *)str : NULL);
}
-static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
+static void request_input_credentials_reply(DBusMessage *reply, void *user_data)
{
struct oc_private_data *data = user_data;
- char *cookie = NULL, *servercert = NULL, *vpnhost = NULL;
- char *key;
+ const char *cookie = NULL;
+ const char *servercert = NULL;
+ const char *vpnhost = NULL;
+ const char *username = NULL;
+ const char *password = NULL;
+ const char *pkcspassword = NULL;
+ const char *key;
DBusMessageIter iter, dict;
+ int err;
- DBG("provider %p", data->provider);
+ connman_info("provider %p", data->provider);
- if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR)
+ if (!reply)
goto err;
+ 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;
+ goto out;
+ }
+
if (!vpn_agent_check_reply_has_dict(reply))
goto err;
@@ -344,7 +1157,6 @@ static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
dbus_message_iter_get_basic(&value, &cookie);
vpn_provider_set_string_hide_value(data->provider,
key, cookie);
-
} else if (g_str_equal(key, "OpenConnect.ServerCert")) {
dbus_message_iter_next(&entry);
if (dbus_message_iter_get_arg_type(&entry)
@@ -369,43 +1181,103 @@ static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
break;
dbus_message_iter_get_basic(&value, &vpnhost);
vpn_provider_set_string(data->provider, key, vpnhost);
+ } else if (g_str_equal(key, "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,
+ "OpenConnect.Username", username);
+ } else if (g_str_equal(key, "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,
+ "OpenConnect.Password", password);
+ } else if (g_str_equal(key, "OpenConnect.PKCSPassword")) {
+ 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, &pkcspassword);
+ vpn_provider_set_string_hide_value(data->provider, key,
+ pkcspassword);
}
dbus_message_iter_next(&dict);
}
- if (!cookie || !servercert || !vpnhost)
- goto err;
-
- run_connect(data->provider, data->task, data->if_name, data->cb,
- data->user_data);
+ switch (data->connect_type) {
+ case OC_CONNECT_COOKIE:
+ if (!cookie)
+ goto err;
+
+ break;
+ case OC_CONNECT_USERPASS:
+ /* fall through */
+ case OC_CONNECT_COOKIE_WITH_USERPASS:
+ if (!username || !password)
+ goto err;
+
+ break;
+ case OC_CONNECT_PUBLICKEY:
+ break; // This should not be reached.
+ case OC_CONNECT_PKCS:
+ if (!pkcspassword)
+ goto err;
+
+ break;
+ }
- free_private_data(data);
+ err = run_connect(data);
+ if (err != -EINPROGRESS)
+ goto err;
return;
err:
- vpn_provider_indicate_error(data->provider,
- VPN_PROVIDER_ERROR_AUTH_FAILED);
+ oc_connect_done(data, EACCES);
+out:
free_private_data(data);
}
-static int request_cookie_input(struct vpn_provider *provider,
- struct oc_private_data *data,
- const char *dbus_sender)
+static int request_input_credentials(struct oc_private_data *data,
+ request_input_reply_cb_t cb)
{
DBusMessage *message;
- const char *path, *agent_sender, *agent_path;
+ const char *path;
+ const char *agent_sender;
+ const char *agent_path;
+ const char *username;
DBusMessageIter iter;
DBusMessageIter dict;
- const char *str;
int err;
void *agent;
- agent = connman_agent_get_info(dbus_sender, &agent_sender,
- &agent_path);
- if (!provider || !agent || !agent_path)
+ if (!data || !cb)
+ return -ESRCH;
+
+ connman_info("provider %p", data->provider);
+
+ agent = connman_agent_get_info(data->dbus_sender,
+ &agent_sender, &agent_path);
+ if (!data->provider || !agent || !agent_path)
return -ESRCH;
message = dbus_message_new_method_call(agent_sender, agent_path,
@@ -416,120 +1288,235 @@ static int request_cookie_input(struct vpn_provider *provider,
dbus_message_iter_init_append(message, &iter);
- path = vpn_provider_get_path(provider);
- dbus_message_iter_append_basic(&iter,
- DBUS_TYPE_OBJECT_PATH, &path);
+ path = vpn_provider_get_path(data->provider);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
connman_dbus_dict_open(&iter, &dict);
- str = vpn_provider_get_string(provider, "OpenConnect.CACert");
- if (str)
- connman_dbus_dict_append_dict(&dict, "OpenConnect.CACert",
+ request_input_append_to_dict(data->provider, &dict,
request_input_append_informational,
- (void *)str);
-
- str = vpn_provider_get_string(provider, "OpenConnect.ClientCert");
- if (str)
- connman_dbus_dict_append_dict(&dict, "OpenConnect.ClientCert",
+ "OpenConnect.CACert");
+
+ /*
+ * For backwards compatibility add OpenConnect.ServerCert and
+ * OpenConnect.VPNHost as madnatory only in the default authentication
+ * mode. Otherwise. add the fields as informational. These should be
+ * set in provider settings and not to be queried with every connection
+ * attempt.
+ */
+ request_input_append_to_dict(data->provider, &dict,
+ data->connect_type == OC_CONNECT_COOKIE ?
+ request_input_append_optional :
request_input_append_informational,
- (void *)str);
-
- connman_dbus_dict_append_dict(&dict, "OpenConnect.ServerCert",
- request_input_append_mandatory, NULL);
+ "OpenConnect.ServerCert");
- connman_dbus_dict_append_dict(&dict, "OpenConnect.VPNHost",
- request_input_append_mandatory, NULL);
+ request_input_append_to_dict(data->provider, &dict,
+ data->connect_type == OC_CONNECT_COOKIE ?
+ request_input_append_optional :
+ request_input_append_informational,
+ "OpenConnect.VPNHost");
+
+ if (vpn_provider_get_authentication_errors(data->provider))
+ vpn_agent_append_auth_failure(&dict, data->provider, NULL);
+
+ switch (data->connect_type) {
+ case OC_CONNECT_COOKIE:
+ request_input_append_to_dict(data->provider, &dict,
+ request_input_append_mandatory,
+ "OpenConnect.Cookie");
+ break;
+ /*
+ * The authentication is done with username and password to get the
+ * cookie for connection.
+ */
+ case OC_CONNECT_COOKIE_WITH_USERPASS:
+ /* fallthrough */
+ case OC_CONNECT_USERPASS:
+ username = vpn_provider_get_string(data->provider,
+ "OpenConnect.Username");
+ vpn_agent_append_user_info(&dict, data->provider, username);
+ break;
+ case OC_CONNECT_PUBLICKEY:
+ return -EINVAL;
+ case OC_CONNECT_PKCS:
+ request_input_append_to_dict(data->provider, &dict,
+ request_input_append_informational,
+ "OpenConnect.PKCSClientCert");
- connman_dbus_dict_append_dict(&dict, "OpenConnect.Cookie",
- request_input_append_mandatory, NULL);
+ request_input_append_to_dict(data->provider, &dict,
+ request_input_append_password,
+ "OpenConnect.PKCSPassword");
+ break;
+ }
- vpn_agent_append_host_and_name(&dict, provider);
+ vpn_agent_append_host_and_name(&dict, data->provider);
connman_dbus_dict_close(&iter, &dict);
- err = connman_agent_queue_message(provider, message,
- connman_timeout_input_request(),
- request_input_cookie_reply, data, agent);
+ err = connman_agent_queue_message(data->provider, message,
+ connman_timeout_input_request(), cb, data, agent);
- if (err < 0 && err != -EBUSY) {
- DBG("error %d sending agent request", err);
- dbus_message_unref(message);
+ dbus_message_unref(message);
+ if (err < 0 && err != -EBUSY) {
+ connman_error("cannot send agent request, error: %d", err);
return err;
}
- dbus_message_unref(message);
-
return -EINPROGRESS;
}
+static enum oc_connect_type get_authentication_type(
+ struct vpn_provider *provider)
+{
+ const char *auth;
+ enum oc_connect_type type;
+
+ auth = vpn_provider_get_string(provider, "OpenConnect.AuthType");
+ if (!auth)
+ goto out;
+
+ for (type = 0; connect_types[type]; type++) {
+ if (!g_strcmp0(auth, connect_types[type])) {
+ connman_info("auth type %d/%s", type,
+ connect_types[type]);
+ return type;
+ }
+ }
+
+out:
+ /* Default to cookie */
+ return OC_CONNECT_COOKIE;
+}
+
static int oc_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 *vpnhost, *vpncookie, *servercert;
+ struct oc_private_data *data;
+ const char *vpncookie;
+ const char *certificate;
+ const char *username;
+ const char *password;
+ const char *private_key;
int err;
- vpnhost = vpn_provider_get_string(provider, "Host");
- if (!vpnhost) {
- connman_error("Host not set; cannot enable VPN");
- return -EINVAL;
- }
+ connman_info("provider %p task %p", provider, task);
+
+ data = g_try_new0(struct oc_private_data, 1);
+ if (!data)
+ return -ENOMEM;
- vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie");
- servercert = vpn_provider_get_string(provider,
- "OpenConnect.ServerCert");
- if (!vpncookie || !servercert) {
- struct oc_private_data *data;
+ vpn_provider_set_plugin_data(provider, data);
+ data->provider = vpn_provider_ref(provider);
+ data->task = task;
+ data->if_name = g_strdup(if_name);
+ data->dbus_sender = g_strdup(dbus_sender);
+ data->cb = cb;
+ data->user_data = user_data;
+ data->connect_type = get_authentication_type(provider);
+
+ switch (data->connect_type) {
+ case OC_CONNECT_COOKIE:
+ vpncookie = vpn_provider_get_string(provider,
+ "OpenConnect.Cookie");
+ if (!vpncookie || !g_strcmp0(vpncookie, "-"))
+ goto request_input;
+
+ break;
+ case OC_CONNECT_USERPASS:
+ username = vpn_provider_get_string(provider,
+ "OpenConnect.Username");
+ password = vpn_provider_get_string(provider,
+ "OpenConnect.Password");
+ if (!username || !password || !g_strcmp0(username, "-") ||
+ !g_strcmp0(password, "-"))
+ goto request_input;
+
+ break;
+ case OC_CONNECT_COOKIE_WITH_USERPASS:
+ vpncookie = vpn_provider_get_string(provider,
+ "OpenConnect.Cookie");
+ /* Username and password must be set if cookie is missing */
+ if (!vpncookie) {
+ username = vpn_provider_get_string(provider,
+ "OpenConnect.Username");
+ password = vpn_provider_get_string(provider,
+ "OpenConnect.Password");
+
+ if (!username || !password ||
+ !g_strcmp0(username, "-") ||
+ !g_strcmp0(password, "-"))
+ goto request_input;
+ } else if (!g_strcmp0(vpncookie, "-")) {
+ goto request_input;
+ }
- data = g_try_new0(struct oc_private_data, 1);
- if (!data)
- return -ENOMEM;
+ break;
+ case OC_CONNECT_PUBLICKEY:
+ certificate = vpn_provider_get_string(provider,
+ "OpenConnect.ClientCert");
+ private_key = vpn_provider_get_string(provider,
+ "OpenConnect.UserPrivateKey");
- data->provider = provider;
- data->task = task;
- data->if_name = g_strdup(if_name);
- data->cb = cb;
- data->user_data = user_data;
+ if (!certificate || !private_key) {
+ connman_warn("missing certificate and/or private key");
+ oc_connect_done(data, EACCES);
+ free_private_data(data);
+ return -EACCES;
+ }
- err = request_cookie_input(provider, data, dbus_sender);
- if (err != -EINPROGRESS) {
- vpn_provider_indicate_error(data->provider,
- VPN_PROVIDER_ERROR_LOGIN_FAILED);
+ break;
+ case OC_CONNECT_PKCS:
+ certificate = vpn_provider_get_string(provider,
+ "OpenConnect.PKCSClientCert");
+ if (!certificate) {
+ connman_warn("missing PKCS certificate");
+ oc_connect_done(data, EACCES);
free_private_data(data);
+ return -EACCES;
}
- return err;
+
+ break;
}
- return run_connect(provider, task, if_name, cb, user_data);
+ return run_connect(data);
+
+request_input:
+ err = request_input_credentials(data, request_input_credentials_reply);
+ if (err != -EINPROGRESS) {
+ oc_connect_done(data, err);
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_LOGIN_FAILED);
+ free_private_data(data);
+ }
+
+ return err;
+}
+
+static void oc_disconnect(struct vpn_provider *provider)
+{
+ connman_info("provider %p", provider);
+
+ if (!provider)
+ return;
+
+ /*
+ * OpenConnect may be disconnect by timeout in connmand before running
+ * the openconnect process. In such case it is important to cancel the
+ * agent request to avoid having multiple ones visible.
+ */
+ connman_agent_cancel(provider);
}
static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile)
{
- const char *setting, *option;
+ const char *save_group;
+ const char *option;
int i;
- setting = vpn_provider_get_string(provider,
- "OpenConnect.ServerCert");
- if (setting)
- g_key_file_set_string(keyfile,
- vpn_provider_get_save_group(provider),
- "OpenConnect.ServerCert", setting);
-
- setting = vpn_provider_get_string(provider,
- "OpenConnect.CACert");
- if (setting)
- g_key_file_set_string(keyfile,
- vpn_provider_get_save_group(provider),
- "OpenConnect.CACert", setting);
-
- setting = vpn_provider_get_string(provider,
- "VPN.MTU");
- if (setting)
- g_key_file_set_string(keyfile,
- vpn_provider_get_save_group(provider),
- "VPN.MTU", setting);
+ save_group = vpn_provider_get_save_group(provider);
for (i = 0; i < (int)ARRAY_SIZE(oc_options); i++) {
if (strncmp(oc_options[i].cm_opt, "OpenConnect.", 12) == 0) {
@@ -538,8 +1525,7 @@ static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile)
if (!option)
continue;
- g_key_file_set_string(keyfile,
- vpn_provider_get_save_group(provider),
+ g_key_file_set_string(keyfile, save_group,
oc_options[i].cm_opt, option);
}
}
@@ -549,13 +1535,22 @@ static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile)
static int oc_error_code(struct vpn_provider *provider, int exit_code)
{
+ connman_info("%d", exit_code);
+
+ /* OpenConnect process return values are ambiguous in definition
+ * https://github.com/openconnect/openconnect/blob/master/main.c#L1693
+ * and it is safer not to rely on them. Login error cannot be
+ * differentiated from connection errors, e.g., when self signed
+ * certificate is rejected by user setting.
+ */
switch (exit_code) {
- case 1:
case 2:
- vpn_provider_set_string_hide_value(provider,
- "OpenConnect.Cookie", NULL);
+ /* Cookie has failed */
+ clear_provider_credentials(provider);
return VPN_PROVIDER_ERROR_LOGIN_FAILED;
+ case 1:
+ /* fall through */
default:
return VPN_PROVIDER_ERROR_UNKNOWN;
}
@@ -593,6 +1588,7 @@ static int oc_route_env_parse(struct vpn_provider *provider, const char *key,
static struct vpn_driver vpn_driver = {
.notify = oc_notify,
.connect = oc_connect,
+ .disconnect = oc_disconnect,
.error_code = oc_error_code,
.save = oc_save,
.route_env_parse = oc_route_env_parse,
diff --git a/vpn/plugins/openvpn.c b/vpn/plugins/openvpn.c
index 6b090e45..9e8ccebf 100755
--- a/vpn/plugins/openvpn.c
+++ b/vpn/plugins/openvpn.c
@@ -3,6 +3,7 @@
* ConnMan VPN daemon
*
* Copyright (C) 2010-2014 BMW Car IT GmbH.
+ * Copyright (C) 2016-2019 Jolla Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -30,6 +31,9 @@
#include <stdio.h>
#include <net/if.h>
#include <linux/if_tun.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
#include <glib.h>
@@ -39,10 +43,15 @@
#include <connman/task.h>
#include <connman/dbus.h>
#include <connman/ipconfig.h>
+#include <connman/agent.h>
+#include <connman/setting.h>
+#include <connman/vpn-dbus.h>
#include "../vpn-provider.h"
+#include "../vpn-agent.h"
#include "vpn.h"
+#include "../vpn.h"
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
@@ -57,7 +66,7 @@ struct {
{ "OpenVPN.CACert", "--ca", 1 },
{ "OpenVPN.Cert", "--cert", 1 },
{ "OpenVPN.Key", "--key", 1 },
- { "OpenVPN.MTU", "--mtu", 1 },
+ { "OpenVPN.MTU", "--tun-mtu", 1 },
{ "OpenVPN.NSCertType", "--ns-cert-type", 1 },
{ "OpenVPN.Proto", "--proto", 1 },
{ "OpenVPN.Port", "--port", 1 },
@@ -67,6 +76,7 @@ struct {
{ "OpenVPN.TLSRemote", "--tls-remote", 1 },
{ "OpenVPN.TLSAuth", NULL, 1 },
{ "OpenVPN.TLSAuthDir", NULL, 1 },
+ { "OpenVPN.TLSCipher", "--tls-cipher", 1},
{ "OpenVPN.Cipher", "--cipher", 1 },
{ "OpenVPN.Auth", "--auth", 1 },
{ "OpenVPN.CompLZO", "--comp-lzo", 0 },
@@ -76,6 +86,50 @@ struct {
{ "OpenVPN.Verb", "--verb", 1 },
};
+struct ov_private_data {
+ struct vpn_provider *provider;
+ struct connman_task *task;
+ char *dbus_sender;
+ char *if_name;
+ vpn_provider_connect_cb_t cb;
+ void *user_data;
+ char *mgmt_path;
+ guint mgmt_timer_id;
+ guint mgmt_event_id;
+ GIOChannel *mgmt_channel;
+ int connect_attempts;
+ int failed_attempts_privatekey;
+};
+
+static void ov_connect_done(struct ov_private_data *data, int err)
+{
+ if (data && data->cb) {
+ vpn_provider_connect_cb_t cb = data->cb;
+ void *user_data = data->user_data;
+
+ /* Make sure we don't invoke this callback twice */
+ data->cb = NULL;
+ data->user_data = NULL;
+ cb(data->provider, user_data, err);
+ }
+
+ if (!err)
+ data->failed_attempts_privatekey = 0;
+}
+
+static void free_private_data(struct ov_private_data *data)
+{
+ if (vpn_provider_get_plugin_data(data->provider) == data)
+ vpn_provider_set_plugin_data(data->provider, NULL);
+
+ ov_connect_done(data, EIO);
+ vpn_provider_unref(data->provider);
+ g_free(data->dbus_sender);
+ g_free(data->if_name);
+ g_free(data->mgmt_path);
+ g_free(data);
+}
+
struct nameserver_entry {
int id;
char *nameserver;
@@ -162,6 +216,7 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider)
char *address = NULL, *gateway = NULL, *peer = NULL, *netmask = NULL;
struct connman_ipaddress *ipaddress;
GSList *nameserver_list = NULL;
+ struct ov_private_data *data = vpn_provider_get_plugin_data(provider);
dbus_message_iter_init(msg, &iter);
@@ -173,8 +228,12 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider)
return VPN_STATE_FAILURE;
}
- if (strcmp(reason, "up"))
+ DBG("%p %s", vpn_provider_get_name(provider), reason);
+
+ if (strcmp(reason, "up")) {
+ ov_connect_done(data, EIO);
return VPN_STATE_DISCONNECT;
+ }
dbus_message_iter_recurse(&iter, &dict);
@@ -266,6 +325,7 @@ static int ov_notify(DBusMessage *msg, struct vpn_provider *provider)
g_free(netmask);
connman_ipaddress_free(ipaddress);
+ ov_connect_done(data, 0);
return VPN_STATE_CONNECT;
}
@@ -304,73 +364,74 @@ static int task_append_config_data(struct vpn_provider *provider,
if (!option)
continue;
+ /*
+ * If the AuthUserPass option is "-", provide the input
+ * via management interface
+ */
+ if (!strcmp(ov_options[i].cm_opt, "OpenVPN.AuthUserPass") &&
+ !strcmp(option, "-"))
+ option = NULL;
+
if (connman_task_add_argument(task,
ov_options[i].ov_opt,
- ov_options[i].has_value ? option : NULL) < 0) {
+ ov_options[i].has_value ? option : NULL) < 0)
return -EIO;
- }
+
}
return 0;
}
-static gboolean can_read_data(GIOChannel *chan,
- GIOCondition cond, gpointer data)
+static void close_management_interface(struct ov_private_data *data)
{
- void (*cbf)(const char *format, ...) = data;
- gchar *str;
- gsize size;
+ if (data->mgmt_path) {
+ if (unlink(data->mgmt_path) && errno != ENOENT)
+ connman_warn("Unable to unlink management socket %s: "
+ "%d", data->mgmt_path, errno);
+
+ g_free(data->mgmt_path);
+ data->mgmt_path = NULL;
+ }
- if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP))
- return FALSE;
+ if (data->mgmt_timer_id != 0) {
+ g_source_remove(data->mgmt_timer_id);
+ data->mgmt_timer_id = 0;
+ }
- g_io_channel_read_line(chan, &str, &size, NULL, NULL);
- cbf(str);
- g_free(str);
+ if (data->mgmt_event_id) {
+ g_source_remove(data->mgmt_event_id);
+ data->mgmt_event_id = 0;
+ }
- return TRUE;
+ if (data->mgmt_channel) {
+ g_io_channel_shutdown(data->mgmt_channel, FALSE, NULL);
+ g_io_channel_unref(data->mgmt_channel);
+ data->mgmt_channel = NULL;
+ }
}
-static int setup_log_read(int stdout_fd, int stderr_fd)
+static void ov_died(struct connman_task *task, int exit_code, void *user_data)
{
- GIOChannel *chan;
- int watch;
+ struct ov_private_data *data = user_data;
- chan = g_io_channel_unix_new(stdout_fd);
- g_io_channel_set_close_on_unref(chan, TRUE);
- watch = g_io_add_watch(chan, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
- can_read_data, connman_debug);
- g_io_channel_unref(chan);
+ /* Cancel any pending agent requests */
+ connman_agent_cancel(data->provider);
- if (watch == 0)
- return -EIO;
+ close_management_interface(data);
- chan = g_io_channel_unix_new(stderr_fd);
- g_io_channel_set_close_on_unref(chan, TRUE);
- watch = g_io_add_watch(chan, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
- can_read_data, connman_error);
- g_io_channel_unref(chan);
+ vpn_died(task, exit_code, data->provider);
- return watch == 0? -EIO : 0;
+ free_private_data(data);
}
-static int ov_connect(struct vpn_provider *provider,
- struct connman_task *task, const char *if_name,
- vpn_provider_connect_cb_t cb, const char *dbus_sender,
- void *user_data)
+static int run_connect(struct ov_private_data *data,
+ vpn_provider_connect_cb_t cb, void *user_data)
{
+ struct vpn_provider *provider = data->provider;
+ struct connman_task *task = data->task;
const char *option;
- int stdout_fd, stderr_fd;
int err = 0;
- option = vpn_provider_get_string(provider, "Host");
- if (!option) {
- connman_error("Host not set; cannot enable VPN");
- return -EINVAL;
- }
-
- task_append_config_data(provider, task);
-
option = vpn_provider_get_string(provider, "OpenVPN.ConfigFile");
if (!option) {
/*
@@ -390,6 +451,17 @@ static int ov_connect(struct vpn_provider *provider,
connman_task_add_argument(task, "--client", NULL);
}
+ if (data->mgmt_path) {
+ connman_task_add_argument(task, "--management", NULL);
+ connman_task_add_argument(task, data->mgmt_path, NULL);
+ connman_task_add_argument(task, "unix", NULL);
+ connman_task_add_argument(task, "--management-query-passwords",
+ NULL);
+ connman_task_add_argument(task, "--auth-retry", "interact");
+ }
+
+ connman_task_add_argument(task, "--syslog", NULL);
+
connman_task_add_argument(task, "--script-security", "2");
connman_task_add_argument(task, "--up",
@@ -408,7 +480,7 @@ static int ov_connect(struct vpn_provider *provider,
connman_task_add_argument(task, "CONNMAN_PATH",
connman_task_get_path(task));
- connman_task_add_argument(task, "--dev", if_name);
+ connman_task_add_argument(task, "--dev", data->if_name);
option = vpn_provider_get_string(provider, "OpenVPN.DeviceType");
if (option) {
connman_task_add_argument(task, "--dev-type", option);
@@ -431,25 +503,634 @@ static int ov_connect(struct vpn_provider *provider,
* moment. The problem is that when OpenVPN decides to switch
* from CONNECTED state to RECONNECTING and then to RESOLVE,
* it is not possible to do a DNS lookup. The DNS server is
- * not accessable through the tunnel anymore and so we end up
+ * not accessible through the tunnel anymore and so we end up
* trying to resolve the OpenVPN servers address.
*/
connman_task_add_argument(task, "--ping-restart", "0");
- err = connman_task_run(task, vpn_died, provider,
- NULL, &stdout_fd, &stderr_fd);
+ /*
+ * Disable connetion retrying when OpenVPN is connected over TCP.
+ * With TCP OpenVPN attempts to handle reconnection silently without
+ * reporting the error back when establishing a connection or
+ * reconnecting as succesful one. The latter causes trouble if the
+ * retries are not limited to 1 (no retry) as the interface is up and
+ * connman regards it as the default route and network ceases to work,
+ * including DNS.
+ */
+ option = vpn_provider_get_string(provider, "OpenVPN.Proto");
+ if (option && g_str_has_prefix(option, "tcp"))
+ connman_task_add_argument(task, "--connect-retry-max", "1");
+
+ err = connman_task_run(task, ov_died, data, NULL, NULL, NULL);
if (err < 0) {
+ data->cb = NULL;
+ data->user_data = NULL;
connman_error("openvpn failed to start");
- err = -EIO;
- goto done;
+ return -EIO;
+ } else {
+ /* This lets the caller know that the actual result of
+ * the operation will be reported to the callback */
+ return -EINPROGRESS;
+ }
+}
+
+static void ov_quote_credential(GString *line, const char *cred)
+{
+ if (!line)
+ return;
+
+ g_string_append_c(line, '"');
+
+ while (*cred != '\0') {
+
+ switch (*cred) {
+ case ' ':
+ case '"':
+ case '\\':
+ g_string_append_c(line, '\\');
+ break;
+ default:
+ break;
+ }
+
+ g_string_append_c(line, *cred++);
+ }
+
+ g_string_append_c(line, '"');
+}
+
+static void ov_return_credentials(struct ov_private_data *data,
+ const char *username, const char *password)
+{
+ GString *reply_string;
+ gchar *reply;
+ gsize len;
+
+ reply_string = g_string_new(NULL);
+
+ g_string_append(reply_string, "username \"Auth\" ");
+ ov_quote_credential(reply_string, username);
+ g_string_append_c(reply_string, '\n');
+
+ g_string_append(reply_string, "password \"Auth\" ");
+ ov_quote_credential(reply_string, password);
+ g_string_append_c(reply_string, '\n');
+
+ len = reply_string->len;
+ reply = g_string_free(reply_string, FALSE);
+
+ g_io_channel_write_chars(data->mgmt_channel, reply, len, NULL, NULL);
+ g_io_channel_flush(data->mgmt_channel, NULL);
+
+ memset(reply, 0, len);
+ g_free(reply);
+}
+
+static void ov_return_private_key_password(struct ov_private_data *data,
+ const char *privatekeypass)
+{
+ GString *reply_string;
+ gchar *reply;
+ gsize len;
+
+ reply_string = g_string_new(NULL);
+
+ g_string_append(reply_string, "password \"Private Key\" ");
+ ov_quote_credential(reply_string, privatekeypass);
+ g_string_append_c(reply_string, '\n');
+
+ len = reply_string->len;
+ reply = g_string_free(reply_string, FALSE);
+
+ g_io_channel_write_chars(data->mgmt_channel, reply, len, NULL, NULL);
+ g_io_channel_flush(data->mgmt_channel, NULL);
+
+ memset(reply, 0, len);
+ g_free(reply);
+}
+
+static void request_input_append_informational(DBusMessageIter *iter,
+ void *user_data)
+{
+ char *str = "string";
+
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str);
+ str = "informational";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+}
+
+static void request_input_append_mandatory(DBusMessageIter *iter,
+ void *user_data)
+{
+ char *str = "string";
+
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str);
+ str = "mandatory";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+}
+
+static void request_input_append_password(DBusMessageIter *iter,
+ void *user_data)
+{
+ char *str = "password";
+
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str);
+ str = "mandatory";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+}
+
+static void request_input_credentials_reply(DBusMessage *reply,
+ void *user_data)
+{
+ struct ov_private_data *data = user_data;
+ char *username = NULL;
+ char *password = NULL;
+ char *key;
+ DBusMessageIter iter, dict;
+ DBusError error;
+ int err = 0;
+
+ connman_info("provider %p", data->provider);
+
+ /*
+ * When connmand calls disconnect because of connection timeout no
+ * reply is received.
+ */
+ if (!reply) {
+ err = ENOENT;
+ goto err;
+ }
+
+ dbus_error_init(&error);
+
+ err = vpn_agent_check_and_process_reply_error(reply, data->provider,
+ data->task, data->cb, data->user_data);
+ if (err) {
+ /* Ensure cb is called only once */
+ data->cb = NULL;
+ data->user_data = NULL;
+ return;
+ }
+
+ if (!vpn_agent_check_reply_has_dict(reply)) {
+ err = ENOENT;
+ goto err;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+ dbus_message_iter_recurse(&iter, &dict);
+ while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter entry, value;
+
+ dbus_message_iter_recurse(&dict, &entry);
+ if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+ break;
+
+ dbus_message_iter_get_basic(&entry, &key);
+
+ if (g_str_equal(key, "OpenVPN.Password")) {
+ dbus_message_iter_next(&entry);
+ if (dbus_message_iter_get_arg_type(&entry)
+ != DBUS_TYPE_VARIANT)
+ break;
+ dbus_message_iter_recurse(&entry, &value);
+ if (dbus_message_iter_get_arg_type(&value)
+ != DBUS_TYPE_STRING)
+ break;
+ dbus_message_iter_get_basic(&value, &password);
+ vpn_provider_set_string_hide_value(data->provider,
+ key, password);
+
+ } else if (g_str_equal(key, "OpenVPN.Username")) {
+ dbus_message_iter_next(&entry);
+ if (dbus_message_iter_get_arg_type(&entry)
+ != DBUS_TYPE_VARIANT)
+ break;
+ dbus_message_iter_recurse(&entry, &value);
+ if (dbus_message_iter_get_arg_type(&value)
+ != DBUS_TYPE_STRING)
+ break;
+ dbus_message_iter_get_basic(&value, &username);
+ vpn_provider_set_string_hide_value(data->provider,
+ key, username);
+ }
+
+ dbus_message_iter_next(&dict);
+ }
+
+ if (!password || !username) {
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_AUTH_FAILED);
+ err = EACCES;
+ goto err;
+ }
+
+ ov_return_credentials(data, username, password);
+
+ return;
+
+err:
+ ov_connect_done(data, err);
+}
+
+static int request_credentials_input(struct ov_private_data *data)
+{
+ DBusMessage *message;
+ const char *path, *agent_sender, *agent_path;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ int err;
+ void *agent;
+
+ agent = connman_agent_get_info(data->dbus_sender, &agent_sender,
+ &agent_path);
+ if (!agent || !agent_path)
+ return -ESRCH;
+
+ message = dbus_message_new_method_call(agent_sender, agent_path,
+ VPN_AGENT_INTERFACE,
+ "RequestInput");
+ if (!message)
+ return -ENOMEM;
+
+ dbus_message_iter_init_append(message, &iter);
+
+ path = vpn_provider_get_path(data->provider);
+ dbus_message_iter_append_basic(&iter,
+ DBUS_TYPE_OBJECT_PATH, &path);
+
+ connman_dbus_dict_open(&iter, &dict);
+
+ if (vpn_provider_get_authentication_errors(data->provider))
+ vpn_agent_append_auth_failure(&dict, data->provider, NULL);
+
+ /* Request temporary properties to pass on to openvpn */
+ connman_dbus_dict_append_dict(&dict, "OpenVPN.Username",
+ request_input_append_mandatory, NULL);
+
+ connman_dbus_dict_append_dict(&dict, "OpenVPN.Password",
+ request_input_append_password, NULL);
+
+ vpn_agent_append_host_and_name(&dict, data->provider);
+
+ connman_dbus_dict_close(&iter, &dict);
+
+ err = connman_agent_queue_message(data->provider, message,
+ connman_timeout_input_request(),
+ request_input_credentials_reply, data, agent);
+
+ if (err < 0 && err != -EBUSY) {
+ connman_error("error %d sending agent request", err);
+ dbus_message_unref(message);
+
+ return err;
+ }
+
+ dbus_message_unref(message);
+
+ return -EINPROGRESS;
+}
+
+static void request_input_private_key_reply(DBusMessage *reply,
+ void *user_data)
+{
+ struct ov_private_data *data = user_data;
+ const char *privatekeypass = NULL;
+ const char *key;
+ DBusMessageIter iter, dict;
+ DBusError error;
+ int err = 0;
+
+ connman_info("provider %p", data->provider);
+
+ /*
+ * When connmand calls disconnect because of connection timeout no
+ * reply is received.
+ */
+ if (!reply) {
+ err = ENOENT;
+ goto err;
+ }
+
+ dbus_error_init(&error);
+
+ err = vpn_agent_check_and_process_reply_error(reply, data->provider,
+ data->task, data->cb, data->user_data);
+ if (err) {
+ /* Ensure cb is called only once */
+ data->cb = NULL;
+ data->user_data = NULL;
+ return;
+ }
+
+ if (!vpn_agent_check_reply_has_dict(reply)) {
+ err = ENOENT;
+ goto err;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+ dbus_message_iter_recurse(&iter, &dict);
+ while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter entry, value;
+
+ dbus_message_iter_recurse(&dict, &entry);
+ if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+ break;
+
+ dbus_message_iter_get_basic(&entry, &key);
+
+ if (g_str_equal(key, "OpenVPN.PrivateKeyPassword")) {
+ dbus_message_iter_next(&entry);
+ if (dbus_message_iter_get_arg_type(&entry)
+ != DBUS_TYPE_VARIANT)
+ break;
+ dbus_message_iter_recurse(&entry, &value);
+ if (dbus_message_iter_get_arg_type(&value)
+ != DBUS_TYPE_STRING)
+ break;
+ dbus_message_iter_get_basic(&value, &privatekeypass);
+ vpn_provider_set_string_hide_value(data->provider,
+ key, privatekeypass);
+
+ }
+
+ dbus_message_iter_next(&dict);
+ }
+
+ if (!privatekeypass) {
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_AUTH_FAILED);
+
+ err = EACCES;
+ goto err;
+ }
+
+ ov_return_private_key_password(data, privatekeypass);
+
+ return;
+
+err:
+ ov_connect_done(data, err);
+}
+
+static int request_private_key_input(struct ov_private_data *data)
+{
+ DBusMessage *message;
+ const char *path, *agent_sender, *agent_path;
+ const char *privatekeypass;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ int err;
+ void *agent;
+
+ /*
+ * First check if this is the second attempt to get the key within
+ * this connection. In such case there has been invalid Private Key
+ * Password and it must be reset, and queried from user.
+ */
+ if (data->failed_attempts_privatekey) {
+ vpn_provider_set_string_hide_value(data->provider,
+ "OpenVPN.PrivateKeyPassword", NULL);
+ } else {
+ /* If the encrypted Private key password is kept in memory and
+ * use it first. If authentication fails this is cleared,
+ * likewise it is when connman-vpnd is restarted.
+ */
+ privatekeypass = vpn_provider_get_string(data->provider,
+ "OpenVPN.PrivateKeyPassword");
+ if (privatekeypass) {
+ ov_return_private_key_password(data, privatekeypass);
+ goto out;
+ }
+ }
+
+ agent = connman_agent_get_info(data->dbus_sender, &agent_sender,
+ &agent_path);
+ if (!agent || !agent_path)
+ return -ESRCH;
+
+ message = dbus_message_new_method_call(agent_sender, agent_path,
+ VPN_AGENT_INTERFACE,
+ "RequestInput");
+ if (!message)
+ return -ENOMEM;
+
+ dbus_message_iter_init_append(message, &iter);
+
+ path = vpn_provider_get_path(data->provider);
+ dbus_message_iter_append_basic(&iter,
+ DBUS_TYPE_OBJECT_PATH, &path);
+
+ connman_dbus_dict_open(&iter, &dict);
+
+ connman_dbus_dict_append_dict(&dict, "OpenVPN.PrivateKeyPassword",
+ request_input_append_password, NULL);
+
+ vpn_agent_append_host_and_name(&dict, data->provider);
+
+ /* Do not allow to store or retrieve the encrypted Private Key pass */
+ vpn_agent_append_allow_credential_storage(&dict, false);
+ vpn_agent_append_allow_credential_retrieval(&dict, false);
+
+ /*
+ * Indicate to keep credentials, the enc Private Key password should
+ * not affect the credential storing.
+ */
+ vpn_agent_append_keep_credentials(&dict, true);
+
+ connman_dbus_dict_append_dict(&dict, "Enter Private Key password",
+ request_input_append_informational, NULL);
+
+ connman_dbus_dict_close(&iter, &dict);
+
+ err = connman_agent_queue_message(data->provider, message,
+ connman_timeout_input_request(),
+ request_input_private_key_reply, data, agent);
+
+ if (err < 0 && err != -EBUSY) {
+ connman_error("error %d sending agent request", err);
+ dbus_message_unref(message);
+
+ return err;
+ }
+
+ dbus_message_unref(message);
+
+out:
+ return -EINPROGRESS;
+}
+
+static gboolean ov_management_handle_input(GIOChannel *source,
+ GIOCondition condition, gpointer user_data)
+{
+ struct ov_private_data *data = user_data;
+ char *str = NULL;
+ int err = 0;
+ bool close = false;
+
+ if (condition & G_IO_IN) {
+ /*
+ * Just return if line is not read and str is not allocated.
+ * Condition check handles closing of the channel later.
+ */
+ if (g_io_channel_read_line(source, &str, NULL, NULL, NULL) !=
+ G_IO_STATUS_NORMAL)
+ return true;
+
+ str[strlen(str) - 1] = '\0';
+ connman_warn("openvpn request %s", str);
+
+ if (g_str_has_prefix(str, ">PASSWORD:Need 'Auth'")) {
+ /*
+ * Request credentials from the user
+ */
+ err = request_credentials_input(data);
+ if (err != -EINPROGRESS)
+ close = true;
+ } else if (g_str_has_prefix(str,
+ ">PASSWORD:Need 'Private Key'")) {
+ err = request_private_key_input(data);
+ if (err != -EINPROGRESS)
+ close = true;
+ } else if (g_str_has_prefix(str,
+ ">PASSWORD:Verification Failed: 'Auth'")) {
+ /*
+ * Add error only, state change indication causes
+ * signal to be sent, which is not desired when
+ * OpenVPN is in interactive mode.
+ */
+ vpn_provider_add_error(data->provider,
+ VPN_PROVIDER_ERROR_AUTH_FAILED);
+ /*
+ * According to the OpenVPN manual about management interface
+ * https://openvpn.net/community-resources/management-interface/
+ * this should be received but it does not seem to be reported
+ * when decrypting private key fails. This requires following
+ * patch for OpenVPN (at least <= 2.4.5) in order to work:
+ * https://git.sailfishos.org/mer-core/openvpn/blob/
+ * 4f4b4af116292a207416c8a990392e35a6fc41af/rpm/privatekey-
+ * passphrase-handling.diff
+ */
+ } else if (g_str_has_prefix(str, ">PASSWORD:Verification "
+ "Failed: 'Private Key'")) {
+ data->failed_attempts_privatekey++;
+ }
+
+ g_free(str);
+ } else if (condition & (G_IO_ERR | G_IO_HUP)) {
+ connman_warn("Management channel termination");
+ close = true;
}
- err = setup_log_read(stdout_fd, stderr_fd);
-done:
- if (cb)
- cb(provider, user_data, err);
+ if (close)
+ close_management_interface(data);
+
+ return true;
+}
+
+static int ov_management_connect_timer_cb(gpointer user_data)
+{
+ struct ov_private_data *data = user_data;
+
+ if (!data->mgmt_channel) {
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd >= 0) {
+ struct sockaddr_un remote;
+ int err;
+
+ memset(&remote, 0, sizeof(remote));
+ remote.sun_family = AF_UNIX;
+ g_strlcpy(remote.sun_path, data->mgmt_path,
+ sizeof(remote.sun_path));
+
+ err = connect(fd, (struct sockaddr *)&remote,
+ sizeof(remote));
+ if (err == 0) {
+ data->mgmt_channel = g_io_channel_unix_new(fd);
+ data->mgmt_event_id =
+ g_io_add_watch(data->mgmt_channel,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ ov_management_handle_input,
+ data);
+
+ connman_warn("Connected management socket");
+ data->mgmt_timer_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+ close(fd);
+ }
+ }
+
+ data->connect_attempts++;
+ if (data->connect_attempts > 30) {
+ connman_warn("Unable to connect management socket");
+ data->mgmt_timer_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static int ov_connect(struct vpn_provider *provider,
+ struct connman_task *task, const char *if_name,
+ vpn_provider_connect_cb_t cb, const char *dbus_sender,
+ void *user_data)
+{
+ const char *tmpdir;
+ struct ov_private_data *data;
+
+ data = g_try_new0(struct ov_private_data, 1);
+ if (!data)
+ return -ENOMEM;
+
+ vpn_provider_set_plugin_data(provider, data);
+ data->provider = vpn_provider_ref(provider);
+ data->task = task;
+ data->dbus_sender = g_strdup(dbus_sender);
+ data->if_name = g_strdup(if_name);
+ data->cb = cb;
+ data->user_data = user_data;
+
+ /*
+ * We need to use the management interface to provide
+ * the user credentials and password for decrypting private key.
+ */
+
+ /* Use env TMPDIR for creating management socket, fall back to /tmp */
+ tmpdir = getenv("TMPDIR");
+ if (!tmpdir || !*tmpdir)
+ tmpdir = "/tmp";
+
+ /* Set up the path for the management interface */
+ data->mgmt_path = g_strconcat(tmpdir, "/connman-vpn-management-",
+ vpn_provider_get_ident(provider), NULL);
+ if (unlink(data->mgmt_path) != 0 && errno != ENOENT) {
+ connman_warn("Unable to unlink management socket %s: %d",
+ data->mgmt_path, errno);
+ }
+
+ data->mgmt_timer_id = g_timeout_add(200,
+ ov_management_connect_timer_cb, data);
+
+ task_append_config_data(provider, task);
+
+ return run_connect(data, cb, user_data);
+}
+
+static void ov_disconnect(struct vpn_provider *provider)
+{
+ if (!provider)
+ return;
+
+ connman_agent_cancel(provider);
- return err;
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT);
}
static int ov_device_flags(struct vpn_provider *provider)
@@ -466,14 +1147,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;
@@ -499,6 +1182,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,
diff --git a/vpn/plugins/pptp.c b/vpn/plugins/pptp.c
index 3dc93b03..5fc861e4 100755
--- a/vpn/plugins/pptp.c
+++ b/vpn/plugins/pptp.c
@@ -137,7 +137,8 @@ static int pptp_notify(DBusMessage *msg, struct vpn_provider *provider)
DBG("authentication failure");
vpn_provider_set_string(provider, "PPTP.User", NULL);
- vpn_provider_set_string(provider, "PPTP.Password", NULL);
+ vpn_provider_set_string_hide_value(provider, "PPTP.Password",
+ NULL);
return VPN_STATE_AUTH_FAILURE;
}
@@ -282,16 +283,28 @@ struct request_input_reply {
static void request_input_reply(DBusMessage *reply, void *user_data)
{
struct request_input_reply *pptp_reply = user_data;
+ struct pptp_private_data *data;
const char *error = NULL;
char *username = NULL, *password = NULL;
char *key;
DBusMessageIter iter, dict;
+ int err;
DBG("provider %p", pptp_reply->provider);
- if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
- if (reply)
- error = dbus_message_get_error_name(reply);
+ if (!reply)
+ goto done;
+
+ data = pptp_reply->user_data;
+
+ err = vpn_agent_check_and_process_reply_error(reply,
+ pptp_reply->provider, data->task, data->cb,
+ data->user_data);
+ if (err) {
+ /* Ensure cb is called only once */
+ data->cb = NULL;
+ data->user_data = NULL;
+ error = dbus_message_get_error_name(reply);
goto done;
}
@@ -384,6 +397,9 @@ static int request_input(struct vpn_provider *provider,
connman_dbus_dict_open(&iter, &dict);
+ if (vpn_provider_get_authentication_errors(provider))
+ vpn_agent_append_auth_failure(&dict, provider, NULL);
+
vpn_agent_append_user_info(&dict, provider, "PPTP.User");
vpn_agent_append_host_and_name(&dict, provider);
@@ -424,13 +440,6 @@ static int run_connect(struct vpn_provider *provider,
char *str;
int err, i;
- host = vpn_provider_get_string(provider, "Host");
- if (!host) {
- connman_error("Host not set; cannot enable VPN");
- err = -EINVAL;
- goto done;
- }
-
if (!username || !password) {
DBG("Cannot connect username %s password %p",
username, password);
@@ -440,6 +449,7 @@ static int run_connect(struct vpn_provider *provider,
DBG("username %s password %p", username, password);
+ host = vpn_provider_get_string(provider, "Host");
str = g_strdup_printf("%s %s --nolaunchpppd --loglevel 2",
PPTP, host);
if (!str) {
@@ -594,7 +604,12 @@ static int pptp_error_code(struct vpn_provider *provider, int exit_code)
static void pptp_disconnect(struct vpn_provider *provider)
{
- vpn_provider_set_string(provider, "PPTP.Password", NULL);
+ if (!provider)
+ return;
+
+ vpn_provider_set_string_hide_value(provider, "PPTP.Password", NULL);
+
+ connman_agent_cancel(provider);
}
static struct vpn_driver vpn_driver = {
diff --git a/vpn/plugins/vpn.c b/vpn/plugins/vpn.c
index d9c6dbbb..c0b29778 100755
--- a/vpn/plugins/vpn.c
+++ b/vpn/plugins/vpn.c
@@ -33,6 +33,9 @@
#include <sys/types.h>
#include <linux/if_tun.h>
#include <net/if.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
#include <dbus/dbus.h>
@@ -47,6 +50,7 @@
#include "../vpn-provider.h"
#include "vpn.h"
+#include "../vpn.h"
struct vpn_data {
struct vpn_provider *provider;
@@ -85,8 +89,10 @@ static int stop_vpn(struct vpn_provider *provider)
vpn_driver_data = g_hash_table_lookup(driver_hash, name);
if (vpn_driver_data && vpn_driver_data->vpn_driver &&
- vpn_driver_data->vpn_driver->flags == VPN_FLAG_NO_TUN)
+ vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN) {
+ vpn_driver_data->vpn_driver->disconnect(data->provider);
return 0;
+ }
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = data->tun_flags | IFF_NO_PI;
@@ -284,7 +290,7 @@ static DBusMessage *vpn_notify(struct connman_task *task,
* We need to remove first the old address, just
* replacing the old address will not work as expected
* because the old address will linger in the interface
- * and not disapper so the clearing is needed here.
+ * and not disappear so the clearing is needed here.
*
* Also the state must change, otherwise the routes
* will not be set properly.
@@ -465,12 +471,155 @@ exist_err:
return ret;
}
+static gboolean is_numeric(const char *str)
+{
+ gint i;
+
+ if(!str || !(*str))
+ return false;
+
+ for(i = 0; str[i] ; i++) {
+ if(!g_ascii_isdigit(str[i]))
+ return false;
+ }
+
+ return true;
+}
+
+static gint get_gid(const char *group_name)
+{
+ gint gid = -1;
+ struct group *grp;
+
+ if(!group_name || !(*group_name))
+ return gid;
+
+ if (is_numeric(group_name)) {
+ gid_t group_id = (gid_t)g_ascii_strtoull(group_name, NULL, 10);
+ grp = getgrgid(group_id);
+ } else {
+ grp = getgrnam(group_name);
+ }
+
+ if (grp)
+ gid = grp->gr_gid;
+
+ return gid;
+}
+
+static gint get_uid(const char *user_name)
+{
+ gint uid = -1;
+ struct passwd *pw;
+
+ if(!user_name || !(*user_name))
+ return uid;
+
+ if (is_numeric(user_name)) {
+ uid_t user_id = (uid_t)g_ascii_strtoull(user_name, NULL, 10);
+ pw = getpwuid(user_id);
+ } else {
+ pw = getpwnam(user_name);
+ }
+
+ if (pw)
+ uid = pw->pw_uid;
+
+ return uid;
+}
+
+static gint get_supplementary_gids(gchar **groups, gid_t **gid_list)
+{
+ gint group_count = 0;
+ gid_t *list = NULL;
+ int i;
+
+ if (groups) {
+ for(i = 0; groups[i]; i++) {
+ group_count++;
+
+ list = (gid_t*)g_try_realloc(list,
+ sizeof(gid_t) * group_count);
+
+ if (!list) {
+ DBG("cannot allocate supplementary group list");
+ break;
+ }
+
+ list[i] = get_gid(groups[i]);
+ }
+ }
+
+ *gid_list = list;
+
+ return group_count;
+}
+
+static void vpn_task_setup(gpointer user_data)
+{
+ struct vpn_plugin_data *data;
+ gint uid;
+ gint gid;
+ gid_t *gid_list = NULL;
+ size_t gid_list_size;
+ const gchar *user;
+ const gchar *group;
+ gchar **suppl_groups;
+
+ data = user_data;
+ user = vpn_settings_get_binary_user(data);
+ group = vpn_settings_get_binary_group(data);
+ suppl_groups = vpn_settings_get_binary_supplementary_groups(data);
+
+ uid = get_uid(user);
+ gid = get_gid(group);
+ gid_list_size = get_supplementary_gids(suppl_groups, &gid_list);
+
+ DBG("vpn_task_setup uid:%d gid:%d supplementary group list size:%zu",
+ uid, gid, gid_list_size);
+
+
+ /* Change group if proper group name was set, requires CAP_SETGID.*/
+ if (gid > 0 && setgid(gid))
+ connman_error("error setting gid %d %s", gid, strerror(errno));
+
+ /* Set the supplementary groups if list exists, requires CAP_SETGID. */
+ if (gid_list_size && gid_list && setgroups(gid_list_size, gid_list))
+ connman_error("error setting gid list %s", strerror(errno));
+
+ /* Change user for the task if set, requires CAP_SETUID */
+ if (uid > 0 && setuid(uid))
+ connman_error("error setting uid %d %s", uid, strerror(errno));
+}
+
+
+static gboolean update_provider_state(gpointer data)
+{
+ struct vpn_provider *provider = data;
+ struct vpn_data *vpn_data;
+ int index;
+
+ DBG("");
+
+ vpn_data = vpn_provider_get_data(provider);
+
+ index = vpn_provider_get_index(provider);
+ DBG("index to watch %d", index);
+ vpn_provider_ref(provider);
+ vpn_data->watch = vpn_rtnl_add_newlink_watch(index,
+ vpn_newlink, provider);
+ connman_inet_ifup(index);
+
+ return FALSE;
+}
+
static int vpn_connect(struct vpn_provider *provider,
vpn_provider_connect_cb_t cb,
const char *dbus_sender, void *user_data)
{
struct vpn_data *data = vpn_provider_get_data(provider);
struct vpn_driver_data *vpn_driver_data;
+ struct vpn_plugin_data *vpn_plugin_data;
const char *name;
int ret = 0, tun_flags = IFF_TUN;
enum vpn_state state = VPN_STATE_UNKNOWN;
@@ -519,7 +668,7 @@ static int vpn_connect(struct vpn_provider *provider,
goto exist_err;
}
- if (vpn_driver_data->vpn_driver->flags != VPN_FLAG_NO_TUN) {
+ if (!(vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_TUN)) {
if (vpn_driver_data->vpn_driver->device_flags) {
tun_flags = vpn_driver_data->vpn_driver->device_flags(provider);
}
@@ -528,7 +677,30 @@ static int vpn_connect(struct vpn_provider *provider,
goto exist_err;
}
- data->task = connman_task_create(vpn_driver_data->program);
+
+ if (vpn_driver_data && vpn_driver_data->vpn_driver &&
+ vpn_driver_data->vpn_driver->flags & VPN_FLAG_NO_DAEMON) {
+
+ ret = vpn_driver_data->vpn_driver->connect(provider,
+ NULL, NULL, NULL, NULL, NULL);
+ if (ret) {
+ stop_vpn(provider);
+ goto exist_err;
+ }
+
+ DBG("%s started with dev %s",
+ vpn_driver_data->provider_driver.name, data->if_name);
+
+ data->state = VPN_STATE_CONNECT;
+
+ g_timeout_add(1, update_provider_state, provider);
+ return -EINPROGRESS;
+ }
+
+ vpn_plugin_data =
+ vpn_settings_get_vpn_plugin_config(vpn_driver_data->name);
+ data->task = connman_task_create(vpn_driver_data->program,
+ vpn_task_setup, vpn_plugin_data);
if (!data->task) {
ret = -ENOMEM;
@@ -609,7 +781,15 @@ static int vpn_disconnect(struct vpn_provider *provider)
}
data->state = VPN_STATE_DISCONNECT;
- connman_task_stop(data->task);
+
+ if (!vpn_driver_data->vpn_driver->disconnect) {
+ DBG("Driver has no disconnect() implementation, set provider "
+ "state to disconnect.");
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_DISCONNECT);
+ }
+
+ if (data->task)
+ connman_task_stop(data->task);
return 0;
}
@@ -633,7 +813,8 @@ static int vpn_remove(struct vpn_provider *provider)
data->watch = 0;
}
- connman_task_stop(data->task);
+ if (data->task)
+ connman_task_stop(data->task);
g_usleep(G_USEC_PER_SEC);
stop_vpn(provider);
@@ -699,6 +880,9 @@ int vpn_register(const char *name, struct vpn_driver *vpn_driver,
data->name = name;
data->program = program;
+ if (vpn_settings_parse_vpn_plugin_config(data->name) != 0)
+ DBG("No configuration provided for VPN plugin %s", data->name);
+
data->vpn_driver = vpn_driver;
data->provider_driver.name = name;
@@ -737,6 +921,7 @@ void vpn_unregister(const char *name)
return;
vpn_provider_driver_unregister(&data->provider_driver);
+ vpn_settings_delete_vpn_plugin_config(name);
g_hash_table_remove(driver_hash, name);
diff --git a/vpn/plugins/vpn.h b/vpn/plugins/vpn.h
index 318a10c5..893e9a1d 100755
--- a/vpn/plugins/vpn.h
+++ b/vpn/plugins/vpn.h
@@ -28,7 +28,8 @@
extern "C" {
#endif
-#define VPN_FLAG_NO_TUN 1
+#define VPN_FLAG_NO_TUN 1
+#define VPN_FLAG_NO_DAEMON 2
enum vpn_state {
VPN_STATE_UNKNOWN = 0,
diff --git a/vpn/plugins/vpnc.c b/vpn/plugins/vpnc.c
index af9dbe76..8350fc3c 100755
--- a/vpn/plugins/vpnc.c
+++ b/vpn/plugins/vpnc.c
@@ -39,15 +39,18 @@
#include <connman/task.h>
#include <connman/ipconfig.h>
#include <connman/dbus.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]))
-static DBusConnection *connection;
-
enum {
OPT_STRING = 1,
OPT_BOOLEAN = 2,
@@ -83,25 +86,104 @@ struct {
true },
};
+struct vc_private_data {
+ struct vpn_provider *provider;
+ struct connman_task *task;
+ char *if_name;
+ vpn_provider_connect_cb_t cb;
+ void *user_data;
+ int err_ch_id;
+ GIOChannel *err_ch;
+};
+
+static void vc_connect_done(struct vc_private_data *data, int err)
+{
+ DBG("data %p err %d", data, 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);
+ }
+}
+
+static void close_io_channel(struct vc_private_data *data, GIOChannel *channel)
+{
+ if (!data || !channel)
+ return;
+
+ if (data->err_ch == channel) {
+ DBG("closing stderr");
+
+ if (data->err_ch_id) {
+ g_source_remove(data->err_ch_id);
+ data->err_ch_id = 0;
+ }
+
+ if (!data->err_ch)
+ return;
+
+ g_io_channel_shutdown(data->err_ch, FALSE, NULL);
+ g_io_channel_unref(data->err_ch);
+
+ data->err_ch = NULL;
+ }
+}
+
+static void free_private_data(struct vc_private_data *data)
+{
+ DBG("data %p", data);
+
+ if (!data || !data->provider)
+ return;
+
+ DBG("provider %p", data->provider);
+
+ if (vpn_provider_get_plugin_data(data->provider) == data)
+ vpn_provider_set_plugin_data(data->provider, NULL);
+
+ vpn_provider_unref(data->provider);
+
+ g_free(data->if_name);
+ g_free(data);
+}
+
static int vc_notify(DBusMessage *msg, struct vpn_provider *provider)
{
DBusMessageIter iter, dict;
char *address = NULL, *netmask = NULL, *gateway = NULL;
struct connman_ipaddress *ipaddress;
const char *reason, *key, *value;
+ struct vc_private_data *data;
+ int type;
+
+ data = vpn_provider_get_plugin_data(provider);
dbus_message_iter_init(msg, &iter);
+ type = dbus_message_iter_get_arg_type(&iter);
+ if (type != DBUS_TYPE_STRING) {
+ DBG("invalid D-Bus arg type %d", type);
+ return VPN_STATE_FAILURE;
+ }
+
dbus_message_iter_get_basic(&iter, &reason);
dbus_message_iter_next(&iter);
if (!provider) {
connman_error("No provider found");
+ vc_connect_done(data, ENOENT);
return VPN_STATE_FAILURE;
}
- if (strcmp(reason, "connect"))
+ if (g_strcmp0(reason, "connect")) {
+ vc_connect_done(data, EIO);
return VPN_STATE_DISCONNECT;
+ }
dbus_message_iter_recurse(&iter, &dict);
@@ -109,8 +191,18 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider *provider)
DBusMessageIter entry;
dbus_message_iter_recurse(&dict, &entry);
+
+ type = dbus_message_iter_get_arg_type(&entry);
+ if (type != DBUS_TYPE_STRING)
+ continue;
+
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
+
+ type = dbus_message_iter_get_arg_type(&entry);
+ if (type != DBUS_TYPE_STRING)
+ continue;
+
dbus_message_iter_get_basic(&entry, &value);
DBG("%s = %s", key, value);
@@ -143,7 +235,7 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider *provider)
g_free(address);
g_free(netmask);
g_free(gateway);
-
+ vc_connect_done(data, EIO);
return VPN_STATE_FAILURE;
}
@@ -155,6 +247,7 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider *provider)
g_free(gateway);
connman_ipaddress_free(ipaddress);
+ vc_connect_done(data, 0);
return VPN_STATE_CONNECT;
}
@@ -263,27 +356,109 @@ static int vc_save(struct vpn_provider *provider, GKeyFile *keyfile)
return 0;
}
-static int vc_connect(struct vpn_provider *provider,
- struct connman_task *task, const char *if_name,
- vpn_provider_connect_cb_t cb, const char *dbus_sender,
- void *user_data)
+static void vc_died(struct connman_task *task, int exit_code, void *user_data)
{
- const char *option;
- int err = 0, fd;
+ struct vc_private_data *data = user_data;
- option = vpn_provider_get_string(provider, "Host");
- if (!option) {
- connman_error("Host not set; cannot enable VPN");
- err = -EINVAL;
- goto done;
+ DBG("task %p data %p exit_code %d user_data %p", task, data, exit_code,
+ user_data);
+
+ if (!data)
+ return;
+
+ if (data->provider) {
+ connman_agent_cancel(data->provider);
+
+ if (task)
+ vpn_died(task, exit_code, data->provider);
}
- option = vpn_provider_get_string(provider, "VPNC.IPSec.ID");
- if (!option) {
- connman_error("Group not set; cannot enable VPN");
- err = -EINVAL;
- goto done;
+
+ free_private_data(data);
+}
+
+static gboolean io_channel_cb(GIOChannel *source, GIOCondition condition,
+ gpointer user_data)
+{
+ struct vc_private_data *data;
+ const char *auth_failures[] = {
+ VPNC ": hash comparison failed",
+ VPNC ": authentication unsuccessful",
+ VPNC ": expected xauth packet; rejected",
+ NULL
+ };
+ const char *conn_failures[] = {
+ VPNC ": unknown host",
+ VPNC ": no response from target",
+ VPNC ": receiving packet: No route to host",
+ NULL
+ };
+ char *str;
+ int i;
+
+ data = user_data;
+
+ if ((condition & G_IO_IN) &&
+ g_io_channel_read_line(source, &str, NULL, NULL, NULL) ==
+ G_IO_STATUS_NORMAL) {
+ str[strlen(str) - 1] = '\0';
+
+ for (i = 0; auth_failures[i]; i++) {
+ if (g_str_has_prefix(str, auth_failures[i])) {
+ DBG("authentication failed: %s", str);
+
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_AUTH_FAILED);
+ }
+ }
+
+ for (i = 0; conn_failures[i]; i++) {
+ if (g_str_has_prefix(str, conn_failures[i])) {
+ DBG("connection failed: %s", str);
+
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_CONNECT_FAILED);
+ }
+ }
+
+ g_free(str);
+ } else if (condition & (G_IO_ERR | G_IO_HUP)) {
+ DBG("Channel termination");
+ close_io_channel(data, source);
+ return G_SOURCE_REMOVE;
}
+ return G_SOURCE_CONTINUE;
+}
+
+static int run_connect(struct vc_private_data *data)
+{
+ struct vpn_provider *provider;
+ struct connman_task *task;
+ const char *credentials[] = {"VPNC.IPSec.Secret", "VPNC.Xauth.Username",
+ "VPNC.Xauth.Password", NULL};
+ const char *if_name;
+ const char *option;
+ int err;
+ int fd_in;
+ int fd_err;
+ int i;
+
+ provider = data->provider;
+ task = data->task;
+ if_name = data->if_name;
+
+ DBG("provider %p task %p interface %s user_data %p", provider, task,
+ if_name, data->user_data);
+
+ /*
+ * Change to use C locale, options should be in ASCII according to
+ * documentation. To be on the safe side, set both LANG and LC_ALL.
+ * This is required especially when the VPNC processe is ran using an
+ * user other than root.
+ */
+ connman_task_add_variable(task,"LANG", "C");
+ connman_task_add_variable(task,"LC_ALL", "C");
+
connman_task_add_argument(task, "--non-inter", NULL);
connman_task_add_argument(task, "--no-detach", NULL);
@@ -298,8 +473,7 @@ static int vc_connect(struct vpn_provider *provider,
connman_task_add_argument(task, "--ifmode", "tun");
}
- connman_task_add_argument(task, "--script",
- SCRIPTDIR "/openconnect-script");
+ connman_task_add_argument(task, "--script", SCRIPTDIR "/vpn-script");
option = vpn_provider_get_string(provider, "VPNC.Debug");
if (option)
@@ -307,25 +481,354 @@ static int vc_connect(struct vpn_provider *provider,
connman_task_add_argument(task, "-", NULL);
- err = connman_task_run(task, vpn_died, provider,
- &fd, NULL, NULL);
+ err = connman_task_run(data->task, vc_died, data, &fd_in, NULL,
+ &fd_err);
if (err < 0) {
connman_error("vpnc failed to start");
err = -EIO;
goto done;
}
- err = vc_write_config_data(provider, fd);
+ err = vc_write_config_data(provider, fd_in);
+
+ if (err) {
+ DBG("config write error %s", strerror(err));
+ goto done;
+ }
+
+ err = -EINPROGRESS;
- close(fd);
+ data->err_ch = g_io_channel_unix_new(fd_err);
+ data->err_ch_id = g_io_add_watch(data->err_ch,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ (GIOFunc)io_channel_cb, data);
done:
- if (cb)
- cb(provider, user_data, err);
+ close(fd_in);
+
+ /*
+ * Clear out credentials if they are non-immutable. If this is called
+ * directly from vc_connect() all credentials are read from config and
+ * are set as immutable, so no change is done. In case a VPN agent is
+ * used these values should be reset to "-" in order to retrieve them
+ * from VPN agent next time VPN connection is established. This supports
+ * then partially defined credentials in .config and some can be
+ * retrieved using an agent.
+ */
+ for (i = 0; credentials[i]; i++) {
+ const char *key = credentials[i];
+ if (!vpn_provider_get_string_immutable(provider, key))
+ vpn_provider_set_string(provider, key, "-");
+ }
return err;
}
+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);
+
+ if (!user_data)
+ return;
+
+ str = user_data;
+ connman_dbus_dict_append_basic(iter, "Value", 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);
+
+ if (!user_data)
+ return;
+
+ str = user_data;
+ connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str);
+}
+
+static void request_input_append_informational(DBusMessageIter *iter,
+ void *user_data)
+{
+ char *str = "password";
+
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str);
+ str = "informational";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+
+ if (!user_data)
+ return;
+
+ str = user_data;
+ connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str);
+}
+
+static void request_input_append_to_dict(struct vpn_provider *provider,
+ DBusMessageIter *dict,
+ connman_dbus_append_cb_t function_cb, const char *key)
+{
+ const char *str;
+ bool immutable = false;
+
+ if (!provider || !dict || !function_cb || !key)
+ return;
+
+ str = vpn_provider_get_string(provider, key);
+
+ /* If value is "-", it is cleared by VPN agent */
+ if (!g_strcmp0(str, "-"))
+ str = NULL;
+
+ if (str)
+ immutable = vpn_provider_get_string_immutable(provider, key);
+
+ if (immutable) {
+ /* Hide immutable password types */
+ if (function_cb == request_input_append_password)
+ str = "********";
+
+ /* Send immutable as informational */
+ function_cb = request_input_append_informational;
+ }
+
+ connman_dbus_dict_append_dict(dict, key, function_cb, (void *)str);
+}
+
+static void request_input_credentials_reply(DBusMessage *reply, void *user_data)
+{
+ struct vc_private_data *data = user_data;
+ char *secret = NULL, *username = NULL, *password = NULL;
+ const char *key;
+ DBusMessageIter iter, dict;
+ int err;
+
+ DBG("provider %p", data->provider);
+
+ if (!reply)
+ goto err;
+
+ 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))
+ 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, "VPNC.IPSec.Secret")) {
+ 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, &secret);
+ vpn_provider_set_string_hide_value(data->provider,
+ key, secret);
+
+ } else if (g_str_equal(key, "VPNC.Xauth.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(data->provider, key, username);
+
+ } else if (g_str_equal(key, "VPNC.Xauth.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);
+ }
+
+ dbus_message_iter_next(&dict);
+ }
+
+ if (!secret || !username || !password)
+ goto err;
+
+ err = run_connect(data);
+ if (err != -EINPROGRESS)
+ goto err;
+
+ return;
+
+err:
+ vc_connect_done(data, EACCES);
+}
+
+static int request_input_credentials(struct vc_private_data *data,
+ const char* dbus_sender)
+{
+ DBusMessage *message;
+ const char *path, *agent_sender, *agent_path;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ int err;
+ void *agent;
+
+ if (!data || !data->provider)
+ return -ENOENT;
+
+ DBG("data %p provider %p sender %s", data, data->provider, dbus_sender);
+
+ agent = connman_agent_get_info(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_input_append_to_dict(data->provider, &dict,
+ request_input_append_password,
+ "VPNC.IPSec.Secret");
+ request_input_append_to_dict(data->provider, &dict,
+ request_input_append_mandatory,
+ "VPNC.Xauth.Username");
+ request_input_append_to_dict(data->provider, &dict,
+ request_input_append_password,
+ "VPNC.Xauth.Password");
+
+ 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);
+
+ dbus_message_unref(message);
+
+ if (err < 0 && err != -EBUSY) {
+ DBG("error %d sending agent request", err);
+ return err;
+ }
+
+ return -EINPROGRESS;
+}
+
+static int vc_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 vc_private_data *data;
+ const char *option;
+ bool username_set = false;
+ bool password_set = false;
+ bool ipsec_secret_set = false;
+ int err;
+
+ DBG("provider %p if_name %s user_data %p", provider, if_name, user_data);
+
+ option = vpn_provider_get_string(provider, "VPNC.IPSec.ID");
+ if (!option) {
+ connman_error("Group not set; cannot enable VPN");
+ return -EINVAL;
+ }
+
+ option = vpn_provider_get_string(provider, "VPNC.IPSec.Secret");
+ if (option && *option && g_strcmp0(option, "-"))
+ ipsec_secret_set = true;
+
+ option = vpn_provider_get_string(provider, "VPNC.Xauth.Username");
+ if (option && *option && g_strcmp0(option, "-"))
+ username_set = true;
+
+ option = vpn_provider_get_string(provider, "VPNC.Xauth.Password");
+ if (option && *option && g_strcmp0(option, "-"))
+ password_set = true;
+
+ data = g_try_new0(struct vc_private_data, 1);
+ if (!data)
+ return -ENOMEM;
+
+ vpn_provider_set_plugin_data(provider, data);
+ data->provider = vpn_provider_ref(provider);
+ data->task = task;
+ data->if_name = g_strdup(if_name);
+ data->cb = cb;
+ data->user_data = user_data;
+
+ if (!ipsec_secret_set || !username_set || !password_set) {
+ err = request_input_credentials(data, dbus_sender);
+ if (err != -EINPROGRESS) {
+ vc_connect_done(data, ECONNABORTED);
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_LOGIN_FAILED);
+ free_private_data(data);
+ }
+
+ return err;
+ }
+
+ return run_connect(data);
+}
+
+static void vc_disconnect(struct vpn_provider *provider)
+{
+ if (!provider)
+ return;
+
+ connman_agent_cancel(provider);
+}
+
static int vc_error_code(struct vpn_provider *provider, int exit_code)
{
switch (exit_code) {
@@ -361,6 +864,7 @@ static int vc_device_flags(struct vpn_provider *provider)
static struct vpn_driver vpn_driver = {
.notify = vc_notify,
.connect = vc_connect,
+ .disconnect = vc_disconnect,
.error_code = vc_error_code,
.save = vc_save,
.device_flags = vc_device_flags,
@@ -368,16 +872,12 @@ static struct vpn_driver vpn_driver = {
static int vpnc_init(void)
{
- connection = connman_dbus_get_connection();
-
return vpn_register("vpnc", &vpn_driver, VPNC);
}
static void vpnc_exit(void)
{
vpn_unregister("vpnc");
-
- dbus_connection_unref(connection);
}
CONNMAN_PLUGIN_DEFINE(vpnc, "vpnc plugin", VERSION,
diff --git a/vpn/plugins/wireguard.c b/vpn/plugins/wireguard.c
new file mode 100644
index 00000000..de2dbda3
--- /dev/null
+++ b/vpn/plugins/wireguard.c
@@ -0,0 +1,404 @@
+/*
+ * ConnMan VPN daemon
+ *
+ * Copyright (C) 2019 Daniel Wagner. All rights reserved.
+ *
+ * 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
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <glib.h>
+
+#define CONNMAN_API_SUBJECT_TO_CHANGE
+#include <connman/plugin.h>
+#include <connman/log.h>
+#include <connman/task.h>
+#include <connman/ipconfig.h>
+#include <connman/inet.h>
+#include <connman/dbus.h>
+#include <connman/setting.h>
+#include <connman/vpn-dbus.h>
+
+#include "../vpn-provider.h"
+#include "../vpn.h"
+
+#include "vpn.h"
+#include "wireguard.h"
+
+static int parse_key(const char *str, wg_key key)
+{
+ unsigned char *buf;
+ size_t len;
+
+ buf = g_base64_decode(str, &len);
+
+ if (len != 32) {
+ g_free(buf);
+ return -EINVAL;
+ }
+
+ memcpy(key, buf, 32);
+
+ g_free(buf);
+ return 0;
+}
+
+static int parse_allowed_ips(const char *allowed_ips, wg_peer *peer)
+{
+ struct wg_allowedip *curaip, *allowedip;
+ char buf[INET6_ADDRSTRLEN];
+ char **tokens, **toks;
+ char *send;
+ int i;
+
+ curaip = NULL;
+ tokens = g_strsplit(allowed_ips, ", ", -1);
+ for (i = 0; tokens[i]; i++) {
+ toks = g_strsplit(tokens[i], "/", -1);
+ if (g_strv_length(toks) != 2) {
+ DBG("Ignore AllowedIPs value %s", tokens[i]);
+ g_strfreev(toks);
+ continue;
+ }
+
+ allowedip = g_malloc0(sizeof(*allowedip));
+
+ if (inet_pton(AF_INET, toks[0], buf) == 1) {
+ allowedip->family = AF_INET;
+ memcpy(&allowedip->ip4, buf, sizeof(allowedip->ip4));
+ } else if (inet_pton(AF_INET6, toks[0], buf) == 1) {
+ allowedip->family = AF_INET6;
+ memcpy(&allowedip->ip6, buf, sizeof(allowedip->ip6));
+ } else {
+ DBG("Ignore AllowedIPs value %s", tokens[i]);
+ g_free(allowedip);
+ g_strfreev(toks);
+ continue;
+ }
+
+ allowedip->cidr = g_ascii_strtoull(toks[1], &send, 10);
+
+ if (!curaip)
+ peer->first_allowedip = allowedip;
+ else
+ curaip->next_allowedip = allowedip;
+
+ curaip = allowedip;
+ }
+
+ peer->last_allowedip = curaip;
+ g_strfreev(tokens);
+
+ return 0;
+}
+
+static int parse_endpoint(const char *host, const char *port, wg_peer *peer)
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sk;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0;
+
+ if (getaddrinfo(host, port, &hints, &result) < 0) {
+ DBG("Failed to resolve host address");
+ return -EINVAL;
+ }
+
+ for (rp = result; rp; rp = rp->ai_next) {
+ sk = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sk < 0)
+ continue;
+ if (connect(sk, rp->ai_addr, rp->ai_addrlen) != -1) {
+ /* success */
+ close(sk);
+ break;
+ }
+
+ close(sk);
+ }
+
+ if (!rp) {
+ freeaddrinfo(result);
+ return -EINVAL;
+ }
+
+ memcpy(&peer->endpoint.addr, rp->ai_addr, rp->ai_addrlen);
+ freeaddrinfo(result);
+
+ return 0;
+}
+
+static int parse_address(const char *address, const char *gateway,
+ struct connman_ipaddress **ipaddress)
+{
+ char buf[INET6_ADDRSTRLEN];
+ unsigned char prefixlen;
+ char **tokens;
+ char *end, *netmask;
+ int err;
+
+ tokens = g_strsplit(address, "/", -1);
+ if (g_strv_length(tokens) != 2) {
+ g_strfreev(tokens);
+ return -EINVAL;
+ }
+
+ prefixlen = g_ascii_strtoull(tokens[1], &end, 10);
+
+ if (inet_pton(AF_INET, tokens[0], buf) == 1) {
+ netmask = g_strdup_printf("%d.%d.%d.%d",
+ ((0xffffffff << (32 - prefixlen)) >> 24) & 0xff,
+ ((0xffffffff << (32 - prefixlen)) >> 16) & 0xff,
+ ((0xffffffff << (32 - prefixlen)) >> 8) & 0xff,
+ ((0xffffffff << (32 - prefixlen)) >> 0) & 0xff);
+
+ *ipaddress = connman_ipaddress_alloc(AF_INET);
+ err = connman_ipaddress_set_ipv4(*ipaddress, tokens[0],
+ netmask, gateway);
+ g_free(netmask);
+ } else if (inet_pton(AF_INET6, tokens[0], buf) == 1) {
+ *ipaddress = connman_ipaddress_alloc(AF_INET6);
+ err = connman_ipaddress_set_ipv6(*ipaddress, tokens[0],
+ prefixlen, gateway);
+ } else {
+ DBG("Invalid Wireguard.Address value");
+ err = -EINVAL;
+ }
+
+ g_strfreev(tokens);
+ if (err)
+ connman_ipaddress_free(*ipaddress);
+
+ return err;
+}
+
+struct ifname_data {
+ char *ifname;
+ bool found;
+};
+
+static void ifname_check_cb(int index, void *user_data)
+{
+ struct ifname_data *data = (struct ifname_data *)user_data;
+ char *ifname;
+
+ ifname = connman_inet_ifname(index);
+
+ if (!g_strcmp0(ifname, data->ifname))
+ data->found = true;
+}
+
+static char *get_ifname(void)
+{
+ struct ifname_data data;
+ int i;
+
+ for (i = 0; i < 256; i++) {
+ data.ifname = g_strdup_printf("wg%d", i);
+ data.found = false;
+ __vpn_ipconfig_foreach(ifname_check_cb, &data);
+
+ if (!data.found)
+ return data.ifname;
+
+ g_free(data.ifname);
+ }
+
+ return NULL;
+}
+
+struct wireguard_info {
+ struct wg_device device;
+ struct wg_peer peer;
+};
+
+static int wg_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 connman_ipaddress *ipaddress = NULL;
+ struct wireguard_info *info;
+ const char *option, *gateway;
+ char *ifname;
+ int err = -EINVAL;
+
+ info = g_malloc0(sizeof(struct wireguard_info));
+ info->peer.flags = WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS;
+ info->device.flags = WGDEVICE_HAS_PRIVATE_KEY;
+ info->device.first_peer = &info->peer;
+ info->device.last_peer = &info->peer;
+
+ vpn_provider_set_plugin_data(provider, info);
+
+ option = vpn_provider_get_string(provider, "WireGuard.ListenPort");
+ if (option) {
+ char *end;
+ info->device.listen_port = g_ascii_strtoull(option, &end, 10);
+ info->device.flags |= WGDEVICE_HAS_LISTEN_PORT;
+ }
+
+ option = vpn_provider_get_string(provider, "WireGuard.DNS");
+ if (option) {
+ err = vpn_provider_set_nameservers(provider, option);
+ if (err)
+ goto done;
+ }
+
+ option = vpn_provider_get_string(provider, "WireGuard.PrivateKey");
+ if (!option) {
+ DBG("WireGuard.PrivateKey is missing");
+ goto done;
+ }
+ err = parse_key(option, info->device.private_key);
+ if (err)
+ goto done;
+
+ option = vpn_provider_get_string(provider, "WireGuard.PublicKey");
+ if (!option) {
+ DBG("WireGuard.PublicKey is missing");
+ goto done;
+ }
+ err = parse_key(option, info->peer.public_key);
+ if (err)
+ goto done;
+
+ option = vpn_provider_get_string(provider, "WireGuard.PresharedKey");
+ if (option) {
+ info->peer.flags |= WGPEER_HAS_PRESHARED_KEY;
+ err = parse_key(option, info->peer.preshared_key);
+ if (err)
+ goto done;
+ }
+
+ option = vpn_provider_get_string(provider, "WireGuard.AllowedIPs");
+ if (!option) {
+ DBG("WireGuard.AllowedIPs is missing");
+ goto done;
+ }
+ err = parse_allowed_ips(option, &info->peer);
+ if (err)
+ goto done;
+
+ option = vpn_provider_get_string(provider,
+ "WireGuard.PersistentKeepalive");
+ if (option) {
+ char *end;
+ info->peer.persistent_keepalive_interval =
+ g_ascii_strtoull(option, &end, 10);
+ info->peer.flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
+ }
+
+ option = vpn_provider_get_string(provider, "WireGuard.EndpointPort");
+ if (!option)
+ option = "51820";
+
+ gateway = vpn_provider_get_string(provider, "Host");
+ err = parse_endpoint(gateway, option, &info->peer);
+ if (err)
+ goto done;
+
+ option = vpn_provider_get_string(provider, "WireGuard.Address");
+ if (!option) {
+ DBG("Missing WireGuard.Address configuration");
+ goto done;
+ }
+ err = parse_address(option, gateway, &ipaddress);
+ if (err)
+ goto done;
+
+ ifname = get_ifname();
+ if (!ifname) {
+ DBG("Failed to find an usable device name");
+ err = -ENOENT;
+ goto done;
+ }
+ stpncpy(info->device.name, ifname, sizeof(info->device.name));
+ g_free(ifname);
+
+ err = wg_add_device(info->device.name);
+ if (err) {
+ DBG("Failed to creating WireGuard device %s", info->device.name);
+ goto done;
+ }
+
+ err = wg_set_device(&info->device);
+ if (err) {
+ DBG("Failed to configure WireGuard device %s", info->device.name);
+ wg_del_device(info->device.name);
+ }
+
+ vpn_set_ifname(provider, info->device.name);
+ if (ipaddress)
+ vpn_provider_set_ipaddress(provider, ipaddress);
+
+done:
+ if (cb)
+ cb(provider, user_data, err);
+
+ connman_ipaddress_free(ipaddress);
+
+ return err;
+}
+
+static void wg_disconnect(struct vpn_provider *provider)
+{
+ struct wireguard_info *info;
+
+ info = vpn_provider_get_plugin_data(provider);
+ if (!info)
+ return;
+ vpn_provider_set_plugin_data(provider, NULL);
+
+ wg_del_device(info->device.name);
+
+ g_free(info);
+}
+
+static struct vpn_driver vpn_driver = {
+ .flags = VPN_FLAG_NO_TUN | VPN_FLAG_NO_DAEMON,
+ .connect = wg_connect,
+ .disconnect = wg_disconnect,
+};
+
+static int wg_init(void)
+{
+ return vpn_register("wireguard", &vpn_driver, NULL);
+}
+
+static void wg_exit(void)
+{
+ vpn_unregister("wireguard");
+}
+
+CONNMAN_PLUGIN_DEFINE(wireguard, "WireGuard VPN plugin", VERSION,
+ CONNMAN_PLUGIN_PRIORITY_DEFAULT, wg_init, wg_exit)
diff --git a/vpn/plugins/wireguard.h b/vpn/plugins/wireguard.h
new file mode 100644
index 00000000..e7a1bbf0
--- /dev/null
+++ b/vpn/plugins/wireguard.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/*
+ * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+ */
+
+#ifndef WIREGUARD_H
+#define WIREGUARD_H
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef uint8_t wg_key[32];
+typedef char wg_key_b64_string[((sizeof(wg_key) + 2) / 3) * 4 + 1];
+
+/* Cross platform __kernel_timespec */
+struct timespec64 {
+ int64_t tv_sec;
+ int64_t tv_nsec;
+};
+
+typedef struct wg_allowedip {
+ uint16_t family;
+ union {
+ struct in_addr ip4;
+ struct in6_addr ip6;
+ };
+ uint8_t cidr;
+ struct wg_allowedip *next_allowedip;
+} wg_allowedip;
+
+enum wg_peer_flags {
+ WGPEER_REMOVE_ME = 1U << 0,
+ WGPEER_REPLACE_ALLOWEDIPS = 1U << 1,
+ WGPEER_HAS_PUBLIC_KEY = 1U << 2,
+ WGPEER_HAS_PRESHARED_KEY = 1U << 3,
+ WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4
+};
+
+typedef struct wg_peer {
+ enum wg_peer_flags flags;
+
+ wg_key public_key;
+ wg_key preshared_key;
+
+ union {
+ struct sockaddr addr;
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+ } endpoint;
+
+ struct timespec64 last_handshake_time;
+ uint64_t rx_bytes, tx_bytes;
+ uint16_t persistent_keepalive_interval;
+
+ struct wg_allowedip *first_allowedip, *last_allowedip;
+ struct wg_peer *next_peer;
+} wg_peer;
+
+enum wg_device_flags {
+ WGDEVICE_REPLACE_PEERS = 1U << 0,
+ WGDEVICE_HAS_PRIVATE_KEY = 1U << 1,
+ WGDEVICE_HAS_PUBLIC_KEY = 1U << 2,
+ WGDEVICE_HAS_LISTEN_PORT = 1U << 3,
+ WGDEVICE_HAS_FWMARK = 1U << 4
+};
+
+typedef struct wg_device {
+ char name[IFNAMSIZ];
+ uint32_t ifindex;
+
+ enum wg_device_flags flags;
+
+ wg_key public_key;
+ wg_key private_key;
+
+ uint32_t fwmark;
+ uint16_t listen_port;
+
+ struct wg_peer *first_peer, *last_peer;
+} wg_device;
+
+#define wg_for_each_device_name(__names, __name, __len) for ((__name) = (__names), (__len) = 0; ((__len) = strlen(__name)); (__name) += (__len) + 1)
+#define wg_for_each_peer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer)
+#define wg_for_each_allowedip(__peer, __allowedip) for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); (__allowedip) = (__allowedip)->next_allowedip)
+
+int wg_set_device(wg_device *dev);
+int wg_get_device(wg_device **dev, const char *device_name);
+int wg_add_device(const char *device_name);
+int wg_del_device(const char *device_name);
+void wg_free_device(wg_device *dev);
+char *wg_list_device_names(void); /* first\0second\0third\0forth\0last\0\0 */
+void wg_key_to_base64(wg_key_b64_string base64, const wg_key key);
+int wg_key_from_base64(wg_key key, const wg_key_b64_string base64);
+bool wg_key_is_zero(const wg_key key);
+void wg_generate_public_key(wg_key public_key, const wg_key private_key);
+void wg_generate_private_key(wg_key private_key);
+void wg_generate_preshared_key(wg_key preshared_key);
+
+#endif
diff --git a/vpn/vpn-agent.c b/vpn/vpn-agent.c
index b0b582b7..ab6fea55 100755
--- a/vpn/vpn-agent.c
+++ b/vpn/vpn-agent.c
@@ -3,6 +3,7 @@
* Connection Manager
*
* Copyright (C) 2012 Intel Corporation. All rights reserved.
+ * Copyright (C) 2019 Jolla Ltd. All rights reserved.
*
* 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,8 @@
#include <gdbus.h>
#include <connman/log.h>
#include <connman/agent.h>
+#include <connman/vpn-dbus.h>
+#include <connman/task.h>
#include <vpn/vpn-provider.h>
#include "vpn-agent.h"
@@ -102,6 +105,7 @@ void vpn_agent_append_host_and_name(DBusMessageIter *iter,
struct user_info_data {
struct vpn_provider *provider;
const char *username_str;
+ const char *type_str;
};
static void request_input_append_user_info(DBusMessageIter *iter,
@@ -109,10 +113,10 @@ static void request_input_append_user_info(DBusMessageIter *iter,
{
struct user_info_data *data = user_data;
struct vpn_provider *provider = data->provider;
- const char *str = "string";
+ const char *str = NULL;
connman_dbus_dict_append_basic(iter, "Type",
- DBUS_TYPE_STRING, &str);
+ DBUS_TYPE_STRING, &data->type_str);
str = "mandatory";
connman_dbus_dict_append_basic(iter, "Requirement",
DBUS_TYPE_STRING, &str);
@@ -134,12 +138,150 @@ void vpn_agent_append_user_info(DBusMessageIter *iter,
.username_str = username_str
};
+ data.type_str = "string";
connman_dbus_dict_append_dict(iter, "Username",
request_input_append_user_info,
&data);
data.username_str = NULL;
+ data.type_str = "password";
connman_dbus_dict_append_dict(iter, "Password",
request_input_append_user_info,
&data);
}
+
+static void request_input_append_flag(DBusMessageIter *iter,
+ void *user_data)
+{
+ dbus_bool_t data = (dbus_bool_t)GPOINTER_TO_INT(user_data);
+ const char *str = NULL;
+
+ str = "boolean";
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str);
+
+ str = "control";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+
+ connman_dbus_dict_append_basic(iter, "Value",
+ DBUS_TYPE_BOOLEAN, &data);
+}
+
+void vpn_agent_append_allow_credential_storage(DBusMessageIter *iter,
+ bool allow)
+{
+ connman_dbus_dict_append_dict(iter, "AllowStoreCredentials",
+ request_input_append_flag,
+ GINT_TO_POINTER(allow));
+}
+
+void vpn_agent_append_allow_credential_retrieval(DBusMessageIter *iter,
+ bool allow)
+{
+ connman_dbus_dict_append_dict(iter, "AllowRetrieveCredentials",
+ request_input_append_flag,
+ 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;
+ const char *key;
+ const char* str;
+};
+
+static void request_input_append_failure(DBusMessageIter *iter,
+ void *user_data)
+{
+ struct failure_data *data;
+ const char *str;
+
+ data = user_data;
+
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &data->type_str);
+ str = "informational";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+
+ str = data->str;
+
+ /* Try to get information from provider about error */
+ if (!str)
+ str = vpn_provider_get_string(data->provider, data->key);
+
+ if (str)
+ connman_dbus_dict_append_basic(iter, "Value",
+ DBUS_TYPE_STRING, &str);
+}
+
+void vpn_agent_append_auth_failure(DBusMessageIter *iter,
+ struct vpn_provider *provider,
+ const char* information)
+{
+ struct failure_data data;
+ unsigned int value;
+
+ /* Skip if there are no auth errors */
+ value = vpn_provider_get_authentication_errors(provider);
+ if (!value)
+ return;
+
+ data.provider = provider;
+ data.type_str = "string";
+ data.key = "VpnAgent.AuthFailure";
+ data.str = information;
+
+ connman_dbus_dict_append_dict(iter, data.key,
+ request_input_append_failure, &data);
+}
+
+int vpn_agent_check_and_process_reply_error(DBusMessage *reply,
+ struct vpn_provider *provider,
+ struct connman_task *task,
+ vpn_provider_connect_cb_t cb, void *user_data)
+{
+ DBusError error;
+ int err;
+
+ if (!reply || !provider)
+ return EINVAL;
+
+ dbus_error_init(&error);
+
+ if (!dbus_set_error_from_message(&error, reply))
+ return 0;
+
+ if (!g_strcmp0(error.name, VPN_AGENT_INTERFACE ".Error.Canceled"))
+ err = ECANCELED;
+ else if (!g_strcmp0(error.name, "org.freedesktop.DBus.Error.Timeout"))
+ err = ETIMEDOUT;
+ else if (!g_strcmp0(error.name, "org.freedesktop.DBus.Error.NoReply"))
+ err = ENOMSG;
+ else
+ err = EACCES;
+
+ dbus_error_free(&error);
+
+ if (cb)
+ cb(provider, user_data, err);
+
+ if (task)
+ connman_task_stop(task);
+
+ /*
+ * VPN agent dialog cancel, timeout, broken connection should set the
+ * VPN back to idle state
+ */
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE);
+
+ return err;
+}
diff --git a/vpn/vpn-agent.h b/vpn/vpn-agent.h
index c7328d7f..dc797665 100755
--- a/vpn/vpn-agent.h
+++ b/vpn/vpn-agent.h
@@ -38,6 +38,18 @@ bool vpn_agent_check_reply_has_dict(DBusMessage *reply);
void vpn_agent_append_user_info(DBusMessageIter *iter,
struct vpn_provider *provider,
const char *username_str);
+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);
+int vpn_agent_check_and_process_reply_error(DBusMessage *reply,
+ struct vpn_provider *provider,
+ struct connman_task *task,
+ vpn_provider_connect_cb_t cb, void *user_data);
#ifdef __cplusplus
}
diff --git a/vpn/vpn-config.c b/vpn/vpn-config.c
index 5f0e749a..2fe03077 100755
--- a/vpn/vpn-config.c
+++ b/vpn/vpn-config.c
@@ -275,9 +275,6 @@ static int load_provider(GKeyFile *keyfile, const char *group,
config_provider->config_entry = g_strdup_printf("provider_%s",
config_provider->ident);
- g_hash_table_insert(config->provider_table,
- config_provider->ident, config_provider);
-
err = __vpn_provider_create_from_config(
config_provider->setting_strings,
config_provider->config_ident,
@@ -288,6 +285,10 @@ static int load_provider(GKeyFile *keyfile, const char *group,
goto err;
}
+ g_hash_table_insert(config->provider_table, config_provider->ident,
+ config_provider);
+
+
connman_info("Added provider configuration %s",
config_provider->ident);
return 0;
diff --git a/vpn/vpn-provider.c b/vpn/vpn-provider.c
index bb1a103a..0e01ca2a 100755
--- a/vpn/vpn-provider.c
+++ b/vpn/vpn-provider.c
@@ -3,6 +3,7 @@
* ConnMan VPN daemon
*
* Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ * Copyright (C) 2019 Jolla Ltd. All rights reserved.
*
* 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
@@ -87,10 +88,14 @@ struct vpn_provider {
bool immutable;
struct connman_ipaddress *prev_ipv4_addr;
struct connman_ipaddress *prev_ipv6_addr;
+ void *plugin_data;
+ unsigned int auth_error_counter;
+ unsigned int conn_error_counter;
};
static void append_properties(DBusMessageIter *iter,
struct vpn_provider *provider);
+static int vpn_provider_save(struct vpn_provider *provider);
static void free_route(gpointer data)
{
@@ -190,6 +195,39 @@ static int provider_routes_changed(struct vpn_provider *provider)
return 0;
}
+/*
+ * Sort vpn_route struct based on (similarly to the route key in hash table):
+ * 1) IP protocol number
+ * 2) Network addresses
+ * 3) Netmask addresses
+ * 4) Gateway addresses
+ */
+static gint compare_route(gconstpointer a, gconstpointer b)
+{
+ const struct vpn_route *route_a = a;
+ const struct vpn_route *route_b = b;
+ int difference;
+
+ /* If IP families differ, prefer IPv6 over IPv4 */
+ if (route_a->family != route_b->family) {
+ if (route_a->family < route_b->family)
+ return -1;
+
+ if (route_a->family > route_b->family)
+ return 1;
+ }
+
+ /* If networks differ, return */
+ if ((difference = g_strcmp0(route_a->network, route_b->network)))
+ return difference;
+
+ /* If netmasks differ, return. */
+ if ((difference = g_strcmp0(route_a->netmask, route_b->netmask)))
+ return difference;
+
+ return g_strcmp0(route_a->gateway, route_b->gateway);
+}
+
static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts)
{
DBusMessageIter dict, value, entry;
@@ -258,9 +296,11 @@ static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts)
} else {
switch (family) {
case '4':
+ case 4:
family = AF_INET;
break;
case '6':
+ case 6:
family = AF_INET6;
break;
default:
@@ -274,7 +314,7 @@ static GSList *read_route_dict(GSList *routes, DBusMessageIter *dicts)
route->netmask = g_strdup(netmask);
route->gateway = g_strdup(gateway);
- routes = g_slist_prepend(routes, route);
+ routes = g_slist_insert_sorted(routes, route, compare_route);
return routes;
}
@@ -404,6 +444,189 @@ static DBusMessage *get_properties(DBusConnection *conn,
return reply;
}
+/* True when lists are equal, false otherwise */
+static bool compare_network_lists(GSList *a, GSList *b)
+{
+ struct vpn_route *route_a, *route_b;
+ GSList *iter_a, *iter_b;
+
+ if (!a && !b)
+ return true;
+
+ /*
+ * If either of lists is NULL or the lists are of different size, the
+ * lists are not equal.
+ */
+ if ((!a || !b) || (g_slist_length(a) != g_slist_length(b)))
+ return false;
+
+ /* Routes are in sorted list so items can be compared in order. */
+ for (iter_a = a, iter_b = b; iter_a && iter_b;
+ iter_a = iter_a->next, iter_b = iter_b->next) {
+
+ route_a = iter_a->data;
+ route_b = iter_b->data;
+
+ if (compare_route(route_a, route_b))
+ return false;
+ }
+
+ return true;
+}
+
+static int set_provider_property(struct vpn_provider *provider,
+ const char *name, DBusMessageIter *value, int type)
+{
+ int err = 0;
+
+ DBG("provider %p", provider);
+
+ if (!provider || !name || !value)
+ return -EINVAL;
+
+ if (g_str_equal(name, "UserRoutes")) {
+ GSList *networks;
+
+ if (type != DBUS_TYPE_ARRAY)
+ return -EINVAL;
+
+ networks = get_user_networks(value);
+
+ if (compare_network_lists(provider->user_networks, networks)) {
+ g_slist_free_full(networks, free_route);
+ return -EALREADY;
+ }
+
+ del_routes(provider);
+ provider->user_networks = networks;
+ set_user_networks(provider, provider->user_networks);
+
+ if (!handle_routes)
+ send_routes(provider, provider->user_routes,
+ "UserRoutes");
+ } else {
+ const char *str;
+
+ if (type != DBUS_TYPE_STRING)
+ return -EINVAL;
+
+ dbus_message_iter_get_basic(value, &str);
+
+ DBG("property %s value %s", name, str);
+
+ /* Empty string clears the value, similar to ClearProperty. */
+ err = vpn_provider_set_string(provider, name,
+ *str ? str : NULL);
+ }
+
+ return err;
+}
+
+static GString *append_to_gstring(GString *str, const char *value)
+{
+ if (!str)
+ return g_string_new(value);
+
+ g_string_append_printf(str, ",%s", value);
+
+ return str;
+}
+
+static DBusMessage *set_properties(DBusMessageIter *iter, DBusMessage *msg,
+ void *data)
+{
+ struct vpn_provider *provider = data;
+ DBusMessageIter dict;
+ const char *key;
+ bool change = false;
+ GString *invalid = NULL;
+ GString *denied = NULL;
+ int type;
+ int err;
+
+ for (dbus_message_iter_recurse(iter, &dict);
+ dbus_message_iter_get_arg_type(&dict) ==
+ DBUS_TYPE_DICT_ENTRY;
+ dbus_message_iter_next(&dict)) {
+ DBusMessageIter entry, value;
+
+ dbus_message_iter_recurse(&dict, &entry);
+ /*
+ * Ignore invalid types in order to process all values in the
+ * dict. If there is an invalid type in between the dict there
+ * may already be changes on some values and breaking out here
+ * would have the provider in an inconsistent state, leaving
+ * the rest, potentially correct property values untouched.
+ */
+ if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+ continue;
+
+ dbus_message_iter_get_basic(&entry, &key);
+
+ DBG("key %s", key);
+
+ dbus_message_iter_next(&entry);
+ /* Ignore and report back all non variant types. */
+ if (dbus_message_iter_get_arg_type(&entry)
+ != DBUS_TYPE_VARIANT) {
+ invalid = append_to_gstring(invalid, key);
+ continue;
+ }
+
+ dbus_message_iter_recurse(&entry, &value);
+
+ type = dbus_message_iter_get_arg_type(&value);
+ /* Ignore and report back all invalid property types */
+ if (type != DBUS_TYPE_STRING && type != DBUS_TYPE_ARRAY) {
+ invalid = append_to_gstring(invalid, key);
+ continue;
+ }
+
+ err = set_provider_property(provider, key, &value, type);
+ switch (err) {
+ case 0:
+ change = true;
+ break;
+ case -EINVAL:
+ invalid = append_to_gstring(invalid, key);
+ break;
+ case -EPERM:
+ denied = append_to_gstring(denied, key);
+ break;
+ }
+ }
+
+ if (change)
+ vpn_provider_save(provider);
+
+ if (invalid || denied) {
+ DBusMessage *error;
+ char *invalid_str = g_string_free(invalid, FALSE);
+ char *denied_str = g_string_free(denied, FALSE);
+
+ /*
+ * If there are both invalid and denied properties report
+ * back invalid arguments. Add also the failed properties to
+ * the error message.
+ */
+ error = g_dbus_create_error(msg, (invalid ?
+ CONNMAN_ERROR_INTERFACE ".InvalidProperty" :
+ CONNMAN_ERROR_INTERFACE ".PermissionDenied"),
+ "%s %s%s%s", (invalid ? "Invalid properties" :
+ "Permission denied"),
+ (invalid ? invalid_str : ""),
+ (invalid && denied ? "," : ""),
+ (denied ? denied_str : ""));
+
+ g_free(invalid_str);
+ g_free(denied_str);
+
+ return error;
+ }
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -411,6 +634,7 @@ static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg,
DBusMessageIter iter, value;
const char *name;
int type;
+ int err;
DBG("conn %p", conn);
@@ -432,28 +656,20 @@ static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg,
dbus_message_iter_recurse(&iter, &value);
type = dbus_message_iter_get_arg_type(&value);
+ if (type == DBUS_TYPE_ARRAY && g_str_equal(name, "Properties"))
+ return set_properties(&value, msg, data);
- if (g_str_equal(name, "UserRoutes")) {
- GSList *networks;
-
- if (type != DBUS_TYPE_ARRAY)
- return __connman_error_invalid_arguments(msg);
-
- networks = get_user_networks(&value);
- if (networks) {
- del_routes(provider);
- provider->user_networks = networks;
- set_user_networks(provider, provider->user_networks);
-
- if (!handle_routes)
- send_routes(provider, provider->user_routes,
- "UserRoutes");
- }
- } else {
- const char *str;
-
- dbus_message_iter_get_basic(&value, &str);
- vpn_provider_set_string(provider, name, str);
+ err = set_provider_property(provider, name, &value, type);
+ switch (err) {
+ case 0:
+ vpn_provider_save(provider);
+ break;
+ case -EALREADY:
+ break;
+ case -EINVAL:
+ return __connman_error_invalid_property(msg);
+ default:
+ return __connman_error_failed(msg, -err);
}
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
@@ -464,6 +680,8 @@ static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg,
{
struct vpn_provider *provider = data;
const char *name;
+ bool change = false;
+ int err;
DBG("conn %p", conn);
@@ -474,16 +692,38 @@ static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg,
DBUS_TYPE_INVALID);
if (g_str_equal(name, "UserRoutes")) {
+ /*
+ * If either user_routes or user_networks has any entries
+ * there is a change that is to be written to settings file.
+ */
+ if (g_hash_table_size(provider->user_routes) ||
+ provider->user_networks)
+ change = true;
+
del_routes(provider);
if (!handle_routes)
send_routes(provider, provider->user_routes, name);
} else if (vpn_provider_get_string(provider, name)) {
- vpn_provider_set_string(provider, name, NULL);
+ err = vpn_provider_set_string(provider, name, NULL);
+ switch (err) {
+ case 0:
+ change = true;
+ /* fall through */
+ case -EALREADY:
+ break;
+ case -EINVAL:
+ return __connman_error_invalid_property(msg);
+ default:
+ return __connman_error_failed(msg, -err);
+ }
} else {
return __connman_error_invalid_property(msg);
}
+ if (change)
+ vpn_provider_save(provider);
+
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
@@ -496,7 +736,7 @@ static DBusMessage *do_connect(DBusConnection *conn, DBusMessage *msg,
DBG("conn %p provider %p", conn, provider);
err = __vpn_provider_connect(provider, msg);
- if (err < 0)
+ if (err < 0 && err != -EINPROGRESS)
return __connman_error_failed(msg, -err);
return NULL;
@@ -855,6 +1095,14 @@ static gchar **create_network_list(GSList *networks, gsize *count)
return result;
}
+static void reset_error_counters(struct vpn_provider *provider)
+{
+ if (!provider)
+ return;
+
+ provider->auth_error_counter = provider->conn_error_counter = 0;
+}
+
static int vpn_provider_save(struct vpn_provider *provider)
{
GKeyFile *keyfile;
@@ -862,6 +1110,11 @@ static int vpn_provider_save(struct vpn_provider *provider)
DBG("provider %p immutable %s", provider,
provider->immutable ? "yes" : "no");
+ reset_error_counters(provider);
+
+ if (provider->state == VPN_PROVIDER_STATE_FAILURE)
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE);
+
if (provider->immutable) {
/*
* Do not save providers that are provisioned via .config
@@ -1085,20 +1338,111 @@ static void connect_cb(struct vpn_provider *provider, void *user_data,
if (reply)
g_dbus_send_message(connection, reply);
- vpn_provider_indicate_error(provider,
+ switch (error) {
+ case EACCES:
+ 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:
+ /* No reply or timed out -> cancel the agent request */
+ connman_agent_cancel(provider);
+ vpn_provider_indicate_error(provider,
+ VPN_PROVIDER_ERROR_UNKNOWN);
+ break;
+ case ECANCELED:
+ /* fall through */
+ case ECONNABORTED:
+ /*
+ * This can be called in other situations than when
+ * VPN agent error checker is called. In such case
+ * react to both ECONNABORTED and ECANCELED as if the
+ * connection was called to terminate and do full
+ * disconnect -> idle cycle when being connected or
+ * ready. Setting the state also using the driver
+ * callback (vpn_set_state()) ensures that the driver is
+ * being disconnected as well and eventually the vpn
+ * process gets killed and vpn_died() is called to make
+ * the provider back to idle state.
+ */
+ if (provider->state == VPN_PROVIDER_STATE_CONNECT ||
+ provider->state ==
+ VPN_PROVIDER_STATE_READY) {
+ if (provider->driver->set_state)
+ provider->driver->set_state(provider,
+ VPN_PROVIDER_STATE_DISCONNECT);
+
+ vpn_provider_set_state(provider,
+ VPN_PROVIDER_STATE_DISCONNECT);
+ }
+ break;
+ default:
+ vpn_provider_indicate_error(provider,
VPN_PROVIDER_ERROR_CONNECT_FAILED);
- vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
- } else
+ vpn_provider_set_state(provider,
+ VPN_PROVIDER_STATE_FAILURE);
+ }
+ } else {
+ reset_error_counters(provider);
g_dbus_send_reply(connection, pending, DBUS_TYPE_INVALID);
+ }
dbus_message_unref(pending);
}
int __vpn_provider_connect(struct vpn_provider *provider, DBusMessage *msg)
{
+ DBusMessage *reply;
int err;
- DBG("provider %p", provider);
+ DBG("provider %p state %d", provider, provider->state);
+
+ switch (provider->state) {
+ /*
+ * When previous connection has failed change state to idle and let
+ * the connmand to process this information as well. Return -EINPROGRESS
+ * to indicate that transition is in progress and next connection
+ * attempt will continue as normal.
+ */
+ case VPN_PROVIDER_STATE_FAILURE:
+ if (provider->driver && provider->driver->set_state)
+ provider->driver->set_state(provider,
+ VPN_PROVIDER_STATE_IDLE);
+
+ vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE);
+ /* fall through */
+ /*
+ * If re-using a provider and it is being disconnected let it finish
+ * the disconnect process in order to let vpn.c:vpn_died() to get
+ * processed and everything cleaned up. Otherwise the reference
+ * counters are not decreased properly causing the previous interface
+ * being left up and its routes will remain in routing table. Return
+ * -EINPROGRESS to indicate that transition is in progress.
+ */
+ case VPN_PROVIDER_STATE_DISCONNECT:
+ /*
+ * Failure transition or disconnecting does not yield a
+ * message to be sent. Send in progress message to avoid
+ * D-Bus LimitsExceeded error message.
+ */
+ reply = __connman_error_in_progress(msg);
+ if (reply)
+ g_dbus_send_message(connection, reply);
+
+ return -EINPROGRESS;
+ case VPN_PROVIDER_STATE_UNKNOWN:
+ case VPN_PROVIDER_STATE_IDLE:
+ case VPN_PROVIDER_STATE_CONNECT:
+ case VPN_PROVIDER_STATE_READY:
+ break;
+ }
if (provider->driver && provider->driver->connect) {
const char *dbus_sender = dbus_message_get_sender(msg);
@@ -1573,6 +1917,22 @@ int vpn_provider_set_state(struct vpn_provider *provider,
return -EINVAL;
}
+void vpn_provider_add_error(struct vpn_provider *provider,
+ enum vpn_provider_error error)
+{
+ 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)
{
@@ -1581,16 +1941,7 @@ int vpn_provider_indicate_error(struct vpn_provider *provider,
vpn_provider_set_state(provider, VPN_PROVIDER_STATE_FAILURE);
- switch (error) {
- case VPN_PROVIDER_ERROR_UNKNOWN:
- case VPN_PROVIDER_ERROR_CONNECT_FAILED:
- break;
-
- case VPN_PROVIDER_ERROR_LOGIN_FAILED:
- case VPN_PROVIDER_ERROR_AUTH_FAILED:
- vpn_provider_set_state(provider, VPN_PROVIDER_STATE_IDLE);
- break;
- }
+ vpn_provider_add_error(provider, error);
if (provider->driver && provider->driver->set_state)
provider->driver->set_state(provider, provider->state);
@@ -1714,6 +2065,13 @@ static struct vpn_provider *vpn_provider_get(const char *identifier)
return provider;
}
+static void vpn_provider_put(const char *identifier)
+{
+ configuration_count_del();
+
+ g_hash_table_remove(provider_hash, identifier);
+}
+
static void provider_dbus_ident(char *ident)
{
int i, len = strlen(ident);
@@ -2101,8 +2459,6 @@ int __vpn_provider_create_from_config(GHashTable *settings,
provider->config_file = g_strdup(config_ident);
provider->config_entry = g_strdup(config_entry);
- provider_register(provider);
-
provider_resolv_host_addr(provider);
}
@@ -2137,6 +2493,7 @@ int __vpn_provider_create_from_config(GHashTable *settings,
return 0;
fail:
+ vpn_provider_put(ident);
g_free(ident);
g_slist_free_full(networks, free_route);
@@ -2184,7 +2541,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;
@@ -2201,35 +2558,54 @@ static int set_string(struct vpn_provider *provider,
hide_value ? "<not printed>" : value);
if (g_str_equal(key, "Type")) {
+ if (!g_strcmp0(provider->type, value))
+ return -EALREADY;
+
g_free(provider->type);
provider->type = g_ascii_strdown(value, -1);
send_value(provider->path, "Type", provider->type);
} else if (g_str_equal(key, "Name")) {
+ if (!g_strcmp0(provider->name, value))
+ return -EALREADY;
+
g_free(provider->name);
provider->name = g_strdup(value);
send_value(provider->path, "Name", provider->name);
} else if (g_str_equal(key, "Host")) {
+ if (!g_strcmp0(provider->host, value))
+ return -EALREADY;
+
g_free(provider->host);
provider->host = g_strdup(value);
send_value(provider->path, "Host", provider->host);
} else if (g_str_equal(key, "VPN.Domain") ||
g_str_equal(key, "Domain")) {
+ if (!g_strcmp0(provider->domain, value))
+ return -EALREADY;
+
g_free(provider->domain);
provider->domain = g_strdup(value);
send_value(provider->path, "Domain", provider->domain);
} else {
struct vpn_setting *setting;
+ bool replace = true;
setting = g_hash_table_lookup(provider->setting_strings, key);
- if (setting && !immutable &&
- setting->immutable) {
- DBG("Trying to set immutable variable %s", key);
- return -EPERM;
- }
+ if (setting) {
+ if (!immutable && setting->immutable) {
+ DBG("Trying to set immutable variable %s", key);
+ return -EPERM;
+ } else if (!g_strcmp0(setting->value, value)) {
+ return -EALREADY;
+ }
- setting = g_try_new0(struct vpn_setting, 1);
- if (!setting)
- return -ENOMEM;
+ g_free(setting->value);
+ replace = false;
+ } else {
+ setting = g_try_new0(struct vpn_setting, 1);
+ if (!setting)
+ return -ENOMEM;
+ }
setting->value = g_strdup(value);
setting->hide_value = hide_value;
@@ -2240,8 +2616,9 @@ static int set_string(struct vpn_provider *provider,
if (!hide_value)
send_value(provider->path, key, setting->value);
- g_hash_table_replace(provider->setting_strings,
- g_strdup(key), setting);
+ if (replace)
+ g_hash_table_replace(provider->setting_strings,
+ g_strdup(key), setting);
}
return 0;
@@ -2295,6 +2672,52 @@ const char *vpn_provider_get_string(struct vpn_provider *provider,
return setting->value;
}
+bool vpn_provider_get_boolean(struct vpn_provider *provider, const char *key,
+ bool default_value)
+{
+ struct vpn_setting *setting;
+
+ connman_info("provider %p key %s", provider, key);
+
+ setting = g_hash_table_lookup(provider->setting_strings, key);
+ if (!setting || !setting->value)
+ return default_value;
+
+ if (!g_strcmp0(setting->value, "true"))
+ return true;
+
+ if (!g_strcmp0(setting->value, "false"))
+ return false;
+
+ return default_value;
+}
+
+bool vpn_provider_get_string_immutable(struct vpn_provider *provider,
+ const char *key)
+{
+ struct vpn_setting *setting;
+
+ /* These values can be changed if the provider is not immutable */
+ if (g_str_equal(key, "Type")) {
+ return provider->immutable;
+ } else if (g_str_equal(key, "Name")) {
+ return provider->immutable;
+ } else if (g_str_equal(key, "Host")) {
+ return provider->immutable;
+ } else if (g_str_equal(key, "HostIP")) {
+ return provider->immutable;
+ } else if (g_str_equal(key, "VPN.Domain") ||
+ g_str_equal(key, "Domain")) {
+ return provider->immutable;
+ }
+
+ setting = g_hash_table_lookup(provider->setting_strings, key);
+ if (!setting)
+ return true; /* Not found, regard as immutable - no changes */
+
+ return setting->immutable;
+}
+
bool __vpn_provider_check_routes(struct vpn_provider *provider)
{
if (!provider)
@@ -2321,6 +2744,16 @@ void vpn_provider_set_data(struct vpn_provider *provider, void *data)
provider->driver_data = data;
}
+void *vpn_provider_get_plugin_data(struct vpn_provider *provider)
+{
+ return provider->plugin_data;
+}
+
+void vpn_provider_set_plugin_data(struct vpn_provider *provider, void *data)
+{
+ provider->plugin_data = data;
+}
+
void vpn_provider_set_index(struct vpn_provider *provider, int index)
{
DBG("index %d provider %p", index, provider);
@@ -2329,7 +2762,7 @@ void vpn_provider_set_index(struct vpn_provider *provider, int index)
provider->ipconfig_ipv4 = __vpn_ipconfig_create(index,
AF_INET);
if (!provider->ipconfig_ipv4) {
- DBG("Couldnt create ipconfig for IPv4");
+ DBG("Couldn't create ipconfig for IPv4");
goto done;
}
}
@@ -2340,7 +2773,7 @@ void vpn_provider_set_index(struct vpn_provider *provider, int index)
provider->ipconfig_ipv6 = __vpn_ipconfig_create(index,
AF_INET6);
if (!provider->ipconfig_ipv6) {
- DBG("Couldnt create ipconfig for IPv6");
+ DBG("Couldn't create ipconfig for IPv6");
goto done;
}
}
@@ -2442,7 +2875,7 @@ int vpn_provider_set_nameservers(struct vpn_provider *provider,
if (!nameservers)
return 0;
- provider->nameservers = g_strsplit(nameservers, " ", 0);
+ provider->nameservers = g_strsplit_set(nameservers, ", ", 0);
return 0;
}
@@ -2568,7 +3001,6 @@ void vpn_provider_driver_unregister(struct vpn_provider_driver *driver)
struct vpn_provider *provider = value;
if (provider && provider->driver &&
- provider->driver->type == driver->type &&
g_strcmp0(provider->driver->name,
driver->name) == 0) {
provider->driver = NULL;
@@ -2591,6 +3023,18 @@ const char *vpn_provider_get_path(struct vpn_provider *provider)
return provider->path;
}
+unsigned int vpn_provider_get_authentication_errors(
+ struct vpn_provider *provider)
+{
+ return provider->auth_error_counter;
+}
+
+unsigned int vpn_provider_get_connection_errors(
+ struct vpn_provider *provider)
+{
+ return provider->conn_error_counter;
+}
+
void vpn_provider_change_address(struct vpn_provider *provider)
{
switch (provider->family) {
diff --git a/vpn/vpn-provider.h b/vpn/vpn-provider.h
index 96452c11..0275d51a 100755
--- a/vpn/vpn-provider.h
+++ b/vpn/vpn-provider.h
@@ -3,6 +3,7 @@
* ConnMan VPN daemon
*
* Copyright (C) 2007-2013 Intel Corporation. All rights reserved.
+ * Copyright (C) 2019 Jolla Ltd. All rights reserved.
*
* 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
@@ -36,11 +37,6 @@ extern "C" {
* @short_description: Functions for handling providers
*/
-enum vpn_provider_type {
- VPN_PROVIDER_TYPE_UNKNOWN = 0,
- VPN_PROVIDER_TYPE_VPN = 1,
-};
-
enum vpn_provider_state {
VPN_PROVIDER_STATE_UNKNOWN = 0,
VPN_PROVIDER_STATE_IDLE = 1,
@@ -85,10 +81,16 @@ int vpn_provider_set_string_hide_value(struct vpn_provider *provider,
const char *key, const char *value);
const char *vpn_provider_get_string(struct vpn_provider *provider,
const char *key);
+bool vpn_provider_get_string_immutable(struct vpn_provider *provider,
+ const char *key);
+bool vpn_provider_get_boolean(struct vpn_provider *provider, const char *key,
+ bool default_value);
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);
@@ -97,6 +99,8 @@ int vpn_provider_get_index(struct vpn_provider *provider);
void vpn_provider_set_data(struct vpn_provider *provider, void *data);
void *vpn_provider_get_data(struct vpn_provider *provider);
+void vpn_provider_set_plugin_data(struct vpn_provider *provider, void *data);
+void *vpn_provider_get_plugin_data(struct vpn_provider *provider);
int vpn_provider_set_ipaddress(struct vpn_provider *provider,
struct connman_ipaddress *ipaddress);
int vpn_provider_set_pac(struct vpn_provider *provider,
@@ -114,6 +118,12 @@ const char *vpn_provider_get_save_group(struct vpn_provider *provider);
const char *vpn_provider_get_name(struct vpn_provider *provider);
const char *vpn_provider_get_host(struct vpn_provider *provider);
const char *vpn_provider_get_path(struct vpn_provider *provider);
+
+unsigned int vpn_provider_get_authentication_errors(
+ struct vpn_provider *provider);
+unsigned int vpn_provider_get_connection_errors(
+ struct vpn_provider *provider);
+
void vpn_provider_change_address(struct vpn_provider *provider);
void vpn_provider_clear_address(struct vpn_provider *provider, int family);
@@ -131,7 +141,6 @@ typedef void (* vpn_provider_password_cb_t) (struct vpn_provider *provider,
struct vpn_provider_driver {
const char *name;
- enum vpn_provider_type type;
int (*probe) (struct vpn_provider *provider);
int (*remove) (struct vpn_provider *provider);
int (*connect) (struct vpn_provider *provider,
diff --git a/vpn/vpn-settings.c b/vpn/vpn-settings.c
new file mode 100644
index 00000000..0eca2bc8
--- /dev/null
+++ b/vpn/vpn-settings.c
@@ -0,0 +1,253 @@
+/*
+ * ConnMan VPN daemon settings
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ * Copyright (C) 2018-2019 Jolla Ltd. All rights reserved.
+ * Contact: jussi.laakkonen@jolla.com
+ *
+ * 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
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <connman/log.h>
+
+#include "vpn.h"
+
+#define DEFAULT_INPUT_REQUEST_TIMEOUT 300 * 1000
+#define PLUGIN_CONFIGDIR CONFIGDIR "/vpn-plugin"
+#define VPN_GROUP "DACPrivileges"
+
+static struct {
+ unsigned int timeout_inputreq;
+ char *binary_user;
+ char *binary_group;
+ char **binary_supplementary_groups;
+} connman_vpn_settings = {
+ .timeout_inputreq = DEFAULT_INPUT_REQUEST_TIMEOUT,
+ .binary_user = NULL,
+ .binary_group = NULL,
+ .binary_supplementary_groups = NULL,
+};
+
+struct vpn_plugin_data {
+ char *binary_user;
+ char *binary_group;
+ char **binary_supplementary_groups;
+};
+
+GHashTable *plugin_hash = NULL;
+
+const char *vpn_settings_get_binary_user(struct vpn_plugin_data *data)
+{
+ if (data && data->binary_user)
+ return data->binary_user;
+
+ return connman_vpn_settings.binary_user;
+}
+
+const char *vpn_settings_get_binary_group(struct vpn_plugin_data *data)
+{
+ if (data && data->binary_group)
+ return data->binary_group;
+
+ return connman_vpn_settings.binary_group;
+}
+
+char **vpn_settings_get_binary_supplementary_groups(struct vpn_plugin_data *data)
+{
+ if (data && data->binary_supplementary_groups)
+ return data->binary_supplementary_groups;
+
+ return connman_vpn_settings.binary_supplementary_groups;
+}
+
+unsigned int __vpn_settings_get_timeout_inputreq()
+{
+ return connman_vpn_settings.timeout_inputreq;
+}
+
+static char *get_string(GKeyFile *config, const char *group, const char *key)
+{
+ char *str = g_key_file_get_string(config, group, key, NULL);
+ return str ? g_strstrip(str) : NULL;
+}
+
+static char **get_string_list(GKeyFile *config, const char *group,
+ const char *key)
+{
+ gsize len = 0;
+ char **str = g_key_file_get_string_list(config, group, key, &len, NULL);
+
+ if (str) {
+ guint i = 0;
+
+ for (i = 0; i < len ; i++) {
+ str[i] = g_strstrip(str[i]);
+ }
+ }
+
+ return str;
+}
+
+static void parse_config(GKeyFile *config, const char *file)
+{
+ const char *group = "General";
+ GError *error = NULL;
+ int timeout;
+
+ if (!config)
+ return;
+
+ DBG("parsing %s", file);
+
+ timeout = g_key_file_get_integer(config, group,
+ "InputRequestTimeout", &error);
+ if (!error && timeout >= 0)
+ connman_vpn_settings.timeout_inputreq = timeout * 1000;
+
+ g_clear_error(&error);
+
+ connman_vpn_settings.binary_user = get_string(config, VPN_GROUP,
+ "User");
+ connman_vpn_settings.binary_group = get_string(config, VPN_GROUP,
+ "Group");
+ connman_vpn_settings.binary_supplementary_groups = get_string_list(
+ config, VPN_GROUP,
+ "SupplementaryGroups");
+}
+
+struct vpn_plugin_data *vpn_settings_get_vpn_plugin_config(const char *name)
+{
+ struct vpn_plugin_data *data = NULL;
+
+ if (plugin_hash)
+ data = g_hash_table_lookup(plugin_hash, name);
+
+ return data;
+}
+
+static void vpn_plugin_data_free(gpointer data)
+{
+ struct vpn_plugin_data *plugin_data = (struct vpn_plugin_data*)data;
+
+ g_free(plugin_data->binary_user);
+ g_free(plugin_data->binary_group);
+ g_strfreev(plugin_data->binary_supplementary_groups);
+
+ g_free(data);
+}
+
+int vpn_settings_parse_vpn_plugin_config(const char *name)
+{
+ struct vpn_plugin_data *data;
+ gchar *file;
+ gchar *ext = ".conf";
+ GKeyFile *config;
+ gint err = 0;
+
+ if (!name || !*name)
+ return -EINVAL;
+
+ if (vpn_settings_get_vpn_plugin_config(name))
+ return -EALREADY;
+
+ file = g_strconcat(PLUGIN_CONFIGDIR, "/", name, ext, NULL);
+
+ config = __vpn_settings_load_config(file);
+
+ if (!config) {
+ err = -ENOENT;
+ DBG("Cannot load config %s for %s", file, name);
+ goto out;
+ }
+
+ data = g_try_new0(struct vpn_plugin_data, 1);
+
+ data->binary_user = get_string(config, VPN_GROUP, "User");
+ data->binary_group = get_string(config, VPN_GROUP, "Group");
+ data->binary_supplementary_groups = get_string_list(config, VPN_GROUP,
+ "SupplementaryGroups");
+
+ DBG("Loaded settings for %s: %s - %s",
+ name, data->binary_user, data->binary_group);
+
+ if (!plugin_hash)
+ plugin_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, vpn_plugin_data_free);
+
+ g_hash_table_replace(plugin_hash, g_strdup(name), data);
+
+ g_key_file_unref(config);
+
+out:
+ g_free(file);
+ return err;
+}
+
+void vpn_settings_delete_vpn_plugin_config(const char *name)
+{
+ if (plugin_hash && name)
+ g_hash_table_remove(plugin_hash, name);
+}
+
+GKeyFile *__vpn_settings_load_config(const char *file)
+{
+ GError *err = NULL;
+ GKeyFile *keyfile;
+
+ keyfile = g_key_file_new();
+
+ g_key_file_set_list_separator(keyfile, ',');
+
+ if (!g_key_file_load_from_file(keyfile, file, 0, &err)) {
+ if (err->code != G_FILE_ERROR_NOENT) {
+ connman_error("Parsing %s failed: %s", file,
+ err->message);
+ }
+
+ g_error_free(err);
+ g_key_file_unref(keyfile);
+ return NULL;
+ }
+
+ return keyfile;
+}
+
+int __vpn_settings_init(const char *file)
+{
+ GKeyFile *config;
+
+ config = __vpn_settings_load_config(file);
+ parse_config(config, file);
+ if (config)
+ g_key_file_unref(config);
+
+ return 0;
+}
+
+void __vpn_settings_cleanup()
+{
+ g_free(connman_vpn_settings.binary_user);
+ g_free(connman_vpn_settings.binary_group);
+ g_strfreev(connman_vpn_settings.binary_supplementary_groups);
+
+ if (plugin_hash) {
+ g_hash_table_destroy(plugin_hash);
+ plugin_hash = NULL;
+ }
+}
diff --git a/vpn/vpn.h b/vpn/vpn.h
index 26b13d70..125450b5 100755
--- a/vpn/vpn.h
+++ b/vpn/vpn.h
@@ -88,7 +88,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);
@@ -119,3 +119,19 @@ char *__vpn_config_get_string(GKeyFile *key_file,
const char *group_name, const char *key, GError **error);
char **__vpn_config_get_string_list(GKeyFile *key_file,
const char *group_name, const char *key, gsize *length, GError **error);
+
+int __vpn_settings_init(const char *file);
+void __vpn_settings_cleanup(void);
+GKeyFile *__vpn_settings_load_config(const char *file);
+unsigned int __vpn_settings_get_timeout_inputreq(void);
+
+struct vpn_plugin_data;
+
+int vpn_settings_parse_vpn_plugin_config(const char* plugin_name);
+void vpn_settings_delete_vpn_plugin_config(const char *name);
+struct vpn_plugin_data* vpn_settings_get_vpn_plugin_config(const char *name);
+
+const char * vpn_settings_get_binary_user(struct vpn_plugin_data *data);
+const char * vpn_settings_get_binary_group(struct vpn_plugin_data *data);
+char ** vpn_settings_get_binary_supplementary_groups(
+ struct vpn_plugin_data *data);