/* SPDX-License-Identifier: GPL-2.0-or-later */
/* nm-l2tp-service - L2TP VPN integration with NetworkManager
*
* Geo Carncross <geocar@gmail.com>
* Alexey Torkhov <atorkhov@gmail.com>
* Based on work by Dan Williams <dcbw@redhat.com>
*
* (C) Copyright 2008 - 2009 Red Hat, Inc.
* (C) Copyright 2011 Alexey Torkhov <atorkhov@gmail.com>
* (C) Copyright 2011 Geo Carncross <geocar@gmail.com>
* (C) Copyright 2012 Sergey Prokhorov <me@seriyps.ru>
* (C) Copyright 2014 Nathan Dorfman <ndorf@rtfm.net>
* (C) Copyright 2016 - 2020 Douglas Kosovic <doug@uq.edu.au>
*/
#include "nm-default.h"
#include "nm-l2tp-service.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <ctype.h>
#include <locale.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "nm-ppp-status.h"
#include "nm-l2tp-pppd-service-dbus.h"
#include "nm-utils/nm-shared-utils.h"
#include "nm-utils/nm-secret-utils.h"
#include "nm-utils/nm-vpn-plugin-macros.h"
#include "shared/utils.h"
#include "nm-l2tp-crypto-nss.h"
#include "nm-l2tp-crypto-openssl.h"
#ifndef DIST_VERSION
#define DIST_VERSION VERSION
#endif
#ifndef RUNSTATEDIR
#define RUNSTATEDIR "/run"
#endif
static struct {
gboolean debug;
int log_level;
} gl /*lobal*/;
static void nm_l2tp_plugin_initable_iface_init(GInitableIface *iface);
G_DEFINE_TYPE_WITH_CODE(NML2tpPlugin,
nm_l2tp_plugin,
NM_TYPE_VPN_SERVICE_PLUGIN,
G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, nm_l2tp_plugin_initable_iface_init));
typedef enum { PASSWORD_AUTH, TLS_AUTH, PSK_AUTH } NML2tpAuthType;
typedef struct {
GPid pid_l2tpd;
gboolean ipsec_up;
guint32 ppp_timeout_handler;
NMConnection * connection;
NMDBusL2tpPpp * dbus_skeleton;
char ipsec_binary_path[256];
char * uuid;
NML2tpAuthType user_authtype;
NML2tpAuthType machine_authtype;
NML2tpIpsecDaemon ipsec_daemon;
NML2tpL2tpDaemon l2tp_daemon;
/* IP of L2TP gateway in numeric and string format */
guint32 naddr;
char * saddr;
} NML2tpPluginPrivate;
#define NM_L2TP_PLUGIN_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_L2TP_PLUGIN, NML2tpPluginPrivate))
#define NM_L2TP_PPPD_PLUGIN PLUGINDIR "/nm-l2tp-pppd-plugin.so"
#define NM_L2TP_WAIT_IPSEC 10000 /* 10 seconds */
#define NM_L2TP_WAIT_PPPD 14000 /* 14 seconds */
#define L2TP_SERVICE_SECRET_TRIES "l2tp-service-secret-tries"
/* Default Phase 1 (Main Mode) and Phase 2 (Quick Mode) proposals which are a
* merge of Windows 10 and macOS/iOS/iPadOS L2TP/IPsec clients' IKEv1 proposal
* algorithms */
#define STRONGSWAN_IKEV1_ALGORITHMS_PHASE1 \
"aes256-sha2_256-modp2048,aes256-sha2_256-modp1536,aes256-sha2_256-modp1024,aes256-sha1-" \
"modp2048,aes256-sha1-modp1536,aes256-sha1-modp1024,aes256-sha1-ecp384,aes128-sha1-modp1024," \
"aes128-sha1-ecp256,3des-sha1-modp2048,3des-sha1-modp1024!"
#define STRONGSWAN_IKEV1_ALGORITHMS_PHASE2 "aes256-sha1,aes128-sha1,3des-sha1!"
#ifdef LIBRESWAN_DH2 /* DH2(modp1024) enabled with configure time --enable_libreswan_dh2 switch */
#define LIBRESWAN_IKEV1_ALGORITHMS_PHASE1 \
"aes256-sha2_256-modp2048,aes256-sha2_256-modp1536,aes256-sha2_256-modp1024,aes256-sha1-" \
"modp2048,aes256-sha1-modp1536,aes256-sha1-modp1024,aes256-sha1-ecp_384,aes128-sha1-modp1024," \
"aes128-sha1-ecp_256,3des-sha1-modp2048,3des-sha1-modp1024"
#else
#define LIBRESWAN_IKEV1_ALGORITHMS_PHASE1 \
"aes256-sha2_256-modp2048,aes256-sha2_256-modp1536,aes256-sha1-modp2048,aes256-sha1-modp1536," \
"aes256-sha1-ecp_384,aes128-sha1-ecp_256,3des-sha1-modp2048"
#endif
#define LIBRESWAN_IKEV1_ALGORITHMS_PHASE2 "aes256-sha1,aes128-sha1,3des-sha1"
/*****************************************************************************/
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
if (gl.log_level >= (level)) { \
g_print("nm-l2tp[%ld] %-7s " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) "\n", \
(long) getpid(), \
nm_utils_syslog_to_str(level) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
static gboolean
_LOGD_enabled(void)
{
return gl.log_level >= LOG_INFO;
}
#define _LOGD(...) _NMLOG(LOG_INFO, __VA_ARGS__)
#define _LOGI(...) _NMLOG(LOG_NOTICE, __VA_ARGS__)
#define _LOGW(...) _NMLOG(LOG_WARNING, __VA_ARGS__)
/*****************************************************************************/
/* Legacy KDE Plasma-nm L2TP keys */
#define KDE_PLASMA_L2TP_KEY_USE_CERT "use-cert"
#define KDE_PLASMA_L2TP_KEY_CERT_PUB "cert-pub"
#define KDE_PLASMA_L2TP_KEY_CERT_CA "cert-ca"
#define KDE_PLASMA_L2TP_KEY_CERT_KEY "cert-key"
typedef struct {
const char *name;
GType type;
bool required : 1;
} ValidProperty;
static const ValidProperty valid_properties[] = {
{NM_L2TP_KEY_GATEWAY, G_TYPE_STRING, TRUE},
{NM_L2TP_KEY_USER_AUTH_TYPE, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_USER, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_DOMAIN, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_EPHEMERAL_PORT, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_USER_CA, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_USER_CERT, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_USER_KEY, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_MTU, G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_MRU, G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_MRRU, G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_REFUSE_EAP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_REFUSE_PAP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_REFUSE_CHAP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_REFUSE_MSCHAP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_REFUSE_MSCHAPV2, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_REQUIRE_MPPE, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_REQUIRE_MPPE_40, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_REQUIRE_MPPE_128, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_MPPE_STATEFUL, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_NOBSDCOMP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_NODEFLATE, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_NO_VJ_COMP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_NO_PCOMP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_NO_ACCOMP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_LCP_ECHO_FAILURE, G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_LCP_ECHO_INTERVAL, G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_UNIT_NUM, G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_MACHINE_AUTH_TYPE, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_MACHINE_CA, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_MACHINE_CERT, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_MACHINE_KEY, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_IPSEC_ENABLE, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_IPSEC_REMOTE_ID, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_IPSEC_GATEWAY_ID, G_TYPE_STRING, FALSE},
/* For legacy purposes, the PSK can also be specified as a non-secret */
{NM_L2TP_KEY_IPSEC_PSK, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_IPSEC_IKE, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_IPSEC_ESP, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_IPSEC_IKELIFETIME, G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_IPSEC_SALIFETIME, G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_IPSEC_FORCEENCAPS, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_IPSEC_IPCOMP, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_IPSEC_IKEV2, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_IPSEC_PFS, G_TYPE_BOOLEAN, FALSE},
{NM_L2TP_KEY_PASSWORD "-flags", G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_USER_CERTPASS "-flags", G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_IPSEC_PSK "-flags", G_TYPE_UINT, FALSE},
{NM_L2TP_KEY_MACHINE_CERTPASS "-flags", G_TYPE_UINT, FALSE},
{KDE_PLASMA_L2TP_KEY_USE_CERT, G_TYPE_UINT, FALSE},
{KDE_PLASMA_L2TP_KEY_CERT_CA, G_TYPE_STRING, FALSE},
{KDE_PLASMA_L2TP_KEY_CERT_PUB, G_TYPE_STRING, FALSE},
{KDE_PLASMA_L2TP_KEY_CERT_KEY, G_TYPE_STRING, FALSE},
{NULL}};
static ValidProperty valid_secrets[] = {{NM_L2TP_KEY_PASSWORD, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_USER_CERTPASS, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_IPSEC_PSK, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_MACHINE_CERTPASS, G_TYPE_STRING, FALSE},
{NM_L2TP_KEY_NOSECRET, G_TYPE_STRING, FALSE},
{NULL}};
static gboolean
nm_l2tp_ipsec_error(GError **error, const char *msg)
{
g_set_error_literal(error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_LAUNCH_FAILED, msg);
return FALSE;
}
static gboolean
has_include_ipsec_secrets(const char *ipsec_secrets_file)
{
g_autofree char *contents = NULL;
g_auto(GStrv) all_lines = NULL;
if (!g_file_get_contents(ipsec_secrets_file, &contents, NULL, NULL))
return FALSE;
all_lines = g_strsplit(contents, "\n", 0);
for (int i = 0; all_lines[i]; i++) {
if (g_str_has_prefix(all_lines[i], "include ")) {
if (strstr(all_lines[i], "ipsec.d/ipsec.nm-l2tp.secrets"))
return TRUE;
}
}
return FALSE;
}
static gboolean
validate_gateway(const char *gateway)
{
const char *p = gateway;
if (!gateway || !gateway[0])
return FALSE;
/* Ensure it's a valid DNS name or IP address */
p = gateway;
while (*p) {
if (!isalnum(*p) && (*p != '-') && (*p != '.'))
return FALSE;
p++;
}
return TRUE;
}
typedef struct ValidateInfo {
const ValidProperty *table;
GError ** error;
gboolean have_items;
} ValidateInfo;
static void
validate_one_property(const char *key, const char *value, gpointer user_data)
{
ValidateInfo *info = (ValidateInfo *) user_data;
int i;
if (*(info->error))
return;
info->have_items = TRUE;
/* 'name' is the setting name; always allowed but unused */
if (!strcmp(key, NM_SETTING_NAME))
return;
for (i = 0; info->table[i].name; i++) {
const ValidProperty prop = info->table[i];
long int tmp;
if (strcmp(prop.name, key))
continue;
switch (prop.type) {
case G_TYPE_STRING:
if (!strcmp(prop.name, NM_L2TP_KEY_GATEWAY)) {
if (validate_gateway(value))
return; /* valid */
g_set_error(info->error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("invalid gateway '%s'"),
value);
}
return;
case G_TYPE_UINT:
errno = 0;
tmp = strtol(value, NULL, 10);
if (errno == 0)
return; /* valid */
g_set_error(info->error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("invalid integer property '%s'"),
key);
break;
case G_TYPE_BOOLEAN:
if (nm_streq(value, "yes") || nm_streq(value, "no"))
return; /* valid */
g_set_error(info->error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("invalid boolean property '%s' (not yes or no)"),
key);
break;
default:
g_set_error(info->error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("unhandled property '%s' type %s"),
key,
g_type_name(prop.type));
break;
}
}
/* Did not find the property from valid_properties or the type did not match */
if (!info->table[i].name) {
g_set_error(info->error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("property '%s' invalid or not supported"),
key);
}
}
static gboolean
nm_l2tp_properties_validate(NMSettingVpn *s_vpn, GError **error)
{
ValidateInfo info = {&valid_properties[0], error, FALSE};
int i;
nm_setting_vpn_foreach_data_item(s_vpn, validate_one_property, &info);
if (!info.have_items) {
g_set_error(error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
"%s",
_("No VPN configuration options."));
return FALSE;
}
if (*error)
return FALSE;
/* Ensure required properties exist */
for (i = 0; valid_properties[i].name; i++) {
const ValidProperty prop = valid_properties[i];
const char * value;
if (!prop.required)
continue;
value = nm_setting_vpn_get_data_item(s_vpn, prop.name);
if (!value || !strlen(value)) {
g_set_error(error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Missing required option '%s'."),
prop.name);
return FALSE;
}
}
return TRUE;
}
static gboolean
nm_l2tp_secrets_validate(NMSettingVpn *s_vpn, GError **error)
{
GError * validate_error = NULL;
ValidateInfo info = {&valid_secrets[0], error, FALSE};
nm_setting_vpn_foreach_secret(s_vpn, validate_one_property, &info);
if (validate_error) {
g_propagate_error(error, validate_error);
return FALSE;
}
return TRUE;
}
static void nm_l2tp_stop_ipsec(NML2tpPluginPrivate *priv);
static void
l2tpd_watch_cb(GPid pid, gint status, gpointer user_data)
{
NML2tpPlugin * plugin = NM_L2TP_PLUGIN(user_data);
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(plugin);
guint error = 0;
if (priv->l2tp_daemon == NM_L2TP_L2TP_DAEMON_KL2TPD) {
if (WIFEXITED(status)) {
error = WEXITSTATUS(status);
if (error != 0)
_LOGW("kl2tpd exited with error code %d", error);
} else if (WIFSTOPPED(status))
_LOGW("kl2tpd stopped unexpectedly with signal %d", WSTOPSIG(status));
else if (WIFSIGNALED(status))
_LOGW("kl2tpd died with signal %d", WTERMSIG(status));
else
_LOGW("kl2tpd died from an unknown cause");
} else {
if (WIFEXITED(status)) {
error = WEXITSTATUS(status);
if (error != 0)
_LOGW("xl2tpd exited with error code %d", error);
} else if (WIFSTOPPED(status))
_LOGW("xl2tpd stopped unexpectedly with signal %d", WSTOPSIG(status));
else if (WIFSIGNALED(status))
_LOGW("xl2tpd died with signal %d", WTERMSIG(status));
else
_LOGW("xl2tpd died from an unknown cause");
}
/* Reap child if needed. */
waitpid(priv->pid_l2tpd, NULL, WNOHANG);
priv->pid_l2tpd = 0;
if (priv->ipsec_up) {
nm_l2tp_stop_ipsec(priv);
}
/* Must be after data->state is set since signals use data->state */
switch (error) {
case 16:
/* hangup */
// FIXME: better failure reason
nm_vpn_service_plugin_failure(NM_VPN_SERVICE_PLUGIN(plugin),
NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
break;
case 2:
/* Couldn't log in due to bad user/pass */
nm_vpn_service_plugin_failure(NM_VPN_SERVICE_PLUGIN(plugin),
NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED);
break;
case 1:
/* Other error (couldn't bind to address, etc) */
nm_vpn_service_plugin_failure(NM_VPN_SERVICE_PLUGIN(plugin),
NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
break;
default:
nm_vpn_service_plugin_disconnect(NM_VPN_SERVICE_PLUGIN(plugin), NULL);
break;
}
}
static gboolean
pppd_timed_out(gpointer user_data)
{
NML2tpPlugin *plugin = NM_L2TP_PLUGIN(user_data);
_LOGW("Looks like pppd didn't initialize our dbus module");
nm_vpn_service_plugin_failure(NM_VPN_SERVICE_PLUGIN(plugin),
NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
return FALSE;
}
static void
free_l2tpd_args(GPtrArray *args)
{
int i;
if (!args)
return;
for (i = 0; i < args->len; i++)
g_free(g_ptr_array_index(args, i));
g_ptr_array_free(args, TRUE);
}
static gboolean
str_to_int(const char *str, long int *out)
{
long int tmp_int;
if (!str)
return FALSE;
errno = 0;
tmp_int = strtol(str, NULL, 10);
if (errno == 0) {
*out = tmp_int;
return TRUE;
}
return FALSE;
}
static inline void
write_config_option(int fd, const char *format, ...)
{
char * string;
va_list args;
int x;
va_start(args, format);
string = g_strdup_vprintf(format, args);
x = write(fd, string, strlen(string));
g_free(string);
va_end(args);
}
typedef struct {
const char *name;
GType type;
const char *write_to_config;
} PPPOpt;
static PPPOpt ppp_auth_options[] = {
{NM_L2TP_KEY_REFUSE_EAP, G_TYPE_BOOLEAN, "refuse-eap\n"},
{NM_L2TP_KEY_REFUSE_PAP, G_TYPE_BOOLEAN, "refuse-pap\n"},
{NM_L2TP_KEY_REFUSE_CHAP, G_TYPE_BOOLEAN, "refuse-chap\n"},
{NM_L2TP_KEY_REFUSE_MSCHAP, G_TYPE_BOOLEAN, "refuse-mschap\n"},
{NM_L2TP_KEY_REFUSE_MSCHAPV2, G_TYPE_BOOLEAN, "refuse-mschap-v2\n"},
{NULL, G_TYPE_NONE, NULL}};
static PPPOpt ppp_options[] = {{NM_L2TP_KEY_REQUIRE_MPPE, G_TYPE_BOOLEAN, "require-mppe\n"},
{NM_L2TP_KEY_REQUIRE_MPPE_40, G_TYPE_BOOLEAN, "require-mppe-40\n"},
{NM_L2TP_KEY_REQUIRE_MPPE_128, G_TYPE_BOOLEAN, "require-mppe-128\n"},
{NM_L2TP_KEY_MPPE_STATEFUL, G_TYPE_BOOLEAN, "mppe-stateful\n"},
{NM_L2TP_KEY_NOBSDCOMP, G_TYPE_BOOLEAN, "nobsdcomp\n"},
{NM_L2TP_KEY_NODEFLATE, G_TYPE_BOOLEAN, "nodeflate\n"},
{NM_L2TP_KEY_NO_VJ_COMP, G_TYPE_BOOLEAN, "novj\n"},
{NM_L2TP_KEY_NO_PCOMP, G_TYPE_BOOLEAN, "nopcomp\n"},
{NM_L2TP_KEY_NO_ACCOMP, G_TYPE_BOOLEAN, "noaccomp\n"},
{NULL, G_TYPE_NONE, NULL}};
/**
* Check that specified UDP socket in 0.0.0.0 is not used and we can bind to it.
**/
static gboolean
is_port_free(int port)
{
struct sockaddr_in addr;
int sock;
g_message("Check port %d", port);
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (!sock) {
_LOGW("Can-not create new test socket");
return FALSE;
}
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
g_message("Can't bind to port %d", port);
return FALSE;
}
close(sock); /* unbind */
return TRUE;
}
static gboolean
nm_l2tp_config_write(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error)
{
GError * config_error = NULL;
NML2tpPluginPrivate * priv = NM_L2TP_PLUGIN_GET_PRIVATE(plugin);
NMSettingIPConfig * s_ip4;
const char * ipsec_secrets_file;
const char * ipsec_conf_dir;
const char * value;
char * filename;
g_autofree char * friendly_name = NULL;
g_autofree char * rundir;
char errorbuf[128];
gint fd = -1;
FILE * fp;
struct in_addr naddr;
int port;
int errsv;
gboolean use_ephemeral_port;
gboolean use_ikev2;
gboolean tls_need_password;
g_autofree char * pwd_base64 = NULL;
const char * tls_key_filename = NULL;
const char * tls_cert_filename = NULL;
const char * tls_ca_filename = NULL;
g_autofree char * tls_key_out_filename = NULL;
g_autofree char * tls_cert_out_filename = NULL;
g_autofree char * tls_ca_out_filename = NULL;
NML2tpCryptoFileFormat tls_key_fileformat = NM_L2TP_CRYPTO_FILE_FORMAT_UNKNOWN;
NML2tpCryptoFileFormat tls_cert_fileformat = NM_L2TP_CRYPTO_FILE_FORMAT_UNKNOWN;
NML2tpCryptoFileFormat tls_ca_fileformat = NM_L2TP_CRYPTO_FILE_FORMAT_UNKNOWN;
GByteArray * p12_array = NULL;
GString * subject_name_str;
GByteArray *subject_name_asn1;
rundir = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s", priv->uuid);
if (!g_file_test(rundir, G_FILE_TEST_IS_DIR)) {
/* Setup runtime directory */
if (mkdir(rundir, 0700) != 0) {
errsv = errno;
g_set_error(error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
"Cannot create run-dir %s (%s)",
rundir,
g_strerror(errsv));
return FALSE;
}
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_EPHEMERAL_PORT);
if (nm_streq0(value, "yes")) {
use_ephemeral_port = TRUE;
} else {
/* Check that UDP port 1701 for is free for L2TP source port */
use_ephemeral_port = !is_port_free(1701);
if (use_ephemeral_port) {
_LOGW("L2TP port 1701 is busy, using ephemeral.");
}
}
/* Map depricated Gateway ID to Remote ID */
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_GATEWAY_ID);
if (value) {
nm_setting_vpn_add_data_item(s_vpn, NM_L2TP_KEY_IPSEC_REMOTE_ID, value);
}
/* Map legacy KDE Plasma-nm keys to equivalent new keys */
value = nm_setting_vpn_get_data_item(s_vpn, KDE_PLASMA_L2TP_KEY_USE_CERT);
if (nm_streq0(value, "yes")) {
nm_setting_vpn_add_data_item(s_vpn, NM_L2TP_KEY_USER_AUTH_TYPE, NM_L2TP_AUTHTYPE_TLS);
value = nm_setting_vpn_get_data_item(s_vpn, KDE_PLASMA_L2TP_KEY_CERT_CA);
nm_setting_vpn_add_data_item(s_vpn, NM_L2TP_KEY_USER_CA, value);
value = nm_setting_vpn_get_data_item(s_vpn, KDE_PLASMA_L2TP_KEY_CERT_PUB);
nm_setting_vpn_add_data_item(s_vpn, NM_L2TP_KEY_USER_CERT, value);
value = nm_setting_vpn_get_data_item(s_vpn, KDE_PLASMA_L2TP_KEY_CERT_KEY);
nm_setting_vpn_add_data_item(s_vpn, NM_L2TP_KEY_USER_KEY, value);
}
priv->user_authtype = PASSWORD_AUTH;
priv->machine_authtype = PSK_AUTH;
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER_AUTH_TYPE);
if (nm_streq0(value, NM_L2TP_AUTHTYPE_TLS)) {
priv->user_authtype = TLS_AUTH;
crypto_init_openssl();
}
/**
* IPsec
**/
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_ENABLE);
if (nm_streq0(value, "yes")) {
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MACHINE_AUTH_TYPE);
if (nm_streq0(value, NM_L2TP_AUTHTYPE_TLS)) {
priv->machine_authtype = TLS_AUTH;
tls_key_filename = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MACHINE_KEY);
tls_cert_filename = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MACHINE_CERT);
tls_ca_filename = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MACHINE_CA);
crypto_init_openssl();
}
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_STRONGSWAN
|| priv->machine_authtype == PSK_AUTH) {
/**
* IPsec secrets
**/
ipsec_secrets_file = NM_IPSEC_SECRETS; /* typically /etc/ipsec.secrets */
ipsec_conf_dir = NM_IPSEC_SECRETS_DIR; /* typically /etc/ipsec.d */
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_STRONGSWAN) {
if (g_file_test("/etc/strongswan", G_FILE_TEST_IS_DIR)) {
/* Fedora uses /etc/strongswan/ instead of /etc/ipsec/ */
ipsec_secrets_file = "/etc/strongswan/ipsec.secrets";
ipsec_conf_dir = "/etc/strongswan/ipsec.d";
}
/* if /etc/ipsec.secrets does not have "include ipsec.d/ipsec.nm-l2tp.secrets", add it */
if (g_file_test(ipsec_secrets_file, G_FILE_TEST_EXISTS)) {
if (!has_include_ipsec_secrets(ipsec_secrets_file)) {
fd = open(ipsec_secrets_file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
if (fd == -1) {
crypto_deinit_openssl();
errsv = errno;
snprintf(errorbuf,
sizeof(errorbuf),
_("Could not open %s for writing: %s"),
ipsec_secrets_file,
g_strerror(errsv));
return nm_l2tp_ipsec_error(error, errorbuf);
}
fp = fdopen(fd, "a");
if (fp == NULL) {
crypto_deinit_openssl();
snprintf(errorbuf,
sizeof(errorbuf),
_("Could not append \"include ipsec.d/ipsec.nm-l2tp.secrets\" "
"to %s"),
ipsec_secrets_file);
return nm_l2tp_ipsec_error(error, errorbuf);
}
fprintf(fp, "\n\ninclude ipsec.d/ipsec.nm-l2tp.secrets\n");
fclose(fp);
close(fd);
}
}
}
filename = g_strdup_printf("%s/ipsec.nm-l2tp.secrets", ipsec_conf_dir);
fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
g_free(filename);
if (fd == -1) {
snprintf(errorbuf,
sizeof(errorbuf),
_("Could not write %s/ipsec.nm-l2tp.secrets"),
ipsec_conf_dir);
crypto_deinit_openssl();
return nm_l2tp_ipsec_error(error, errorbuf);
}
if (priv->machine_authtype == PSK_AUTH) {
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_REMOTE_ID);
if (value) {
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
write_config_option(fd, "%%any ");
}
/* Only literal strings starting with @ and IP addresses
are allowed as IDs with IKEv1 PSK */
if (value[0] == '@') {
write_config_option(fd, "%s ", value);
} else if (inet_pton(AF_INET, value, &naddr)) {
write_config_option(fd, "%s ", value);
} else {
write_config_option(fd, "%%any ");
}
}
value = nm_setting_vpn_get_secret_or_legacy_data_item(s_vpn, NM_L2TP_KEY_IPSEC_PSK);
if (!value)
value = "";
if (g_str_has_prefix(value, "0s")) {
write_config_option(fd, ": PSK %s\n", value);
} else {
pwd_base64 = g_base64_encode((const unsigned char *) value, strlen(value));
write_config_option(fd, ": PSK 0s%s\n", pwd_base64);
}
} else { /* TLS_AUTH */
if (!tls_key_filename) {
close(fd);
crypto_deinit_openssl();
return nm_l2tp_ipsec_error(error, _("Machine private key file not supplied"));
}
tls_key_fileformat =
crypto_file_format(tls_key_filename, &tls_need_password, &config_error);
if (config_error) {
close(fd);
crypto_deinit_openssl();
g_propagate_error(error, config_error);
return FALSE;
}
switch (tls_key_fileformat) {
case NM_L2TP_CRYPTO_FILE_FORMAT_PKCS12:
write_config_option(fd, ": P12");
break;
case NM_L2TP_CRYPTO_FILE_FORMAT_PKCS8_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_PKCS8_PEM:
write_config_option(fd, ": PKCS8");
break;
case NM_L2TP_CRYPTO_FILE_FORMAT_RSA_PKEY_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_RSA_PKEY_PEM:
write_config_option(fd, ": RSA");
break;
case NM_L2TP_CRYPTO_FILE_FORMAT_DSA_PKEY_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_DSA_PKEY_PEM:
/* strongSwan no longer supports DSA,
we let strongSwan produce an error message */
write_config_option(fd, ": DSA");
break;
case NM_L2TP_CRYPTO_FILE_FORMAT_ECDSA_PKEY_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_ECDSA_PKEY_PEM:
write_config_option(fd, ": ECDSA");
break;
default:
write_config_option(fd, ": RSA");
}
write_config_option(fd, " \"%s\"", tls_key_filename);
if (tls_need_password) {
/* password for PKC#12 certificate or private key */
value = nm_setting_vpn_get_secret(s_vpn, NM_L2TP_KEY_MACHINE_CERTPASS);
if (value) {
pwd_base64 = g_base64_encode((const unsigned char *) value, strlen(value));
write_config_option(fd, " 0s%s", pwd_base64);
}
}
write_config_option(fd, "\n");
}
close(fd);
}
/**
* Libreswan NSS database
**/
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN
&& priv->machine_authtype == TLS_AUTH) {
crypto_init_nss(NM_IPSEC_NSS_DIR, &config_error);
if (config_error) {
close(fd);
crypto_deinit_openssl();
g_propagate_error(error, config_error);
return FALSE;
}
tls_key_fileformat = crypto_file_format(tls_key_filename, NULL, &config_error);
if (config_error) {
close(fd);
crypto_deinit_nss(NULL);
crypto_deinit_openssl();
g_propagate_error(error, config_error);
return FALSE;
}
friendly_name = g_strdup_printf("nm-l2tp-%s", priv->uuid);
if (tls_key_fileformat == NM_L2TP_CRYPTO_FILE_FORMAT_PKCS12) {
p12_array = crypto_decrypt_pkcs12_data(
tls_key_filename,
nm_setting_vpn_get_secret(s_vpn, NM_L2TP_KEY_MACHINE_CERTPASS),
friendly_name,
&config_error);
} else {
p12_array = crypto_create_pkcs12_data(
tls_key_filename,
tls_cert_filename,
tls_ca_filename,
nm_setting_vpn_get_secret(s_vpn, NM_L2TP_KEY_MACHINE_CERTPASS),
friendly_name,
&config_error);
}
if (config_error) {
crypto_deinit_nss(NULL);
crypto_deinit_openssl();
g_propagate_error(error, config_error);
return FALSE;
}
crypto_import_nss_pkcs12(p12_array, &config_error);
g_byte_array_free(p12_array, TRUE);
if (config_error) {
crypto_deinit_nss(NULL);
crypto_deinit_openssl();
g_propagate_error(error, config_error);
return FALSE;
}
crypto_deinit_nss(NULL);
}
/**
* IPsec config
**/
filename = g_strdup_printf("%s/ipsec.conf", rundir);
fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
g_free(filename);
if (fd == -1) {
crypto_deinit_openssl();
return nm_l2tp_ipsec_error(error, _("Could not write ipsec config"));
}
/* IPsec config section */
write_config_option(fd, "config setup\n");
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
if (getenv("PLUTODEBUG")) {
write_config_option(fd, " plutodebug=\"%s\"\n\n", getenv("PLUTODEBUG"));
} else if (_LOGD_enabled()) {
write_config_option(fd, " plutodebug=\"all\"\n\n");
}
} else if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_STRONGSWAN) {
if (getenv("CHARONDEBUG")) {
write_config_option(fd, " charondebug=\"%s\"\n\n", getenv("CHARONDEBUG"));
} else if (_LOGD_enabled()) {
/* Default strongswan debug level is 1 (control), set it to 2 (controlmore) for ike, esp & cfg */
write_config_option(fd, " charondebug=\"ike 2, esp 2, cfg 2\"\n\n");
}
}
/* strongSwan CA section */
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_STRONGSWAN
&& priv->machine_authtype == TLS_AUTH) {
if (tls_ca_filename) {
tls_ca_fileformat = crypto_file_format(tls_ca_filename, NULL, &config_error);
if (config_error) {
close(fd);
crypto_deinit_openssl();
g_propagate_error(error, config_error);
return FALSE;
}
if (tls_ca_fileformat == NM_L2TP_CRYPTO_FILE_FORMAT_X509_DER
|| tls_ca_fileformat == NM_L2TP_CRYPTO_FILE_FORMAT_X509_PEM) {
write_config_option(fd, "ca %s-ca\n", priv->uuid);
write_config_option(fd, " cacert=\"%s\"\n", tls_ca_filename);
write_config_option(fd, " auto=add\n\n", tls_ca_filename);
}
}
}
/* IPsec connection section */
write_config_option(fd, "conn %s\n", priv->uuid);
write_config_option(fd, " auto=add\n");
write_config_option(fd, " type=transport\n");
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN
&& priv->machine_authtype == TLS_AUTH) {
write_config_option(fd, " authby=rsasig\n");
} else if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_STRONGSWAN
&& priv->machine_authtype == TLS_AUTH) {
write_config_option(fd, " authby=pubkey\n");
} else {
write_config_option(fd, " authby=secret\n");
}
write_config_option(fd, " left=%%defaultroute\n");
if (use_ephemeral_port) {
write_config_option(fd, " leftprotoport=udp/%%any\n");
} else {
write_config_option(fd, " leftprotoport=udp/l2tp\n");
}
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN
&& priv->machine_authtype == TLS_AUTH) {
write_config_option(fd, " leftcert=%s\n", friendly_name);
write_config_option(fd, " leftrsasigkey=%%cert\n");
} else if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_STRONGSWAN
&& priv->machine_authtype == TLS_AUTH) {
if (tls_key_fileformat == NM_L2TP_CRYPTO_FILE_FORMAT_PKCS12) {
crypto_pkcs12_get_subject_name(
tls_key_filename,
(const char *) nm_setting_vpn_get_secret(s_vpn, NM_L2TP_KEY_MACHINE_CERTPASS),
&subject_name_str,
&subject_name_asn1,
&config_error);
if (config_error) {
close(fd);
crypto_deinit_openssl();
g_propagate_error(error, config_error);
return FALSE;
}
write_config_option(fd, " #leftid=\"%s\"\n", subject_name_str->str);
write_config_option(fd, " leftid=\"asn1dn:#");
for (size_t i = 0; i < subject_name_asn1->len; i++)
write_config_option(fd, "%02x", subject_name_asn1->data[i]);
write_config_option(fd, "\"\n");
g_string_free(subject_name_str, TRUE);
g_byte_array_free(subject_name_asn1, TRUE);
} else {
if (!tls_key_filename) {
close(fd);
crypto_deinit_openssl();
return nm_l2tp_ipsec_error(error, _("Machine certificate file not supplied"));
}
write_config_option(fd, " leftcert=\"%s\"\n", tls_cert_filename);
}
}
write_config_option(fd, " rightprotoport=udp/l2tp\n");
write_config_option(fd, " right=%s\n", priv->saddr);
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_REMOTE_ID);
if (value) {
write_config_option(fd, " rightid=%s\n", value);
} else {
write_config_option(fd, " rightid=%%any\n");
}
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN
&& priv->machine_authtype == TLS_AUTH) {
write_config_option(fd, " rightrsasigkey=%%cert\n");
}
write_config_option(fd, " keyingtries=%%forever\n");
use_ikev2 = FALSE;
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_IKEV2);
if (nm_streq0(value, "yes")) {
use_ikev2 = TRUE;
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_IKE);
if (value) {
write_config_option(fd, " ike=%s\n", value);
} else if (!use_ikev2) {
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
write_config_option(fd, " ike=%s\n", LIBRESWAN_IKEV1_ALGORITHMS_PHASE1);
} else {
write_config_option(fd, " ike=%s\n", STRONGSWAN_IKEV1_ALGORITHMS_PHASE1);
}
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_ESP);
if (value) {
write_config_option(fd, " esp=%s\n", value);
} else if (!use_ikev2) {
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
write_config_option(fd, " esp=%s\n", LIBRESWAN_IKEV1_ALGORITHMS_PHASE2);
} else {
write_config_option(fd, " esp=%s\n", STRONGSWAN_IKEV1_ALGORITHMS_PHASE2);
}
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_IKELIFETIME);
if (value)
write_config_option(fd, " ikelifetime=%s\n", value);
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_SALIFETIME);
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
if (value)
write_config_option(fd, " salifetime=%s\n", value);
} else {
if (value)
write_config_option(fd, " lifetime=%s\n", value);
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_FORCEENCAPS);
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
if (value)
write_config_option(fd, " encapsulation=%s\n", value);
} else {
if (value)
write_config_option(fd, " forceencaps=%s\n", value);
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_IPCOMP);
if (value)
write_config_option(fd, " compress=%s\n", value);
if (use_ikev2) {
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
write_config_option(fd, " ikev2=yes\n");
} else {
write_config_option(fd, " keyexchange=ikev2\n");
}
} else {
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
write_config_option(fd, " ikev2=no\n");
} else {
write_config_option(fd, " keyexchange=ikev1\n");
}
}
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_PFS);
if (value)
write_config_option(fd, " pfs=%s\n", value);
}
close(fd);
}
/**
* L2TP options
**/
/* If xl2tpd's default port 1701 is busy, use 0 (ephemeral random port) */
port = 1701;
if (use_ephemeral_port) {
port = 0;
}
if (priv->l2tp_daemon == NM_L2TP_L2TP_DAEMON_KL2TPD) {
/* kl2tpd config */
filename = g_strdup_printf("%s/kl2tpd.conf", rundir);
fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
g_free(filename);
if (fd == -1) {
crypto_deinit_openssl();
return nm_l2tp_ipsec_error(error, _("Could not write kl2tpd config."));
}
write_config_option(fd, "[tunnel.t1]\n");
if (!use_ephemeral_port)
write_config_option(fd, "local = \"0.0.0.0:1701\"\n");
write_config_option(fd, "peer = \"%s:1701\"\n", priv->saddr);
write_config_option(fd, "version = \"l2tpv2\"\n");
write_config_option(fd, "encap = \"udp\"\n");
write_config_option(fd, "[tunnel.t1.session.s1]\n");
write_config_option(fd, "pseudowire = \"ppp\"\n");
write_config_option(fd, "pppd_args = \"%s/ppp-options\"\n", rundir);
} else {
/* xl2tpd config */
filename = g_strdup_printf("%s/xl2tpd.conf", rundir);
fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
g_free(filename);
if (fd == -1) {
crypto_deinit_openssl();
return nm_l2tp_ipsec_error(error, _("Could not write xl2tpd config."));
}
write_config_option(fd, "[global]\n");
write_config_option(fd, "access control = yes\n");
write_config_option(fd, "port = %d\n", port);
if (_LOGD_enabled()) {
/* write_config_option (fd, "debug network = yes\n"); */
write_config_option(fd, "debug state = yes\n");
write_config_option(fd, "debug tunnel = yes\n");
write_config_option(fd, "debug avp = yes\n");
}
write_config_option(fd, "[lac l2tp]\n");
/* value = nm_setting_vpn_get_data_item (s_vpn, NM_L2TP_KEY_GATEWAY); */
write_config_option(fd, "lns = %s\n", priv->saddr);
if (_LOGD_enabled())
write_config_option(fd, "ppp debug = yes\n");
write_config_option(fd, "pppoptfile = %s/ppp-options\n", rundir);
write_config_option(fd, "autodial = yes\n");
write_config_option(fd, "tunnel rws = 8\n");
write_config_option(fd, "tx bps = 100000000\n");
write_config_option(fd, "rx bps = 100000000\n");
}
close(fd);
/* PPP options */
filename = g_strdup_printf("%s/ppp-options", rundir);
fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
g_free(filename);
if (fd == -1) {
crypto_deinit_openssl();
return nm_l2tp_ipsec_error(error, _("Could not write ppp options."));
}
if (_LOGD_enabled())
write_config_option(fd, "debug\n");
write_config_option(fd, "ipparam nm-l2tp-service-%s\n", priv->uuid);
/* pass gateway IP address to nm-l2tp-pppd-plugin via ipcp_wantoptions[0].hisaddr,
but let pppd use the remote IP address being offered by the peer using IPCP */
write_config_option(fd, ":%s\n", priv->saddr);
write_config_option(fd, "ipcp-accept-remote\n");
write_config_option(fd, "nodetach\n");
s_ip4 = nm_connection_get_setting_ip4_config(priv->connection);
if (!nm_setting_ip_config_get_ignore_auto_dns(s_ip4)) {
write_config_option(fd, "usepeerdns\n");
}
write_config_option(fd, "noipdefault\n");
write_config_option(fd, "nodefaultroute\n");
/* Don't need to auth the L2TP server */
write_config_option(fd, "noauth\n");
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MRRU);
if (value) {
write_config_option(fd, "multilink\n");
write_config_option(fd, "mrru %s\n", value);
}
/* pppd and xl2tpd on Linux require this option to support Android and iOS clients,
and pppd on Linux clients won't work without the same option */
write_config_option(fd, "noccp\n");
if (priv->user_authtype == TLS_AUTH) {
/* EAP-TLS patch for pppd only supports PEM keys & certs, so do conversion if necessary */
tls_key_filename = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER_KEY);
tls_cert_filename = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER_CERT);
tls_ca_filename = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER_CA);
tls_key_fileformat = crypto_file_format(tls_key_filename, &tls_need_password, error);
if (*error) {
close(fd);
crypto_deinit_openssl();
return FALSE;
}
if (tls_need_password)
value = nm_setting_vpn_get_secret(s_vpn, NM_L2TP_KEY_USER_CERTPASS);
else
value = NULL;
tls_key_out_filename = g_strdup_printf("%s/key.pem", rundir);
tls_cert_out_filename = g_strdup_printf("%s/cert.pem", rundir);
tls_ca_out_filename = g_strdup_printf("%s/ca.pem", rundir);
unlink(tls_key_out_filename);
unlink(tls_cert_out_filename);
unlink(tls_ca_out_filename);
if (tls_key_fileformat == NM_L2TP_CRYPTO_FILE_FORMAT_PKCS12) {
crypto_pkcs12_to_pem_files(tls_cert_filename,
value,
tls_key_out_filename,
tls_cert_out_filename,
tls_ca_out_filename,
error);
if (*error) {
close(fd);
crypto_deinit_openssl();
return FALSE;
}
} else {
switch (tls_key_fileformat) {
case NM_L2TP_CRYPTO_FILE_FORMAT_PKCS8_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_RSA_PKEY_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_DSA_PKEY_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_ECDSA_PKEY_DER:
crypto_pkey_der_to_pem_file(tls_key_filename, value, tls_key_out_filename, error);
if (*error) {
close(fd);
crypto_deinit_openssl();
return FALSE;
}
break;
default:
g_free(tls_key_out_filename);
tls_key_out_filename = NULL;
}
tls_cert_fileformat = crypto_file_format(tls_cert_filename, NULL, error);
if (*error) {
close(fd);
crypto_deinit_openssl();
return FALSE;
}
if (tls_cert_fileformat == NM_L2TP_CRYPTO_FILE_FORMAT_X509_DER) {
crypto_x509_der_to_pem_file(tls_cert_filename, tls_cert_out_filename, error);
if (*error) {
close(fd);
crypto_deinit_openssl();
return FALSE;
}
} else {
g_free(tls_cert_out_filename);
tls_cert_out_filename = NULL;
}
if (tls_ca_filename) {
tls_ca_fileformat = crypto_file_format(tls_ca_filename, NULL, error);
if (*error) {
close(fd);
crypto_deinit_openssl();
return FALSE;
}
if (tls_ca_fileformat == NM_L2TP_CRYPTO_FILE_FORMAT_X509_DER) {
crypto_x509_der_to_pem_file(tls_ca_filename, tls_ca_out_filename, error);
if (*error) {
close(fd);
crypto_deinit_openssl();
return FALSE;
}
} else {
g_free(tls_ca_out_filename);
tls_ca_out_filename = NULL;
}
} else {
g_free(tls_ca_out_filename);
tls_ca_out_filename = NULL;
}
}
write_config_option(fd, "need-peer-eap\n");
if (tls_key_out_filename) {
if (g_file_test(tls_key_out_filename, G_FILE_TEST_EXISTS)) {
write_config_option(fd, "key \"%s\"\n", tls_key_out_filename);
}
} else {
write_config_option(fd, "key \"%s\"\n", tls_key_filename);
}
if (tls_cert_out_filename) {
if (g_file_test(tls_cert_out_filename, G_FILE_TEST_EXISTS)) {
write_config_option(fd, "cert \"%s\"\n", tls_cert_out_filename);
}
} else {
write_config_option(fd, "cert \"%s\"\n", tls_cert_filename);
}
if (tls_ca_out_filename) {
if (g_file_test(tls_ca_out_filename, G_FILE_TEST_EXISTS)) {
write_config_option(fd, "ca \"%s\"\n", tls_ca_out_filename);
}
} else if (tls_ca_filename) {
write_config_option(fd, "ca \"%s\"\n", tls_ca_filename);
}
} else {
/* Username; try L2TP specific username first, then generic username */
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER);
if (!value || !*value)
value = nm_setting_vpn_get_user_name(s_vpn);
if (!value || !*value) {
write_config_option(fd, "name %s\n", value);
}
for (int i = 0; ppp_auth_options[i].name; i++) {
value = nm_setting_vpn_get_data_item(s_vpn, ppp_auth_options[i].name);
if (nm_streq0(value, "yes"))
write_config_option(fd, ppp_auth_options[i].write_to_config);
}
}
for (int i = 0; ppp_options[i].name; i++) {
value = nm_setting_vpn_get_data_item(s_vpn, ppp_options[i].name);
if (nm_streq0(value, "yes"))
write_config_option(fd, ppp_options[i].write_to_config);
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_LCP_ECHO_FAILURE);
if (value && *value) {
long int tmp_int;
/* Convert to integer and then back to string for security's sake
* because strtol ignores some leading and trailing characters.
*/
if (str_to_int(value, &tmp_int)) {
write_config_option(fd, "lcp-echo-failure %ld\n", tmp_int);
} else {
_LOGW("failed to convert lcp-echo-failure value '%s'", value);
}
} else {
write_config_option(fd, "lcp-echo-failure 0\n");
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_LCP_ECHO_INTERVAL);
if (value && *value) {
long int tmp_int;
if (str_to_int(value, &tmp_int)) {
write_config_option(fd, "lcp-echo-interval %ld\n", tmp_int);
} else {
_LOGW("failed to convert lcp-echo-interval value '%s'", value);
}
} else {
write_config_option(fd, "lcp-echo-interval 0\n");
}
write_config_option(fd, "plugin %s\n", NM_L2TP_PPPD_PLUGIN);
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MRU);
if (value) {
write_config_option(fd, "mru %s\n", value);
} else {
write_config_option(fd, "mru 1400\n");
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MTU);
if (value) {
write_config_option(fd, "mtu %s\n", value);
} else {
/* Default MTU to 1400, which is also what Microsoft Windows uses */
write_config_option(fd, "mtu 1400\n");
}
close(fd);
crypto_deinit_openssl();
return TRUE;
}
static void
nm_l2tp_stop_ipsec(NML2tpPluginPrivate *priv)
{
char cmdbuf[256];
GPtrArray *whack_argv;
int sys = 0;
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
whack_argv = g_ptr_array_new();
g_ptr_array_add(whack_argv, (gpointer) g_strdup(priv->ipsec_binary_path));
g_ptr_array_add(whack_argv, (gpointer) g_strdup("whack"));
g_ptr_array_add(whack_argv, (gpointer) g_strdup("--delete"));
g_ptr_array_add(whack_argv, (gpointer) g_strdup("--name"));
g_ptr_array_add(whack_argv, (gpointer) g_strdup(priv->uuid));
g_ptr_array_add(whack_argv, NULL);
if (!g_spawn_sync(NULL,
(char **) whack_argv->pdata,
NULL,
0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL)) {
free_l2tpd_args(whack_argv);
return;
}
} else {
snprintf(cmdbuf, sizeof(cmdbuf), "%s stop", priv->ipsec_binary_path);
sys = system(cmdbuf);
}
g_message("ipsec shut down");
}
static gboolean
nm_l2tp_start_ipsec(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error)
{
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(plugin);
char cmdbuf[256];
char * output = NULL;
int sys = 0, status, retry;
int msec;
gboolean rc = FALSE;
gchar * argv[5];
GPid pid_ipsec_up;
pid_t wpid;
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
snprintf(cmdbuf, sizeof(cmdbuf), "%s auto --status > /dev/null", priv->ipsec_binary_path);
sys = system(cmdbuf);
if (sys == 1) {
snprintf(cmdbuf, sizeof(cmdbuf), "%s start", priv->ipsec_binary_path);
sys = system(cmdbuf);
if (sys) {
return nm_l2tp_ipsec_error(error, _("Could not start the ipsec service."));
}
} else {
snprintf(cmdbuf, sizeof(cmdbuf), "%s restart", priv->ipsec_binary_path);
sys = system(cmdbuf);
if (sys) {
return nm_l2tp_ipsec_error(error, _("Could not restart the ipsec service."));
}
}
/* wait for Libreswan to get ready before performing an up operation */
snprintf(cmdbuf, sizeof(cmdbuf), "%s auto --ready", priv->ipsec_binary_path);
sys = system(cmdbuf);
for (retry = 0; retry < 5 && sys != 0; retry++) {
sleep(1);
sys = system(cmdbuf);
}
} else {
snprintf(cmdbuf, sizeof(cmdbuf), "%s status > /dev/null", priv->ipsec_binary_path);
sys = system(cmdbuf);
if (sys == 3) {
snprintf(cmdbuf,
sizeof(cmdbuf),
"%s start "
" --conf " RUNSTATEDIR "/nm-l2tp-%s/ipsec.conf --debug",
priv->ipsec_binary_path,
priv->uuid);
sys = system(cmdbuf);
if (sys) {
return nm_l2tp_ipsec_error(error, _("Could not start the ipsec service."));
}
} else {
snprintf(cmdbuf,
sizeof(cmdbuf),
"%s restart "
" --conf " RUNSTATEDIR "/nm-l2tp-%s/ipsec.conf --debug",
priv->ipsec_binary_path,
priv->uuid);
sys = system(cmdbuf);
if (sys) {
return nm_l2tp_ipsec_error(error, _("Could not restart the ipsec service."));
}
}
/* wait for strongSwan to get ready before performing an up operation */
snprintf(cmdbuf, sizeof(cmdbuf), "%s rereadsecrets", priv->ipsec_binary_path);
sys = system(cmdbuf);
for (retry = 0; retry < 5 && sys != 0; retry++) {
sleep(1);
sys = system(cmdbuf);
}
}
/* spawn ipsec script asynchronously as it sometimes doesn't exit */
pid_ipsec_up = 0;
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
snprintf(cmdbuf,
sizeof(cmdbuf),
"%s auto "
" --config " RUNSTATEDIR "/nm-l2tp-%s/ipsec.conf --verbose"
" --add '%s'",
priv->ipsec_binary_path,
priv->uuid,
priv->uuid);
sys = system(cmdbuf);
if (!sys) {
argv[0] = priv->ipsec_binary_path;
argv[1] = "auto";
argv[2] = "--up";
argv[3] = priv->uuid;
argv[4] = NULL;
if (!g_spawn_async(NULL,
argv,
NULL,
G_SPAWN_DO_NOT_REAP_CHILD,
NULL,
NULL,
&pid_ipsec_up,
NULL)) {
pid_ipsec_up = 0;
} else {
if (pid_ipsec_up)
_LOGI("Spawned ipsec auto --up script with PID %d.", pid_ipsec_up);
}
}
} else {
argv[0] = priv->ipsec_binary_path;
argv[1] = "up";
argv[2] = priv->uuid;
argv[3] = NULL;
if (!g_spawn_async(NULL,
argv,
NULL,
G_SPAWN_DO_NOT_REAP_CHILD,
NULL,
NULL,
&pid_ipsec_up,
NULL)) {
pid_ipsec_up = 0;
} else {
if (pid_ipsec_up)
_LOGI("Spawned ipsec up script with PID %d.", pid_ipsec_up);
}
}
if (pid_ipsec_up > 0) {
msec = 0;
do {
usleep(250000); /* 0.25 seconds */
msec += 250; /* 250 ms == 0.25 seconds */
wpid = waitpid(pid_ipsec_up, &status, WNOHANG);
} while (wpid == 0 && msec < NM_L2TP_WAIT_IPSEC);
if (wpid <= 0) {
if (kill(pid_ipsec_up, 0) == 0) {
_LOGW("Timeout trying to establish IPsec connection");
_LOGI("Terminating ipsec script with PID %d.", pid_ipsec_up);
kill(pid_ipsec_up, SIGKILL);
/* Reap child */
waitpid(pid_ipsec_up, NULL, 0);
}
} else if (wpid == pid_ipsec_up && WIFEXITED(status)) {
if (!WEXITSTATUS(status)) {
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
rc = TRUE;
_LOGI("Libreswan IPsec tunnel is up.");
} else {
/* Do not trust exit status of strongSwan 'ipsec up' command.
explictly check if connection is established.
strongSwan bug #1449.
*/
snprintf(cmdbuf,
sizeof(cmdbuf),
"%s status '%s'",
priv->ipsec_binary_path,
priv->uuid);
if (g_spawn_command_line_sync(cmdbuf, &output, NULL, NULL, NULL)) {
rc = output && strstr(output, "ESTABLISHED");
g_free(output);
if (rc) {
_LOGI("strongSwan IPsec tunnel is up.");
}
}
}
}
}
}
if (!rc) {
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_STRONGSWAN) {
snprintf(cmdbuf, sizeof(cmdbuf), "%s stop", priv->ipsec_binary_path);
sys = system(cmdbuf);
}
_LOGW("Could not establish IPsec tunnel.");
}
return rc;
}
static gboolean
nm_l2tp_start_l2tpd_binary(NML2tpPlugin *plugin, NMSettingVpn *s_vpn, GError **error)
{
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(plugin);
GPid pid;
const char * l2tpd_binary;
GPtrArray * l2tpd_argv;
l2tpd_binary = nm_find_l2tpd(NULL);
if (!l2tpd_binary) {
return nm_l2tp_ipsec_error(error, _("Could not find kl2tpd or xl2tpd binaries."));
}
l2tpd_argv = g_ptr_array_new();
if (priv->l2tp_daemon == NM_L2TP_L2TP_DAEMON_KL2TPD) {
g_ptr_array_add(l2tpd_argv, (gpointer) g_strdup(l2tpd_binary));
g_ptr_array_add(l2tpd_argv, (gpointer) g_strdup("-config"));
g_ptr_array_add(
l2tpd_argv,
(gpointer) g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/kl2tpd.conf", priv->uuid));
} else {
g_ptr_array_add(l2tpd_argv, (gpointer) g_strdup(l2tpd_binary));
g_ptr_array_add(l2tpd_argv, (gpointer) g_strdup("-D"));
g_ptr_array_add(l2tpd_argv, (gpointer) g_strdup("-c"));
g_ptr_array_add(
l2tpd_argv,
(gpointer) g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/xl2tpd.conf", priv->uuid));
g_ptr_array_add(l2tpd_argv, (gpointer) g_strdup("-C"));
g_ptr_array_add(
l2tpd_argv,
(gpointer) g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/xl2tpd-control", priv->uuid));
g_ptr_array_add(l2tpd_argv, (gpointer) g_strdup("-p"));
g_ptr_array_add(
l2tpd_argv,
(gpointer) g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/xl2tpd.pid", priv->uuid));
}
g_ptr_array_add(l2tpd_argv, NULL);
if (!g_spawn_async(NULL,
(char **) l2tpd_argv->pdata,
NULL,
G_SPAWN_DO_NOT_REAP_CHILD,
NULL,
NULL,
&pid,
error)) {
g_ptr_array_free(l2tpd_argv, TRUE);
return FALSE;
}
free_l2tpd_args(l2tpd_argv);
if (priv->l2tp_daemon == NM_L2TP_L2TP_DAEMON_KL2TPD) {
g_message("kl2tpd started with pid %d", pid);
} else {
g_message("xl2tpd started with pid %d", pid);
}
NM_L2TP_PLUGIN_GET_PRIVATE(plugin)->pid_l2tpd = pid;
g_child_watch_add(pid, l2tpd_watch_cb, plugin);
priv->ppp_timeout_handler = g_timeout_add(NM_L2TP_WAIT_PPPD, pppd_timed_out, plugin);
return TRUE;
}
static void
remove_timeout_handler(NML2tpPlugin *plugin)
{
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(plugin);
if (priv->ppp_timeout_handler) {
g_source_remove(priv->ppp_timeout_handler);
priv->ppp_timeout_handler = 0;
}
}
static gboolean
handle_need_secrets(NMDBusL2tpPpp *object, GDBusMethodInvocation *invocation, gpointer user_data)
{
NML2tpPlugin * self = NM_L2TP_PLUGIN(user_data);
NML2tpPluginPrivate * priv = NM_L2TP_PLUGIN_GET_PRIVATE(self);
NMSettingVpn * s_vpn;
NML2tpCryptoFileFormat tls_key_fileformat;
const char * user, *password, *domain, *auth_type, *tls_key_filename;
gchar * username;
gchar * key_filename;
gboolean tls_need_password = FALSE;
remove_timeout_handler(NM_L2TP_PLUGIN(user_data));
s_vpn = nm_connection_get_setting_vpn(priv->connection);
g_assert(s_vpn);
auth_type = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER_AUTH_TYPE);
if (nm_streq0(auth_type, NM_L2TP_AUTHTYPE_TLS)) {
tls_key_filename = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER_KEY);
tls_key_fileformat = crypto_file_format(tls_key_filename, &tls_need_password, NULL);
switch (tls_key_fileformat) {
case NM_L2TP_CRYPTO_FILE_FORMAT_PKCS12:
case NM_L2TP_CRYPTO_FILE_FORMAT_PKCS8_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_RSA_PKEY_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_DSA_PKEY_DER:
case NM_L2TP_CRYPTO_FILE_FORMAT_ECDSA_PKEY_DER:
key_filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/key.pem", priv->uuid);
break;
default:
key_filename = g_strdup(tls_key_filename);
}
if (!tls_need_password) {
nmdbus_l2tp_ppp_complete_need_secrets(object, invocation, key_filename, "");
} else {
password = nm_setting_vpn_get_secret(s_vpn, NM_L2TP_KEY_USER_CERTPASS);
if (!password || !strlen(password)) {
g_dbus_method_invocation_return_error_literal(
invocation,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
_("Missing or invalid VPN user certificate password."));
g_free(key_filename);
return FALSE;
;
}
nmdbus_l2tp_ppp_complete_need_secrets(object, invocation, key_filename, password);
}
g_free(key_filename);
} else {
/* Username; try L2TP specific username first, then generic username */
user = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER);
if (!user || !strlen(user))
user = nm_setting_vpn_get_user_name(s_vpn);
if (!user || !strlen(user)) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
_("Missing VPN username."));
return FALSE;
}
password = nm_setting_vpn_get_secret(s_vpn, NM_L2TP_KEY_PASSWORD);
if (!password || !strlen(password)) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
_("Missing or invalid VPN password."));
return FALSE;
;
}
/* Domain is optional */
domain = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_DOMAIN);
/* Success */
if (domain && strlen(domain))
username = g_strdup_printf("%s\\%s", domain, user);
else
username = g_strdup(user);
nmdbus_l2tp_ppp_complete_need_secrets(object, invocation, username, password);
g_free(username);
}
return TRUE;
}
static gboolean
handle_set_state(NMDBusL2tpPpp * object,
GDBusMethodInvocation *invocation,
guint arg_state,
gpointer user_data)
{
remove_timeout_handler(NM_L2TP_PLUGIN(user_data));
if (arg_state == NM_PPP_STATUS_DEAD || arg_state == NM_PPP_STATUS_DISCONNECT)
nm_vpn_service_plugin_disconnect(NM_VPN_SERVICE_PLUGIN(user_data), NULL);
g_dbus_method_invocation_return_value(invocation, NULL);
return TRUE;
}
static gboolean
handle_set_ip4_config(NMDBusL2tpPpp * object,
GDBusMethodInvocation *invocation,
GVariant * arg_config,
gpointer user_data)
{
NML2tpPlugin * plugin = NM_L2TP_PLUGIN(user_data);
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(plugin);
GVariantIter iter;
const char * key;
GVariant * value;
GVariantBuilder builder;
GVariant * new_config;
remove_timeout_handler(plugin);
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
g_variant_iter_init(&iter, arg_config);
while (g_variant_iter_next(&iter, "{&sv}", &key, &value)) {
g_variant_builder_add(&builder, "{sv}", key, value);
g_variant_unref(value);
}
/* Insert the external VPN gateway into the table, which the pppd plugin
* simply doesn't know about.
*/
g_variant_builder_add(&builder, "{sv}", NM_L2TP_KEY_GATEWAY, g_variant_new_uint32(priv->naddr));
new_config = g_variant_builder_end(&builder);
g_variant_ref_sink(new_config);
nm_vpn_service_plugin_set_ip4_config(NM_VPN_SERVICE_PLUGIN(plugin), new_config);
g_variant_unref(new_config);
g_dbus_method_invocation_return_value(invocation, NULL);
return TRUE;
}
static gboolean
lookup_gateway(NML2tpPlugin *self, const char *src, GError **error)
{
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(self);
const char * p = src;
gboolean is_name = FALSE;
struct in_addr naddr;
struct addrinfo hints;
struct addrinfo * result = NULL, *rp;
int err;
char buf[INET_ADDRSTRLEN];
g_return_val_if_fail(src != NULL, FALSE);
if (priv->saddr) {
g_free(priv->saddr);
priv->saddr = NULL;
}
while (*p) {
if (*p != '.' && !isdigit(*p)) {
is_name = TRUE;
break;
}
p++;
}
if (is_name == FALSE) {
errno = 0;
if (inet_pton(AF_INET, src, &naddr) <= 0) {
return nm_l2tp_ipsec_error(error, _("couldn't convert L2TP VPN gateway IP address."));
}
priv->naddr = naddr.s_addr;
priv->saddr = g_strdup(src);
return TRUE;
}
/* It's a hostname, resolve it */
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_flags = AI_ADDRCONFIG;
err = getaddrinfo(src, NULL, &hints, &result);
if (err != 0) {
return nm_l2tp_ipsec_error(error, _("couldn't look up L2TP VPN gateway IP address "));
}
/* If the hostname resolves to multiple IP addresses, use the first one.
* FIXME: maybe we just want to use a random one instead?
*/
memset(&naddr, 0, sizeof(naddr));
for (rp = result; rp; rp = rp->ai_next) {
if ((rp->ai_family == AF_INET) && (rp->ai_addrlen == sizeof(struct sockaddr_in))) {
struct sockaddr_in *inptr = (struct sockaddr_in *) rp->ai_addr;
memcpy(&naddr, &(inptr->sin_addr), sizeof(struct in_addr));
break;
}
}
freeaddrinfo(result);
if (naddr.s_addr == 0) {
return nm_l2tp_ipsec_error(error, _("no usable addresses returned for L2TP VPN gateway "));
}
priv->naddr = naddr.s_addr;
priv->saddr = g_strdup(inet_ntop(AF_INET, &naddr, buf, sizeof(buf)));
return TRUE;
}
static gboolean
real_connect(NMVpnServicePlugin *plugin, NMConnection *connection, GError **error)
{
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(plugin);
NMSettingVpn * s_vpn;
const char * gwaddr;
const char * value;
const char * uuid;
s_vpn = nm_connection_get_setting_vpn(connection);
g_assert(s_vpn);
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_ENABLE);
_LOGI("ipsec enable flag: %s", value ? value : "(null)");
priv->ipsec_daemon = NM_L2TP_IPSEC_DAEMON_UNKNOWN;
if (nm_streq0(value, "yes")) {
if (!(value = nm_find_ipsec())) {
return nm_l2tp_ipsec_error(
error,
_("Could not find the ipsec binary. Is Libreswan or strongSwan installed?"));
}
strncpy(priv->ipsec_binary_path, value, sizeof(priv->ipsec_binary_path) - 1);
priv->ipsec_daemon = check_ipsec_daemon(priv->ipsec_binary_path);
if (priv->ipsec_daemon == NM_L2TP_IPSEC_DAEMON_OPENSWAN) {
return nm_l2tp_ipsec_error(
error,
_("Openswan is no longer supported, use Libreswan or strongSwan."));
} else if (priv->ipsec_daemon != NM_L2TP_IPSEC_DAEMON_STRONGSWAN
&& priv->ipsec_daemon != NM_L2TP_IPSEC_DAEMON_LIBRESWAN) {
return nm_l2tp_ipsec_error(error, _("Neither Libreswan nor strongSwan were found."));
}
}
priv->l2tp_daemon = NM_L2TP_L2TP_DAEMON_UNKNOWN;
nm_find_l2tpd(&(priv->l2tp_daemon));
g_clear_object(&priv->connection);
priv->connection = g_object_ref(connection);
uuid = nm_connection_get_uuid(priv->connection);
if (!(uuid && *uuid)) {
return nm_l2tp_ipsec_error(error, _("could not retrieve connection UUID"));
}
g_free(priv->uuid);
priv->uuid = g_strdup(uuid);
gwaddr = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_GATEWAY);
if (!gwaddr || !strlen(gwaddr)) {
return nm_l2tp_ipsec_error(error, _("Invalid or missing L2TP gateway."));
}
/* Look up the IP address of the L2TP server; if the server has multiple
* addresses, because we can't get the actual IP used back from xl2tp itself,
* we need to do name->addr conversion here and only pass the IP address
* down to pppd/l2tp. If only xl2tp could somehow return the IP address it's
* using for the connection, we wouldn't need to do this...
*/
if (!lookup_gateway(NM_L2TP_PLUGIN(plugin), gwaddr, error))
return FALSE;
if (!nm_l2tp_properties_validate(s_vpn, error))
return FALSE;
if (!nm_l2tp_secrets_validate(s_vpn, error))
return FALSE;
if (!nm_l2tp_config_write(NM_L2TP_PLUGIN(plugin), s_vpn, error))
return FALSE;
if (getenv("NM_L2TP_DUMP_CONNECTION") || _LOGD_enabled())
nm_connection_dump(connection);
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_ENABLE);
if (nm_streq0(value, "yes")) {
_LOGI("starting ipsec");
if (!nm_l2tp_start_ipsec(NM_L2TP_PLUGIN(plugin), s_vpn, error))
return FALSE;
priv->ipsec_up = TRUE;
}
return nm_l2tp_start_l2tpd_binary(NM_L2TP_PLUGIN(plugin), s_vpn, error);
}
static gboolean
real_need_secrets(NMVpnServicePlugin *plugin,
NMConnection * connection,
const char ** setting_name,
GError ** error)
{
NMSettingVpn * s_vpn;
NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
const char * value;
gboolean need_secrets = FALSE;
g_return_val_if_fail(NM_IS_VPN_SERVICE_PLUGIN(plugin), FALSE);
g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);
s_vpn = nm_connection_get_setting_vpn(connection);
/* Legacy KDE Plasma-nm certificate support does not handle password protected private keys */
value = nm_setting_vpn_get_data_item(s_vpn, KDE_PLASMA_L2TP_KEY_USE_CERT);
if (nm_streq0(value, "yes")) {
return FALSE;
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER_AUTH_TYPE);
if (nm_streq0(value, NM_L2TP_AUTHTYPE_TLS)) {
/* Check if user certificate or private key needs password */
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_USER_KEY);
crypto_file_format(value, &need_secrets, NULL);
/* Don't need the password if we already have one */
if (need_secrets
&& nm_setting_vpn_get_secret(NM_SETTING_VPN(s_vpn), NM_L2TP_KEY_USER_CERTPASS)) {
need_secrets = FALSE;
}
} else {
/* Check if need password for user credentials */
nm_setting_get_secret_flags(NM_SETTING(s_vpn), NM_L2TP_KEY_PASSWORD, &flags, NULL);
/* Need the password if user specified it is required */
if (!(flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED))
need_secrets = TRUE;
/* Don't need the password if we already have one */
if (need_secrets && nm_setting_vpn_get_secret(NM_SETTING_VPN(s_vpn), NM_L2TP_KEY_PASSWORD))
need_secrets = FALSE;
}
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_IPSEC_ENABLE);
if (nm_streq0(value, "yes")) {
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MACHINE_AUTH_TYPE);
if (!need_secrets && nm_streq0(value, NM_L2TP_AUTHTYPE_TLS)) {
/* Check if machine certificate or machine private key need a password */
value = nm_setting_vpn_get_data_item(s_vpn, NM_L2TP_KEY_MACHINE_KEY);
crypto_file_format(value, &need_secrets, NULL);
/* Don't need the password if we already have one */
if (need_secrets
&& nm_setting_vpn_get_secret(NM_SETTING_VPN(s_vpn), NM_L2TP_KEY_MACHINE_CERTPASS)) {
need_secrets = FALSE;
}
} else if (!need_secrets) { /* NM_L2TP_AUTHTYPE_PSK */
/* Check if need PSK */
nm_setting_get_secret_flags(NM_SETTING(s_vpn), NM_L2TP_KEY_IPSEC_PSK, &flags, NULL);
/* Need the PSK if user specified it is required */
if (!(flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED))
need_secrets = TRUE;
/* Don't need the PSK if we already have one */
if (need_secrets
&& nm_setting_vpn_get_secret_or_legacy_data_item(NM_SETTING_VPN(s_vpn),
NM_L2TP_KEY_IPSEC_PSK)) {
need_secrets = FALSE;
}
}
}
if (need_secrets)
*setting_name = NM_SETTING_VPN_SETTING_NAME;
return need_secrets;
}
static gboolean
ensure_killed(gpointer data)
{
int pid = GPOINTER_TO_INT(data);
if (kill(pid, 0) == 0)
kill(pid, SIGKILL);
return FALSE;
}
static gboolean
real_disconnect(NMVpnServicePlugin *plugin, GError **err)
{
char * filename;
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(plugin);
if (priv->pid_l2tpd) {
if (kill(priv->pid_l2tpd, SIGTERM) == 0)
g_timeout_add(2000, ensure_killed, GINT_TO_POINTER(priv->pid_l2tpd));
else
kill(priv->pid_l2tpd, SIGKILL);
if (priv->l2tp_daemon == NM_L2TP_L2TP_DAEMON_KL2TPD) {
_LOGI("Terminated kl2tpd daemon with PID %d.", priv->pid_l2tpd);
} else {
_LOGI("Terminated xl2tpd daemon with PID %d.", priv->pid_l2tpd);
}
priv->pid_l2tpd = 0;
}
if (priv->ipsec_up) {
nm_l2tp_stop_ipsec(priv);
}
g_clear_object(&priv->connection);
if (priv->saddr) {
g_free(priv->saddr);
priv->saddr = NULL;
}
if (!gl.debug) {
/* Clean up config files */
filename = g_strdup_printf(NM_IPSEC_SECRETS_DIR "/ipsec.nm-l2tp.secrets");
unlink(filename);
g_free(filename);
filename = g_strdup_printf("/etc/strongswan/ipsec.d/ipsec.nm-l2tp.secrets");
unlink(filename);
g_free(filename);
filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/kl2tpd.conf", priv->uuid);
unlink(filename);
g_free(filename);
filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/xl2tpd.conf", priv->uuid);
unlink(filename);
g_free(filename);
filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/xl2tpd-control", priv->uuid);
unlink(filename);
g_free(filename);
filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/xl2tpd.pid", priv->uuid);
unlink(filename);
g_free(filename);
filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/ppp-options", priv->uuid);
unlink(filename);
g_free(filename);
filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s/ipsec.conf", priv->uuid);
unlink(filename);
g_free(filename);
filename = g_strdup_printf(RUNSTATEDIR "/nm-l2tp-%s", priv->uuid);
rmdir(filename);
g_free(filename);
}
return TRUE;
}
static void
state_changed_cb(GObject *object, NMVpnServiceState state, gpointer user_data)
{
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(object);
switch (state) {
case NM_VPN_SERVICE_STATE_STARTED:
remove_timeout_handler(NM_L2TP_PLUGIN(object));
break;
case NM_VPN_SERVICE_STATE_UNKNOWN:
case NM_VPN_SERVICE_STATE_INIT:
case NM_VPN_SERVICE_STATE_SHUTDOWN:
case NM_VPN_SERVICE_STATE_STOPPING:
case NM_VPN_SERVICE_STATE_STOPPED:
remove_timeout_handler(NM_L2TP_PLUGIN(object));
g_clear_object(&priv->connection);
if (priv->saddr) {
g_free(priv->saddr);
priv->saddr = NULL;
}
break;
default:
break;
}
}
static void
dispose(GObject *object)
{
NML2tpPluginPrivate * priv = NM_L2TP_PLUGIN_GET_PRIVATE(object);
GDBusInterfaceSkeleton *skeleton = NULL;
if (priv->dbus_skeleton)
skeleton = G_DBUS_INTERFACE_SKELETON(priv->dbus_skeleton);
if (skeleton) {
if (g_dbus_interface_skeleton_get_object_path(skeleton))
g_dbus_interface_skeleton_unexport(skeleton);
g_signal_handlers_disconnect_by_func(skeleton, handle_need_secrets, object);
g_signal_handlers_disconnect_by_func(skeleton, handle_set_state, object);
g_signal_handlers_disconnect_by_func(skeleton, handle_set_ip4_config, object);
}
g_clear_object(&priv->connection);
if (priv->saddr) {
g_free(priv->saddr);
priv->saddr = NULL;
}
G_OBJECT_CLASS(nm_l2tp_plugin_parent_class)->dispose(object);
}
static void
nm_l2tp_plugin_init(NML2tpPlugin *plugin)
{}
static void
nm_l2tp_plugin_class_init(NML2tpPluginClass *l2tp_class)
{
GObjectClass * object_class = G_OBJECT_CLASS(l2tp_class);
NMVpnServicePluginClass *parent_class = NM_VPN_SERVICE_PLUGIN_CLASS(l2tp_class);
g_type_class_add_private(object_class, sizeof(NML2tpPluginPrivate));
/* virtual methods */
object_class->dispose = dispose;
parent_class->connect = real_connect;
parent_class->need_secrets = real_need_secrets;
parent_class->disconnect = real_disconnect;
}
static GInitableIface *ginitable_parent_iface = NULL;
static gboolean
init_sync(GInitable *object, GCancellable *cancellable, GError **error)
{
NML2tpPluginPrivate *priv = NM_L2TP_PLUGIN_GET_PRIVATE(object);
GDBusConnection * bus;
if (!ginitable_parent_iface->init(object, cancellable, error))
return FALSE;
g_signal_connect(G_OBJECT(object), "state-changed", G_CALLBACK(state_changed_cb), NULL);
bus = nm_vpn_service_plugin_get_connection(NM_VPN_SERVICE_PLUGIN(object)),
priv->dbus_skeleton = nmdbus_l2tp_ppp_skeleton_new();
if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(priv->dbus_skeleton),
bus,
NM_DBUS_PATH_L2TP_PPP,
error)) {
g_prefix_error(error, "Failed to export helper interface: ");
g_object_unref(bus);
return FALSE;
}
g_dbus_connection_register_object(bus,
NM_DBUS_PATH_L2TP_PPP,
nmdbus_l2tp_ppp_interface_info(),
NULL,
NULL,
NULL,
NULL);
g_signal_connect(priv->dbus_skeleton,
"handle-need-secrets",
G_CALLBACK(handle_need_secrets),
object);
g_signal_connect(priv->dbus_skeleton, "handle-set-state", G_CALLBACK(handle_set_state), object);
g_signal_connect(priv->dbus_skeleton,
"handle-set-ip4-config",
G_CALLBACK(handle_set_ip4_config),
object);
g_object_unref(bus);
return TRUE;
}
static void
nm_l2tp_plugin_initable_iface_init(GInitableIface *iface)
{
ginitable_parent_iface = g_type_interface_peek_parent(iface);
iface->init = init_sync;
}
NML2tpPlugin *
nm_l2tp_plugin_new(const char *bus_name)
{
NML2tpPlugin *plugin;
GError * error = NULL;
plugin = g_initable_new(NM_TYPE_L2TP_PLUGIN,
NULL,
&error,
NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME,
bus_name,
NM_VPN_SERVICE_PLUGIN_DBUS_WATCH_PEER,
!_LOGD_enabled(),
NULL);
if (!plugin) {
_LOGW("Failed to initialize a plugin instance: %s", error->message);
g_error_free(error);
}
return plugin;
}
static void
quit_mainloop(NML2tpPlugin *plugin, gpointer user_data)
{
g_main_loop_quit((GMainLoop *) user_data);
}
int
main(int argc, char *argv[])
{
NML2tpPlugin * plugin;
GMainLoop * main_loop;
gboolean persist = FALSE;
GOptionContext * opt_ctx = NULL;
GError * error = NULL;
g_autofree char *bus_name_free = NULL;
const char * bus_name;
char sbuf[30];
GOptionEntry options[] = {{"persist",
0,
0,
G_OPTION_ARG_NONE,
&persist,
N_("Don't quit when VPN connection terminates"),
NULL},
{"debug",
0,
0,
G_OPTION_ARG_NONE,
&gl.debug,
N_("Enable verbose debug logging (may expose passwords)"),
NULL},
{"bus-name",
0,
0,
G_OPTION_ARG_STRING,
&bus_name_free,
N_("D-Bus name to use for this instance"),
NULL},
{NULL}};
/* locale will be set according to environment LC_* variables */
setlocale(LC_ALL, "");
bindtextdomain(GETTEXT_PACKAGE, NM_L2TP_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
textdomain(GETTEXT_PACKAGE);
/* Parse options */
opt_ctx = g_option_context_new(NULL);
g_option_context_set_translation_domain(opt_ctx, GETTEXT_PACKAGE);
g_option_context_set_ignore_unknown_options(opt_ctx, FALSE);
g_option_context_set_help_enabled(opt_ctx, TRUE);
g_option_context_add_main_entries(opt_ctx, options, NULL);
g_option_context_set_summary(opt_ctx,
_("nm-l2tp-service provides L2TP VPN capability with optional "
"IPsec support to NetworkManager."));
if (!g_option_context_parse(opt_ctx, &argc, &argv, &error)) {
g_printerr("Error parsing the command line options: %s\n", error->message);
g_option_context_free(opt_ctx);
g_error_free(error);
return EXIT_FAILURE;
}
g_option_context_free(opt_ctx);
bus_name = bus_name_free ?: NM_DBUS_SERVICE_L2TP;
if (getenv("NM_PPP_DEBUG"))
gl.debug = TRUE;
gl.log_level = _nm_utils_ascii_str_to_int64(getenv("NM_VPN_LOG_LEVEL"),
10,
0,
LOG_DEBUG,
gl.debug ? LOG_INFO : LOG_NOTICE);
_LOGD("nm-l2tp-service (version " DIST_VERSION ") starting...");
_LOGD(" uses%s --bus-name \"%s\"", bus_name_free ? "" : " default", bus_name);
setenv("NM_VPN_LOG_LEVEL", nm_sprintf_buf(sbuf, "%d", gl.log_level), TRUE);
setenv("NM_VPN_LOG_PREFIX_TOKEN", nm_sprintf_buf(sbuf, "%ld", (long) getpid()), TRUE);
setenv("NM_DBUS_SERVICE_L2TP", bus_name, 0);
plugin = nm_l2tp_plugin_new(bus_name);
if (!plugin)
exit(EXIT_FAILURE);
main_loop = g_main_loop_new(NULL, FALSE);
if (!persist)
g_signal_connect(plugin, "quit", G_CALLBACK(quit_mainloop), main_loop);
g_main_loop_run(main_loop);
g_main_loop_unref(main_loop);
g_object_unref(plugin);
return EXIT_SUCCESS;
}