diff options
author | Niraj Kumar Goit <niraj.g@samsung.com> | 2020-12-01 18:14:19 +0530 |
---|---|---|
committer | Niraj Kumar Goit <niraj.g@samsung.com> | 2021-01-04 05:50:23 +0000 |
commit | c647a4b6f1132684c9d8b8ad71ec38d81147b278 (patch) | |
tree | b346bee32f204af1f3b13cfacb2ad3caa9a9c484 /vpn | |
parent | 04d1dbacf6aabbb44f16f6776496192964d460d8 (diff) | |
download | connman-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-x | vpn/main.c | 7 | ||||
-rwxr-xr-x | vpn/plugins/l2tp.c | 45 | ||||
-rw-r--r-- | vpn/plugins/libwireguard.c | 998 | ||||
-rwxr-xr-x | vpn/plugins/openconnect.c | 1318 | ||||
-rwxr-xr-x | vpn/plugins/openvpn.c | 798 | ||||
-rwxr-xr-x | vpn/plugins/pptp.c | 39 | ||||
-rwxr-xr-x | vpn/plugins/vpn.c | 197 | ||||
-rwxr-xr-x | vpn/plugins/vpn.h | 3 | ||||
-rwxr-xr-x | vpn/plugins/vpnc.c | 564 | ||||
-rw-r--r-- | vpn/plugins/wireguard.c | 404 | ||||
-rw-r--r-- | vpn/plugins/wireguard.h | 103 | ||||
-rwxr-xr-x | vpn/vpn-agent.c | 146 | ||||
-rwxr-xr-x | vpn/vpn-agent.h | 12 | ||||
-rwxr-xr-x | vpn/vpn-config.c | 7 | ||||
-rwxr-xr-x | vpn/vpn-provider.c | 554 | ||||
-rwxr-xr-x | vpn/vpn-provider.h | 21 | ||||
-rw-r--r-- | vpn/vpn-settings.c | 253 | ||||
-rwxr-xr-x | vpn/vpn.h | 18 |
18 files changed, 5138 insertions, 349 deletions
@@ -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; + } +} @@ -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); |