Skip to content
Permalink
Browse files
Merge branch 'improve_HIP_check'
https: //gitlab.com/openconnect/openconnect/-/merge_requests/56
Signed-off-by: Daniel Lenski <dlenski@gmail.com>
  • Loading branch information
dlenski committed Mar 17, 2020
2 parents 8820105 + 0246406 commit 41eda6d628724704087aef18a85b7e245db33f42
Showing with 178 additions and 59 deletions.
  1. +46 −25 auth-globalprotect.c
  2. +85 −31 gpst.c
  3. +5 −0 libopenconnect.map.in
  4. +5 −0 library.c
  5. +7 −1 main.c
  6. +14 −0 mainloop.c
  7. +3 −0 openconnect-internal.h
  8. +8 −1 openconnect.8.in
  9. +5 −1 openconnect.h
@@ -323,11 +323,12 @@ static int parse_login_xml(struct openconnect_info *vpninfo, xmlNode *xml_node,
static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node, void *cb_data)
{
struct oc_auth_form *form;
xmlNode *x = NULL;
xmlNode *x, *x2, *x3, *gateways = 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)
@@ -349,32 +350,45 @@ 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
* user, as opposed to the VPN administrator. The exceptions are the
* list of gateways in policy/gateways/external/list and the interval
* for 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
for (x = xml_node->children; x; x = x->next) {
if (xmlnode_is_named(x, "gateways")) {
for (x2 = x->children; x2; x2 = x2->next)
if (xmlnode_is_named(x2, "external"))
for (x3 = x2->children; x3; x3 = x3->next)
if (xmlnode_is_named(x3, "list"))
gateways = x3;
} else if (xmlnode_is_named(x, "hip-collection")) {
for (x2 = x->children; x2; x2 = x2->next) {
if (!xmlnode_get_val(x2, "hip-report-interval", &hip_interval)) {
int sec = atoi(hip_interval);
if (!vpninfo->csd_wrapper)
vpn_progress(vpninfo, PRG_INFO, _("Ignoring portal's HIP report interval (%d minutes), because no HIP report script provided.\n"),
sec/60);
else if (vpninfo->trojan_interval)
vpn_progress(vpninfo, PRG_INFO, _("Ignoring portal's HIP report interval (%d minutes), because interval is already set to %d minutes.\n"),
sec/60, vpninfo->trojan_interval/60);
else {
vpninfo->trojan_interval = sec;
vpn_progress(vpninfo, PRG_INFO, _("Portal set HIP report interval to %d minutes).\n"),
sec/60);
}
}
}
} else
xmlnode_get_val(x, "portal-name", &portal);
}
}

if (xml_node) {
for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next)
if (xmlnode_is_named(xml_node, "external")) {
for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
if (xmlnode_is_named(xml_node, "list"))
goto gateways;
}
break;
}
if (!gateways) {
result = -EINVAL;
goto out;
}
result = -EINVAL;
goto out;

gateways:
if (vpninfo->write_new_config) {
buf = buf_alloc();
buf_append(buf, "<GPPortal>\n <ServerList>\n");
@@ -389,7 +403,7 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node,
}

/* first, count the number of gateways */
for (x = xml_node->children; x; x = x->next)
for (x = gateways->children; x; x = x->next)
if (xmlnode_is_named(x, "entry"))
max_choices++;

@@ -401,17 +415,17 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node,

