blob: af8dfe6b7df9c0737b2aba017262217ff62760e3 [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2010 BMW Car IT GmbH. All rights reserved.
* Copyright (C) 2011 Google, Inc. 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
*
*/
/*
* OpenVPN plugin built on top of vpn support.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <glib.h>
#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/plugin.h>
#include <connman/dbus.h>
#include <connman/provider.h>
#include <connman/inet.h>
#include <connman/ipconfig.h>
#include <connman/log.h>
#include <connman/task.h>
#ifdef ENABLE_NSS
#include "nss.h"
#endif
#include "vpn.h"
#define _DBG_VPN(fmt, arg...) DBG(DBG_VPN, fmt, ## arg)
#define MAXDNS 10 /* max DNS servers per lease */
#define MAXDOMAINS 10 /* max DNS search domains per lease */
enum openvpn_state {
OV_STATE_UNKNOWN,
OV_STATE_CONNECTING,
OV_STATE_WAIT,
OV_STATE_AUTH,
OV_STATE_GET_CONFIG,
OV_STATE_ASSIGN_IP,
OV_STATE_ADD_ROUTES,
OV_STATE_CONNECTED,
OV_STATE_RECONNECTING,
OV_STATE_RESOLVE,
OV_STATE_EXITING
};
static DBusConnection *connection;
static const char *kLSBReleaseFilename = "/etc/lsb-release";
static const char *kChromeOSReleaseName = "CHROMEOS_RELEASE_NAME";
static const char *kChromeOSReleaseVersion = "CHROMEOS_RELEASE_VERSION";
static char *platform_name;
static char *platform_version;
struct mgmt_server_data {
struct connman_provider *provider;
char *vpnhost;
struct sockaddr_in addr;
GIOChannel *channel;
guint watch;
char *username;
char *password;
char *state_id;
enum openvpn_state state;
};
struct openvpn_data {
struct mgmt_server_data *server;
struct connman_ipaddress ipaddr;
in_addr_t vpn_addr;
in_addr_t gw_addr;
uint32_t gw_index;
char *tls_auth_file;
};
struct ov_route {
char *host;
char *netmask;
char *gateway;
};
static void destroy_route(gpointer user_data)
{
struct ov_route *route = user_data;
g_free(route->host);
g_free(route->netmask);
g_free(route->gateway);
g_free(route);
}
static void ov_provider_append_routes(gpointer key, gpointer value,
gpointer user_data)
{
struct ov_route *route = value;
struct connman_provider *provider = user_data;
connman_provider_append_route(provider, route->host, route->netmask,
route->gateway);
}
static struct ov_route *ov_route_lookup(const char *key, const char *prefix_key,
GHashTable *routes)
{
unsigned long idx;
const char *start;
char *end;
struct ov_route *route;
if (g_str_has_prefix(key, prefix_key) == FALSE)
return NULL;
start = key + strlen(prefix_key);
idx = g_ascii_strtoull(start, &end, 10);
if (idx == 0 && start == end) {
connman_error("%s: string conversion failed %s",
__func__, start);
return NULL;
}
route = g_hash_table_lookup(routes, GINT_TO_POINTER(idx));
if (route == NULL) {
route = g_try_new0(struct ov_route, 1);
if (route == NULL) {
connman_error("%s: out of memory", __func__);
return NULL;
}
g_hash_table_replace(routes, GINT_TO_POINTER(idx),
route);
}
return route;
}
static void ov_append_route(const char *key, const char *value, GHashTable *routes)
{
struct ov_route *route;
/*
* OpenVPN pushes routing tupples (host, nw, gw) as several
* environment values, e.g.
*
* route_gateway_2 = 10.242.2.13
* route_netmask_2 = 255.255.0.0
* route_network_2 = 192.168.0.0
* route_gateway_1 = 10.242.2.13
* route_netmask_1 = 255.255.255.255
* route_network_1 = 10.242.2.1
*
* The hash table is used to group the separate environment
* variables together. It also makes sure all tupples are
* complete even when OpenVPN pushes the information in a
* wrong order (unlikely).
*/
route = ov_route_lookup(key, "route_network_", routes);
if (route != NULL) {
route->host = g_strdup(value);
return;
}
route = ov_route_lookup(key, "route_netmask_", routes);
if (route != NULL) {
route->netmask = g_strdup(value);
return;
}
route = ov_route_lookup(key, "route_gateway_", routes);
if (route != NULL)
route->gateway = g_strdup(value);
}
static int mask2prefix(const char *str)
{
in_addr_t mask = inet_network(str);
int prefix;
for (prefix = 0; mask != 0; prefix++)
mask <<= 1;
return prefix;
}
struct dns_state {
char **servers;
int n_servers;
char **domains;
int n_domains;
};
static void parse_dns_cleanup(struct dns_state *dns)
{
g_strfreev(dns->domains);
g_strfreev(dns->servers);
g_free(dns);
}
static struct dns_state *parse_dns_init(void)
{
struct dns_state *dns = g_try_new0(struct dns_state, 1);
if (dns == NULL) {
connman_error("%s: no memory for dns state", __func__);
return NULL;
}
dns->servers = g_try_new0(char *, MAXDNS + 1);
if (dns->servers == NULL) {
connman_error("%s: no memory for dns_servers", __func__);
goto bad;
}
dns->domains = g_try_new0(char *, MAXDNS + 1);
if (dns->domains == NULL) {
connman_error("%s: no memory for dns_domains", __func__);
goto bad;
}
return dns;
bad:
parse_dns_cleanup(dns);
return NULL;
}
static void parse_dhcp_option(struct dns_state *dns, gchar **opts)
{
#define iseq(_a, _b) (g_ascii_strcasecmp((_a), (_b)) == 0)
if (opts[1] == NULL)
return;
if (iseq(opts[1], "DOMAIN") && opts[2] != NULL) {
if (dns->n_domains >= MAXDNS) {
connman_warn("%s: too many DNS search domains, "
"%s ignored", __func__, opts[2]);
return;
}
dns->domains[dns->n_domains++] = g_strdup(opts[2]);
} else if (iseq(opts[1], "DNS") && opts[2] != NULL) {
if (dns->n_servers >= MAXDNS) {
connman_warn("%s: too many DNS servers, %s ignored",
__func__, opts[2]);
return;
}
dns->servers[dns->n_servers++] = g_strdup(opts[2]);
}
#undef iseq
}
static void set_string(char **storage, const char *str)
{
g_free(*storage);
*storage = g_strdup(str);
}
struct foreign_option {
const char *key;
const char *value;
};
static gint foreign_option_compare(gconstpointer a, gconstpointer b,
gpointer user_data) {
const struct foreign_option *option_a = (const void *) a;
const struct foreign_option *option_b = (const void *) b;
return g_strcmp0(option_a->key, option_b->key);
}
static void parse_foreign_option(gpointer opt, gpointer dns) {
const struct foreign_option *option = (const void *) opt;
gchar **options = g_strsplit(option->value, " ", 3);
if (options[0] != NULL && g_ascii_strcasecmp(options[0], "dhcp-option") == 0)
parse_dhcp_option(dns, options);
g_strfreev(options);
}
static int ov_notify(DBusMessage *msg, struct connman_provider *provider)
{
#define iseq(_a, _b) (g_ascii_strcasecmp((_a), (_b)) == 0)
struct openvpn_data *data = vpn_get_specific_data(provider);
DBusMessageIter iter, dict;
const char *reason, *key;
char *value;
GSequence *foreign_options;
GHashTable *routes;
struct dns_state *dns;
const char *trusted_ip = NULL;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &reason);
dbus_message_iter_next(&iter);
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &reason);
dbus_message_iter_next(&iter);
_DBG_VPN("reason %s provider %p", reason, provider);
if (provider == NULL) {
connman_error("%s: no provider found", __func__);
return VPN_STATE_FAILURE;
}
if (strcmp(reason, "up") != 0)
return VPN_STATE_DISCONNECT;
dns = parse_dns_init();
if (dns == NULL) {
connman_error("%s: no memory for dns state", __func__);
return VPN_STATE_FAILURE;
}
foreign_options = g_sequence_new(NULL);
routes = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, destroy_route);
dbus_message_iter_recurse(&iter, &dict);
while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry;
dbus_message_iter_recurse(&dict, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_get_basic(&entry, &value);
_DBG_VPN("%s = %s", key, value);
if (iseq(key, "ifconfig_local")) {
set_string(&data->ipaddr.local, value);
data->ipaddr.mask |= CONNMAN_IPCONFIG_LOCAL;
} else if (iseq(key, "ifconfig_broadcast")) {
set_string(&data->ipaddr.broadcast, value);
data->ipaddr.mask |= CONNMAN_IPCONFIG_BCAST;
} else if (iseq(key, "ifconfig_netmask")) {
data->ipaddr.prefixlen = mask2prefix(value);
data->ipaddr.mask |= CONNMAN_IPCONFIG_PREFIX;
} else if (iseq(key, "ifconfig_remote")) {
set_string(&data->ipaddr.peer, value);
data->ipaddr.mask |= CONNMAN_IPCONFIG_PEER;
} else if (iseq(key, "route_vpn_gateway")) {
set_string(&data->ipaddr.gateway, value);
data->ipaddr.mask |= CONNMAN_IPCONFIG_GW;
} else if (iseq(key, "trusted_ip")) {
trusted_ip = value;
} else if (iseq(key, "tun_mtu")) {
int mtu = (int) strtol(value, NULL, 10);
/* NB: restrict min MTU per dhcpcd-script */
if (576 <= mtu) {
data->ipaddr.mtu = mtu;
data->ipaddr.mask |= CONNMAN_IPCONFIG_MTU;
} else
connman_warn("%s: MTU %s too small (ignored)",
__func__, value);
} else if (g_str_has_prefix(key, "foreign_option_") == TRUE) {
struct foreign_option *option =
g_new0(struct foreign_option, 1);
option->key = key;
option->value = value;
g_sequence_insert_sorted(foreign_options,
option,
foreign_option_compare,
NULL);
} else if (g_str_has_prefix(key, "route_") == TRUE) {
ov_append_route(key, value, routes);
}
dbus_message_iter_next(&dict);
}
if ((data->ipaddr.mask & CONNMAN_IPCONFIG_GW) && trusted_ip != NULL) {
/*
* If we are creating a new default gateway, we need
* to first pin a host route to the VPN server so that
* encapsulated packets continue to be sent to the VPN
* server endpoint.
*
* NB: route_net_gateway may have the gateway but we also
* need the interface index and both are returned by
* calling connman_inet_get_route so don't bother.
*/
_DBG_VPN("add host VPN server route for %s", trusted_ip);
data->vpn_addr = inet_addr(trusted_ip);
if (!connman_inet_get_route(data->vpn_addr,
&data->gw_index,
&data->gw_addr,
NULL)) {
connman_error("%s: unable to get route to %s",
__func__, trusted_ip);
return VPN_STATE_FAILURE;
}
connman_inet_add_hostroute(data->gw_index, data->vpn_addr,
data->gw_addr);
}
g_sequence_foreach(foreign_options, parse_foreign_option, dns);
if (dns->n_servers > 0) {
data->ipaddr.dns_servers = dns->servers;
data->ipaddr.mask |= CONNMAN_IPCONFIG_DNS;
}
if (dns->n_domains > 0) {
data->ipaddr.search_domains = dns->domains;
data->ipaddr.mask |= CONNMAN_IPCONFIG_SEARCH;
}
connman_provider_ipconfig_set(provider, &data->ipaddr);
parse_dns_cleanup(dns);
/* NB: dns configuration is sent for restarts */
data->ipaddr.mask &= ~(CONNMAN_IPCONFIG_DNS|CONNMAN_IPCONFIG_SEARCH);
g_sequence_free(foreign_options);
g_hash_table_foreach(routes, ov_provider_append_routes, provider);
/* TODO(sleffler): routes are not sent for restarts */
g_hash_table_destroy(routes);
return VPN_STATE_CONNECT;
#undef iseq
}
/*
* OpenVPN management channel support.
*/
static const char *kOTPProperty = "OpenVPN.OTP";
static const char *kPasswordProperty = "OpenVPN.Password";
static const char *kPINProperty = "OpenVPN.Pkcs11.PIN";
static const char *kUserProperty = "OpenVPN.User";
static void mgmt_event_need_auth(struct mgmt_server_data *data,
const char *tag);
static void mgmt_event_need_tpm_pin(struct mgmt_server_data *data,
const char *tag);
static void mgmt_event_need_static_challenge(struct mgmt_server_data *data,
const char *tag, char *cr);
static void mgmt_event_need_dynamic_challenge(struct mgmt_server_data *data,
const char *tag);
static void mgmt_close_channel(struct mgmt_server_data *data);
static ssize_t mgmt_send(struct mgmt_server_data *data, const char *s,
ssize_t len, const char *func)
{
int fd = g_io_channel_unix_get_fd(data->channel);
ssize_t n = write(fd, s, len);
if (n < 0)
connman_error("%s: write error: %s", func, strerror(errno));
else if (n != len)
connman_error("%s: write short, %zd != %zd", func, n, len);
return n;
}
static void mgmt_send_username(struct mgmt_server_data *data,
const char *tag, const char *username, const char *func)
{
gchar *str = g_strdup_printf("username \"%s\" %s\n", tag, username);
_DBG_VPN("tag %s username %s", tag,
connman_log_get_masked_value(kUserProperty, username));
mgmt_send(data, str, strlen(str), func);
g_free(str);
}
static void mgmt_send_password(struct mgmt_server_data *data,
const char *tag, const char *password, const char *func)
{
gchar *str = g_strdup_printf("password \"%s\" \"%s\"\n",
tag, password);
_DBG_VPN("tag %s password %s", tag,
connman_log_get_masked_value(kPasswordProperty, password));
mgmt_send(data, str, strlen(str), func);
g_free(str);
}
/*
* Send a static challenge/response msg on receiving a
* respose; see the protocol description below.
*/
static void mgmt_send_static_challenge_response(struct mgmt_server_data *data,
const char *tag, const char *username, const char *password,
const char *otp)
{
gchar *reply;
_DBG_VPN("tag %s username %s password %s otp %s", tag,
connman_log_get_masked_value(kUserProperty, username),
connman_log_get_masked_value(kPasswordProperty, password),
connman_log_get_masked_value(kOTPProperty, otp));
mgmt_send_username(data, tag, username, __func__);
reply = g_strdup_printf("SCRV1:%s:%s", password, otp);
mgmt_send_password(data, tag, reply, __func__);
g_free(reply);
}
/*
* Send a dynamic challenge/response msg on receiving a
* response; see the protocol description below.
*/
static void mgmt_send_dynamic_challenge_response(struct mgmt_server_data *data,
const char *tag, const char *username, const char *state_id,
const char *otp)
{
gchar *reply;
_DBG_VPN("username %s state_id %s otp %s",
connman_log_get_masked_value(kUserProperty, username),
state_id,
connman_log_get_masked_value(kOTPProperty, otp));
mgmt_send_username(data, tag, username, __func__);
reply = g_strdup_printf("CRV1::%s:%s", state_id, otp);
mgmt_send_password(data, tag, reply, __func__);
g_free(reply);
}
/*
* >STATE:* msg support.
*
* State messages are of the form:
* >STATE:<date>,<state>,<detail>,<local-ip>,<remote-ip>
* where:
* <date> is the current time (since epoch) in seconds
* <state> is one of:
* INITIAL, CONNECTING, WAIT, AUTH, GET_CONFIG, ASSIGN_IP,
* ADD_ROUTES, CONNECTED, RECONNECTING, EXITING, RESOLVE, TCP_CONNECT
* <detail> is a free-form string giving details about the state change
* <local-ip> is a dotted-quad for the local IPv4 address (when available)
* <remote-ip> is a dotted-quad for the remote IPv4 address (when available)
*/
static void mgmt_event_state(struct mgmt_server_data *data, const char *tag)
{
const char *cp = strchr(tag, ',');
if (cp == NULL) {
connman_error("%s: bad >STATE msg \"%s\"", __func__, tag);
return;
}
cp++; /* skip ',' */
if (g_str_has_prefix(cp, "CONNECTING,") == TRUE) {
data->state = OV_STATE_CONNECTING;
} else if (g_str_has_prefix(cp, "WAIT,") == TRUE) {
data->state = OV_STATE_WAIT;
} else if (g_str_has_prefix(cp, "AUTH,") == TRUE) {
data->state = OV_STATE_AUTH;
} else if (g_str_has_prefix(cp, "GET_CONFIG,") == TRUE) {
data->state = OV_STATE_GET_CONFIG;
} else if (g_str_has_prefix(cp, "ASSIGN_IP,") == TRUE) {
data->state = OV_STATE_ASSIGN_IP;
} else if (g_str_has_prefix(cp, "ADD_ROUTES,") == TRUE) {
data->state = OV_STATE_ADD_ROUTES;
} else if (g_str_has_prefix(cp, "CONNECTED,") == TRUE) {
data->state = OV_STATE_CONNECTED;
} else if (g_str_has_prefix(cp, "RECONNECTING,") == TRUE) {
data->state = OV_STATE_RECONNECTING;
vpn_reconnect(data->provider);
} else if (g_str_has_prefix(cp, "RESOLVE,") == TRUE) {
data->state = OV_STATE_RESOLVE;
} else if (g_str_has_prefix(cp, "EXITING,") == TRUE) {
data->state = OV_STATE_EXITING;
} else {
connman_warn("%s: unknown STATE, openvpn sent \"%s\"",
__func__, tag);
return;
}
/* TODO(sleffler) dispatch signal for UI? */
}
static void mgmt_send_state(struct mgmt_server_data *data,
const char *tag, const char *func)
{
gchar *str = g_strdup_printf("state %s\n", tag);
_DBG_VPN("state %s", tag);
mgmt_send(data, str, strlen(str), func);
g_free(str);
}
/*
* Authentication callback support. When an Auth request is
* received over the management channel we post a request to
* an associated Agent for the necessary data. On receiving
* a reply we get a callback with the state block we construct
* to hold the data required to complete the request. If there
* is no reply or an error occurs we also get a callback but
* a "failure" indication. If we hit an Agent failure we shut
* down the management channel which will cause openvpn to abort
* and unwind our side to force the provider and service into
* an failure state.
*/
struct need_auth_cb_data {
struct mgmt_server_data *data;
char *tag;
};
static void *new_auth_cb_arg(struct mgmt_server_data *data, const char *tag)
{
struct need_auth_cb_data *arg = g_try_new0(struct need_auth_cb_data, 1);
if (arg != NULL) {
arg->data = data;
arg->tag = g_strdup(tag);
}
return arg;
}
static void free_auth_cb_arg(struct need_auth_cb_data *arg)
{
g_free(arg->tag);
g_free(arg);
}
/*
* Agent callback for user/password and dynamic challenge/response
* authentication requests (static c/r handled separately).
*/
static void need_auth_cb(struct connman_provider *provider, void *arg,
int success)
{
struct need_auth_cb_data *data = arg;
if (success)
mgmt_event_need_auth(data->data, data->tag);
else
mgmt_close_channel(data->data);
free_auth_cb_arg(data);
}
/*
* >PASSWORD:Need "Auth" support for user/password requests.
* If we have all the data just send a response. Otherwise
* notify the Agent and wait for a reply.
*/
static void mgmt_event_need_auth(struct mgmt_server_data *data,
const char *tag)
{
struct connman_provider *provider = data->provider;
const char *user = connman_provider_get_string(provider, kUserProperty);
const char *passwd = connman_provider_get_string(provider,
kPasswordProperty);
if (user == NULL || passwd == NULL) {
struct need_auth_cb_data *arg = new_auth_cb_arg(data, tag);
_DBG_VPN("no username/password yet; defer reply");
if (connman_provider_request_input(provider,
need_auth_cb, new_auth_cb_arg(data, tag),
"User", kUserProperty,
"Password", kPasswordProperty, NULL) < 0)
need_auth_cb(provider, arg, FALSE);
return;
}
mgmt_send_username(data, tag, user, __func__);
mgmt_send_password(data, tag, passwd, __func__);
}
/*
* Agent callback for TPM PIN requests.
*/
static void need_tpm_pin_cb(struct connman_provider *provider, void *arg,
int success)
{
struct need_auth_cb_data *data = arg;
if (success)
mgmt_event_need_tpm_pin(data->data, data->tag);
else
mgmt_close_channel(data->data);
free_auth_cb_arg(data);
}
/*
* >PASSWORD:Need TPM PIN support.
*/
static void mgmt_event_need_tpm_pin(struct mgmt_server_data *data,
const char *tag)
{
struct connman_provider *provider = data->provider;
const char *pin = connman_provider_get_string(provider, kPINProperty);
if (pin == NULL) {
struct need_auth_cb_data *arg = new_auth_cb_arg(data, tag);
if (connman_provider_request_input(provider,
need_tpm_pin_cb, new_auth_cb_arg(data, tag),
"PIN", kPINProperty, NULL) < 0)
need_tpm_pin_cb(provider, arg, FALSE);
_DBG_VPN("no PIN; defer reply");
} else
mgmt_send_password(data, tag, pin, __func__);
}
/*
* Agent callback for static challenge/response requests.
*/
static void need_static_challenge_cb(struct connman_provider *provider,
void *arg, int success)
{
struct need_auth_cb_data *data = arg;
if (success)
mgmt_event_need_static_challenge(data->data, data->tag, NULL);
else
mgmt_close_channel(data->data);
free_auth_cb_arg(data);
}
/*
* >PASSWORD:Need "Auth" support for static challenge/response requests.
*/
static void mgmt_event_need_static_challenge(struct mgmt_server_data *data,
const char *tag, char *cr)
{
struct connman_provider *provider = data->provider;
const char *username = connman_provider_get_string(provider,
kUserProperty);
const char *password = connman_provider_get_string(provider,
kPasswordProperty);
const char *otp = connman_provider_get_string(provider, kOTPProperty);
if (username == NULL || password == NULL || otp == NULL) {
struct need_auth_cb_data *arg = new_auth_cb_arg(data, tag);
/* missing required credentials, request input */
_DBG_VPN("missing username/passwd/OTP; defer reply");
if (connman_provider_request_input(provider,
need_static_challenge_cb, arg,
"User", kUserProperty,
"Password", kPasswordProperty,
"OTP", kOTPProperty, NULL) < 0)
need_static_challenge_cb(provider, arg, FALSE);
} else {
gchar *password_base64 = g_base64_encode(
(const guchar *)password, strlen(password));
gchar *otp_base64 = g_base64_encode(
(const guchar *)otp, strlen(otp));
mgmt_send_static_challenge_response(data, tag, username,
password_base64, otp_base64);
g_free(otp_base64);
g_free(password_base64);
/* NB: never re-use OTP, also avoids looping */
connman_provider_clear_property(provider, kOTPProperty);
}
}
/*
* Agent callback for dynamic challenge/response requests.
*/
static void need_dynamic_challenge_cb(struct connman_provider *provider,
void *arg, int success)
{
struct need_auth_cb_data *data = arg;
if (success)
mgmt_event_need_dynamic_challenge(data->data, data->tag);
else
mgmt_close_channel(data->data);
free_auth_cb_arg(data);
}
/*
* >PASSWORD:Need "Auth" support for dynamic challenge/response
* requests. If we have all the data just send a response.
* Otherwise notify the Agent and wait for a reply.
*/
static void mgmt_event_need_dynamic_challenge(struct mgmt_server_data *data,
const char *tag)
{
struct connman_provider *provider = data->provider;
const char *otp = connman_provider_get_string(provider, kOTPProperty);
if (otp == NULL) {
struct need_auth_cb_data *arg = new_auth_cb_arg(data, tag);
/* missing OTP, request input */
_DBG_VPN("missing OTP; defer reply");
if (connman_provider_request_input(provider,
need_dynamic_challenge_cb, new_auth_cb_arg(data, tag),
"OTP", kOTPProperty, NULL) < 0)
need_dynamic_challenge_cb(provider, arg, FALSE);
} else {
mgmt_send_dynamic_challenge_response(data, tag,
data->username, data->state_id, otp);
g_free(data->username);
data->username = NULL;
g_free(data->state_id);
data->state_id = NULL;
/* NB: never re-use OTP, also avoids looping */
connman_provider_clear_property(provider, kOTPProperty);
}
}
/*
* Parse Dynamic Challenge/Response Protocol. The failure
* msg contains the challenge question formatted according to:
*
* CRV1:<flags>:<state_id>:<username_base64>:<challenge_text>
*
* flags: a series of optional, comma-separate flags:
* E : echo the response when the user types it
* R : a response is required
*
* state_id: an opaque string that should be returned to the
* server along with the response.
*
* username_base64 : the username formatted as base64
*
* challenge_text : the challenge text to be shown to the user
*
* After prompting for the reponse we must respond to the next
* >PASSWORD:Need request with:
*
* Username: [username decoded from username_base64]
* Password: CRV1::<state_id>::<response_text>
*
* (using the values sent in the failure message).
*/
static void missing_colon(const char *func, const char *what, const char *data)
{
connman_error("%s: no ':' parsing %s in \"%s\"", func, what, data);
}
static void mgmt_event_challenge_response(struct mgmt_server_data *data,
char *emsg)
{
char *cp, *state_id, *username_base64, *challenge_text;
int respond, echo;
respond = echo = FALSE;
/* NB: caller verifies "CRV1:" is present so +5 is ok here */
for (cp = emsg + 5; *cp != ':'; cp++) {
if (*cp == '\0') {
missing_colon(__func__, "flags", emsg);
return;
}
if (*cp == 'R')
respond = TRUE;
else if (*cp == 'E')
echo = TRUE;
}
state_id = cp+1;
cp = strchr(state_id, ':');
if (cp == NULL) {
missing_colon(__func__, "state_id", emsg);
return;
}
*cp = '\0';
username_base64 = cp+1;
cp = strchr(username_base64, ':');
if (cp == NULL) {
missing_colon(__func__, "username", emsg);
return;
}
*cp = '\0';
challenge_text = cp+1;
connman_info("%s: state_id \"%s\" username \"%s\" challenge \"%s\"%s%s",
__func__, state_id, username_base64, challenge_text,
respond ? ", Respond" : "", echo ? ", Echo" : "");
if (respond) {
gsize len;
/* TODO(sleffler) state_id and username should be NULL, should we assert? */
g_free(data->state_id);
data->state_id = g_strdup(state_id);
g_free(data->username);
/* TODO(sleffler) can &len be NULL? */
data->username = (char *)g_base64_decode(username_base64, &len);
mgmt_event_need_dynamic_challenge(data, "Auth");
}
}
/*
* Handle a password verification failure. We process
* challenge/response requests and push everything else
* back to the client.
*/
static void missing_token(const char *func, const char *what, const char *data)
{
connman_error("%s: missing '%s' in \"%s\"", func, what, data);
}
static void mgmt_event_verification(struct mgmt_server_data *data,
char *line)
{
char *emsg, *cp;
if (g_str_has_prefix(line, ">PASSWORD:Verification Failed: 'Auth'") == FALSE)
return;
/*
* Auth failures may include a custom server-generated string.
* Dynamic c/r msgs are of the form ['CRV1:...']; we extract
* those here and hand them off for processing. All other msgs
* are sent back to the client for presentation.
*/
emsg = strchr(line, '[');
if (emsg != NULL) {
emsg++;
if (emsg[0] == '\'')
emsg++;
cp = strchr(emsg, ']');
if (cp == NULL) {
missing_token(__func__, "]", line);
goto done;
}
if (cp[-1] == '\'')
cp--;
*cp = '\0';
if (g_str_has_prefix(emsg, "CRV1:") == TRUE) {
mgmt_event_challenge_response(data, emsg);
return;
}
/* NB: stripped ['...'] from the original string */
}
done:
_DBG_VPN("Auth failure '%s'", emsg);
/* NB: no way to pass auth failure indication to client */
connman_provider_indicate_error(data->provider,
CONNMAN_PROVIDER_ERROR_BAD_PASSPHRASE);
/* TODO(sleffler) clear state, OpenVPN.Password? */
}
/*
* Process data from the management control socket connected to openvpn.
* We process only ">PASSWORD" requests; tossing everything else.
*/
static gboolean mgmt_server_event(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
struct mgmt_server_data *data = user_data;
char buf[512];
int s, i;
ssize_t len;
gchar **lines;
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
connman_error("%s: server channel error (condition 0x%x)",
__func__, condition);
data->watch = 0;
return FALSE;
}
s = g_io_channel_unix_get_fd(channel);
len = read(s, buf, sizeof(buf)-1);
if (len < 0) {
/* TODO(sleffler) disable or recover? */
connman_error("%s: read error %s", __func__, strerror(errno));
data->watch = 0;
return FALSE;
}
/* TODO(sleffler) buffer data until we see a \n? */
buf[len] = '\0';
lines = g_strsplit(buf, "\n", 0);
for (i = 0; lines[i] != NULL; i++) {
if (lines[i][0] == '\0')
continue;
_DBG_VPN("\"%s\"", lines[i]);
if (g_str_has_prefix(lines[i], ">INFO") == TRUE)
continue;
if (g_str_has_prefix(lines[i], ">PASSWORD:Need ") == TRUE) {
char *type, *etype;
/* extract tag, e.g. "Auth" */
type = strchr(lines[i], '\'');
if (type == NULL) {
missing_token(__func__, "open-'", lines[i]);
goto skip;
}
type++;
etype = strchr(type, '\'');
if (etype == NULL) {
missing_token(__func__, "close-'", lines[i]);
goto skip;
}
*etype++ = '\0';
if (g_strcmp0(type, "Auth") == 0) {
gchar *sc;
/* check for static-challenge component */
sc = g_strstr_len(etype, strlen(etype), "SC:");
if (sc != NULL) {
mgmt_event_need_static_challenge(data,
type, sc);
} else
mgmt_event_need_auth(data, type);
continue;
}
if (g_str_has_prefix(type, "User-Specific TPM Token") == TRUE) {
mgmt_event_need_tpm_pin(data, type);
continue;
}
}
if (g_str_has_prefix(lines[i], ">PASSWORD:Verif") == TRUE) {
mgmt_event_verification(data, lines[i]);
continue;
}
if (g_str_has_prefix(lines[i], ">STATE:") == TRUE) {
mgmt_event_state(data, lines[i]);
continue;
}
skip:
connman_info("%s: ignore \"%s\"", __func__, lines[i]);
}
g_strfreev(lines);
return TRUE;
}
/*
* Management channel callback to accept a connection from openvpn.
* This callback is setup for the socket we listen on. Once we accept
* a connection on it we then discard the listen socket and arrange
* for callbacks over the data socket by mgmt_server_event.
*/
static gboolean mgmt_server_accept(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
struct mgmt_server_data *data = user_data;
int s;
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
connman_error("%s: server channel error (condition 0x%x)",
__func__, condition);
data->watch = 0;
return FALSE;
}
/* TODO(sleffler): verify peer's address is INADDR_LOOPBACK */
s = accept(g_io_channel_unix_get_fd(channel), NULL, NULL);
if (s < 0) {
connman_error("%s: accept error %s", __func__, strerror(errno));
data->watch = 0;
return FALSE;
}
/* clear old state to prepare for replacements */
g_source_remove(data->watch);
data->watch = 0;
g_io_channel_unref(data->channel);
/* now setup channel for accepted socket */
data->channel = g_io_channel_unix_new(s);
if (data->channel == NULL) {
connman_error("Failed to create data channel for %s",
data->vpnhost);
close(s);
/* NB: data->watch already zero */
return FALSE;
}
g_io_channel_set_close_on_unref(data->channel, TRUE);
data->watch = g_io_add_watch(data->channel, G_IO_IN,
mgmt_server_event, data);
connman_info("OpenVPN management channel for %s created @%s:%d",
data->vpnhost, inet_ntoa(data->addr.sin_addr),
ntohs(data->addr.sin_port));
mgmt_send_state(data, "on", __func__);
return TRUE;
}
static struct mgmt_server_data *mgmt_create_server(
struct connman_provider *provider, const char *vpnhost)
{
struct mgmt_server_data *data;
int s;
socklen_t slen;
_DBG_VPN("vpnhost %s", vpnhost);
data = g_try_new0(struct mgmt_server_data, 1);
if (data == NULL) {
connman_error("Failed to allocate server data");
return NULL;
}
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s < 0) {
connman_error("Server socket create failed for %s (%s)",
vpnhost, strerror(errno));
g_free(data);
return NULL;
}
memset(&data->addr, 0, sizeof(data->addr));
data->addr.sin_family = AF_INET;
data->addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(s, (struct sockaddr *) &data->addr, sizeof(data->addr)) < 0) {
connman_error("Could not bind address for %s: %s",
vpnhost, strerror(errno));
goto bad;
}
if (listen(s, 1) < 0) {
connman_error("Server socket listen failed for %s: %s",
vpnhost, strerror(errno));
goto bad;
}
slen = sizeof(data->addr);
if (getsockname(s, (struct sockaddr *) &data->addr, &slen) < 0) {
connman_error("Could not get bound address for %s: %s",
vpnhost, strerror(errno));
goto bad;
}
data->channel = g_io_channel_unix_new(s);
if (data->channel == NULL) {
connman_error("Failed to create server channel for %s",
vpnhost);
goto bad;
}
g_io_channel_set_close_on_unref(data->channel, TRUE);
data->watch = g_io_add_watch(data->channel, G_IO_IN,
mgmt_server_accept, data);
data->provider = connman_provider_ref(provider);
data->vpnhost = g_strdup(vpnhost);
data->state = OV_STATE_UNKNOWN;
return data;
bad:
close(s);
g_free(data);
return NULL;
}
static void mgmt_close_channel(struct mgmt_server_data *data)
{
if (data->watch > 0) {
g_source_remove(data->watch);
data->watch = -1;
}
if (data->channel != NULL) {
g_io_channel_unref(data->channel);
data->channel = NULL;
}
}
static void mgmt_destroy_server(struct mgmt_server_data *data)
{
_DBG_VPN("vpnhost %s", data->vpnhost);
mgmt_close_channel(data);
connman_provider_unref(data->provider);
g_free(data->username);
g_free(data->password);
g_free(data->state_id);
g_free(data->vpnhost);
g_free(data);
}
static int ov_init_data(struct connman_provider *provider, const char *vpnhost)
{
struct openvpn_data *data;
data = g_try_new0(struct openvpn_data, 1);
if (data == NULL) {
connman_error("%s: out of memory", __func__);
return -ENOMEM;
}
data->gw_index = (uint32_t)-1;
data->ipaddr.af = AF_INET;
data->ipaddr.mask |= CONNMAN_IPCONFIG_AF;
vpn_set_specific_data(provider, data);
return 0;
}
static void ov_cleanup_data(struct connman_provider *provider)
{
struct openvpn_data *data = vpn_get_specific_data(provider);
_DBG_VPN("provider %p data %p", provider, data);
if (data == NULL)
return;
if (data->server != NULL)
mgmt_destroy_server(data->server);
if (data->gw_index != (uint32_t)-1) {
_DBG_VPN("tear down VPN host route");
connman_inet_del_hostroute(data->gw_index, data->vpn_addr,
data->gw_addr);
}
if (data->tls_auth_file != NULL) {
unlink(data->tls_auth_file);
g_free(data->tls_auth_file);
}
if (data->ipaddr.mask & CONNMAN_IPCONFIG_LOCAL)
g_free(data->ipaddr.local);
if (data->ipaddr.mask & CONNMAN_IPCONFIG_BCAST)
g_free(data->ipaddr.broadcast);
if (data->ipaddr.mask & CONNMAN_IPCONFIG_PEER)
g_free(data->ipaddr.peer);
if (data->ipaddr.mask & CONNMAN_IPCONFIG_GW)
g_free(data->ipaddr.gateway);
vpn_set_specific_data(provider, NULL);
g_free(data);
}
static void ov_vpn_died(struct connman_task *task, void *user_data)
{
struct connman_provider *provider = user_data;
_DBG_VPN("provider %p", provider);
ov_cleanup_data(provider);
vpn_died(task, user_data, CONNMAN_PROVIDER_ERROR_CONNECT_FAILED);
}
#define OPT_STR(property, option) do { \
const char *s = connman_provider_get_string(provider, property); \
if (s != NULL) \
connman_task_add_argument(task, option, "%s", s); \
} while (0)
#define OPT_BOOL(property, option) do { \
const char *s = connman_provider_get_string(provider, property); \
if (s != NULL) \
connman_task_add_argument(task, option, NULL); \
} while (0)
int add_mgmt_arguments(struct connman_provider *provider,
struct connman_task *task,
const char *vpnhost)
{
struct openvpn_data *data = vpn_get_specific_data(provider);
const char *static_challenge;
gchar *port;
/*
* Setup a management control channel between openvpn and us
* to do things like query for passwords. We specify that
* openvpn connects back to us to avoid a race on startup and
* to allow us to bind a (random) system-selected port (on
* the loopback interface) to use. This plus accepting only
* one connection minimizes the window by which a process can
* setup this channel over which sensitive data are passed.
*/
data->server = mgmt_create_server(provider, vpnhost);
if (data->server == NULL) {
/* NB: mgmt_create_server logs reason */
ov_cleanup_data(provider);
return -EINVAL;
}
/* TODO(sleffler) total hack for crappy api */
connman_task_add_argument(task, "--management",
inet_ntoa(data->server->addr.sin_addr));
port = g_strdup_printf("%d", ntohs(data->server->addr.sin_port));
connman_task_add_argument(task, port, NULL);
g_free(port);
connman_task_add_argument(task, "--management-client", NULL);
connman_task_add_argument(task, "--management-query-passwords", NULL);
OPT_BOOL("OpenVPN.AuthUserPass","--auth-user-pass");
static_challenge = connman_provider_get_string(provider,
"OpenVPN.StaticChallenge");
if (static_challenge != NULL) {
connman_task_add_argument(task, "--static-challenge",
static_challenge);
/* NB: force echo */
connman_task_add_argument(task, "1", NULL);
}
/* TODO(sleffler) verify OpenVPN.Pkcs11.PIN is present */
/* TODO(sleffler) set/verify OpenVPN.AuthRetry? */
return 0;
}
/*
* Duplicate a string from /etc/lsb-release. The data are
* scrubbed for whitespace and any terminating \n is removed.
*/
static char *dup_value(char *str)
{
char buf[256], c;
int i, j;
j = 0;
for (i = 0; (c = str[i]) != '\0'; i++) {
if (c == ' ') /* remove whitespace */
continue;
if (c == '\n') /* stop at first \n */
break;
buf[j++] = c;
}
buf[j] = '\0';
return strdup(buf);
}
/*
* Read platform name and version from /etc/lsb-release
* and install them in platform_name and platform_version,
* respectively. Return true on success.
*/
static gboolean read_platform_data(void)
{
char line[256], name[256], version[256];
FILE *fd;
fd = fopen(kLSBReleaseFilename, "r");
if (fd == NULL) {
connman_error("%s: Cannot open %s", __func__,
kLSBReleaseFilename);
return FALSE;
}
name[0] = version[0] = '\0';
while (fgets(line, sizeof(line), fd) != NULL) {
char *cp = strchr(line, '=');
if (cp == NULL)
continue;
*cp++ = '\0';
if (strcmp(line, kChromeOSReleaseName)== 0)
strncpy(name, cp, sizeof(name));
else if (strcmp(line, kChromeOSReleaseVersion) == 0)
strncpy(version, cp, sizeof(version));
}
fclose(fd);
if (name[0] == '\0' || version[0] == '\0') {
connman_error("%s: Missing data: name=\"%s\" version=\"%s\"",
__func__, name, version);
return FALSE;
}
/* TODO(sleffler) worth checking return values? */
platform_name = dup_value(name);
platform_version = dup_value(version);
return (platform_name != NULL && platform_version != NULL);
}
/*
* Add the platform name and version information to the environment
* so that openvpn will send them to the server (when
* OpenVPN.PushPeerInfo is set).
*/
static void add_platform_vars(struct connman_task *task)
{
if (platform_name == NULL && !read_platform_data())
return;
/* NB: these are passed through the environment */
connman_task_add_variable(task, "IV_PLAT", platform_name);
connman_task_add_variable(task, "IV_PLAT_REL", platform_version);
}
static int ov_connect(struct connman_provider *provider,
struct connman_task *task, const char *if_name)
{
const char *vpnhost;
const char *remote_cert_tls;
const char *tls_auth_contents;
const char *ca_cert_nss;
const char *pkcs11_id;
const char *verb;
int err, fd;
vpnhost = connman_provider_get_string(provider, CONNMAN_PROVIDER_HOST);
if (!vpnhost) {
connman_error("%s: host not set; cannot enable VPN", __func__);
return -EINVAL;
}
err = ov_init_data(provider, vpnhost);
if (err < 0)
return err;
connman_task_add_argument(task, "--client", NULL);
connman_task_add_argument(task, "--tls-client", NULL);
connman_task_add_argument(task, "--remote", "%s", vpnhost);
connman_task_add_argument(task, "--nobind", NULL);
connman_task_add_argument(task, "--persist-key", NULL);
connman_task_add_argument(task, "--persist-tun", NULL);
connman_task_add_argument(task, "--dev", "%s", if_name);
connman_task_add_argument(task, "--dev-type", "%s", "tun");
connman_task_add_argument(task, "--syslog", NULL);
verb = connman_provider_get_string(provider, "OpenVPN.Verb");
if (verb == NULL && connman_debug_enabled(DBG_VPN) == TRUE)
verb = "3";
connman_task_add_argument(task, "--verb", "%s", verb);
OPT_STR("VPN.MTU", "--mtu");
OPT_STR("OpenVPN.Proto", "--proto");
OPT_STR("OpenVPN.Port", "--port");
OPT_STR("OpenVPN.TLSAuth", "--tls-auth");
tls_auth_contents = connman_provider_get_string(
provider,
"OpenVPN.TLSAuthContents");
if (tls_auth_contents != NULL) {
struct openvpn_data *data = vpn_get_specific_data(provider);
int handle;
handle = g_file_open_tmp("tls-auth.XXXXXX",
&data->tls_auth_file,
NULL);
if (handle < 0) {
ov_cleanup_data(provider);
connman_error("%s: unable to create temp tls-auth",
__func__);
return -EIO;
}
close(handle);
if (!g_file_set_contents(data->tls_auth_file, tls_auth_contents,
strlen(tls_auth_contents), NULL)) {
ov_cleanup_data(provider);
connman_error("%s: unable to write to tls auth file",
__func__);
return -EIO;
}
connman_task_add_argument(task, "--tls-auth",
data->tls_auth_file);
}
OPT_STR("OpenVPN.TLSRemote", "--tls-remote");
OPT_STR("OpenVPN.Cipher", "--cipher");
OPT_STR("OpenVPN.Auth", "--auth");
OPT_BOOL("OpenVPN.AuthNoCache", "--auth-nocache");
OPT_STR("OpenVPN.AuthRetry", "--auth-retry");
OPT_BOOL("OpenVPN.CompLZO", "--comp-lzo");
OPT_BOOL("OpenVPN.CompNoAdapt", "--comp-noadapt");
OPT_BOOL("OpenVPN.PushPeerInfo","--push-peer-info");
OPT_STR("OpenVPN.RenegSec", "--reneg-sec");
OPT_STR("OpenVPN.Shaper", "--shaper");
OPT_STR("OpenVPN.ServerPollTimeout", "--server-poll-timeout");
#ifdef ENABLE_NSS
ca_cert_nss =
connman_provider_get_string(provider, "OpenVPN.CACertNSS");
if (ca_cert_nss != NULL) {
char *filename;
if (connman_provider_get_string(provider,
"OpenVPN.CACert") != NULL) {
connman_error("%s: CACert and CACertNSS cannot both be "
"specified", __func__);
return -EIO;
}
filename = nss_get_pem_certfile(ca_cert_nss, (uint8_t *)vpnhost,
strlen(vpnhost));
if (filename != NULL) {
connman_task_add_argument(task, "--ca",
"%s", filename);
} else {
connman_error("%s: Could not extract certificate %s",
__func__, ca_cert_nss);
}
g_free(filename);
}
#endif
/* client-side ping support */
OPT_STR("OpenVPN.Ping", "--ping");
OPT_STR("OpenVPN.PingExit", "--ping-exit");
OPT_STR("OpenVPN.PingRestart", "--ping-restart");
OPT_STR("OpenVPN.CACert", "--ca");
OPT_STR("OpenVPN.Cert", "--cert");
OPT_STR("OpenVPN.NsCertType", "--ns-cert-type");
OPT_STR("OpenVPN.Key", "--key");
/* PKCS#11 support. */
pkcs11_id = connman_provider_get_string(provider, "OpenVPN.Pkcs11.ID");
if (pkcs11_id != NULL) {
const char *pkcs11_provider;
pkcs11_provider = connman_provider_get_string(provider,
"OpenVPN.Pkcs11.Provider");
if (pkcs11_provider != NULL) {
connman_task_add_argument(task, "--pkcs11-providers",
"%s", pkcs11_provider);
} else {
#ifdef DEFAULT_PKCS11
connman_task_add_argument(task, "--pkcs11-providers",
"%s", DEFAULT_PKCS11);
#endif
}
connman_task_add_argument(task, "--pkcs11-id", "%s", pkcs11_id);
}
/* TLS support */
remote_cert_tls = connman_provider_get_string(provider,
"OpenVPN.RemoteCertTLS");
if (remote_cert_tls == NULL)
remote_cert_tls = "server";
if (g_strcmp0(remote_cert_tls, "none") != 0)
connman_task_add_argument(task, "--remote-cert-tls",
"%s", remote_cert_tls);
/* NB: undocumented cmd line arg; works like .cfg file entry */
/* TODO(sleffler) maybe roll into --tls-auth? */
OPT_STR("OpenVPN.KeyDirection", "--key-direction");
/* TODO(sleffler) support >1 eku parameter */
OPT_STR("OpenVPN.RemoteCertEKU","--remote-cert-eku");
OPT_STR("OpenVPN.RemoteCertKU", "--remote-cert-ku");
err = add_mgmt_arguments(provider, task, vpnhost);
if (err < 0)
return err;
/*
* Arrange for our callback script to be called with the
* DBus info required to send us the Layer 3 configuration;
* see ov_notify for our handling.
*/
connman_task_add_argument(task, "--setenv", NULL);
connman_task_add_argument(task, "CONNMAN_BUSNAME",
"%s", dbus_bus_get_unique_name(connection));
connman_task_add_argument(task, "--setenv", NULL);
connman_task_add_argument(task, "CONNMAN_INTERFACE",
"%s", CONNMAN_TASK_INTERFACE);
connman_task_add_argument(task, "--setenv", NULL);
connman_task_add_argument(task, "CONNMAN_PATH",
"%s", connman_task_get_path(task));
add_platform_vars(task);
connman_task_add_argument(task, "--script-security", "%d", 2);
connman_task_add_argument(task, "--up",
"%s", SCRIPTDIR "/openvpn-script");
connman_task_add_argument(task, "--up-restart", NULL);
/* disable openvpn handling, we do route+ifconfig work */
connman_task_add_argument(task, "--route-noexec", NULL);
connman_task_add_argument(task, "--ifconfig-noexec", NULL);
/*
* Drop root privileges on connection and enable callback
* scripts to talk to the Task interface to send notify
* messages (after dropping privs).
*/
connman_task_add_argument(task, "--user", "%s", "openvpn");
connman_task_add_argument(task, "--group", "%s", "openvpn");
fd = fileno(stderr);
err = connman_task_run(task, ov_vpn_died, provider,
NULL, &fd, &fd);
if (err < 0) {
connman_error("%s: openvpn failed to start task (err %d)",
__func__, err);
ov_cleanup_data(provider);
return -EIO;
}
return 0;
}
#undef OPT_BOOL
#undef OPT_STR
static const char *ov_public_props[] = {
"OpenVPN.Auth",
"OpenVPN.AuthNoCache",
"OpenVPN.AuthRetry",
"OpenVPN.AuthUserPass",
"OpenVPN.CACert",
"OpenVPN.CACertNSS",
"OpenVPN.Cert",
"OpenVPN.Cipher",
"OpenVPN.CompLZO",
"OpenVPN.CompNoAdapt",
"OpenVPN.Key",
"OpenVPN.KeyDirection",
"OpenVPN.NsCertType",
"OpenVPN.Pkcs11.PIN",
"OpenVPN.Pkcs11.Provider",
"OpenVPN.Ping",
"OpenVPN.PingExit",
"OpenVPN.PingRestart",
"OpenVPN.Port",
"OpenVPN.Proto",
"OpenVPN.PushPeerInfo",
"OpenVPN.RemoteCertEKU",
"OpenVPN.RemoteCertKU",
"OpenVPN.RemoteCertTLS",
"OpenVPN.RenegSec",
"OpenVPN.Shaper",
"OpenVPN.ServerPollTimeout",
"OpenVPN.StaticChallenge",
"OpenVPN.TLSAuth",
"OpenVPN.TLSAuthContents",
"OpenVPN.TLSRemote",
"OpenVPN.User",
"OpenVPN.Verb",
"VPN.MTU",
NULL
};
/*
* Append plugin-specific properties to the D-Bus dictionary.
*/
void ov_append_props(struct connman_provider *provider, DBusMessageIter *iter,
connman_bool_t isprivileged)
{
static const char *ov_priv_props[] = {
"OpenVPN.OTP",
/* NB: intentionally leave out OpenVPN.Password */
"OpenVPN.Pkcs11.ID",
NULL
};
dbus_bool_t required;
connman_provider_append_properties(provider, ov_public_props, iter);
if (isprivileged)
connman_provider_append_properties(provider, ov_priv_props,
iter);
required = connman_provider_property_is_empty(provider,
kPasswordProperty);
connman_dbus_dict_append_basic(iter, "PassphraseRequired",
DBUS_TYPE_BOOLEAN, &required);
}
/*
* Load plugin-specific propetiers from the profile.
*/
static void ov_load_props(struct connman_provider *provider, GKeyFile *keyfile)
{
connman_provider_load_save_properties(
provider, ov_public_props, connman_provider_load_property, keyfile);
connman_provider_load_encrypted_property(provider, kPasswordProperty,
keyfile);
}
/*
* Save plugin-specific properties to the profile.
*/
static void ov_save_props(struct connman_provider *provider, GKeyFile *keyfile)
{
connman_provider_load_save_properties(
provider, ov_public_props, connman_provider_save_property, keyfile);
connman_provider_save_encrypted_property(provider, kPasswordProperty,
keyfile);
}
static struct vpn_driver vpn_driver = {
.notify = ov_notify,
.connect = ov_connect,
.append_props = ov_append_props,
.load_props = ov_load_props,
.save_props = ov_save_props,
};
static void __mask_value_of_keys(void)
{
connman_log_mask_value_of_key(kOTPProperty);
connman_log_mask_value_of_key(kPasswordProperty);
connman_log_mask_value_of_key(kUserProperty);
}
static int openvpn_init(void)
{
connection = connman_dbus_get_connection();
__mask_value_of_keys();
return vpn_register("openvpn", &vpn_driver, OPENVPN);
}
static void openvpn_exit(void)
{
vpn_unregister("openvpn");
dbus_connection_unref(connection);
free(platform_name);
free(platform_version);
}
CONNMAN_PLUGIN_DEFINE(openvpn, "OpenVPN plugin", VERSION,
CONNMAN_PLUGIN_PRIORITY_DEFAULT, openvpn_init, openvpn_exit)