]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Move oc_text_buf functions out to textbuf.c for easier unit testing
authorDavid Woodhouse <dwmw2@infradead.org>
Tue, 18 May 2021 08:53:06 +0000 (09:53 +0100)
committerDavid Woodhouse <dwmw2@infradead.org>
Tue, 18 May 2021 08:53:06 +0000 (09:53 +0100)
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Makefile.am
http-auth.c
http.c
openconnect-internal.h
textbuf.c [new file with mode: 0644]

index fc6fa44771c2b4aa24c535fd2b5db3ab76fdacab..2b9dd139cba236ab528ce39f15d95511e3229323 100644 (file)
@@ -35,7 +35,7 @@ openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) \
 if OPENCONNECT_WIN32
 openconnect_SOURCES += openconnect.rc
 endif
-library_srcs = ssl.c http.c http-auth.c auth-common.c auth-html.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c mtucalc.c openconnect-internal.h
+library_srcs = ssl.c http.c textbuf.c http-auth.c auth-common.c auth-html.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c mtucalc.c openconnect-internal.h
 lib_srcs_cisco = auth.c cstp.c
 lib_srcs_juniper = oncp.c lzo.c auth-juniper.c
 lib_srcs_pulse = pulse.c
index d6e2c6b7982d605622a1e464af0b2695a5e72a37..fe142000efae0a681c42a4ded21ee6215ea5a0fe 100644 (file)
 
 #include "openconnect-internal.h"
 
-/* Ick. Yet another wheel to reinvent. But although we could pull it
-   in from OpenSSL, we can't from GnuTLS */
-
-static inline int b64_char(char c)
-{
-       if (c >= 'A' && c <= 'Z')
-               return c - 'A';
-       if (c >= 'a' && c <= 'z')
-               return c - 'a' + 26;
-       if (c >= '0' && c <= '9')
-               return c - '0' + 52;
-       if (c == '+')
-               return 62;
-       if (c == '/')
-               return 63;
-       return -1;
-}
-
-void *openconnect_base64_decode(int *ret_len, const char *in)
-{
-       unsigned char *buf;
-       int b[4];
-       int len = strlen(in);
-
-       if (len & 3) {
-               *ret_len = -EINVAL;
-               return NULL;
-       }
-       len = (len * 3) / 4;
-       buf = malloc(len);
-       if (!buf) {
-               *ret_len = -ENOMEM;
-               return NULL;
-       }
-
-       len = 0;
-       while (*in) {
-               if (!in[1] || !in[2] || !in[3])
-                       goto err;
-               b[0] = b64_char(in[0]);
-               b[1] = b64_char(in[1]);
-               if (b[0] < 0 || b[1] < 0)
-                       goto err;
-               buf[len++] = (b[0] << 2) | (b[1] >> 4);
-
-               if (in[2] == '=') {
-                       if (in[3] != '=' || in[4] != 0)
-                               goto err;
-                       break;
-               }
-               b[2] = b64_char(in[2]);
-               if (b[2] < 0)
-                       goto err;
-               buf[len++] = (b[1] << 4) | (b[2] >> 2);
-               if (in[3] == '=') {
-                       if (in[4] != 0)
-                               goto err;
-                       break;
-               }
-               b[3] = b64_char(in[3]);
-               if (b[3] < 0)
-                       goto err;
-               buf[len++] = (b[2] << 6) | b[3];
-               in += 4;
-       }
-       *ret_len = len;
-       return buf;
-
- err:
-       free(buf);
-       *ret_len = -EINVAL;
-       return NULL;
-}
-
-static const char b64_table[] = {
-       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
-       'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
-       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
-       'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
-};
-
-void buf_append_base64(struct oc_text_buf *buf, const void *bytes, int len,
-                      int line_len)
-{
-       const unsigned char *in = bytes;
-       int hibits;
-
-       if (!buf || buf->error)
-               return;
-
-       unsigned int needed = ((len + 2u) / 3) * 4 + 1;
-       if (line_len)
-               needed += needed / line_len;
-
-       if (needed >= (unsigned)(INT_MAX - buf->pos)) {
-               buf->error = -E2BIG;
-               return;
-       }
-
-       if (buf_ensure_space(buf, needed))
-               return;
-
-       int ll = 0;
-       while (len > 0) {
-               if (line_len) {
-                       ll += 4;
-                       if (ll >= line_len) {
-                               ll = 0;
-                               buf->data[buf->pos++] = '\n';
-                       }
-               }
-
-               buf->data[buf->pos++] = b64_table[in[0] >> 2];
-               hibits = (in[0] << 4) & 0x30;
-               if (len == 1) {
-                       buf->data[buf->pos++] = b64_table[hibits];
-                       buf->data[buf->pos++] = '=';
-                       buf->data[buf->pos++] = '=';
-                       break;
-               }
-               buf->data[buf->pos++] = b64_table[hibits | (in[1] >> 4)];
-               hibits = (in[1] << 2) & 0x3c;
-               if (len == 2) {
-                       buf->data[buf->pos++] = b64_table[hibits];
-                       buf->data[buf->pos++] = '=';
-                       break;
-               }
-               buf->data[buf->pos++] = b64_table[hibits | (in[2] >> 6)];
-               buf->data[buf->pos++] = b64_table[in[2] & 0x3f];
-               in += 3;
-               len -= 3;
-       }
-       buf->data[buf->pos] = 0;
-}
-
 static int basic_authorization(struct openconnect_info *vpninfo, int proxy,
                               struct http_auth_state *auth_state,
                               struct oc_text_buf *hdrbuf)