/* each entry looks like <entry name="host[:443]"><description>Label</description></entry> */
vpn_progress(vpninfo, PRG_INFO, _("%d gateway servers available:\n"), max_choices);
for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
if (xmlnode_is_named(xml_node, "entry")) {
for (x = gateways->children; x; x = x->next) {
if (xmlnode_is_named(x, "entry")) {
struct oc_choice *choice = calloc(1, sizeof(*choice));
if (!choice) {
result = -ENOMEM;
goto out;
}

xmlnode_get_prop(xml_node, "name", &choice->name);
for (x = xml_node->children; x; x=x->next)
if (!xmlnode_get_val(x, "description", &choice->label)) {
xmlnode_get_prop(x, "name", &choice->name);
for (x2 = x->children; x2; x2=x2->next)
if (!xmlnode_get_val(x2, "description", &choice->label)) {
if (vpninfo->write_new_config) {
buf_append(buf, " <HostEntry><HostName>");
buf_append_xmlescaped(buf, choice->label);
@@ -425,6 +439,12 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node,
choice->label, choice->name);
}
}
if (!opt->nr_choices) {
vpn_progress(vpninfo, PRG_ERR,
_("GlobalProtect portal configuration lists no gateway servers.\n"));
result = -EINVAL;
goto out;
}
if (!vpninfo->authgroup && opt->nr_choices)
vpninfo->authgroup = strdup(opt->choices[0]->name);

@@ -452,6 +472,7 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node,
out:
buf_free(buf);
free(portal);
free(hip_interval);
free_auth_form(form);
return result;
}
116 gpst.c
@@ -723,6 +723,13 @@ static int gpst_connect(struct openconnect_info *vpninfo)
const char start_tunnel[12] = "START_TUNNEL"; /* NOT zero-terminated */
char buf[256];

/* We do NOT actually start the HTTPS tunnel if ESP is enabled and we received
* ESP keys, because the ESP keys become invalid as soon as the HTTPS tunnel
* is connected! >:-(
*/
if (vpninfo->dtls_state != DTLS_DISABLED && vpninfo->dtls_state != DTLS_NOSECRET)
return 0;

/* Connect to SSL VPN tunnel */
vpn_progress(vpninfo, PRG_DEBUG,
_("Connecting to HTTPS tunnel endpoint ...\n"));
@@ -910,9 +917,14 @@ static int run_hip_script(struct openconnect_info *vpninfo)
if (!vpninfo->csd_wrapper) {
vpn_progress(vpninfo, PRG_ERR,
_("WARNING: Server asked us to submit HIP report with md5sum %s.\n"
"VPN connectivity may be disabled or limited without HIP report submission.\n"
"You need to provide a --csd-wrapper argument with the HIP report submission script.\n"),
vpninfo->csd_token);
" VPN connectivity may be disabled or limited without HIP report submission.\n %s\n"),
vpninfo->csd_token,
#if defined(_WIN32) || defined(__native_client__)
_("However, running the HIP report submission script on this platform is not yet implemented.")
#else
_("You need to provide a --csd-wrapper argument with the HIP report submission script.")
#endif
);
/* XXX: Many GlobalProtect VPNs work fine despite allegedly requiring HIP report submission */
return 0;
}
@@ -1003,6 +1015,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;
@@ -1016,24 +1044,24 @@ int gpst_setup(struct openconnect_info *vpninfo)
if (ret)
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)
/* Always check HIP once (even if no --csd-wrapper specified) */
if (!vpninfo->last_trojan) {
ret = check_and_maybe_submit_hip_report(vpninfo);
if (ret)
goto out;
} else if (ret == 0)
vpn_progress(vpninfo, PRG_DEBUG,
_("Gateway says no HIP report submission is needed.\n"));
}

/* 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
* is connected! >:-(
/* Default HIP re-checking to 3600 seconds unless already set by
* --force-trojan or portal config. There's no point to rechecking
* HIP if --csd-wrapper wasn't specified: either the VPN will
* work despite lack of HIP submission, or it won't.
*/
if (vpninfo->dtls_state == DTLS_DISABLED || vpninfo->dtls_state == DTLS_NOSECRET)
ret = gpst_connect(vpninfo);
if (vpninfo->csd_wrapper && !vpninfo->trojan_interval)
vpninfo->trojan_interval = 3600;
vpninfo->last_trojan = time(NULL);

/* Connect tunnel immediately if ESP is not going to be used */
ret = gpst_connect(vpninfo);

