blob: 242edd4ccbb291d2ffc2bd6973064fce3a2276fa [file] [log] [blame]
/*
* L2TP/IPsec VPN plugin.
*
* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <net/if.h>
#include <netdb.h>
#include <glib.h>
#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/plugin.h>
#include <connman/dbus.h>
#include <connman/provider.h>
#include <connman/log.h>
#include <connman/task.h>
#include <connman/inet.h>
#ifdef ENABLE_NSS
#include "nss.h"
#endif
#include "vpn.h"
#define _DBG_VPN(fmt, arg...) DBG(DBG_VPN, fmt, ## arg)
/* Exit status of l2tpipsec_vpn defined in vpn-manager/service_error.h */
enum l2tpipsec_exit_status {
L2TPIPSEC_ERROR_NO_ERROR = 0,
/* Common errors */
L2TPIPSEC_ERROR_INTERNAL = 1,
L2TPIPSEC_ERROR_INVALID_ARGUMENT = 2,
L2TPIPSEC_ERROR_RESOLVE_HOSTNAME_FAILED = 3,
/* IPsec specific errors */
L2TPIPSEC_ERROR_IPSEC_CONNECTION_FAILED = 32,
L2TPIPSEC_ERROR_IPSEC_PSK_AUTH_FAILED = 33,
L2TPIPSEC_ERROR_IPSEC_CERT_AUTH_FAILED = 34,
/* LT2P specific errors */
L2TPIPSEC_ERROR_L2TP_CONNECTION_FAILED = 64,
/* PPP specific errors */
L2TPIPSEC_ERROR_PPP_CONNECTION_FAILED = 128,
L2TPIPSEC_ERROR_PPP_AUTH_FAILED = 129
};
/* Structure used to store any L2TP/IPsec VPN-specific data. */
struct l2tpipsec_data {
/* Path to file containing PSK or NULL if PSK not needed. */
gchar *psk_file;
connman_bool_t added_host_route;
in_addr_t vpn_addr;
in_addr_t gw_addr;
uint32_t gw_index;
};
static DBusConnection *connection;
static const char *kPSKProperty = "L2TPIPsec.PSK";
static const char *kPasswordProperty = "L2TPIPsec.Password";
static const char *kUserProperty = "L2TPIPsec.User";
static DBusMessage *li_get_sec(struct connman_task *task,
DBusMessage *msg, void *user_data)
{
const char *user, *passwd;
struct connman_provider *provider = user_data;
if (dbus_message_get_no_reply(msg) == FALSE) {
DBusMessage *reply;
user = connman_provider_get_string(provider, kUserProperty);
passwd = connman_provider_get_string(provider,
kPasswordProperty);
if (user == NULL || strlen(user) == 0) {
_DBG_VPN("User not set");
return NULL;
}
if (passwd == NULL || strlen(passwd) == 0) {
_DBG_VPN("Password not set");
return NULL;
}
reply = dbus_message_new_method_return(msg);
if (reply == NULL)
return NULL;
dbus_message_append_args(reply, DBUS_TYPE_STRING, &user,
DBUS_TYPE_STRING, &passwd,
DBUS_TYPE_INVALID);
return reply;
}
return NULL;
}
static void remove_psk_file(struct connman_provider *provider)
{
struct l2tpipsec_data *specific_data;
specific_data = vpn_get_specific_data(provider);
if (specific_data == NULL)
return;
if (specific_data->psk_file == NULL)
return;
unlink(specific_data->psk_file);
g_free(specific_data->psk_file);
specific_data->psk_file = NULL;
}
static int li_notify(DBusMessage *msg, struct connman_provider *provider)
{
DBusMessageIter iter, dict;
const char *reason, *key;
char *value;
char *ifname = NULL;
char *dns_servers[3];
struct connman_ipaddress ipaddr;
char *remote_server_address = NULL;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_get_basic(&iter, &reason);
dbus_message_iter_next(&iter);
_DBG_VPN("Reason %s", reason);
if (provider == NULL) {
connman_error("%s: No provider found", __func__);
return VPN_STATE_FAILURE;
}
if (strcmp(reason, "connect"))
return VPN_STATE_DISCONNECT;
memset(&ipaddr, 0, sizeof(ipaddr));
ipaddr.af = AF_INET;
ipaddr.mask |= CONNMAN_IPCONFIG_AF;
memset(dns_servers, 0, sizeof(dns_servers));
ipaddr.dns_servers = dns_servers;
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 (!strcmp(key, "INTERNAL_IP4_ADDRESS")) {
ipaddr.local = value;
ipaddr.mask |= CONNMAN_IPCONFIG_LOCAL;
} else if (!strcmp(key, "EXTERNAL_IP4_ADDRESS")) {
ipaddr.peer = value;
ipaddr.mask |= CONNMAN_IPCONFIG_PEER;
} else if (!strcmp(key, "GATEWAY_ADDRESS")) {
ipaddr.gateway = value;
ipaddr.mask |= CONNMAN_IPCONFIG_GW;
/*
* Set the prefixlen so that dns configuration occurs
* and host routes are enabled. The kernel expects
* PPP device netmasks to be 32b, otherwise it removes
* host routes that it configures.
*/
ipaddr.prefixlen = 32;
ipaddr.mask |= CONNMAN_IPCONFIG_PREFIX;
} else if (!strcmp(key, "DNS1")) {
ipaddr.dns_servers[0] = value;
ipaddr.mask |= CONNMAN_IPCONFIG_DNS;
} else if (!strcmp(key, "DNS2")) {
ipaddr.dns_servers[1] = value;
} else if (!strcmp(key, "INTERNAL_IFNAME")) {
ifname = value;
} else if (!strcmp(key, "LNS_ADDRESS")) {
remote_server_address = value;
}
dbus_message_iter_next(&dict);
}
if (vpn_set_ifname(provider, ifname) < 0) {
return VPN_STATE_FAILURE;
}
if (ipaddr.mask & CONNMAN_IPCONFIG_GW) {
/*
* 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. Note that we do not need to
* observe default gateway changes since IPsec v1
* which is used in L2TP over IPsec itself is not able
* to handle switching of underlying networks.
*/
struct l2tpipsec_data *specific_data;
if (remote_server_address == NULL) {
connman_error("%s: request to set gw without "
"LNS_ADDRESS", __func__);
return VPN_STATE_FAILURE;
}
_DBG_VPN("Adding host VPN server route");
specific_data = vpn_get_specific_data(provider);
if (!specific_data) {
connman_error("%s: no specific data", __func__);
return VPN_STATE_FAILURE;
}
specific_data->vpn_addr = inet_addr(remote_server_address);
if (!connman_inet_get_route(specific_data->vpn_addr,
&specific_data->gw_index,
&specific_data->gw_addr,
NULL)) {
connman_error("%s: unable to get route", __func__);
return VPN_STATE_FAILURE;
}
connman_inet_add_hostroute(
specific_data->gw_index, specific_data->vpn_addr,
specific_data->gw_addr);
specific_data->added_host_route = TRUE;
}
connman_provider_ipconfig_set(provider, &ipaddr);
remove_psk_file(provider);
return VPN_STATE_CONNECT;
}
static enum connman_provider_error exit_status_to_error(gint status)
{
if (WIFEXITED(status)) {
switch (WEXITSTATUS(status)) {
case L2TPIPSEC_ERROR_NO_ERROR:
return CONNMAN_PROVIDER_ERROR_NO_ERROR;
case L2TPIPSEC_ERROR_RESOLVE_HOSTNAME_FAILED:
return CONNMAN_PROVIDER_ERROR_RESOLVE_HOSTNAME_FAILED;
case L2TPIPSEC_ERROR_IPSEC_CONNECTION_FAILED:
case L2TPIPSEC_ERROR_L2TP_CONNECTION_FAILED:
case L2TPIPSEC_ERROR_PPP_CONNECTION_FAILED:
return CONNMAN_PROVIDER_ERROR_CONNECT_FAILED;
case L2TPIPSEC_ERROR_IPSEC_PSK_AUTH_FAILED:
return CONNMAN_PROVIDER_ERROR_IPSEC_PSK_AUTH_FAILED;
case L2TPIPSEC_ERROR_IPSEC_CERT_AUTH_FAILED:
return CONNMAN_PROVIDER_ERROR_IPSEC_CERT_AUTH_FAILED;
case L2TPIPSEC_ERROR_PPP_AUTH_FAILED:
return CONNMAN_PROVIDER_ERROR_PPP_AUTH_FAILED;
default:
return CONNMAN_PROVIDER_ERROR_INTERNAL;
}
}
return CONNMAN_PROVIDER_ERROR_INTERNAL;
}
static void li_vpn_died(struct connman_task *task, void *user_data)
{
struct connman_provider *provider = user_data;
struct l2tpipsec_data *specific_data;
gint status = connman_task_get_exit_status(task);
enum connman_provider_error error = exit_status_to_error(status);
remove_psk_file(provider);
specific_data = vpn_get_specific_data(provider);
if (specific_data && specific_data->added_host_route) {
_DBG_VPN("Tearing down host VPN server route");
connman_inet_del_hostroute(specific_data->gw_index,
specific_data->vpn_addr,
specific_data->gw_addr);
specific_data->added_host_route = FALSE;
}
vpn_set_specific_data(provider, NULL);
g_free(specific_data);
vpn_died(task, user_data, error);
}
static int init_specific_data(struct connman_provider *provider)
{
struct l2tpipsec_data *specific_data;
specific_data = g_try_new0(struct l2tpipsec_data, 1);
if (specific_data == NULL) {
connman_error("%s: out of memory", __func__);
return -ENOMEM;
}
vpn_set_specific_data(provider, specific_data);
return 0;
}
static int create_psk_file(struct connman_provider *provider,
const char *psk,
const char **psk_file)
{
struct l2tpipsec_data *specific_data = NULL;
char psk_file_template[] = "/var/run/flimflam/l2tpipsec_XXXXXX";
int psk_file_fd;
/* mkstemp assures 0600 file permissions. */
psk_file_fd = mkstemp(psk_file_template);
if (psk_file_fd < 0) {
connman_error("%s: mkstemp failed", __func__);
return -EIO;
}
if (write(psk_file_fd, psk, strlen(psk)) != strlen(psk)) {
connman_error("%s: bad write", __func__);
return -EIO;
}
close(psk_file_fd);
specific_data = vpn_get_specific_data(provider);
specific_data->psk_file = g_strdup(psk_file_template);
*psk_file = specific_data->psk_file;
return 0;
}
static int li_connect(struct connman_provider *provider,
struct connman_task *task, const char *if_name)
{
#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, true_option, false_option) do { \
const char *s = connman_provider_get_string(provider, property); \
if (s != NULL) { \
connman_task_add_argument(task, strcmp("true", s) == 0 ? \
true_option : false_option, \
NULL); \
} \
} while (0)
const char *vpnhost;
const char *value;
const char *psk_file;
int err, fd;
if (init_specific_data(provider) < 0)
return -ENOMEM;
if (connman_task_set_notify(task, "getsec",
li_get_sec, provider))
return -ENOMEM;
vpnhost = connman_provider_get_string(provider, CONNMAN_PROVIDER_HOST);
if (!vpnhost) {
connman_error("%s: host not set; cannot enable VPN", __func__);
return -EINVAL;
}
value = connman_provider_get_string(provider, kPSKProperty);
if (value != NULL && value[0] != '\0') {
if (create_psk_file(provider, value, &psk_file) < 0) {
connman_error("%s: unable to write psk file", __func__);
} else {
connman_task_add_argument(task, "--psk_file",
"%s", psk_file);
}
}
connman_task_add_argument(task, "--remote_host",
"%s", vpnhost);
connman_task_add_argument(task, "--pppd_plugin",
"%s", SCRIPTDIR "/libppp-plugin.so");
/* Disable pppd from configuring IP addresses, routes, dns. */
connman_task_add_argument(task, "--nosystemconfig", NULL);
#ifdef ENABLE_NSS
value = connman_provider_get_string(provider, "L2TPIPsec.CACertNSS");
if (value != NULL) {
char *filename;
filename = nss_get_der_certfile(value, (uint8_t *)vpnhost,
strlen(vpnhost));
if (filename != NULL) {
connman_task_add_argument(task, "--server_ca_file",
"%s", filename);
}
g_free(filename);
}
#endif
OPT_STR("L2TPIPsec.ClientCertID", "--client_cert_id");
OPT_STR("L2TPIPsec.ClientCertSlot", "--client_cert_slot");
OPT_STR("L2TPIPsec.PIN", "--user_pin");
OPT_STR("L2TPIPsec.User", "--user");
OPT_STR("L2TPIPsec.IPsecTimeout", "--ipsec_timeout");
OPT_STR("L2TPIPsec.LeftProtoPort", "--leftprotoport");
OPT_BOOL("L2TPIPsec.PFS", "--pfs", "--nopfs");
OPT_BOOL("L2TPIPsec.Rekey", "--rekey", "--norekey");
OPT_STR("L2TPIPsec.RightProtoPort", "--leftprotoport");
OPT_BOOL("L2TPIPsec.RequireChap", "--require_chap",
"--norequire_chap");
OPT_BOOL("L2TPIPsec.RefusePap", "--refuse_pap", "--norefuse_pap");
OPT_BOOL("L2TPIPsec.RequireAuth", "--require_authentication",
"--norequire_authentication");
OPT_BOOL("L2TPIPsec.LengthBit", "--length_bit", "--nolength_bit");
if (connman_debug_enabled(DBG_VPN) == TRUE)
connman_task_add_argument(task, "--debug", NULL);
fd = fileno(stderr);
err = connman_task_run(task, li_vpn_died, provider,
NULL, &fd, &fd);
if (err < 0) {
connman_error("l2tpipsec failed to start");
return -EIO;
}
return 0;
}
static const char *li_public_props[] = {
"L2TPIPsec.CACertNSS",
"L2TPIPsec.ClientCertID",
"L2TPIPsec.ClientCertSlot",
"L2TPIPsec.IPsecTimeout",
"L2TPIPsec.LeftProtoPort",
"L2TPIPsec.LengthBit",
"L2TPIPsec.PFS",
"L2TPIPsec.RefusePap",
"L2TPIPsec.Rekey",
"L2TPIPsec.RequireAuth",
"L2TPIPsec.RequireChap",
"L2TPIPsec.RightProtoPort",
"L2TPIPsec.User",
NULL
};
/*
* Append plugin-specific properties to the D-Bus dictionary.
*/
void li_append_props(struct connman_provider *provider, DBusMessageIter *iter,
connman_bool_t isprivileged)
{
static const char *li_priv_props[] = {
"L2TPIPsec.Password",
"L2TPIPsec.PIN",
"L2TPIPsec.PSK",
NULL
};
dbus_bool_t required;
connman_provider_append_properties(provider, li_public_props, iter);
if (isprivileged)
connman_provider_append_properties(provider, li_priv_props,
iter);
required = connman_provider_property_is_empty(provider,
kPasswordProperty);
connman_dbus_dict_append_basic(iter, "PassphraseRequired",
DBUS_TYPE_BOOLEAN, &required);
required = connman_provider_property_is_empty(provider,
kPSKProperty);
connman_dbus_dict_append_basic(iter, "L2TPIPsec.PSKRequired",
DBUS_TYPE_BOOLEAN, &required);
}
/*
* Load plugin-specific properties from the profile.
*/
static void li_load_props(struct connman_provider *provider, GKeyFile *keyfile)
{
connman_provider_load_save_properties(
provider, li_public_props, connman_provider_load_property, keyfile);
connman_provider_load_encrypted_property(provider, kPSKProperty,
keyfile);
connman_provider_load_encrypted_property(provider, kPasswordProperty,
keyfile);
}
/*
* Save plugin-specific properties to the profile.
*/
static void li_save_props(struct connman_provider *provider, GKeyFile *keyfile)
{
connman_provider_load_save_properties(
provider, li_public_props, connman_provider_save_property, keyfile);
connman_provider_save_encrypted_property(provider, kPSKProperty,
keyfile);
connman_provider_save_encrypted_property(provider, kPasswordProperty,
keyfile);
}
static struct vpn_driver vpn_driver = {
.flags = VPN_FLAG_NO_TUN,
.notify = li_notify,
.connect = li_connect,
.append_props = li_append_props,
.load_props = li_load_props,
.save_props = li_save_props,
};
static void __mask_value_of_keys(void)
{
connman_log_mask_value_of_key(kPSKProperty);
connman_log_mask_value_of_key(kPasswordProperty);
connman_log_mask_value_of_key(kUserProperty);
}
static int li_init(void)
{
connection = connman_dbus_get_connection();
__mask_value_of_keys();
return vpn_register("l2tpipsec", &vpn_driver, L2TPIPSEC);
}
static void li_exit(void)
{
vpn_unregister("l2tpipsec");
dbus_connection_unref(connection);
}
CONNMAN_PLUGIN_DEFINE(l2tpipsec, "l2tpipsec plugin", VERSION,
CONNMAN_PLUGIN_PRIORITY_DEFAULT, li_init, li_exit)