diff options
| author | Michael Biebl <biebl@debian.org> | 2020-10-05 22:27:18 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2020-10-05 22:27:18 +0200 |
| commit | aafc1dbe4712c86189bbc1d4d54ad8cb4c69be7e (patch) | |
| tree | a2a9bb4d007339a0b1304540388230ccedac32bd /clients/common/nm-vpn-helpers.c | |
| parent | e7b44ef4c80907346ec7492a09c45277459924fc (diff) | |
New upstream version 1.27.90 upstream/1.27.90
Diffstat (limited to 'clients/common/nm-vpn-helpers.c')
| -rw-r--r-- | clients/common/nm-vpn-helpers.c | 1500 |
1 files changed, 746 insertions, 754 deletions
diff --git a/clients/common/nm-vpn-helpers.c b/clients/common/nm-vpn-helpers.c index 74ff52bb..8312043f 100644 --- a/clients/common/nm-vpn-helpers.c +++ b/clients/common/nm-vpn-helpers.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (C) 2013 - 2015 Red Hat, Inc. */ @@ -23,807 +23,799 @@ /*****************************************************************************/ NMVpnEditorPlugin * -nm_vpn_get_editor_plugin (const char *service_type, GError **error) +nm_vpn_get_editor_plugin(const char *service_type, GError **error) { - NMVpnEditorPlugin *plugin = NULL; - NMVpnPluginInfo *plugin_info; - gs_free_error GError *local = NULL; - - g_return_val_if_fail (service_type, NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - plugin_info = nm_vpn_plugin_info_list_find_by_service (nm_vpn_get_plugin_infos (), service_type); - - if (!plugin_info) { - g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED, - _("unknown VPN plugin \"%s\""), service_type); - return NULL; - } - plugin = nm_vpn_plugin_info_get_editor_plugin (plugin_info); - if (!plugin) - plugin = nm_vpn_plugin_info_load_editor_plugin (plugin_info, &local); - - if (!plugin) { - if ( !nm_vpn_plugin_info_get_plugin (plugin_info) - && nm_vpn_plugin_info_lookup_property (plugin_info, NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "properties")) { - g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED, - _("cannot load legacy-only VPN plugin \"%s\" for \"%s\""), - nm_vpn_plugin_info_get_name (plugin_info), - nm_vpn_plugin_info_get_filename (plugin_info)); - } else if (g_error_matches (local, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { - g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED, - _("cannot load VPN plugin \"%s\" due to missing \"%s\". Missing client plugin?"), - nm_vpn_plugin_info_get_name (plugin_info), - nm_vpn_plugin_info_get_plugin (plugin_info)); - } else { - g_set_error (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED, - _("failed to load VPN plugin \"%s\": %s"), - nm_vpn_plugin_info_get_name (plugin_info), - local->message); - } - return NULL; - } - - return plugin; + NMVpnEditorPlugin *plugin = NULL; + NMVpnPluginInfo * plugin_info; + gs_free_error GError *local = NULL; + + g_return_val_if_fail(service_type, NULL); + g_return_val_if_fail(error == NULL || *error == NULL, NULL); + + plugin_info = nm_vpn_plugin_info_list_find_by_service(nm_vpn_get_plugin_infos(), service_type); + + if (!plugin_info) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("unknown VPN plugin \"%s\""), + service_type); + return NULL; + } + plugin = nm_vpn_plugin_info_get_editor_plugin(plugin_info); + if (!plugin) + plugin = nm_vpn_plugin_info_load_editor_plugin(plugin_info, &local); + + if (!plugin) { + if (!nm_vpn_plugin_info_get_plugin(plugin_info) + && nm_vpn_plugin_info_lookup_property(plugin_info, + NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, + "properties")) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("cannot load legacy-only VPN plugin \"%s\" for \"%s\""), + nm_vpn_plugin_info_get_name(plugin_info), + nm_vpn_plugin_info_get_filename(plugin_info)); + } else if (g_error_matches(local, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { + g_set_error( + error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("cannot load VPN plugin \"%s\" due to missing \"%s\". Missing client plugin?"), + nm_vpn_plugin_info_get_name(plugin_info), + nm_vpn_plugin_info_get_plugin(plugin_info)); + } else { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("failed to load VPN plugin \"%s\": %s"), + nm_vpn_plugin_info_get_name(plugin_info), + local->message); + } + return NULL; + } + + return plugin; } GSList * -nm_vpn_get_plugin_infos (void) +nm_vpn_get_plugin_infos(void) { - static bool plugins_loaded; - static GSList *plugins = NULL; - - if (G_LIKELY (plugins_loaded)) - return plugins; - plugins_loaded = TRUE; - plugins = nm_vpn_plugin_info_list_load (); - return plugins; + static bool plugins_loaded; + static GSList *plugins = NULL; + + if (G_LIKELY(plugins_loaded)) + return plugins; + plugins_loaded = TRUE; + plugins = nm_vpn_plugin_info_list_load(); + return plugins; } gboolean -nm_vpn_supports_ipv6 (NMConnection *connection) +nm_vpn_supports_ipv6(NMConnection *connection) { - NMSettingVpn *s_vpn; - const char *service_type; - NMVpnEditorPlugin *plugin; - guint32 capabilities; + NMSettingVpn * s_vpn; + const char * service_type; + NMVpnEditorPlugin *plugin; + guint32 capabilities; - s_vpn = nm_connection_get_setting_vpn (connection); - g_return_val_if_fail (s_vpn != NULL, FALSE); + s_vpn = nm_connection_get_setting_vpn(connection); + g_return_val_if_fail(s_vpn != NULL, FALSE); - service_type = nm_setting_vpn_get_service_type (s_vpn); - if (!service_type) - return FALSE; + service_type = nm_setting_vpn_get_service_type(s_vpn); + if (!service_type) + return FALSE; - plugin = nm_vpn_get_editor_plugin (service_type, NULL); - if (!plugin) - return FALSE; + plugin = nm_vpn_get_editor_plugin(service_type, NULL); + if (!plugin) + return FALSE; - capabilities = nm_vpn_editor_plugin_get_capabilities (plugin); - return NM_FLAGS_HAS (capabilities, NM_VPN_EDITOR_PLUGIN_CAPABILITY_IPV6); + capabilities = nm_vpn_editor_plugin_get_capabilities(plugin); + return NM_FLAGS_HAS(capabilities, NM_VPN_EDITOR_PLUGIN_CAPABILITY_IPV6); } const NmcVpnPasswordName * -nm_vpn_get_secret_names (const char *service_type) +nm_vpn_get_secret_names(const char *service_type) { - const char *type; - - if (!service_type) - return NULL; - - if ( !NM_STR_HAS_PREFIX (service_type, NM_DBUS_INTERFACE) - || service_type[NM_STRLEN (NM_DBUS_INTERFACE)] != '.') { - /* all our well-known, hard-coded vpn-types start with NM_DBUS_INTERFACE. */ - return NULL; - } - - type = service_type + (NM_STRLEN (NM_DBUS_INTERFACE) + 1); - -#define _VPN_PASSWORD_LIST(...) \ - ({ \ - static const NmcVpnPasswordName _arr[] = { \ - __VA_ARGS__ \ - { 0 }, \ - }; \ - _arr; \ - }) - - if (NM_IN_STRSET (type, "pptp", - "iodine", - "ssh", - "l2tp", - "fortisslvpn")) { - return _VPN_PASSWORD_LIST ( - { "password", N_("Password") }, - ); - } - - if (NM_IN_STRSET (type, "openvpn")) { - return _VPN_PASSWORD_LIST ( - { "password", N_("Password") }, - { "cert-pass", N_("Certificate password") }, - { "http-proxy-password", N_("HTTP proxy password") }, - ); - } - - if (NM_IN_STRSET (type, "vpnc")) { - return _VPN_PASSWORD_LIST ( - { "Xauth password", N_("Password") }, - { "IPSec secret", N_("Group password") }, - ); - }; - - if (NM_IN_STRSET (type, "openswan", - "libreswan", - "strongswan")) { - return _VPN_PASSWORD_LIST ( - { "xauthpassword", N_("Password") }, - { "pskvalue", N_("Group password") }, - ); - }; - - if (NM_IN_STRSET (type, "openconnect")) { - return _VPN_PASSWORD_LIST ( - { "gateway", N_("Gateway") }, - { "cookie", N_("Cookie") }, - { "gwcert", N_("Gateway certificate hash") }, - ); - }; - - return NULL; + const char *type; + + if (!service_type) + return NULL; + + if (!NM_STR_HAS_PREFIX(service_type, NM_DBUS_INTERFACE) + || service_type[NM_STRLEN(NM_DBUS_INTERFACE)] != '.') { + /* all our well-known, hard-coded vpn-types start with NM_DBUS_INTERFACE. */ + return NULL; + } + + type = service_type + (NM_STRLEN(NM_DBUS_INTERFACE) + 1); + +#define _VPN_PASSWORD_LIST(...) \ + ({ \ + static const NmcVpnPasswordName _arr[] = { \ + __VA_ARGS__{0}, \ + }; \ + _arr; \ + }) + + if (NM_IN_STRSET(type, "pptp", "iodine", "ssh", "l2tp", "fortisslvpn")) { + return _VPN_PASSWORD_LIST({"password", N_("Password")}, ); + } + + if (NM_IN_STRSET(type, "openvpn")) { + return _VPN_PASSWORD_LIST({"password", N_("Password")}, + {"cert-pass", N_("Certificate password")}, + {"http-proxy-password", N_("HTTP proxy password")}, ); + } + + if (NM_IN_STRSET(type, "vpnc")) { + return _VPN_PASSWORD_LIST({"Xauth password", N_("Password")}, + {"IPSec secret", N_("Group password")}, ); + }; + + if (NM_IN_STRSET(type, "openswan", "libreswan", "strongswan")) { + return _VPN_PASSWORD_LIST({"xauthpassword", N_("Password")}, + {"pskvalue", N_("Group password")}, ); + }; + + if (NM_IN_STRSET(type, "openconnect")) { + return _VPN_PASSWORD_LIST({"gateway", N_("Gateway")}, + {"cookie", N_("Cookie")}, + {"gwcert", N_("Gateway certificate hash")}, ); + }; + + return NULL; } static gboolean -_extract_variable_value (char *line, const char *tag, char **value) +_extract_variable_value(char *line, const char *tag, char **value) { - char *p1, *p2; - - if (!g_str_has_prefix (line, tag)) - return FALSE; - - p1 = line + strlen (tag); - p2 = line + strlen (line) - 1; - if ((*p1 == '\'' || *p1 == '"') && (*p1 == *p2)) { - p1++; - *p2 = '\0'; - } - NM_SET_OUT (value, g_strdup (p1)); - return TRUE; + char *p1, *p2; + + if (!g_str_has_prefix(line, tag)) + return FALSE; + + p1 = line + strlen(tag); + p2 = line + strlen(line) - 1; + if ((*p1 == '\'' || *p1 == '"') && (*p1 == *p2)) { + p1++; + *p2 = '\0'; + } + NM_SET_OUT(value, g_strdup(p1)); + return TRUE; } gboolean -nm_vpn_openconnect_authenticate_helper (const char *host, - char **cookie, - char **gateway, - char **gwcert, - int *status, - GError **error) +nm_vpn_openconnect_authenticate_helper(const char *host, + char ** cookie, + char ** gateway, + char ** gwcert, + int * status, + GError ** error) { - gs_free char *output = NULL; - gs_free const char **output_v = NULL; - const char *const*iter; - const char *path; - const char *const DEFAULT_PATHS[] = { - "/sbin/", - "/usr/sbin/", - "/usr/local/sbin/", - "/bin/", - "/usr/bin/", - "/usr/local/bin/", - NULL, - }; - - path = nm_utils_file_search_in_paths ("openconnect", "/usr/sbin/openconnect", DEFAULT_PATHS, - G_FILE_TEST_IS_EXECUTABLE, NULL, NULL, error); - if (!path) - return FALSE; - - if (!g_spawn_sync (NULL, - (char **) NM_MAKE_STRV (path, "--authenticate", host), - NULL, - G_SPAWN_SEARCH_PATH - | G_SPAWN_CHILD_INHERITS_STDIN, - NULL, - NULL, - &output, - NULL, - status, - error)) - return FALSE; - - /* Parse output and set cookie, gateway and gwcert - * output example: - * COOKIE='loremipsum' - * HOST='1.2.3.4' - * FINGERPRINT='sha1:32bac90cf09a722e10ecc1942c67fe2ac8c21e2e' - */ - output_v = nm_utils_strsplit_set_with_empty (output, "\r\n"); - for (iter = output_v; iter && *iter; iter++) { - char *s_mutable = (char *) *iter; - - _extract_variable_value (s_mutable, "COOKIE=", cookie); - _extract_variable_value (s_mutable, "HOST=", gateway); - _extract_variable_value (s_mutable, "FINGERPRINT=", gwcert); - } - - return TRUE; + gs_free char * output = NULL; + gs_free const char **output_v = NULL; + const char *const * iter; + const char * path; + const char *const DEFAULT_PATHS[] = { + "/sbin/", + "/usr/sbin/", + "/usr/local/sbin/", + "/bin/", + "/usr/bin/", + "/usr/local/bin/", + NULL, + }; + + path = nm_utils_file_search_in_paths("openconnect", + "/usr/sbin/openconnect", + DEFAULT_PATHS, + G_FILE_TEST_IS_EXECUTABLE, + NULL, + NULL, + error); + if (!path) + return FALSE; + + if (!g_spawn_sync(NULL, + (char **) NM_MAKE_STRV(path, "--authenticate", host), + NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN, + NULL, + NULL, + &output, + NULL, + status, + error)) + return FALSE; + + /* Parse output and set cookie, gateway and gwcert + * output example: + * COOKIE='loremipsum' + * HOST='1.2.3.4' + * FINGERPRINT='sha1:32bac90cf09a722e10ecc1942c67fe2ac8c21e2e' + */ + output_v = nm_utils_strsplit_set_with_empty(output, "\r\n"); + for (iter = output_v; iter && *iter; iter++) { + char *s_mutable = (char *) *iter; + + _extract_variable_value(s_mutable, "COOKIE=", cookie); + _extract_variable_value(s_mutable, "HOST=", gateway); + _extract_variable_value(s_mutable, "FINGERPRINT=", gwcert); + } + + return TRUE; } static gboolean -_wg_complete_peer (GPtrArray **p_peers, - NMWireGuardPeer *peer_take, - gsize peer_start_line_nr, - const char *filename, - GError **error) +_wg_complete_peer(GPtrArray ** p_peers, + NMWireGuardPeer *peer_take, + gsize peer_start_line_nr, + const char * filename, + GError ** error) { - nm_auto_unref_wgpeer NMWireGuardPeer *peer = peer_take; - gs_free_error GError *local = NULL; - - if (!peer) - return TRUE; - - if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &local)) { - nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, - _("Invalid peer starting at %s:%zu: %s"), - filename, - peer_start_line_nr, - local->message); - return FALSE; - } - - if (!*p_peers) - *p_peers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_wireguard_peer_unref); - g_ptr_array_add (*p_peers, g_steal_pointer (&peer)); - return TRUE; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = peer_take; + gs_free_error GError *local = NULL; + + if (!peer) + return TRUE; + + if (!nm_wireguard_peer_is_valid(peer, TRUE, TRUE, &local)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + _("Invalid peer starting at %s:%zu: %s"), + filename, + peer_start_line_nr, + local->message); + return FALSE; + } + + if (!*p_peers) + *p_peers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_wireguard_peer_unref); + g_ptr_array_add(*p_peers, g_steal_pointer(&peer)); + return TRUE; } static gboolean -_line_match (char *line, const char *key, gsize key_len, const char **out_key, char **out_value) +_line_match(char *line, const char *key, gsize key_len, const char **out_key, char **out_value) { - nm_assert (line); - nm_assert (key); - nm_assert (strlen (key) == key_len); - nm_assert (!strchr (key, '=')); - nm_assert (out_key && !*out_key); - nm_assert (out_value && !*out_value); - - /* Note that `wg-quick` (linux.bash) does case-insensitive comparison (shopt -s nocasematch). - * `wg setconf` does case-insensitive comparison too (with strncasecmp, which is locale dependent). - * - * We do a case-insensitive comparison of the key, however in a locale-independent manner. */ - - if (g_ascii_strncasecmp (line, key, key_len) != 0) - return FALSE; - - if (line[key_len] != '=') - return FALSE; - - *out_key = key; - *out_value = &line[key_len + 1]; - return TRUE; + nm_assert(line); + nm_assert(key); + nm_assert(strlen(key) == key_len); + nm_assert(!strchr(key, '=')); + nm_assert(out_key && !*out_key); + nm_assert(out_value && !*out_value); + + /* Note that `wg-quick` (linux.bash) does case-insensitive comparison (shopt -s nocasematch). + * `wg setconf` does case-insensitive comparison too (with strncasecmp, which is locale dependent). + * + * We do a case-insensitive comparison of the key, however in a locale-independent manner. */ + + if (g_ascii_strncasecmp(line, key, key_len) != 0) + return FALSE; + + if (line[key_len] != '=') + return FALSE; + + *out_key = key; + *out_value = &line[key_len + 1]; + return TRUE; } #define line_match(line, key, out_key, out_value) \ - _line_match ((line), ""key"", NM_STRLEN (key), (out_key), (out_value)) + _line_match((line), "" key "", NM_STRLEN(key), (out_key), (out_value)) static gboolean -value_split_word (char **line_remainder, char **out_word) +value_split_word(char **line_remainder, char **out_word) { - char *str; + char *str; - if ((*line_remainder)[0] == '\0') - return FALSE; + if ((*line_remainder)[0] == '\0') + return FALSE; - *out_word = *line_remainder; + *out_word = *line_remainder; - str = strchrnul (*line_remainder, ','); - if (str[0] == ',') { - str[0] = '\0'; - *line_remainder = &str[1]; - } else - *line_remainder = str; - return TRUE; + str = strchrnul(*line_remainder, ','); + if (str[0] == ',') { + str[0] = '\0'; + *line_remainder = &str[1]; + } else + *line_remainder = str; + return TRUE; } NMConnection * -nm_vpn_wireguard_import (const char *filename, - GError **error) +nm_vpn_wireguard_import(const char *filename, GError **error) { - nm_auto_clear_secret_ptr NMSecretPtr file_content = NM_SECRET_PTR_INIT (); - char ifname[IFNAMSIZ]; - gs_free char *uuid = NULL; - gboolean ifname_valid = FALSE; - const char *cstr; - char *line_remainder; - gs_unref_object NMConnection *connection = NULL; - NMSettingConnection *s_con; - NMSettingIPConfig *s_ip4; - NMSettingIPConfig *s_ip6; - NMSettingWireGuard *s_wg; - gs_free_error GError *local = NULL; - enum { - LINE_CONTEXT_INIT, - LINE_CONTEXT_INTERFACE, - LINE_CONTEXT_PEER, - } line_context; - gsize line_nr; - gsize current_peer_start_line_nr = 0; - nm_auto_unref_wgpeer NMWireGuardPeer *current_peer = NULL; - gs_unref_ptrarray GPtrArray *data_dns_search = NULL; - gs_unref_ptrarray GPtrArray *data_dns_v4 = NULL; - gs_unref_ptrarray GPtrArray *data_dns_v6 = NULL; - gs_unref_ptrarray GPtrArray *data_addr_v4 = NULL; - gs_unref_ptrarray GPtrArray *data_addr_v6 = NULL; - gs_unref_ptrarray GPtrArray *data_peers = NULL; - const char *data_private_key = NULL; - gint64 data_table; - guint data_listen_port = 0; - guint data_fwmark = 0; - guint data_mtu = 0; - int is_v4; - guint i; - - g_return_val_if_fail (filename, NULL); - g_return_val_if_fail (!error || !*error, NULL); - - /* contrary to "wg-quick", we never interpret the filename as "/etc/wireguard/$INTERFACE.conf". - * If the filename has no '/', it is interpreted as relative to the current working directory. - * However, we do require a suitable filename suffix and that the name corresponds to the interface - * name. */ - cstr = strrchr (filename, '/'); - cstr = cstr ? &cstr[1] : filename; - if (NM_STR_HAS_SUFFIX (cstr, ".conf")) { - gsize len = strlen (cstr) - NM_STRLEN (".conf"); - - if (len > 0 && len < sizeof (ifname)) { - memcpy (ifname, cstr, len); - ifname[len] = '\0'; - - if (nm_utils_ifname_valid (ifname, NMU_IFACE_KERNEL, NULL)) - ifname_valid = TRUE; - } - } - if (!ifname_valid) { - nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, - _("The name of the WireGuard config must be a valid interface name followed by \".conf\"")); - return FALSE; - } - - if (!nm_utils_file_get_contents (-1, - filename, - 10*1024*1024, - NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET, - &file_content.str, - &file_content.len, - NULL, - error)) - return NULL; - - /* We interpret the file like `wg-quick up` and `wg setconf` do. - * - * Of course the WireGuard scripts do something fundamentlly different. They - * perform actions to configure the WireGuard link in kernel, add routes and - * addresses, and call resolvconf. It all happens at the time when the script - * run. - * - * This code here instead generates a NetworkManager connection profile so that - * NetworkManager will apply a similar configuration when later activating the profile. */ - -#define _TABLE_AUTO ((gint64) -1) -#define _TABLE_OFF ((gint64) -2) - - data_table = _TABLE_AUTO; - - line_remainder = file_content.str; - line_context = LINE_CONTEXT_INIT; - line_nr = 0; - while (line_remainder[0] != '\0') { - const char *matched_key = NULL; - char *value = NULL; - char *line; - char ch; - gint64 i64; - - line_nr++; - - line = line_remainder; - line_remainder = strchrnul (line, '\n'); - if (line_remainder[0] != '\0') - (line_remainder++)[0] = '\0'; - - /* Drop all spaces and truncate at first '#'. - * See wg's config_read_line(). - * - * Note that wg-quick doesn't do that. - * - * Neither `wg setconf` nor `wg-quick` does a strict parsing. - * We don't either. Just try to interpret the file (mostly) the same as - * they would. - */ - { - gsize l, n; - - n = 0; - for (l = 0; (ch = line[l]); l++) { - if (g_ascii_isspace (ch)) { - /* wg-setconf strips all whitespace before parsing the content. That means, - * *[I nterface]" will be accepted. We do that too. */ - continue; - } - if (ch == '#') - break; - line[n++] = line[l]; - } - if (n == 0) - continue; - line[n] = '\0'; - } - - if (g_ascii_strcasecmp (line, "[Interface]") == 0) { - if (!_wg_complete_peer (&data_peers, - g_steal_pointer (¤t_peer), - current_peer_start_line_nr, - filename, - error)) - return FALSE; - line_context = LINE_CONTEXT_INTERFACE; - continue; - } - - if (g_ascii_strcasecmp (line, "[Peer]") == 0) { - if (!_wg_complete_peer (&data_peers, - g_steal_pointer (¤t_peer), - current_peer_start_line_nr, - filename, - error)) - return FALSE; - current_peer_start_line_nr = line_nr; - current_peer = nm_wireguard_peer_new (); - line_context = LINE_CONTEXT_PEER; - continue; - } - - if (line_context == LINE_CONTEXT_INTERFACE) { - - if (line_match (line, "Address", &matched_key, &value)) { - char *value_word; - - while (value_split_word (&value, &value_word)) { - GPtrArray **p_data_addr; - NMIPAddr addr_bin; - int addr_family; - int prefix_len; - - if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, - value_word, - &addr_family, - &addr_bin, - &prefix_len)) - goto fail_invalid_value; - - p_data_addr = (addr_family == AF_INET) - ? &data_addr_v4 - : &data_addr_v6; - - if (!*p_data_addr) - *p_data_addr = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref); - - g_ptr_array_add (*p_data_addr, - nm_ip_address_new_binary (addr_family, - &addr_bin, - prefix_len == -1 - ? ((addr_family == AF_INET) ? 32 : 128) - : prefix_len, - NULL)); - } - continue; - } - - if (line_match (line, "MTU", &matched_key, &value)) { - i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXUINT32, -1); - if (i64 == -1) - goto fail_invalid_value; - - /* wg-quick accepts the "MTU" value, but it also fetches routes to - * autodetect it. NetworkManager won't do that, we can only configure - * an explicit MTU or no autodetection will be performed. */ - data_mtu = i64; - continue; - } - - if (line_match (line, "DNS", &matched_key, &value)) { - char *value_word; - - while (value_split_word (&value, &value_word)) { - GPtrArray **p_data_dns; - NMIPAddr addr_bin; - int addr_family; - - if (nm_utils_parse_inaddr_bin (AF_UNSPEC, - value_word, - &addr_family, - &addr_bin)) { - p_data_dns = (addr_family == AF_INET) - ? &data_dns_v4 - : &data_dns_v6; - if (!*p_data_dns) - *p_data_dns = g_ptr_array_new_with_free_func (g_free); - - g_ptr_array_add (*p_data_dns, - nm_utils_inet_ntop_dup (addr_family, &addr_bin)); - continue; - } - - if (!data_dns_search) - data_dns_search = g_ptr_array_new_with_free_func (g_free); - g_ptr_array_add (data_dns_search, g_strdup (value_word)); - } - continue; - } - - if (line_match (line, "Table", &matched_key, &value)) { - - if (nm_streq (value, "auto")) - data_table = _TABLE_AUTO; - else if (nm_streq (value, "off")) - data_table = _TABLE_OFF; - else { - /* we don't support table names from /etc/iproute2/rt_tables - * But we accept hex like `ip route add` would. */ - i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXINT32, -1); - if (i64 == -1) - goto fail_invalid_value; - data_table = i64; - } - continue; - } - - if ( line_match (line, "PreUp", &matched_key, &value) - || line_match (line, "PreDown", &matched_key, &value) - || line_match (line, "PostUp", &matched_key, &value) - || line_match (line, "PostDown", &matched_key, &value)) { - /* we don't run any scripts. Silently ignore these parameters. */ - continue; - } - - if (line_match (line, "SaveConfig", &matched_key, &value)) { - /* we ignore the setting, but enforce that it's either true or false (like - * wg-quick. */ - if (!NM_IN_STRSET (value, "true", "false")) - goto fail_invalid_value; - continue; - } - - if (line_match (line, "ListenPort", &matched_key, &value)) { - /* we don't use getaddrinfo(), unlike `wg setconf`. Just interpret - * the port as plain decimal number. */ - i64 = _nm_utils_ascii_str_to_int64 (value, 10, 0, 0xFFFF, -1); - if (i64 == -1) - goto fail_invalid_value; - data_listen_port = i64; - continue; - } - - if (line_match (line, "FwMark", &matched_key, &value)) { - if (nm_streq (value, "off")) - data_fwmark = 0; - else { - i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXINT32, -1); - if (i64 == -1) - goto fail_invalid_value; - data_fwmark = i64; - } - continue; - } - - if (line_match (line, "PrivateKey", &matched_key, &value)) { - if (!nm_utils_base64secret_decode (value, NM_WIREGUARD_PUBLIC_KEY_LEN, NULL)) - goto fail_invalid_secret; - data_private_key = value; - continue; - } - - goto fail_invalid_line; - } - - - if (line_context == LINE_CONTEXT_PEER) { - - if (line_match (line, "Endpoint", &matched_key, &value)) { - if (!nm_wireguard_peer_set_endpoint (current_peer, value, FALSE)) - goto fail_invalid_value; - continue; - } - - if (line_match (line, "PublicKey", &matched_key, &value)) { - if (!nm_wireguard_peer_set_public_key (current_peer, value, FALSE)) - goto fail_invalid_value; - continue; - } - - if (line_match (line, "AllowedIPs", &matched_key, &value)) { - char *value_word; - - while (value_split_word (&value, &value_word)) { - if (!nm_wireguard_peer_append_allowed_ip (current_peer, - value_word, - FALSE)) - goto fail_invalid_value; - } - continue; - } - - if (line_match (line, "PersistentKeepalive", &matched_key, &value)) { - if (nm_streq (value, "off")) - i64 = 0; - else { - i64 = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT16, -1); - if (i64 == -1) - goto fail_invalid_value; - } - nm_wireguard_peer_set_persistent_keepalive (current_peer, i64); - continue; - } - - if (line_match (line, "PresharedKey", &matched_key, &value)) { - if (!nm_wireguard_peer_set_preshared_key (current_peer, value, FALSE)) - goto fail_invalid_secret; - nm_wireguard_peer_set_preshared_key_flags (current_peer, NM_SETTING_SECRET_FLAG_NONE); - continue; - } - - goto fail_invalid_line; - } + nm_auto_clear_secret_ptr NMSecretPtr file_content = NM_SECRET_PTR_INIT(); + char ifname[IFNAMSIZ]; + gs_free char * uuid = NULL; + gboolean ifname_valid = FALSE; + const char * cstr; + char * line_remainder; + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingWireGuard * s_wg; + gs_free_error GError *local = NULL; + enum { + LINE_CONTEXT_INIT, + LINE_CONTEXT_INTERFACE, + LINE_CONTEXT_PEER, + } line_context; + gsize line_nr; + gsize current_peer_start_line_nr = 0; + nm_auto_unref_wgpeer NMWireGuardPeer *current_peer = NULL; + gs_unref_ptrarray GPtrArray *data_dns_search = NULL; + gs_unref_ptrarray GPtrArray *data_dns_v4 = NULL; + gs_unref_ptrarray GPtrArray *data_dns_v6 = NULL; + gs_unref_ptrarray GPtrArray *data_addr_v4 = NULL; + gs_unref_ptrarray GPtrArray *data_addr_v6 = NULL; + gs_unref_ptrarray GPtrArray *data_peers = NULL; + const char * data_private_key = NULL; + gint64 data_table; + guint data_listen_port = 0; + guint data_fwmark = 0; + guint data_mtu = 0; + int is_v4; + guint i; + + g_return_val_if_fail(filename, NULL); + g_return_val_if_fail(!error || !*error, NULL); + + /* contrary to "wg-quick", we never interpret the filename as "/etc/wireguard/$INTERFACE.conf". + * If the filename has no '/', it is interpreted as relative to the current working directory. + * However, we do require a suitable filename suffix and that the name corresponds to the interface + * name. */ + cstr = strrchr(filename, '/'); + cstr = cstr ? &cstr[1] : filename; + if (NM_STR_HAS_SUFFIX(cstr, ".conf")) { + gsize len = strlen(cstr) - NM_STRLEN(".conf"); + + if (len > 0 && len < sizeof(ifname)) { + memcpy(ifname, cstr, len); + ifname[len] = '\0'; + + if (nm_utils_ifname_valid(ifname, NMU_IFACE_KERNEL, NULL)) + ifname_valid = TRUE; + } + } + if (!ifname_valid) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_UNKNOWN, + _("The name of the WireGuard config must be a valid interface " + "name followed by \".conf\"")); + return FALSE; + } + + if (!nm_utils_file_get_contents(-1, + filename, + 10 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET, + &file_content.str, + &file_content.len, + NULL, + error)) + return NULL; + + /* We interpret the file like `wg-quick up` and `wg setconf` do. + * + * Of course the WireGuard scripts do something fundamentlly different. They + * perform actions to configure the WireGuard link in kernel, add routes and + * addresses, and call resolvconf. It all happens at the time when the script + * run. + * + * This code here instead generates a NetworkManager connection profile so that + * NetworkManager will apply a similar configuration when later activating the profile. */ + +#define _TABLE_AUTO ((gint64) -1) +#define _TABLE_OFF ((gint64) -2) + + data_table = _TABLE_AUTO; + + line_remainder = file_content.str; + line_context = LINE_CONTEXT_INIT; + line_nr = 0; + while (line_remainder[0] != '\0') { + const char *matched_key = NULL; + char * value = NULL; + char * line; + char ch; + gint64 i64; + + line_nr++; + + line = line_remainder; + line_remainder = strchrnul(line, '\n'); + if (line_remainder[0] != '\0') + (line_remainder++)[0] = '\0'; + + /* Drop all spaces and truncate at first '#'. + * See wg's config_read_line(). + * + * Note that wg-quick doesn't do that. + * + * Neither `wg setconf` nor `wg-quick` does a strict parsing. + * We don't either. Just try to interpret the file (mostly) the same as + * they would. + */ + { + gsize l, n; + + n = 0; + for (l = 0; (ch = line[l]); l++) { + if (g_ascii_isspace(ch)) { + /* wg-setconf strips all whitespace before parsing the content. That means, + * *[I nterface]" will be accepted. We do that too. */ + continue; + } + if (ch == '#') + break; + line[n++] = line[l]; + } + if (n == 0) + continue; + line[n] = '\0'; + } + + if (g_ascii_strcasecmp(line, "[Interface]") == 0) { + if (!_wg_complete_peer(&data_peers, + g_steal_pointer(¤t_peer), + current_peer_start_line_nr, + filename, + error)) + return FALSE; + line_context = LINE_CONTEXT_INTERFACE; + continue; + } + + if (g_ascii_strcasecmp(line, "[Peer]") == 0) { + if (!_wg_complete_peer(&data_peers, + g_steal_pointer(¤t_peer), + current_peer_start_line_nr, + filename, + error)) + return FALSE; + current_peer_start_line_nr = line_nr; + current_peer = nm_wireguard_peer_new(); + line_context = LINE_CONTEXT_PEER; + continue; + } + + if (line_context == LINE_CONTEXT_INTERFACE) { + if (line_match(line, "Address", &matched_key, &value)) { + char *value_word; + + while (value_split_word(&value, &value_word)) { + GPtrArray **p_data_addr; + NMIPAddr addr_bin; + int addr_family; + int prefix_len; + + if (!nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, + value_word, + &addr_family, + &addr_bin, + &prefix_len)) + goto fail_invalid_value; + + p_data_addr = (addr_family == AF_INET) ? &data_addr_v4 : &data_addr_v6; + + if (!*p_data_addr) + *p_data_addr = + g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref); + + g_ptr_array_add( + *p_data_addr, + nm_ip_address_new_binary( + addr_family, + &addr_bin, + prefix_len == -1 ? ((addr_family == AF_INET) ? 32 : 128) : prefix_len, + NULL)); + } + continue; + } + + if (line_match(line, "MTU", &matched_key, &value)) { + i64 = _nm_utils_ascii_str_to_int64(value, 0, 0, G_MAXUINT32, -1); + if (i64 == -1) + goto fail_invalid_value; + + /* wg-quick accepts the "MTU" value, but it also fetches routes to + * autodetect it. NetworkManager won't do that, we can only configure + * an explicit MTU or no autodetection will be performed. */ + data_mtu = i64; + continue; + } + + if (line_match(line, "DNS", &matched_key, &value)) { + char *value_word; + + while (value_split_word(&value, &value_word)) { + GPtrArray **p_data_dns; + NMIPAddr addr_bin; + int addr_family; + + if (nm_utils_parse_inaddr_bin(AF_UNSPEC, value_word, &addr_family, &addr_bin)) { + p_data_dns = (addr_family == AF_INET) ? &data_dns_v4 : &data_dns_v6; + if (!*p_data_dns) + *p_data_dns = g_ptr_array_new_with_free_func(g_free); + + g_ptr_array_add(*p_data_dns, + nm_utils_inet_ntop_dup(addr_family, &addr_bin)); + continue; + } + + if (!data_dns_search) + data_dns_search = g_ptr_array_new_with_free_func(g_free); + g_ptr_array_add(data_dns_search, g_strdup(value_word)); + } + continue; + } + + if (line_match(line, "Table", &matched_key, &value)) { + if (nm_streq(value, "auto")) + data_table = _TABLE_AUTO; + else if (nm_streq(value, "off")) + data_table = _TABLE_OFF; + else { + /* we don't support table names from /etc/iproute2/rt_tables + * But we accept hex like `ip route add` would. */ + i64 = _nm_utils_ascii_str_to_int64(value, 0, 0, G_MAXINT32, -1); + if (i64 == -1) + goto fail_invalid_value; + data_table = i64; + } + continue; + } + + if (line_match(line, "PreUp", &matched_key, &value) + || line_match(line, "PreDown", &matched_key, &value) + || line_match(line, "PostUp", &matched_key, &value) + || line_match(line, "PostDown", &matched_key, &value)) { + /* we don't run any scripts. Silently ignore these parameters. */ + continue; + } + + if (line_match(line, "SaveConfig", &matched_key, &value)) { + /* we ignore the setting, but enforce that it's either true or false (like + * wg-quick. */ + if (!NM_IN_STRSET(value, "true", "false")) + goto fail_invalid_value; + continue; + } + + if (line_match(line, "ListenPort", &matched_key, &value)) { + /* we don't use getaddrinfo(), unlike `wg setconf`. Just interpret + * the port as plain decimal number. */ + i64 = _nm_utils_ascii_str_to_int64(value, 10, 0, 0xFFFF, -1); + if (i64 == -1) + goto fail_invalid_value; + data_listen_port = i64; + continue; + } + + if (line_match(line, "FwMark", &matched_key, &value)) { + if (nm_streq(value, "off")) + data_fwmark = 0; + else { + i64 = _nm_utils_ascii_str_to_int64(value, 0, 0, G_MAXINT32, -1); + if (i64 == -1) + goto fail_invalid_value; + data_fwmark = i64; + } + continue; + } + + if (line_match(line, "PrivateKey", &matched_key, &value)) { + if (!nm_utils_base64secret_decode(value, NM_WIREGUARD_PUBLIC_KEY_LEN, NULL)) + goto fail_invalid_secret; + data_private_key = value; + continue; + } + + goto fail_invalid_line; + } + + if (line_context == LINE_CONTEXT_PEER) { + if (line_match(line, "Endpoint", &matched_key, &value)) { + if (!nm_wireguard_peer_set_endpoint(current_peer, value, FALSE)) + goto fail_invalid_value; + continue; + } + + if (line_match(line, "PublicKey", &matched_key, &value)) { + if (!nm_wireguard_peer_set_public_key(current_peer, value, FALSE)) + goto fail_invalid_value; + continue; + } + + if (line_match(line, "AllowedIPs", &matched_key, &value)) { + char *value_word; + + while (value_split_word(&value, &value_word)) { + if (!nm_wireguard_peer_append_allowed_ip(current_peer, value_word, FALSE)) + goto fail_invalid_value; + } + continue; + } + + if (line_match(line, "PersistentKeepalive", &matched_key, &value)) { + if (nm_streq(value, "off")) + i64 = 0; + else { + i64 = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXUINT16, -1); + if (i64 == -1) + goto fail_invalid_value; + } + nm_wireguard_peer_set_persistent_keepalive(current_peer, i64); + continue; + } + + if (line_match(line, "PresharedKey", &matched_key, &value)) { + if (!nm_wireguard_peer_set_preshared_key(current_peer, value, FALSE)) + goto fail_invalid_secret; + nm_wireguard_peer_set_preshared_key_flags(current_peer, + NM_SETTING_SECRET_FLAG_NONE); + continue; + } + + goto fail_invalid_line; + } fail_invalid_line: - nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT, - _("unrecognized line at %s:%zu"), - filename, line_nr); - return FALSE; + nm_utils_error_set(error, + NM_UTILS_ERROR_INVALID_ARGUMENT, + _("unrecognized line at %s:%zu"), + filename, + line_nr); + return FALSE; fail_invalid_value: - nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT, - _("invalid value for '%s' at %s:%zu"), - matched_key, filename, line_nr); - return FALSE; + nm_utils_error_set(error, + NM_UTILS_ERROR_INVALID_ARGUMENT, + _("invalid value for '%s' at %s:%zu"), + matched_key, + filename, + line_nr); + return FALSE; fail_invalid_secret: - nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT, - _("invalid secret '%s' at %s:%zu"), - matched_key, filename, line_nr); - return FALSE; - } - - if (!_wg_complete_peer (&data_peers, - g_steal_pointer (¤t_peer), - current_peer_start_line_nr, - filename, - error)) - return FALSE; - - connection = nm_simple_connection_new (); - 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)); - s_ip6 = NM_SETTING_IP_CONFIG (nm_setting_ip6_config_new ()); - nm_connection_add_setting (connection, NM_SETTING (s_ip6)); - s_wg = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ()); - nm_connection_add_setting (connection, NM_SETTING (s_wg)); - - uuid = nm_utils_uuid_generate (); - - g_object_set (s_con, - NM_SETTING_CONNECTION_ID, - ifname, - NM_SETTING_CONNECTION_UUID, - uuid, - NM_SETTING_CONNECTION_TYPE, - NM_SETTING_WIREGUARD_SETTING_NAME, - NM_SETTING_CONNECTION_INTERFACE_NAME, - ifname, - NULL); - - g_object_set (s_wg, - NM_SETTING_WIREGUARD_PRIVATE_KEY, - data_private_key, - NM_SETTING_WIREGUARD_LISTEN_PORT, - data_listen_port, - NM_SETTING_WIREGUARD_FWMARK, - data_fwmark, - NM_SETTING_WIREGUARD_MTU, - data_mtu, - NULL); - - if (data_peers) { - for (i = 0; i < data_peers->len; i++) - nm_setting_wireguard_append_peer (s_wg, data_peers->pdata[i]); - } - - for (is_v4 = 0; is_v4 < 2; is_v4++) { - const char *method_disabled = is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED : NM_SETTING_IP6_CONFIG_METHOD_DISABLED; - const char *method_manual = is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_MANUAL : NM_SETTING_IP6_CONFIG_METHOD_MANUAL; - NMSettingIPConfig *s_ip = is_v4 ? s_ip4 : s_ip6; - GPtrArray *data_dns = is_v4 ? data_dns_v4 : data_dns_v6; - GPtrArray *data_addr = is_v4 ? data_addr_v4 : data_addr_v6; - GPtrArray *data_dns_search2 = data_dns_search; - - if (data_dns && !data_addr) { - /* When specifying "DNS", we also require an "Address" for the same address - * family. That is because a NMSettingIPConfig cannot have @method_disabled - * and DNS settings at the same time. - * - * We don't have addresses. Silently ignore the DNS setting. */ - data_dns = NULL; - data_dns_search2 = NULL; - } - - g_object_set (s_ip, - NM_SETTING_IP_CONFIG_METHOD, - data_addr ? method_manual : method_disabled, - NULL); - - /* For WireGuard profiles, always set dns-priority to a negative value, - * so that DNS servers on other profiles get ignored. This is also what - * wg-quick does, by calling `resolvconf -x`. */ - g_object_set (s_ip, - NM_SETTING_IP_CONFIG_DNS_PRIORITY, - (int) -10, - NULL); - - if (data_addr) { - for (i = 0; i < data_addr->len; i++) - nm_setting_ip_config_add_address (s_ip, data_addr->pdata[i]); - } - if (data_dns) { - for (i = 0; i < data_dns->len; i++) - nm_setting_ip_config_add_dns (s_ip, data_dns->pdata[i]); - - /* Of the wg-quick doesn't specify a search domain, assume the user - * wants to use the domain server for all searches. */ - if (!data_dns_search2) - nm_setting_ip_config_add_dns_search (s_ip, "~"); - } - if (data_dns_search2) { - for (i = 0; i < data_dns_search2->len; i++) - nm_setting_ip_config_add_dns_search (s_ip, data_dns_search2->pdata[i]); - } - - if (data_table == _TABLE_AUTO) { - /* in the "auto" setting, wg-quick adds peer-routes automatically to the main - * table. NetworkManager will do that too, but there are differences: - * - * - NetworkManager (contrary to wg-quick) does not check whether the peer-route is necessary. - * It will always add a route for each allowed-ips range, even if there is already another - * route that would ensure packets to the endpoint are routed via the WireGuard interface. - * If you don't want that, disable "wireguard.peer-routes", and add the necessary routes - * yourself to "ipv4.routes" and "ipv6.routes". - * - * - With "auto", wg-quick also configures policy routing to handle default-routes (/0) to - * avoid routing loops. - * The imported connection profile will have wireguard.ip4-auto-default-route and - * wireguard.ip6-auto-default-route set to "default". It will thus configure wg-quick's - * policy routing if the profile has any AllowedIPs ranges with /0. - */ - } else if (data_table == _TABLE_OFF) { - if (is_v4) { - g_object_set (s_wg, - NM_SETTING_WIREGUARD_PEER_ROUTES, - FALSE, - NULL); - } - } else { - g_object_set (s_ip, - NM_SETTING_IP_CONFIG_ROUTE_TABLE, - (guint) data_table, - NULL); - } - } - - if (!nm_connection_normalize (connection, NULL, NULL, &local)) { - nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT, - _("Failed to create WireGuard connection: %s"), - local->message); - return FALSE; - } - - return g_steal_pointer (&connection); + nm_utils_error_set(error, + NM_UTILS_ERROR_INVALID_ARGUMENT, + _("invalid secret '%s' at %s:%zu"), + matched_key, + filename, + line_nr); + return FALSE; + } + + if (!_wg_complete_peer(&data_peers, + g_steal_pointer(¤t_peer), + current_peer_start_line_nr, + filename, + error)) + return FALSE; + + connection = nm_simple_connection_new(); + 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)); + s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + s_wg = NM_SETTING_WIREGUARD(nm_setting_wireguard_new()); + nm_connection_add_setting(connection, NM_SETTING(s_wg)); + + uuid = nm_utils_uuid_generate(); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + ifname, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME, + ifname, + NULL); + + g_object_set(s_wg, + NM_SETTING_WIREGUARD_PRIVATE_KEY, + data_private_key, + NM_SETTING_WIREGUARD_LISTEN_PORT, + data_listen_port, + NM_SETTING_WIREGUARD_FWMARK, + data_fwmark, + NM_SETTING_WIREGUARD_MTU, + data_mtu, + NULL); + + if (data_peers) { + for (i = 0; i < data_peers->len; i++) + nm_setting_wireguard_append_peer(s_wg, data_peers->pdata[i]); + } + + for (is_v4 = 0; is_v4 < 2; is_v4++) { + const char *method_disabled = + is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED : NM_SETTING_IP6_CONFIG_METHOD_DISABLED; + const char *method_manual = + is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_MANUAL : NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + NMSettingIPConfig *s_ip = is_v4 ? s_ip4 : s_ip6; + GPtrArray * data_dns = is_v4 ? data_dns_v4 : data_dns_v6; + GPtrArray * data_addr = is_v4 ? data_addr_v4 : data_addr_v6; + GPtrArray * data_dns_search2 = data_dns_search; + + if (data_dns && !data_addr) { + /* When specifying "DNS", we also require an "Address" for the same address + * family. That is because a NMSettingIPConfig cannot have @method_disabled + * and DNS settings at the same time. + * + * We don't have addresses. Silently ignore the DNS setting. */ + data_dns = NULL; + data_dns_search2 = NULL; + } + + g_object_set(s_ip, + NM_SETTING_IP_CONFIG_METHOD, + data_addr ? method_manual : method_disabled, + NULL); + + /* For WireGuard profiles, always set dns-priority to a negative value, + * so that DNS servers on other profiles get ignored. This is also what + * wg-quick does, by calling `resolvconf -x`. */ + g_object_set(s_ip, NM_SETTING_IP_CONFIG_DNS_PRIORITY, (int) -50, NULL); + + if (data_addr) { + for (i = 0; i < data_addr->len; i++) + nm_setting_ip_config_add_address(s_ip, data_addr->pdata[i]); + } + if (data_dns) { + for (i = 0; i < data_dns->len; i++) + nm_setting_ip_config_add_dns(s_ip, data_dns->pdata[i]); + + /* Of the wg-quick doesn't specify a search domain, assume the user + * wants to use the domain server for all searches. */ + if (!data_dns_search2) + nm_setting_ip_config_add_dns_search(s_ip, "~"); + } + if (data_dns_search2) { + for (i = 0; i < data_dns_search2->len; i++) + nm_setting_ip_config_add_dns_search(s_ip, data_dns_search2->pdata[i]); + } + + if (data_table == _TABLE_AUTO) { + /* in the "auto" setting, wg-quick adds peer-routes automatically to the main + * table. NetworkManager will do that too, but there are differences: + * + * - NetworkManager (contrary to wg-quick) does not check whether the peer-route is necessary. + * It will always add a route for each allowed-ips range, even if there is already another + * route that would ensure packets to the endpoint are routed via the WireGuard interface. + * If you don't want that, disable "wireguard.peer-routes", and add the necessary routes + * yourself to "ipv4.routes" and "ipv6.routes". + * + * - With "auto", wg-quick also configures policy routing to handle default-routes (/0) to + * avoid routing loops. + * The imported connection profile will have wireguard.ip4-auto-default-route and + * wireguard.ip6-auto-default-route set to "default". It will thus configure wg-quick's + * policy routing if the profile has any AllowedIPs ranges with /0. + */ + } else if (data_table == _TABLE_OFF) { + if (is_v4) { + g_object_set(s_wg, NM_SETTING_WIREGUARD_PEER_ROUTES, FALSE, NULL); + } + } else { + g_object_set(s_ip, NM_SETTING_IP_CONFIG_ROUTE_TABLE, (guint) data_table, NULL); + } + } + + if (!nm_connection_normalize(connection, NULL, NULL, &local)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_INVALID_ARGUMENT, + _("Failed to create WireGuard connection: %s"), + local->message); + return FALSE; + } + + return g_steal_pointer(&connection); } |