network-manager-wireguard/properties/import-export.c

2754 lines
79 KiB
C
Raw Normal View History

2017-11-06 15:02:05 +03:00
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/***************************************************************************
*
* 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.
*
* Copyright (C) 2008 - 2013 Dan Williams <dcbw@redhat.com> and Red Hat, Inc.
*
**************************************************************************/
#include "nm-default.h"
#include "import-export.h"
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include "utils.h"
#include "nm-utils/nm-shared-utils.h"
#define INLINE_BLOB_CA "ca"
#define INLINE_BLOB_CERT "cert"
#define INLINE_BLOB_KEY "key"
#define INLINE_BLOB_PKCS12 "pkcs12"
#define INLINE_BLOB_SECRET "secret"
#define INLINE_BLOB_TLS_AUTH "tls-auth"
#define INLINE_BLOB_TLS_CRYPT "tls-crypt"
const char *_nmovpn_test_temp_path = NULL;
/*****************************************************************************/
static const char *
_arg_is_set (const char *value)
{
return (value && value[0]) ? value : NULL;
}
static void
_auto_free_gstring_p (GString **ptr)
{
if (*ptr)
g_string_free (*ptr, TRUE);
}
static gboolean
_is_utf8 (const char *str)
{
g_return_val_if_fail (str, FALSE);
return g_utf8_validate (str, -1, NULL);
}
/*****************************************************************************/
static void
__attribute__((__format__ (__printf__, 3, 4)))
setting_vpn_add_data_item_v (NMSettingVpn *setting,
const char *key,
const char *format,
...)
{
char buf[256];
char *s;
int l;
va_list ap, ap2;
g_return_if_fail (NM_IS_SETTING_VPN (setting));
g_return_if_fail (key && key[0]);
/* let's first try with a stack allocated buffer,
* it's large enough for most cases. */
va_start (ap, format);
va_copy (ap2, ap);
l = g_vsnprintf (buf, sizeof (buf), format, ap2);
va_end (ap2);
if (l < sizeof (buf) - 1) {
va_end (ap);
nm_setting_vpn_add_data_item (setting, key, buf);
return;
}
s = g_strdup_vprintf (format, ap);
va_end (ap);
nm_setting_vpn_add_data_item (setting, key, s);
g_free (s);
}
static void
setting_vpn_add_data_item_int64 (NMSettingVpn *setting,
const char *key,
gint64 value)
{
setting_vpn_add_data_item_v (setting, key, "%"G_GINT64_FORMAT, value);
}
static void
setting_vpn_add_data_item (NMSettingVpn *setting,
const char *key,
const char *value)
{
g_return_if_fail (NM_IS_SETTING_VPN (setting));
g_return_if_fail (key && key[0]);
g_return_if_fail (value && value[0]);
g_return_if_fail (_is_utf8 (value));
nm_setting_vpn_add_data_item (setting, key, value);
}
static void
setting_vpn_add_data_item_utf8safe (NMSettingVpn *setting,
const char *key,
const char *value)
{
gs_free char *s = NULL;
g_return_if_fail (NM_IS_SETTING_VPN (setting));
g_return_if_fail (key && key[0]);
g_return_if_fail (value && value[0]);
nm_setting_vpn_add_data_item (setting, key,
nm_utils_str_utf8safe_escape (value, 0, &s));
}
static void
setting_vpn_add_data_item_path (NMSettingVpn *setting,
const char *key,
const char *value)
{
setting_vpn_add_data_item_utf8safe (setting, key, value);
}
static gboolean
setting_vpn_eq_data_item_utf8safe (NMSettingVpn *setting,
const char *key,
const char *expected_value)
{
gs_free char *s_free = NULL;
const char *s;
s = nm_setting_vpn_get_data_item (setting, key);
if (!s)
return expected_value == NULL;
if (!expected_value)
return FALSE;
return nm_streq (expected_value, nm_utils_str_utf8safe_unescape (s, &s_free));
}
/*****************************************************************************/
static gboolean
args_params_check_nargs_minmax (const char **params, guint nargs_min, guint nargs_max, char **out_error)
{
guint nargs;
g_return_val_if_fail (params, FALSE);
g_return_val_if_fail (params[0], FALSE);
g_return_val_if_fail (out_error && !*out_error, FALSE);
nargs = g_strv_length ((char **) params) - 1;
if (nargs < nargs_min || nargs > nargs_max) {
if (nargs_min != nargs_max) {
*out_error = g_strdup_printf (ngettext ("option %s expects between %u and %u argument",
"option %s expects between %u and %u arguments",
nargs_max),
params[0], nargs_min, nargs_max);
} else if (nargs_min == 0)
*out_error = g_strdup_printf (_("option %s expects no arguments"), params[0]);
else {
*out_error = g_strdup_printf (ngettext ("option %s expects exactly one argument",
"option %s expects exactly %u arguments",
nargs_min),
params[0], nargs_min);
}
return FALSE;
}
return TRUE;
}
static gboolean
args_params_check_nargs_n (const char **params, guint nargs, char **out_error)
{
return args_params_check_nargs_minmax (params, nargs, nargs, out_error);
}
static gboolean
args_params_check_arg_nonempty (const char **params,
guint n_param,
const char *argument_name,
char **out_error)
{
g_return_val_if_fail (params, FALSE);
g_return_val_if_fail (params[0], FALSE);
g_return_val_if_fail (n_param > 0 && n_param < g_strv_length ((char **) params), FALSE);
g_return_val_if_fail (out_error && !*out_error, FALSE);
if (params[n_param][0] == '\0') {
if (argument_name)
*out_error = g_strdup_printf (_("argument %s of “%s” can not be empty"), argument_name, params[0]);
else
*out_error = g_strdup_printf (_("argument of “%s” can not be empty"), params[0]);
return FALSE;
}
return TRUE;
}
static gboolean
args_params_check_arg_utf8 (const char **params,
guint n_param,
const char *argument_name,
char **out_error)
{
if (!args_params_check_arg_nonempty (params, n_param, argument_name, out_error))
return FALSE;
if (!_is_utf8 (params[n_param])) {
if (argument_name)
*out_error = g_strdup_printf (_("argument %s of “%s” must be UTF-8 encoded"), argument_name, params[0]);
else
*out_error = g_strdup_printf (_("argument of “%s” must be UTF-8 encoded"), params[0]);
return FALSE;
}
return TRUE;
}
static gboolean
args_params_parse_int64 (const char **params,
guint n_param,
gint64 min,
gint64 max,
gint64 *out,
char **out_error)
{
gint64 v;
g_return_val_if_fail (params, FALSE);
g_return_val_if_fail (params[0], FALSE);
g_return_val_if_fail (n_param > 0, FALSE);
g_return_val_if_fail (n_param < g_strv_length ((char **) params), FALSE);
g_return_val_if_fail (out_error && !*out_error, FALSE);
v = _nm_utils_ascii_str_to_int64 (params[n_param], 10, min, max, -1);
if (errno) {
*out_error = g_strdup_printf (_("invalid %uth argument to “%s” where number expected"),
n_param,
params[0]);
return FALSE;
}
*out = v;
return TRUE;
}
static gboolean
args_params_parse_port (const char **params, guint n_param, gint64 *out, char **out_error)
{
return args_params_parse_int64 (params, n_param, 1, 65535, out, out_error);
}
static gboolean
args_params_parse_ip4 (const char **params,
guint n_param,
gboolean ovpn_extended_format,
in_addr_t *out,
char **out_error)
{
in_addr_t a;
const char *p;
g_return_val_if_fail (params, FALSE);
g_return_val_if_fail (params[0], FALSE);
g_return_val_if_fail (n_param > 0, FALSE);
g_return_val_if_fail (n_param < g_strv_length ((char **) params), FALSE);
g_return_val_if_fail (out, FALSE);
g_return_val_if_fail (out_error && !*out_error, FALSE);
if (inet_pton (AF_INET, params[n_param], &a) == 1) {
*out = a;
return TRUE;
}
if ( ovpn_extended_format
&& NM_IN_STRSET (params[n_param], "vpn_gateway", "net_gateway", "remote_host")) {
/* we don't support these special destinations, as they currently cannot be expressed
* in a connection. */
*out_error = g_strdup_printf (_("unsupported %uth argument %s to “%s”"),
n_param,
params[n_param],
params[0]);
return FALSE;
}
if ( ovpn_extended_format
&& params[n_param]
&& strlen (params[n_param]) <= 255) {
for (p = params[n_param]; *p; p++) {
if (NM_IN_SET (*p, '-', '.'))
continue;
if (g_ascii_isalnum (*p))
continue;
goto not_dns;
}
/* we also don't support specifing a FQDN. */
*out_error = g_strdup_printf (_("unsupported %uth argument to “%s” which looks like a FQDN but only IPv4 address supported"),
n_param,
params[0]);
return FALSE;
}
not_dns:
*out_error = g_strdup_printf (_("invalid %uth argument to “%s” where IPv4 address expected"),
n_param,
params[0]);
return FALSE;
}
static gboolean
args_params_parse_key_direction (const char **params,
guint n_param,
const char **out_key_direction,
char **out_error)
{
g_return_val_if_fail (params, FALSE);
g_return_val_if_fail (params[0], FALSE);
g_return_val_if_fail (n_param > 0, FALSE);
g_return_val_if_fail (n_param < g_strv_length ((char **) params), FALSE);
g_return_val_if_fail (out_key_direction, FALSE);
g_return_val_if_fail (out_error && !*out_error, FALSE);
/* params will be freed in the next loop iteration. "internalize" the values. */
if (nm_streq (params[n_param], "0"))
*out_key_direction = "0";
else if (nm_streq (params[n_param], "1"))
*out_key_direction = "1";
else {
*out_error = g_strdup_printf (_("invalid %uth key-direction argument to “%s”"), n_param, params[0]);
return FALSE;
}
return TRUE;
}
static char *
args_params_error_message_invalid_arg (const char **params, guint n_param)
{
g_return_val_if_fail (params, NULL);
g_return_val_if_fail (params[0], NULL);
g_return_val_if_fail (n_param > 0, FALSE);
g_return_val_if_fail (n_param < g_strv_length ((char **) params), FALSE);
return g_strdup_printf (_("invalid %uth argument to “%s”"), n_param, params[0]);
}
/*****************************************************************************/
static char
_ch_step_1 (const char **str, gsize *len)
{
char ch;
g_assert (str);
g_assert (len && *len > 0);
ch = (*str)[0];
(*str)++;
(*len)--;
return ch;
}
static void
_ch_skip_over_leading_whitespace (const char **str, gsize *len)
{
while (*len > 0 && g_ascii_isspace ((*str)[0]))
_ch_step_1 (str, len);
}
static void
_strbuf_append_c (char **buf, gsize *len, char ch)
{
nm_assert (buf);
nm_assert (len);
g_return_if_fail (*len > 0);
(*buf)[0] = ch;
(*len)--;
*buf = &(*buf)[1];
}
static gboolean
args_parse_line (const char *line,
gsize line_len,
const char ***out_p,
char **out_error)
{
gs_unref_array GArray *index = NULL;
gs_free char *str_buf_orig = NULL;
char *str_buf;
gsize str_buf_len;
gsize i;
const char *line_start = line;
char **data;
char *pdata;
/* reimplement openvpn's parse_line(). */
g_return_val_if_fail (line, FALSE);
g_return_val_if_fail (out_p && !*out_p, FALSE);
g_return_val_if_fail (out_error && !*out_error, FALSE);
*out_p = NULL;
/* we expect no newline during the first line_len chars. */
for (i = 0; i < line_len; i++) {
if (NM_IN_SET (line[i], '\0', '\n'))
g_return_val_if_reached (FALSE);
}
/* if the line ends with '\r', drop that right way (covers \r\n). */
if (line_len > 0 && line[line_len - 1] == '\r')
line_len--;
/* skip over leading space. */
_ch_skip_over_leading_whitespace (&line, &line_len);
if (line_len == 0)
return TRUE;
if (NM_IN_SET (line[0], ';', '#')) {
/* comment. Note that als openvpn allows for leading spaces
* *before* the comment starts */
return TRUE;
}
/* the maximum required buffer is @line_len+1 characters. We don't produce
* *more* characters then given in the input (plus trailing '\0'). */
str_buf_len = line_len + 1;
str_buf_orig = g_malloc (str_buf_len);
str_buf = str_buf_orig;
index = g_array_new (FALSE, FALSE, sizeof (gsize));
for (;;) {
char quote, ch0;
gssize word_start = line - line_start;
gsize index_i;
index_i = str_buf - str_buf_orig;
g_array_append_val (index, index_i);
switch ((ch0 = _ch_step_1 (&line, &line_len))) {
case '"':
case '\'':
quote = ch0;
while (line_len > 0 && line[0] != quote) {
if (quote == '"' && line[0] == '\\') {
_ch_step_1 (&line, &line_len);
if (line_len <= 0)
break;
}
_strbuf_append_c (&str_buf, &str_buf_len, _ch_step_1 (&line, &line_len));
}
if (line_len <= 0) {
*out_error = g_strdup_printf (_("unterminated %s at position %lld"),
quote == '"' ? _("double quote") : _("single quote"),
(long long) word_start);
return FALSE;
}
/* openvpn terminates parsing of quoted paramaters after the closing quote.
* E.g. "'a'b" gives "a", "b". */
_ch_step_1 (&line, &line_len);
break;
default:
/* once openvpn encounters a non-quoted word, it doesn't consider quoting
* inside the word.
* E.g. "a'b'" gives "a'b'". */
for (;;) {
if (ch0 == '\\') {
if (line_len <= 0) {
*out_error = g_strdup_printf (_("trailing escaping backslash at position %lld"),
(long long) word_start);
return FALSE;
}
ch0 = _ch_step_1 (&line, &line_len);
}
_strbuf_append_c (&str_buf, &str_buf_len, ch0);
if (line_len <= 0)
break;
ch0 = _ch_step_1 (&line, &line_len);
if (g_ascii_isspace (ch0))
break;
}
break;
}
/* the current word is complete.*/
_strbuf_append_c (&str_buf, &str_buf_len, '\0');
_ch_skip_over_leading_whitespace (&line, &line_len);
if (line_len <= 0)
break;
if (NM_IN_SET (line[0], ';', '#')) {
/* comments are allowed to start at the beginning of the next word. */
break;
}
}
str_buf_len = str_buf - str_buf_orig;
/* pack the result in a strv array */
data = g_malloc ((sizeof (const char *) * (index->len + 1)) + str_buf_len);
pdata = (char *) &data[index->len + 1];
memcpy (pdata, str_buf_orig, str_buf_len);
for (i = 0; i < index->len; i++)
data[i] = &pdata[g_array_index (index, gsize, i)];
data[i] = NULL;
*out_p = (const char **) data;
return TRUE;
}
gboolean
_nmovpn_test_args_parse_line (const char *line,
gsize line_len,
const char ***out_p,
char **out_error)
{
return args_parse_line (line, line_len, out_p, out_error);
}
static gboolean
args_next_line (const char **content,
gsize *content_len,
const char **cur_line,
gsize *cur_line_len,
const char **cur_line_delimiter)
{
const char *s;
gsize l, offset;
g_return_val_if_fail (content, FALSE);
g_return_val_if_fail (content_len, FALSE);
g_return_val_if_fail (cur_line, FALSE);
g_return_val_if_fail (cur_line_len, FALSE);
g_return_val_if_fail (cur_line_delimiter, FALSE);
l = *content_len;
if (l <= 0)
return FALSE;
*cur_line = s = *content;
while (l > 0 && !NM_IN_SET (s[0], '\0', '\n'))
_ch_step_1 (&s, &l);
offset = s - *content;
*cur_line_len = offset;
/* cur_line_delimiter will point to a (static) string
* containing the dropped character.
* Or NULL if we reached the end of content. */
if (l > 0) {
if (s[0] == '\0')
*cur_line_delimiter = "\0";
else
*cur_line_delimiter = "\n";
offset++;
} else
*cur_line_delimiter = NULL;
*content_len -= offset;
*content += offset;
return TRUE;
}
/*****************************************************************************/
static gboolean
parse_http_proxy_auth (const char *default_path,
const char *file,
char **out_user,
char **out_pass,
char **out_error)
{
gs_free char *file_free = NULL;
gs_free char *contents = NULL;
char **lines, **iter;
g_return_val_if_fail (out_user && !*out_user, FALSE);
g_return_val_if_fail (out_pass && !*out_pass, FALSE);
g_return_val_if_fail (out_error && !*out_error, FALSE);
if (!file ||
NM_IN_STRSET (file, "stdin", "auto"))
return TRUE;
if (!g_path_is_absolute (file)) {
file_free = g_build_path ("/", default_path, file, NULL);
file = file_free;
}
/* Grab user/pass from authfile */
if (!g_file_get_contents (file, &contents, NULL, NULL)) {
*out_error = g_strdup_printf (_("unable to read HTTP proxy auth file"));
return FALSE;
}
lines = g_strsplit_set (contents, "\n\r", 0);
for (iter = lines; iter && *iter; iter++) {
if ((*iter)[0] == '\0')
continue;
if (!*out_user)
*out_user = g_strdup (g_strstrip (*iter));
else if (!*out_pass) {
*out_pass = g_strdup (g_strstrip (*iter));
break;
}
}
g_strfreev (lines);
if (!*out_user || !*out_pass) {
*out_error = g_strdup_printf (_("cannot read user/password from HTTP proxy auth file"));
g_clear_pointer (out_user, g_free);
g_clear_pointer (out_pass, g_free);
return FALSE;
}
if ( !_is_utf8 (*out_user)
|| !_is_utf8 (*out_pass)) {
*out_error = g_strdup_printf (_("user/password from HTTP proxy auth file must be UTF-8 encoded"));
g_clear_pointer (out_user, g_free);
g_clear_pointer (out_pass, g_free);
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
typedef struct {
char *token;
char *path;
gsize token_start_line;
GString *blob_data;
const char *key;
} InlineBlobData;
static void
inline_blob_data_free (InlineBlobData *data)
{
g_return_if_fail (data);
g_free (data->token);
g_free (data->path);
g_string_free (data->blob_data, TRUE);
g_slice_free (InlineBlobData, data);
}
static char *
inline_blob_construct_path (const char *basename, const char *token)
{
gs_free char *f_filename = NULL;
g_return_val_if_fail (basename, NULL);
g_return_val_if_fail (token && token[0], NULL);
/* Construct file name to write the data in */
f_filename = g_strdup_printf ("%s-%s.pem", basename, token);
if (_nmovpn_test_temp_path)
return g_build_filename (_nmovpn_test_temp_path, f_filename, NULL);
return g_build_filename (g_get_home_dir (), ".cert/nm-openvpn", f_filename, NULL);
}
static gboolean
inline_blob_mkdir_parents (const InlineBlobData *data, const char *filepath, char **out_error)
{
gs_free char *dirname = NULL;
g_return_val_if_fail (filepath && filepath[0], FALSE);
g_return_val_if_fail (out_error && !*out_error, FALSE);
dirname = g_path_get_dirname (filepath);
if (NM_IN_STRSET (dirname, "/", "."))
return TRUE;
if (g_file_test (dirname, G_FILE_TEST_IS_DIR))
return TRUE;
if (g_file_test (dirname, G_FILE_TEST_EXISTS)) {
*out_error = g_strdup_printf (_("“%s” is not a directory"), dirname);
return FALSE;
}
if (!inline_blob_mkdir_parents (data, dirname, out_error))
return FALSE;
if (mkdir (dirname, 0755) < 0) {
*out_error = g_strdup_printf (_("cannot create “%s” directory"), dirname);
return FALSE;
}
return TRUE;
}
static gboolean
inline_blob_write_out (const InlineBlobData *data, GError **error)
{
mode_t saved_umask;
if (!_nmovpn_test_temp_path) {
gs_free char *err_msg = NULL;
/* in test mode we don't create the certificate directory. */
if (!inline_blob_mkdir_parents (data, data->path, &err_msg)) {
g_set_error (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FAILED,
_("cannot write <%s> blob from line %ld to file (%s)"),
data->token,
(long) data->token_start_line,
err_msg);
return FALSE;
}
}
saved_umask = umask (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
/* The file is written with the default umask. Whether that is safe enough
* to protect (potentally) private data or allows the openvpn service to
* access the file later on is left as exercise for the user. */
if (!g_file_set_contents (data->path, data->blob_data->str, data->blob_data->len, NULL)) {
g_set_error (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FAILED,
_("cannot write <%s> blob from line %ld to file “%s”"),
data->token,
(long) data->token_start_line,
data->path);
umask (saved_umask);
return FALSE;
}
umask (saved_umask);
return TRUE;
}
/*****************************************************************************/
2018-02-09 17:05:19 +03:00
static gboolean
_parse_common (const char **line, int *idx, char **out_error)
{
int len = 0;
while(line && line[len]){
len++;
}
// TODO what happens when we have KEY=VALUE?
if(!line[0]){
*out_error = g_strdup_printf("Nothing found in the line");
return FALSE;
}
else if(!line[1]){
*out_error = g_strdup_printf("No value found for setting '%s'", line[0]);
return FALSE;
}
else if(!g_strcmp0("=", line[1])){
// we have an equals sign included
// KEY = VALUE
if(!line[2]){
*out_error = g_strdup_printf("Expected line to be of form KEY = VALUE");
return FALSE;
}
*idx = 2;
}
else{
// we don't have an equals sign included
// KEY VALUE
*idx = 1;
}
return TRUE;
}
static gboolean
parse_endpoint(const char **line, char **endpoint, char **out_error)
{
int idx = 0;
if(!_parse_common(line, &idx, out_error)){
*endpoint = NULL;
return FALSE;
}
*endpoint = g_strdup(line[idx]);
return TRUE;
}
static gboolean
2018-02-10 00:43:16 +03:00
parse_listen_port(const char **line, guint64 *port, char **out_error)
2018-02-09 17:05:19 +03:00
{
int idx = 0;
2018-02-10 00:43:16 +03:00
char *tmp = NULL;
gboolean success = TRUE;
2018-02-09 17:05:19 +03:00
if(!_parse_common(line, &idx, out_error)){
return FALSE;
}
2018-02-10 00:43:16 +03:00
tmp = g_strdup(line[idx]);
if(!g_ascii_string_to_unsigned(tmp, 10, 0, 65535, port, NULL)){
*out_error = g_strdup_printf("'%s' is not a valid port assignment!", tmp);
*port = -1;
success = FALSE;
2018-02-09 17:05:19 +03:00
}
2018-02-10 00:43:16 +03:00
g_free(tmp);
return success;
2018-02-09 17:05:19 +03:00
}
static gboolean
parse_private_key(const char **line, char **key, char **out_error)
{
int idx = 0;
if(!_parse_common(line, &idx, out_error)){
*key = NULL;
return FALSE;
}
*key = g_strdup(line[idx]);
// TODO check if base64?
// TOOD check length?
return TRUE;
}
#define parse_public_key(line, key, out_error) parse_private_key(line, key, out_error)
static gboolean
parse_preshared_key(const char **line, char **key, char **out_error)
{
int idx = 0;
if(!_parse_common(line, &idx, out_error)){
*key = NULL;
return FALSE;
}
*key = g_strdup(line[idx]);
// TODO any checks?
return TRUE;
}
static gboolean
_is_ip4(char *addr)
{
int idx = 0;
int dots = 0;
2018-02-10 00:43:16 +03:00
gchar **parts;
gchar **tmp;
gchar **tmp2;
gchar *lastpart;
gboolean success = TRUE;
if(!addr){
return FALSE;
}
2018-02-09 17:05:19 +03:00
while(addr && addr[idx]){
if(addr[idx] == '.'){
dots++;
}
idx++;
}
if(dots != 3){
return FALSE;
}
2018-02-10 00:43:16 +03:00
parts = g_strsplit(addr, ".", 0);
// iterate over the first three parts, which cannot be anything else than numbers
for(idx = 0; idx < 3; idx++){
if(!g_ascii_string_to_unsigned(parts[idx], 10, 0, 255, NULL, NULL)){
success = FALSE;
goto ip4end;
}
}
2018-02-09 17:05:19 +03:00
2018-02-10 00:43:16 +03:00
// if the last part is a number, we're fine
lastpart = parts[3];
if(g_ascii_string_to_unsigned(lastpart, 10, 0, 255, NULL, NULL)){
success = TRUE;
goto ip4end;
}
2018-02-09 17:05:19 +03:00
2018-02-10 00:43:16 +03:00
// might have a subnet suffix after a slash (e.g. 192.168.1.254/24)
2018-02-09 17:05:19 +03:00
// might have a port suffix after a colon (e.g. 192.168.1.254:8080)
2018-02-10 00:43:16 +03:00
if(g_strrstr(lastpart, ":") && g_strrstr(lastpart, "/")){
tmp = g_strsplit(lastpart, ":", 2);
tmp2 = g_strsplit(tmp[1], "/", 2);
2018-02-09 17:05:19 +03:00
2018-02-10 00:43:16 +03:00
if(!g_ascii_string_to_unsigned(tmp[0], 10, 0, 255, NULL, NULL)){
// the last part of the IP
success = FALSE;
}
if(!g_ascii_string_to_unsigned(tmp2[0], 10, 0, 65535, NULL, NULL)){
// the port
success = FALSE;
}
if(!g_ascii_string_to_unsigned(tmp2[1], 10, 0, 32, NULL, NULL)){
// the subnet portion
success = FALSE;
}
g_strfreev(tmp);
g_strfreev(tmp2);
}
else if(g_strrstr(lastpart, "/")){
tmp = g_strsplit(lastpart, "/", 2);
if(!g_ascii_string_to_unsigned(tmp[0], 10, 0, 255, NULL, NULL)){
// the last part of the IP
success = FALSE;
}
if(!g_ascii_string_to_unsigned(tmp[1], 10, 0, 32, NULL, NULL)){
// the subnet portion
success = FALSE;
}
g_strfreev(tmp);
}
else if(g_strrstr(lastpart, ":")){
tmp = g_strsplit(lastpart, ":", 2);
if(!g_ascii_string_to_unsigned(tmp[0], 10, 0, 255, NULL, NULL)){
// the last part of the IP
success = FALSE;
}
if(!g_ascii_string_to_unsigned(tmp[1], 10, 0, 65535, NULL, NULL)){
// the port
success = FALSE;
}
g_strfreev(tmp);
}
else{
// we have neither a port nor a subnet suffix, but it's not a number either
success = FALSE;
}
ip4end:
g_strfreev(parts);
return success;
2018-02-09 17:05:19 +03:00
}
static gboolean
_is_ip6(char *addr)
{
2018-02-10 00:43:16 +03:00
gchar **parts;
gchar **tmp;
gchar *lastpart;
int len = 0;
int i = 0;
int num_empty = 0;
gboolean success = TRUE;
if(!addr){
return FALSE;
}
else if(!g_strrstr(addr, ":")){
return FALSE;
}
parts = g_strsplit(addr, ":", 0);
while(parts && parts[len]){
len++;
}
num_empty = 0;
for(i = 0; i < (len-1); i++){
if((i == 0) && (!g_strcmp0("", parts[i]))){
// the beginning may be empty (e.g. in "::1")
continue;
}
if(!g_strcmp0("", parts[i]) && (num_empty < 1)){
// there may be one "skipped" part in the IP6
num_empty++;
}
else if(!g_ascii_string_to_unsigned(parts[i], 16, 0, 65536, NULL, NULL)){
// the rest of the parts have to be numerals between 0 and 16^4 in hex
success = FALSE;
goto ip6end;
}
}
lastpart = parts[len-1];
if(g_strrstr(lastpart, "/")){
// we have a subnet portion
tmp = g_strsplit(lastpart, "/", 2);
if(g_strcmp0("", tmp[0]) && !g_ascii_string_to_unsigned(tmp[0], 16, 0, 65536, NULL, NULL)){
success = FALSE;
}
else if(!g_ascii_string_to_unsigned(tmp[1], 10, 0, 128, NULL, NULL)){
success = FALSE;
}
g_strfreev(tmp);
}
else{
// there is only a number, or an empty string (e.g. in the case of "::")
if(g_strcmp0("", lastpart) && !g_ascii_string_to_unsigned(lastpart, 16, 0, 65536, NULL, NULL)){
success = FALSE;
}
}
ip6end:
g_strfreev(parts);
return success;
2018-02-09 17:05:19 +03:00
}
static char *
2018-02-10 00:43:16 +03:00
_parse_ip4_address(const char *address)
2018-02-09 17:05:19 +03:00
{
char *ip4 = g_strdup(address);
size_t len = strlen(ip4);
// if there is a trailing comma, remove it
// -- might be, because the config can have an IP4 and IP6
if(ip4[len - 1] == ','){
ip4[len - 1] = '\0';
}
if(!_is_ip4(ip4)){
g_free(ip4);
ip4 = NULL;
}
return ip4;
}
static char *
2018-02-10 00:43:16 +03:00
_parse_ip6_address(const char *address)
2018-02-09 17:05:19 +03:00
{
char *ip6 = g_strdup(address);
size_t len = strlen(ip6);
// same as for IP4
if(ip6[len - 1] == ','){
ip6[len - 1] = '\0';
}
if(!_is_ip6(ip6)){
g_free(ip6);
ip6 = NULL;
}
return ip6;
}
static gboolean
parse_address(const char **line, char **ip4_address, char **ip6_address, char **out_error)
{
int idx = 0;
char *ip4 = NULL;
char *ip6 = NULL;
2018-02-10 00:43:16 +03:00
gboolean success = FALSE;
2018-02-09 17:05:19 +03:00
if(!_parse_common(line, &idx, out_error)){
*ip4_address = NULL;
*ip6_address = NULL;
return FALSE;
}
while(line && line[idx]){
ip4 = _parse_ip4_address(line[idx]);
if(ip4){
*ip4_address = ip4;
idx++;
2018-02-10 00:43:16 +03:00
success = TRUE;
2018-02-09 17:05:19 +03:00
continue;
}
ip6 = _parse_ip6_address(line[idx]);
if(ip6){
*ip6_address = ip6;
2018-02-10 00:43:16 +03:00
success = TRUE;
2018-02-09 17:05:19 +03:00
}
idx++;
}
2018-02-10 00:43:16 +03:00
return success;
}
static gboolean
parse_allowed_ips(const char **line, GArray **addresses, char **out_error)
{
int idx = 0;
char *ip4 = NULL;
char *ip6 = NULL;
gboolean success = FALSE;
if(!_parse_common(line, &idx, out_error)){
*addresses = NULL;
return FALSE;
}
*addresses = g_array_new(TRUE, TRUE, sizeof(char *));
while(line && line[idx]){
ip4 = _parse_ip4_address(line[idx]);
if(ip4){
g_array_append_val(*addresses, ip4);
success = TRUE;
goto ip4next;
}
ip6 = _parse_ip6_address(line[idx]);
if(ip6){
g_array_append_val(*addresses, ip6);
success = TRUE;
}
ip4next:
idx++;
}
if(!success){
g_array_free(*addresses, TRUE);
}
return success;
}
static gboolean
parse_script(const char **line, char **script, char **out_error)
{
int idx = 0;
char *tmp = NULL;
int len = 0;
int idx2 = 0;
if(!_parse_common(line, &idx, out_error)){
*script = NULL;
return FALSE;
}
// calculate how much space we are going to need
idx2 = idx;
while(line && line[idx2]){
// one extra character for the space between the commands
len += strlen(line[idx2]) + 1;
idx2++;
}
// the last extra slot isn't taken by a space, but by a NULL-byte
*script = g_malloc(len);
tmp = g_stpcpy(*script, "");
while(line && line[idx]){
tmp = g_stpcpy(tmp, line[idx]);
if(line[idx+1]){
tmp = g_stpcpy(tmp, " ");
}
idx++;
}
2018-02-09 17:05:19 +03:00
return TRUE;
}
2018-02-10 00:43:16 +03:00
static gchar *
concatenate_strings(const GArray *string_array, char *separator)
{
int i = 0;
int len = 0;
int sep_len = 0;
char *result;
char *tmp;
if(!string_array){
return NULL;
}
if(!separator){
separator = ",";
}
// check how much space we are going to need
sep_len = strlen(separator);
for(i = 0; i < string_array->len; i++){
len += strlen(g_array_index(string_array, char *, i));
if(i < (string_array->len - 1)){
len += sep_len;
}
}
// space for the trailing NULL-byte
len += 1;
// allocate memory and do the appending
result = g_malloc(len);
tmp = g_stpcpy(result, "");
for(i = 0; i < string_array->len; i++){
tmp = g_stpcpy(tmp, g_array_index(string_array, char *, i));
if(i < (string_array->len - 1)){
tmp = g_stpcpy(tmp, separator);
}
}
return result;
}
2018-02-09 17:05:19 +03:00
2017-11-06 15:02:05 +03:00
NMConnection *
do_import (const char *path, const char *contents, gsize contents_len, GError **error)
{
gs_unref_object NMConnection *connection_free = NULL;
NMConnection *connection;
NMSettingConnection *s_con;
NMSettingIPConfig *s_ip4;
NMSettingVpn *s_vpn;
const char *cur_line, *cur_line_delimiter;
gsize cur_line_len;
gsize contents_cur_line;
gboolean have_client = FALSE, have_remote = FALSE;
gboolean have_pass = FALSE, have_sk = FALSE;
const char *ctype = NULL;
gs_free char *basename = NULL;
gs_free char *default_path = NULL;
char *tmp, *tmp2;
const char *ta_direction = NULL, *secret_direction = NULL;
gboolean allow_ta_direction = FALSE, allow_secret_direction = FALSE;
gboolean have_certs, have_ca;
GSList *inline_blobs = NULL, *sl_iter;
g_return_val_if_fail (!error || !*error, NULL);
connection = nm_simple_connection_new ();
connection_free = connection;
s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
nm_connection_add_setting (connection, NM_SETTING (s_con));
s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
nm_connection_add_setting (connection, NM_SETTING (s_ip4));
g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
s_vpn = NM_SETTING_VPN (nm_setting_vpn_new ());
g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, NM_VPN_SERVICE_TYPE_OPENVPN, NULL);
nm_connection_add_setting (connection, NM_SETTING (s_vpn));
/* Get the default path for ca, cert, key file, these files maybe
* in same path with the configuration file */
if (g_path_is_absolute (path))
default_path = g_path_get_dirname (path);
else {
tmp = g_get_current_dir ();
tmp2 = g_path_get_dirname (path);
default_path = g_build_filename (tmp, tmp2, NULL);
g_free (tmp);
g_free (tmp2);
}
basename = g_path_get_basename (path);
tmp = strrchr (basename, '.');
if (tmp)
*tmp = '\0';
g_object_set (s_con, NM_SETTING_CONNECTION_ID, basename, NULL);
if (strncmp (contents, "\xEF\xBB\xBF", 3) == 0) {
/* skip over UTF-8 BOM */
contents += 3;
contents_len -= 3;
}
contents_cur_line = 0;
while (args_next_line (&contents,
&contents_len,
&cur_line,
&cur_line_len,
&cur_line_delimiter)) {
gs_free const char **params = NULL;
char *line_error = NULL;
gint64 v_int64;
contents_cur_line++;
if (!args_parse_line (cur_line, cur_line_len, &params, &line_error))
goto handle_line_error;
if (!params) {
/* empty line of comments. */
continue;
}
g_assert (params[0]);
2018-02-09 17:05:19 +03:00
// interface section
if (NM_IN_STRSET (params[0], NMV_WG_TAG_INTERFACE)){
2018-02-10 00:43:16 +03:00
printf("%s\n", params[0]);
2018-02-09 17:05:19 +03:00
continue;
}
if (NM_IN_STRSET (params[0], NMV_WG_TAG_LISTEN_PORT)){
2018-02-10 00:43:16 +03:00
guint64 port = 0;
2018-02-09 17:05:19 +03:00
if(!parse_listen_port(params, &port, &line_error)){
goto handle_line_error;
}
2018-02-10 00:43:16 +03:00
setting_vpn_add_data_item_int64(s_vpn, NM_WG_KEY_LISTEN_PORT, port);
printf("%s = %ld\n", NMV_WG_TAG_LISTEN_PORT, port);
2018-02-09 17:05:19 +03:00
continue;
}
if (NM_IN_STRSET (params[0], NMV_WG_TAG_ADDRESS)){
char *addr4 = NULL;
char *addr6 = NULL;
if(!parse_address(params, &addr4, &addr6, &line_error)){
goto handle_line_error;
}
2018-02-10 00:43:16 +03:00
if(addr4 && addr6){
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_ADDR_IP4, addr4);
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_ADDR_IP6, addr6);
printf("%s = %s, %s\n", NMV_WG_TAG_ADDRESS, addr4, addr6);
}
else if(addr4){
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_ADDR_IP4, addr4);
printf("%s = %s\n", NMV_WG_TAG_ADDRESS, addr4);
}
else if(addr6){
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_ADDR_IP6, addr6);
printf("%s = %s\n", NMV_WG_TAG_ADDRESS, addr6);
}
2018-02-09 17:05:19 +03:00
continue;
}
if (NM_IN_STRSET (params[0], NMV_WG_TAG_PRIVATE_KEY)){
char *key = NULL;
if(!parse_private_key(params, &key, &line_error)){
goto handle_line_error;
}
2018-02-10 00:43:16 +03:00
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_PRIVATE_KEY, key);
printf("%s = %s\n", NMV_WG_TAG_PRIVATE_KEY, key);
2018-02-09 17:05:19 +03:00
continue;
}
if (NM_IN_STRSET (params[0], NMV_WG_TAG_PRESHARED_KEY)){
char *psk = NULL;
if(!parse_preshared_key(params, &psk, &line_error)){
goto handle_line_error;
}
2018-02-10 00:43:16 +03:00
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_PRESHARED_KEY, psk);
printf("%s = %s\n", NMV_WG_TAG_PRESHARED_KEY, psk);
2018-02-09 17:05:19 +03:00
continue;
}
if (NM_IN_STRSET (params[0], NMV_WG_TAG_POST_UP)){
char *script = NULL;
2018-02-10 00:43:16 +03:00
if(!parse_script(params, &script, &line_error)){
goto handle_line_error;
}
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_POST_UP, script);
printf("%s = %s\n", NMV_WG_TAG_POST_UP, script);
2018-02-09 17:05:19 +03:00
continue;
}
if (NM_IN_STRSET (params[0], NMV_WG_TAG_POST_DOWN)){
2018-02-10 00:43:16 +03:00
char *script = NULL;
if(!parse_script(params, &script, &line_error)){
goto handle_line_error;
}
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_POST_DOWN, script);
printf("%s = %s\n", NMV_WG_TAG_POST_DOWN, script);
2018-02-09 17:05:19 +03:00
continue;
}
// peer section
if (NM_IN_STRSET (params[0], NMV_WG_TAG_PEER)){
2018-02-10 00:43:16 +03:00
printf("%s\n", params[0]);
2018-02-09 17:05:19 +03:00
continue;
}
if(NM_IN_STRSET (params[0], NMV_WG_TAG_ALLOWED_IPS)){
2018-02-10 00:43:16 +03:00
GArray *addrs = NULL;
int i = 0;
int len = 0;
char *ip_string = NULL;
char *allowed_ips = NULL;
if(!parse_allowed_ips(params, &addrs, &line_error)){
goto handle_line_error;
}
for(i = 0; i < addrs->len; i++){
len += strlen(g_array_index(addrs, char *, i)) + 2;
}
ip_string = concatenate_strings(addrs, ", ");
allowed_ips = concatenate_strings(addrs, ",");
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_ALLOWED_IPS, allowed_ips);
printf("%s = %s\n", NMV_WG_TAG_ALLOWED_IPS, ip_string);
2018-02-09 17:05:19 +03:00
continue;
}
if(NM_IN_STRSET (params[0], NMV_WG_TAG_PUBLIC_KEY)){
char *key = NULL;
if(!parse_public_key(params, &key, &line_error)){
goto handle_line_error;
}
2018-02-10 00:43:16 +03:00
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_PUBLIC_KEY, key);
printf("%s = %s\n", NMV_WG_TAG_PUBLIC_KEY, key);
2018-02-09 17:05:19 +03:00
continue;
}
if (NM_IN_STRSET (params[0], NMV_WG_TAG_ENDPOINT)){
char *endpoint;
if(!parse_endpoint(params, &endpoint, &line_error)){
goto handle_line_error;
}
2018-02-10 00:43:16 +03:00
setting_vpn_add_data_item(s_vpn, NM_WG_KEY_ENDPOINT, endpoint);
printf("%s = %s\n", NMV_WG_TAG_ENDPOINT, endpoint);
2018-02-09 17:05:19 +03:00
continue;
}
/*************************************************************************************/
2017-11-06 15:02:05 +03:00
/* allow for a leading double-dash and skip over it (bypass_doubledash). */
if (g_str_has_prefix (params[0], "--"))
params[0] = &params[0][2];
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_CLIENT, NMV_OVPN_TAG_TLS_CLIENT)) {
if (!args_params_check_nargs_n (params, 0, &line_error))
goto handle_line_error;
have_client = TRUE;
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_KEY_DIRECTION)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_parse_key_direction (params, 1, &ta_direction, &line_error))
goto handle_line_error;
secret_direction = ta_direction;
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_DEV)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_check_arg_nonempty (params, 1, NULL, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_utf8safe (s_vpn, NM_OPENVPN_KEY_DEV, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_DEV_TYPE)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!NM_IN_STRSET (params[1], "tun", "tap")) {
line_error = args_params_error_message_invalid_arg (params, 1);
goto handle_line_error;
}
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_DEV_TYPE, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_PROTO)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
/* Valid parameters are "udp", "tcp-client" and "tcp-server".
* 'tcp' isn't technically valid, but it used to be accepted so
* we'll handle it here anyway.
*/
if (nm_streq (params[1], "udp")) {
/* ignore; udp is default */
} else if (NM_IN_STRSET (params[1], "tcp-client", "tcp-server", "tcp"))
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROTO_TCP, "yes");
else {
line_error = args_params_error_message_invalid_arg (params, 1);
goto handle_line_error;
}
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_MSSFIX)) {
if (!args_params_check_nargs_minmax (params, 0, 1, &line_error))
goto handle_line_error;
if (params[1]) {
if (!args_params_parse_int64 (params, 1, 1, G_MAXINT32, &v_int64, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_MSSFIX, v_int64);
} else
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_MSSFIX, "yes");
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_MTU_DISC)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!NM_IN_STRSET (params[1], "no", "maybe", "yes")) {
line_error = g_strdup_printf (_("unsupported mtu-disc argument"));
goto handle_line_error;
}
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_MTU_DISC, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_NS_CERT_TYPE)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!NM_IN_STRSET (params[1], NM_OPENVPN_NS_CERT_TYPE_CLIENT, NM_OPENVPN_NS_CERT_TYPE_SERVER)) {
line_error = g_strdup_printf (_("invalid option"));
goto handle_line_error;
}
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_NS_CERT_TYPE, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_TUN_MTU)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_parse_int64 (params, 1, 0, 0xffff, &v_int64, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_TUNNEL_MTU, v_int64);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_FRAGMENT)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_parse_int64 (params, 1, 0, 0xffff, &v_int64, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_FRAGMENT_SIZE, v_int64);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_COMP_LZO)) {
const char *v;
if (!args_params_check_nargs_minmax (params, 0, 1, &line_error))
goto handle_line_error;
v = params[1] ?: "adaptive";
if (nm_streq (v, "no")) {
/* old plasma-nm used to set "comp-lzo=no" to mean unset, thus it spoiled
* to "no" option to be used in the connection. Workaround that, by instead
* using "no-by-default" (bgo#769177). */
v = "no-by-default";
} else if (!NM_IN_STRSET (v, "yes", "adaptive")) {
line_error = g_strdup_printf (_("unsupported comp-lzo argument"));
goto handle_line_error;
}
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_COMP_LZO, v);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_FLOAT)) {
if (!args_params_check_nargs_n (params, 0, &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_FLOAT, "yes");
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_RENEG_SEC)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_parse_int64 (params, 1, 0, G_MAXINT, &v_int64, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_RENEG_SECONDS, v_int64);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_MAX_ROUTES)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_parse_int64 (params, 1, 0, 100000000, &v_int64, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_MAX_ROUTES, v_int64);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_HTTP_PROXY_RETRY, NMV_OVPN_TAG_SOCKS_PROXY_RETRY)) {
if (!args_params_check_nargs_n (params, 0, &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_RETRY, "yes");
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_HTTP_PROXY, NMV_OVPN_TAG_SOCKS_PROXY)) {
const char *proxy_type = NULL;
gint64 port = 0;
gs_free char *user = NULL;
gs_free char *pass = NULL;
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_HTTP_PROXY)) {
proxy_type = "http";
if (!args_params_check_nargs_minmax (params, 2, 4, &line_error))
goto handle_line_error;
} else {
proxy_type = "socks";
if (!args_params_check_nargs_minmax (params, 1, 3, &line_error))
goto handle_line_error;
}
if (!args_params_check_arg_utf8 (params, 1, "service", &line_error))
goto handle_line_error;
if (params[2]) {
if (!args_params_parse_port (params, 2, &port, &line_error))
goto handle_line_error;
if (params[3]) {
if (!parse_http_proxy_auth (default_path, params[3], &user, &pass, &line_error))
goto handle_line_error;
}
}
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_TYPE, proxy_type);
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_SERVER, params[1]);
if (port > 0)
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_PROXY_PORT, port);
if (user)
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_USERNAME, user);
if (pass) {
nm_setting_vpn_add_secret (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD, pass);
nm_setting_set_secret_flags (NM_SETTING (s_vpn),
NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD,
NM_SETTING_SECRET_FLAG_AGENT_OWNED,
NULL);
}
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_REMOTE)) {
const char *prev;
GString *new_remote;
int port = -1;
gboolean host_has_colon;
struct in6_addr a;
if (!args_params_check_nargs_minmax (params, 1, 3, &line_error))
goto handle_line_error;
if (!args_params_check_arg_utf8 (params, 1, NULL, &line_error))
goto handle_line_error;
if (strchr (params[1], ' ')) {
line_error = g_strdup_printf (_("remote cannot contain space"));
goto handle_line_error;
}
if (strchr (params[1], ',')) {
line_error = g_strdup_printf (_("remote cannot contain comma"));
goto handle_line_error;
}
if (params[2]) {
if (!args_params_parse_port (params, 2, &v_int64, &line_error))
goto handle_line_error;
port = v_int64;
if (params[3]) {
if (!NM_IN_STRSET (params[3], NMOVPN_PROTCOL_TYPES)) {
line_error = g_strdup_printf (_("remote expects protocol type like “udp” or “tcp”"));
goto handle_line_error;
}
}
}
new_remote = g_string_sized_new (64);
prev = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE);
if (prev) {
g_string_assign (new_remote, prev);
g_string_append (new_remote, ", ");
}
host_has_colon = (strchr (params[1], ':') != NULL);
if ( host_has_colon
&& inet_pton (AF_INET6, params[1], &a) == 1) {
/* need to escape the host. */
g_string_append_printf (new_remote, "[%s]", params[1]);
} else
g_string_append (new_remote, params[1]);
if (params[2]) {
g_string_append_printf (new_remote, ":%d", port);
if (params[3]) {
g_string_append_c (new_remote, ':');
g_string_append (new_remote, params[3]);
} else if (host_has_colon)
g_string_append_c (new_remote, ':');
} else if (host_has_colon)
g_string_append (new_remote, "::");
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE, new_remote->str);
g_string_free (new_remote, TRUE);
have_remote = TRUE;
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_REMOTE_RANDOM)) {
if (!args_params_check_nargs_n (params, 0, &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_RANDOM, "yes");
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_TUN_IPV6)) {
if (!args_params_check_nargs_n (params, 0, &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TUN_IPV6, "yes");
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_PORT, NMV_OVPN_TAG_RPORT)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_parse_port (params, 1, &v_int64, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_PORT, v_int64);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_PING, NMV_OVPN_TAG_PING_EXIT, NMV_OVPN_TAG_PING_RESTART)) {
const char *key = NULL;
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_parse_int64 (params, 1, 0, G_MAXINT, &v_int64, &line_error))
goto handle_line_error;
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_PING))
key = NM_OPENVPN_KEY_PING;
else if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_PING_EXIT))
key = NM_OPENVPN_KEY_PING_EXIT;
else if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_PING_RESTART))
key = NM_OPENVPN_KEY_PING_RESTART;
setting_vpn_add_data_item_int64 (s_vpn, key, v_int64);
continue;
}
if (NM_IN_STRSET (params[0],
NMV_OVPN_TAG_PKCS12,
NMV_OVPN_TAG_CA,
NMV_OVPN_TAG_CERT,
NMV_OVPN_TAG_KEY,
NMV_OVPN_TAG_SECRET,
NMV_OVPN_TAG_TLS_AUTH,
NMV_OVPN_TAG_TLS_CRYPT)) {
const char *file;
gs_free char *file_free = NULL;
gboolean can_have_direction;
const char *s_direction = NULL;
can_have_direction = NM_IN_STRSET (params[0],
NMV_OVPN_TAG_SECRET,
NMV_OVPN_TAG_TLS_AUTH);
if (!args_params_check_nargs_minmax (params, 1, can_have_direction ? 2 : 1, &line_error))
goto handle_line_error;
if (!args_params_check_arg_nonempty (params, 1, NULL, &line_error))
goto handle_line_error;
file = params[1];
if (params[2]) {
if (!args_params_parse_key_direction (params, 2, &s_direction, &line_error))
goto handle_line_error;
}
if (!g_path_is_absolute (file))
file = file_free = g_build_filename (default_path, file, NULL);
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_PKCS12)) {
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_CA, file);
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_CERT, file);
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_KEY, file);
} else if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_CA))
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_CA, file);
else if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_CERT))
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_CERT, file);
else if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_KEY))
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_KEY, file);
else if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_SECRET)) {
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_STATIC_KEY, file);
if (s_direction)
secret_direction = s_direction;
allow_secret_direction = TRUE;
have_sk = TRUE;
} else if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_TLS_AUTH)) {
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_TA, file);
if (s_direction)
ta_direction = s_direction;
allow_ta_direction = TRUE;
} else if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_TLS_CRYPT))
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_TLS_CRYPT, file);
else
g_assert_not_reached ();
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_CIPHER)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_check_arg_utf8 (params, 1, NULL, &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CIPHER, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_TLS_CIPHER)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_check_arg_utf8 (params, 1, NULL, &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TLS_CIPHER, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_KEEPALIVE)) {
gint64 v2;
if (!args_params_check_nargs_n (params, 2, &line_error))
goto handle_line_error;
if (!args_params_parse_int64 (params, 1, 0, G_MAXINT, &v_int64, &line_error))
goto handle_line_error;
if (!args_params_parse_int64 (params, 2, 0, G_MAXINT, &v2, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_PING, v_int64);
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_PING_RESTART, v2);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_KEYSIZE)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_parse_int64 (params, 1, 1, 65535, &v_int64, &line_error))
goto handle_line_error;
setting_vpn_add_data_item_int64 (s_vpn, NM_OPENVPN_KEY_KEYSIZE, v_int64);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_TLS_REMOTE)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_check_arg_utf8 (params, 1, NULL, &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TLS_REMOTE, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_VERIFY_X509_NAME)) {
const char *type = NM_OPENVPN_VERIFY_X509_NAME_TYPE_SUBJECT;
gs_free char *item = NULL;
if (!args_params_check_nargs_minmax (params, 1, 2, &line_error))
goto handle_line_error;
if (!args_params_check_arg_utf8 (params, 1, NULL, &line_error))
goto handle_line_error;
if (params[2]) {
if (!NM_IN_STRSET (params[2],
NM_OPENVPN_VERIFY_X509_NAME_TYPE_SUBJECT,
NM_OPENVPN_VERIFY_X509_NAME_TYPE_NAME,
NM_OPENVPN_VERIFY_X509_NAME_TYPE_NAME_PREFIX)) {
line_error = g_strdup_printf (_("invalid verify-x509-name type"));
goto handle_line_error;
}
type = params[2];
}
item = g_strdup_printf ("%s:%s", type, params[1]);
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_VERIFY_X509_NAME, item);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_REMOTE_CERT_TLS)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!NM_IN_STRSET (params[1], NM_OPENVPN_REM_CERT_TLS_CLIENT, NM_OPENVPN_REM_CERT_TLS_SERVER)) {
line_error = g_strdup_printf (_("invalid option"));
goto handle_line_error;
}
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_CERT_TLS, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_IFCONFIG)) {
if (!args_params_check_nargs_n (params, 2, &line_error))
goto handle_line_error;
if (!args_params_check_arg_utf8 (params, 1, "local", &line_error))
goto handle_line_error;
if (!args_params_check_arg_utf8 (params, 2, "remote", &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_LOCAL_IP, params[1]);
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_IP, params[2]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_AUTH_USER_PASS)) {
if (!args_params_check_nargs_minmax (params, 0, 1, &line_error))
goto handle_line_error;
have_pass = TRUE;
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_AUTH)) {
if (!args_params_check_nargs_n (params, 1, &line_error))
goto handle_line_error;
if (!args_params_check_arg_utf8 (params, 1, NULL, &line_error))
goto handle_line_error;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_AUTH, params[1]);
continue;
}
if (NM_IN_STRSET (params[0], NMV_OVPN_TAG_ROUTE)) {
in_addr_t network;
in_addr_t gateway = 0;
guint32 prefix = 32;
gint64 metric = -1;
if (!args_params_check_nargs_minmax (params, 1, 4, &line_error))
goto handle_line_error;
if (!args_params_parse_ip4 (params, 1, TRUE, &network, &line_error))
goto handle_line_error;
if (params[2]) {
in_addr_t netmask;
if (!args_params_parse_ip4 (params, 2, FALSE, &netmask, &line_error))
goto handle_line_error;
prefix = nm_utils_ip4_netmask_to_prefix (netmask);
if (params[3]) {
if (!args_params_parse_ip4 (params, 3, TRUE, &gateway, &line_error))
goto handle_line_error;
if (params[4]) {
if (!args_params_parse_int64 (params, 4, 0, G_MAXUINT32, &v_int64, &line_error))
goto handle_line_error;
metric = (guint32) v_int64;
}
}
}
if (prefix == 0 && network == 0) {
/* the default-route cannot be specified as normal route in NMSettingIPConfig.
* Just set never-default=FALSE (which is already the default). */
g_object_set (s_ip4,
NM_SETTING_IP_CONFIG_NEVER_DEFAULT,
FALSE,
NULL);
continue;
}
{
#ifdef NM_VPN_OLD
NMIP4Route *route;
route = nm_ip4_route_new ();
nm_ip4_route_set_dest (route, network);
nm_ip4_route_set_prefix (route, prefix);
nm_ip4_route_set_next_hop (route, gateway);
if (metric >= 0)
nm_ip4_route_set_metric (route, metric);
nm_setting_ip4_config_add_route (s_ip4, route);
nm_ip4_route_unref (route);
#else
NMIPRoute *route;
route = nm_ip_route_new_binary (AF_INET, &network, prefix, params[3] ? &gateway : NULL, metric, NULL);
nm_setting_ip_config_add_route (s_ip4, route);
nm_ip_route_unref (route);
#endif
}
}
if (params[0][0] == '<' && params[0][strlen (params[0]) - 1] == '>') {
gs_free char *token = g_strndup (&params[0][1], strlen (params[0]) - 2);
gs_free char *end_token = NULL;
gsize end_token_len;
gsize my_contents_cur_line = contents_cur_line;
gboolean is_base64 = FALSE;
char *f_path;
const char *key;
GString *blob_data;
InlineBlobData *inline_blob_data;
if (nm_streq (token, INLINE_BLOB_CA))
key = NM_OPENVPN_KEY_CA;
else if (nm_streq (token, INLINE_BLOB_CERT))
key = NM_OPENVPN_KEY_CERT;
else if (nm_streq (token, INLINE_BLOB_KEY))
key = NM_OPENVPN_KEY_KEY;
else if (nm_streq (token, INLINE_BLOB_PKCS12)) {
is_base64 = TRUE;
key = NULL;
} else if (nm_streq (token, INLINE_BLOB_TLS_CRYPT))
key = NM_OPENVPN_KEY_TLS_CRYPT;
else if (nm_streq (token, INLINE_BLOB_TLS_AUTH)) {
key = NM_OPENVPN_KEY_TA;
allow_ta_direction = TRUE;
} else if (nm_streq (token, INLINE_BLOB_SECRET)) {
key = NM_OPENVPN_KEY_STATIC_KEY;
allow_secret_direction = TRUE;
} else {
line_error = g_strdup_printf (_("unsupported blob/xml element"));
goto handle_line_error;
}
end_token = g_strdup_printf ("</%s>", token);
end_token_len = strlen (end_token);
blob_data = g_string_new (NULL);
while (args_next_line (&contents,
&contents_len,
&cur_line,
&cur_line_len,
&cur_line_delimiter)) {
my_contents_cur_line++;
/* skip over trailing space like openvpn does. */
_ch_skip_over_leading_whitespace (&cur_line, &cur_line_len);
if (!strncmp (cur_line, end_token, end_token_len)) {
end_token_len = 0;
break;
}
g_string_append_len (blob_data, cur_line, cur_line_len);
if (cur_line_delimiter)
g_string_append_c (blob_data, cur_line_delimiter[0]);
}
if (end_token_len) {
line_error = g_strdup_printf (_("unterminated blob element <%s>"), token);
g_string_free (blob_data, TRUE);
goto handle_line_error;
}
if (is_base64) {
gs_free guint8 *d = NULL;
gsize l;
d = g_base64_decode (blob_data->str, &l);
g_string_truncate (blob_data, 0);
g_string_append_len (blob_data, (const char *) d, l);
}
/* the latest cert wins... */
for (sl_iter = inline_blobs; sl_iter; sl_iter = sl_iter->next) {
InlineBlobData *d = sl_iter->data;
if (nm_streq (d->token, token)) {
inline_blobs = g_slist_delete_link (inline_blobs, sl_iter);
inline_blob_data_free (d);
break;
}
}
f_path = inline_blob_construct_path (basename, token);
inline_blob_data = g_slice_new (InlineBlobData);
inline_blob_data->blob_data = blob_data;
inline_blob_data->token_start_line = contents_cur_line;
inline_blob_data->path = f_path;
inline_blob_data->token = token;
inline_blob_data->key = key;
token = NULL;
inline_blobs = g_slist_prepend (inline_blobs, inline_blob_data);
contents_cur_line = my_contents_cur_line;
if (key)
setting_vpn_add_data_item_path (s_vpn, key, f_path);
else {
nm_assert (nm_streq (token, INLINE_BLOB_PKCS12));
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_CA, f_path);
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_CERT, f_path);
setting_vpn_add_data_item_path (s_vpn, NM_OPENVPN_KEY_KEY, f_path);
}
continue;
}
/* TODO: currently we ignore any unknown options and skip over them. */
continue;
handle_line_error:
g_set_error (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FILE_INVALID,
_("configuration error: %s (line %ld)"),
line_error ? : _("unknown or unsupported option"),
(long) contents_cur_line);
g_free (line_error);
goto out_error;
}
if (allow_secret_direction && secret_direction)
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_STATIC_KEY_DIRECTION, secret_direction);
if (allow_ta_direction && ta_direction)
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_TA_DIR, ta_direction);
if (!have_client && !have_sk) {
g_set_error_literal (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FILE_NOT_VPN,
_("The file to import wasnt a valid OpenVPN client configuration"));
goto out_error;
}
if (!have_remote) {
g_set_error_literal (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FILE_NOT_VPN,
_("The file to import wasnt a valid OpenVPN configure (no remote)"));
goto out_error;
}
have_certs = FALSE;
have_ca = FALSE;
if (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA))
have_ca = TRUE;
if ( have_ca
&& nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT)
&& nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY))
have_certs = TRUE;
/* Determine connection type */
if (have_pass) {
if (have_certs)
ctype = NM_OPENVPN_CONTYPE_PASSWORD_TLS;
else if (have_ca)
ctype = NM_OPENVPN_CONTYPE_PASSWORD;
} else if (have_certs) {
ctype = NM_OPENVPN_CONTYPE_TLS;
} else if (have_sk)
ctype = NM_OPENVPN_CONTYPE_STATIC_KEY;
if (!ctype)
ctype = NM_OPENVPN_CONTYPE_TLS;
setting_vpn_add_data_item (s_vpn, NM_OPENVPN_KEY_CONNECTION_TYPE, ctype);
/* Default secret flags to be agent-owned */
if (have_pass) {
nm_setting_set_secret_flags (NM_SETTING (s_vpn),
NM_OPENVPN_KEY_PASSWORD,
NM_SETTING_SECRET_FLAG_AGENT_OWNED,
NULL);
}
if (have_certs) {
gs_free char *key_path_free = NULL;
const char *key_path;
key_path = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
if (is_encrypted (nm_utils_str_utf8safe_unescape (key_path, &key_path_free))) {
/* If there should be a private key password, default it to
* being agent-owned.
*/
nm_setting_set_secret_flags (NM_SETTING (s_vpn),
NM_OPENVPN_KEY_CERTPASS,
NM_SETTING_SECRET_FLAG_AGENT_OWNED,
NULL);
}
}
inline_blobs = g_slist_reverse (inline_blobs);
for (sl_iter = inline_blobs; sl_iter; sl_iter = sl_iter->next) {
const InlineBlobData *data = sl_iter->data;
/* Check whether the setting was not overwritten by a later entry in the config-file. */
if (nm_streq (data->token, INLINE_BLOB_PKCS12)) {
if ( !setting_vpn_eq_data_item_utf8safe (s_vpn, NM_OPENVPN_KEY_CA, data->path)
&& !setting_vpn_eq_data_item_utf8safe (s_vpn, NM_OPENVPN_KEY_CERT, data->path)
&& !setting_vpn_eq_data_item_utf8safe (s_vpn, NM_OPENVPN_KEY_KEY, data->path))
continue;
} else {
if (!setting_vpn_eq_data_item_utf8safe (s_vpn, data->key, data->path))
continue;
}
if (!inline_blob_write_out (sl_iter->data, error))
goto out_error;
}
g_slist_free_full (inline_blobs, (GDestroyNotify) inline_blob_data_free);
connection_free = NULL;
g_return_val_if_fail (!error || !*error, connection);
return connection;
out_error:
g_slist_free_full (inline_blobs, (GDestroyNotify) inline_blob_data_free);
g_return_val_if_fail (!error || *error, NULL);
return NULL;
}
/*****************************************************************************/
static const char *
escape_arg (const char *value, char **buf)
{
const char *s;
char *result, *i_result;
gboolean has_single_quote = FALSE;
gboolean needs_quotation = FALSE;
gsize len;
nm_assert (value);
nm_assert (buf && !*buf);
if (value[0] == '\0')
return (*buf = g_strdup ("''"));
/* check if the string contains only benign characters... */
len = 0;
for (s = value; s[0]; s++) {
char c = s[0];
len++;
if ( (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| NM_IN_SET (c, '_', '-', ':', '/'))
continue;
needs_quotation = TRUE;
if (c == '\'')
has_single_quote = TRUE;
}
if (!needs_quotation)
return value;
if (!has_single_quote) {
result = g_malloc (len + 2 + 1);
result[0] = '\'';
memcpy (&result[1], value, len);
result[1 + len] = '\'';
result[2 + len] = '\0';
} else {
i_result = result = g_malloc (len * 2 + 3);
*(i_result++) = '"';
for (s = value; s[0]; s++) {
if (NM_IN_SET (s[0], '\\', '"'))
*(i_result++) = '\\';
*(i_result++) = s[0];
}
*(i_result++) = '"';
*(i_result++) = '\0';
}
*buf = result;
return result;
}
static void
args_write_line_v (GString *f, gsize nargs, const char **args)
{
gsize i;
gboolean printed = FALSE;
nm_assert (args);
nm_assert (args[0]);
for (i = 0; i < nargs; i++) {
gs_free char *tmp = NULL;
/* NULL is skipped. This is for convenience to specify
* optional arguments. */
if (!args[i])
continue;
if (printed)
g_string_append_c (f, ' ');
printed = TRUE;
g_string_append (f, escape_arg (args[i], &tmp));
}
g_string_append_c (f, '\n');
}
#define args_write_line(f, ...) args_write_line_v(f, NM_NARG (__VA_ARGS__), (const char *[]) { __VA_ARGS__ })
static void
args_write_line_int64 (GString *f, const char *key, gint64 value)
{
char tmp[64];
args_write_line (f, key, nm_sprintf_buf (tmp, "%"G_GINT64_FORMAT, value));
}
static void
args_write_line_setting_value_int (GString *f,
const char *tag_key,
NMSettingVpn *s_vpn,
const char *setting_key)
{
const char *value;
gint64 v;
nm_assert (tag_key && tag_key[0]);
nm_assert (NM_IS_SETTING_VPN (s_vpn));
nm_assert (setting_key && setting_key[0]);
value = nm_setting_vpn_get_data_item (s_vpn, setting_key);
if (!_arg_is_set (value))
return;
v = _nm_utils_ascii_str_to_int64 (value, 10, G_MININT64, G_MAXINT64, 0);
if (errno)
return;
args_write_line_int64 (f, tag_key, v);
}
static void
args_write_line_setting_value (GString *f,
const char *tag_key,
NMSettingVpn *s_vpn,
const char *setting_key)
{
const char *value;
value = nm_setting_vpn_get_data_item (s_vpn, setting_key);
if (_arg_is_set (value))
args_write_line (f, tag_key, value);
}
/*****************************************************************************/
static GString *
do_export_create (NMConnection *connection, const char *path, GError **error)
{
NMSettingConnection *s_con;
NMSettingIPConfig *s_ip4;
NMSettingVpn *s_vpn;
const char *value;
const char *gateways;
char **gw_list, **gw_iter;
const char *connection_type;
const char *local_ip = NULL;
const char *remote_ip = NULL;
const char *proxy_type = NULL;
guint i, num;
nm_auto(_auto_free_gstring_p) GString *f = NULL;
if (!path || !path[0]) {
g_set_error_literal (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FILE_NOT_VPN,
_("missing path argument"));
return NULL;
}
s_con = nm_connection_get_setting_connection (connection);
s_vpn = nm_connection_get_setting_vpn (connection);
if (!s_con || !s_vpn) {
g_set_error_literal (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FILE_NOT_VPN,
_("connection is not a valid OpenVPN connection"));
return NULL;
}
gateways = _arg_is_set (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE));
if (!gateways) {
g_set_error_literal (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FILE_NOT_VPN,
_("connection was incomplete (missing gateway)"));
return NULL;
}
connection_type = _arg_is_set (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CONNECTION_TYPE));
f = g_string_sized_new (512);
args_write_line (f, NMV_OVPN_TAG_CLIENT);
/* 'remote' */
gw_list = g_strsplit_set (gateways, " \t,", 0);
for (gw_iter = gw_list; gw_iter && *gw_iter; gw_iter++) {
gs_free char *str_free = NULL;
const char *host, *port, *proto;
gssize eidx;
eidx = nmovpn_remote_parse (*gw_iter,
&str_free,
&host,
&port,
&proto,
NULL);
if (eidx >= 0)
continue;
args_write_line (f,
NMV_OVPN_TAG_REMOTE,
host,
port ?: (proto ? "1194" : NULL),
proto);
}
g_strfreev (gw_list);
if (nm_streq0 (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_RANDOM), "yes"))
args_write_line (f, NMV_OVPN_TAG_REMOTE_RANDOM);
if (nm_streq0 (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TUN_IPV6), "yes"))
args_write_line (f, NMV_OVPN_TAG_TUN_IPV6);
{
gs_free char *cacert_free = NULL, *user_cert_free = NULL, *private_key_free = NULL;
const char *cacert = NULL, *user_cert = NULL, *private_key = NULL;
if (NM_IN_STRSET (connection_type, NM_OPENVPN_CONTYPE_TLS,
NM_OPENVPN_CONTYPE_PASSWORD,
NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA);
if (_arg_is_set (value))
cacert = nm_utils_str_utf8safe_unescape (value, &cacert_free);
}
if (NM_IN_STRSET (connection_type, NM_OPENVPN_CONTYPE_TLS,
NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT);
if (_arg_is_set (value))
user_cert = nm_utils_str_utf8safe_unescape (value, &user_cert_free);
value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
if (_arg_is_set (value))
private_key = nm_utils_str_utf8safe_unescape (value, &private_key_free);
}
if ( cacert && user_cert && private_key
&& nm_streq (cacert, user_cert) && nm_streq (cacert, private_key)) {
/* Handle PKCS#12 (all certs are the same file) */
args_write_line (f, NMV_OVPN_TAG_PKCS12, cacert);
} else {
if (cacert)
args_write_line (f, NMV_OVPN_TAG_CA, cacert);
if (user_cert)
args_write_line (f, NMV_OVPN_TAG_CERT, user_cert);
if (private_key)
args_write_line (f, NMV_OVPN_TAG_KEY, private_key);
}
}
if (NM_IN_STRSET (connection_type, NM_OPENVPN_CONTYPE_PASSWORD,
NM_OPENVPN_CONTYPE_PASSWORD_TLS))
args_write_line (f, NMV_OVPN_TAG_AUTH_USER_PASS);
if (NM_IN_STRSET (connection_type, NM_OPENVPN_CONTYPE_STATIC_KEY)) {
value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_STATIC_KEY);
if (_arg_is_set (value)) {
gs_free char *s_free = NULL;
args_write_line (f,
NMV_OVPN_TAG_SECRET,
nm_utils_str_utf8safe_unescape (value, &s_free),
_arg_is_set (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_STATIC_KEY_DIRECTION)));
}
}
args_write_line_setting_value_int (f, NMV_OVPN_TAG_RENEG_SEC, s_vpn, NM_OPENVPN_KEY_RENEG_SECONDS);
args_write_line_setting_value_int (f, NMV_OVPN_TAG_MAX_ROUTES, s_vpn, NM_OPENVPN_KEY_MAX_ROUTES);
args_write_line_setting_value (f, NMV_OVPN_TAG_CIPHER, s_vpn, NM_OPENVPN_KEY_CIPHER);
args_write_line_setting_value (f, NMV_OVPN_TAG_TLS_CIPHER, s_vpn, NM_OPENVPN_KEY_TLS_CIPHER);
args_write_line_setting_value_int (f, NMV_OVPN_TAG_KEYSIZE, s_vpn, NM_OPENVPN_KEY_KEYSIZE);
value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_COMP_LZO);
if (value) {
if (nm_streq (value, "no-by-default"))
value = "no";
args_write_line (f, NMV_OVPN_TAG_COMP_LZO, value);
}
if (nm_streq0 (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_FLOAT), "yes"))
args_write_line (f, NMV_OVPN_TAG_FLOAT);
value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_MSSFIX);
if (nm_streq0 (value, "yes"))
args_write_line (f, NMV_OVPN_TAG_MSSFIX);
else if (value)
args_write_line_setting_value_int (f, NMV_OVPN_TAG_MSSFIX, s_vpn, NM_OPENVPN_KEY_MSSFIX);
args_write_line_setting_value (f, NMV_OVPN_TAG_MTU_DISC, s_vpn, NM_OPENVPN_KEY_MTU_DISC);
args_write_line_setting_value_int (f, NMV_OVPN_TAG_TUN_MTU, s_vpn, NM_OPENVPN_KEY_TUNNEL_MTU);
args_write_line_setting_value_int (f, NMV_OVPN_TAG_FRAGMENT, s_vpn, NM_OPENVPN_KEY_FRAGMENT_SIZE);
{
gs_free char *device_free = NULL;
const char *device_type, *device;
device_type = _arg_is_set (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_DEV_TYPE));
device = _arg_is_set (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_DEV));
device = nm_utils_str_utf8safe_unescape (device, &device_free);
args_write_line (f,
NMV_OVPN_TAG_DEV,
device ?:
(device_type ?:
(nm_streq0 (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TAP_DEV), "yes")
? "tap" : "tun")));
if (device_type)
args_write_line (f, NMV_OVPN_TAG_DEV_TYPE, device_type);
}
args_write_line (f,
NMV_OVPN_TAG_PROTO,
nm_streq0 (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROTO_TCP), "yes")
? "tcp" : "udp");
args_write_line_setting_value_int (f, NMV_OVPN_TAG_PORT, s_vpn, NM_OPENVPN_KEY_PORT);
args_write_line_setting_value_int (f, NMV_OVPN_TAG_PING, s_vpn, NM_OPENVPN_KEY_PING);
args_write_line_setting_value_int (f, NMV_OVPN_TAG_PING_EXIT, s_vpn, NM_OPENVPN_KEY_PING_EXIT);
args_write_line_setting_value_int (f, NMV_OVPN_TAG_PING_RESTART, s_vpn, NM_OPENVPN_KEY_PING_RESTART);
local_ip = _arg_is_set (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_LOCAL_IP));
remote_ip = _arg_is_set (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_REMOTE_IP));
if (local_ip && remote_ip)
args_write_line (f, NMV_OVPN_TAG_IFCONFIG, local_ip, remote_ip);
if (NM_IN_STRSET (connection_type,
NM_OPENVPN_CONTYPE_TLS,
NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
const char *x509_name, *key;
args_write_line_setting_value (f, NMV_OVPN_TAG_REMOTE_CERT_TLS, s_vpn, NM_OPENVPN_KEY_REMOTE_CERT_TLS);
args_write_line_setting_value (f, NMV_OVPN_TAG_NS_CERT_TYPE, s_vpn, NM_OPENVPN_KEY_NS_CERT_TYPE);
args_write_line_setting_value (f, NMV_OVPN_TAG_TLS_REMOTE, s_vpn, NM_OPENVPN_KEY_TLS_REMOTE);
x509_name = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_VERIFY_X509_NAME);
if (_arg_is_set (x509_name)) {
const char *name;
gs_free char *type = NULL;
name = strchr (x509_name, ':');
if (name) {
type = g_strndup (x509_name, name - x509_name);
name++;
} else
name = x509_name;
args_write_line (f, NMV_OVPN_TAG_VERIFY_X509_NAME, name, type);
}
key = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TA);
if (_arg_is_set (key)) {
gs_free char *s_free = NULL;
args_write_line (f,
NMV_OVPN_TAG_TLS_AUTH,
nm_utils_str_utf8safe_unescape (key, &s_free),
_arg_is_set (nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TA_DIR)));
}
key = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_TLS_CRYPT);
if (_arg_is_set (key)) {
gs_free char *s_free = NULL;
args_write_line (f,
NMV_OVPN_TAG_TLS_CRYPT,
nm_utils_str_utf8safe_unescape (key, &s_free));
}
}
proxy_type = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_TYPE);
if (_arg_is_set (proxy_type)) {
const char *proxy_server;
const char *proxy_port;
const char *proxy_retry;
const char *proxy_username;
const char *proxy_password;
proxy_server = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_SERVER);
proxy_port = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_PORT);
proxy_retry = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_PROXY_RETRY);
proxy_username = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_USERNAME);
proxy_password = nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_HTTP_PROXY_PASSWORD);
if (nm_streq (proxy_type, "http") && proxy_server) {
char *authfile, *authcontents, *base, *dirname;
if (!proxy_port)
proxy_port = "8080";
/* If there's a username, need to write an authfile */
base = g_path_get_basename (path);
dirname = g_path_get_dirname (path);
authfile = g_strdup_printf ("%s/%s-httpauthfile", dirname, base);
g_free (base);
g_free (dirname);
args_write_line (f,
NMV_OVPN_TAG_HTTP_PROXY,
proxy_server,
proxy_port,
proxy_username ? authfile : NULL);
if (proxy_retry && !strcmp (proxy_retry, "yes"))
args_write_line (f, NMV_OVPN_TAG_HTTP_PROXY_RETRY);
/* Write out the authfile */
if (proxy_username) {
authcontents = g_strdup_printf ("%s\n%s\n",
proxy_username,
proxy_password ? proxy_password : "");
g_file_set_contents (authfile, authcontents, -1, NULL);
g_free (authcontents);
}
g_free (authfile);
} else if (nm_streq (proxy_type, "socks") && proxy_server) {
if (!proxy_port)
proxy_port = "1080";
args_write_line (f, NMV_OVPN_TAG_SOCKS_PROXY, proxy_server, proxy_port);
if (proxy_retry && !strcmp (proxy_retry, "yes"))
args_write_line (f, NMV_OVPN_TAG_SOCKS_PROXY_RETRY);
}
}
s_ip4 = nm_connection_get_setting_ip4_config (connection);
if (s_ip4) {
#ifdef NM_VPN_OLD
num = nm_setting_ip4_config_get_num_routes (s_ip4);
#else
num = nm_setting_ip_config_get_num_routes (s_ip4);
#endif
for (i = 0; i < num; i++) {
char netmask_str[INET_ADDRSTRLEN] = { 0 };
const char *next_hop_str, *dest_str;
in_addr_t netmask;
guint prefix;
guint64 metric;
char metric_buf[50];
#ifdef NM_VPN_OLD
char next_hop_str_buf[INET_ADDRSTRLEN] = { 0 };
char dest_str_buf[INET_ADDRSTRLEN] = { 0 };
in_addr_t dest, next_hop;
NMIP4Route *route = nm_setting_ip4_config_get_route (s_ip4, i);
dest = nm_ip4_route_get_dest (route);
inet_ntop (AF_INET, (const void *) &dest, dest_str_buf, sizeof (dest_str_buf));
dest_str = dest_str_buf;
next_hop = nm_ip4_route_get_next_hop (route);
inet_ntop (AF_INET, (const void *) &next_hop, next_hop_str_buf, sizeof (next_hop_str_buf));
next_hop_str = next_hop_str_buf;
prefix = nm_ip4_route_get_prefix (route);
metric = nm_ip4_route_get_metric (route);
#else
NMIPRoute *route = nm_setting_ip_config_get_route (s_ip4, i);
dest_str = nm_ip_route_get_dest (route);
next_hop_str = nm_ip_route_get_next_hop (route) ? : "0.0.0.0",
prefix = nm_ip_route_get_prefix (route);
metric = nm_ip_route_get_metric (route);
#endif
netmask = nm_utils_ip4_prefix_to_netmask (prefix);
inet_ntop (AF_INET, (const void *) &netmask, netmask_str, sizeof (netmask_str));
args_write_line (f,
NMV_OVPN_TAG_ROUTE,
dest_str,
netmask_str,
next_hop_str,
metric == -1 ? NULL : nm_sprintf_buf (metric_buf, "%u", (unsigned) metric));
}
}
/* Add hard-coded stuff */
args_write_line (f, NMV_OVPN_TAG_NOBIND);
args_write_line (f, NMV_OVPN_TAG_AUTH_NOCACHE);
args_write_line (f, NMV_OVPN_TAG_SCRIPT_SECURITY, "2");
args_write_line (f, NMV_OVPN_TAG_PERSIST_KEY);
args_write_line (f, NMV_OVPN_TAG_PERSIST_TUN);
args_write_line (f, NMV_OVPN_TAG_USER, NM_OPENVPN_USER);
args_write_line (f, NMV_OVPN_TAG_GROUP, NM_OPENVPN_GROUP);
return g_steal_pointer (&f);
}
gboolean
do_export (const char *path, NMConnection *connection, GError **error)
{
nm_auto(_auto_free_gstring_p) GString *f = NULL;
gs_free_error GError *local = NULL;
f = do_export_create (connection, path, error);
if (!f)
return FALSE;
if (!g_file_set_contents (path, f->str, f->len, &local)) {
g_set_error (error,
NMV_EDITOR_PLUGIN_ERROR,
NMV_EDITOR_PLUGIN_ERROR_FILE_NOT_VPN,
_("failed to write file: %s"),
local->message);
return FALSE;
}
return TRUE;
}