network-manager-wireguard-u.../src/nm-wireguard-service.c

2827 lines
83 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* nm-openvpn-service - openvpn integration with NetworkManager
*
* Copyright (C) 2005 - 2008 Tim Niemueller <tim@niemueller.de>
* Copyright (C) 2005 - 2010 Dan Williams <dcbw@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* $Id: nm-openvpn-service.c 4232 2008-10-29 09:13:40Z tambeti $
*
*/
#include "nm-default.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 <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <pwd.h>
#include <grp.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <glib-unix.h>
#include "utils.h"
#include "import-export.h"
#include "nm-utils/nm-shared-utils.h"
#include "nm-utils/nm-vpn-plugin-macros.h"
#if !defined(DIST_VERSION)
# define DIST_VERSION VERSION
#endif
// TODO remove me -- just for getting rid of error underlining
#ifndef LOCALSTATEDIR
#define LOCALSTATEDIR ""
#endif
#ifndef LIBEXECDIR
#define LIBEXECDIR ""
#endif
#ifndef NM_WIREGUARD_LOCALEDIR
#define NM_WIREGUARD_LOCALEDIR ""
#endif
#define RUNDIR LOCALSTATEDIR"/run/NetworkManager"
static struct {
gboolean debug;
int log_level;
int log_level_ovpn;
bool log_syslog;
GSList *pids_pending_list;
} gl/*obal*/;
#define NM_OPENVPN_HELPER_PATH LIBEXECDIR"/nm-openvpn-service-openvpn-helper"
/*****************************************************************************/
#define NM_TYPE_WIREGUARD_PLUGIN (nm_wireguard_plugin_get_type ())
#define NM_WIREGUARD_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIREGUARD_PLUGIN, NMWireguardPlugin))
#define NM_WIREGUARD_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIREGUARD_PLUGIN, NMWireguardPluginClass))
#define NM_IS_WIREGUARD_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIREGUARD_PLUGIN))
#define NM_IS_WIREGUARD_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIREGUARD_PLUGIN))
#define NM_WIREGUARD_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIREGUARD_PLUGIN, NMWireguardPluginClass))
typedef struct {
NMVpnServicePlugin parent;
} NMWireguardPlugin;
typedef struct {
NMVpnServicePluginClass parent;
} NMWireguardPluginClass;
GType nm_wireguard_plugin_get_type (void);
NMWireguardPlugin *nm_wireguard_plugin_new (const char *bus_name);
/*****************************************************************************/
typedef struct _Configs{
NMVpnServicePlugin *plugin;
GVariant *config;
GVariant *ip4config;
GVariant *ip6config;
} Configs;
typedef enum {
OPENVPN_BINARY_VERSION_INVALID,
OPENVPN_BINARY_VERSION_UNKNOWN,
OPENVPN_BINARY_VERSION_2_3_OR_OLDER,
OPENVPN_BINARY_VERSION_2_4_OR_NEWER,
} OpenvpnBinaryVersion;
typedef struct {
GPid pid;
guint watch_id;
guint kill_id;
NMWireguardPlugin *plugin;
} PidsPendingData;
typedef struct {
char *default_username;
char *username;
char *password;
char *priv_key_pass;
char *proxy_username;
char *proxy_password;
char *pending_auth;
char *challenge_state_id;
char *challenge_text;
GIOChannel *socket_channel;
guint socket_channel_eventid;
} NMWireguardPluginIOData;
typedef struct {
GPid pid;
guint connect_timer;
guint connect_count;
NMWireguardPluginIOData *io_data;
gboolean interactive;
char *mgt_path;
char *connection_file;
GString *connection_config;
} NMWireguardPluginPrivate;
G_DEFINE_TYPE (NMWireguardPlugin, nm_wireguard_plugin, NM_TYPE_VPN_SERVICE_PLUGIN)
#define NM_WIREGUARD_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_WIREGUARD_PLUGIN, NMWireguardPluginPrivate))
/*****************************************************************************/
typedef struct _PluginConnection {
NMVpnServicePlugin *plugin;
NMConnection *connection;
} PluginConnection;
typedef struct {
const char *name;
GType type;
gint int_min;
gint int_max;
gboolean address;
} ValidProperty;
static const ValidProperty valid_properties[] = {
{ NM_OPENVPN_KEY_AUTH, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_CA, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_CERT, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_CIPHER, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_KEYSIZE, G_TYPE_INT, 1, 65535, FALSE },
{ NM_OPENVPN_KEY_COMP_LZO, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_CONNECTION_TYPE, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_FLOAT, G_TYPE_BOOLEAN, 0, 0, FALSE },
{ NM_OPENVPN_KEY_FRAGMENT_SIZE, G_TYPE_INT, 0, G_MAXINT, FALSE },
{ NM_OPENVPN_KEY_KEY, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_LOCAL_IP, G_TYPE_STRING, 0, 0, TRUE },
{ NM_OPENVPN_KEY_MSSFIX, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_MTU_DISC, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_PING, G_TYPE_INT, 0, G_MAXINT, FALSE },
{ NM_OPENVPN_KEY_PING_EXIT, G_TYPE_INT, 0, G_MAXINT, FALSE },
{ NM_OPENVPN_KEY_PING_RESTART, G_TYPE_INT, 0, G_MAXINT, FALSE },
{ NM_OPENVPN_KEY_MAX_ROUTES, G_TYPE_INT, 0, 100000000, FALSE },
{ NM_OPENVPN_KEY_PROTO_TCP, G_TYPE_BOOLEAN, 0, 0, FALSE },
{ NM_OPENVPN_KEY_PORT, G_TYPE_INT, 1, 65535, FALSE },
{ NM_OPENVPN_KEY_PROXY_TYPE, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_PROXY_SERVER, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_PROXY_PORT, G_TYPE_INT, 1, 65535, FALSE },
{ NM_OPENVPN_KEY_PROXY_RETRY, G_TYPE_BOOLEAN, 0, 0, FALSE },
{ NM_OPENVPN_KEY_HTTP_PROXY_USERNAME, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_REMOTE, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_REMOTE_RANDOM, G_TYPE_BOOLEAN, 0, 0, FALSE },
{ NM_OPENVPN_KEY_REMOTE_IP, G_TYPE_STRING, 0, 0, TRUE },
{ NM_OPENVPN_KEY_RENEG_SECONDS, G_TYPE_INT, 0, G_MAXINT, FALSE },
{ NM_OPENVPN_KEY_STATIC_KEY, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_STATIC_KEY_DIRECTION, G_TYPE_INT, 0, 1, FALSE },
{ NM_OPENVPN_KEY_TA, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_TA_DIR, G_TYPE_INT, 0, 1, FALSE },
{ NM_OPENVPN_KEY_TAP_DEV, G_TYPE_BOOLEAN, 0, 0, FALSE },
{ NM_OPENVPN_KEY_DEV, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_DEV_TYPE, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_TUN_IPV6, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_TLS_CIPHER, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_TLS_CRYPT, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_TLS_REMOTE, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_VERIFY_X509_NAME, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_REMOTE_CERT_TLS, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_NS_CERT_TYPE, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_TUNNEL_MTU, G_TYPE_INT, 0, G_MAXINT, FALSE },
{ NM_OPENVPN_KEY_USERNAME, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_PASSWORD"-flags", G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_CERTPASS"-flags", G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_NOSECRET, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD"-flags", G_TYPE_STRING, 0, 0, FALSE },
{ NULL, G_TYPE_NONE, FALSE }
};
static const ValidProperty valid_secrets[] = {
{ NM_OPENVPN_KEY_PASSWORD, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_CERTPASS, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_NOSECRET, G_TYPE_STRING, 0, 0, FALSE },
{ NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD, G_TYPE_STRING, 0, 0, FALSE },
{ NULL, G_TYPE_NONE, FALSE }
};
/*****************************************************************************/
#define _NMLOG(level, ...) \
G_STMT_START { \
if (gl.log_level >= (level)) { \
g_print ("nm-openvpn[%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__)
/*****************************************************************************/
static const char *
wg_quick_find_exepath (void)
{
static const char *paths[] = {
"/usr/sbin/wg-quick",
"/usr/bin/wg-quick",
"/sbin/wg-quick",
"/bin/wg-quick",
"/usr/local/sbin/wg-quick",
"/usr/local/bin/wg-quick",
};
int i;
for (i = 0; i < G_N_ELEMENTS(paths); i++) {
if (g_file_test (paths[i], G_FILE_TEST_EXISTS)) {
return paths[i];
}
}
return NULL;
}
static const char *
openvpn_binary_find_exepath (void)
{
static const char *paths[] = {
"/usr/sbin/openvpn",
"/sbin/openvpn",
"/usr/local/sbin/openvpn",
};
int i;
for (i = 0; i < G_N_ELEMENTS (paths); i++) {
if (g_file_test (paths[i], G_FILE_TEST_EXISTS))
return paths[i];
}
return NULL;
}
static OpenvpnBinaryVersion
openvpn_binary_detect_version (const char *exepath)
{
gs_free char *s_stdout = NULL;
const char *s;
int exit_code;
int n;
g_return_val_if_fail (exepath && exepath[0] == '/', OPENVPN_BINARY_VERSION_UNKNOWN);
if (!g_spawn_sync (NULL,
(char *[]) { (char *) exepath, "--version", NULL },
NULL,
G_SPAWN_STDERR_TO_DEV_NULL,
NULL,
NULL,
&s_stdout,
NULL,
&exit_code,
NULL))
return OPENVPN_BINARY_VERSION_UNKNOWN;
if ( !WIFEXITED (exit_code)
|| WEXITSTATUS (exit_code) != 1) {
/* expect return code 1 (OPENVPN_EXIT_STATUS_USAGE) */
return OPENVPN_BINARY_VERSION_UNKNOWN;
}
/* the output for --version starts with title_string, which starts with PACKAGE_STRING,
* which looks like "OpenVPN 2.#...". Do a strict parsing here... */
if ( !s_stdout
|| !g_str_has_prefix (s_stdout, "OpenVPN 2."))
return OPENVPN_BINARY_VERSION_UNKNOWN;
s = &s_stdout[NM_STRLEN ("OpenVPN 2.")];
if (!g_ascii_isdigit (s[0]))
return OPENVPN_BINARY_VERSION_UNKNOWN;
n = 0;
do {
if (n > G_MAXINT / 100)
return OPENVPN_BINARY_VERSION_UNKNOWN;
n = (n * 10) + (s[0] - '0');
} while (g_ascii_isdigit ((++s)[0]));
if (n <= 3)
return OPENVPN_BINARY_VERSION_2_3_OR_OLDER;
return OPENVPN_BINARY_VERSION_2_4_OR_NEWER;
}
static OpenvpnBinaryVersion
openvpn_binary_detect_version_cached (const char *exepath, OpenvpnBinaryVersion *cached)
{
if (G_UNLIKELY (*cached == OPENVPN_BINARY_VERSION_INVALID))
*cached = openvpn_binary_detect_version (exepath);
return *cached;
}
/*****************************************************************************/
static void
pids_pending_data_free (PidsPendingData *pid_data)
{
nm_clear_g_source (&pid_data->watch_id);
nm_clear_g_source (&pid_data->kill_id);
if (pid_data->plugin)
g_object_remove_weak_pointer ((GObject *) pid_data->plugin, (gpointer *) &pid_data->plugin);
g_slice_free (PidsPendingData, pid_data);
}
static PidsPendingData *
pids_pending_get (GPid pid)
{
GSList *iter;
for (iter = gl.pids_pending_list; iter; iter = iter->next) {
if (((PidsPendingData *) iter->data)->pid == pid)
return iter->data;
}
g_return_val_if_reached (NULL);
}
static void openvpn_child_terminated (NMWireguardPlugin *plugin, GPid pid, gint status);
static void
pids_pending_child_watch_cb (GPid pid, gint status, gpointer user_data)
{
PidsPendingData *pid_data = user_data;
NMWireguardPlugin *plugin;
if (WIFEXITED (status)) {
int exit_status;
exit_status = WEXITSTATUS (status);
if (exit_status != 0)
_LOGW ("openvpn[%ld] exited with error code %d", (long) pid, exit_status);
else
_LOGI ("openvpn[%ld] exited with success", (long) pid);
}
else if (WIFSTOPPED (status))
_LOGW ("openvpn[%ld] stopped unexpectedly with signal %d", (long) pid, WSTOPSIG (status));
else if (WIFSIGNALED (status))
_LOGW ("openvpn[%ld] died with signal %d", (long) pid, WTERMSIG (status));
else
_LOGW ("openvpn[%ld] died from an unnatural cause", (long) pid);
g_return_if_fail (pid_data);
g_return_if_fail (pid_data->pid == pid);
g_return_if_fail (g_slist_find (gl.pids_pending_list, pid_data));
plugin = pid_data->plugin;
pid_data->watch_id = 0;
gl.pids_pending_list = g_slist_remove (gl.pids_pending_list , pid_data);
pids_pending_data_free (pid_data);
if (plugin)
openvpn_child_terminated (plugin, pid, status);
}
static void
pids_pending_add (GPid pid, NMWireguardPlugin *plugin)
{
PidsPendingData *pid_data;
g_return_if_fail (NM_IS_WIREGUARD_PLUGIN (plugin));
g_return_if_fail (pid > 0);
_LOGI ("openvpn[%ld] started", (long) pid);
pid_data = g_slice_new (PidsPendingData);
pid_data->pid = pid;
pid_data->kill_id = 0;
pid_data->watch_id = g_child_watch_add (pid, pids_pending_child_watch_cb, pid_data);
pid_data->plugin = plugin;
g_object_add_weak_pointer ((GObject *) plugin, (gpointer *) &pid_data->plugin);
gl.pids_pending_list = g_slist_prepend (gl.pids_pending_list, pid_data);
}
// TODO implement me right
static void
wg_quick_closed(GPid pid, gint status, gpointer user_data)
{
PidsPendingData *pid_data = user_data;
NMWireguardPlugin *plugin;
if (WIFEXITED (status)) {
int exit_status;
exit_status = WEXITSTATUS (status);
if (exit_status != 0)
_LOGW ("wg-quick[%ld] exited with error code %d", (long) pid, exit_status);
else
_LOGI ("wg-quick[%ld] exited with success", (long) pid);
}
else if (WIFSTOPPED (status))
_LOGW ("wg-quick[%ld] stopped unexpectedly with signal %d", (long) pid, WSTOPSIG (status));
else if (WIFSIGNALED (status))
_LOGW ("wg-quick[%ld] died with signal %d", (long) pid, WTERMSIG (status));
else
_LOGW ("wg-quick[%ld] died from an unnatural cause", (long) pid);
g_return_if_fail (pid_data);
g_return_if_fail (pid_data->pid == pid);
g_return_if_fail (g_slist_find (gl.pids_pending_list, pid_data));
plugin = pid_data->plugin;
pid_data->watch_id = 0;
gl.pids_pending_list = g_slist_remove (gl.pids_pending_list , pid_data);
pids_pending_data_free (pid_data);
}
// TODO implement me right
static void
pids_pending_add_wg (GPid pid, NMWireguardPlugin *plugin)
{
PidsPendingData *pid_data;
g_return_if_fail (NM_IS_WIREGUARD_PLUGIN (plugin));
g_return_if_fail (pid > 0);
_LOGI ("wg-quick[%ld] started", (long) pid);
pid_data = g_slice_new (PidsPendingData);
pid_data->pid = pid;
pid_data->kill_id = 0;
pid_data->watch_id = g_child_watch_add (pid, wg_quick_closed, pid_data);
pid_data->plugin = plugin;
g_object_add_weak_pointer ((GObject *) plugin, (gpointer *) &pid_data->plugin);
gl.pids_pending_list = g_slist_prepend (gl.pids_pending_list, pid_data);
}
static gboolean
pids_pending_ensure_killed (gpointer user_data)
{
PidsPendingData *pid_data = user_data;
g_return_val_if_fail (pid_data && pid_data == pids_pending_get (pid_data->pid), FALSE);
_LOGI ("openvpn[%ld]: send SIGKILL", (long) pid_data->pid);
pid_data->kill_id = 0;
kill (pid_data->pid, SIGKILL);
return FALSE;
}
static void
pids_pending_send_sigterm (GPid pid)
{
PidsPendingData *pid_data;
pid_data = pids_pending_get (pid);
g_return_if_fail (pid_data);
_LOGI ("openvpn[%ld]: send SIGTERM", (long) pid);
kill (pid, SIGTERM);
pid_data->kill_id = g_timeout_add (2000, pids_pending_ensure_killed, pid_data);
}
static void
pids_pending_wait_for_processes (GMainLoop *main_loop)
{
if (gl.pids_pending_list) {
_LOGI ("wait for %u openvpn processes to terminate...", g_slist_length (gl.pids_pending_list));
do {
g_main_context_iteration (g_main_loop_get_context (main_loop), TRUE);
} while (gl.pids_pending_list);
}
}
/*****************************************************************************/
static gboolean
validate_address (const char *address)
{
const char *p = address;
if (!address || !strlen (address))
return FALSE;
/* Ensure it's a valid DNS name or IP address */
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 (!prop->address || validate_address (value))
return; /* valid */
g_set_error (info->error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("invalid address “%s”"),
key);
break;
case G_TYPE_INT:
errno = 0;
tmp = strtol (value, NULL, 10);
if (errno == 0 && tmp >= prop->int_min && tmp <= prop->int_max)
return; /* valid */
g_set_error (info->error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("invalid integer property “%s” or out of range [%d -> %d]"),
key, prop->int_min, prop->int_max);
break;
case G_TYPE_BOOLEAN:
if (!strcmp (value, "yes") || !strcmp (value, "no"))
return; /* valid */
g_set_error (info->error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
/* Translators: keep "yes" and "no" untranslated! */
_("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_openvpn_properties_validate (NMSettingVpn *s_vpn, GError **error)
{
GError *validate_error = NULL;
ValidateInfo info = { &valid_properties[0], &validate_error, FALSE };
nm_setting_vpn_foreach_data_item (s_vpn, validate_one_property, &info);
if (!info.have_items) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("No VPN configuration options."));
return FALSE;
}
if (validate_error) {
*error = validate_error;
return FALSE;
}
return TRUE;
}
static gboolean
nm_openvpn_secrets_validate (NMSettingVpn *s_vpn, GError **error)
{
GError *validate_error = NULL;
ValidateInfo info = { &valid_secrets[0], &validate_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_openvpn_disconnect_management_socket (NMWireguardPlugin *plugin)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
NMWireguardPluginIOData *io_data = priv->io_data;
/* This should not throw a warning since this can happen in
non-password modes */
if (!io_data)
return;
if (io_data->socket_channel_eventid)
g_source_remove (io_data->socket_channel_eventid);
if (io_data->socket_channel) {
g_io_channel_shutdown (io_data->socket_channel, FALSE, NULL);
g_io_channel_unref (io_data->socket_channel);
}
g_free (io_data->username);
g_free (io_data->proxy_username);
g_free (io_data->pending_auth);
if (io_data->password)
memset (io_data->password, 0, strlen (io_data->password));
g_free (io_data->password);
if (io_data->priv_key_pass)
memset (io_data->priv_key_pass, 0, strlen (io_data->priv_key_pass));
g_free (io_data->priv_key_pass);
if (io_data->proxy_password)
memset (io_data->proxy_password, 0, strlen (io_data->proxy_password));
g_free (io_data->proxy_password);
g_free (io_data->challenge_state_id);
g_free (io_data->challenge_text);
g_free (priv->io_data);
priv->io_data = NULL;
}
static char *
ovpn_quote_string (const char *unquoted)
{
char *quoted = NULL, *q;
char *u = (char *) unquoted;
g_return_val_if_fail (unquoted != NULL, NULL);
/* FIXME: use unpaged memory */
quoted = q = g_malloc0 (strlen (unquoted) * 2);
while (*u) {
/* Escape certain characters */
if (*u == ' ' || *u == '\\' || *u == '"')
*q++ = '\\';
*q++ = *u++;
}
return quoted;
}
static char *
get_detail (const char *input, const char *prefix)
{
const char *end;
nm_assert (prefix);
if (!g_str_has_prefix (input, prefix))
return NULL;
/* Grab characters until the next ' */
input += strlen (prefix);
end = strchr (input, '\'');
if (end)
return g_strndup (input, end - input);
return NULL;
}
/* Parse challenge response protocol message of the form
* CRV1:flags:state_id:username:text
*/
static gboolean
parse_challenge (const char *failure_reason, char **challenge_state_id, char **challenge_text)
{
const char *colon[4];
if ( !failure_reason
|| !g_str_has_prefix (failure_reason, "CRV1:"))
return FALSE;
colon[0] = strchr (failure_reason, ':');
if (!colon[0])
return FALSE;
colon[1] = strchr (colon[0] + 1, ':');
if (!colon[1])
return FALSE;
colon[2] = strchr (colon[1] + 1, ':');
if (!colon[2])
return FALSE;
colon[3] = strchr (colon[2] + 1, ':');
if (!colon[3])
return FALSE;
*challenge_state_id = g_strndup (colon[1] + 1, colon[2] - colon[1] - 1);
*challenge_text = g_strdup (colon[3] + 1);
return TRUE;
}
static void
write_user_pass (GIOChannel *channel,
const char *authtype,
const char *user,
const char *pass)
{
char *quser, *qpass, *buf;
/* Quote strings passed back to openvpn */
quser = ovpn_quote_string (user);
qpass = ovpn_quote_string (pass);
buf = g_strdup_printf ("username \"%s\" \"%s\"\n"
"password \"%s\" \"%s\"\n",
authtype, quser,
authtype, qpass);
memset (qpass, 0, strlen (qpass));
g_free (qpass);
g_free (quser);
/* Will always write everything in blocking channels (on success) */
g_io_channel_write_chars (channel, buf, strlen (buf), NULL, NULL);
g_io_channel_flush (channel, NULL);
memset (buf, 0, strlen (buf));
g_free (buf);
}
static gboolean
handle_auth (NMWireguardPluginIOData *io_data,
const char *requested_auth,
const char **out_message,
char ***out_hints)
{
gboolean handled = FALSE;
guint i = 0;
char **hints = NULL;
g_return_val_if_fail (requested_auth != NULL, FALSE);
g_return_val_if_fail (out_message != NULL, FALSE);
g_return_val_if_fail (out_hints != NULL, FALSE);
if (strcmp (requested_auth, "Auth") == 0) {
const char *username = io_data->username;
/* Fall back to the default username if it wasn't overridden by the user */
if (!username)
username = io_data->default_username;
if (username != NULL && io_data->password != NULL && io_data->challenge_state_id) {
gs_free char *response = NULL;
response = g_strdup_printf ("CRV1::%s::%s",
io_data->challenge_state_id,
io_data->password);
write_user_pass (io_data->socket_channel,
requested_auth,
username,
response);
nm_clear_g_free (&io_data->challenge_state_id);
nm_clear_g_free (&io_data->challenge_text);
} else if (username != NULL && io_data->password != NULL) {
write_user_pass (io_data->socket_channel,
requested_auth,
username,
io_data->password);
} else {
hints = g_new0 (char *, 3);
if (!username) {
hints[i++] = NM_OPENVPN_KEY_USERNAME;
*out_message = _("A username is required.");
}
if (!io_data->password) {
hints[i++] = NM_OPENVPN_KEY_PASSWORD;
*out_message = _("A password is required.");
}
if (!username && !io_data->password)
*out_message = _("A username and password are required.");
if (io_data->challenge_text)
*out_message = io_data->challenge_text;
}
handled = TRUE;
} else if (!strcmp (requested_auth, "Private Key")) {
if (io_data->priv_key_pass) {
char *qpass, *buf;
/* Quote strings passed back to openvpn */
qpass = ovpn_quote_string (io_data->priv_key_pass);
buf = g_strdup_printf ("password \"%s\" \"%s\"\n", requested_auth, qpass);
memset (qpass, 0, strlen (qpass));
g_free (qpass);
/* Will always write everything in blocking channels (on success) */
g_io_channel_write_chars (io_data->socket_channel, buf, strlen (buf), NULL, NULL);
g_io_channel_flush (io_data->socket_channel, NULL);
g_free (buf);
} else {
hints = g_new0 (char *, 2);
hints[i++] = NM_OPENVPN_KEY_CERTPASS;
*out_message = _("A private key password is required.");
}
handled = TRUE;
} else if (strcmp (requested_auth, "HTTP Proxy") == 0) {
if (io_data->proxy_username != NULL && io_data->proxy_password != NULL) {
write_user_pass (io_data->socket_channel,
requested_auth,
io_data->proxy_username,
io_data->proxy_password);
} else {
hints = g_new0 (char *, 3);
if (!io_data->proxy_username) {
hints[i++] = NM_OPENVPN_KEY_HTTP_PROXY_USERNAME;
*out_message = _("An HTTP Proxy username is required.");
}
if (!io_data->proxy_password) {
hints[i++] = NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD;
*out_message = _("An HTTP Proxy password is required.");
}
if (!io_data->proxy_username && !io_data->proxy_password)
*out_message = _("An HTTP Proxy username and password are required.");
}
handled = TRUE;
}
*out_hints = hints;
return handled;
}
static gboolean
handle_management_socket (NMWireguardPlugin *plugin,
GIOChannel *source,
GIOCondition condition,
NMVpnPluginFailure *out_failure)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
gboolean again = TRUE;
char *str = NULL, *auth = NULL;
const char *message = NULL;
char **hints = NULL;
g_assert (out_failure);
if (!(condition & G_IO_IN))
return TRUE;
if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL)
return TRUE;
if (!str[0]) {
g_free (str);
return TRUE;
}
_LOGD ("VPN request '%s'", str);
auth = get_detail (str, ">PASSWORD:Need '");
if (auth) {
if (priv->io_data->pending_auth)
g_free (priv->io_data->pending_auth);
priv->io_data->pending_auth = auth;
if (handle_auth (priv->io_data, auth, &message, &hints)) {
/* Request new secrets if we need any */
if (message) {
if (priv->interactive) {
gs_free char *joined = NULL;
_LOGD ("Requesting new secrets: '%s', %s%s%s", message,
NM_PRINT_FMT_QUOTED (hints, "(", (joined = g_strjoinv (",", (char **) hints)), ")", "no hints"));
nm_vpn_service_plugin_secrets_required ((NMVpnServicePlugin *) plugin, message, (const char **) hints);
} else {
/* Interactive not allowed, can't ask for more secrets */
_LOGW ("More secrets required but cannot ask interactively");
*out_failure = NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED;
again = FALSE;
}
}
if (hints)
g_free (hints); /* elements are 'const' */
} else {
_LOGW ("Unhandled management socket request '%s'", auth);
*out_failure = NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED;
again = FALSE;
}
goto out;
}
auth = get_detail (str, ">PASSWORD:Verification Failed: '");
if (auth) {
gboolean fail = TRUE;
if (!strcmp (auth, "Auth")) {
gs_free char *failure_reason = NULL;
failure_reason = get_detail (auth, ">PASSWORD:Verification Failed: 'Auth' ['");
if (parse_challenge (failure_reason, &priv->io_data->challenge_state_id, &priv->io_data->challenge_text)) {
_LOGD ("Received challenge '%s' for state '%s'",
priv->io_data->challenge_state_id,
priv->io_data->challenge_text);
} else
_LOGW ("Password verification failed");
if (priv->interactive) {
/* Clear existing password in interactive mode, openvpn
* will request a new one after restarting.
*/
if (priv->io_data->password)
memset (priv->io_data->password, 0, strlen (priv->io_data->password));
g_clear_pointer (&priv->io_data->password, g_free);
fail = FALSE;
}
} else if (!strcmp (auth, "Private Key"))
_LOGW ("Private key verification failed");
else
_LOGW ("Unknown verification failed: %s", auth);
if (fail) {
*out_failure = NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED;
again = FALSE;
}
g_free (auth);
}
out:
g_free (str);
return again;
}
static gboolean
nm_openvpn_socket_data_cb (GIOChannel *source, GIOCondition condition, gpointer user_data)
{
NMWireguardPlugin *plugin = NM_WIREGUARD_PLUGIN (user_data);
NMVpnPluginFailure failure = NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED;
if (!handle_management_socket (plugin, source, condition, &failure)) {
nm_vpn_service_plugin_failure ((NMVpnServicePlugin *) plugin, failure);
return FALSE;
}
return TRUE;
}
static gboolean
nm_openvpn_connect_timer_cb (gpointer data)
{
NMWireguardPlugin *plugin = NM_WIREGUARD_PLUGIN (data);
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
NMWireguardPluginIOData *io_data = priv->io_data;
struct sockaddr_un remote = { 0 };
int fd;
priv->connect_count++;
/* open socket and start listener */
fd = socket (AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
_LOGW ("Could not create management socket");
nm_vpn_service_plugin_failure (NM_VPN_SERVICE_PLUGIN (plugin), NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
goto out;
}
remote.sun_family = AF_UNIX;
g_strlcpy (remote.sun_path, priv->mgt_path, sizeof (remote.sun_path));
if (connect (fd, (struct sockaddr *) &remote, sizeof (remote)) != 0) {
close (fd);
if (priv->connect_count <= 30)
return G_SOURCE_CONTINUE;
priv->connect_timer = 0;
_LOGW ("Could not open management socket");
nm_vpn_service_plugin_failure (NM_VPN_SERVICE_PLUGIN (plugin), NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED);
} else {
io_data->socket_channel = g_io_channel_unix_new (fd);
g_io_channel_set_encoding (io_data->socket_channel, NULL, NULL);
io_data->socket_channel_eventid = g_io_add_watch (io_data->socket_channel,
G_IO_IN,
nm_openvpn_socket_data_cb,
plugin);
}
out:
priv->connect_timer = 0;
return G_SOURCE_REMOVE;
}
static void
nm_openvpn_schedule_connect_timer (NMWireguardPlugin *plugin)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
if (priv->connect_timer == 0)
priv->connect_timer = g_timeout_add (200, nm_openvpn_connect_timer_cb, plugin);
}
static void
openvpn_child_terminated (NMWireguardPlugin *plugin, GPid pid, gint status)
{
NMWireguardPluginPrivate *priv;
NMVpnPluginFailure failure = NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED;
gboolean good_exit = FALSE;
g_return_if_fail (NM_IS_WIREGUARD_PLUGIN (plugin));
priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
/* Reap child if needed. */
if (priv->pid != pid) {
/* the dead child is not the currently active process. Nothing to do, we just
* reaped the PID. */
return;
}
priv->pid = 0;
/* OpenVPN doesn't supply useful exit codes :( */
if (WIFEXITED (status) && WEXITSTATUS (status) == 0)
good_exit = TRUE;
/* Try to get the last bits of data from openvpn */
if (priv->io_data && priv->io_data->socket_channel) {
GIOChannel *channel = priv->io_data->socket_channel;
GIOCondition condition;
while ((condition = g_io_channel_get_buffer_condition (channel)) & G_IO_IN) {
if (!handle_management_socket (plugin, channel, condition, &failure)) {
good_exit = FALSE;
break;
}
}
}
if (good_exit)
nm_vpn_service_plugin_disconnect ((NMVpnServicePlugin *) plugin, NULL);
else
nm_vpn_service_plugin_failure ((NMVpnServicePlugin *) plugin, failure);
}
static gboolean
validate_auth (const char *auth)
{
if (auth) {
if ( !strcmp (auth, NM_OPENVPN_AUTH_NONE)
|| !strcmp (auth, NM_OPENVPN_AUTH_RSA_MD4)
|| !strcmp (auth, NM_OPENVPN_AUTH_MD5)
|| !strcmp (auth, NM_OPENVPN_AUTH_SHA1)
|| !strcmp (auth, NM_OPENVPN_AUTH_SHA224)
|| !strcmp (auth, NM_OPENVPN_AUTH_SHA256)
|| !strcmp (auth, NM_OPENVPN_AUTH_SHA384)
|| !strcmp (auth, NM_OPENVPN_AUTH_SHA512)
|| !strcmp (auth, NM_OPENVPN_AUTH_RIPEMD160))
return TRUE;
}
return FALSE;
}
static const char *
validate_connection_type (const char *ctype)
{
if (ctype) {
if ( !strcmp (ctype, NM_OPENVPN_CONTYPE_TLS)
|| !strcmp (ctype, NM_OPENVPN_CONTYPE_STATIC_KEY)
|| !strcmp (ctype, NM_OPENVPN_CONTYPE_PASSWORD)
|| !strcmp (ctype, NM_OPENVPN_CONTYPE_PASSWORD_TLS))
return ctype;
}
return NULL;
}
static gboolean
connection_type_is_tls_mode (const char *connection_type)
{
return strcmp (connection_type, NM_OPENVPN_CONTYPE_TLS) == 0
|| strcmp (connection_type, NM_OPENVPN_CONTYPE_PASSWORD) == 0
|| strcmp (connection_type, NM_OPENVPN_CONTYPE_PASSWORD_TLS) == 0;
}
static void
add_openvpn_arg (GPtrArray *args, const char *arg)
{
g_return_if_fail (args != NULL);
g_return_if_fail (arg != NULL);
g_ptr_array_add (args, g_strdup (arg));
}
static const char *
add_openvpn_arg_utf8safe (GPtrArray *args, const char *arg)
{
char *arg_unescaped;
g_return_val_if_fail (args, NULL);
g_return_val_if_fail (arg, NULL);
arg_unescaped = nm_utils_str_utf8safe_unescape_cp (arg);
g_ptr_array_add (args, arg_unescaped);
return arg_unescaped;
}
static gboolean
add_openvpn_arg_int (GPtrArray *args, const char *arg)
{
long int tmp_int;
g_return_val_if_fail (args != NULL, FALSE);
g_return_val_if_fail (arg != NULL, FALSE);
/* Convert -> int and back to string for security's sake since
* strtol() ignores some leading and trailing characters.
*/
errno = 0;
tmp_int = strtol (arg, NULL, 10);
if (errno != 0)
return FALSE;
g_ptr_array_add (args, (gpointer) g_strdup_printf ("%d", (guint32) tmp_int));
return TRUE;
}
static void
add_cert_args (GPtrArray *args, NMSettingVpn *s_vpn)
{
const char *ca, *cert, *key;
gs_free char *ca_free = NULL, *cert_free = NULL, *key_free = NULL;
g_return_if_fail (args != NULL);
g_return_if_fail (s_vpn != NULL);
ca = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA);
cert = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT);
key = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
ca = nm_utils_str_utf8safe_unescape (ca, &ca_free);
cert = nm_utils_str_utf8safe_unescape (cert, &cert_free);
key = nm_utils_str_utf8safe_unescape (key, &key_free);
if ( ca && strlen (ca)
&& cert && strlen (cert)
&& key && strlen (key)
&& !strcmp (ca, cert)
&& !strcmp (ca, key)) {
add_openvpn_arg (args, "--pkcs12");
add_openvpn_arg (args, ca);
} else {
if (ca && strlen (ca)) {
add_openvpn_arg (args, "--ca");
add_openvpn_arg (args, ca);
}
if (cert && strlen (cert)) {
add_openvpn_arg (args, "--cert");
add_openvpn_arg (args, cert);
}
if (key && strlen (key)) {
add_openvpn_arg (args, "--key");
add_openvpn_arg (args, key);
}
}
}
static void
update_io_data_from_vpn_setting (NMWireguardPluginIOData *io_data,
NMSettingVpn *s_vpn,
const char *default_username)
{
const char *tmp;
if (default_username) {
g_free (io_data->default_username);
io_data->default_username = g_strdup (default_username);
}
g_free (io_data->username);
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_USERNAME);
io_data->username = tmp ? g_strdup (tmp) : NULL;
if (io_data->password) {
memset (io_data->password, 0, strlen (io_data->password));
g_free (io_data->password);
}
tmp = nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_PASSWORD);
io_data->password = tmp ? g_strdup (tmp) : NULL;
if (io_data->priv_key_pass) {
memset (io_data->priv_key_pass, 0, strlen (io_data->priv_key_pass));
g_free (io_data->priv_key_pass);
}
tmp = nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_CERTPASS);
io_data->priv_key_pass = tmp ? g_strdup (tmp) : NULL;
g_free (io_data->proxy_username);
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_USERNAME);
io_data->proxy_username = tmp ? g_strdup (tmp) : NULL;
if (io_data->proxy_password) {
memset (io_data->proxy_password, 0, strlen (io_data->proxy_password));
g_free (io_data->proxy_password);
}
tmp = nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD);
io_data->proxy_password = tmp ? g_strdup (tmp) : NULL;
}
static char *
mgt_path_create (NMConnection *connection, GError **error)
{
int errsv;
/* Setup runtime directory */
if (g_mkdir_with_parents (RUNDIR, 0755) != 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 NULL;
}
return g_strdup_printf (RUNDIR"/nm-openvpn-%s",
nm_connection_get_uuid (connection));
}
#define MAX_GROUPS 128
static gboolean
is_dir_writable (const char *dir, const char *user)
{
struct stat sb;
struct passwd *pw;
if (stat (dir, &sb) == -1)
return FALSE;
pw = getpwnam (user);
if (!pw)
return FALSE;
if (pw->pw_uid == 0)
return TRUE;
if (sb.st_mode & S_IWOTH)
return TRUE;
else if (sb.st_mode & S_IWGRP) {
/* Group has write access. Is user in that group? */
int i, ngroups = MAX_GROUPS;
gid_t groups[MAX_GROUPS];
getgrouplist (user, pw->pw_gid, groups, &ngroups);
for (i = 0; i < ngroups && i < MAX_GROUPS; i++) {
if (groups[i] == sb.st_gid)
return TRUE;
}
} else if (sb.st_mode & S_IWUSR) {
/* The owner has write access. Does the user own the file? */
if (pw->pw_uid == sb.st_uid)
return TRUE;
}
return FALSE;
}
/* Check existence of 'tmp' directory inside @chdir
* and write access in @chdir and @chdir/tmp for @user.
*/
static gboolean
check_chroot_dir_usability (const char *chdir, const char *user)
{
char *tmp_dir;
gboolean b1, b2;
tmp_dir = g_strdup_printf ("%s/tmp", chdir);
if (!g_file_test (tmp_dir, G_FILE_TEST_IS_DIR)) {
g_free (tmp_dir);
return FALSE;
}
b1 = is_dir_writable (chdir, user);
b2 = is_dir_writable (tmp_dir, user);
g_free (tmp_dir);
return b1 && b2;
}
static gboolean
nm_wireguard_start_interface(NMWireguardPlugin *plugin,
NMConnection *connection,
GError **error)
{
// NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE(plugin);
// const char *wg_connection_name = NULL;
return TRUE;
}
static gboolean
nm_openvpn_start_openvpn_binary (NMWireguardPlugin *plugin,
NMConnection *connection,
GError **error)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
const char *openvpn_binary, *auth, *tmp, *tmp2, *tmp3, *tmp4;
gs_unref_ptrarray GPtrArray *args = NULL;
GPid pid;
gboolean dev_type_is_tap;
char *stmp;
const char *defport, *proto_tcp;
const char *tls_remote = NULL;
const char *nm_openvpn_user, *nm_openvpn_group, *nm_openvpn_chroot;
gs_free char *bus_name = NULL;
NMSettingVpn *s_vpn;
const char *connection_type;
gint64 v_int64;
char sbuf_64[65];
OpenvpnBinaryVersion openvpn_binary_version = OPENVPN_BINARY_VERSION_INVALID;
s_vpn = nm_connection_get_setting_vpn (connection);
if (!s_vpn) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
_("Could not process the request because the VPN connection settings were invalid."));
return FALSE;
}
connection_type = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CONNECTION_TYPE);
if (!validate_connection_type (connection_type)) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid connection type."));
return FALSE;
}
/* Validate the properties */
if (!nm_openvpn_properties_validate (s_vpn, error))
return FALSE;
/* Validate secrets */
if (!nm_openvpn_secrets_validate (s_vpn, error))
return FALSE;
/* Find openvpn */
openvpn_binary = openvpn_binary_find_exepath ();
if (!openvpn_binary) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Could not find the openvpn binary."));
return FALSE;
}
auth = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_AUTH);
if (auth) {
if (!validate_auth(auth)) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid HMAC auth."));
return FALSE;
}
}
args = g_ptr_array_new_with_free_func (g_free);
add_openvpn_arg (args, openvpn_binary);
defport = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PORT);
if (defport && !defport[0])
defport = NULL;
proto_tcp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROTO_TCP);
if (proto_tcp && !proto_tcp[0])
proto_tcp = NULL;
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE);
if (tmp && *tmp) {
gs_free char *tmp_clone = NULL;
char *tmp_remaining;
const char *tok;
tmp_remaining = tmp_clone = g_strdup (tmp);
while ((tok = strsep (&tmp_remaining, " \t,")) != NULL) {
gs_free char *str_free = NULL;
const char *host, *port, *proto;
gssize eidx;
eidx = nmovpn_remote_parse (tok,
&str_free,
&host,
&port,
&proto,
NULL);
if (eidx >= 0)
continue;
add_openvpn_arg (args, "--remote");
add_openvpn_arg (args, host);
if (port) {
if (!add_openvpn_arg_int (args, port)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid port number “%s”."), port);
return FALSE;
}
} else if (defport) {
if (!add_openvpn_arg_int (args, defport)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid port number “%s”."),
defport);
return FALSE;
}
} else
add_openvpn_arg (args, "1194"); /* default IANA port */
if (proto) {
if (nm_streq (proto, "tcp"))
add_openvpn_arg (args, "tcp-client");
else if (nm_streq (proto, "tcp4"))
add_openvpn_arg (args, "tcp4-client");
else if (nm_streq (proto, "tcp6"))
add_openvpn_arg (args, "tcp6-client");
else if (NM_IN_STRSET (proto, NMOVPN_PROTCOL_TYPES))
add_openvpn_arg (args, proto);
else {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid proto “%s”."), proto);
return FALSE;
}
} else if (proto_tcp && !strcmp (proto_tcp, "yes"))
add_openvpn_arg (args, "tcp-client");
else
add_openvpn_arg (args, "udp");
}
}
/* Remote random */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_RANDOM);
if (tmp && !strcmp (tmp, "yes"))
add_openvpn_arg (args, "--remote-random");
/* tun-ipv6 */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TUN_IPV6);
if (tmp && !strcmp (tmp, "yes"))
add_openvpn_arg (args, "--tun-ipv6");
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_TYPE);
tmp2 = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_SERVER);
tmp3 = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_PORT);
tmp4 = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_RETRY);
if (tmp && strlen (tmp) && tmp2 && strlen (tmp2)) {
if (!strcmp (tmp, "http")) {
add_openvpn_arg (args, "--http-proxy");
add_openvpn_arg (args, tmp2);
add_openvpn_arg (args, tmp3 ? tmp3 : "8080");
add_openvpn_arg (args, "auto"); /* Automatic proxy auth method detection */
if (tmp4)
add_openvpn_arg (args, "--http-proxy-retry");
} else if (!strcmp (tmp, "socks")) {
add_openvpn_arg (args, "--socks-proxy");
add_openvpn_arg (args, tmp2);
add_openvpn_arg (args, tmp3 ? tmp3 : "1080");
if (tmp4)
add_openvpn_arg (args, "--socks-proxy-retry");
} else {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid proxy type “%s”."),
tmp);
return FALSE;
}
}
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_COMP_LZO);
/* openvpn understands 4 different modes for --comp-lzo, which have
* different meaning:
* 1) no --comp-lzo option
* 2) --comp-lzo yes
* 3) --comp-lzo [adaptive]
* 4) --comp-lzo no
*
* In the past, nm-openvpn only supported 1) and 2) by having no
* comp-lzo connection setting or "comp-lzo=yes", respectively.
*
* However, old plasma-nm would set "comp-lzo=no" in the connection
* to mean 1). Thus, "comp-lzo=no" is spoiled to mean 4) in order
* to preserve backward compatibily.
* We use instead a special value "no-by-default" to express "no".
*
* See bgo#769177
*/
if (NM_IN_STRSET (tmp, "no")) {
/* means no --comp-lzo option. */
tmp = NULL;
} else if (NM_IN_STRSET (tmp, "no-by-default"))
tmp = "no";
if (NM_IN_STRSET (tmp, "yes", "no", "adaptive")) {
add_openvpn_arg (args, "--comp-lzo");
add_openvpn_arg (args, tmp);
}
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_FLOAT);
if (tmp && !strcmp (tmp, "yes"))
add_openvpn_arg (args, "--float");
/* ping, ping-exit, ping-restart */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PING);
if (tmp) {
add_openvpn_arg (args, "--ping");
if (!add_openvpn_arg_int (args, tmp)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid ping duration “%s”."),
tmp);
return FALSE;
}
}
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PING_EXIT);
if (tmp) {
add_openvpn_arg (args, "--ping-exit");
if (!add_openvpn_arg_int (args, tmp)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid ping-exit duration “%s”."),
tmp);
return FALSE;
}
}
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PING_RESTART);
if (tmp) {
add_openvpn_arg (args, "--ping-restart");
if (!add_openvpn_arg_int (args, tmp)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid ping-restart duration “%s”."),
tmp);
return FALSE;
}
}
add_openvpn_arg (args, "--nobind");
/* max routes allowed from openvpn server */
tmp = nm_setting_vpn_get_data_item(s_vpn, NM_OPENVPN_KEY_MAX_ROUTES);
if (tmp) {
/* max-routes option is deprecated in 2.4 release
* https://github.com/OpenVPN/openvpn/commit/d0085293e709c8a722356cfa68ad74c962aef9a2
*/
add_openvpn_arg (args, "--max-routes");
if (!add_openvpn_arg_int (args, tmp)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid max-routes argument “%s”."),
tmp);
return FALSE;
}
}
/* Device and device type, defaults to tun */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_DEV);
tmp2 = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_DEV_TYPE);
tmp3 = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TAP_DEV);
add_openvpn_arg (args, "--dev");
if (tmp) {
const char *tmp_unescaped;
tmp_unescaped = add_openvpn_arg_utf8safe (args, tmp);
dev_type_is_tap = g_str_has_prefix (tmp_unescaped, "tap");
} else if (tmp2) {
add_openvpn_arg (args, tmp2);
dev_type_is_tap = FALSE; /* will be reset below (avoid maybe-uninitialized warning) */
} else if (tmp3 && !strcmp (tmp3, "yes")) {
add_openvpn_arg (args, "tap");
dev_type_is_tap = TRUE;
} else {
add_openvpn_arg (args, "tun");
dev_type_is_tap = FALSE;
}
/* Add '--dev-type' if the type was explicitly set */
if (tmp2) {
add_openvpn_arg (args, "--dev-type");
add_openvpn_arg (args, tmp2);
dev_type_is_tap = (strcmp (tmp2, "tap") == 0);
}
/* Cipher */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CIPHER);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--cipher");
add_openvpn_arg (args, tmp);
}
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TLS_CIPHER);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--tls-cipher");
add_openvpn_arg (args, tmp);
}
/* Keysize */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEYSIZE);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--keysize");
if (!add_openvpn_arg_int (args, tmp)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid keysize “%s”."),
tmp);
return FALSE;
}
}
/* Auth */
if (auth) {
add_openvpn_arg (args, "--auth");
add_openvpn_arg (args, auth);
}
add_openvpn_arg (args, "--auth-nocache");
/* tls-auth */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TA);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--tls-auth");
add_openvpn_arg_utf8safe (args, tmp);
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TA_DIR);
if (tmp && tmp[0])
add_openvpn_arg (args, tmp);
}
/* tls-crypt */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TLS_CRYPT);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--tls-crypt");
add_openvpn_arg_utf8safe (args, tmp);
}
/* tls-remote */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TLS_REMOTE);
if (tmp && tmp[0]) {
if (openvpn_binary_detect_version_cached (openvpn_binary, &openvpn_binary_version) != OPENVPN_BINARY_VERSION_2_4_OR_NEWER) {
_LOGW ("the tls-remote option is deprecated and removed from OpenVPN 2.4. Update your connection to use verify-x509-name");
add_openvpn_arg (args, "--tls-remote");
add_openvpn_arg (args, tmp);
} else {
_LOGW ("the tls-remote option is deprecated and removed from OpenVPN 2.4. For compatibility, the plugin uses \"verify-x509-name\" \"%s\" \"name\" instead. Update your connection to use verify-x509-name", tmp);
add_openvpn_arg (args, "--verify-x509-name");
add_openvpn_arg (args, tmp);
add_openvpn_arg (args, "name");
}
tls_remote = tmp;
}
/* verify-x509-name */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_VERIFY_X509_NAME);
if (tmp && tmp[0]) {
const char *name;
gs_free char *type = NULL;
if (tls_remote) {
g_set_error (error, NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid configuration with tls-remote and verify-x509-name."));
return FALSE;
}
name = strchr (tmp, ':');
if (name) {
type = g_strndup (tmp, name - tmp);
name++;
} else
name = tmp;
if (!name[0] || !g_utf8_validate(name, -1, NULL)) {
g_set_error (error, NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid verify-x509-name."));
return FALSE;
}
add_openvpn_arg (args, "--verify-x509-name");
add_openvpn_arg (args, name);
add_openvpn_arg (args, type ?: "subject");
}
/* remote-cert-tls */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_CERT_TLS);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--remote-cert-tls");
add_openvpn_arg (args, tmp);
}
/* ns-cert-type */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_NS_CERT_TYPE);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--ns-cert-type");
add_openvpn_arg (args, tmp);
}
/* Reneg seconds */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_RENEG_SECONDS);
if (!connection_type_is_tls_mode (connection_type)) {
/* Ignore --reneg-sec option if we are not in TLS mode (as enabled
* by --client below). openvpn will error out otherwise, see bgo#749050. */
} else if (tmp && tmp[0]) {
add_openvpn_arg (args, "--reneg-sec");
if (!add_openvpn_arg_int (args, tmp)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid reneg seconds “%s”."),
tmp);
return FALSE;
}
} else {
/* Either the server and client must agree on the renegotiation
* interval, or it should be disabled on one side to prevent
* too-frequent renegotiations, which make two-factor auth quite
* painful.
*/
add_openvpn_arg (args, "--reneg-sec");
add_openvpn_arg (args, "0");
}
if (gl.log_level_ovpn >= 0) {
add_openvpn_arg (args, "--verb");
add_openvpn_arg (args, nm_sprintf_buf (sbuf_64, "%d", gl.log_level_ovpn));
}
if (gl.log_syslog) {
add_openvpn_arg (args, "--syslog");
add_openvpn_arg (args, "nm-openvpn");
}
/* TUN MTU size */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TUNNEL_MTU);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--tun-mtu");
if (!add_openvpn_arg_int (args, tmp)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid TUN MTU size “%s”."),
tmp);
return FALSE;
}
}
/* fragment size */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_FRAGMENT_SIZE);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--fragment");
if (!add_openvpn_arg_int (args, tmp)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid fragment size “%s”."),
tmp);
return FALSE;
}
}
/* mssfix */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_MSSFIX);
if (tmp) {
if (nm_streq (tmp, "yes"))
add_openvpn_arg (args, "--mssfix");
else if ((v_int64 = _nm_utils_ascii_str_to_int64 (tmp, 10, 1, G_MAXINT32, 0))) {
add_openvpn_arg (args, "--mssfix");
add_openvpn_arg (args, nm_sprintf_buf (sbuf_64, "%d", (int) v_int64));
}
}
/* mtu-disc */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_MTU_DISC);
if (NM_IN_STRSET (tmp, "no", "maybe", "yes")) {
add_openvpn_arg (args, "--mtu-disc");
add_openvpn_arg (args, tmp);
}
/* ifconfig */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_LOCAL_IP);
tmp2 = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_IP);
if (tmp && tmp2) {
add_openvpn_arg (args, "--ifconfig");
add_openvpn_arg (args, tmp);
add_openvpn_arg (args, tmp2);
}
/* Punch script security in the face; this option was added to OpenVPN 2.1-rc9
* and defaults to disallowing any scripts, a behavior change from previous
* versions.
*/
add_openvpn_arg (args, "--script-security");
add_openvpn_arg (args, "2");
/* Up script, called when connection has been established or has been restarted */
add_openvpn_arg (args, "--up");
g_object_get (plugin, NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME, &bus_name, NULL);
stmp = g_strdup_printf ("%s --debug %d %ld --bus-name %s %s --",
NM_OPENVPN_HELPER_PATH,
gl.log_level, (long) getpid(),
bus_name,
dev_type_is_tap ? "--tap" : "--tun");
add_openvpn_arg (args, stmp);
g_free (stmp);
add_openvpn_arg (args, "--up-restart");
/* Keep key and tun if restart is needed */
add_openvpn_arg (args, "--persist-key");
add_openvpn_arg (args, "--persist-tun");
/* Management socket for localhost access to supply username and password */
g_clear_pointer (&priv->mgt_path, g_free);
priv->mgt_path = mgt_path_create (connection, error);
if (!priv->mgt_path)
return FALSE;
add_openvpn_arg (args, "--management");
add_openvpn_arg (args, priv->mgt_path);
add_openvpn_arg (args, "unix");
add_openvpn_arg (args, "--management-client-user");
add_openvpn_arg (args, "root");
add_openvpn_arg (args, "--management-client-group");
add_openvpn_arg (args, "root");
/* Query on the management socket for user/pass */
add_openvpn_arg (args, "--management-query-passwords");
add_openvpn_arg (args, "--auth-retry");
add_openvpn_arg (args, "interact");
/* do not let openvpn setup routes or addresses, NM will handle it */
add_openvpn_arg (args, "--route-noexec");
add_openvpn_arg (args, "--ifconfig-noexec");
/* Now append configuration options which are dependent on the configuration type */
if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_TLS)) {
add_openvpn_arg (args, "--client");
add_cert_args (args, s_vpn);
} else if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_STATIC_KEY)) {
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_STATIC_KEY);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--secret");
add_openvpn_arg_utf8safe (args, tmp);
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_STATIC_KEY_DIRECTION);
if (tmp && tmp[0])
add_openvpn_arg (args, tmp);
}
} else if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_PASSWORD)) {
/* Client mode */
add_openvpn_arg (args, "--client");
/* Use user/path authentication */
add_openvpn_arg (args, "--auth-user-pass");
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA);
if (tmp && tmp[0]) {
add_openvpn_arg (args, "--ca");
add_openvpn_arg_utf8safe (args, tmp);
}
} else if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
add_openvpn_arg (args, "--client");
add_cert_args (args, s_vpn);
/* Use user/path authentication */
add_openvpn_arg (args, "--auth-user-pass");
} else {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Unknown connection type “%s”."),
connection_type);
return FALSE;
}
/* Allow openvpn to be run as a specified user:group.
*
* We do this by default. The only way to disable it is by setting
* empty environment variables NM_OPENVPN_USER and NM_OPENVPN_GROUP. */
nm_openvpn_user = getenv ("NM_OPENVPN_USER") ?: NM_OPENVPN_USER;
nm_openvpn_group = getenv ("NM_OPENVPN_GROUP") ?: NM_OPENVPN_GROUP;
if (*nm_openvpn_user) {
if (getpwnam (nm_openvpn_user)) {
add_openvpn_arg (args, "--user");
add_openvpn_arg (args, nm_openvpn_user);
} else {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("User “%s” not found, check NM_OPENVPN_USER."),
nm_openvpn_user);
return FALSE;
}
}
if (*nm_openvpn_group) {
if (getgrnam (nm_openvpn_group)) {
add_openvpn_arg (args, "--group");
add_openvpn_arg (args, nm_openvpn_group);
} else {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Group “%s” not found, check NM_OPENVPN_GROUP."),
nm_openvpn_group);
return FALSE;
}
}
/* we try to chroot be default. The only way to disable that is by
* setting the an empty environment variable NM_OPENVPN_CHROOT. */
nm_openvpn_chroot = getenv ("NM_OPENVPN_CHROOT") ?: NM_OPENVPN_CHROOT;
if (*nm_openvpn_chroot) {
if (check_chroot_dir_usability (nm_openvpn_chroot, nm_openvpn_user)) {
add_openvpn_arg (args, "--chroot");
add_openvpn_arg (args, nm_openvpn_chroot);
} else
_LOGW ("Directory '%s' not usable for chroot by '%s', openvpn will not be chrooted.",
nm_openvpn_chroot, nm_openvpn_user);
}
g_ptr_array_add (args, NULL);
{
gs_free char *cmd = NULL;
_LOGD ("EXEC: '%s'", (cmd = g_strjoinv (" ", (char **) args->pdata)));
}
if (!g_spawn_async (NULL, (char **) args->pdata, NULL,
G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, error))
return FALSE;
pids_pending_add (pid, plugin);
g_warn_if_fail (!priv->pid);
priv->pid = pid;
/* Listen to the management socket for a few connection types:
PASSWORD: Will require username and password
X509USERPASS: Will require username and password and maybe certificate password
X509: May require certificate password
*/
if ( !strcmp (connection_type, NM_OPENVPN_CONTYPE_TLS)
|| !strcmp (connection_type, NM_OPENVPN_CONTYPE_PASSWORD)
|| !strcmp (connection_type, NM_OPENVPN_CONTYPE_PASSWORD_TLS)
|| nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_USERNAME)) {
priv->io_data = g_malloc0 (sizeof (NMWireguardPluginIOData));
update_io_data_from_vpn_setting (priv->io_data, s_vpn,
nm_setting_vpn_get_user_name (s_vpn));
nm_openvpn_schedule_connect_timer (plugin);
}
return TRUE;
}
static const char *
check_need_secrets (NMSettingVpn *s_vpn, gboolean *need_secrets)
{
const char *tmp, *key, *ctype;
NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
gs_free char *key_free = NULL;
g_return_val_if_fail (s_vpn != NULL, FALSE);
g_return_val_if_fail (need_secrets != NULL, FALSE);
*need_secrets = FALSE;
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CONNECTION_TYPE);
ctype = validate_connection_type (tmp);
if (!ctype)
return NULL;
if (!strcmp (ctype, NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
/* Will require a password and maybe private key password */
key = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
key = nm_utils_str_utf8safe_unescape (key, &key_free);
if (is_encrypted (key) && !nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_CERTPASS))
*need_secrets = TRUE;
if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_PASSWORD)) {
*need_secrets = TRUE;
if (nm_setting_get_secret_flags (NM_SETTING (s_vpn), NM_OPENVPN_KEY_PASSWORD, &secret_flags, NULL)) {
if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
*need_secrets = FALSE;
}
}
} else if (!strcmp (ctype, NM_OPENVPN_CONTYPE_PASSWORD)) {
/* Will require a password */
if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_PASSWORD)) {
*need_secrets = TRUE;
if (nm_setting_get_secret_flags (NM_SETTING (s_vpn), NM_OPENVPN_KEY_PASSWORD, &secret_flags, NULL)) {
if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
*need_secrets = FALSE;
}
}
} else if (!strcmp (ctype, NM_OPENVPN_CONTYPE_TLS)) {
/* May require private key password */
key = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
key = nm_utils_str_utf8safe_unescape (key, &key_free);
if (is_encrypted (key) && !nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_CERTPASS))
*need_secrets = TRUE;
} else {
/* Static key doesn't need passwords */
}
/* HTTP Proxy might require a password; assume so if there's an HTTP proxy username */
tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_USERNAME);
if (tmp && !nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD))
*need_secrets = TRUE;
return ctype;
}
// IMPLEMENT ME RIGHT
static gboolean
wg_disconnect(NMVpnServicePlugin *plugin,
GError **error)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE(plugin);
const char *wg_quick_path = wg_quick_find_exepath();
char *filename = priv->connection_file;
GString *cfg_content = priv->connection_config;
char *command;
int retcode = 1;
if(wg_quick_path == NULL){
_LOGW("Error: Could not find wg-quick!");
return FALSE;
}
if(!filename || !cfg_content){
_LOGW("Error: Cannot remember the connection details for Disconnect");
g_set_error_literal(error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_FAILED,
"Cannot remember the connection details for Disconnect");
return FALSE;
}
// create the temporary configuration file
g_file_set_contents(filename, cfg_content->str, cfg_content->len, error);
g_chmod(filename, 0400);
// join together our command
command = g_strdup_printf("%s down %s", wg_quick_path, filename);
if(!g_spawn_command_line_sync(command, NULL, NULL, &retcode, error)){
_LOGW("An error occured while spawning wg-quick! (Error: %s)", (*error)->message);
}
// delete the file and free temporary private data
g_remove(filename);
g_free(command);
g_string_free(priv->connection_config, TRUE);
g_free(priv->connection_file);
priv->connection_config = NULL;
priv->connection_file = NULL;
_LOGI("Disconnected from Wireguard Connection!");
return TRUE;
}
static gboolean
real_disconnect (NMVpnServicePlugin *plugin,
GError **err)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
if (priv->mgt_path) {
/* openvpn does not cleanup the management socket upon exit,
* possibly it could not even because it changed user */
(void) unlink (priv->mgt_path);
g_clear_pointer (&priv->mgt_path, g_free);
}
if (priv->pid) {
pids_pending_send_sigterm (priv->pid);
priv->pid = 0;
}
return TRUE;
}
static const gchar *
get_setting(NMSettingVpn *s_vpn, const char *key)
{
const gchar *setting = nm_setting_vpn_get_data_item(s_vpn, key);
if(!setting || !setting[0]){
return NULL;
}
return setting;
}
static GVariant *
ip4_to_gvariant (const char *str)
{
gchar *addr;
gchar **tmp, **tmp2;
struct in_addr temp_addr;
GVariant *res;
/* Empty */
if (!str || strlen (str) < 1){
return NULL;
}
// strip the port and subnet
tmp = g_strsplit(str, "/", 0);
tmp2 = g_strsplit(tmp[0], ":", 0);
addr = g_strdup(tmp[0]);
if (inet_pton (AF_INET, addr, &temp_addr) <= 0){
res = NULL;;
}
else{
res = g_variant_new_uint32 (temp_addr.s_addr);
}
g_strfreev(tmp);
g_strfreev(tmp2);
g_free(addr);
return res;
}
static GVariant *
ip6_to_gvariant (const char *str)
{
struct in6_addr temp_addr;
gchar *addr;
gchar **tmp;
GVariantBuilder builder;
int i;
/* Empty */
if (!str || strlen (str) < 1){
return NULL;
}
// since we accept a subnet at the end, let's do away with that.
tmp = g_strsplit(str, "/", 0);
addr = g_strdup(tmp[0]);
g_strfreev(tmp);
if (inet_pton (AF_INET6, addr, &temp_addr) <= 0){
return NULL;
}
g_variant_builder_init (&builder, G_VARIANT_TYPE ("ay"));
for (i = 0; i < sizeof (temp_addr); i++){
g_variant_builder_add (&builder, "y", ((guint8 *) &temp_addr)[i]);
}
return g_variant_builder_end (&builder);
}
static gboolean
send_config(gpointer data)
{
Configs *cfgs = data;
nm_vpn_service_plugin_set_config(cfgs->plugin, cfgs->config);
if(cfgs->ip4config){
nm_vpn_service_plugin_set_ip4_config(cfgs->plugin, cfgs->ip4config);
}
if(cfgs->ip6config){
nm_vpn_service_plugin_set_ip6_config(cfgs->plugin, cfgs->ip6config);
}
// if we don't return FALSE, it's gonna get called again and again and again and...
return FALSE;
}
static gboolean
set_config(NMVpnServicePlugin *plugin, NMConnection *connection)
{
NMSettingVpn *s_vpn = nm_connection_get_setting_vpn(connection);
GVariantBuilder builder, ip4builder, ip6builder;
GVariant *config, *ip4config, *ip6config;
GVariant *val;
const char *setting;
guint64 subnet = 24;
gboolean has_ip4 = FALSE;
gboolean has_ip6 = FALSE;
Configs *configs = malloc(sizeof(Configs));
memset(configs, 0, sizeof(Configs));
// get ready to build the IP4 stuff and send it
// (required that the connection does not time-out)
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_init(&ip4builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_init(&ip6builder, G_VARIANT_TYPE_VARDICT);
// build the configs
setting = get_setting(s_vpn, NM_WG_KEY_ADDR_IP4);
if(setting){
val = ip4_to_gvariant(setting);
if(val){
g_variant_builder_add(&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, val);
// try to find the subnet from the IP
if(g_strrstr(setting, "/")){
gchar **tmp;
tmp = g_strsplit(setting, "/", 2);
if(!g_ascii_string_to_unsigned(tmp[1], 10, 0, 32, &subnet, NULL)){
subnet = 24;
}
g_strfreev(tmp);
}
val = g_variant_new_uint32((guint32)subnet);
g_variant_builder_add(&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PREFIX, val);
has_ip4 = TRUE;
}
}
setting = get_setting(s_vpn, NM_WG_KEY_DNS);
if(setting){
// TODO
}
setting = get_setting(s_vpn, NM_WG_KEY_ENDPOINT);
if(setting){
// TODO
}
setting = get_setting(s_vpn, NM_WG_KEY_MTU);
if(setting){
guint32 mtu = 1420;
if(!g_ascii_string_to_unsigned(setting, 10, 0, 1500, &mtu, NULL)){
mtu = 1420;
}
val = g_variant_new_uint32(mtu);
g_variant_builder_add(&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_MTU, val);
g_variant_builder_add(&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_MTU, val);
}
val = g_variant_new_string(nm_connection_get_id(connection));
g_variant_builder_add(&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV, val);
g_variant_builder_add(&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_TUNDEV, val);
setting = get_setting(s_vpn, NM_WG_KEY_ADDR_IP6);
if(setting){
val = ip6_to_gvariant(setting);
if(val){
g_variant_builder_add(&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS, setting);
has_ip6 = TRUE;
}
}
// check if we have any of IP4 or IP6 and if so, include them in the config
if(!has_ip4 && !has_ip6){
return FALSE;
}
if(has_ip4){
val = g_variant_new_boolean(TRUE);
g_variant_builder_add(&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_HAS_IP4, val);
}
if(has_ip6){
val = g_variant_new_boolean(TRUE);
g_variant_builder_add(&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_HAS_IP6, val);
}
// finish the builders
config = g_variant_builder_end(&builder);
ip4config = g_variant_builder_end(&ip4builder);
ip6config = g_variant_builder_end(&ip6builder);
// populate the configs struct and send the configuration asynchronously
configs->ip4config = (has_ip4) ? ip4config : NULL;
configs->ip6config = (has_ip6) ? ip6config : NULL;
configs->plugin = plugin;
configs->config = config;
g_timeout_add(0, send_config, configs);
return TRUE;
}
static gboolean
connect_common(NMVpnServicePlugin *plugin,
NMConnection *connection,
GVariant *details,
GError **error)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE(plugin);
const char *wg_quick_path = wg_quick_find_exepath();
const char *connection_name = nm_connection_get_id(connection);
char *command;
int retcode = 1;
char *filename = NULL;
GString *connection_config = NULL;
_LOGI("Setting up Wireguard Connection ('%s')", connection_name);
if(wg_quick_path == NULL){
_LOGW("Error: Could not find wg-quick!");
return FALSE;
}
// take the connection details and create the configuration string from it
connection_config = create_config_string(connection, error);
if(!connection_config){
_LOGW("Error: Could not create configuration for connection '%s'!", connection_name);
g_set_error_literal(error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_FAILED,
"Could not create configuration from connection");
return FALSE;
}
priv->connection_config = connection_config;
filename = g_strdup_printf("/tmp/%s.conf", connection_name);
priv->connection_file = filename;
if(!do_export(filename, connection, error)){
_LOGW("Error: Could not create temporary configuration file for connection '%s'", connection_name);
return FALSE;
}
g_chmod(filename, 0400);
// join together our command
command = g_strdup_printf("%s up %s", wg_quick_path, filename);
if(!g_spawn_command_line_sync(command, NULL, NULL, &retcode, error)){
_LOGW("An error occured while spawning wg-quick! (Error: %s)", (*error)->message);
return FALSE;
}
// remove the file and free the command string
g_remove(filename);
g_free(command);
set_config(plugin, connection);
return TRUE;
}
// IMPLEMENT ME RIGHT
static gboolean
wg_connect (NMVpnServicePlugin *plugin,
NMConnection *connection,
GError **error)
{
_LOGI("Connecting to Wireguard: '%s'", nm_connection_get_id(connection));
return connect_common(plugin, connection, NULL, error);
}
// IMPLEMENT ME RIGHT
// this is the function that is actually called when the user clicks the connection in the GUI
static gboolean
wg_connect_interactive(NMVpnServicePlugin *plugin,
NMConnection *connection,
GVariant *details,
GError **error)
{
_LOGI("Connecting interactively to Wireguard: '%s'", nm_connection_get_id(connection));
if(!connect_common(plugin, connection, details, error)){
return FALSE;
}
NM_WIREGUARD_PLUGIN_GET_PRIVATE(plugin)->interactive = TRUE;
return TRUE;
}
static gboolean
_connect_common (NMVpnServicePlugin *plugin,
NMConnection *connection,
GVariant *details,
GError **error)
{
GError *local = NULL;
if (!real_disconnect (plugin, &local)) {
_LOGW ("Could not clean up previous daemon run: %s", local->message);
g_error_free (local);
}
return nm_openvpn_start_openvpn_binary (NM_WIREGUARD_PLUGIN (plugin),
connection,
error);
}
static gboolean
real_connect (NMVpnServicePlugin *plugin,
NMConnection *connection,
GError **error)
{
return _connect_common (plugin, connection, NULL, error);
}
static gboolean
real_connect_interactive (NMVpnServicePlugin *plugin,
NMConnection *connection,
GVariant *details,
GError **error)
{
if (!_connect_common (plugin, connection, details, error))
return FALSE;
NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin)->interactive = TRUE;
return TRUE;
}
// IMPLEMENT ME RIGHT
static gboolean
test_need_secrets (NMVpnServicePlugin *plugin,
NMConnection *connection,
const char **setting_name,
GError **error)
{
_LOGI("I require no secrets!");
return FALSE;
}
static gboolean
real_need_secrets (NMVpnServicePlugin *plugin,
NMConnection *connection,
const char **setting_name,
GError **error)
{
NMSettingVpn *s_vpn;
const char *connection_type;
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);
if (_LOGD_enabled ()) {
_LOGD ("connection -------------------------------------");
nm_connection_dump (connection);
}
s_vpn = nm_connection_get_setting_vpn (connection);
if (!s_vpn) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
_("Could not process the request because the VPN connection settings were invalid."));
return FALSE;
}
connection_type = check_need_secrets (s_vpn, &need_secrets);
if (!connection_type) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
_("Invalid connection type."));
return FALSE;
}
if (need_secrets)
*setting_name = NM_SETTING_VPN_SETTING_NAME;
return need_secrets;
}
// IMPLEMENT ME RIGHT
static gboolean
test_new_secrets (NMVpnServicePlugin *plugin,
NMConnection *connection,
GError **error)
{
_LOGI("New Secrets, anyone?");
return TRUE;
}
static gboolean
real_new_secrets (NMVpnServicePlugin *plugin,
NMConnection *connection,
GError **error)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
NMSettingVpn *s_vpn;
const char *message = NULL;
char **hints = NULL;
s_vpn = nm_connection_get_setting_vpn (connection);
if (!s_vpn) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_INVALID_CONNECTION,
_("Could not process the request because the VPN connection settings were invalid."));
return FALSE;
}
_LOGD ("VPN received new secrets; sending to management interface");
update_io_data_from_vpn_setting (priv->io_data, s_vpn, NULL);
g_warn_if_fail (priv->io_data->pending_auth);
if (!handle_auth (priv->io_data, priv->io_data->pending_auth, &message, &hints)) {
g_set_error_literal (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_FAILED,
_("Unhandled pending authentication."));
return FALSE;
}
/* Request new secrets if we need any */
if (message) {
_LOGD ("Requesting new secrets: '%s'", message);
nm_vpn_service_plugin_secrets_required (plugin, message, (const char **) hints);
}
if (hints)
g_free (hints); /* elements are 'const' */
return TRUE;
}
static void
nm_wireguard_plugin_init (NMWireguardPlugin *plugin)
{
/*
// FIXME this is only for testing if the function gets called
GPid pid = 0;
GError *error = NULL;
char **cmd = malloc(sizeof(char *) * 3);
cmd[0] = "/usr/bin/touch";
cmd[1] = "/home/maxmanski/givemeyournumber";
cmd[2] = NULL;
GSpawnFlags spawn_flags = G_SPAWN_DO_NOT_REAP_CHILD;
spawn_flags = G_SPAWN_DEFAULT;
if (!g_spawn_async (NULL, cmd, NULL, spawn_flags, NULL, NULL, &pid, &error)){
printf("Could not spawn:%s\n", error->message);
}
printf("Spawned:%d.\n", pid);
*/
}
static void
dispose (GObject *object)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (object);
nm_clear_g_source (&priv->connect_timer);
if (priv->pid) {
pids_pending_send_sigterm (priv->pid);
priv->pid = 0;
}
G_OBJECT_CLASS (nm_wireguard_plugin_parent_class)->dispose (object);
}
static void
nm_wireguard_plugin_class_init (NMWireguardPluginClass *plugin_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (plugin_class);
NMVpnServicePluginClass *parent_class = NM_VPN_SERVICE_PLUGIN_CLASS (plugin_class);
g_type_class_add_private (object_class, sizeof (NMWireguardPluginPrivate));
object_class->dispose = dispose;
/* virtual methods */
// IMPLEMENT ME RIGHT
parent_class->connect = wg_connect;
parent_class->connect_interactive = wg_connect_interactive;
parent_class->need_secrets = test_need_secrets;
parent_class->disconnect = wg_disconnect;
parent_class->new_secrets = test_new_secrets;
}
static void
plugin_state_changed (NMWireguardPlugin *plugin,
NMVpnServiceState state,
gpointer user_data)
{
NMWireguardPluginPrivate *priv = NM_WIREGUARD_PLUGIN_GET_PRIVATE (plugin);
switch (state) {
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:
/* Cleanup on failure */
nm_clear_g_source (&priv->connect_timer);
//nm_openvpn_disconnect_management_socket (plugin);
break;
default:
break;
}
}
NMWireguardPlugin *
nm_wireguard_plugin_new (const char *bus_name)
{
NMWireguardPlugin *plugin;
GError *error = NULL;
// NOTE: owning this name must be allowed in a DBUS configuration file:
// "/etc/dbus-1/system.d/nm-wireguard-service.conf"
// (an example conf file was copied to the root of this project)
plugin = (NMWireguardPlugin *) g_initable_new (NM_TYPE_WIREGUARD_PLUGIN, NULL, &error,
NM_VPN_SERVICE_PLUGIN_DBUS_SERVICE_NAME, bus_name,
NM_VPN_SERVICE_PLUGIN_DBUS_WATCH_PEER, !gl.debug,
NULL);
if (plugin) {
printf("Listening to bus-name %s\n", bus_name);
g_signal_connect (G_OBJECT (plugin), "state-changed", G_CALLBACK (plugin_state_changed), NULL);
} else {
_LOGW ("Failed to initialize a plugin instance: %s", error->message);
g_error_free (error);
}
return plugin;
}
static gboolean
signal_handler (gpointer user_data)
{
g_main_loop_quit (user_data);
return G_SOURCE_REMOVE;
}
static void
quit_mainloop (NMVpnServicePlugin *plugin, gpointer user_data)
{
g_main_loop_quit ((GMainLoop *) user_data);
}
int
main (int argc, char *argv[])
{
NMWireguardPlugin *plugin;
gboolean persist = FALSE;
GOptionContext *opt_ctx = NULL;
gchar *bus_name = NM_DBUS_SERVICE_OPENVPN;
GError *error = NULL;
GMainLoop *loop;
GOptionEntry options[] = {
{ "persist", 0, 0, G_OPTION_ARG_NONE, &persist, N_("Dont 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, N_("D-Bus name to use for this instance"), NULL },
{NULL}
};
#if !GLIB_CHECK_VERSION (2, 35, 0)
g_type_init ();
#endif
// TODO rem, was: "OPENVPN_DEBUG"
if (getenv ("WIREGUARD_DEBUG"))
gl.debug = TRUE;
/* locale will be set according to environment LC_* variables */
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, NM_WIREGUARD_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);
// TODO translate
g_option_context_set_summary (opt_ctx,
_("nm-openvpn-service provides integrated "
"OpenVPN capability 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_clear_error (&error);
exit (1);
}
g_option_context_free (opt_ctx);
gl.log_level = _nm_utils_ascii_str_to_int64 (getenv ("NM_VPN_LOG_LEVEL"),
10, 0, LOG_DEBUG, -1);
if (gl.log_level >= 0) {
if (gl.log_level >= LOG_DEBUG)
gl.log_level_ovpn = 10;
else if (gl.log_level >= LOG_INFO)
gl.log_level_ovpn = 5;
else if (gl.log_level > 0)
gl.log_level_ovpn = 2;
else
gl.log_level_ovpn = 1;
} else if (gl.debug)
gl.log_level_ovpn = 10;
else {
/* the default level is already "--verb 1", which is fine for us. */
gl.log_level_ovpn = -1;
}
if (gl.log_level < 0)
gl.log_level = gl.debug ? LOG_INFO : LOG_NOTICE;
gl.log_syslog = _nm_utils_ascii_str_to_int64 (getenv ("NM_VPN_LOG_SYSLOG"),
10, 0, 1,
gl.debug ? 0 : 1);
_LOGD ("nm-wireguard-service (version " DIST_VERSION ") starting...");
// TODO what is this, rem
if ( !g_file_test ("/sys/class/misc/tun", G_FILE_TEST_EXISTS)
&& (system ("/sbin/modprobe tun") == -1)){
exit (EXIT_FAILURE);
}
// TODO fails here:
// nm-openvpn[27808] <warn> Failed to initialize a plugin instance: Connection ":1.598" is not allowed to own the service "org.freedesktop.NetworkManager.openvpn" due to security policies in the configuration file
plugin = nm_wireguard_plugin_new (bus_name);
if (!plugin){
exit (EXIT_FAILURE);
}
loop = g_main_loop_new (NULL, FALSE);
if (!persist)
g_signal_connect (plugin, "quit", G_CALLBACK (quit_mainloop), loop);
signal (SIGPIPE, SIG_IGN);
g_unix_signal_add (SIGTERM, signal_handler, loop);
g_unix_signal_add (SIGINT, signal_handler, loop);
printf("Running the main loop ;>\n");
g_main_loop_run (loop);
g_object_unref (plugin);
pids_pending_wait_for_processes (loop);
g_main_loop_unref (loop);
printf("Exiting...\n");
exit (EXIT_SUCCESS);
}