diff --git a/http.c b/http.c
index 26a3c38b81e156eff87d45a19e58937e2952be1e..790f2cd8bb7463d7743869badf4d520ea8ed49dc 100644 (file)
--- a/http.c
+++ b/http.c
 static int proxy_write(struct openconnect_info *vpninfo, char *buf, size_t len);
 static int proxy_read(struct openconnect_info *vpninfo, char *buf, size_t len);
 
-#define BUF_CHUNK_SIZE 4096
-
-struct oc_text_buf *buf_alloc(void)
-{
-       return calloc(1, sizeof(struct oc_text_buf));
-}
-
-void buf_append_urlencoded(struct oc_text_buf *buf, const char *str)
-{
-       while (str && *str) {
-               unsigned char c = *str;
-               if (c < 0x80 && (isalnum((int)(c)) || c=='-' || c=='_' || c=='.' || c=='~'))
-                       buf_append_bytes(buf, str, 1);
-               else
-                       buf_append(buf, "%%%02x", c);
-
-               str++;
-       }
-}
-
-void buf_append_xmlescaped(struct oc_text_buf *buf, const char *str)
-{
-       while (str && *str) {
-               unsigned char c = *str;
-               if (c=='<' || c=='>' || c=='&' || c=='"' || c=='\'')
-                       buf_append(buf, "&#x%02x;", c);
-               else
-                       buf_append_bytes(buf, str, 1);
-
-               str++;
-       }
-}
-
-void buf_append_be16(struct oc_text_buf *buf, uint16_t val)
-{
-       unsigned char b[2];
-
-       store_be16(b, val);
-
-       buf_append_bytes(buf, b, 2);
-}
-
-void buf_append_be32(struct oc_text_buf *buf, uint32_t val)
-{
-       unsigned char b[4];
-
-       store_be32(b, val);
-
-       buf_append_bytes(buf, b, 4);
-}
-
-void buf_append_le16(struct oc_text_buf *buf, uint16_t val)
-{
-       unsigned char b[2];
-
-       store_le16(b, val);
-
-       buf_append_bytes(buf, b, 2);
-}
-
-void buf_append_hex(struct oc_text_buf *buf, const void *str, unsigned len)
-{
-       const unsigned char *data = str;
-       unsigned i;
-
-       for (i = 0; i < len; i++)
-               buf_append(buf, "%02x", (unsigned)data[i]);
-}
-
-void buf_truncate(struct oc_text_buf *buf)
-{
-       if (!buf)
-               return;
-
-       if (buf->data)
-               memset(buf->data, 0, buf->pos);
-
-       buf->pos = 0;
-}
-
-int buf_ensure_space(struct oc_text_buf *buf, int len)
-{
-       unsigned int new_buf_len;
-
-       if (!buf)
-               return -ENOMEM;
-
-       new_buf_len = (buf->pos + len + BUF_CHUNK_SIZE - 1) & ~(BUF_CHUNK_SIZE - 1);
-
-       if (new_buf_len <= buf->buf_len)
-               return 0;
-
-       if (new_buf_len > INT_MAX) {
-               buf->error = -E2BIG;
-               return buf->error;
-       } else {
-               realloc_inplace(buf->data, new_buf_len);
-               if (!buf->data)
-                       buf->error = -ENOMEM;
-               else
-                       buf->buf_len = new_buf_len;
-       }
-       return buf->error;
-}
-
-void  __attribute__ ((format (printf, 2, 3)))
-       buf_append(struct oc_text_buf *buf, const char *fmt, ...)
-{
-       va_list ap;
-
-       if (!buf || buf->error)
-               return;
-
-       if (buf_ensure_space(buf, 1))
-               return;
-
-       while (1) {
-               int max_len = buf->buf_len - buf->pos, ret;
-
-               va_start(ap, fmt);
-               ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
-               va_end(ap);
-               if (ret < 0) {
-                       buf->error = -EIO;
-                       break;
-               } else if (ret < max_len) {
-                       buf->pos += ret;
-                       break;
-               } else if (buf_ensure_space(buf, ret))
-                       break;
-       }
-}
-
-void buf_append_bytes(struct oc_text_buf *buf, const void *bytes, int len)
-{
-       if (!buf || buf->error)
-               return;
-
-       if (buf_ensure_space(buf, len + 1))
-               return;
-
-       memcpy(buf->data + buf->pos, bytes, len);
-       buf->pos += len;
-       buf->data[buf->pos] = 0;
-}
-
-void buf_append_from_utf16le(struct oc_text_buf *buf, const void *_utf16)
-{
-       const unsigned char *utf16 = _utf16;
-       unsigned char utf8[4];
-       int c;
-
-       if (!utf16)
-               return;
-
-       while (utf16[0] || utf16[1]) {
-               if ((utf16[1] & 0xfc) == 0xd8 && (utf16[3] & 0xfc) == 0xdc) {
-                       c = ((load_le16(utf16) & 0x3ff) << 10)|
-                               (load_le16(utf16 + 2) & 0x3ff);
-                       c += 0x10000;
-                       utf16 += 4;
-               } else {
-                       c = load_le16(utf16);
-                       utf16 += 2;
-               }
-
-               if (c < 0x80) {
-                       utf8[0] = c;
-                       buf_append_bytes(buf, utf8, 1);
-               } else if (c < 0x800) {
-                       utf8[0] = 0xc0 | (c >> 6);
-                       utf8[1] = 0x80 | (c & 0x3f);
-                       buf_append_bytes(buf, utf8, 2);
-               } else if (c < 0x10000) {
-                       utf8[0] = 0xe0 | (c >> 12);
-                       utf8[1] = 0x80 | ((c >> 6) & 0x3f);
-                       utf8[2] = 0x80 | (c & 0x3f);
-                       buf_append_bytes(buf, utf8, 3);
-               } else {
-                       utf8[0] = 0xf0 | (c >> 18);
-                       utf8[1] = 0x80 | ((c >> 12) & 0x3f);
-                       utf8[2] = 0x80 | ((c >> 6) & 0x3f);
-                       utf8[3] = 0x80 | (c & 0x3f);
-                       buf_append_bytes(buf, utf8, 4);
-               }
-       }
-       utf8[0] = 0;
-       buf_append_bytes(buf, utf8, 1);
-}
-
-int get_utf8char(const char **p)
-{
-       const char *utf8 = *p;
-       unsigned char c;
-       int utfchar, nr_extra, min;
-
-       c = *(utf8++);
-       if (c < 128) {
-               utfchar = c;
-               nr_extra = 0;
-               min = 0;
-       } else if ((c & 0xe0) == 0xc0) {
-               utfchar = c & 0x1f;
-               nr_extra = 1;
-               min = 0x80;
-       } else if ((c & 0xf0) == 0xe0) {
-               utfchar = c & 0x0f;
-               nr_extra = 2;
-               min = 0x800;
-       } else if ((c & 0xf8) == 0xf0) {
-               utfchar = c & 0x07;
-               nr_extra = 3;
-               min = 0x10000;
-       } else {
-               return -EILSEQ;
-       }
-
-       while (nr_extra--) {
-               c = *(utf8++);
-               if ((c & 0xc0) != 0x80)
-                       return -EILSEQ;
-
-               utfchar <<= 6;
-               utfchar |= (c & 0x3f);
-       }
-       if (utfchar > 0x10ffff || utfchar < min)
-               return -EILSEQ;
-
-       *p = utf8;
-       return utfchar;
-}
-
-int buf_append_utf16le(struct oc_text_buf *buf, const char *utf8)
-{
-       int utfchar, len = 0;
-
-       /* Ick. Now I'm implementing my own UTF8 handling too. Perhaps it's
-          time to bite the bullet and start requiring something like glib? */
-       while (*utf8) {
-               utfchar = get_utf8char(&utf8);
-               if (utfchar < 0) {
-                       if (buf)
-                               buf->error = utfchar;
-                       return utfchar;
-               }
-               if (!buf)
-                       continue;
-
-               if (utfchar >= 0x10000) {
-                       utfchar -= 0x10000;
-                       if (buf_ensure_space(buf, 4))
-                               return buf_error(buf);
-                       store_le16(buf->data + buf->pos, (utfchar >> 10) | 0xd800);
-                       store_le16(buf->data + buf->pos + 2, (utfchar & 0x3ff) | 0xdc00);
-                       buf->pos += 4;
-                       len += 4;
-               } else {
-                       if (buf_ensure_space(buf, 2))
-                               return buf_error(buf);
-                       store_le16(buf->data + buf->pos, utfchar);
-                       buf->pos += 2;
-                       len += 2;
-               }
-       }
-
-       /* We were only being used for validation */
-       if (!buf)
-               return 0;
-
-       /* Ensure UTF16 is NUL-terminated */
-       if (buf_ensure_space(buf, 2))
-               return buf_error(buf);
-       buf->data[buf->pos] = buf->data[buf->pos + 1] = 0;
-
-       return len;
-}
-
-int buf_error(struct oc_text_buf *buf)
-{
-       return buf ? buf->error : -ENOMEM;
-}
-
-int buf_free(struct oc_text_buf *buf)
-{
-       int error = buf_error(buf);
-
-       if (buf) {
-               buf_truncate(buf);
-               if (buf->data)
-                       free(buf->data);
-               free(buf);
-       }
-
-       return error;
-}
-
 /*
  * We didn't really want to have to do this for ourselves -- one might have
  * thought that it would be available in a library somewhere. But neither
index 754c7159e10fa6f6904561388f22739f3c2cb35a..cc4de49a04435df1f00ad53664051a7c4636b29c 100644 (file)
@@ -1285,14 +1285,17 @@ int can_gen_tokencode(struct openconnect_info *vpninfo,
                      struct oc_auth_form *form,
                      struct oc_form_opt *opt);
 
-/* http.c */
+/* textbuf,c */
 struct oc_text_buf *buf_alloc(void);
