From: David Woodhouse Date: Fri, 14 May 2021 10:57:11 +0000 (+0100) Subject: Add openconnect_get_connect_url(), use it in --authenticate output X-Git-Tag: v8.20~186 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=refs%2Fheads%2Fconnect-url;p=users%2Fdwmw2%2Fopenconnect.git Add openconnect_get_connect_url(), use it in --authenticate output As noted in the long comment in openconnect.h, we need to provide the actual *URL* for the connection including the real hostname. And that means we also need to provide a suitable --resolve argument to pass on to the connecting openconnect. Add openconnect_get_connect_url() for the GUI auth-dialogs to use, and make 'openconnect --authenticate' emit the same. If we just put it into $HOST that might break existing setups that don't use the newly-added $RESOLVE variable too, so put it in another new variable $CONNECT_URL and leave the original $HOST alone. Signed-off-by: David Woodhouse --- diff --git a/java/src/org/infradead/libopenconnect/LibOpenConnect.java b/java/src/org/infradead/libopenconnect/LibOpenConnect.java index d55c7784..4e283f48 100644 --- a/java/src/org/infradead/libopenconnect/LibOpenConnect.java +++ b/java/src/org/infradead/libopenconnect/LibOpenConnect.java @@ -156,6 +156,7 @@ public abstract class LibOpenConnect { /* connection info */ + public synchronized native String getConnectUrl(); public synchronized native String getHostname(); public synchronized native String getDNSName(); public synchronized native String getUrlpath(); diff --git a/jni.c b/jni.c index 3573d852..2c9d581d 100644 --- a/jni.c +++ b/jni.c @@ -1301,6 +1301,14 @@ JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getPr RETURN_STRING_END } +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getConnectUrl( + JNIEnv *jenv, jobject jobj) +{ + RETURN_STRING_START + buf = openconnect_get_connect_url(ctx->vpninfo); + RETURN_STRING_END +} + #define SET_STRING_START() \ struct libctx *ctx = getctx(jenv, jobj); \ const char *arg = NULL; \ diff --git a/libopenconnect.map.in b/libopenconnect.map.in index 55aec62e..2e87f68e 100644 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@ -114,6 +114,7 @@ OPENCONNECT_5_7 { openconnect_set_allow_insecure_crypto; openconnect_get_auth_expiration; openconnect_disable_dtls; + openconnect_get_connect_url; } OPENCONNECT_5_6; OPENCONNECT_PRIVATE { diff --git a/library.c b/library.c index c8ec44df..e4bea9d0 100644 --- a/library.c +++ b/library.c @@ -577,6 +577,7 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) free_split_routes(&vpninfo->ip_info); free(vpninfo->hostname); free(vpninfo->unique_hostname); + buf_free(vpninfo->connect_urlbuf); free(vpninfo->urlpath); free(vpninfo->redirect_url); free_pass(&vpninfo->cookie); @@ -694,6 +695,39 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) free(vpninfo); } + +const char *openconnect_get_connect_url(struct openconnect_info *vpninfo) +{ + struct oc_text_buf *urlbuf = vpninfo->connect_urlbuf; + + if (!urlbuf) + urlbuf = buf_alloc(); + + buf_append(urlbuf, "https://%s", vpninfo->hostname); + if (vpninfo->port != 443) + buf_append(urlbuf, ":%d", vpninfo->port); + buf_append(urlbuf, "/"); + + /* Other protocols don't care and just leave noise from the + * authentication process in ->urlpath. Pulse does care, and + * you have to *connect* to a given usergroup at the correct + * path, not just authenticate. + * + * https://gitlab.gnome.org/GNOME/NetworkManager-openconnect/-/issues/53 + * https://gitlab.gnome.org/GNOME/NetworkManager-openconnect/-/merge_requests/22 + */ + if (vpninfo->proto->proto == PROTO_PULSE) + buf_append(urlbuf, "%s", vpninfo->urlpath); + if (buf_error(urlbuf)) { + buf_free(urlbuf); + vpninfo->connect_urlbuf = NULL; + return NULL; + } + + vpninfo->connect_urlbuf = urlbuf; + return urlbuf->data; +} + const char *openconnect_get_hostname(struct openconnect_info *vpninfo) { return vpninfo->unique_hostname?:vpninfo->hostname; diff --git a/main.c b/main.c index 504030c5..a0809def 100644 --- a/main.c +++ b/main.c @@ -2097,8 +2097,21 @@ int main(int argc, char **argv) /* --authenticate */ printf("COOKIE='%s'\n", vpninfo->cookie); printf("HOST='%s'\n", openconnect_get_hostname(vpninfo)); + printf("CONNECT_URL='%s'\n", openconnect_get_connect_url(vpninfo)); printf("FINGERPRINT='%s'\n", openconnect_get_peer_cert_hash(vpninfo)); + if (vpninfo->unique_hostname) { + char *p = vpninfo->unique_hostname; + int l = strlen(p); + + if (vpninfo->unique_hostname[0] == '[' && + vpninfo->unique_hostname[l-1] == ']') { + p++; + l -=2; + } + printf("RESOLVE='%s:%.*s'\n", vpninfo->hostname, l, p); + } else + printf("RESOLVE="); openconnect_vpninfo_free(vpninfo); exit(0); } else if (cookieonly) { diff --git a/openconnect-internal.h b/openconnect-internal.h index 1ba62fbd..fb0a580b 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -493,10 +493,22 @@ struct openconnect_info { struct http_auth_state proxy_auth[MAX_AUTH_TYPES]; char *localname; - char *hostname; - char *unique_hostname; + + char *hostname; /* This is the original hostname (or IP address) + * we were asked to connect to */ + + char *unique_hostname; /* This is the IP address of the actual host + * that we connected to; the result of the + * DNS lookup. We do this so that we can be + * sure we reconnect to the same server we + * authenticated to. */ int port; char *urlpath; + + /* The application might ask us to recreate a connection URL, + * and we own the string so cache it for later freeing. */ + struct oc_text_buf *connect_urlbuf; + int cert_expire_warning; struct cert_info certinfo[1]; diff --git a/openconnect.h b/openconnect.h index 389d5a87..8dcccc8f 100644 --- a/openconnect.h +++ b/openconnect.h @@ -37,6 +37,7 @@ extern "C" { /* * API version 5.7: + * - Add openconnect_get_connect_url() * - Add openconnect_set_cookie() * - Add openconnect_set_allow_insecure_crypto() * - Add openconnect_get_auth_expiration() @@ -453,6 +454,77 @@ const char *openconnect_get_dtls_cipher(struct openconnect_info *); const char *openconnect_get_cstp_compression(struct openconnect_info *); const char *openconnect_get_dtls_compression(struct openconnect_info *); + +/* + * Since authentication can run in a separate environment to the connection + * itself, there is a simple set of information which needs to be passed + * from one to the other. Basically it's just the server to connect to, and + * the cookie we need for authentication. And luckily, as we've added more + * and more protocols over the years, the "cookie" part has remained true + * and we haven't needed to use client certificates for the *connection*. + * + * The *server* part is a little more complex. Firstly, the certificate + * might not be valid and may have been accepted manually by the user, + * so we pass the certificate fingerprint separately, as a second piece + * of information. + + * In the beginning, we passed the server hostname as the third piece of + * information, and all was well. + * + * Then we found servers on a non-standard port, so the authentication + * dialogs would use openconnect_get_port() and just append it (":%d") + * to the hostname string they passed on. + * + * Then we encountered servers with round-robin or geo-DNS, which gave + * different IP addresses for a given hostname, and we switched the + * openconnect_get_hostname() function to return the *IP* address instead, + * since the actual name didn't matter when it wasn't being used to check + * the server's certificate anyway. + * + * At some point later, openconnect_get_dnsname() was added to return the + * actual hostname, during the authentication phase where the cert was + * being presented to the user for manual acceptance. But that wasn't + * really important for the authentication → connection handoff. + * + * Later still, Legacy IP addresses got scarce and SNI was invented, and + * we started to see servers behind proxies that forward a connection + * based on the SNI in the incoming ClientHello. So returning just the + * IP address from openconnect_get_hostname() now made things break. + * + * So... we need to pass *both* the actual hostname *and* the IP address + * to the connecting openconnect invocation. As well as the port. + * + * In addition, the Pulse protocol introduced a new requirement for the + * connection. Instead of connecting to a fixed endpoint on the server, + * we must connect to the appropriate *path*, which varies. So in fact + * it isn't just the "hostname" any more, but the full URL. + * + * So, now we have openconnect_get_connect_url() which gets the full URL + * including the port and path, and the original hostname. + * + * Since we're now back to giving openconnect a hosthame, we need to add + * a '--resolve' argument to avoid the round robin DNS problem and ensure + * that we actually connect to the same server we authenticated to. The + * arguments for that can be obtained from openconnect_get_dnsname() and + * openconnect_get_hostname() — the latter of which, as noted, was changed + * years ago to return a numeric address. We end up invoking openconnect + * to make the connection as follows: + * + * openconnect $CONNECT_URL --servercert $FINGERPRINT --cookie $COOKIE \ + * --resolve $DNSNAME:$HOSTNAME + * + * ... where '$HOSTNAME', as noted, isn't actually a hostname. Sorry. + * + * In fact, what you get back from openconnect_get_hostname() is the + * IP literal in the form it would appear in a URL. So IPv6 addresses + * are wrapped in [], and that needs to be *stripped* in order to pass + * it to openconnect's --resolve argument. As I realise and type this, + * it doesn't seem particularly useful to provide yet another function + * that will return the non-[]-wrapped version, as we'd still need UI + * tools to do it themselves for backward compatibility. Sorry again :) + */ +const char *openconnect_get_connect_url(struct openconnect_info *); + /* Returns the IP address of the exact host to which the connection * was made. In --cookieonly mode or in any other scenario involving * a "two stage" connection, it is important to reconnect by IP because diff --git a/www/changelog.xml b/www/changelog.xml index c84e0030..f1aac03f 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -41,6 +41,10 @@
  • Ignore failures to fetch the NC landing page if the authentication was successful.
  • Add support for Array Networks SSL VPN (#102)
  • Support TLSv1.3 with TPMv2 EC and RSA keys, add test cases for swtpm and hardware TPM.
  • +
  • Add openconnect_get_connect_url() to simplify passing correct server information to + to the connecting openconnect process. (NetworkManager-openconnect + #46, + #53)

  • OpenConnect v8.10