From 84ecff1b949ae2373fb6fb2992bffa7e1f76d35a Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Mon, 5 Jan 2015 16:32:07 +0000 Subject: [PATCH] Add decompress-only support for LZS Newer gateways support LZS compression, and it seems to be mutually exclusive with deflate. Signed-off-by: David Woodhouse --- Makefile.am | 2 +- cstp.c | 70 ++++++++++++++-------- lzs.c | 129 +++++++++++++++++++++++++++++++++++++++++ main.c | 3 +- openconnect-internal.h | 6 +- www/changelog.xml | 2 +- 6 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 lzs.c diff --git a/Makefile.am b/Makefile.am index 0f78a751..f5e459fb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ openconnect_SOURCES = xml.c main.c openconnect_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) $(LIBPSKC_CFLAGS) $(GSSAPI_CFLAGS) $(INTL_CFLAGS) $(ICONV_CFLAGS) $(LIBPCSCLITE_CFLAGS) openconnect_LDADD = libopenconnect.la $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(INTL_LIBS) $(ICONV_LIBS) -library_srcs = ssl.c http.c auth.c library.c compat.c dtls.c cstp.c \ +library_srcs = ssl.c http.c auth.c library.c compat.c dtls.c cstp.c lzs.c \ mainloop.c script.c ntlm.c digest.c lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c gnutls_tpm.c lib_srcs_openssl = openssl.c openssl-pkcs11.c diff --git a/cstp.c b/cstp.c index e14d552e..2689cc6b 100644 --- a/cstp.c +++ b/cstp.c @@ -191,6 +191,10 @@ static int start_cstp_connection(struct openconnect_info *vpninfo) if (vpninfo->req_compr) { char sep = ' '; buf_append(reqbuf, "X-CSTP-Accept-Encoding:"); + if (vpninfo->req_compr & COMPR_LZS) { + buf_append(reqbuf, "%clzs", sep); + sep = ','; + } if (vpninfo->req_compr & COMPR_DEFLATE) { buf_append(reqbuf, "%cdeflate", sep); sep = ','; @@ -375,6 +379,8 @@ static int start_cstp_connection(struct openconnect_info *vpninfo) } else if (!strcmp(buf + 7, "Content-Encoding")) { if (!strcmp(colon, "deflate")) vpninfo->cstp_compr = COMPR_DEFLATE; + else if (!strcmp(colon, "lzs")) + vpninfo->cstp_compr = COMPR_LZS; else { vpn_progress(vpninfo, PRG_ERR, _("Unknown CSTP-Content-Encoding %s\n"), @@ -653,44 +659,61 @@ static int cstp_reconnect(struct openconnect_info *vpninfo) return 0; } -static int inflate_and_queue_packet(struct openconnect_info *vpninfo, - unsigned char *buf, int len) +static int decompress_and_queue_packet(struct openconnect_info *vpninfo, + unsigned char *buf, int len) { struct pkt *new = malloc(sizeof(struct pkt) + vpninfo->ip_info.mtu); - uint32_t pkt_sum; + const char *comprtype; if (!new) return -ENOMEM; new->next = NULL; - vpninfo->inflate_strm.next_in = buf; - vpninfo->inflate_strm.avail_in = len - 4; + if (vpninfo->cstp_compr == COMPR_DEFLATE) { + uint32_t pkt_sum; + + /* Not sure this actually needs to be translated? */ + comprtype = _("deflate"); - vpninfo->inflate_strm.next_out = new->data; - vpninfo->inflate_strm.avail_out = vpninfo->ip_info.mtu; - vpninfo->inflate_strm.total_out = 0; + vpninfo->inflate_strm.next_in = buf; + vpninfo->inflate_strm.avail_in = len - 4; - if (inflate(&vpninfo->inflate_strm, Z_SYNC_FLUSH)) { - vpn_progress(vpninfo, PRG_ERR, _("inflate failed\n")); - free(new); - return -EINVAL; - } + vpninfo->inflate_strm.next_out = new->data; + vpninfo->inflate_strm.avail_out = vpninfo->ip_info.mtu; + vpninfo->inflate_strm.total_out = 0; - new->len = vpninfo->inflate_strm.total_out; + if (inflate(&vpninfo->inflate_strm, Z_SYNC_FLUSH)) { + vpn_progress(vpninfo, PRG_ERR, _("inflate failed\n")); + free(new); + return -EINVAL; + } + + new->len = vpninfo->inflate_strm.total_out; - vpninfo->inflate_adler32 = adler32(vpninfo->inflate_adler32, - new->data, new->len); + vpninfo->inflate_adler32 = adler32(vpninfo->inflate_adler32, + new->data, new->len); - pkt_sum = buf[len - 1] | (buf[len - 2] << 8) | - (buf[len - 3] << 16) | (buf[len - 4] << 24); + pkt_sum = buf[len - 1] | (buf[len - 2] << 8) | + (buf[len - 3] << 16) | (buf[len - 4] << 24); - if (vpninfo->inflate_adler32 != pkt_sum) - vpninfo->quit_reason = "Compression (inflate) adler32 failure"; + if (vpninfo->inflate_adler32 != pkt_sum) + vpninfo->quit_reason = "Compression (inflate) adler32 failure"; + } else { + comprtype = "LZS"; + + new->len = lzs_decompress(new->data, vpninfo->ip_info.mtu, buf, len); + if (new->len < 0) { + vpn_progress(vpninfo, PRG_ERR, _("LZS decompression failed: %s\n"), + strerror(-new->len)); + free(new); + return len; + } + } vpn_progress(vpninfo, PRG_TRACE, - _("Received compressed data packet of %ld bytes\n"), - (long)vpninfo->inflate_strm.total_out); + _("Received %s compressed data packet of %d bytes (was %d)\n"), + comprtype, new->len, len); queue_packet(&vpninfo->incoming_queue, new); return 0; @@ -880,7 +903,8 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout) _("Compressed packet received in !deflate mode\n")); goto unknown_pkt; } - inflate_and_queue_packet(vpninfo, vpninfo->cstp_pkt->data, payload_len); + decompress_and_queue_packet(vpninfo, vpninfo->cstp_pkt->data, + payload_len); work_done = 1; continue; diff --git a/lzs.c b/lzs.c new file mode 100644 index 00000000..e783b3d3 --- /dev/null +++ b/lzs.c @@ -0,0 +1,129 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2008-2014 Intel Corporation. + * Copyright © 2008 Nick Andrew + * + * Author: David Woodhouse + * + * 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" + +#define GET_BITS(bits) \ +do { \ + if (srclen < 1 + (bits_left < bits)) \ + return -EINVAL; \ + /* Explicit comparison with 8 to optimise the bits == 9 case \ + * because the compiler doesn't know that bits_left can never \ + * be larger than 8. */ \ + if (bits >= 8 || bits >= bits_left) { \ + /* We need *all* the bits that are left in the current \ + * byte. Take them and bump the input pointer. */ \ + data = (src[0] << (bits - bits_left)) & ((1ULL << bits) - 1); \ + src++; \ + srclen--; \ + bits_left += 8 - bits; \ + if (bits > 8 || bits_left < 8) { \ + /* We need bits from the next byte too... */ \ + data |= src[0] >> bits_left; \ + /* ...if we used *all* of them then bump the \ + * input pointer again so we never leave \ + * bits_left == 0. */ \ + if (!bits_left) { \ + bits_left = 8; \ + src++; \ + srclen--; \ + } \ + } \ + } else { \ + /* We need fewer bits than are left in the current byte */ \ + data = (src[0] >> (bits_left - bits)) & ((1ULL << bits) - 1); \ + bits_left -= bits; \ + } \ +} while (0) + +int lzs_decompress(unsigned char *dst, int dstlen, const unsigned char *src, int srclen) +{ + int outlen = 0; + int bits_left = 8; /* Bits left in the current byte at *src */ + int data; + int offset, length; + + while (1) { + /* Get 9 bits, which is the minimum and a common case */ + GET_BITS(9); + + /* 0bbbbbbbb is a literal byte */ + if (data < 0x100) { + if (outlen == dstlen) + return -EFBIG; + dst[outlen++] = data; + continue; + } + + /* 110000000 is the end marker */ + if (data == 0x180) + return outlen; + + /* 11bbbbbbb is a 7-bit offset */ + offset = data & 0x7f; + + /* 10bbbbbbbbbbb is an 11-bit offset, so get the next 4 bits */ + if (data < 0x180) { + GET_BITS(4); + + offset <<= 4; + offset |= data; + } + + /* This is a compressed sequence; now get the length */ + GET_BITS(2); + if (data != 3) { + /* 00, 01, 10 ==> 2, 3, 4 */ + length = data + 2; + } else { + GET_BITS(2); + if (data != 3) { + /* 1100, 1101, 1110 => 5, 6, 7 */ + length = data + 5; + } else { + /* For each 1111 prefix add 15 to the length. Then add + the value of final nybble. */ + length = 8; + + while (1) { + GET_BITS(4); + if (data != 15) { + length += data; + break; + } + length += 15; + } + } + } + if (offset > outlen) + return -EINVAL; + if (length + outlen > dstlen) + return -EFBIG; + + while (length) { + dst[outlen] = dst[outlen - offset]; + outlen++; + length--; + } + } + return -EINVAL; +} diff --git a/main.c b/main.c index f640ef58..47be0374 100644 --- a/main.c +++ b/main.c @@ -1429,7 +1429,8 @@ int main(int argc, char **argv) (ip_info->netmask6 && ip_info->addr) ? " + " : "", ip_info->netmask6 ? : "", (vpninfo->dtls_state != DTLS_CONNECTED) ? - (vpninfo->cstp_compr == COMPR_DEFLATE) ? "SSL + deflate" : "SSL" + (vpninfo->cstp_compr == COMPR_DEFLATE) ? "SSL + deflate" : + (vpninfo->cstp_compr == COMPR_LZS) ? "SSL + lzs" : "SSL" : "DTLS"); if (!vpninfo->vpnc_script) { diff --git a/openconnect-internal.h b/openconnect-internal.h index 2401147c..9775c06d 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -141,7 +141,8 @@ struct pkt { #define DTLS_CONNECTED 4 #define COMPR_DEFLATE (1<<0) -#define COMPR_ALL (COMPR_DEFLATE) +#define COMPR_LZS (1<<1) +#define COMPR_ALL (COMPR_DEFLATE | COMPR_LZS) struct keepalive_info { int dpd; @@ -631,6 +632,9 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout); int cstp_bye(struct openconnect_info *vpninfo, const char *reason); void cstp_free_splits(struct openconnect_info *vpninfo); +/* lzs.c */ +int lzs_decompress(unsigned char *dst, int dstlen, const unsigned char *src, int srclen); + /* ssl.c */ unsigned string_is_hostname(const char* str); int connect_https_socket(struct openconnect_info *vpninfo); diff --git a/www/changelog.xml b/www/changelog.xml index d71c78f8..b0e0370d 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -15,7 +15,7 @@
  • OpenConnect HEAD
      -
    • No changelog entries yet
    • +
    • Add support for LZS decompression.

  • OpenConnect v7.02