123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- /*
- * 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 "openconnect-internal.h"
- #include <unistd.h>
- #include <string.h>
- #include <ctype.h>
- #include <errno.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <stddef.h>
- #include <limits.h>
- #include <stdarg.h>
- #define BUF_CHUNK_SIZE 4096
- #define OC_BUF_MAX ((unsigned)(16*1024*1024))
- 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;
- }
- 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_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;
- }
- 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 > OC_BUF_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;
- if (!utf8)
- return 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;
- if (len < 0 || line_len < 0 || (line_len & 3)) {
- buf->error = -EINVAL;
- return;
- }
- unsigned int needed = ((len + 2u) / 3) * 4;
- /* Line endings, but not for the last line if it reaches line_len */
- if (line_len && needed)
- needed += (needed - 1) / line_len;
- needed++; /* Allow for the trailing NUL */
- if (needed >= (unsigned)(OC_BUF_MAX - buf->pos)) {
- buf->error = -E2BIG;
- return;
- }
- if (buf_ensure_space(buf, needed))
- return;
- #ifdef BUFTEST
- int orig_len = len, orig_pos = buf->pos;
- #endif
- int ll = 0;
- while (len > 0) {
- if (line_len) {
- if (ll >= line_len) {
- ll = 0;
- buf->data[buf->pos++] = '\n';
- }
- ll += 4;
- }
- 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;
- }
- #ifdef BUFTEST
- if (buf->pos != orig_pos + needed - 1) {
- printf("Used %d instead of calculated %d for %d bytes at line len %d\n",
- buf->pos - orig_pos, needed, orig_len, line_len);
- buf->error = -EIO;
- }
- #endif
- buf->data[buf->pos] = 0;
- }
|