-void dump_buf(struct openconnect_info *vpninfo, char prefix, char *buf);
-void dump_buf_hex(struct openconnect_info *vpninfo, int loglevel, char prefix, unsigned char *buf, int len);
+int buf_error(struct oc_text_buf *buf);
+int buf_free(struct oc_text_buf *buf);
+void buf_truncate(struct oc_text_buf *buf);
 int buf_ensure_space(struct oc_text_buf *buf, int len);
+void buf_append_bytes(struct oc_text_buf *buf, const void *bytes, int len);
 void  __attribute__ ((format (printf, 2, 3)))
        buf_append(struct oc_text_buf *buf, const char *fmt, ...);
-void buf_append_bytes(struct oc_text_buf *buf, const void *bytes, int len);
+void buf_append_urlencoded(struct oc_text_buf *buf, const char *str);
+void buf_append_xmlescaped(struct oc_text_buf *buf, const char *str);
 void buf_append_be16(struct oc_text_buf *buf, uint16_t val);
 void buf_append_be32(struct oc_text_buf *buf, uint32_t val);
 void buf_append_le16(struct oc_text_buf *buf, uint16_t val);
@@ -1300,11 +1303,11 @@ void buf_append_hex(struct oc_text_buf *buf, const void *str, unsigned len);
 int buf_append_utf16le(struct oc_text_buf *buf, const char *utf8);
 int get_utf8char(const char **utf8);
 void buf_append_from_utf16le(struct oc_text_buf *buf, const void *utf16);
