From c1d6418861d32e9b6989bd6ec0d5066aaded847b Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Tue, 18 May 2021 09:53:06 +0100 Subject: [PATCH] Move oc_text_buf functions out to textbuf.c for easier unit testing Signed-off-by: David Woodhouse --- Makefile.am | 2 +- http-auth.c | 135 ------------ http.c | 296 -------------------------- openconnect-internal.h | 22 +- textbuf.c | 462 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 475 insertions(+), 442 deletions(-) create mode 100644 textbuf.c diff --git a/Makefile.am b/Makefile.am index fc6fa447..2b9dd139 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/http-auth.c b/http-auth.c index d6e2c6b7..fe142000 100644 --- a/http-auth.c +++ b/http-auth.c @@ -29,141 +29,6 @@ #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 26a3c38b..790f2cd8 100644 --- a/http.c +++ b/http.c @@ -35,302 +35,6 @@ 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 diff --git a/openconnect-internal.h b/openconnect-internal.h index 754c7159..cc4de49a 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -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 index 00000000..129475e5 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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; +} -- 2.49.0