/* -*- 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 and Red Hat, Inc. * **************************************************************************/ #include "nm-default.h" #include "import-export.h" #include #include #include #include #include #include #include #include #include #include #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; } /*****************************************************************************/ 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, ¶ms, &line_error)) goto handle_line_error; if (!params) { /* empty line of comments. */ continue; } g_assert (params[0]); /* allow for a leading double-dash and skip over it (bypass_doubledash). */ if (g_str_has_prefix (params[0], "--")) params[0] = ¶ms[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 (¶ms[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 ("", 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 wasn’t 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 wasn’t 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; }