diff options
Diffstat (limited to 'openvpn/buffer.c')
-rw-r--r-- | openvpn/buffer.c | 743 |
1 files changed, 743 insertions, 0 deletions
diff --git a/openvpn/buffer.c b/openvpn/buffer.c new file mode 100644 index 0000000..cfff06e --- /dev/null +++ b/openvpn/buffer.c @@ -0,0 +1,743 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef WIN32 +#include "config-win32.h" +#else +#include "config.h" +#endif + +#include "syshead.h" + +#include "common.h" +#include "buffer.h" +#include "error.h" +#include "mtu.h" +#include "thread.h" + +#include "memdbg.h" + +struct buffer +#ifdef DMALLOC +alloc_buf_debug (size_t size, const char *file, int line) +#else +alloc_buf (size_t size) +#endif +{ +#ifdef DMALLOC + return alloc_buf_gc_debug (size, NULL, file, line); +#else + return alloc_buf_gc (size, NULL); +#endif +} + +struct buffer +#ifdef DMALLOC +alloc_buf_gc_debug (size_t size, struct gc_arena *gc, const char *file, int line) +#else +alloc_buf_gc (size_t size, struct gc_arena *gc) +#endif +{ + struct buffer buf; + buf.capacity = (int)size; + buf.offset = 0; + buf.len = 0; +#ifdef DMALLOC + buf.data = (uint8_t *) gc_malloc_debug (size, false, gc, file, line); +#else + buf.data = (uint8_t *) gc_malloc (size, false, gc); +#endif + if (size) + *buf.data = 0; + return buf; +} + +struct buffer +#ifdef DMALLOC +clone_buf_debug (const struct buffer* buf, const char *file, int line) +#else +clone_buf (const struct buffer* buf) +#endif +{ + struct buffer ret; + ret.capacity = buf->capacity; + ret.offset = buf->offset; + ret.len = buf->len; +#ifdef DMALLOC + ret.data = (uint8_t *) openvpn_dmalloc (file, line, buf->capacity); +#else + ret.data = (uint8_t *) malloc (buf->capacity); +#endif + check_malloc_return (ret.data); + memcpy (BPTR (&ret), BPTR (buf), BLEN (buf)); + return ret; +} + +#ifdef BUF_INIT_TRACKING + +bool +buf_init_debug (struct buffer *buf, int offset, const char *file, int line) +{ + buf->debug_file = file; + buf->debug_line = line; + return buf_init_dowork (buf, offset); +} + +static inline int +buf_debug_line (const struct buffer *buf) +{ + return buf->debug_line; +} + +static const char * +buf_debug_file (const struct buffer *buf) +{ + return buf->debug_file; +} + +#else + +#define buf_debug_line(buf) 0 +#define buf_debug_file(buf) "[UNDEF]" + +#endif + +void +buf_clear (struct buffer *buf) +{ + if (buf->capacity > 0) + memset (buf->data, 0, buf->capacity); + buf->len = 0; + buf->offset = 0; +} + +bool +buf_assign (struct buffer *dest, const struct buffer *src) +{ + if (!buf_init (dest, src->offset)) + return false; + return buf_write (dest, BPTR (src), BLEN (src)); +} + +struct buffer +clear_buf () +{ + struct buffer buf; + CLEAR (buf); + return buf; +} + +void +free_buf (struct buffer *buf) +{ + if (buf->data) + free (buf->data); + CLEAR (*buf); +} + +/* + * Return a buffer for write that is a subset of another buffer + */ +struct buffer +buf_sub (struct buffer *buf, int size, bool prepend) +{ + struct buffer ret; + uint8_t *data; + + CLEAR (ret); + data = prepend ? buf_prepend (buf, size) : buf_write_alloc (buf, size); + if (data) + { + ret.capacity = size; + ret.data = data; + } + return ret; +} + +/* + * printf append to a buffer with overflow check + */ +void +buf_printf (struct buffer *buf, const char *format, ...) +{ + if (buf_defined (buf)) + { + va_list arglist; + uint8_t *ptr = BEND (buf); + int cap = buf_forward_capacity (buf); + + if (cap > 0) + { + va_start (arglist, format); + vsnprintf ((char *)ptr, cap, format, arglist); + va_end (arglist); + *(buf->data + buf->capacity - 1) = 0; /* windows vsnprintf needs this */ + buf->len += (int) strlen ((char *)ptr); + } + } +} + +/* + * This is necessary due to certain buggy implementations of snprintf, + * that don't guarantee null termination for size > 0. + */ + +int openvpn_snprintf(char *str, size_t size, const char *format, ...) +{ + va_list arglist; + int ret = 0; + if (size > 0) + { + va_start (arglist, format); + ret = vsnprintf (str, size, format, arglist); + va_end (arglist); + str[size - 1] = 0; + } + return ret; +} + +/* + * write a string to the end of a buffer that was + * truncated by buf_printf + */ +void +buf_catrunc (struct buffer *buf, const char *str) +{ + if (buf_forward_capacity (buf) <= 1) + { + int len = (int) strlen (str) + 1; + if (len < buf_forward_capacity_total (buf)) + { + strncpynt ((char *)(buf->data + buf->capacity - len), str, len); + } + } +} + +/* + * convert a multi-line output to one line + */ +void +convert_to_one_line (struct buffer *buf) +{ + uint8_t *cp = BPTR(buf); + int len = BLEN(buf); + while (len--) + { + if (*cp == '\n') + *cp = '|'; + ++cp; + } +} + +/* NOTE: requires that string be null terminated */ +void +buf_write_string_file (const struct buffer *buf, const char *filename, int fd) +{ + const int len = strlen ((char *) BPTR (buf)); + const int size = write (fd, BPTR (buf), len); + if (size != len) + msg (M_ERR, "Write error on file '%s'", filename); +} + +/* + * Garbage collection + */ + +void * +#ifdef DMALLOC +gc_malloc_debug (size_t size, bool clear, struct gc_arena *a, const char *file, int line) +#else +gc_malloc (size_t size, bool clear, struct gc_arena *a) +#endif +{ + void *ret; + if (a) + { + struct gc_entry *e; +#ifdef DMALLOC + e = (struct gc_entry *) openvpn_dmalloc (file, line, size + sizeof (struct gc_entry)); +#else + e = (struct gc_entry *) malloc (size + sizeof (struct gc_entry)); +#endif + check_malloc_return (e); + ret = (char *) e + sizeof (struct gc_entry); + /*mutex_lock_static (L_GC_MALLOC);*/ + e->next = a->list; + a->list = e; + /*mutex_unlock_static (L_GC_MALLOC);*/ + } + else + { +#ifdef DMALLOC + ret = openvpn_dmalloc (file, line, size); +#else + ret = malloc (size); +#endif + check_malloc_return (ret); + } +#ifndef ZERO_BUFFER_ON_ALLOC + if (clear) +#endif + memset (ret, 0, size); + return ret; +} + +void +x_gc_free (struct gc_arena *a) +{ + struct gc_entry *e; + /*mutex_lock_static (L_GC_MALLOC);*/ + e = a->list; + a->list = NULL; + /*mutex_unlock_static (L_GC_MALLOC);*/ + + while (e != NULL) + { + struct gc_entry *next = e->next; + free (e); + e = next; + } +} + +/* + * Hex dump -- Output a binary buffer to a hex string and return it. + */ + +char * +format_hex_ex (const uint8_t *data, int size, int maxoutput, + int space_break, const char* separator, + struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc (maxoutput ? maxoutput : + ((size * 2) + (size / space_break) * (int) strlen (separator) + 2), + gc); + int i; + for (i = 0; i < size; ++i) + { + if (separator && i && !(i % space_break)) + buf_printf (&out, "%s", separator); + buf_printf (&out, "%02x", data[i]); + } + buf_catrunc (&out, "[more...]"); + return (char *)out.data; +} + +/* + * remove specific trailing character + */ + +void +buf_rmtail (struct buffer *buf, uint8_t remove) +{ + uint8_t *cp = BLAST(buf); + if (cp && *cp == remove) + { + *cp = '\0'; + --buf->len; + } +} + +/* + * force a null termination even it requires + * truncation of the last char. + */ +void +buf_null_terminate (struct buffer *buf) +{ + char *last = (char *) BLAST (buf); + if (last && *last == '\0') /* already terminated? */ + return; + + if (!buf_safe (buf, 1)) /* make space for trailing null */ + buf_inc_len (buf, -1); + + buf_write_u8 (buf, 0); +} + +/* + * Remove trailing \r and \n chars and ensure + * null termination. + */ +void +buf_chomp (struct buffer *buf) +{ + while (true) + { + char *last = (char *) BLAST (buf); + if (!last) + break; + if (char_class (*last, CC_CRLF|CC_NULL)) + { + if (!buf_inc_len (buf, -1)) + break; + } + else + break; + } + buf_null_terminate (buf); +} + +/* + * like buf_null_terminate, but operate on strings + */ +void +string_null_terminate (char *str, int len, int capacity) +{ + ASSERT (len >= 0 && len <= capacity && capacity > 0); + if (len < capacity) + *(str + len) = '\0'; + else if (len == capacity) + *(str + len - 1) = '\0'; +} + +/* + * Remove trailing \r and \n chars. + */ +void +chomp (char *str) +{ + bool modified; + do { + const int len = strlen (str); + modified = false; + if (len > 0) + { + char *cp = str + (len - 1); + if (*cp == '\n' || *cp == '\r') + { + *cp = '\0'; + modified = true; + } + } + } while (modified); +} + +/* + * Allocate a string + */ +char * +#ifdef DMALLOC +string_alloc_debug (const char *str, struct gc_arena *gc, const char *file, int line) +#else +string_alloc (const char *str, struct gc_arena *gc) +#endif +{ + if (str) + { + const int n = strlen (str) + 1; + char *ret; + +#ifdef DMALLOC + ret = (char *) gc_malloc_debug (n, false, gc, file, line); +#else + ret = (char *) gc_malloc (n, false, gc); +#endif + memcpy (ret, str, n); + return ret; + } + else + return NULL; +} + +/* + * Allocate a string inside a buffer + */ +struct buffer +#ifdef DMALLOC +string_alloc_buf_debug (const char *str, struct gc_arena *gc, const char *file, int line) +#else +string_alloc_buf (const char *str, struct gc_arena *gc) +#endif +{ + struct buffer buf; + + ASSERT (str); + +#ifdef DMALLOC + buf_set_read (&buf, (uint8_t*) string_alloc_debug (str, gc, file, line), strlen (str) + 1); +#else + buf_set_read (&buf, (uint8_t*) string_alloc (str, gc), strlen (str) + 1); +#endif + + if (buf.len > 0) /* Don't count trailing '\0' as part of length */ + --buf.len; + + return buf; +} + +/* + * String comparison + */ + +bool +buf_string_match_head_str (const struct buffer *src, const char *match) +{ + const int size = strlen (match); + if (size < 0 || size > src->len) + return false; + return memcmp (BPTR (src), match, size) == 0; +} + +bool +buf_string_compare_advance (struct buffer *src, const char *match) +{ + if (buf_string_match_head_str (src, match)) + { + buf_advance (src, strlen (match)); + return true; + } + else + return false; +} + +int +buf_substring_len (const struct buffer *buf, int delim) +{ + int i = 0; + struct buffer tmp = *buf; + int c; + + while ((c = buf_read_u8 (&tmp)) >= 0) + { + ++i; + if (c == delim) + return i; + } + return -1; +} + +/* + * String parsing + */ + +bool +buf_parse (struct buffer *buf, const int delim, char *line, const int size) +{ + bool eol = false; + int n = 0; + int c; + + ASSERT (size > 0); + + do + { + c = buf_read_u8 (buf); + if (c < 0) + eol = true; + if (c <= 0 || c == delim) + c = 0; + if (n >= size) + break; + line[n++] = c; + } + while (c); + + line[size-1] = '\0'; + return !(eol && !strlen (line)); +} + +/* + * Classify and mutate strings based on character types. + */ + +bool +char_class (const char c, const unsigned int flags) +{ + if (!flags) + return false; + if (flags & CC_ANY) + return true; + + if ((flags & CC_NULL) && c == '\0') + return true; + + if ((flags & CC_ALNUM) && isalnum (c)) + return true; + if ((flags & CC_ALPHA) && isalpha (c)) + return true; + if ((flags & CC_ASCII) && isascii (c)) + return true; + if ((flags & CC_CNTRL) && iscntrl (c)) + return true; + if ((flags & CC_DIGIT) && isdigit (c)) + return true; + if ((flags & CC_PRINT) && isprint (c)) + return true; + if ((flags & CC_PUNCT) && ispunct (c)) + return true; + if ((flags & CC_SPACE) && isspace (c)) + return true; + if ((flags & CC_XDIGIT) && isxdigit (c)) + return true; + + if ((flags & CC_BLANK) && (c == ' ' || c == '\t')) + return true; + if ((flags & CC_NEWLINE) && c == '\n') + return true; + if ((flags & CC_CR) && c == '\r') + return true; + + if ((flags & CC_BACKSLASH) && c == '\\') + return true; + if ((flags & CC_UNDERBAR) && c == '_') + return true; + if ((flags & CC_DASH) && c == '-') + return true; + if ((flags & CC_DOT) && c == '.') + return true; + if ((flags & CC_COMMA) && c == ',') + return true; + if ((flags & CC_COLON) && c == ':') + return true; + if ((flags & CC_SLASH) && c == '/') + return true; + if ((flags & CC_SINGLE_QUOTE) && c == '\'') + return true; + if ((flags & CC_DOUBLE_QUOTE) && c == '\"') + return true; + if ((flags & CC_REVERSE_QUOTE) && c == '`') + return true; + if ((flags & CC_AT) && c == '@') + return true; + if ((flags & CC_EQUAL) && c == '=') + return true; + + return false; +} + +static inline bool +char_inc_exc (const char c, const unsigned int inclusive, const unsigned int exclusive) +{ + return char_class (c, inclusive) && !char_class (c, exclusive); +} + +bool +string_class (const char *str, const unsigned int inclusive, const unsigned int exclusive) +{ + char c; + ASSERT (str); + while ((c = *str++)) + { + if (!char_inc_exc (c, inclusive, exclusive)) + return false; + } + return true; +} + +/* + * Modify string in place. + * Guaranteed to not increase string length. + */ +bool +string_mod (char *str, const unsigned int inclusive, const unsigned int exclusive, const char replace) +{ + const char *in = str; + bool ret = true; + + ASSERT (str); + + while (true) + { + char c = *in++; + if (c) + { + if (!char_inc_exc (c, inclusive, exclusive)) + { + c = replace; + ret = false; + } + if (c) + *str++ = c; + } + else + { + *str = '\0'; + break; + } + } + return ret; +} + +const char * +string_mod_const (const char *str, + const unsigned int inclusive, + const unsigned int exclusive, + const char replace, + struct gc_arena *gc) +{ + if (str) + { + char *buf = string_alloc (str, gc); + string_mod (buf, inclusive, exclusive, replace); + return buf; + } + else + return NULL; +} + +#ifdef CHARACTER_CLASS_DEBUG + +#define CC_INCLUDE (CC_PRINT) +#define CC_EXCLUDE (0) +#define CC_REPLACE ('.') + +void +character_class_debug (void) +{ + char buf[256]; + + while (fgets (buf, sizeof (buf), stdin) != NULL) + { + string_mod (buf, CC_INCLUDE, CC_EXCLUDE, CC_REPLACE); + printf ("%s", buf); + } +} + +#endif + +#ifdef VERIFY_ALIGNMENT +void +valign4 (const struct buffer *buf, const char *file, const int line) +{ + if (buf && buf->len) + { + int msglevel = D_ALIGN_DEBUG; + const unsigned int u = (unsigned int) BPTR (buf); + + if (u & (PAYLOAD_ALIGN-1)) + msglevel = D_ALIGN_ERRORS; + + msg (msglevel, "%sAlignment at %s/%d ptr=" ptr_format " OLC=%d/%d/%d I=%s/%d", + (msglevel == D_ALIGN_ERRORS) ? "ERROR: " : "", + file, + line, + (ptr_type)buf->data, + buf->offset, + buf->len, + buf->capacity, + buf_debug_file (buf), + buf_debug_line (buf)); + } +} +#endif |