From b33b3af862587ed8ce9df9737871e29b38584681 Mon Sep 17 00:00:00 2001 From: Corey Wright Date: Fri, 9 Aug 2019 16:53:24 -0500 Subject: [PATCH 1/5] GlobalProtect: Refactor HIP functionality into separate function Refactor calling check_or_submit_hip_report() and follow-up behavior out of gpst_setup() and into separate function check_and_maybe_submit_hip_report() so it can be called from elsewhere. Signed-off-by: Corey Wright --- gpst.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/gpst.c b/gpst.c index 91b57cba3..99650b72e 100644 --- a/gpst.c +++ b/gpst.c @@ -1003,6 +1003,22 @@ static int run_hip_script(struct openconnect_info *vpninfo) #endif /* !_WIN32 && !__native_client__ */ } +static int check_and_maybe_submit_hip_report(struct openconnect_info *vpninfo) +{ + int ret; + + ret = check_or_submit_hip_report(vpninfo, NULL); + if (ret == -EAGAIN) { + vpn_progress(vpninfo, PRG_DEBUG, + _("Gateway says HIP report submission is needed.\n")); + ret = run_hip_script(vpninfo); + } else if (ret == 0) + vpn_progress(vpninfo, PRG_DEBUG, + _("Gateway says no HIP report submission is needed.\n")); + + return ret; +} + int gpst_setup(struct openconnect_info *vpninfo) { int ret; @@ -1017,16 +1033,9 @@ int gpst_setup(struct openconnect_info *vpninfo) goto out; /* Check HIP */ - ret = check_or_submit_hip_report(vpninfo, NULL); - if (ret == -EAGAIN) { - vpn_progress(vpninfo, PRG_DEBUG, - _("Gateway says HIP report submission is needed.\n")); - ret = run_hip_script(vpninfo); - if (ret != 0) - goto out; - } else if (ret == 0) - vpn_progress(vpninfo, PRG_DEBUG, - _("Gateway says no HIP report submission is needed.\n")); + ret = check_and_maybe_submit_hip_report(vpninfo); + if (ret) + goto out; /* We do NOT actually start the HTTPS tunnel yet if we want to * use ESP, because the ESP tunnel won't work if the HTTPS tunnel -- GitLab From ead96540f588140cf9ca1c1f8227061b7d1ba0f4 Mon Sep 17 00:00:00 2001 From: Corey Wright Date: Sun, 29 Sep 2019 22:56:03 -0500 Subject: [PATCH 2/5] Add framework to execute trojan periodically Some protocols, eg GlobalProtect, can require a client to execute the trojan at a specified interval, or at least check with the server if the trojan needs to be executed and the subsequent report submitted. To support this trojan interval, add: * variables to the `openconnect_info` struct * `trojan_interval` configures the number of seconds between trojan executions * `last_trojan` records the time of last trojan execution * `trojan_check_deadline()` to calculate if/when the trojan needs to be executed (based on aforementioned variables and the current time) Signed-off-by: Corey Wright --- mainloop.c | 14 ++++++++++++++ openconnect-internal.h | 3 +++ 2 files changed, 17 insertions(+) diff --git a/mainloop.c b/mainloop.c index af283fb47..9b12a1f63 100644 --- a/mainloop.c +++ b/mainloop.c @@ -392,3 +392,17 @@ int keepalive_action(struct keepalive_info *ka, int *timeout) return KA_NONE; } + +int trojan_check_deadline(struct openconnect_info *vpninfo, int *timeout) +{ + time_t now = time(NULL); + + if (vpninfo->trojan_interval && + ka_check_deadline(timeout, now, + vpninfo->last_trojan + vpninfo->trojan_interval)) { + vpninfo->last_trojan = now; + return 1; + } else { + return 0; + } +} diff --git a/openconnect-internal.h b/openconnect-internal.h index 30c65848a..514b15982 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -444,6 +444,8 @@ struct openconnect_info { char *dtls_ciphers; char *dtls12_ciphers; char *csd_wrapper; + int trojan_interval; + time_t last_trojan; int no_http_keepalive; int dump_http_traffic; @@ -1014,6 +1016,7 @@ int queue_new_packet(struct pkt_q *q, void *buf, int len); int keepalive_action(struct keepalive_info *ka, int *timeout); int ka_stalled_action(struct keepalive_info *ka, int *timeout); int ka_check_deadline(int *timeout, time_t now, time_t due); +int trojan_check_deadline(struct openconnect_info *vpninfo, int *timeout); /* xml.c */ ssize_t read_file_into_string(struct openconnect_info *vpninfo, const char *fname, -- GitLab From a7e0826a8f5ffc1e67ea92bbec41e7d4514c47d8 Mon Sep 17 00:00:00 2001 From: Corey Wright Date: Fri, 9 Aug 2019 17:04:24 -0500 Subject: [PATCH 3/5] GlobalProtect: Add HIP check timeout separate from (re)connecting Instead of only submitting a GlobalProtect (GP) Host Information Profile (HIP) check after connecting to the GP gateway, submit one as frequently as specified by the GP portal or, if bypassing the GP portal by logging in directly to the GP gateway, every 3600 seconds (ie 1 hour), which seems a reasonable default based on HIP report intervals seen in the wild. When logging into the GP portal and getting its configuration, ie `POST /global-protect/getconfig.esp`, use the provided `hip-report-interval` value as the number of seconds to wait between HIP report checks. 3600 Signed-off-by: Corey Wright --- auth-globalprotect.c | 12 ++++++++++-- gpst.c | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/auth-globalprotect.c b/auth-globalprotect.c index dd9a5ea5e..7c108667d 100644 --- a/auth-globalprotect.c +++ b/auth-globalprotect.c @@ -319,10 +319,12 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node, { struct oc_auth_form *form; xmlNode *x = NULL; + xmlNode *grandchild = NULL; struct oc_form_opt_select *opt; struct oc_text_buf *buf = NULL; int max_choices = 0, result; char *portal = NULL; + char *hip_interval = NULL; form = calloc(1, sizeof(*form)); if (!form) @@ -345,13 +347,18 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node, * The portal contains a ton of stuff, but basically none of it is * useful to a VPN client that wishes to give control to the client * user, as opposed to the VPN administrator. The exception is the - * list of gateways in policy/gateways/external/list + * list of gateways in policy/gateways/external/list and the seconds + * between HIP checks in policy/hip-collection/hip-report-interval */ if (xmlnode_is_named(xml_node, "policy")) { for (x = xml_node->children, xml_node = NULL; x; x = x->next) { if (xmlnode_is_named(x, "gateways")) xml_node = x; - else + else if (xmlnode_is_named(x, "hip-collection")) { + for (grandchild = x->children; grandchild; grandchild = grandchild->next) + if (!xmlnode_get_val(grandchild, "hip-report-interval", &hip_interval)) + vpninfo->trojan_interval = atoi(hip_interval); + } else xmlnode_get_val(x, "portal-name", &portal); } } @@ -444,6 +451,7 @@ gateways: out: buf_free(buf); free(portal); + free(hip_interval); free_auth_form(form); return result; } diff --git a/gpst.c b/gpst.c index 99650b72e..3f8a2349e 100644 --- a/gpst.c +++ b/gpst.c @@ -605,6 +605,12 @@ static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_ vpninfo->ssl_times.dpd = 10; vpninfo->ssl_times.keepalive = vpninfo->esp_ssl_fallback = vpninfo->ssl_times.dpd; + /* Default HIP check to 3600 seconds unless already set by + * portal config */ + if (!vpninfo->trojan_interval) + vpninfo->trojan_interval = 3600; + vpninfo->last_trojan = time(NULL); + free(s); return 0; } @@ -1069,6 +1075,25 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) /* Rekey if needed */ if (keepalive_action(&vpninfo->ssl_times, timeout) == KA_REKEY) goto do_rekey; + /* Submit HIP check if needed and only do it here, ie when + * using an ESP tunnel, because when using an HTTPS tunnel the + * one supported HTTPS connection is used by the HTTPS tunnel + * and unavailable for checking and submitting the HIP + * report */ + else if (trojan_check_deadline(vpninfo, timeout)) { + vpn_progress(vpninfo, PRG_INFO, _("GlobalProtect HIP check due\n")); + ret = check_and_maybe_submit_hip_report(vpninfo); + /* Close HTTPS connection to prevent trying to read + * from it, eg fetching HTTPS response to next HIP + * check submission, after the GP gateway half-closes + * it after 60 seconds. */ + openconnect_close_https(vpninfo, 0); + if (ret) { + vpn_progress(vpninfo, PRG_ERR, _("HIP check or report failed\n")); + vpninfo->quit_reason = "HIP check or report failed"; + return ret; + } + } return 0; case DTLS_SECRET: case DTLS_SLEEPING: -- GitLab From c3493a856851f7b4ca5b4d826e0c19275fe45426 Mon Sep 17 00:00:00 2001 From: Corey Wright Date: Sun, 29 Sep 2019 23:37:58 -0500 Subject: [PATCH 4/5] Allow user of application or API to override trojan interval Add `openconnect_set_trojan_interval` function to library and `--force-trojan` option and documentation to binary to allow a user of either to specify an interval that overrides both the server-requested and default intervals for executing the trojan. Signed-off-by: Corey Wright --- libopenconnect.map.in | 5 +++++ library.c | 5 +++++ main.c | 6 ++++++ openconnect.8.in | 7 +++++++ openconnect.h | 6 +++++- 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/libopenconnect.map.in b/libopenconnect.map.in index 58f04e763..350180c12 100644 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@ -103,6 +103,11 @@ OPENCONNECT_5_5 { openconnect_set_version_string; } OPENCONNECT_5_4; +OPENCONNECT_5_6 { + global: + openconnect_set_trojan_interval; +} OPENCONNECT_5_5; + OPENCONNECT_PRIVATE { global: @SYMVER_TIME@ @SYMVER_GETLINE@ @SYMVER_JAVA@ @SYMVER_ASPRINTF@ @SYMVER_VASPRINTF@ @SYMVER_WIN32_STRERROR@ openconnect_fopen_utf8; diff --git a/library.c b/library.c index e517fdc95..2f157d5d0 100644 --- a/library.c +++ b/library.c @@ -577,6 +577,11 @@ void openconnect_set_dpd(struct openconnect_info *vpninfo, int min_seconds) vpninfo->dtls_times.dpd = vpninfo->ssl_times.dpd = 2; } +void openconnect_set_trojan_interval(struct openconnect_info *vpninfo, int seconds) +{ + vpninfo->trojan_interval = seconds; +} + int openconnect_get_idle_timeout(struct openconnect_info *vpninfo) { return vpninfo->idle_timeout; diff --git a/main.c b/main.c index 19d64377e..ec66938d5 100644 --- a/main.c +++ b/main.c @@ -161,6 +161,7 @@ enum { OPT_DTLS12_CIPHERS, OPT_DUMP_HTTP, OPT_FORCE_DPD, + OPT_FORCE_TROJAN, OPT_GNUTLS_DEBUG, OPT_JUNIPER, OPT_KEY_PASSWORD_FROM_FSID, @@ -266,6 +267,7 @@ static const struct option long_options[] = { OPTION("no-http-keepalive", 0, OPT_NO_HTTP_KEEPALIVE), OPTION("no-cert-check", 0, OPT_NO_CERT_CHECK), OPTION("force-dpd", 1, OPT_FORCE_DPD), + OPTION("force-trojan", 1, OPT_FORCE_TROJAN), OPTION("non-inter", 0, OPT_NON_INTER), OPTION("dtls-local-port", 1, OPT_DTLS_LOCAL_PORT), OPTION("token-mode", 1, OPT_TOKEN_MODE), @@ -900,6 +902,7 @@ static void usage(void) printf("\n%s:\n", _("Server bugs")); printf(" --no-http-keepalive %s\n", _("Disable HTTP connection re-use")); printf(" --no-xmlpost %s\n", _("Do not attempt XML POST authentication")); + printf(" --force-trojan=INTERVAL %s\n", _("Run trojan every INTERVAL secs")); printf("\n"); @@ -1447,6 +1450,9 @@ int main(int argc, char **argv) case OPT_FORCE_DPD: openconnect_set_dpd(vpninfo, atoi(config_arg)); break; + case OPT_FORCE_TROJAN: + openconnect_set_trojan_interval(vpninfo, atoi(config_arg)); + break; case OPT_DTLS_LOCAL_PORT: vpninfo->dtls_local_port = atoi(config_arg); break; diff --git a/openconnect.8.in b/openconnect.8.in index b7ba20fe1..1a52e8c23 100644 --- a/openconnect.8.in +++ b/openconnect.8.in @@ -15,6 +15,7 @@ openconnect \- Multi-protocol VPN client, for Cisco AnyConnect VPNs and others .OP \-d,\-\-deflate .OP \-D,\-\-no\-deflate .OP \-\-force\-dpd interval +.OP \-\-force\-trojan interval .OP \-F,\-\-form\-entry form:opt=value .OP \-g,\-\-usergroup group .OP \-h,\-\-help @@ -177,6 +178,12 @@ Use .I INTERVAL as minimum Dead Peer Detection interval for CSTP and DTLS, forcing use of DPD even when the server doesn't request it. .TP +.B \-\-force\-trojan=INTERVAL +Use +.I INTERVAL +as trojan binary or script execution interval, overriding server requested and +default interval. +.TP .B \-g,\-\-usergroup=GROUP Use .I GROUP diff --git a/openconnect.h b/openconnect.h index 0f83e37cd..0991ee5ea 100644 --- a/openconnect.h +++ b/openconnect.h @@ -33,9 +33,12 @@ extern "C" { #endif #define OPENCONNECT_API_VERSION_MAJOR 5 -#define OPENCONNECT_API_VERSION_MINOR 5 +#define OPENCONNECT_API_VERSION_MINOR 6 /* + * API version 5.6: + * - Add openconnect_set_trojan_interval() + * * API version 5.5 (v8.00; 2019-01-05): * - add openconnect_set_version_string() * - add openconnect_set_key_password() @@ -521,6 +524,7 @@ int openconnect_set_key_password(struct openconnect_info *vpninfo, const char *p const char *openconnect_get_ifname(struct openconnect_info *); void openconnect_set_reqmtu(struct openconnect_info *, int reqmtu); void openconnect_set_dpd(struct openconnect_info *, int min_seconds); +void openconnect_set_trojan_interval(struct openconnect_info *, int seconds); int openconnect_get_idle_timeout(struct openconnect_info *); /* The returned structures are owned by the library and may be freed/replaced -- GitLab From 88223fbdee9b21afa77b850b147f4da5bf46d6ba Mon Sep 17 00:00:00 2001 From: Corey Wright Date: Sat, 10 Aug 2019 12:10:05 -0500 Subject: [PATCH 5/5] GlobalProtect: Allow overriding trojan interval If `trojan_interval` is initialized to a non-zero value, then allow it to override both the GP portal's HIP report interval and our 1-hour default. Signed-off-by: Corey Wright --- auth-globalprotect.c | 9 ++++++--- gpst.c | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/auth-globalprotect.c b/auth-globalprotect.c index 7c108667d..1e6ba79ad 100644 --- a/auth-globalprotect.c +++ b/auth-globalprotect.c @@ -355,9 +355,12 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node, if (xmlnode_is_named(x, "gateways")) xml_node = x; else if (xmlnode_is_named(x, "hip-collection")) { - for (grandchild = x->children; grandchild; grandchild = grandchild->next) - if (!xmlnode_get_val(grandchild, "hip-report-interval", &hip_interval)) - vpninfo->trojan_interval = atoi(hip_interval); + /* don't find and set HIP interval if already + * set with --force-trojan */ + if (!vpninfo->trojan_interval) + for (grandchild = x->children; grandchild; grandchild = grandchild->next) + if (!xmlnode_get_val(grandchild, "hip-report-interval", &hip_interval)) + vpninfo->trojan_interval = atoi(hip_interval); } else xmlnode_get_val(x, "portal-name", &portal); } diff --git a/gpst.c b/gpst.c index 3f8a2349e..f4b45023b 100644 --- a/gpst.c +++ b/gpst.c @@ -606,7 +606,7 @@ static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_ vpninfo->ssl_times.keepalive = vpninfo->esp_ssl_fallback = vpninfo->ssl_times.dpd; /* Default HIP check to 3600 seconds unless already set by - * portal config */ + * --force-trojan or portal config */ if (!vpninfo->trojan_interval) vpninfo->trojan_interval = 3600; vpninfo->last_trojan = time(NULL); -- GitLab