summaryrefslogtreecommitdiff
path: root/src/libstrongswan/utils/chunk.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstrongswan/utils/chunk.c')
-rw-r--r--src/libstrongswan/utils/chunk.c821
1 files changed, 821 insertions, 0 deletions
diff --git a/src/libstrongswan/utils/chunk.c b/src/libstrongswan/utils/chunk.c
new file mode 100644
index 000000000..04f3eea7d
--- /dev/null
+++ b/src/libstrongswan/utils/chunk.c
@@ -0,0 +1,821 @@
+/*
+ * Copyright (C) 2008-2013 Tobias Brunner
+ * Copyright (C) 2005-2006 Martin Willi
+ * Copyright (C) 2005 Jan Hutter
+ * Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 General Public License
+ * for more details.
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pthread.h>
+#include <ctype.h>
+
+#include "chunk.h"
+#include "debug.h"
+
+/**
+ * Empty chunk.
+ */
+chunk_t chunk_empty = { NULL, 0 };
+
+/**
+ * Described in header.
+ */
+chunk_t chunk_create_clone(u_char *ptr, chunk_t chunk)
+{
+ chunk_t clone = chunk_empty;
+
+ if (chunk.ptr && chunk.len > 0)
+ {
+ clone.ptr = ptr;
+ clone.len = chunk.len;
+ memcpy(clone.ptr, chunk.ptr, chunk.len);
+ }
+
+ return clone;
+}
+
+/**
+ * Described in header.
+ */
+size_t chunk_length(const char* mode, ...)
+{
+ va_list chunks;
+ size_t length = 0;
+
+ va_start(chunks, mode);
+ while (TRUE)
+ {
+ switch (*mode++)
+ {
+ case 'm':
+ case 'c':
+ case 's':
+ {
+ chunk_t ch = va_arg(chunks, chunk_t);
+ length += ch.len;
+ continue;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ va_end(chunks);
+ return length;
+}
+
+/**
+ * Described in header.
+ */
+chunk_t chunk_create_cat(u_char *ptr, const char* mode, ...)
+{
+ va_list chunks;
+ chunk_t construct = chunk_create(ptr, 0);
+
+ va_start(chunks, mode);
+ while (TRUE)
+ {
+ bool free_chunk = FALSE, clear_chunk = FALSE;
+ chunk_t ch;
+
+ switch (*mode++)
+ {
+ case 's':
+ clear_chunk = TRUE;
+ /* FALL */
+ case 'm':
+ free_chunk = TRUE;
+ /* FALL */
+ case 'c':
+ ch = va_arg(chunks, chunk_t);
+ memcpy(ptr, ch.ptr, ch.len);
+ ptr += ch.len;
+ construct.len += ch.len;
+ if (clear_chunk)
+ {
+ chunk_clear(&ch);
+ }
+ else if (free_chunk)
+ {
+ free(ch.ptr);
+ }
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ va_end(chunks);
+
+ return construct;
+}
+
+/**
+ * Described in header.
+ */
+void chunk_split(chunk_t chunk, const char *mode, ...)
+{
+ va_list chunks;
+ u_int len;
+ chunk_t *ch;
+
+ va_start(chunks, mode);
+ while (TRUE)
+ {
+ if (*mode == '\0')
+ {
+ break;
+ }
+ len = va_arg(chunks, u_int);
+ ch = va_arg(chunks, chunk_t*);
+ /* a null chunk means skip len bytes */
+ if (ch == NULL)
+ {
+ chunk = chunk_skip(chunk, len);
+ continue;
+ }
+ switch (*mode++)
+ {
+ case 'm':
+ {
+ ch->len = min(chunk.len, len);
+ if (ch->len)
+ {
+ ch->ptr = chunk.ptr;
+ }
+ else
+ {
+ ch->ptr = NULL;
+ }
+ chunk = chunk_skip(chunk, ch->len);
+ continue;
+ }
+ case 'a':
+ {
+ ch->len = min(chunk.len, len);
+ if (ch->len)
+ {
+ ch->ptr = malloc(ch->len);
+ memcpy(ch->ptr, chunk.ptr, ch->len);
+ }
+ else
+ {
+ ch->ptr = NULL;
+ }
+ chunk = chunk_skip(chunk, ch->len);
+ continue;
+ }
+ case 'c':
+ {
+ ch->len = min(ch->len, chunk.len);
+ ch->len = min(ch->len, len);
+ if (ch->len)
+ {
+ memcpy(ch->ptr, chunk.ptr, ch->len);
+ }
+ else
+ {
+ ch->ptr = NULL;
+ }
+ chunk = chunk_skip(chunk, ch->len);
+ continue;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ va_end(chunks);
+}
+
+/**
+ * Described in header.
+ */
+bool chunk_write(chunk_t chunk, char *path, char *label, mode_t mask, bool force)
+{
+ mode_t oldmask;
+ FILE *fd;
+ bool good = FALSE;
+
+ if (!force && access(path, F_OK) == 0)
+ {
+ DBG1(DBG_LIB, " %s file '%s' already exists", label, path);
+ return FALSE;
+ }
+ oldmask = umask(mask);
+ fd = fopen(path, "w");
+ if (fd)
+ {
+ if (fwrite(chunk.ptr, sizeof(u_char), chunk.len, fd) == chunk.len)
+ {
+ DBG1(DBG_LIB, " written %s file '%s' (%d bytes)",
+ label, path, chunk.len);
+ good = TRUE;
+ }
+ else
+ {
+ DBG1(DBG_LIB, " writing %s file '%s' failed: %s",
+ label, path, strerror(errno));
+ }
+ fclose(fd);
+ }
+ else
+ {
+ DBG1(DBG_LIB, " could not open %s file '%s': %s", label, path,
+ strerror(errno));
+ }
+ umask(oldmask);
+ return good;
+}
+
+
+/** hex conversion digits */
+static char hexdig_upper[] = "0123456789ABCDEF";
+static char hexdig_lower[] = "0123456789abcdef";
+
+/**
+ * Described in header.
+ */
+chunk_t chunk_to_hex(chunk_t chunk, char *buf, bool uppercase)
+{
+ int i, len;
+ char *hexdig = hexdig_lower;
+
+ if (uppercase)
+ {
+ hexdig = hexdig_upper;
+ }
+
+ len = chunk.len * 2;
+ if (!buf)
+ {
+ buf = malloc(len + 1);
+ }
+ buf[len] = '\0';
+
+ for (i = 0; i < chunk.len; i++)
+ {
+ buf[i*2] = hexdig[(chunk.ptr[i] >> 4) & 0xF];
+ buf[i*2+1] = hexdig[(chunk.ptr[i] ) & 0xF];
+ }
+ return chunk_create(buf, len);
+}
+
+/**
+ * convert a signle hex character to its binary value
+ */
+static char hex2bin(char hex)
+{
+ switch (hex)
+ {
+ case '0' ... '9':
+ return hex - '0';
+ case 'A' ... 'F':
+ return hex - 'A' + 10;
+ case 'a' ... 'f':
+ return hex - 'a' + 10;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * Described in header.
+ */
+chunk_t chunk_from_hex(chunk_t hex, char *buf)
+{
+ int i, len;
+ u_char *ptr;
+ bool odd = FALSE;
+
+ /* subtract the number of optional ':' separation characters */
+ len = hex.len;
+ ptr = hex.ptr;
+ for (i = 0; i < hex.len; i++)
+ {
+ if (*ptr++ == ':')
+ {
+ len--;
+ }
+ }
+
+ /* compute the number of binary bytes */
+ if (len % 2)
+ {
+ odd = TRUE;
+ len++;
+ }
+ len /= 2;
+
+ /* allocate buffer memory unless provided by caller */
+ if (!buf)
+ {
+ buf = malloc(len);
+ }
+
+ /* buffer is filled from the right */
+ memset(buf, 0, len);
+ hex.ptr += hex.len;
+
+ for (i = len - 1; i >= 0; i--)
+ {
+ /* skip separation characters */
+ if (*(--hex.ptr) == ':')
+ {
+ --hex.ptr;
+ }
+ buf[i] = hex2bin(*hex.ptr);
+ if (i > 0 || !odd)
+ {
+ buf[i] |= hex2bin(*(--hex.ptr)) << 4;
+ }
+ }
+ return chunk_create(buf, len);
+}
+
+/** base 64 conversion digits */
+static char b64digits[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/**
+ * Described in header.
+ */
+chunk_t chunk_to_base64(chunk_t chunk, char *buf)
+{
+ int i, len;
+ char *pos;
+
+ len = chunk.len + ((3 - chunk.len % 3) % 3);
+ if (!buf)
+ {
+ buf = malloc(len * 4 / 3 + 1);
+ }
+ pos = buf;
+ for (i = 0; i < len; i+=3)
+ {
+ *pos++ = b64digits[chunk.ptr[i] >> 2];
+ if (i+1 >= chunk.len)
+ {
+ *pos++ = b64digits[(chunk.ptr[i] & 0x03) << 4];
+ *pos++ = '=';
+ *pos++ = '=';
+ break;
+ }
+ *pos++ = b64digits[((chunk.ptr[i] & 0x03) << 4) | (chunk.ptr[i+1] >> 4)];
+ if (i+2 >= chunk.len)
+ {
+ *pos++ = b64digits[(chunk.ptr[i+1] & 0x0F) << 2];
+ *pos++ = '=';
+ break;
+ }
+ *pos++ = b64digits[((chunk.ptr[i+1] & 0x0F) << 2) | (chunk.ptr[i+2] >> 6)];
+ *pos++ = b64digits[chunk.ptr[i+2] & 0x3F];
+ }
+ *pos = '\0';
+ return chunk_create(buf, len * 4 / 3);
+}
+
+/**
+ * convert a base 64 digit to its binary form (inversion of b64digits array)
+ */
+static int b642bin(char b64)
+{
+ switch (b64)
+ {
+ case 'A' ... 'Z':
+ return b64 - 'A';
+ case 'a' ... 'z':
+ return ('Z' - 'A' + 1) + b64 - 'a';
+ case '0' ... '9':
+ return ('Z' - 'A' + 1) + ('z' - 'a' + 1) + b64 - '0';
+ case '+':
+ case '-':
+ return 62;
+ case '/':
+ case '_':
+ return 63;
+ case '=':
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+/**
+ * Described in header.
+ */
+chunk_t chunk_from_base64(chunk_t base64, char *buf)
+{
+ u_char *pos, byte[4];
+ int i, j, len, outlen;
+
+ len = base64.len / 4 * 3;
+ if (!buf)
+ {
+ buf = malloc(len);
+ }
+ pos = base64.ptr;
+ outlen = 0;
+ for (i = 0; i < len; i+=3)
+ {
+ outlen += 3;
+ for (j = 0; j < 4; j++)
+ {
+ if (*pos == '=')
+ {
+ outlen--;
+ }
+ byte[j] = b642bin(*pos++);
+ }
+ buf[i] = (byte[0] << 2) | (byte[1] >> 4);
+ buf[i+1] = (byte[1] << 4) | (byte[2] >> 2);
+ buf[i+2] = (byte[2] << 6) | (byte[3]);
+ }
+ return chunk_create(buf, outlen);
+}
+
+/** base 32 conversion digits */
+static char b32digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+/**
+ * Described in header.
+ */
+chunk_t chunk_to_base32(chunk_t chunk, char *buf)
+{
+ int i, len;
+ char *pos;
+
+ len = chunk.len + ((5 - chunk.len % 5) % 5);
+ if (!buf)
+ {
+ buf = malloc(len * 8 / 5 + 1);
+ }
+ pos = buf;
+ for (i = 0; i < len; i+=5)
+ {
+ *pos++ = b32digits[chunk.ptr[i] >> 3];
+ if (i+1 >= chunk.len)
+ {
+ *pos++ = b32digits[(chunk.ptr[i] & 0x07) << 2];
+ memset(pos, '=', 6);
+ pos += 6;
+ break;
+ }
+ *pos++ = b32digits[((chunk.ptr[i] & 0x07) << 2) |
+ (chunk.ptr[i+1] >> 6)];
+ *pos++ = b32digits[(chunk.ptr[i+1] & 0x3E) >> 1];
+ if (i+2 >= chunk.len)
+ {
+ *pos++ = b32digits[(chunk.ptr[i+1] & 0x01) << 4];
+ memset(pos, '=', 4);
+ pos += 4;
+ break;
+ }
+ *pos++ = b32digits[((chunk.ptr[i+1] & 0x01) << 4) |
+ (chunk.ptr[i+2] >> 4)];
+ if (i+3 >= chunk.len)
+ {
+ *pos++ = b32digits[(chunk.ptr[i+2] & 0x0F) << 1];
+ memset(pos, '=', 3);
+ pos += 3;
+ break;
+ }
+ *pos++ = b32digits[((chunk.ptr[i+2] & 0x0F) << 1) |
+ (chunk.ptr[i+3] >> 7)];
+ *pos++ = b32digits[(chunk.ptr[i+3] & 0x7F) >> 2];
+ if (i+4 >= chunk.len)
+ {
+ *pos++ = b32digits[(chunk.ptr[i+3] & 0x03) << 3];
+ *pos++ = '=';
+ break;
+ }
+ *pos++ = b32digits[((chunk.ptr[i+3] & 0x03) << 3) |
+ (chunk.ptr[i+4] >> 5)];
+ *pos++ = b32digits[chunk.ptr[i+4] & 0x1F];
+ }
+ *pos = '\0';
+ return chunk_create(buf, len * 8 / 5);
+}
+
+/**
+ * Described in header.
+ */
+int chunk_compare(chunk_t a, chunk_t b)
+{
+ int compare_len = a.len - b.len;
+ int len = (compare_len < 0)? a.len : b.len;
+
+ if (compare_len != 0 || len == 0)
+ {
+ return compare_len;
+ }
+ return memcmp(a.ptr, b.ptr, len);
+};
+
+
+/**
+ * Described in header.
+ */
+bool chunk_increment(chunk_t chunk)
+{
+ int i;
+
+ for (i = chunk.len - 1; i >= 0; i--)
+ {
+ if (++chunk.ptr[i] != 0)
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Remove non-printable characters from a chunk.
+ */
+bool chunk_printable(chunk_t chunk, chunk_t *sane, char replace)
+{
+ bool printable = TRUE;
+ int i;
+
+ if (sane)
+ {
+ *sane = chunk_clone(chunk);
+ }
+ for (i = 0; i < chunk.len; i++)
+ {
+ if (!isprint(chunk.ptr[i]))
+ {
+ if (sane)
+ {
+ sane->ptr[i] = replace;
+ }
+ printable = FALSE;
+ }
+ }
+ return printable;
+}
+
+/**
+ * Helper functions for chunk_mac()
+ */
+static inline u_int64_t sipget(u_char *in)
+{
+ u_int64_t v = 0;
+ int i;
+
+ for (i = 0; i < 64; i += 8, ++in)
+ {
+ v |= ((u_int64_t)*in) << i;
+ }
+ return v;
+}
+
+static inline u_int64_t siprotate(u_int64_t v, int shift)
+{
+ return (v << shift) | (v >> (64 - shift));
+}
+
+static inline void sipround(u_int64_t *v0, u_int64_t *v1, u_int64_t *v2,
+ u_int64_t *v3)
+{
+ *v0 += *v1;
+ *v1 = siprotate(*v1, 13);
+ *v1 ^= *v0;
+ *v0 = siprotate(*v0, 32);
+
+ *v2 += *v3;
+ *v3 = siprotate(*v3, 16);
+ *v3 ^= *v2;
+
+ *v2 += *v1;
+ *v1 = siprotate(*v1, 17);
+ *v1 ^= *v2;
+ *v2 = siprotate(*v2, 32);
+
+ *v0 += *v3;
+ *v3 = siprotate(*v3, 21);
+ *v3 ^= *v0;
+}
+
+static inline void sipcompress(u_int64_t *v0, u_int64_t *v1, u_int64_t *v2,
+ u_int64_t *v3, u_int64_t m)
+{
+ *v3 ^= m;
+ sipround(v0, v1, v2, v3);
+ sipround(v0, v1, v2, v3);
+ *v0 ^= m;
+}
+
+static inline u_int64_t siplast(size_t len, u_char *pos)
+{
+ u_int64_t b;
+ int rem = len & 7;
+
+ b = ((u_int64_t)len) << 56;
+ switch (rem)
+ {
+ case 7:
+ b |= ((u_int64_t)pos[6]) << 48;
+ case 6:
+ b |= ((u_int64_t)pos[5]) << 40;
+ case 5:
+ b |= ((u_int64_t)pos[4]) << 32;
+ case 4:
+ b |= ((u_int64_t)pos[3]) << 24;
+ case 3:
+ b |= ((u_int64_t)pos[2]) << 16;
+ case 2:
+ b |= ((u_int64_t)pos[1]) << 8;
+ case 1:
+ b |= ((u_int64_t)pos[0]);
+ break;
+ case 0:
+ break;
+ }
+ return b;
+}
+
+/**
+ * Caculate SipHash-2-4 with an optional first block given as argument.
+ */
+static u_int64_t chunk_mac_inc(chunk_t chunk, u_char *key, u_int64_t m)
+{
+ u_int64_t v0, v1, v2, v3, k0, k1;
+ size_t len = chunk.len;
+ u_char *pos = chunk.ptr, *end;
+
+ end = chunk.ptr + len - (len % 8);
+
+ k0 = sipget(key);
+ k1 = sipget(key + 8);
+
+ v0 = k0 ^ 0x736f6d6570736575ULL;
+ v1 = k1 ^ 0x646f72616e646f6dULL;
+ v2 = k0 ^ 0x6c7967656e657261ULL;
+ v3 = k1 ^ 0x7465646279746573ULL;
+
+ if (m)
+ {
+ sipcompress(&v0, &v1, &v2, &v3, m);
+ }
+
+ /* compression with c = 2 */
+ for (; pos != end; pos += 8)
+ {
+ m = sipget(pos);
+ sipcompress(&v0, &v1, &v2, &v3, m);
+ }
+ sipcompress(&v0, &v1, &v2, &v3, siplast(len, pos));
+
+ /* finalization with d = 4 */
+ v2 ^= 0xff;
+ sipround(&v0, &v1, &v2, &v3);
+ sipround(&v0, &v1, &v2, &v3);
+ sipround(&v0, &v1, &v2, &v3);
+ sipround(&v0, &v1, &v2, &v3);
+ return v0 ^ v1 ^ v2 ^ v3;
+}
+
+/**
+ * Described in header.
+ */
+u_int64_t chunk_mac(chunk_t chunk, u_char *key)
+{
+ return chunk_mac_inc(chunk, key, 0);
+}
+
+/**
+ * Secret key allocated randomly during first use.
+ */
+static u_char key[16];
+
+/**
+ * Static key used in case predictable hash values are required.
+ */
+static u_char static_key[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
+
+/**
+ * Only allocate the key once
+ */
+static pthread_once_t key_allocated = PTHREAD_ONCE_INIT;
+
+/**
+ * Allocate a key on first use, we do this manually to avoid dependencies on
+ * plugins.
+ */
+static void allocate_key()
+{
+ ssize_t len;
+ size_t done = 0;
+ int fd;
+
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd >= 0)
+ {
+ while (done < sizeof(key))
+ {
+ len = read(fd, key + done, sizeof(key) - done);
+ if (len < 0)
+ {
+ break;
+ }
+ done += len;
+ }
+ close(fd);
+ }
+ /* on error we use random() to generate the key (better than nothing) */
+ if (done < sizeof(key))
+ {
+ srandom(time(NULL) + getpid());
+ for (; done < sizeof(key); done++)
+ {
+ key[done] = (u_char)random();
+ }
+ }
+}
+
+/**
+ * Described in header.
+ */
+u_int32_t chunk_hash_inc(chunk_t chunk, u_int32_t hash)
+{
+ pthread_once(&key_allocated, allocate_key);
+ /* we could use a mac of the previous hash, but this is faster */
+ return chunk_mac_inc(chunk, key, ((u_int64_t)hash) << 32 | hash);
+}
+
+/**
+ * Described in header.
+ */
+u_int32_t chunk_hash(chunk_t chunk)
+{
+ pthread_once(&key_allocated, allocate_key);
+ return chunk_mac(chunk, key);
+}
+
+/**
+ * Described in header.
+ */
+u_int32_t chunk_hash_static_inc(chunk_t chunk, u_int32_t hash)
+{ /* we could use a mac of the previous hash, but this is faster */
+ return chunk_mac_inc(chunk, static_key, ((u_int64_t)hash) << 32 | hash);
+}
+
+/**
+ * Described in header.
+ */
+u_int32_t chunk_hash_static(chunk_t chunk)
+{
+ return chunk_mac(chunk, static_key);
+}
+
+/**
+ * Described in header.
+ */
+int chunk_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec,
+ const void *const *args)
+{
+ chunk_t *chunk = *((chunk_t**)(args[0]));
+ bool first = TRUE;
+ chunk_t copy = *chunk;
+ int written = 0;
+
+ if (!spec->hash)
+ {
+ u_int chunk_len = chunk->len;
+ const void *new_args[] = {&chunk->ptr, &chunk_len};
+ return mem_printf_hook(data, spec, new_args);
+ }
+
+ while (copy.len > 0)
+ {
+ if (first)
+ {
+ first = FALSE;
+ }
+ else
+ {
+ written += print_in_hook(data, ":");
+ }
+ written += print_in_hook(data, "%02x", *copy.ptr++);
+ copy.len--;
+ }
+ return written;
+}