about summary refs log tree commit diff
path: root/src/libnmc-base
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnmc-base')
-rw-r--r--src/libnmc-base/nm-polkit-listener.c10
-rw-r--r--src/libnmc-base/nm-secret-agent-simple.c6
-rw-r--r--src/libnmc-base/nm-vpn-helpers.c246
-rw-r--r--src/libnmc-base/nm-vpn-helpers.h8
4 files changed, 239 insertions, 31 deletions
diff --git a/src/libnmc-base/nm-polkit-listener.c b/src/libnmc-base/nm-polkit-listener.c
index fbc2c704..c715b049 100644
--- a/src/libnmc-base/nm-polkit-listener.c
+++ b/src/libnmc-base/nm-polkit-listener.c
@@ -499,7 +499,6 @@ out:
 static void
 begin_authentication(AuthRequest *request)
 {
-    int         fd_flags;
     const char *helper_argv[] = {
         POLKIT_AGENT_HELPER_1_PATH,
         request->username,
@@ -514,7 +513,7 @@ begin_authentication(AuthRequest *request)
     if (!g_spawn_async_with_pipes(NULL,
                                   (char **) helper_argv,
                                   NULL,
-                                  G_SPAWN_STDERR_TO_DEV_NULL,
+                                  G_SPAWN_CLOEXEC_PIPES | G_SPAWN_STDERR_TO_DEV_NULL,
                                   NULL,
                                   NULL,
                                   NULL,
@@ -534,11 +533,8 @@ begin_authentication(AuthRequest *request)
         return;
     }
 
-    fd_flags = fcntl(request->child_stdin, F_GETFL, 0);
-    fcntl(request->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
-
-    fd_flags = fcntl(request->child_stdout, F_GETFL, 0);
-    fcntl(request->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
+    nm_io_fcntl_setfl_update_nonblock(request->child_stdin);
+    nm_io_fcntl_setfl_update_nonblock(request->child_stdout);
 
     request->child_stdout_watch_source = nm_g_unix_fd_source_new(request->child_stdout,
                                                                  G_IO_IN | G_IO_ERR | G_IO_HUP,
diff --git a/src/libnmc-base/nm-secret-agent-simple.c b/src/libnmc-base/nm-secret-agent-simple.c
index de3de05a..1b9aa571 100644
--- a/src/libnmc-base/nm-secret-agent-simple.c
+++ b/src/libnmc-base/nm-secret-agent-simple.c
@@ -787,7 +787,7 @@ try_spawn_vpn_auth_helper(RequestData *request, GPtrArray *secrets)
     if (!g_spawn_async_with_pipes(NULL,
                                   (char **) auth_dialog_argv->pdata,
                                   NULL,
-                                  G_SPAWN_DO_NOT_REAP_CHILD,
+                                  G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD,
                                   NULL,
                                   NULL,
                                   &auth_dialog_pid,
@@ -1084,7 +1084,7 @@ get_secrets(NMSecretAgentOld              *agent,
  * nm_secret_agent_simple_response:
  * @self: the #NMSecretAgentSimple
  * @request_id: the request ID being responded to
- * @secrets: (allow-none): the array of secrets, or %NULL
+ * @secrets: (nullable): the array of secrets, or %NULL
  *
  * Response to a #NMSecretAgentSimple::get-secrets signal.
  *
@@ -1252,7 +1252,7 @@ delete_secrets(NMSecretAgentOld                 *agent,
 /**
  * nm_secret_agent_simple_enable:
  * @self: the #NMSecretAgentSimple
- * @path: (allow-none): the path of the connection (if any) to handle secrets
+ * @path: (nullable): the path of the connection (if any) to handle secrets
  *        for.  If %NULL, secrets for any connection will be handled.
  *
  * Enables servicing the requests including the already queued ones.  If @path
diff --git a/src/libnmc-base/nm-vpn-helpers.c b/src/libnmc-base/nm-vpn-helpers.c
index 476fbe51..cbe76f5f 100644
--- a/src/libnmc-base/nm-vpn-helpers.c
+++ b/src/libnmc-base/nm-vpn-helpers.c
@@ -16,6 +16,7 @@
 #include <net/if.h>
 
 #include "nm-client-utils.h"
+#include "nm-secret-agent-simple.h"
 #include "nm-utils.h"
 #include "libnm-glib-aux/nm-io-utils.h"
 #include "libnm-glib-aux/nm-secret-utils.h"
@@ -160,9 +161,10 @@ nm_vpn_get_secret_names(const char *service_type)
     };
 
     if (NM_IN_STRSET(type, "openconnect")) {
-        return _VPN_PASSWORD_LIST({"gateway", N_("Gateway")},
+        return _VPN_PASSWORD_LIST({"gateway", N_("Gateway URL")},
                                   {"cookie", N_("Cookie")},
-                                  {"gwcert", N_("Gateway certificate hash")}, );
+                                  {"gwcert", N_("Gateway certificate hash")},
+                                  {"resolve", N_("Gateway DNS resolution ('host:IP')")}, );
     };
 
     return NULL;
@@ -186,18 +188,102 @@ _extract_variable_value(char *line, const char *tag, char **value)
     return TRUE;
 }
 
+#define NM_OPENCONNECT_KEY_GATEWAY              "gateway"
+#define NM_OPENCONNECT_KEY_COOKIE               "cookie"
+#define NM_OPENCONNECT_KEY_GWCERT               "gwcert"
+#define NM_OPENCONNECT_KEY_RESOLVE              "resolve"
+#define NM_OPENCONNECT_KEY_AUTHTYPE             "authtype"
+#define NM_OPENCONNECT_KEY_USERCERT             "usercert"
+#define NM_OPENCONNECT_KEY_CACERT               "cacert"
+#define NM_OPENCONNECT_KEY_PRIVKEY              "userkey"
+#define NM_OPENCONNECT_KEY_KEY_PASS             "key_pass"
+#define NM_OPENCONNECT_KEY_MTU                  "mtu"
+#define NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID  "pem_passphrase_fsid"
+#define NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT "prevent_invalid_cert"
+#define NM_OPENCONNECT_KEY_DISABLE_UDP          "disable_udp"
+#define NM_OPENCONNECT_KEY_PROTOCOL             "protocol"
+#define NM_OPENCONNECT_KEY_PROXY                "proxy"
+#define NM_OPENCONNECT_KEY_CSD_ENABLE           "enable_csd_trojan"
+#define NM_OPENCONNECT_KEY_USERAGENT            "useragent"
+#define NM_OPENCONNECT_KEY_CSD_WRAPPER          "csd_wrapper"
+#define NM_OPENCONNECT_KEY_TOKEN_MODE           "stoken_source"
+#define NM_OPENCONNECT_KEY_TOKEN_SECRET         "stoken_string"
+#define NM_OPENCONNECT_KEY_REPORTED_OS          "reported_os"
+#define NM_OPENCONNECT_KEY_MCACERT              "mcacert"
+#define NM_OPENCONNECT_KEY_MCAKEY               "mcakey"
+#define NM_OPENCONNECT_KEY_MCA_PASS             "mca_key_pass"
+
+static const struct {
+    const char *property;
+    const char *cmdline;
+} oc_property_args[] = {
+    {NM_OPENCONNECT_KEY_USERCERT, "--certificate"},
+    {NM_OPENCONNECT_KEY_CACERT, "--cafile"},
+    {NM_OPENCONNECT_KEY_PRIVKEY, "--sslkey"},
+    {NM_OPENCONNECT_KEY_KEY_PASS, "--key-password"},
+    {NM_OPENCONNECT_KEY_PROTOCOL, "--protocol"},
+    {NM_OPENCONNECT_KEY_PROXY, "--proxy"},
+    {NM_OPENCONNECT_KEY_USERAGENT, "--useragent"},
+    {NM_OPENCONNECT_KEY_REPORTED_OS, "--os"},
+    {NM_OPENCONNECT_KEY_MCACERT, "--mca-certificate"},
+    {NM_OPENCONNECT_KEY_MCAKEY, "--mca-key"},
+    {NM_OPENCONNECT_KEY_MCA_PASS, "--mca-key-password"},
+};
+
+/*
+ * For old versions of openconnect we need to extract the port# and
+ * append it to the hostname that is returned to us. Use a cut-down
+ * version of openconnect's own internal_parse_url() function.
+ */
+static int
+extract_url_port(const char *url)
+{
+    const char *host, *port_str, *path;
+    char       *end;
+    int         port_nr;
+
+    /* Skip the scheme, if present */
+    host = strstr(url, "://");
+    if (host)
+        host += 3;
+    else
+        host = url;
+
+    port_str = strrchr(host, ':');
+    if (!port_str)
+        return 0;
+
+    /*
+     * If the host is an IPv6 literal, port_str may point somewhere
+     * inside it rather than to an actual port#. But IPv6 literals
+     * are always enclosed in [], e.g. '[fec0::1]:443'. So we check
+     * that the end pointer returned by strtol points exactly to the
+     * end of the hostname (either the end of the string, or to the
+     * first '/' of the path element if there is one).
+     */
+    path    = strchr(host, '/');
+    port_nr = strtol(port_str + 1, &end, 10);
+
+    if (end == path || (!path && !*end))
+        return port_nr;
+
+    return 0;
+}
+
 gboolean
-nm_vpn_openconnect_authenticate_helper(const char *host,
-                                       char      **cookie,
-                                       char      **gateway,
-                                       char      **gwcert,
-                                       int        *status,
-                                       GError    **error)
+nm_vpn_openconnect_authenticate_helper(NMSettingVpn *s_vpn, GPtrArray *secrets, GError **error)
 {
-    gs_free char        *output   = NULL;
-    gs_free const char **output_v = NULL;
+    gs_free char        *output      = NULL;
+    gs_free char        *legacy_host = NULL;
+    gs_free char        *connect_url = NULL;
+    gs_free char        *cookie      = NULL;
+    gs_free char        *gwcert      = NULL;
+    gs_free char        *resolve     = NULL;
+    gs_free const char **output_v    = NULL;
+    int                  status      = 0;
     const char *const   *iter;
     const char          *path;
+    const char          *opt;
     const char *const    DEFAULT_PATHS[] = {
         "/sbin/",
         "/usr/sbin/",
@@ -207,6 +293,23 @@ nm_vpn_openconnect_authenticate_helper(const char *host,
         "/usr/local/bin/",
         NULL,
     };
+    const char *oc_argv[(12 + 2 * G_N_ELEMENTS(oc_property_args))];
+    const char *gw;
+    int         port;
+    guint       oc_argc = 0;
+    guint       i;
+
+    /* Get gateway and port */
+    gw = nm_setting_vpn_get_data_item(s_vpn, "gateway");
+    if (!gw) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("no gateway configured"));
+        return FALSE;
+    }
+
+    port = extract_url_port(gw);
 
     path = nm_utils_file_search_in_paths("openconnect",
                                          "/usr/sbin/openconnect",
@@ -218,31 +321,144 @@ nm_vpn_openconnect_authenticate_helper(const char *host,
     if (!path)
         return FALSE;
 
+    oc_argv[oc_argc++] = path;
+    oc_argv[oc_argc++] = "--authenticate";
+    oc_argv[oc_argc++] = gw;
+
+    for (i = 0; i < G_N_ELEMENTS(oc_property_args); i++) {
+        opt = nm_setting_vpn_get_data_item(s_vpn, oc_property_args[i].property);
+        if (opt) {
+            oc_argv[oc_argc++] = oc_property_args[i].cmdline;
+            oc_argv[oc_argc++] = opt;
+        }
+    }
+
+    opt = nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID);
+    if (opt && nm_streq(opt, "yes"))
+        oc_argv[oc_argc++] = "--key-password-from-fsid";
+
+    opt = nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_CSD_ENABLE);
+    if (opt && nm_streq(opt, "yes")) {
+        opt = nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_CSD_WRAPPER);
+        if (opt) {
+            oc_argv[oc_argc++] = "--csd-wrapper";
+            oc_argv[oc_argc++] = opt;
+        }
+    }
+
+    opt = nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_TOKEN_MODE);
+    if (opt) {
+        const char *token_secret =
+            nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_TOKEN_SECRET);
+        if (nm_streq(opt, "manual") && token_secret) {
+            opt = "rsa";
+        } else if (nm_streq(opt, "stokenrc")) {
+            opt          = "rsa";
+            token_secret = NULL;
+        } else if (!nm_streq(opt, "totp") && !nm_streq(opt, "hotp") && !nm_streq(opt, "yubioath")) {
+            opt = NULL;
+        }
+        if (opt) {
+            oc_argv[oc_argc++] = "--token-mode";
+            oc_argv[oc_argc++] = opt;
+        }
+        if (token_secret) {
+            oc_argv[oc_argc++] = "--token-secret";
+            oc_argv[oc_argc++] = token_secret;
+        }
+    }
+
+    oc_argv[oc_argc++] = NULL;
+
+    nm_assert(oc_argc <= G_N_ELEMENTS(oc_argv));
+
     if (!g_spawn_sync(NULL,
-                      (char **) NM_MAKE_STRV(path, "--authenticate", host),
+                      (char **) oc_argv,
                       NULL,
                       G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN,
                       NULL,
                       NULL,
                       &output,
                       NULL,
-                      status,
+                      &status,
                       error))
         return FALSE;
 
+    if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+        /* The caller will prepend "Error: openconnect failed: " to this */
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("exited with status %d"),
+                    WEXITSTATUS(status));
+        return FALSE;
+    } else if (WIFSIGNALED(status)) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("exited on signal %d"),
+                    WTERMSIG(status));
+        return FALSE;
+    }
+
     /* Parse output and set cookie, gateway and gwcert
      * output example:
      * COOKIE='loremipsum'
      * HOST='1.2.3.4'
      * FINGERPRINT='sha1:32bac90cf09a722e10ecc1942c67fe2ac8c21e2e'
+     *
+     * Since OpenConnect v8.20 (2022-02-20) OpenConnect has also passed e.g.:
+     *
+     * CONNECT_URL='https://vpn.example.com:8443/ConnectPath'
+     * RESOLVE=vpn.example.com:1.2.3.4
      */
     output_v = nm_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);
+        _extract_variable_value(s_mutable, "COOKIE=", &cookie);
+        _extract_variable_value(s_mutable, "CONNECT_URL=", &connect_url);
+        _extract_variable_value(s_mutable, "HOST=", &legacy_host);
+        _extract_variable_value(s_mutable, "FINGERPRINT=", &gwcert);
+        _extract_variable_value(s_mutable, "RESOLVE=", &resolve);
+    }
+
+    if (!cookie || !gwcert || (!legacy_host && !connect_url)) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("insufficent secrets returned"));
+        return FALSE;
+    }
+
+    for (i = 0; i < secrets->len; i++) {
+        NMSecretAgentSimpleSecret *secret = secrets->pdata[i];
+
+        if (secret->secret_type != NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET)
+            continue;
+        if (!nm_streq0(secret->vpn_type, NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT))
+            continue;
+        if (nm_streq0(secret->entry_id, NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "cookie")) {
+            g_free(secret->value);
+            secret->value = g_steal_pointer(&cookie);
+        } else if (nm_streq0(secret->entry_id,
+                             NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gateway")) {
+            g_free(secret->value);
+            if (connect_url)
+                secret->value = g_steal_pointer(&connect_url);
+            else if (port)
+                secret->value = g_strdup_printf("%s:%d", legacy_host, port);
+            else
+                secret->value = g_steal_pointer(&legacy_host);
+        } else if (nm_streq0(secret->entry_id,
+                             NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gwcert")) {
+            g_free(secret->value);
+            secret->value = g_steal_pointer(&gwcert);
+        } else if (nm_streq0(secret->entry_id,
+                             NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "resolve")) {
+            g_free(secret->value);
+            secret->value = g_steal_pointer(&resolve);
+        }
     }
 
     return TRUE;
diff --git a/src/libnmc-base/nm-vpn-helpers.h b/src/libnmc-base/nm-vpn-helpers.h
index 8bea8460..afd56590 100644
--- a/src/libnmc-base/nm-vpn-helpers.h
+++ b/src/libnmc-base/nm-vpn-helpers.h
@@ -19,11 +19,7 @@ gboolean nm_vpn_supports_ipv6(NMConnection *connection);
 
 const NmcVpnPasswordName *nm_vpn_get_secret_names(const char *service_type);
 
-gboolean nm_vpn_openconnect_authenticate_helper(const char *host,
-                                                char      **cookie,
-                                                char      **gateway,
-                                                char      **gwcert,
-                                                int        *status,
-                                                GError    **error);
+gboolean
+nm_vpn_openconnect_authenticate_helper(NMSettingVpn *s_vpn, GPtrArray *secrets, GError **error);
 
 #endif /* __NM_VPN_HELPERS_H__ */