-void buf_truncate(struct oc_text_buf *buf);
-void buf_append_urlencoded(struct oc_text_buf *buf, const char *str);
-void buf_append_xmlescaped(struct oc_text_buf *buf, const char *str);
-int buf_error(struct oc_text_buf *buf);
-int buf_free(struct oc_text_buf *buf);
+void buf_append_base64(struct oc_text_buf *buf, const void *bytes, int len, int line_len);
+
+/* http.c */
+void dump_buf(struct openconnect_info *vpninfo, char prefix, char *buf);
+void dump_buf_hex(struct openconnect_info *vpninfo, int loglevel, char prefix, unsigned char *buf, int len);
 char *openconnect_create_useragent(const char *base);
 int process_proxy(struct openconnect_info *vpninfo, int ssl_sock);
 int internal_parse_url(const char *url, char **res_proto, char **res_host,
@@ -1324,7 +1327,6 @@ int handle_redirect(struct openconnect_info *vpninfo);
 void http_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf);
 
 /* http-auth.c */
-void buf_append_base64(struct oc_text_buf *buf, const void *bytes, int len, int line_len);
 void *openconnect_base64_decode(int *len, const char *in);
 void clear_auth_states(struct openconnect_info *vpninfo,
                       struct http_auth_state *auth_states, int reset);
