File 0001-GlobalProtect-Add-external-browser-with-CAS-support.patch of Package openconnect
diff '--color=auto' -urN openconnect-9.12/auth-globalprotect.c openconnect-9.12-new/auth-globalprotect.c
--- openconnect-9.12/auth-globalprotect.c 2023-04-11 15:48:19.000000000 +0200
+++ openconnect-9.12-new/auth-globalprotect.c 2024-07-24 15:32:07.127698518 +0200
@@ -24,6 +24,20 @@
#include <ctype.h>
#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifndef _WIN32
+#include <pwd.h>
+#include <sys/inotify.h>
+#endif
+
+#define MAX_EVENTS 2
+#define LEN_NAME 32
+#define EVENT_SIZE ( sizeof (struct inotify_event) ) /*size of one event*/
+#define BUF_LEN ( MAX_EVENTS * ( EVENT_SIZE + LEN_NAME ))
+
+#define GP_DATA_FILE "globalprotect.dat"
struct login_context {
char *username; /* Username that has already succeeded in some form */
@@ -31,6 +45,7 @@
char *portal_userauthcookie; /* portal-userauthcookie (from global-protect/getconfig.esp) */
char *portal_prelogonuserauthcookie; /* portal-prelogonuserauthcookie (from global-protect/getconfig.esp) */
struct oc_auth_form *form;
+ int cas_auth; /* Flag indicating whether cas auth is to be used */
};
void gpst_common_headers(struct openconnect_info *vpninfo,
@@ -59,6 +74,32 @@
return "Windows";
}
+static char *get_user_cache_dir(void)
+{
+ char *cache_dir;
+ int cache_dir_len;
+#ifndef _WIN32
+ struct passwd *pw = getpwuid(getuid());
+ char *home = strdup(pw->pw_dir);
+#else
+ /* TODO: Add WIN32 implementation */
+ char *home = strdup("");
+#endif
+
+ /* Ensure that the openconnect cache dir exists */
+ cache_dir_len = strlen(home) + strlen("/.cache/openconnect/") + 1;
+ cache_dir = malloc(cache_dir_len);
+ snprintf(cache_dir, cache_dir_len, "%s/.cache/openconnect/", home);
+ free(home);
+
+#ifndef _WIN32
+ mkdir(cache_dir, 0700);
+#else
+ CreateDirectory(cache_dir, NULL);
+#endif
+
+ return cache_dir;
+}
/* Parse pre-login response ({POST,GET} /{global-protect,ssl-vpn}/pre-login.esp)
*
@@ -81,11 +122,18 @@
char *prompt = NULL, *username_label = NULL, *password_label = NULL;
char *s = NULL, *saml_method = NULL, *saml_path = NULL;
int result = -EINVAL;
+ int default_browser = 0;
+ int cas_auth = 0;
if (!xmlnode_is_named(xml_node, "prelogin-response"))
goto out;
for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
+ if (xmlnode_is_named(xml_node, "saml-default-browser"))
+ default_browser = xmlnode_bool_or_int_value(xml_node);
+ if (xmlnode_is_named(xml_node, "cas-auth"))
+ cas_auth = xmlnode_bool_or_int_value(xml_node);
+
xmlnode_get_val(xml_node, "saml-request", &s);
xmlnode_get_val(xml_node, "saml-auth-method", &saml_method);
xmlnode_get_trimmed_val(xml_node, "authentication-message", &prompt);
@@ -94,6 +142,11 @@
/* XX: should we save the certificate username from <ccusername/> ? */
}
+ if (default_browser != 0) {
+ vpn_progress(vpninfo, PRG_INFO, "Using default browser\n");
+ ctx->cas_auth = cas_auth;
+ }
+
if (saml_method && s) {
/* Allow the legacy workflow (no GUI setting up open_webview) to keep working */
if (!vpninfo->open_webview && ctx->portal_userauthcookie)
@@ -638,8 +691,8 @@
}
if (!keep_urlpath) {
orig_path = vpninfo->urlpath;
- if (asprintf(&vpninfo->urlpath, "%s/prelogin.esp?tmp=tmp&clientVer=4100&clientos=%s",
- portal ? "global-protect" : "ssl-vpn", gpst_os_name(vpninfo)) < 0) {
+ if (asprintf(&vpninfo->urlpath, "%s/prelogin.esp?tmp=tmp&clientVer=4100&clientos=%s&default-browser=4&cas-support=yes",
+ portal ? "global-protect" : "ssl-vpn", gpst_os_name(vpninfo)) < 0) {
result = -ENOMEM;
goto out;
}
@@ -688,6 +741,8 @@
append_opt(request_body, "os-version", vpninfo->platname);
append_opt(request_body, "server", vpninfo->hostname);
append_opt(request_body, "computer", vpninfo->localname);
+ if (ctx->cas_auth && vpninfo->sso_token_cookie)
+ append_opt(request_body, "token", vpninfo->sso_token_cookie);
if (ctx->portal_userauthcookie)
append_opt(request_body, "portal-userauthcookie", ctx->portal_userauthcookie);
if (ctx->portal_prelogonuserauthcookie)
@@ -697,9 +752,11 @@
append_opt(request_body, "preferred-ip", vpninfo->ip_info.addr);
if (vpninfo->ip_info.addr6)
append_opt(request_body, "preferred-ipv6", vpninfo->ip_info.addr);
- if (ctx->form->action)
- append_opt(request_body, "inputStr", ctx->form->action);
- append_form_opts(vpninfo, ctx->form, request_body);
+ if (ctx->form) {
+ if (ctx->form->action)
+ append_opt(request_body, "inputStr", ctx->form->action);
+ append_form_opts(vpninfo, ctx->form, request_body);
+ }
if ((result = buf_error(request_body)))
goto out;
@@ -717,14 +774,18 @@
/* Invalid username/password; reuse same form, but blank,
* unless we just did a blind retry.
*/
- nuke_opt_values(ctx->form->opts);
+ if (ctx->form)
+ nuke_opt_values(ctx->form->opts);
+ else
+ blind_retry = 0;
+
if (!blind_retry)
goto got_form;
else
blind_retry = 0;
} else {
/* Save successful username */
- if (!ctx->username)
+ if (!ctx->username && ctx->form && ctx->form->opts->_value)
ctx->username = strdup(ctx->form->opts->_value);
if (result == -EAGAIN) {
/* New form is already populated from the challenge */
@@ -736,7 +797,7 @@
*/
portal = 0;
if (ctx->portal_userauthcookie || ctx->portal_prelogonuserauthcookie ||
- (strcmp(ctx->form->auth_id, "_challenge") && !ctx->alt_secret)) {
+ (ctx->form &&(strcmp(ctx->form->auth_id, "_challenge")) && !ctx->alt_secret)) {
blind_retry = 1;
goto replay_form;
}
@@ -839,3 +900,233 @@
free(xml_buf);
return result;
}
+
+static int parse_callback_file (struct openconnect_info *vpninfo, const char *data_file)
+{
+ FILE *fptr;
+ char *data;
+ int len;
+ void *xml = NULL;
+ xmlDocPtr xml_doc;
+ xmlNode *xml_node;
+ char *comment = NULL;
+ char *prelogin_cookie = NULL;
+ char *saml_username = NULL;
+ int size;
+
+ /* Open callback data file */
+ fptr = fopen(data_file, "r");
+ if (!fptr) {
+ vpn_progress(vpninfo, PRG_INFO, "Failed to open file %s\n", data_file);
+ return -1;
+ }
+
+ /* Get file size */
+ fseek(fptr, 0L, SEEK_END);
+ size = ftell(fptr);
+ fseek(fptr, 0L, SEEK_SET);
+
+ /* Read data */
+ data = malloc(size + 1);
+ memset(data, 0, size + 1);
+ fread(data, size, 1, fptr);
+ fclose(fptr);
+
+ if (strstr(data, "cas-as%3D")) {
+ char *token = strstr (data, "token%3D");
+ char *un = strstr (data, "un%3D");
+
+ if (!token || !un) {
+ free(data);
+ return -1;
+ }
+
+ vpn_progress(vpninfo, PRG_INFO, "CAS method for user %s\n", un);
+
+ saml_username = malloc (token - un);
+ if (saml_username) {
+ strncpy(saml_username, un + 5, token - un - 5 - 3);
+ free(vpninfo->sso_username);
+ vpninfo->sso_username = saml_username;
+ }
+
+ vpninfo->sso_token_cookie = strdup(token + 8);
+ free(data);
+
+ return 0;
+ }
+
+ vpn_progress(vpninfo, PRG_INFO, "Token method\n");
+
+ xml = openconnect_base64_decode (&len, data);
+ free(data);
+
+ vpn_progress(vpninfo, PRG_INFO, "xml (%d): %s\n", len, (char*)xml);
+
+ xml_doc = xmlReadMemory((char*)xml, len, NULL, NULL, XML_PARSE_NOERROR|XML_PARSE_RECOVER);
+ free(xml);
+
+ xml_node = xmlDocGetRootElement(xml_doc);
+ if (!xml_node)
+ return -1;
+
+ for (xmlNode *x = xml_node->children; x; x = x->next) {
+ /* Weird protocol response...
+ * <html>
+ * <!-- <saml-auth-status>1</saml-auth-status> // Authentication Status 1 = OK
+ * <prelogin-cookie>TOKEN</prelogin-cookie> // Prelogin Cookie
+ * <saml-username>NAME</saml-username> // Username
+ * <saml-slo>no</saml-slo> // SAML Single Log-Out (SLO)
+ * <saml-SessionNotOnOrAfter>2023-11-18T03:13:37.368Z</saml-SessionNotOnOrAfter> // Session Lifetime, used for reconnect
+ * -></html>
+ */
+ if (!xmlnode_get_val(x, "comment", &comment)) {
+ /* Create a parent xml node for parsing */
+ char *buf = malloc(strlen("<parent>") + strlen(comment) + strlen("</parent>"));
+
+ strcpy(buf, "<parent>");
+ strcat(buf, comment);
+ strcat(buf, "</parent>");
+
+ xmlDocPtr xml2_doc = xmlReadMemory((char*)buf, strlen(buf), NULL, NULL, XML_PARSE_NOERROR|XML_PARSE_RECOVER);
+ free(buf);
+ xmlNode *xml2_node = xmlDocGetRootElement(xml2_doc);
+
+ for (xmlNode *y = xml2_node->children; y; y = y->next) {
+ vpn_progress(vpninfo, PRG_DEBUG, "-> Got node: %s\n", y->name);
+ xmlnode_get_val(y, "prelogin-cookie", &prelogin_cookie);
+ xmlnode_get_val(y, "saml-username", &saml_username);
+ }
+ xmlFreeDoc(xml2_doc);
+ }
+ }
+
+ if (saml_username) {
+ vpn_progress(vpninfo, PRG_INFO, "saml_username: %s\n", saml_username);
+ free(vpninfo->sso_username);
+ vpninfo->sso_username = strdup(saml_username);
+ }
+
+ if (prelogin_cookie) {
+ vpn_progress(vpninfo, PRG_INFO, "prelogin_cookie: %s\n", prelogin_cookie);
+ free(vpninfo->sso_token_cookie);
+ free(vpninfo->sso_cookie_value);
+ vpninfo->sso_token_cookie = strdup("prelogin_cookie");
+ vpninfo->sso_cookie_value = strdup(prelogin_cookie);
+ }
+
+ return 0;
+}
+
+int gpst_handle_external_browser(struct openconnect_info *vpninfo)
+{
+ int ret = -1;
+ char *data_file = NULL;
+ char *uri = NULL;
+ char *oc_cache_dir = NULL;
+ int data_file_len;
+
+ oc_cache_dir = get_user_cache_dir ();
+
+ /* Create data */
+ data_file_len = strlen (oc_cache_dir) + 1 + strlen(GP_DATA_FILE) + 1;
+ data_file = malloc(data_file_len);
+ strcpy(data_file, oc_cache_dir);
+ strcat(data_file, "/");
+ strcat(data_file, GP_DATA_FILE);
+
+ /* Check whether the sso_login is actually a data field */
+ if (strlen(vpninfo->sso_login) > 22 && strncmp(vpninfo->sso_login, "data:text/html;base64,", 22) == 0) {
+ int len;
+ void *out = openconnect_base64_decode(&len, vpninfo->sso_login + 22);
+ char *tmp_file;
+ FILE *file;
+ int tmp_file_len;
+
+ vpn_progress(vpninfo, PRG_INFO, "data: %s\n", vpninfo->sso_login);
+ if (!out)
+ goto finish;
+
+ tmp_file_len = strlen(oc_cache_dir) + strlen("/saml.html") + 1;
+ tmp_file = malloc(tmp_file_len);
+ strcpy(tmp_file, oc_cache_dir);
+ strcat(tmp_file, "/saml.html");
+ file = fopen(tmp_file, "w");
+ fwrite((char*)out, 1, len, file);
+ fclose(file);
+
+ free(out);
+
+ uri = malloc(strlen("file:///") + strlen(tmp_file) + 1);
+ strcpy(uri, "file:///");
+ strcat(uri, tmp_file);
+ free(tmp_file);
+ } else {
+ uri = strdup(vpninfo->sso_login);
+ }
+
+#ifndef _WIN32
+ int fd, wd;
+
+ fd = inotify_init();
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
+ goto finish;
+
+ wd = inotify_add_watch(fd, oc_cache_dir, IN_CLOSE_WRITE);
+ if (wd == -1) {
+ vpn_progress(vpninfo, PRG_DEBUG, "Could not watch : %s\n", oc_cache_dir);
+ } else {
+ vpn_progress(vpninfo, PRG_DEBUG, "Watching : %s\n", oc_cache_dir);
+ }
+
+ if (vpninfo->open_ext_browser) {
+ ret = vpninfo->open_ext_browser(vpninfo, uri, vpninfo->cbdata);
+ } else {
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to spawn external browser for %s\n"),
+ vpninfo->sso_login);
+ goto finish;
+ }
+
+ /* TODO: Add timeout check to prevent lock up */
+ while (1) {
+ int i = 0, length;
+ char buffer[BUF_LEN];
+
+ length = read(fd, buffer, BUF_LEN);
+
+ while (i < length) {
+ struct inotify_event *event = (struct inotify_event *) &buffer[i];
+
+ if (event->len) {
+ if ((event->mask & IN_CLOSE_WRITE) && !(event->mask & IN_ISDIR) && strcmp(event->name, GP_DATA_FILE) == 0) {
+ vpn_progress(vpninfo, PRG_INFO, "The file %s was written.\n", event->name);
+ ret = parse_callback_file (vpninfo, data_file);
+ goto finish;
+ }
+ }
+ i += EVENT_SIZE + event->len;
+ }
+ }
+#else
+ vpn_progress(vpninfo, PRG_INFO, "Proper callback data handling missing on Windows\n");
+ Sleep(10 * 1000);
+ ret = parse_callback_file (vpninfo, data_file);
+#endif
+
+finish:
+ if (uri)
+ free(uri);
+
+ if (oc_cache_dir)
+ free(oc_cache_dir);
+
+ if (data_file)
+ free(data_file);
+
+ return ret;
+}
diff '--color=auto' -urN openconnect-9.12/auth-globalprotect.c.orig openconnect-9.12-new/auth-globalprotect.c.orig
diff '--color=auto' -urN openconnect-9.12/gp_browser_helper.c openconnect-9.12-new/gp_browser_helper.c
--- openconnect-9.12/gp_browser_helper.c 1970-01-01 01:00:00.000000000 +0100
+++ openconnect-9.12-new/gp_browser_helper.c 2024-07-24 15:32:07.127698518 +0200
@@ -0,0 +1,101 @@
+/*
+ * OpenConnect (SSL + DTLS) VPN client
+ *
+ * Copyright © 2023 Jan-Michael Brummer <jan-michael.brummer1@volkswagen.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifndef _WIN32
+#include <pwd.h>
+#endif
+
+#define DATA_FILE_NAME "globalprotect.dat"
+
+static char *get_user_cache_dir(void)
+{
+ char *cache_dir;
+ int cache_dir_len;
+#ifndef _WIN32
+ struct passwd *pw = getpwuid(getuid());
+ char *home = strdup(pw->pw_dir);
+#else
+ /* TODO: Add WIN32 implementation */
+ char *home = strdup("");
+#endif
+
+ /* Ensure that the openconnect cache dir exists */
+ cache_dir_len = strlen(home) + strlen("/.cache/openconnect/") + 1;
+ cache_dir = malloc(cache_dir_len);
+ snprintf(cache_dir, cache_dir_len, "%s/.cache/openconnect/", home);
+ free(home);
+
+#ifndef _WIN32
+ mkdir(cache_dir, 0700);
+#else
+ mkdir(cache_dir);
+#endif
+
+ return cache_dir;
+}
+
+int main(int argc, char **argv)
+{
+ FILE *fp;
+ char *callback_data;
+ char *oc_cache_dir;
+ char *data_file;
+ int data_file_len;
+ int ret = 0;
+
+ if (argc < 2)
+ return -1;
+
+ callback_data = argv[1];
+
+ oc_cache_dir = get_user_cache_dir();
+
+ data_file_len = strlen (oc_cache_dir) + strlen (DATA_FILE_NAME) + 2;
+ data_file = malloc (data_file_len);
+ strcpy(data_file, oc_cache_dir);
+ strcat(data_file, "/");
+ strcat(data_file, DATA_FILE_NAME);
+
+ fp = fopen(data_file, "w");
+ if (fp) {
+ /* callback_data format:
+ * globalprotectcallback:DATA
+ *
+ * DATA:
+ * - Using CAS: cas-as=1&un=<USER>&token=<TOKEN>
+ * - Without CAS: <TOKEN>
+ *
+ * As we just want the data, we skip the prefix
+ */
+ fwrite(callback_data + strlen("globalprotectcallback:"),
+ strlen(callback_data) - strlen("globalprotectcallback:"),
+ 1,
+ fp);
+ fclose (fp);
+ ret = 0;
+ }
+
+ free(data_file);
+ free(oc_cache_dir);
+
+ return ret;
+}
diff '--color=auto' -urN openconnect-9.12/gp_browser_helper.desktop openconnect-9.12-new/gp_browser_helper.desktop
--- openconnect-9.12/gp_browser_helper.desktop 1970-01-01 01:00:00.000000000 +0100
+++ openconnect-9.12-new/gp_browser_helper.desktop 2024-07-24 15:32:07.128698526 +0200
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Version=1.0
+Name=OpenConnect Browser Helper for GlobalProtect
+Type=Application
+Terminal=false
+NoDisplay=true
+Exec=gp_browser_helper %U
+MimeType=x-scheme-handler/globalprotectcallback;
\ Kein Zeilenumbruch am Dateiende.
diff '--color=auto' -urN openconnect-9.12/library.c openconnect-9.12-new/library.c
--- openconnect-9.12/library.c 2023-05-11 17:21:08.000000000 +0200
+++ openconnect-9.12-new/library.c 2024-07-24 15:32:07.128698526 +0200
@@ -176,6 +176,7 @@
.obtain_cookie = gpst_obtain_cookie,
.sso_detect_done = gpst_sso_detect_done,
.udp_protocol = "ESP",
+ .handle_external_browser = gpst_handle_external_browser,
#ifdef HAVE_ESP
.udp_setup = esp_setup,
.udp_mainloop = esp_mainloop,
@@ -1709,15 +1710,20 @@
vpninfo->sso_cookie_value = NULL;
vpninfo->sso_username = NULL;
- /* Handle the special Cisco external browser mode */
- if (vpninfo->sso_browser_mode && !strcmp(vpninfo->sso_browser_mode, "external")) {
- ret = handle_external_browser(vpninfo);
- } else if (vpninfo->open_webview) {
- ret = vpninfo->open_webview(vpninfo, vpninfo->sso_login, vpninfo->cbdata);
- } else {
- vpn_progress(vpninfo, PRG_ERR,
- _("No SSO handler\n")); /* XX: print more debugging info */
- ret = -EINVAL;
+ if (vpninfo->proto->handle_external_browser)
+ ret = vpninfo->proto->handle_external_browser(vpninfo);
+
+ if (ret != 0) {
+ /* Handle the special Cisco external browser mode */
+ if (vpninfo->sso_browser_mode && !strcmp(vpninfo->sso_browser_mode, "external")) {
+ ret = handle_external_browser(vpninfo);
+ } else if (vpninfo->open_webview) {
+ ret = vpninfo->open_webview(vpninfo, vpninfo->sso_login, vpninfo->cbdata);
+ } else {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("No SSO handler\n")); /* XX: print more debugging info */
+ ret = -EINVAL;
+ }
}
if (!ret) {
for (opt = form->opts; opt; opt = opt->next) {
diff '--color=auto' -urN openconnect-9.12/library.c.orig openconnect-9.12-new/library.c.orig
diff '--color=auto' -urN openconnect-9.12/Makefile.am openconnect-9.12-new/Makefile.am
--- openconnect-9.12/Makefile.am 2023-05-19 18:12:15.000000000 +0200
+++ openconnect-9.12-new/Makefile.am 2024-07-24 15:32:07.126698511 +0200
@@ -16,6 +16,7 @@
endif
lib_LTLIBRARIES = libopenconnect.la
+bin_PROGRAMS = gp_browser_helper
sbin_PROGRAMS = openconnect
man8_MANS = openconnect.8
noinst_PROGRAMS :=
@@ -23,6 +24,16 @@
AM_CFLAGS = @WFLAGS@
AM_CPPFLAGS = -DLOCALEDIR="\"$(localedir)\""
+# GlobalProtect Browser Helper
+gp_browser_helper_SOURCES: gp_browser_helper.c
+
+desktopdir = $(datadir)/applications
+dist_desktop_DATA = gp_browser_helper.desktop
+install-data-hook:
+ if which update-desktop-database>/dev/null 2>&1 ; then \
+ update-desktop-database; \
+ fi
+
openconnect_SOURCES = xml.c main.c
openconnect_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) \
$(LIBXML2_CFLAGS) $(JSON_CFLAGS) $(LIBPROXY_CFLAGS) \
diff '--color=auto' -urN openconnect-9.12/Makefile.am.orig openconnect-9.12-new/Makefile.am.orig
diff '--color=auto' -urN openconnect-9.12/openconnect-internal.h openconnect-9.12-new/openconnect-internal.h
--- openconnect-9.12/openconnect-internal.h 2023-05-19 18:12:15.000000000 +0200
+++ openconnect-9.12-new/openconnect-internal.h 2024-07-24 15:32:07.129698533 +0200
@@ -861,6 +861,9 @@
/* Catch probe packet confirming the (UDP) session */
int (*udp_catch_probe)(struct openconnect_info *vpninfo, struct pkt *p);
+
+ /* External browser */
+ int (*handle_external_browser)(struct openconnect_info *vpninfo);
};
static inline struct pkt *dequeue_packet(struct pkt_q *q)
@@ -1394,6 +1397,7 @@
int gpst_esp_send_probes(struct openconnect_info *vpninfo);
int gpst_esp_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt);
int gpst_sso_detect_done(struct openconnect_info *vpninfo, const struct oc_webview_result *result);
+int gpst_handle_external_browser(struct openconnect_info *vpninfo);
/* lzs.c */
int lzs_decompress(unsigned char *dst, int dstlen, const unsigned char *src, int srclen);
diff '--color=auto' -urN openconnect-9.12/openconnect-internal.h.orig openconnect-9.12-new/openconnect-internal.h.orig