]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Add initial SonicWall NetExtender support
authorAndreas Gnau <rondom@rondom.de>
Mon, 11 May 2020 19:20:58 +0000 (21:20 +0200)
committerAndreas Gnau <rondom@rondom.de>
Thu, 14 May 2020 16:26:38 +0000 (18:26 +0200)
LCP works, no error handling yet.

Signed-off-by: Andreas Gnau <rondom@rondom.de>
Makefile.am
library.c
nx.c [new file with mode: 0644]
openconnect-internal.h
ppp.c

index f8c57574ab794f957af0bfa874459ea358a4ec42..c7b7ac628cdfd1ae6a7d6080f4bf66dbe746087c 100644 (file)
@@ -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
index f6b5c87d334ee2269444ae848f9da2c1c459037c..e771cd52ad8b4defd3101d06240027e901080811 100644 (file)
--- 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 (file)
index 0000000..d9789b6
--- /dev/null
+++ b/nx.c
@@ -0,0 +1,123 @@
+/*
+ * OpenConnect (SSL + DTLS) VPN client
+ *
+ * Copyright © 2020 Andreas Gnau
+ *
+ * Author: Andreas Gnau <rondom@rondom.de>
+ *
+ * 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 <config.h>
+
+#include <errno.h>
+
+#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
index 1c1a8784c678bfdd9b0872a95c3f2c7453739f6e..35bf7c1b6032d316bd6174001d7a56fb3744655a 100644 (file)
@@ -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 64cd8cb54197d94100df387cca7c74cb5e4efb67..f2a3342a1197e0a469dd716f2ff60f505567de72 100644 (file)
--- 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;