diff --git a/textbuf.c b/textbuf.c
new file mode 100644 (file)
index 0000000..129475e
--- /dev/null
+++ b/textbuf.c
@@ -0,0 +1,462 @@
+/*
+ * OpenConnect (SSL + DTLS) VPN client
+ *
+ * Copyright © 2008-2016 Intel Corporation.
+ * Copyright © 2016-2021 David Woodhouse.
+ *
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ *
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "openconnect-internal.h"
+
+#define BUF_CHUNK_SIZE 4096
+
+struct oc_text_buf *buf_alloc(void)
+{
+       return calloc(1, sizeof(struct oc_text_buf));
+}
+
+int buf_error(struct oc_text_buf *buf)
+{
+       return buf ? buf->error : -ENOMEM;
+}
+
+int buf_free(struct oc_text_buf *buf)
+{
+       int error = buf_error(buf);
+
+       if (buf) {
+               buf_truncate(buf);
+               if (buf->data)
+                       free(buf->data);
+               free(buf);
+       }
+
+       return error;
+}
+
+void buf_truncate(struct oc_text_buf *buf)
+{
+       if (!buf)
+               return;
+
+       if (buf->data)
+               memset(buf->data, 0, buf->pos);
+
+       buf->pos = 0;
+}
+
+int buf_ensure_space(struct oc_text_buf *buf, int len)
+{
+       unsigned int new_buf_len;
+
+       if (!buf)
+               return -ENOMEM;
+
+       new_buf_len = (buf->pos + len + BUF_CHUNK_SIZE - 1) & ~(BUF_CHUNK_SIZE - 1);
+
+       if (new_buf_len <= buf->buf_len)
+               return 0;
+
+       if (new_buf_len > INT_MAX) {
+               buf->error = -E2BIG;
+               return buf->error;
+       } else {
+               realloc_inplace(buf->data, new_buf_len);
+               if (!buf->data)
+                       buf->error = -ENOMEM;
+               else
+                       buf->buf_len = new_buf_len;
+       }
+       return buf->error;
+}
+
+void buf_append_bytes(struct oc_text_buf *buf, const void *bytes, int len)
+{
+       if (!buf || buf->error)
+               return;
+
+       if (buf_ensure_space(buf, len + 1))
+               return;
+
+       memcpy(buf->data + buf->pos, bytes, len);
+       buf->pos += len;
+       buf->data[buf->pos] = 0;
+}
+
+void  __attribute__ ((format (printf, 2, 3)))
+       buf_append(struct oc_text_buf *buf, const char *fmt, ...)
+{
+       va_list ap;
+
+       if (!buf || buf->error)
+               return;
+
+       if (buf_ensure_space(buf, 1))
+               return;
+
+       while (1) {
+               int max_len = buf->buf_len - buf->pos, ret;
+
+               va_start(ap, fmt);
+               ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
+               va_end(ap);
+               if (ret < 0) {
+                       buf->error = -EIO;
+                       break;
+               } else if (ret < max_len) {
+                       buf->pos += ret;
+                       break;
+               } else if (buf_ensure_space(buf, ret))
+                       break;
+       }
+}
+
+void buf_append_urlencoded(struct oc_text_buf *buf, const char *str)
+{
+       while (str && *str) {
+               unsigned char c = *str;
+               if (c < 0x80 && (isalnum((int)(c)) || c=='-' || c=='_' || c=='.' || c=='~'))
+                       buf_append_bytes(buf, str, 1);
+               else
+                       buf_append(buf, "%%%02x", c);
+
+               str++;
+       }
+}
+
+void buf_append_xmlescaped(struct oc_text_buf *buf, const char *str)
+{
+       while (str && *str) {
+               unsigned char c = *str;
+               if (c=='<' || c=='>' || c=='&' || c=='"' || c=='\'')
+                       buf_append(buf, "&#x%02x;", c);
+               else
+                       buf_append_bytes(buf, str, 1);
+
+               str++;
+       }
+}
+
+void buf_append_be16(struct oc_text_buf *buf, uint16_t val)
+{
+       unsigned char b[2];
+
+       store_be16(b, val);
+
+       buf_append_bytes(buf, b, 2);
+}
+
+void buf_append_be32(struct oc_text_buf *buf, uint32_t val)
+{
+       unsigned char b[4];
+
+       store_be32(b, val);
+
+       buf_append_bytes(buf, b, 4);
+}
+
+void buf_append_le16(struct oc_text_buf *buf, uint16_t val)
+{
+       unsigned char b[2];
+
+       store_le16(b, val);
+
+       buf_append_bytes(buf, b, 2);
+}
+
+void buf_append_hex(struct oc_text_buf *buf, const void *str, unsigned len)
+{
+       const unsigned char *data = str;
+       unsigned i;
+
+       for (i = 0; i < len; i++)
+               buf_append(buf, "%02x", (unsigned)data[i]);
+}
+
+void buf_append_from_utf16le(struct oc_text_buf *buf, const void *_utf16)
+{
+       const unsigned char *utf16 = _utf16;
+       unsigned char utf8[4];
+       int c;
+
+       if (!utf16)
+               return;
+
+       while (utf16[0] || utf16[1]) {
+               if ((utf16[1] & 0xfc) == 0xd8 && (utf16[3] & 0xfc) == 0xdc) {
+                       c = ((load_le16(utf16) & 0x3ff) << 10)|
+                               (load_le16(utf16 + 2) & 0x3ff);
+                       c += 0x10000;
+                       utf16 += 4;
+               } else {
+                       c = load_le16(utf16);
+                       utf16 += 2;
+               }
+
+               if (c < 0x80) {
+                       utf8[0] = c;
+                       buf_append_bytes(buf, utf8, 1);
+               } else if (c < 0x800) {
+                       utf8[0] = 0xc0 | (c >> 6);
+                       utf8[1] = 0x80 | (c & 0x3f);
+                       buf_append_bytes(buf, utf8, 2);
+               } else if (c < 0x10000) {
+                       utf8[0] = 0xe0 | (c >> 12);
+                       utf8[1] = 0x80 | ((c >> 6) & 0x3f);
+                       utf8[2] = 0x80 | (c & 0x3f);
+                       buf_append_bytes(buf, utf8, 3);
+               } else {
+                       utf8[0] = 0xf0 | (c >> 18);
+                       utf8[1] = 0x80 | ((c >> 12) & 0x3f);
+                       utf8[2] = 0x80 | ((c >> 6) & 0x3f);
+                       utf8[3] = 0x80 | (c & 0x3f);
+                       buf_append_bytes(buf, utf8, 4);
+               }
+       }
+       utf8[0] = 0;
+       buf_append_bytes(buf, utf8, 1);
+}
+
+int get_utf8char(const char **p)
+{
+       const char *utf8 = *p;
+       unsigned char c;
+       int utfchar, nr_extra, min;
+
+       c = *(utf8++);
+       if (c < 128) {
+               utfchar = c;
+               nr_extra = 0;
+               min = 0;
+       } else if ((c & 0xe0) == 0xc0) {
+               utfchar = c & 0x1f;
+               nr_extra = 1;
+               min = 0x80;
+       } else if ((c & 0xf0) == 0xe0) {
+               utfchar = c & 0x0f;
+               nr_extra = 2;
+               min = 0x800;
+       } else if ((c & 0xf8) == 0xf0) {
+               utfchar = c & 0x07;
+               nr_extra = 3;
+               min = 0x10000;
+       } else {
+               return -EILSEQ;
+       }
+
+       while (nr_extra--) {
+               c = *(utf8++);
+               if ((c & 0xc0) != 0x80)
+                       return -EILSEQ;
+
+               utfchar <<= 6;
+               utfchar |= (c & 0x3f);
+       }
+       if (utfchar > 0x10ffff || utfchar < min)
+               return -EILSEQ;
+
+       *p = utf8;
+       return utfchar;
+}
+
+int buf_append_utf16le(struct oc_text_buf *buf, const char *utf8)
+{
+       int utfchar, len = 0;
+
+       /* Ick. Now I'm implementing my own UTF8 handling too. Perhaps it's
+          time to bite the bullet and start requiring something like glib? */
+       while (*utf8) {
+               utfchar = get_utf8char(&utf8);
+               if (utfchar < 0) {
+                       if (buf)
+                               buf->error = utfchar;
+                       return utfchar;
+               }
+               if (!buf)
+                       continue;
+
+               if (utfchar >= 0x10000) {
+                       utfchar -= 0x10000;
+                       if (buf_ensure_space(buf, 4))
+                               return buf_error(buf);
+                       store_le16(buf->data + buf->pos, (utfchar >> 10) | 0xd800);
+                       store_le16(buf->data + buf->pos + 2, (utfchar & 0x3ff) | 0xdc00);
+                       buf->pos += 4;
+                       len += 4;
+               } else {
+                       if (buf_ensure_space(buf, 2))
+                               return buf_error(buf);
+                       store_le16(buf->data + buf->pos, utfchar);
+                       buf->pos += 2;
+                       len += 2;
+               }
+       }
+
+       /* We were only being used for validation */
+       if (!buf)
+               return 0;
+
+       /* Ensure UTF16 is NUL-terminated */
+       if (buf_ensure_space(buf, 2))
+               return buf_error(buf);
+       buf->data[buf->pos] = buf->data[buf->pos + 1] = 0;
+
+       return len;
+}
+
+/* Ick. Yet another wheel to reinvent. But although we could pull it
+   in from OpenSSL, we can't from GnuTLS */
+
+static inline int b64_char(char c)
+{
+       if (c >= 'A' && c <= 'Z')
+               return c - 'A';
+       if (c >= 'a' && c <= 'z')
+               return c - 'a' + 26;
+       if (c >= '0' && c <= '9')
+               return c - '0' + 52;
+       if (c == '+')
+               return 62;
+       if (c == '/')
+               return 63;
+       return -1;
+}
+
+void *openconnect_base64_decode(int *ret_len, const char *in)
+{
+       unsigned char *buf;
+       int b[4];
+       int len = strlen(in);
+
+       if (len & 3) {
+               *ret_len = -EINVAL;
+               return NULL;
+       }
+       len = (len * 3) / 4;
+       buf = malloc(len);
+       if (!buf) {
+               *ret_len = -ENOMEM;
+               return NULL;
+       }
+
+       len = 0;
+       while (*in) {
+               if (!in[1] || !in[2] || !in[3])
+                       goto err;
+               b[0] = b64_char(in[0]);
+               b[1] = b64_char(in[1]);
+               if (b[0] < 0 || b[1] < 0)
+                       goto err;
+               buf[len++] = (b[0] << 2) | (b[1] >> 4);
+
+               if (in[2] == '=') {
+                       if (in[3] != '=' || in[4] != 0)
+                               goto err;
+                       break;
+               }
+               b[2] = b64_char(in[2]);
+               if (b[2] < 0)
+                       goto err;
+               buf[len++] = (b[1] << 4) | (b[2] >> 2);
+               if (in[3] == '=') {
+                       if (in[4] != 0)
+                               goto err;
+                       break;
+               }
+               b[3] = b64_char(in[3]);
+               if (b[3] < 0)
+                       goto err;
+               buf[len++] = (b[2] << 6) | b[3];
+               in += 4;
+       }
+       *ret_len = len;
+       return buf;
+
+ err:
+       free(buf);
+       *ret_len = -EINVAL;
+       return NULL;
+}
+
+static const char b64_table[] = {
+       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+       'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+       'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+void buf_append_base64(struct oc_text_buf *buf, const void *bytes, int len,
+                      int line_len)
+{
+       const unsigned char *in = bytes;
+       int hibits;
+
+       if (!buf || buf->error)
+               return;
+
+       unsigned int needed = ((len + 2u) / 3) * 4 + 1;
+       if (line_len)
+               needed += needed / line_len;
+
+       if (needed >= (unsigned)(INT_MAX - buf->pos)) {
+               buf->error = -E2BIG;
+               return;
+       }
+
+       if (buf_ensure_space(buf, needed))
+               return;
+
+       int ll = 0;
+       while (len > 0) {
+               if (line_len) {
+                       ll += 4;
+                       if (ll >= line_len) {
+                               ll = 0;
+                               buf->data[buf->pos++] = '\n';
+                       }
+               }
+
+               buf->data[buf->pos++] = b64_table[in[0] >> 2];
+               hibits = (in[0] << 4) & 0x30;
+               if (len == 1) {
+                       buf->data[buf->pos++] = b64_table[hibits];
+                       buf->data[buf->pos++] = '=';
+                       buf->data[buf->pos++] = '=';
+                       break;
+               }
+               buf->data[buf->pos++] = b64_table[hibits | (in[1] >> 4)];
+               hibits = (in[1] << 2) & 0x3c;
+               if (len == 2) {
+                       buf->data[buf->pos++] = b64_table[hibits];
+                       buf->data[buf->pos++] = '=';
+                       break;
+               }
+               buf->data[buf->pos++] = b64_table[hibits | (in[2] >> 6)];
+               buf->data[buf->pos++] = b64_table[in[2] & 0x3f];
+               in += 3;
+               len -= 3;
+       }
+       buf->data[buf->pos] = 0;
+}