/* * 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-2008 Telethra, Inc. * * 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 */ #include "syshead.h" #include "common.h" #include "buffer.h" #include "error.h" #include "mtu.h" #include "thread.h" #include "memdbg.h" size_t array_mult_safe (const size_t m1, const size_t m2) { const unsigned long long limit = 0xFFFFFFFF; unsigned long long res = (unsigned long long)m1 * (unsigned long long)m2; if (unlikely(m1 > limit) || unlikely(m2 > limit) || unlikely(res > limit)) msg (M_FATAL, "attemped allocation of excessively large array"); return (size_t) res; } 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 */ bool buf_printf (struct buffer *buf, const char *format, ...) { int ret = false; if (buf_defined (buf)) { va_list arglist; uint8_t *ptr = BEND (buf); int cap = buf_forward_capacity (buf); if (cap > 0) { int stat; va_start (arglist, format); stat = 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); if (stat >= 0 && stat < cap) ret = true; } } return ret; } /* * 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; } /* * A printf-like function (that only recognizes a subset of standard printf * format operators) that prints arguments to an argv list instead * of a standard string. This is used to build up argv arrays for passing * to execve. */ void argv_init (struct argv *a) { a->argc = 0; a->argv = NULL; } struct argv argv_new (void) { struct argv ret; argv_init (&ret); return ret; } void argv_reset (struct argv *a) { size_t i; for (i = 0; i < a->argc; ++i) free (a->argv[i]); free (a->argv); a->argc = 0; a->argv = NULL; } size_t argv_argc (const char *format) { char *term; const char *f = format; size_t argc = 0; while ((term = argv_term (&f)) != NULL) { ++argc; free (term); } return argc; } struct argv argv_insert_head (const struct argv *a, const char *head) { struct argv r; size_t i; r.argc = (a ? a->argc : 0) + 1; ALLOC_ARRAY_CLEAR (r.argv, char *, r.argc + 1); r.argv[0] = string_alloc (head, NULL); if (a) { for (i = 0; i < a->argc; ++i) r.argv[i+1] = string_alloc (a->argv[i], NULL); } return r; } char * argv_term (const char **f) { const char *p = *f; const char *term = NULL; size_t termlen = 0; if (*p == '\0') return NULL; while (true) { const int c = *p; if (c == '\0') break; if (term) { if (!isspace (c)) ++termlen; else break; } else { if (!isspace (c)) { term = p; termlen = 1; } } ++p; } *f = p; if (term) { char *ret; ASSERT (termlen > 0); ret = malloc (termlen + 1); check_malloc_return (ret); memcpy (ret, term, termlen); ret[termlen] = '\0'; return ret; } else return NULL; } const char * argv_str (const struct argv *a, struct gc_arena *gc, const unsigned int flags) { if (a->argv) return print_argv ((const char **)a->argv, gc, flags); else return ""; } void argv_msg (const int msglev, const struct argv *a) { struct gc_arena gc = gc_new (); msg (msglev, "%s", argv_str (a, &gc, 0)); gc_free (&gc); } void argv_msg_prefix (const int msglev, const struct argv *a, const char *prefix) { struct gc_arena gc = gc_new (); msg (msglev, "%s: %s", prefix, argv_str (a, &gc, 0)); gc_free (&gc); } void argv_printf (struct argv *a, const char *format, ...) { va_list arglist; va_start (arglist, format); argv_printf_arglist (a, format, 0, arglist); va_end (arglist); } void argv_printf_cat (struct argv *a, const char *format, ...) { va_list arglist; va_start (arglist, format); argv_printf_arglist (a, format, APA_CAT, arglist); va_end (arglist); } void argv_printf_arglist (struct argv *a, const char *format, const unsigned int flags, va_list arglist) { char *term; const char *f = format; size_t argc = 0; if (flags & APA_CAT) { char **old_argv = a->argv; size_t i; argc = a->argc; a->argc += argv_argc (format); ALLOC_ARRAY_CLEAR (a->argv, char *, a->argc + 1); for (i = 0; i < argc; ++i) a->argv[i] = old_argv[i]; free (old_argv); } else { argv_reset (a); a->argc = argv_argc (format); ALLOC_ARRAY_CLEAR (a->argv, char *, a->argc + 1); } while ((term = argv_term (&f)) != NULL) { ASSERT (argc < a->argc); if (term[0] == '%') { if (!strcmp (term, "%s")) { char *s = va_arg (arglist, char *); if (!s) s = ""; a->argv[argc++] = string_alloc (s, NULL); } else if (!strcmp (term, "%d")) { char numstr[64]; openvpn_snprintf (numstr, sizeof (numstr), "%d", va_arg (arglist, int)); a->argv[argc++] = string_alloc (numstr, NULL); } else if (!strcmp (term, "%u")) { char numstr[64]; openvpn_snprintf (numstr, sizeof (numstr), "%u", va_arg (arglist, unsigned int)); a->argv[argc++] = string_alloc (numstr, NULL); } else if (!strcmp (term, "%s/%d")) { char numstr[64]; char *s = va_arg (arglist, char *); if (!s) s = ""; openvpn_snprintf (numstr, sizeof (numstr), "%d", va_arg (arglist, int)); { const size_t len = strlen(s) + strlen(numstr) + 2; char *combined = (char *) malloc (len); check_malloc_return (combined); strcpy (combined, s); strcat (combined, "/"); strcat (combined, numstr); a->argv[argc++] = combined; } } else if (!strcmp (term, "%s%s")) { char *s1 = va_arg (arglist, char *); char *s2 = va_arg (arglist, char *); char *combined; if (!s1) s1 = ""; if (!s2) s2 = ""; combined = (char *) malloc (strlen(s1) + strlen(s2) + 1); check_malloc_return (combined); strcpy (combined, s1); strcat (combined, s2); a->argv[argc++] = combined; } else ASSERT (0); free (term); } else { a->argv[argc++] = term; } } ASSERT (argc == a->argc); } /* * 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; } } /* * Transfer src arena to dest, resetting src to an empty arena. */ void gc_transfer (struct gc_arena *dest, struct gc_arena *src) { if (dest && src) { struct gc_entry *e = src->list; if (e) { while (e->next != NULL) e = e->next; e->next = dest->list; dest->list = src->list; src->list = NULL; } } } /* * 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); } const char * skip_leading_whitespace (const char *str) { while (*str) { const char c = *str; if (!(c == ' ' || c == '\t')) break; ++str; } return str; } /* * 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) { rm_trailing_chars (str, "\r\n"); } /* * Remove trailing chars */ void rm_trailing_chars (char *str, const char *what_to_delete) { bool modified; do { const int len = strlen (str); modified = false; if (len > 0) { char *cp = str + (len - 1); if (strchr (what_to_delete, *cp) != NULL) { *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; } /* * Erase all characters in a string */ void string_clear (char *str) { if (str) { const int len = strlen (str); if (len > 0) memset (str, 0, len); } } /* * Return the length of a string array */ int string_array_len (const char **array) { int i = 0; if (array) { while (array[i]) ++i; } return i; } char * print_argv (const char **p, struct gc_arena *gc, const unsigned int flags) { struct buffer out = alloc_buf_gc (256, gc); int i = 0; for (;;) { const char *cp = *p++; if (!cp) break; if (i) buf_printf (&out, " "); if (flags & PA_BRACKET) buf_printf (&out, "[%s]", cp); else buf_printf (&out, "%s", cp); ++i; } return BSTR (&out); } /* * 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)); } /* * Print a string which might be NULL */ const char * np (const char *str) { if (str) return str; else return "[NULL]"; } /* * Classify and mutate strings based on character types. */ bool char_class (const unsigned 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; } void string_replace_leading (char *str, const char match, const char replace) { ASSERT (match != '\0'); while (*str) { if (*str == match) *str = replace; else break; ++str; } } #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 /* * struct buffer_list */ #ifdef ENABLE_BUFFER_LIST struct buffer_list * buffer_list_new (const int max_size) { struct buffer_list *ret; ALLOC_OBJ_CLEAR (ret, struct buffer_list); ret->max_size = max_size; ret->size = 0; return ret; } void buffer_list_free (struct buffer_list *ol) { buffer_list_reset (ol); free (ol); } bool buffer_list_defined (const struct buffer_list *ol) { return ol->head != NULL; } void buffer_list_reset (struct buffer_list *ol) { struct buffer_entry *e = ol->head; while (e) { struct buffer_entry *next = e->next; free_buf (&e->buf); free (e); e = next; } ol->head = ol->tail = NULL; ol->size = 0; } void buffer_list_push (struct buffer_list *ol, const unsigned char *str) { if (!ol->max_size || ol->size < ol->max_size) { struct buffer_entry *e; ALLOC_OBJ_CLEAR (e, struct buffer_entry); ++ol->size; if (ol->tail) { ASSERT (ol->head); ol->tail->next = e; } else { ASSERT (!ol->head); ol->head = e; } e->buf = string_alloc_buf ((const char *) str, NULL); ol->tail = e; } } const struct buffer * buffer_list_peek (struct buffer_list *ol) { if (ol->head) return &ol->head->buf; else return NULL; } static void buffer_list_pop (struct buffer_list *ol) { if (ol->head) { struct buffer_entry *e = ol->head->next; free_buf (&ol->head->buf); free (ol->head); ol->head = e; --ol->size; if (!e) ol->tail = NULL; } } void buffer_list_advance (struct buffer_list *ol, int n) { if (ol->head) { struct buffer *buf = &ol->head->buf; ASSERT (buf_advance (buf, n)); if (!BLEN (buf)) buffer_list_pop (ol); } } struct buffer_list * buffer_list_file (const char *fn, int max_line_len) { FILE *fp = fopen (fn, "r"); struct buffer_list *bl = NULL; if (fp) { char *line = (char *) malloc (max_line_len); if (line) { bl = buffer_list_new (0); while (fgets (line, max_line_len, fp) != NULL) buffer_list_push (bl, (unsigned char *)line); free (line); } fclose (fp); } return bl; } #endif