From: Andreas Gnau Date: Mon, 11 May 2020 19:20:58 +0000 (+0200) Subject: Add initial SonicWall NetExtender support X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=0f64527489cc49d56d301b19f0fbfda215ce0a87;p=users%2Fdwmw2%2Fopenconnect.git Add initial SonicWall NetExtender support LCP works, no error handling yet. Signed-off-by: Andreas Gnau --- diff --git a/Makefile.am b/Makefile.am index f8c57574..c7b7ac62 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,7 @@ endif library_srcs = ssl.c http.c http-auth.c auth-common.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c openconnect-internal.h lib_srcs_cisco = auth.c cstp.c lib_srcs_juniper = oncp.c lzo.c auth-juniper.c +lib_srcs_nx = nx.c lib_srcs_pulse = pulse.c lib_srcs_f5 = f5.c lib_srcs_ppp = ppp.c ppp.h @@ -40,7 +41,9 @@ lib_srcs_oidc = oidc.c library_srcs += $(lib_srcs_juniper) $(lib_srcs_cisco) $(lib_srcs_oath) \ $(lib_srcs_globalprotect) $(lib_srcs_pulse) $(lib_srcs_f5) \ - $(lib_srcs_ppp) $(lib_srcs_fortinet) $(lib_srcs_oidc) + $(lib_srcs_ppp) $(lib_srcs_fortinet) $(lib_srcs_nx) \ + $(lib_srcs_oidc) + lib_srcs_gnutls = gnutls.c gnutls_tpm.c gnutls_tpm2.c lib_srcs_openssl = openssl.c openssl-pkcs11.c diff --git a/library.c b/library.c index f6b5c87d..e771cd52 100644 --- a/library.c +++ b/library.c @@ -167,6 +167,16 @@ static const struct vpn_proto openconnect_protos[] = { .udp_catch_probe = gpst_esp_catch_probe, #endif }, { + .name = "nx", + .pretty_name = N_("SonicWall NetExtender"), + .description = N_("Compatible with SonicWall NetExtender SSL VPN"), + .flags = OC_PROTO_PROXY, + .vpn_close_session = nx_bye, + .tcp_connect = nx_connect, + .tcp_mainloop = ppp_mainloop, + .add_http_headers = nx_common_headers, + .obtain_cookie = nx_obtain_cookie, + }, { .name = "pulse", .pretty_name = N_("Pulse Connect Secure"), .description = N_("Compatible with Pulse Connect Secure SSL VPN"), diff --git a/nx.c b/nx.c new file mode 100644 index 00000000..d9789b68 --- /dev/null +++ b/nx.c @@ -0,0 +1,123 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2020 Andreas Gnau + * + * Author: Andreas Gnau + * + * 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 + +#include + +#include "openconnect-internal.h" + +int nx_obtain_cookie(struct openconnect_info *vpninfo) +{ + vpn_progress( + vpninfo, PRG_ERR, + _("Authentication for Net Extender not implemented yet.\n")); + return -EINVAL; +} + +void nx_common_headers(struct openconnect_info *vpninfo, + struct oc_text_buf *buf) +{ + http_common_headers(vpninfo, buf); + dump_buf(vpninfo, PRG_ERR, buf->data); // TODO: XXX + // TODO: Is this the place to manipulate user agent (NX requires the UA to contain netextender) +} + +int nx_connect(struct openconnect_info *vpninfo) +{ + int ret = -EINVAL; + struct oc_text_buf *reqbuf = NULL; + char *auth_token = NULL; + int auth_token_len = -1; + int ipv4 = 1; // TODO: get from info + int ipv6 = 0; + + // TODO: check for correct swap-cookie + if (!vpninfo->cookie) { + vpn_progress(vpninfo, PRG_ERR, + _("Malformed cookie or no cookie given\n")); + return -EINVAL; + } + // TODO: get auth_token and other info from /cgi-bin/sslvpnclient?launchplatform=mac&neProto=3&supportipv6=yes + auth_token = openconnect_base64_decode(&auth_token_len, vpninfo->cookie); + if (!auth_token) + return auth_token_len; + // TODO: get ECP (trojan) info from /cgi-bin/sslvpnclient?epcversionquery=nxx + ret = openconnect_open_https(vpninfo); + if (ret) + return ret; + + reqbuf = buf_alloc(); + if (!reqbuf) + return -errno; + + buf_append(reqbuf, "CONNECT localhost:0 HTTP/1.0\r\n"); + buf_append(reqbuf, "X-SSLVPN-PROTOCOL: 2.0\r\n"); + buf_append(reqbuf, "X-SSLVPN-SERVICE: NETEXTENDER\r\n"); + buf_append(reqbuf, "Connection-Medium: MacOS\r\n"); + buf_append(reqbuf, "Frame-Encode: off\r\n"); + buf_append(reqbuf, "X-NE-PROTOCOL: 2.0\r\n"); + buf_append(reqbuf, "Proxy-Authorization: %.*s\r\n", auth_token_len, + auth_token); + // TODO: use set string for nx in openconnect_set_reported_os + buf_append(reqbuf, "X-NX-Client-Platform: Linux\r\n"); + buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent); + buf_append(reqbuf, "\r\n"); + if ((ret = buf_error(reqbuf) != 0)) { + vpn_progress(vpninfo, PRG_ERR, + _("Error creating HTTPS CONNECT request\n")); + goto out; + } + if (vpninfo->dump_http_traffic) + dump_buf(vpninfo, '>', reqbuf->data); + vpninfo->ssl_write(vpninfo, reqbuf->data, reqbuf->pos); + + // In case of success, there won't be a HTTP 200, data will start straight away + // TODO: refactor process_http_response to handle this, so we can use it and do proper error handling + // We expect either a HTTP response (failure) or a size (BE, 4b) (success). + // The size will be smaller than 0x01000000 for sure, so we can use the + // first byte as an indicator of success and don't need to check for "HTTP" + // TODO: actually handle errors as described above + vpn_progress(vpninfo, PRG_DEBUG, _("Connection established\n")); + vpninfo->ppp = openconnect_ppp_new(PPP_ENCAP_NX_HDLC, ipv4, ipv6); + if (!vpninfo->ppp) { + ret = -ENOMEM; + goto out; + } + + ret = 0; + +out: + if (ret < 0) + openconnect_close_https(vpninfo, 0); + else { + monitor_fd_new(vpninfo, ssl); + monitor_read_fd(vpninfo, ssl); + monitor_except_fd(vpninfo, ssl); + } + + buf_free(reqbuf); + free(auth_token); + return ret; +} + +int nx_bye(struct openconnect_info *vpninfo, const char *reason) +{ + //ppp_bye(vpninfo); + // TODO: implement + return -EINVAL; +} \ No newline at end of file diff --git a/openconnect-internal.h b/openconnect-internal.h index 1c1a8784..35bf7c1b 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -179,7 +179,8 @@ struct pkt { #define PPP_ENCAP_F5 1 /* F5 BigIP no HDLC */ #define PPP_ENCAP_F5_HDLC 2 /* F5 BigIP HDLC */ #define PPP_ENCAP_FORTINET_HDLC 3 /* Fortinet HDLC */ -#define PPP_ENCAP_MAX PPP_ENCAP_FORTINET_HDLC +#define PPP_ENCAP_NX_HDLC 4 /* SonicWall NetExtender HDLC */ +#define PPP_ENCAP_MAX PPP_ENCAP_NX_HDLC #define COMPR_DEFLATE (1<<0) #define COMPR_LZS (1<<1) @@ -925,6 +926,12 @@ void oncp_esp_close(struct openconnect_info *vpninfo); int oncp_esp_send_probes(struct openconnect_info *vpninfo); int oncp_esp_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt); +/* nx.c */ +int nx_obtain_cookie(struct openconnect_info *vpninfo); +void nx_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf); +int nx_connect(struct openconnect_info *vpninfo); +int nx_bye(struct openconnect_info *vpninfo, const char *reason); + /* pulse.c */ int pulse_obtain_cookie(struct openconnect_info *vpninfo); void pulse_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf); diff --git a/ppp.c b/ppp.c index 64cd8cb5..f2a3342a 100644 --- a/ppp.c +++ b/ppp.c @@ -158,7 +158,7 @@ static int unhdlc_in_place(struct openconnect_info *vpninfo, unsigned char *byte static const char *ppps_names[] = { "DEAD", "ESTABLISH", "OPENED", "AUTHENTICATE", "NETWORK", "TERMINATE" }; static const char *encap_names[PPP_ENCAP_MAX+1] = { NULL, - "F5", "F5 HDLC", "FORTINET HDLC" }; + "F5", "F5 HDLC", "FORTINET HDLC", "NX HDLC" }; static const char *lcp_names[] = { NULL, "Configure-Request", "Configure-Ack", "Configure-Nak", "Configure-Reject", @@ -186,6 +186,11 @@ struct oc_ppp *openconnect_ppp_new(int encap, int want_ipv4, int want_ipv6) ppp->hdlc = 1; break; + case PPP_ENCAP_NX_HDLC: + ppp->encap_len = 4; + ppp->hdlc = 1; + break; + default: free(ppp); return NULL; @@ -684,7 +689,7 @@ int ppp_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) handle that */ unsigned char *ph, *pp; int receive_mtu = MAX(16384, vpninfo->ip_info.mtu); - int len, payload_len; + int len, payload_len, payload_len_hdr; if (!vpninfo->cstp_pkt) { vpninfo->cstp_pkt = malloc(sizeof(struct pkt) + receive_mtu); @@ -752,10 +757,25 @@ int ppp_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) continue; /* unhdlc_in_place already logged */ if (pp != ph + len) vpn_progress(vpninfo, PRG_ERR, - _("Packet contains %ld bytes after payload. Concatenated packets are not handled yet.\n"), - len - (pp - ph)); - //if (vpninfo->dump_http_traffic) - // dump_buf_hex(vpninfo, PRG_TRACE, '<', pp, payload_len); + _("Packet contains %ld bytes after payload. Concatenated packets are not handled yet.\n"), + len - (pp - ph)); + if (vpninfo->dump_http_traffic) + dump_buf_hex(vpninfo, PRG_TRACE, '<', pp, payload_len); + break; + + case PPP_ENCAP_NX_HDLC: + payload_len_hdr = load_be32(ph); + payload_len = unhdlc_in_place(vpninfo, ph + ppp->encap_len, len, &pp); + vpn_progress(vpninfo, PRG_INFO, "payload_len_hdr: %x, payload_len: %x, len: %x\n", + payload_len_hdr, payload_len, len); + if (payload_len < 0) + continue; /* unhdlc_in_place already logged */ + if (pp != ph + len) + vpn_progress(vpninfo, PRG_ERR, + _("Packet contains %ld bytes after payload. Concatenated packets are not handled yet.\n"), + len - (pp - ph)); + if (vpninfo->dump_http_traffic) + dump_buf_hex(vpninfo, PRG_TRACE, '<', pp, payload_len); break; default: @@ -953,6 +973,17 @@ int ppp_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable) free(vpninfo->current_ssl_pkt); vpninfo->current_ssl_pkt = this; break; + case PPP_ENCAP_NX_HDLC: + /* XX: use worst-case escaping for LCP */ + this = hdlc_into_new_pkt(vpninfo, this->data + n, this->len - n, + proto == PPP_LCP ? ASYNCMAP_LCP : ppp->out_asyncmap); + if (!this) + return 1; /* XX */ + store_be32(this->data + n - 4, this->len - n); + free(vpninfo->current_ssl_pkt); + this->ppp.hlen = -n + 4; + vpninfo->current_ssl_pkt = this; + break; default: /* XX: fail */ break;