diff --git a/auth-globalprotect.c b/auth-globalprotect.c index 9ce8d43c..5c4dfe73 100644 --- a/auth-globalprotect.c +++ b/auth-globalprotect.c @@ -173,6 +173,7 @@ static int parse_login_xml(struct openconnect_info *vpninfo, xmlNode *xml_node) free(value); value = NULL; } + append_opt(cookie, "computer", vpninfo->localname); if (!buf_error(cookie)) { vpninfo->cookie = cookie->data; @@ -294,7 +295,7 @@ static int parse_portal_xml(struct openconnect_info *vpninfo, xmlNode *xml_node) buf_append(buf, " \n\n"); if ((result = buf_error(buf))) goto out; - if ((result = vpninfo->write_new_config(vpninfo, buf->data, buf->pos))) + if ((result = vpninfo->write_new_config(vpninfo->cbdata, buf->data, buf->pos))) goto out; } @@ -475,8 +476,7 @@ int gpst_bye(struct openconnect_info *vpninfo, const char *reason) * * Don't blame me. I didn't design this. */ - append_opt(request_body, "computer", vpninfo->localname); - buf_append(request_body, "&%s", vpninfo->cookie); + buf_append(request_body, "%s", vpninfo->cookie); if ((result = buf_error(request_body))) goto out; diff --git a/auth-juniper.c b/auth-juniper.c index acd9c77e..30ceb3ae 100644 --- a/auth-juniper.c +++ b/auth-juniper.c @@ -47,7 +47,6 @@ void oncp_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *b { http_common_headers(vpninfo, buf); - buf_append(buf, "Connection: close\r\n"); // buf_append(buf, "Content-Length: 256\r\n"); buf_append(buf, "NCP-Version: 3\r\n"); // buf_append(buf, "Accept-Encoding: gzip\r\n"); diff --git a/cstp.c b/cstp.c index 5477c5c8..68c3d511 100644 --- a/cstp.c +++ b/cstp.c @@ -462,6 +462,8 @@ static int start_cstp_connection(struct openconnect_info *vpninfo) if (!strcmp(buf + 7, "Keepalive")) { vpninfo->ssl_times.keepalive = atol(colon); + } else if (!strcmp(buf + 7, "Idle-Timeout")) { + vpninfo->idle_timeout = atol(colon); } else if (!strcmp(buf + 7, "DPD")) { int j = atol(colon); if (j && (!vpninfo->ssl_times.dpd || j < vpninfo->ssl_times.dpd)) @@ -729,7 +731,11 @@ static int cstp_reconnect(struct openconnect_info *vpninfo) int decompress_and_queue_packet(struct openconnect_info *vpninfo, int compr_type, unsigned char *buf, int len) { - struct pkt *new = malloc(sizeof(struct pkt) + vpninfo->ip_info.mtu); + /* Some servers send us packets that are larger than + negotiated MTU after decompression. We reserve some extra + space to handle that */ + int receive_mtu = MAX(16384, vpninfo->ip_info.mtu); + struct pkt *new = malloc(sizeof(struct pkt) + receive_mtu); const char *comprname = ""; if (!new) @@ -746,7 +752,7 @@ int decompress_and_queue_packet(struct openconnect_info *vpninfo, int compr_type vpninfo->inflate_strm.avail_in = len - 4; vpninfo->inflate_strm.next_out = new->data; - vpninfo->inflate_strm.avail_out = vpninfo->ip_info.mtu; + vpninfo->inflate_strm.avail_out = receive_mtu; vpninfo->inflate_strm.total_out = 0; if (inflate(&vpninfo->inflate_strm, Z_SYNC_FLUSH)) { @@ -768,7 +774,7 @@ int decompress_and_queue_packet(struct openconnect_info *vpninfo, int compr_type } else if (compr_type == COMPR_LZS) { comprname = "LZS"; - new->len = lzs_decompress(new->data, vpninfo->ip_info.mtu, buf, len); + new->len = lzs_decompress(new->data, receive_mtu, buf, len); if (new->len < 0) { len = new->len; if (len == 0) @@ -781,7 +787,7 @@ int decompress_and_queue_packet(struct openconnect_info *vpninfo, int compr_type #ifdef HAVE_LZ4 } else if (compr_type == COMPR_LZ4) { comprname = "LZ4"; - new->len = LZ4_decompress_safe((void *)buf, (void *)new->data, len, vpninfo->ip_info.mtu); + new->len = LZ4_decompress_safe((void *)buf, (void *)new->data, len, receive_mtu); if (new->len <= 0) { len = new->len; if (len == 0) @@ -882,18 +888,21 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout) and add POLLOUT. As it is, though, it'll just chew CPU time in that fairly unlikely situation, until the write backlog clears. */ while (1) { - int len = MAX(16384, vpninfo->deflate_pkt_size ? : vpninfo->ip_info.mtu); - int payload_len; + /* Some servers send us packets that are larger than + negotiated MTU. We reserve some extra space to + handle that */ + int receive_mtu = MAX(16384, vpninfo->deflate_pkt_size ? : vpninfo->ip_info.mtu); + int len, payload_len; if (!vpninfo->cstp_pkt) { - vpninfo->cstp_pkt = malloc(sizeof(struct pkt) + len); + vpninfo->cstp_pkt = malloc(sizeof(struct pkt) + receive_mtu); if (!vpninfo->cstp_pkt) { vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n")); break; } } - len = ssl_nonblock_read(vpninfo, vpninfo->cstp_pkt->cstp.hdr, len + 8); + len = ssl_nonblock_read(vpninfo, vpninfo->cstp_pkt->cstp.hdr, receive_mtu + 8); if (!len) break; if (len < 0) diff --git a/esp.c b/esp.c index 30de764d..dce7f90b 100644 --- a/esp.c +++ b/esp.c @@ -106,10 +106,14 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) struct esp *esp = &vpninfo->esp_in[vpninfo->current_esp_in]; struct esp *old_esp = &vpninfo->esp_in[vpninfo->current_esp_in ^ 1]; struct pkt *this; - int receive_mtu = MAX(2048, vpninfo->ip_info.mtu + 256); int work_done = 0; int ret; + /* Some servers send us packets that are larger than negotiated + MTU, or lack the ability to negotiate MTU (see gpst.c). We + reserve some extra space to handle that */ + int receive_mtu = MAX(2048, vpninfo->ip_info.mtu + 256); + if (vpninfo->dtls_state == DTLS_SLEEPING) { if (ka_check_deadline(timeout, time(NULL), vpninfo->new_dtls_started + vpninfo->dtls_attempt_period) || vpninfo->dtls_need_reconnect) { @@ -142,6 +146,7 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) len); work_done = 1; + /* both supported algos (SHA1 and MD5) have 12-byte MAC lengths (RFC2403 and RFC2404) */ if (len <= sizeof(pkt->esp) + 12) continue; @@ -165,6 +170,11 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) continue; } + /* Possible values of the Next Header field are: + 0x04: IP[v4]-in-IP + 0x05: supposed to mean Internet Stream Protocol + (XXX: but used for LZO compressed packets by Juniper) + 0x29: IPv6 encapsulation */ if (pkt->data[len - 1] != 0x04 && pkt->data[len - 1] != 0x29 && pkt->data[len - 1] != 0x05) { vpn_progress(vpninfo, PRG_ERR, diff --git a/gpst.c b/gpst.c index 1cd98644..9742fe16 100644 --- a/gpst.c +++ b/gpst.c @@ -481,6 +481,11 @@ static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_ else if (!xmlnode_get_text(xml_node, "mtu", &s)) { vpninfo->ip_info.mtu = atoi(s); free(s); + } else if (!xmlnode_get_text(xml_node, "disconnect-on-idle", &s)) { + int sec = atoi(s); + vpn_progress(vpninfo, PRG_INFO, _("Idle timeout is %d minutes.\n"), sec/60); + vpninfo->idle_timeout = sec; + free(s); } else if (!xmlnode_get_text(xml_node, "ssl-tunnel-url", &s)) { free(vpninfo->urlpath); vpninfo->urlpath = s; @@ -630,7 +635,7 @@ static int gpst_get_config(struct openconnect_info *vpninfo) vpninfo->ip_info.mtu = calculate_mtu(vpninfo, !no_esp_reason); vpn_progress(vpninfo, PRG_ERR, _("No MTU received. Calculated %d for %s%s\n"), vpninfo->ip_info.mtu, - no_esp_reason ? "TLS tunnel. " : "ESP tunnel", no_esp_reason ? : ""); + no_esp_reason ? "SSL tunnel. " : "ESP tunnel", no_esp_reason ? : ""); /* return -EINVAL; */ } if (!vpninfo->ip_info.addr) { @@ -789,9 +794,8 @@ static int build_csd_token(struct openconnect_info *vpninfo) if (!vpninfo->csd_token) return -ENOMEM; - /* use localname and cookie (excluding volatile authcookie and preferred-ip) to build md5sum */ + /* use cookie (excluding volatile authcookie and preferred-ip) to build md5sum */ buf = buf_alloc(); - append_opt(buf, "computer", vpninfo->localname); filter_opts(buf, vpninfo->cookie, "authcookie,preferred-ip", 0); if (buf_error(buf)) goto out; @@ -815,9 +819,8 @@ static int check_or_submit_hip_report(struct openconnect_info *vpninfo, const ch const char *method = "POST"; char *xml_buf=NULL, *orig_path; - /* cookie gives us these fields: authcookie, portal, user, domain, and (maybe the unnecessary) preferred-ip */ + /* cookie gives us these fields: authcookie, portal, user, domain, computer, and (maybe the unnecessary) preferred-ip */ buf_append(request_body, "client-role=global-protect-full&%s", vpninfo->cookie); - append_opt(request_body, "computer", vpninfo->localname); append_opt(request_body, "client-ip", vpninfo->ip_info.addr); if (report) { /* XML report contains many characters requiring URL-encoding (%xx) */ @@ -887,9 +890,15 @@ static int run_hip_script(struct openconnect_info *vpninfo) buf_append_bytes(report_buf, b, i); waitpid(child, &status, 0); - if (status != 0) { + if (!WIFEXITED(status)) { + vpn_progress(vpninfo, PRG_ERR, + _("HIP script '%s' exited abnormally\n"), + vpninfo->csd_wrapper); + ret = -EINVAL; + } else if (WEXITSTATUS(status) != 0) { vpn_progress(vpninfo, PRG_ERR, - _("HIP script returned non-zero status: %d\n"), status); + _("HIP script '%s' returned non-zero status: %d\n"), + vpninfo->csd_wrapper, WEXITSTATUS(status)); ret = -EINVAL; } else { ret = check_or_submit_hip_report(vpninfo, report_buf->data); @@ -912,8 +921,6 @@ static int run_hip_script(struct openconnect_info *vpninfo) hip_argv[i++] = openconnect_utf8_to_legacy(vpninfo, vpninfo->csd_wrapper); hip_argv[i++] = (char *)"--cookie"; hip_argv[i++] = vpninfo->cookie; - hip_argv[i++] = (char *)"--computer"; - hip_argv[i++] = vpninfo->localname; hip_argv[i++] = (char *)"--client-ip"; hip_argv[i++] = (char *)vpninfo->ip_info.addr; hip_argv[i++] = (char *)"--md5"; @@ -1014,7 +1021,10 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout) goto do_reconnect; while (1) { - int receive_mtu = MAX(2048, vpninfo->ip_info.mtu + 256); + /* Some servers send us packets that are larger than + negotiated MTU. We reserve some extra space to + handle that */ + int receive_mtu = MAX(16384, vpninfo->ip_info.mtu); int len, payload_len; if (!vpninfo->cstp_pkt) { diff --git a/hipreport.sh b/hipreport.sh index a25cf139..832aabc8 100755 --- a/hipreport.sh +++ b/hipreport.sh @@ -6,10 +6,7 @@ # # --cookie: a URL-encoded string, as output by openconnect # --authenticate --protocol=gp, which includes parameters -# --from the /ssl-vpn/login.esp response -# -# --computer: local hostname, which can be overriden with -# --openconnect local-hostname=HOSTNAME +# from the /ssl-vpn/login.esp response # # --client-ip: IPv4 address allocated by the GlobalProtect VPN for # this client (included in /ssl-vpn/getconfig.esp @@ -22,26 +19,25 @@ # Read command line arguments into variables COOKIE= -COMPUTER= IP= MD5= while [ "$1" ]; do if [ "$1" = "--cookie" ]; then shift; COOKIE="$1"; fi - if [ "$1" = "--computer" ]; then shift; COMPUTER="$1"; fi if [ "$1" = "--client-ip" ]; then shift; IP="$1"; fi if [ "$1" = "--md5" ]; then shift; MD5="$1"; fi shift done -if [ -z "$COOKIE" -o -z "$COMPUTER" -o -z "$IP" -o -z "$MD5" ]; then +if [ -z "$COOKIE" -o -z "$IP" -o -z "$MD5" ]; then echo "Parameters --cookie, --computer, --client-ip, and --md5 are required" >&2 exit 1; fi -# Extract username and domain from cookie +# Extract username and domain and computer from cookie USER=$(echo "$COOKIE" | sed -rn 's/(.+&|^)user=([^&]+)(&.+|$)/\2/p') DOMAIN=$(echo "$COOKIE" | sed -rn 's/(.+&|^)domain=([^&]+)(&.+|$)/\2/p') +COMPUTER=$(echo "$COOKIE" | sed -rn 's/(.+&|^)computer=([^&]+)(&.+|$)/\2/p') # Timestamp in the format expected by GlobalProtect server NOW=$(date +'%m/%d/%Y %H:%M:%S') diff --git a/java/README b/java/README index 1bdec001..8963ddea 100644 --- a/java/README +++ b/java/README @@ -11,7 +11,7 @@ From the top level, run: make cd java ant - sudo java -Djava.library.path=../.libs -jar dist/example.jar + sudo java -Djava.library.path=../.libs -jar dist/example.jar [protocol] If ocproxy[1] is installed somewhere in your $PATH, this can be run as a non-root user and it should be pingable from across the VPN. diff --git a/java/src/com/example/LibTest.java b/java/src/com/example/LibTest.java index 78e77b58..034e450e 100644 --- a/java/src/com/example/LibTest.java +++ b/java/src/com/example/LibTest.java @@ -204,12 +204,30 @@ private static void printIPInfo(LibOpenConnect.IPInfo ip) { System.out.println(""); } + private static void describeProtocol(LibOpenConnect.VPNProto vp) { + ArrayList flags = new ArrayList(); + if ((vp.flags & LibOpenConnect.OC_PROTO_PROXY) != 0) flags.add("proxy"); + if ((vp.flags & LibOpenConnect.OC_PROTO_CSD) != 0) flags.add("CSD"); + if ((vp.flags & LibOpenConnect.OC_PROTO_AUTH_CERT) != 0) flags.add("auth-cert"); + if ((vp.flags & LibOpenConnect.OC_PROTO_AUTH_OTP) != 0) flags.add("auth-otp"); + if ((vp.flags & LibOpenConnect.OC_PROTO_AUTH_OTP) != 0) flags.add("auth-stoken"); + + System.out.println(" " + vp.name + + ") PRETTY_NAME=" + vp.prettyName + + ", DESCRIPTION=" + vp.description + + ", FLAGS=" + String.join("+", flags)); + } + public static void main(String argv[]) { System.loadLibrary("openconnect-wrapper"); LibOpenConnect lib = new TestLib(); + String server_name, protocol; - if (argv.length != 1) - die("usage: LibTest "); + if (argv.length != 1 && argv.length != 2) + die("usage: LibTest [protocol]"); + + server_name = argv[0]; + protocol = argv.length == 2 ? argv[1] : null; System.out.println("OpenConnect version: " + lib.getVersion()); System.out.println(" PKCS=" + lib.hasPKCS11Support() + @@ -217,13 +235,29 @@ public static void main(String argv[]) { ", STOKEN=" + lib.hasStokenSupport() + ", OATH=" + lib.hasOATHSupport() + ", YUBIOATH=" + lib.hasYubiOATHSupport()); + + System.out.println("Supported protocols:"); + for (LibOpenConnect.VPNProto vp : lib.getSupportedProtocols()) + describeProtocol(vp); + if (protocol == null) { + System.out.println("Using default VPN protocol of " + lib.getProtocol()); + } else { + System.out.println("Setting VPN protocol to " + protocol); + if (lib.setProtocol(protocol) != 0) + die("Error setting VPN protocol"); + } + lib.setReportedOS("win"); lib.setLogLevel(lib.PRG_DEBUG); //lib.setTokenMode(LibOpenConnect.OC_TOKEN_MODE_STOKEN, null); - if (new File("csd.sh").exists()) { - lib.setCSDWrapper("csd.sh", null, null); + String csd_wrapper = "./csd-" + lib.getProtocol() + ".sh"; + if (new File(csd_wrapper).exists()) { + System.out.println("Using CSD wrapper script " + csd_wrapper); + lib.setCSDWrapper(csd_wrapper, null, null); + } else { + System.out.println("Skipping CSD wrapper (script " + csd_wrapper + " doesn't exist)"); } - lib.parseURL(argv[0]); + lib.parseURL(server_name); lib.setSystemTrust(true); int ret = lib.obtainCookie(); if (ret < 0) @@ -241,6 +275,8 @@ else if (ret > 0) if (lib.makeCSTPConnection() != 0) die("Error establishing VPN link"); + int idleTimeout = lib.getIdleTimeout(); + System.out.println("Idle Timeout: " + idleTimeout + " seconds"); printIPInfo(lib.getIPInfo()); if (lib.setupDTLS(60) != 0) diff --git a/java/src/org/infradead/libopenconnect/LibOpenConnect.java b/java/src/org/infradead/libopenconnect/LibOpenConnect.java index ce4ffcc2..1ba7b420 100644 --- a/java/src/org/infradead/libopenconnect/LibOpenConnect.java +++ b/java/src/org/infradead/libopenconnect/LibOpenConnect.java @@ -22,6 +22,12 @@ public abstract class LibOpenConnect { /* constants */ + public static final int OC_PROTO_PROXY = 1; + public static final int OC_PROTO_CSD = 2; + public static final int OC_PROTO_AUTH_CERT = 4; + public static final int OC_PROTO_AUTH_OTP = 8; + public static final int OC_PROTO_AUTH_STOKEN = 16; + public static final int OC_FORM_OPT_TEXT = 1; public static final int OC_FORM_OPT_PASSWORD = 2; public static final int OC_FORM_OPT_SELECT = 3; @@ -135,6 +141,7 @@ public synchronized native void setMobileInfo(String mobilePlatformVersion, public synchronized native void setReqMTU(int mtu); public synchronized native void setPFS(boolean isEnabled); public synchronized native void setSystemTrust(boolean isEnabled); + public synchronized native int setProtocol(String protocol); /* connection info */ @@ -149,6 +156,8 @@ public synchronized native void setMobileInfo(String mobilePlatformVersion, public synchronized native String getDTLSCipher(); public synchronized native String getCSTPCompression(); public synchronized native String getDTLSCompression(); + public synchronized native String getProtocol(); + public synchronized native int getIdleTimeout(); /* certificate info */ @@ -166,6 +175,7 @@ public synchronized native void setMobileInfo(String mobilePlatformVersion, public static native boolean hasStokenSupport(); public static native boolean hasOATHSupport(); public static native boolean hasYubiOATHSupport(); + public static native VPNProto[] getSupportedProtocols(); /* public data structures */ @@ -238,6 +248,7 @@ public static class IPInfo { public String proxyPac; public String gatewayAddr; public int MTU; + public int idleTimeoutSec; public ArrayList splitDNS = new ArrayList(); public ArrayList splitIncludes = new ArrayList(); @@ -265,6 +276,13 @@ public static class VPNStats { public Object userData; }; + public static class VPNProto { + public String name; + public String prettyName; + public String description; + public int flags; + }; + /* Optional storage for caller's data */ public Object userData; diff --git a/jni.c b/jni.c index d72ac2e1..be170bcc 100644 --- a/jni.c +++ b/jni.c @@ -879,8 +879,8 @@ JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setCSDWr !get_cstring(ctx->jenv, jarg2, &arg2)) { openconnect_setup_csd(ctx->vpninfo, getuid(), 1, arg0); - openconnect_set_csd_environ(ctx->vpninfo, "TMPDIR", arg1); - openconnect_set_csd_environ(ctx->vpninfo, "PATH", arg2); + if (arg1) openconnect_set_csd_environ(ctx->vpninfo, "TMPDIR", arg1); + if (arg2) openconnect_set_csd_environ(ctx->vpninfo, "PATH", arg2); } release_cstring(ctx->jenv, jarg0, arg0); @@ -1108,6 +1108,16 @@ JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setXMLPo openconnect_set_xmlpost(ctx->vpninfo, arg); } +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getIdleTimeout( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return -EINVAL; + return openconnect_get_idle_timeout(ctx->vpninfo); +} + /* simple cases: return a const string (no need to free it) */ #define RETURN_STRING_START \ @@ -1197,6 +1207,14 @@ JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getCS RETURN_STRING_END } +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getProtocol( + JNIEnv *jenv, jobject jobj) +{ + RETURN_STRING_START + buf = openconnect_get_protocol(ctx->vpninfo); + RETURN_STRING_END +} + #define SET_STRING_START(ret) \ struct libctx *ctx = getctx(jenv, jobj); \ const char *arg = NULL; \ @@ -1247,6 +1265,16 @@ JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setHTTPP return ret; } +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setProtocol( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + int ret; + SET_STRING_START(-ENOMEM) + ret = openconnect_set_protocol(ctx->vpninfo, arg); + SET_STRING_END(); + return ret; +} + JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setXMLSHA1( JNIEnv *jenv, jobject jobj, jstring jarg) { @@ -1381,3 +1409,53 @@ JNIEXPORT jobject JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getIP return jobj; } + +JNIEXPORT jobjectArray JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getSupportedProtocols( + JNIEnv *jenv, jclass jcls) +{ + jmethodID mid; + jobjectArray result; + struct libctx ctx = { .jenv = jenv, .jobj = NULL, .async_lock = NULL, .vpninfo = NULL, .cmd_fd = -1, .loglevel = -1 }; + + /* call C library */ + struct oc_vpn_proto *protos; + int np, ii; + np = openconnect_get_supported_protocols(&protos); + if (np < 0) + return NULL; + + /* get VPNProto class, its init method, and create array */ + jcls = (*jenv)->FindClass(jenv, + "org/infradead/libopenconnect/LibOpenConnect$VPNProto"); + if (jcls == NULL) + goto err; + mid = (*jenv)->GetMethodID(jenv, jcls, "", "()V"); + if (!mid) + goto err; + result = (*jenv)->NewObjectArray(jenv, np, jcls, NULL); + if (result == NULL) + goto nomem; + + for (ii=0; iiNewObject(jenv, jcls, mid); + if (!jobj) + goto nomem; + + if (set_string(&ctx, jobj, "name", protos[ii].name) || + set_string(&ctx, jobj, "prettyName", protos[ii].pretty_name) || + set_string(&ctx, jobj, "description", protos[ii].description) || + set_int (&ctx, jobj, "flags", protos[ii].flags)) + goto nomem; + + (*jenv)->SetObjectArrayElement(jenv, result, ii, jobj); + } + + openconnect_free_supported_protocols(protos); + return result; + +nomem: + OOM(jenv); +err: + openconnect_free_supported_protocols(protos); + return NULL; +} diff --git a/libopenconnect.map.in b/libopenconnect.map.in index 04b8ed5b..1f297268 100644 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@ -94,6 +94,8 @@ OPENCONNECT_5_4 { OPENCONNECT_5_5 { global: + openconnect_get_idle_timeout; + openconnect_get_protocol; openconnect_get_supported_protocols; openconnect_free_supported_protocols; } OPENCONNECT_5_4; diff --git a/library.c b/library.c index e5bbda48..3d134995 100644 --- a/library.c +++ b/library.c @@ -117,6 +117,7 @@ const struct vpn_proto openconnect_protos[] = { .tcp_mainloop = cstp_mainloop, .add_http_headers = cstp_common_headers, .obtain_cookie = cstp_obtain_cookie, + .udp_protocol = "DTLS", #ifdef HAVE_DTLS .udp_setup = dtls_setup, .udp_mainloop = dtls_mainloop, @@ -133,6 +134,7 @@ const struct vpn_proto openconnect_protos[] = { .tcp_mainloop = oncp_mainloop, .add_http_headers = oncp_common_headers, .obtain_cookie = oncp_obtain_cookie, + .udp_protocol = "ESP", #ifdef HAVE_ESP .udp_setup = esp_setup, .udp_mainloop = esp_mainloop, @@ -145,12 +147,13 @@ const struct vpn_proto openconnect_protos[] = { .name = "gp", .pretty_name = N_("Palo Alto Networks GlobalProtect"), .description = N_("Compatible with Palo Alto Networks (PAN) GlobalProtect SSL VPN"), - .flags = OC_PROTO_PROXY | OC_PROTO_AUTH_CERT | OC_PROTO_AUTH_OTP | OC_PROTO_AUTH_STOKEN, + .flags = OC_PROTO_PROXY | OC_PROTO_CSD | OC_PROTO_AUTH_CERT | OC_PROTO_AUTH_OTP | OC_PROTO_AUTH_STOKEN, .vpn_close_session = gpst_bye, .tcp_connect = gpst_setup, .tcp_mainloop = gpst_mainloop, .add_http_headers = gpst_common_headers, .obtain_cookie = gpst_obtain_cookie, + .udp_protocol = "ESP", #ifdef HAVE_ESP .udp_setup = esp_setup, .udp_mainloop = esp_mainloop, @@ -186,6 +189,11 @@ void openconnect_free_supported_protocols(struct oc_vpn_proto *protos) free((void *)protos); } +const char *openconnect_get_protocol(struct openconnect_info *vpninfo) +{ + return vpninfo->proto->name; +} + int openconnect_set_protocol(struct openconnect_info *vpninfo, const char *protocol) { const struct vpn_proto *p; @@ -530,6 +538,11 @@ void openconnect_set_dpd(struct openconnect_info *vpninfo, int min_seconds) vpninfo->dtls_times.dpd = vpninfo->ssl_times.dpd = 2; } +int openconnect_get_idle_timeout(struct openconnect_info *vpninfo) +{ + return vpninfo->idle_timeout; +} + int openconnect_get_ip_info(struct openconnect_info *vpninfo, const struct oc_ip_info **info, const struct oc_vpn_option **cstp_options, @@ -904,7 +917,8 @@ int openconnect_setup_tun_device(struct openconnect_info *vpninfo, static const char *compr_name_map[] = { [COMPR_DEFLATE] = "Deflate", [COMPR_LZS] = "LZS", - [COMPR_LZ4] = "LZ4" + [COMPR_LZ4] = "LZ4", + [COMPR_LZO] = "LZO", }; const char *openconnect_get_cstp_compression(struct openconnect_info * vpninfo) diff --git a/main.c b/main.c index 283db780..379cf5de 100644 --- a/main.c +++ b/main.c @@ -863,8 +863,8 @@ static void usage(void) printf(" -x, --xmlconfig=CONFIG %s\n", _("XML config file")); printf(" -m, --mtu=MTU %s\n", _("Request MTU from server (legacy servers only)")); printf(" --base-mtu=MTU %s\n", _("Indicate path MTU to/from server")); - printf(" -d, --deflate %s\n", _("Enable compression (default)")); - printf(" -D, --no-deflate %s\n", _("Disable compression")); + 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(" --pfs %s\n", _("Require perfect forward secrecy")); printf(" --no-dtls %s\n", _("Disable DTLS and ESP")); @@ -1083,7 +1083,7 @@ int main(int argc, char **argv) char *urlpath = NULL; struct oc_vpn_option *gai; char *ip; - const char *compr = ""; + const char *ssl_compr, *udp_compr; char *proxy = getenv("https_proxy"); char *vpnc_script = NULL; const struct oc_ip_info *ip_info; @@ -1596,33 +1596,21 @@ int main(int argc, char **argv) * reconnects end up in infinite loop trying to connect * to non existing DTLS */ vpninfo->dtls_state = DTLS_DISABLED; - fprintf(stderr, _("Set up DTLS failed; using SSL instead\n")); + fprintf(stderr, _("Set up UDP failed; using SSL instead\n")); } openconnect_get_ip_info(vpninfo, &ip_info, NULL, NULL); - if (vpninfo->dtls_state != DTLS_CONNECTED) { - if (vpninfo->cstp_compr == COMPR_DEFLATE) - compr = " + deflate"; - else if (vpninfo->cstp_compr == COMPR_LZS) - compr = " + lzs"; - else if (vpninfo->cstp_compr == COMPR_LZ4) - compr = " + lz4"; - } else { - if (vpninfo->dtls_compr == COMPR_DEFLATE) - compr = " + deflate"; - else if (vpninfo->dtls_compr == COMPR_LZS) - compr = " + lzs"; - else if (vpninfo->dtls_compr == COMPR_LZ4) - compr = " + lz4"; - } + ssl_compr = openconnect_get_cstp_compression(vpninfo); + udp_compr = openconnect_get_dtls_compression(vpninfo); vpn_progress(vpninfo, PRG_INFO, - _("Connected as %s%s%s, using %s%s\n"), + _("Connected as %s%s%s, using SSL%s%s, with %s%s%s %s\n"), ip_info->addr?:"", (ip_info->netmask6 && ip_info->addr) ? " + " : "", ip_info->netmask6 ? : "", - (vpninfo->dtls_state != DTLS_CONNECTED) ? "SSL" - : "DTLS", compr); + ssl_compr ? " + " : "", ssl_compr ? : "", + vpninfo->proto->udp_protocol ? : "UDP", udp_compr ? " + " : "", udp_compr ? : "", + (vpninfo->dtls_state == DTLS_DISABLED || vpninfo->dtls_state == DTLS_NOSECRET ? _("disabled") : _("in progress"))); if (!vpninfo->vpnc_script) { vpn_progress(vpninfo, PRG_INFO, diff --git a/oncp.c b/oncp.c index 6fa33332..4e625e00 100644 --- a/oncp.c +++ b/oncp.c @@ -323,6 +323,7 @@ static int process_attr(struct openconnect_info *vpninfo, int group, int attr, if (attrlen != 1) goto badlen; vpninfo->esp_compr = data[0]; + vpninfo->dtls_compr = data[0] ? COMPR_LZO : 0; vpn_progress(vpninfo, PRG_DEBUG, _("ESP compression: %d\n"), data[0]); break; @@ -559,52 +560,14 @@ int oncp_connect(struct openconnect_info *vpninfo) reqbuf = buf_alloc(); - buf_append(reqbuf, "POST /dana/js?prot=1&svc=1 HTTP/1.1\r\n"); - oncp_common_headers(vpninfo, reqbuf); - buf_append(reqbuf, "Content-Length: 256\r\n"); - buf_append(reqbuf, "\r\n"); - - if (buf_error(reqbuf)) { - vpn_progress(vpninfo, PRG_ERR, - _("Error creating oNCP negotiation request\n")); - ret = buf_error(reqbuf); - goto out; - } - - ret = vpninfo->ssl_write(vpninfo, reqbuf->data, reqbuf->pos); - if (ret < 0) - goto out; - - /* The server is fairly weird. It sends Connection: close which would - * indicate an HTTP 1.0-style body, but doesn't seem to actually close - * the connection. So tell process_http_response() it was a CONNECT - * request, since we don't care about the body anyway, and then close - * the connection for ourselves. */ - ret = process_http_response(vpninfo, 1, NULL, reqbuf); - openconnect_close_https(vpninfo, 0); - if (ret < 0) { - /* We'll already have complained about whatever offended us */ - goto out; - } - if (ret != 200) { - vpn_progress(vpninfo, PRG_ERR, - _("Unexpected %d result from server\n"), - ret); - ret = -EINVAL; - goto out; - } - - /* Now the second request. We should reduce the duplication - here but let's not overthink it for now; we should see what - the authentication requests are going to look like, and make - do_https_request() or a new helper function work for those - too. */ - ret = openconnect_open_https(vpninfo); - if (ret) - goto out; - - buf_truncate(reqbuf); buf_append(reqbuf, "POST /dana/js?prot=1&svc=4 HTTP/1.1\r\n"); + /* The TLS socket actually remains open for use by the oNCP + tunnel, but the "Connection: close" header is nevertheless + required here. It appears to signal to the server to stop + treating this as an HTTP connection and to start treating + it as an oNCP connection. + */ + buf_append(reqbuf, "Connection: close\r\n"); oncp_common_headers(vpninfo, reqbuf); buf_append(reqbuf, "Content-Length: 256\r\n"); buf_append(reqbuf, "\r\n"); diff --git a/openconnect-internal.h b/openconnect-internal.h index 038cb35d..729d3014 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -174,10 +174,11 @@ struct pkt { #define COMPR_DEFLATE (1<<0) #define COMPR_LZS (1<<1) #define COMPR_LZ4 (1<<2) -#define COMPR_MAX COMPR_LZ4 +#define COMPR_LZO (1<<3) +#define COMPR_MAX COMPR_LZO #ifdef HAVE_LZ4 -#define COMPR_STATELESS (COMPR_LZS | COMPR_LZ4) +#define COMPR_STATELESS (COMPR_LZS | COMPR_LZ4 | COMPR_LZO) #else #define COMPR_STATELESS (COMPR_LZS) #endif @@ -263,6 +264,7 @@ struct vpn_proto { const char *name; const char *pretty_name; const char *description; + const char *udp_protocol; unsigned int flags; int (*vpn_close_session)(struct openconnect_info *vpninfo, const char *reason); @@ -584,6 +586,7 @@ struct openconnect_info { struct oc_ip_info ip_info; int cstp_basemtu; /* Returned by server */ + int idle_timeout; /* Returned by server */ #ifdef _WIN32 long dtls_monitored, ssl_monitored, cmd_monitored, tun_monitored; diff --git a/openconnect.h b/openconnect.h index e97dacbc..74a5124a 100644 --- a/openconnect.h +++ b/openconnect.h @@ -39,6 +39,8 @@ extern "C" { * API version 5.5: * - Add openconnect_get_supported_protocols() * - Add openconnect_free_supported_protocols() + * - Add openconnect_get_protocol() + * - Add openconnect_get_idle_timeout() * * API version 5.4 (v7.08; 2016-12-13): * - Add openconnect_set_pass_tos() @@ -513,6 +515,7 @@ int openconnect_set_client_cert(struct openconnect_info *, const char *cert, 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); +int openconnect_get_idle_timeout(struct openconnect_info *); /* The returned structures are owned by the library and may be freed/replaced due to rekey or reconnect. Assume that once the mainloop starts, the @@ -664,6 +667,7 @@ int openconnect_has_system_key_support(void); /* Query and select from among supported protocols */ int openconnect_get_supported_protocols(struct oc_vpn_proto **protos); void openconnect_free_supported_protocols(struct oc_vpn_proto *protos); +const char *openconnect_get_protocol(struct openconnect_info *vpninfo); int openconnect_set_protocol(struct openconnect_info *vpninfo, const char *protocol); struct addrinfo; diff --git a/www/anyconnect.xml b/www/anyconnect.xml index 5ee1ce17..fd7e90ac 100644 --- a/www/anyconnect.xml +++ b/www/anyconnect.xml @@ -59,7 +59,7 @@ The username/password for OpenSSL RT is 'guest/guest'

GnuTLS

-

Support for Cisco's version of DTLS was included in GnuTLS from 3.0.21 onwards.

+

Support for Cisco's version of DTLS was included in GnuTLS from 3.0.21 onwards (commited in fd5ca1af).

diff --git a/www/changelog.xml b/www/changelog.xml index be4ce60a..6eb3378a 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -21,6 +21,12 @@
  • Fix portability of shell scripts in test suite.
  • Add Google Authenticator TOTP support for Juniper.
  • Add RFC7469 key PIN support for cert hashes.
  • +
  • Add protocol method to securely log out the Juniper session.
  • +
  • Relax requirements for Juniper hostname packet response to support old gateways.
  • +
  • Add API functions to query the supported protocols.
  • +
  • Verify ESP sequence numbers and warn even if replay protection is disabled.
  • +
  • Add support for PAN GlobalProtect VPN protocol (--protocol=gp).
  • +
  • Reorganize listing of command-line options, and include information on supported protocols.

  • OpenConnect v7.08 @@ -628,4 +634,3 @@ - diff --git a/www/features.xml b/www/features.xml index cbe91447..f878e96d 100644 --- a/www/features.xml +++ b/www/features.xml @@ -24,7 +24,7 @@
  • Automatic update of VPN server list / configuration.
  • Roaming support, allowing reconnection when the local IP address changes.
  • Run without root privileges (see here).
  • -
  • Support for "Cisco Secure Desktop" (see here) and "GlobalProtect HIP report" (see here).
  • +
  • Support for "Cisco Secure Desktop" (see here), Juniper TNCC (see here), and "GlobalProtect HIP report" (see here).
  • Graphical connection tools for various environments (see here).
  • diff --git a/www/globalprotect.xml b/www/globalprotect.xml index 655db9a2..a9de423a 100644 --- a/www/globalprotect.xml +++ b/www/globalprotect.xml @@ -16,6 +16,12 @@ href="https://tools.ietf.org/html/rfc3948">ESP, with routing and configuration information distributed in XML format.

    +

    GlobalProtect mode is requested by adding --protocol=gp +to the command line: +

    +  openconnect --protocol=gp vpn.example.com
    +

    +

    Authentication

    To authenticate, you connect to the secure web server (POST diff --git a/www/index.xml b/www/index.xml index 28d0b95a..ec2147e9 100644 --- a/www/index.xml +++ b/www/index.xml @@ -9,15 +9,18 @@

    OpenConnect

    -

    OpenConnect is an SSL VPN client initially created to support Cisco's AnyConnect SSL VPN. It has since been ported to support the Juniper SSL VPN which is now known as Pulse Connect Secure.

    +

    OpenConnect is an SSL VPN client initially created to support Cisco's AnyConnect SSL VPN. +It has since been ported to support the Juniper SSL VPN (which is now known as Pulse Connect Secure), +and to the Palo Alto Networks GlobalProtect SSL VPN.

    OpenConnect is released under the GNU Lesser Public License, version 2.1.

    Like vpnc, OpenConnect is not officially supported by, or associated in any way -with, Cisco Systems, Juniper Networks or Pulse Secure. It just happens to interoperate with their equipment. +with, Cisco Systems, Juniper Networks, Pulse Secure, or Palo Alto Networks. +It just happens to interoperate with their equipment.

    -

    Development of OpenConnect was started after a trial of the Cisco +

    Development of OpenConnect was started after a trial of the Cisco client under Linux found it to have many deficiencies:

    • Inability to use SSL certificates from a TPM or diff --git a/www/juniper.xml b/www/juniper.xml index d4f3fbfe..82f31061 100644 --- a/www/juniper.xml +++ b/www/juniper.xml @@ -16,10 +16,10 @@ experimental, and is quite likely to be deprecated in favour of the newer Junos Pulse protocol.

      -

      For the time being, Juniper mode is requested by adding --juniper +

      Juniper mode is requested by adding --protocol=nc to the command line:

      -  openconnect --juniper vpn.example.com
      +  openconnect --protocol=nc vpn.example.com
       

      Network Connect works very similarly to @@ -65,7 +65,7 @@ pass the cookie to OpenConnect with its -C option, for example:

      -

      Host Checker (tncc.jar)

      +

      Host Checker (tncc.jar)

      Many sites require a Java applet to run certain tests as a precondition of authentication. This works by sending a DSPREAUTH cookie @@ -80,7 +80,7 @@ along with the tncc-preload.so from this repository. It may also be necessary to pass a Mozilla-compatible user agent string:

      -  ./openconnect --juniper --useragent  'Mozilla/5.0 (Linux) Firefox' --csd-wrapper=./tncc-wrapper.py vpn.example.com
      +  ./openconnect --protocol=nc --useragent 'Mozilla/5.0 (Linux) Firefox' --csd-wrapper=./tncc-wrapper.py vpn.example.com