out:
return ret;
@@ -1064,23 +1092,26 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
*timeout = vpninfo->dtls_times.dpd * 1000;
/* fall through */
case DTLS_CONNECTED:
/* Rekey if needed */
/* Rekey or check-and-resubmit HIP if needed */
if (keepalive_action(&vpninfo->ssl_times, timeout) == KA_REKEY)
goto do_rekey;
else if (trojan_check_deadline(vpninfo, timeout))
goto do_recheck_hip;
return 0;
case DTLS_SECRET:
case DTLS_SLEEPING:
if (!ka_check_deadline(timeout, time(NULL), vpninfo->new_dtls_started + 5)) {
/* Allow 5 seconds after configuration for ESP to start */
/* Allow 5 seconds after configuration for ESP to start */
if (!ka_check_deadline(timeout, time(NULL), vpninfo->new_dtls_started + 5))
return 0;
} else {
/* ... before we switch to HTTPS instead */
vpn_progress(vpninfo, PRG_ERR,

/* ... before we switch to HTTPS instead */
vpn_progress(vpninfo, PRG_ERR,
_("Failed to connect ESP tunnel; using HTTPS instead.\n"));
if (gpst_connect(vpninfo)) {
vpninfo->quit_reason = "GPST connect failed";
return 1;
}
/* XX: gpst_connect does nothing if ESP is enabled and has secrets */
vpninfo->dtls_state = DTLS_NOSECRET;
if (gpst_connect(vpninfo)) {
vpninfo->quit_reason = "GPST connect failed";
return 1;
}
break;
case DTLS_NOSECRET:
@@ -1217,6 +1248,29 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
vpninfo->current_ssl_pkt = NULL;
}

if (trojan_check_deadline(vpninfo, timeout)) {
do_recheck_hip:
vpn_progress(vpninfo, PRG_INFO, _("GlobalProtect HIP check due\n"));
/* We could just be lazy and treat this as a reconnect, but that
* would require us to repull the routing configuration and new ESP
* keys, instead of just redoing the HIP check/submission.
*
* Therefore we'll just close the HTTPS tunnel (if up),
* redo the HIP check/submission, and reconnect the HTTPS tunnel
* if needed.
*/
openconnect_close_https(vpninfo, 0);
ret = check_and_maybe_submit_hip_report(vpninfo);
if (ret) {
vpn_progress(vpninfo, PRG_ERR, _("HIP check or report failed\n"));
vpninfo->quit_reason = "HIP check or report failed";
return ret;
}
if (gpst_connect(vpninfo))
vpninfo->quit_reason = "GPST connect failed";
return 1;
}

switch (keepalive_action(&vpninfo->ssl_times, timeout)) {
case KA_REKEY:
do_rekey:
@@ -1344,7 +1398,7 @@ int gpst_esp_send_probes(struct openconnect_info *vpninfo)
iph->ip_id = htons(0x4747); /* what the Windows client uses */
iph->ip_off = htons(IP_DF); /* don't fragment, frag offset = 0 */
iph->ip_ttl = 64; /* hops */
iph->ip_p = 1; /* ICMP */
iph->ip_p = IPPROTO_ICMP;
iph->ip_src.s_addr = inet_addr(vpninfo->ip_info.addr);
iph->ip_dst.s_addr = vpninfo->esp_magic;
iph->ip_sum = csum((uint16_t *)iph, sizeof(*iph)/2);
@@ -1374,10 +1428,10 @@ int gpst_esp_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt)
struct ip *iph = (void *)(pkt->data);

return ( pkt->len >= 21 && iph->ip_v==4 /* IPv4 header */
&& iph->ip_p==1 /* IPv4 protocol field == ICMP */
&& iph->ip_p==IPPROTO_ICMP /* IPv4 protocol field == ICMP */
&& iph->ip_src.s_addr == vpninfo->esp_magic /* source == magic address */
&& pkt->len >= (iph->ip_hl<<2) + ICMP_MINLEN + sizeof(magic_ping_payload) /* No short-packet segfaults */
&& pkt->data[iph->ip_hl<<2]==0 /* ICMP reply */
&& pkt->data[iph->ip_hl<<2]==ICMP_ECHOREPLY /* ICMP reply */
&& !memcmp(&pkt->data[(iph->ip_hl<<2) + ICMP_MINLEN], magic_ping_payload, sizeof(magic_ping_payload)) /* Same magic payload in response */
);
}
@@ -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;
@@ -580,6 +580,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;
8 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),
@@ -878,7 +880,7 @@ static void usage(void)
printf(" --base-mtu=MTU %s\n", _("Indicate path MTU to/from server"));
printf(" -d, --deflate %s\n", _("Enable stateful compression (default is stateless only)"));
printf(" -D, --no-deflate %s\n", _("Disable all compression"));
printf(" --force-dpd=INTERVAL %s\n", _("Set minimum Dead Peer Detection interval"));
printf(" --force-dpd=INTERVAL %s\n", _("Set minimum Dead Peer Detection interval (in seconds)"));
printf(" --pfs %s\n", _("Require perfect forward secrecy"));
printf(" --no-dtls %s\n", _("Disable DTLS and ESP"));
printf(" --dtls-ciphers=LIST %s\n", _("OpenSSL ciphers to support for DTLS"));
@@ -895,6 +897,7 @@ static void usage(void)
printf("\n%s:\n", _("Trojan binary (CSD) execution"));
printf(" --csd-user=USER %s\n", _("Drop privileges during trojan execution"));
printf(" --csd-wrapper=SCRIPT %s\n", _("Run SCRIPT instead of trojan binary"));
printf(" --force-trojan=INTERVAL %s\n", _("Set minimum interval for rerunning trojan (in seconds)"));
#endif

printf("\n%s:\n", _("Server bugs"));
@@ -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;
@@ -398,3 +398,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;
}
}

0 comments on commit 41eda6d

Please sign in to comment.