X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=misc.c;h=9aff234b9fdde6c04527c2b79db32ef02e1122e4;hb=510f49e405e71ba5c97875e7a019364e1ef5fac9;hp=e4c6d4ac5e0ab0f3a2e8a8d5a8e42b82c9e5e45e;hpb=86977efa810b9eabbee6f4a35ddc87e7e222f529;p=PuTTY.git diff --git a/misc.c b/misc.c index e4c6d4ac..9aff234b 100644 --- a/misc.c +++ b/misc.c @@ -1,9 +1,283 @@ +/* + * Platform-independent routines shared between all PuTTY programs. + */ + #include #include #include +#include #include #include #include "putty.h" +#include "misc.h" + +/* + * Parse a string block size specification. This is approximately a + * subset of the block size specs supported by GNU fileutils: + * "nk" = n kilobytes + * "nM" = n megabytes + * "nG" = n gigabytes + * All numbers are decimal, and suffixes refer to powers of two. + * Case-insensitive. + */ +unsigned long parse_blocksize(const char *bs) +{ + char *suf; + unsigned long r = strtoul(bs, &suf, 10); + if (*suf != '\0') { + while (*suf && isspace((unsigned char)*suf)) suf++; + switch (*suf) { + case 'k': case 'K': + r *= 1024ul; + break; + case 'm': case 'M': + r *= 1024ul * 1024ul; + break; + case 'g': case 'G': + r *= 1024ul * 1024ul * 1024ul; + break; + case '\0': + default: + break; + } + } + return r; +} + +/* + * Parse a ^C style character specification. + * Returns NULL in `next' if we didn't recognise it as a control character, + * in which case `c' should be ignored. + * The precise current parsing is an oddity inherited from the terminal + * answerback-string parsing code. All sequences start with ^; all except + * ^<123> are two characters. The ones that are worth keeping are probably: + * ^? 127 + * ^@A-Z[\]^_ 0-31 + * a-z 1-26 + * specified by number (decimal, 0octal, 0xHEX) + * ~ ^ escape + */ +char ctrlparse(char *s, char **next) +{ + char c = 0; + if (*s != '^') { + *next = NULL; + } else { + s++; + if (*s == '\0') { + *next = NULL; + } else if (*s == '<') { + s++; + c = (char)strtol(s, next, 0); + if ((*next == s) || (**next != '>')) { + c = 0; + *next = NULL; + } else + (*next)++; + } else if (*s >= 'a' && *s <= 'z') { + c = (*s - ('a' - 1)); + *next = s+1; + } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) { + c = ('@' ^ *s); + *next = s+1; + } else if (*s == '~') { + c = '^'; + *next = s+1; + } + } + return c; +} + +/* + * Find a character in a string, unless it's a colon contained within + * square brackets. Used for untangling strings of the form + * 'host:port', where host can be an IPv6 literal. + * + * We provide several variants of this function, with semantics like + * various standard string.h functions. + */ +static const char *host_strchr_internal(const char *s, const char *set, + int first) +{ + int brackets = 0; + const char *ret = NULL; + + while (1) { + if (!*s) + return ret; + + if (*s == '[') + brackets++; + else if (*s == ']' && brackets > 0) + brackets--; + else if (brackets && *s == ':') + /* never match */ ; + else if (strchr(set, *s)) { + ret = s; + if (first) + return ret; + } + + s++; + } +} +size_t host_strcspn(const char *s, const char *set) +{ + const char *answer = host_strchr_internal(s, set, TRUE); + if (answer) + return answer - s; + else + return strlen(s); +} +char *host_strchr(const char *s, int c) +{ + char set[2]; + set[0] = c; + set[1] = '\0'; + return (char *) host_strchr_internal(s, set, TRUE); +} +char *host_strrchr(const char *s, int c) +{ + char set[2]; + set[0] = c; + set[1] = '\0'; + return (char *) host_strchr_internal(s, set, FALSE); +} + +#ifdef TEST_HOST_STRFOO +int main(void) +{ + int passes = 0, fails = 0; + +#define TEST1(func, string, arg2, suffix, result) do \ + { \ + const char *str = string; \ + unsigned ret = func(string, arg2) suffix; \ + if (ret == result) { \ + passes++; \ + } else { \ + printf("fail: %s(%s,%s)%s = %u, expected %u\n", \ + #func, #string, #arg2, #suffix, ret, result); \ + fails++; \ + } \ +} while (0) + + TEST1(host_strchr, "[1:2:3]:4:5", ':', -str, 7); + TEST1(host_strrchr, "[1:2:3]:4:5", ':', -str, 9); + TEST1(host_strcspn, "[1:2:3]:4:5", "/:",, 7); + TEST1(host_strchr, "[1:2:3]", ':', == NULL, 1); + TEST1(host_strrchr, "[1:2:3]", ':', == NULL, 1); + TEST1(host_strcspn, "[1:2:3]", "/:",, 7); + TEST1(host_strcspn, "[1:2/3]", "/:",, 4); + TEST1(host_strcspn, "[1:2:3]/", "/:",, 7); + + printf("passed %d failed %d total %d\n", passes, fails, passes+fails); + return fails != 0 ? 1 : 0; +} +/* Stubs to stop the rest of this module causing compile failures. */ +void modalfatalbox(const char *fmt, ...) {} +int conf_get_int(Conf *conf, int primary) { return 0; } +char *conf_get_str(Conf *conf, int primary) { return NULL; } +#endif /* TEST_HOST_STRFOO */ + +/* + * Trim square brackets off the outside of an IPv6 address literal. + * Leave all other strings unchanged. Returns a fresh dynamically + * allocated string. + */ +char *host_strduptrim(const char *s) +{ + if (s[0] == '[') { + const char *p = s+1; + int colons = 0; + while (*p && *p != ']') { + if (isxdigit((unsigned char)*p)) + /* OK */; + else if (*p == ':') + colons++; + else + break; + p++; + } + if (*p == ']' && !p[1] && colons > 1) { + /* + * This looks like an IPv6 address literal (hex digits and + * at least two colons, contained in square brackets). + * Trim off the brackets. + */ + return dupprintf("%.*s", (int)(p - (s+1)), s+1); + } + } + + /* + * Any other shape of string is simply duplicated. + */ + return dupstr(s); +} + +prompts_t *new_prompts(void *frontend) +{ + prompts_t *p = snew(prompts_t); + p->prompts = NULL; + p->n_prompts = 0; + p->frontend = frontend; + p->data = NULL; + p->to_server = TRUE; /* to be on the safe side */ + p->name = p->instruction = NULL; + p->name_reqd = p->instr_reqd = FALSE; + return p; +} +void add_prompt(prompts_t *p, char *promptstr, int echo) +{ + prompt_t *pr = snew(prompt_t); + pr->prompt = promptstr; + pr->echo = echo; + pr->result = NULL; + pr->resultsize = 0; + p->n_prompts++; + p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *); + p->prompts[p->n_prompts-1] = pr; +} +void prompt_ensure_result_size(prompt_t *pr, int newlen) +{ + if ((int)pr->resultsize < newlen) { + char *newbuf; + newlen = newlen * 5 / 4 + 512; /* avoid too many small allocs */ + + /* + * We don't use sresize / realloc here, because we will be + * storing sensitive stuff like passwords in here, and we want + * to make sure that the data doesn't get copied around in + * memory without the old copy being destroyed. + */ + newbuf = snewn(newlen, char); + memcpy(newbuf, pr->result, pr->resultsize); + smemclr(pr->result, pr->resultsize); + sfree(pr->result); + pr->result = newbuf; + pr->resultsize = newlen; + } +} +void prompt_set_result(prompt_t *pr, const char *newstr) +{ + prompt_ensure_result_size(pr, strlen(newstr) + 1); + strcpy(pr->result, newstr); +} +void free_prompts(prompts_t *p) +{ + size_t i; + for (i=0; i < p->n_prompts; i++) { + prompt_t *pr = p->prompts[i]; + smemclr(pr->result, pr->resultsize); /* burn the evidence */ + sfree(pr->result); + sfree(pr->prompt); + sfree(pr); + } + sfree(p->prompts); + sfree(p->name); + sfree(p->instruction); + sfree(p); +} /* ---------------------------------------------------------------------- * String handling routines. @@ -11,9 +285,12 @@ char *dupstr(const char *s) { - int len = strlen(s); - char *p = smalloc(len + 1); - strcpy(p, s); + char *p = NULL; + if (s) { + int len = strlen(s); + p = snewn(len + 1, char); + strcpy(p, s); + } return p; } @@ -34,7 +311,7 @@ char *dupcat(const char *s1, ...) } va_end(ap); - p = smalloc(len + 1); + p = snewn(len + 1, char); strcpy(p, s1); q = p + strlen(p); @@ -51,42 +328,113 @@ char *dupcat(const char *s1, ...) return p; } +void burnstr(char *string) /* sfree(str), only clear it first */ +{ + if (string) { + smemclr(string, strlen(string)); + sfree(string); + } +} + +int toint(unsigned u) +{ + /* + * Convert an unsigned to an int, without running into the + * undefined behaviour which happens by the strict C standard if + * the value overflows. You'd hope that sensible compilers would + * do the sensible thing in response to a cast, but actually I + * don't trust modern compilers not to do silly things like + * assuming that _obviously_ you wouldn't have caused an overflow + * and so they can elide an 'if (i < 0)' test immediately after + * the cast. + * + * Sensible compilers ought of course to optimise this entire + * function into 'just return the input value'! + */ + if (u <= (unsigned)INT_MAX) + return (int)u; + else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ + return INT_MIN + (int)(u - (unsigned)INT_MIN); + else + return INT_MIN; /* fallback; should never occur on binary machines */ +} + /* * Do an sprintf(), but into a custom-allocated buffer. * - * Irritatingly, we don't seem to be able to do this portably using - * vsnprintf(), because there appear to be issues with re-using the - * same va_list for two calls, and the excellent C99 va_copy is not - * yet widespread. Bah. Instead I'm going to do a horrid, horrid - * hack, in which I trawl the format string myself, work out the - * maximum length of each format component, and resize the buffer - * before printing it. + * Currently I'm doing this via vsnprintf. This has worked so far, + * but it's not good, because vsnprintf is not available on all + * platforms. There's an ifdef to use `_vsnprintf', which seems + * to be the local name for it on Windows. Other platforms may + * lack it completely, in which case it'll be time to rewrite + * this function in a totally different way. + * + * The only `properly' portable solution I can think of is to + * implement my own format string scanner, which figures out an + * upper bound for the length of each formatting directive, + * allocates the buffer as it goes along, and calls sprintf() to + * actually process each directive. If I ever need to actually do + * this, some caveats: + * + * - It's very hard to find a reliable upper bound for + * floating-point values. %f, in particular, when supplied with + * a number near to the upper or lower limit of representable + * numbers, could easily take several hundred characters. It's + * probably feasible to predict this statically using the + * constants in , or even to predict it dynamically by + * looking at the exponent of the specific float provided, but + * it won't be fun. + * + * - Don't forget to _check_, after calling sprintf, that it's + * used at most the amount of space we had available. + * + * - Fault any formatting directive we don't fully understand. The + * aim here is to _guarantee_ that we never overflow the buffer, + * because this is a security-critical function. If we see a + * directive we don't know about, we should panic and die rather + * than run any risk. */ -char *dupprintf(const char *fmt, ...) -{ - char *ret; - va_list ap; - va_start(ap, fmt); - ret = dupvprintf(fmt, ap); - va_end(ap); - return ret; -} -char *dupvprintf(const char *fmt, va_list ap) +static char *dupvprintf_inner(char *buf, int oldlen, int *oldsize, + const char *fmt, va_list ap) { - char *buf; - int len, size; - - buf = smalloc(512); - size = 512; + int len, size, newsize; + + assert(*oldsize >= oldlen); + size = *oldsize - oldlen; + if (size == 0) { + size = 512; + newsize = oldlen + size; + buf = sresize(buf, newsize, char); + } else { + newsize = *oldsize; + } while (1) { -#ifdef _WINDOWS +#if defined _WINDOWS && !defined __WINE__ && _MSC_VER < 1900 /* 1900 == VS2015 has real snprintf */ #define vsnprintf _vsnprintf #endif - len = vsnprintf(buf, size, fmt, ap); +#ifdef va_copy + /* Use the `va_copy' macro mandated by C99, if present. + * XXX some environments may have this as __va_copy() */ + va_list aq; + va_copy(aq, ap); + len = vsnprintf(buf + oldlen, size, fmt, aq); + va_end(aq); +#else + /* Ugh. No va_copy macro, so do something nasty. + * Technically, you can't reuse a va_list like this: it is left + * unspecified whether advancing a va_list pointer modifies its + * value or something it points to, so on some platforms calling + * vsnprintf twice on the same va_list might fail hideously + * (indeed, it has been observed to). + * XXX the autoconf manual suggests that using memcpy() will give + * "maximum portability". */ + len = vsnprintf(buf + oldlen, size, fmt, ap); +#endif if (len >= 0 && len < size) { /* This is the C99-specified criterion for snprintf to have * been completely successful. */ + *oldsize = newsize; return buf; } else if (len > 0) { /* This is the C99 error condition: the returned length is @@ -97,16 +445,113 @@ char *dupvprintf(const char *fmt, va_list ap) * buffer wasn't big enough, so we enlarge it a bit and hope. */ size += 512; } - buf = srealloc(buf, size); + newsize = oldlen + size; + buf = sresize(buf, newsize, char); } } +char *dupvprintf(const char *fmt, va_list ap) +{ + int size = 0; + return dupvprintf_inner(NULL, 0, &size, fmt, ap); +} +char *dupprintf(const char *fmt, ...) +{ + char *ret; + va_list ap; + va_start(ap, fmt); + ret = dupvprintf(fmt, ap); + va_end(ap); + return ret; +} + +struct strbuf { + char *s; + int len, size; +}; +strbuf *strbuf_new(void) +{ + strbuf *buf = snew(strbuf); + buf->len = 0; + buf->size = 512; + buf->s = snewn(buf->size, char); + *buf->s = '\0'; + return buf; +} +void strbuf_free(strbuf *buf) +{ + sfree(buf->s); + sfree(buf); +} +char *strbuf_str(strbuf *buf) +{ + return buf->s; +} +char *strbuf_to_str(strbuf *buf) +{ + char *ret = buf->s; + sfree(buf); + return ret; +} +void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap) +{ + buf->s = dupvprintf_inner(buf->s, buf->len, &buf->size, fmt, ap); + buf->len += strlen(buf->s + buf->len); +} +void strbuf_catf(strbuf *buf, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + strbuf_catfv(buf, fmt, ap); + va_end(ap); +} + +/* + * Read an entire line of text from a file. Return a buffer + * malloced to be as big as necessary (caller must free). + */ +char *fgetline(FILE *fp) +{ + char *ret = snewn(512, char); + int size = 512, len = 0; + while (fgets(ret + len, size - len, fp)) { + len += strlen(ret + len); + if (len > 0 && ret[len-1] == '\n') + break; /* got a newline, we're done */ + size = len + 512; + ret = sresize(ret, size, char); + } + if (len == 0) { /* first fgets returned NULL */ + sfree(ret); + return NULL; + } + ret[len] = '\0'; + return ret; +} + +/* + * Perl-style 'chomp', for a line we just read with fgetline. Unlike + * Perl chomp, however, we're deliberately forgiving of strange + * line-ending conventions. Also we forgive NULL on input, so you can + * just write 'line = chomp(fgetline(fp));' and not bother checking + * for NULL until afterwards. + */ +char *chomp(char *str) +{ + if (str) { + int len = strlen(str); + while (len > 0 && (str[len-1] == '\r' || str[len-1] == '\n')) + len--; + str[len] = '\0'; + } + return str; +} + /* ---------------------------------------------------------------------- - * Base64 encoding routine. This is required in public-key writing - * but also in HTTP proxy handling, so it's centralised here. + * Core base64 encoding and decoding routines. */ -void base64_encode_atom(unsigned char *data, int n, char *out) +void base64_encode_atom(const unsigned char *data, int n, char *out) { static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -130,6 +575,54 @@ void base64_encode_atom(unsigned char *data, int n, char *out) out[3] = '='; } +int base64_decode_atom(const char *atom, unsigned char *out) +{ + int vals[4]; + int i, v, len; + unsigned word; + char c; + + for (i = 0; i < 4; i++) { + c = atom[i]; + if (c >= 'A' && c <= 'Z') + v = c - 'A'; + else if (c >= 'a' && c <= 'z') + v = c - 'a' + 26; + else if (c >= '0' && c <= '9') + v = c - '0' + 52; + else if (c == '+') + v = 62; + else if (c == '/') + v = 63; + else if (c == '=') + v = -1; + else + return 0; /* invalid atom */ + vals[i] = v; + } + + if (vals[0] == -1 || vals[1] == -1) + return 0; + if (vals[2] == -1 && vals[3] != -1) + return 0; + + if (vals[3] != -1) + len = 3; + else if (vals[2] != -1) + len = 2; + else + len = 1; + + word = ((vals[0] << 18) | + (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F)); + out[0] = (word >> 16) & 0xFF; + if (len > 1) + out[1] = (word >> 8) & 0xFF; + if (len > 2) + out[2] = word & 0xFF; + return len; +} + /* ---------------------------------------------------------------------- * Generic routines to deal with send buffers: a linked list of * smallish blocks, with the operations @@ -143,12 +636,11 @@ void base64_encode_atom(unsigned char *data, int n, char *out) * - return the current size of the buffer chain in bytes */ -#define BUFFER_GRANULE 512 +#define BUFFER_MIN_GRANULE 512 struct bufchain_granule { struct bufchain_granule *next; - int buflen, bufpos; - char buf[BUFFER_GRANULE]; + char *bufpos, *bufend, *bufmax; }; void bufchain_init(bufchain *ch) @@ -178,30 +670,33 @@ void bufchain_add(bufchain *ch, const void *data, int len) { const char *buf = (const char *)data; + if (len == 0) return; + ch->buffersize += len; - if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) { - int copylen = min(len, BUFFER_GRANULE - ch->tail->buflen); - memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen); - buf += copylen; - len -= copylen; - ch->tail->buflen += copylen; - } while (len > 0) { - int grainlen = min(len, BUFFER_GRANULE); - struct bufchain_granule *newbuf; - newbuf = smalloc(sizeof(struct bufchain_granule)); - newbuf->bufpos = 0; - newbuf->buflen = grainlen; - memcpy(newbuf->buf, buf, grainlen); - buf += grainlen; - len -= grainlen; - if (ch->tail) - ch->tail->next = newbuf; - else - ch->head = ch->tail = newbuf; - newbuf->next = NULL; - ch->tail = newbuf; + if (ch->tail && ch->tail->bufend < ch->tail->bufmax) { + int copylen = min(len, ch->tail->bufmax - ch->tail->bufend); + memcpy(ch->tail->bufend, buf, copylen); + buf += copylen; + len -= copylen; + ch->tail->bufend += copylen; + } + if (len > 0) { + int grainlen = + max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE); + struct bufchain_granule *newbuf; + newbuf = smalloc(grainlen); + newbuf->bufpos = newbuf->bufend = + (char *)newbuf + sizeof(struct bufchain_granule); + newbuf->bufmax = (char *)newbuf + grainlen; + newbuf->next = NULL; + if (ch->tail) + ch->tail->next = newbuf; + else + ch->head = newbuf; + ch->tail = newbuf; + } } } @@ -213,13 +708,13 @@ void bufchain_consume(bufchain *ch, int len) while (len > 0) { int remlen = len; assert(ch->head != NULL); - if (remlen >= ch->head->buflen - ch->head->bufpos) { - remlen = ch->head->buflen - ch->head->bufpos; + if (remlen >= ch->head->bufend - ch->head->bufpos) { + remlen = ch->head->bufend - ch->head->bufpos; tmp = ch->head; ch->head = tmp->next; - sfree(tmp); if (!ch->head) ch->tail = NULL; + sfree(tmp); } else ch->head->bufpos += remlen; ch->buffersize -= remlen; @@ -229,8 +724,8 @@ void bufchain_consume(bufchain *ch, int len) void bufchain_prefix(bufchain *ch, void **data, int *len) { - *len = ch->head->buflen - ch->head->bufpos; - *data = ch->head->buf + ch->head->bufpos; + *len = ch->head->bufend - ch->head->bufpos; + *data = ch->head->bufpos; } void bufchain_fetch(bufchain *ch, void *data, int len) @@ -245,9 +740,9 @@ void bufchain_fetch(bufchain *ch, void *data, int len) int remlen = len; assert(tmp != NULL); - if (remlen >= tmp->buflen - tmp->bufpos) - remlen = tmp->buflen - tmp->bufpos; - memcpy(data_c, tmp->buf + tmp->bufpos, remlen); + if (remlen >= tmp->bufend - tmp->bufpos) + remlen = tmp->bufend - tmp->bufpos; + memcpy(data_c, tmp->bufpos, remlen); tmp = tmp->next; len -= remlen; @@ -266,225 +761,10 @@ void bufchain_fetch(bufchain *ch, void *data, int len) */ #ifdef MINEFIELD -/* - * Minefield - a Windows equivalent for Electric Fence - */ - -#define PAGESIZE 4096 - -/* - * Design: - * - * We start by reserving as much virtual address space as Windows - * will sensibly (or not sensibly) let us have. We flag it all as - * invalid memory. - * - * Any allocation attempt is satisfied by committing one or more - * pages, with an uncommitted page on either side. The returned - * memory region is jammed up against the _end_ of the pages. - * - * Freeing anything causes instantaneous decommitment of the pages - * involved, so stale pointers are caught as soon as possible. - */ - -static int minefield_initialised = 0; -static void *minefield_region = NULL; -static long minefield_size = 0; -static long minefield_npages = 0; -static long minefield_curpos = 0; -static unsigned short *minefield_admin = NULL; -static void *minefield_pages = NULL; - -static void minefield_admin_hide(int hide) -{ - int access = hide ? PAGE_NOACCESS : PAGE_READWRITE; - VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL); -} - -static void minefield_init(void) -{ - int size; - int admin_size; - int i; - - for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) { - minefield_region = VirtualAlloc(NULL, size, - MEM_RESERVE, PAGE_NOACCESS); - if (minefield_region) - break; - } - minefield_size = size; - - /* - * Firstly, allocate a section of that to be the admin block. - * We'll need a two-byte field for each page. - */ - minefield_admin = minefield_region; - minefield_npages = minefield_size / PAGESIZE; - admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1); - minefield_npages = (minefield_size - admin_size) / PAGESIZE; - minefield_pages = (char *) minefield_region + admin_size; - - /* - * Commit the admin region. - */ - VirtualAlloc(minefield_admin, minefield_npages * 2, - MEM_COMMIT, PAGE_READWRITE); - - /* - * Mark all pages as unused (0xFFFF). - */ - for (i = 0; i < minefield_npages; i++) - minefield_admin[i] = 0xFFFF; - - /* - * Hide the admin region. - */ - minefield_admin_hide(1); - - minefield_initialised = 1; -} - -static void minefield_bomb(void) -{ - div(1, *(int *) minefield_pages); -} - -static void *minefield_alloc(int size) -{ - int npages; - int pos, lim, region_end, region_start; - int start; - int i; - - npages = (size + PAGESIZE - 1) / PAGESIZE; - - minefield_admin_hide(0); - - /* - * Search from current position until we find a contiguous - * bunch of npages+2 unused pages. - */ - pos = minefield_curpos; - lim = minefield_npages; - while (1) { - /* Skip over used pages. */ - while (pos < lim && minefield_admin[pos] != 0xFFFF) - pos++; - /* Count unused pages. */ - start = pos; - while (pos < lim && pos - start < npages + 2 && - minefield_admin[pos] == 0xFFFF) - pos++; - if (pos - start == npages + 2) - break; - /* If we've reached the limit, reset the limit or stop. */ - if (pos >= lim) { - if (lim == minefield_npages) { - /* go round and start again at zero */ - lim = minefield_curpos; - pos = 0; - } else { - minefield_admin_hide(1); - return NULL; - } - } - } - - minefield_curpos = pos - 1; - - /* - * We have npages+2 unused pages starting at start. We leave - * the first and last of these alone and use the rest. - */ - region_end = (start + npages + 1) * PAGESIZE; - region_start = region_end - size; - /* FIXME: could align here if we wanted */ - - /* - * Update the admin region. - */ - for (i = start + 2; i < start + npages + 1; i++) - minefield_admin[i] = 0xFFFE; /* used but no region starts here */ - minefield_admin[start + 1] = region_start % PAGESIZE; - - minefield_admin_hide(1); - - VirtualAlloc((char *) minefield_pages + region_start, size, - MEM_COMMIT, PAGE_READWRITE); - return (char *) minefield_pages + region_start; -} - -static void minefield_free(void *ptr) -{ - int region_start, i, j; - - minefield_admin_hide(0); - - region_start = (char *) ptr - (char *) minefield_pages; - i = region_start / PAGESIZE; - if (i < 0 || i >= minefield_npages || - minefield_admin[i] != region_start % PAGESIZE) - minefield_bomb(); - for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) { - minefield_admin[j] = 0xFFFF; - } - - VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT); - - minefield_admin_hide(1); -} - -static int minefield_get_size(void *ptr) -{ - int region_start, i, j; - - minefield_admin_hide(0); - - region_start = (char *) ptr - (char *) minefield_pages; - i = region_start / PAGESIZE; - if (i < 0 || i >= minefield_npages || - minefield_admin[i] != region_start % PAGESIZE) - minefield_bomb(); - for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++); - - minefield_admin_hide(1); - - return j * PAGESIZE - region_start; -} - -static void *minefield_c_malloc(size_t size) -{ - if (!minefield_initialised) - minefield_init(); - return minefield_alloc(size); -} - -static void minefield_c_free(void *p) -{ - if (!minefield_initialised) - minefield_init(); - minefield_free(p); -} - -/* - * realloc _always_ moves the chunk, for rapid detection of code - * that assumes it won't. - */ -static void *minefield_c_realloc(void *p, size_t size) -{ - size_t oldsize; - void *q; - if (!minefield_initialised) - minefield_init(); - q = minefield_alloc(size); - oldsize = minefield_get_size(p); - memcpy(q, p, (oldsize < size ? oldsize : size)); - minefield_free(p); - return q; -} - -#endif /* MINEFIELD */ +void *minefield_c_malloc(size_t size); +void minefield_c_free(void *p); +void *minefield_c_realloc(void *p, size_t size); +#endif #ifdef MALLOC_LOG static FILE *fp = NULL; @@ -505,14 +785,22 @@ void mlog(char *file, int line) } #endif -void *safemalloc(size_t size) +void *safemalloc(size_t n, size_t size) { void *p; + + if (n > INT_MAX / size) { + p = NULL; + } else { + size *= n; + if (size == 0) size = 1; #ifdef MINEFIELD - p = minefield_c_malloc(size); + p = minefield_c_malloc(size); #else - p = malloc(size); + p = malloc(size); #endif + } + if (!p) { char str[200]; #ifdef MALLOC_LOG @@ -523,7 +811,7 @@ void *safemalloc(size_t size) #else strcpy(str, "Out of memory!"); #endif - modalfatalbox(str); + modalfatalbox("%s", str); } #ifdef MALLOC_LOG if (fp) @@ -532,22 +820,29 @@ void *safemalloc(size_t size) return p; } -void *saferealloc(void *ptr, size_t size) +void *saferealloc(void *ptr, size_t n, size_t size) { void *p; - if (!ptr) { + + if (n > INT_MAX / size) { + p = NULL; + } else { + size *= n; + if (!ptr) { #ifdef MINEFIELD - p = minefield_c_malloc(size); + p = minefield_c_malloc(size); #else - p = malloc(size); + p = malloc(size); #endif - } else { + } else { #ifdef MINEFIELD - p = minefield_c_realloc(ptr, size); + p = minefield_c_realloc(ptr, size); #else - p = realloc(ptr, size); + p = realloc(ptr, size); #endif + } } + if (!p) { char str[200]; #ifdef MALLOC_LOG @@ -558,7 +853,7 @@ void *saferealloc(void *ptr, size_t size) #else strcpy(str, "Out of memory!"); #endif - modalfatalbox(str); + modalfatalbox("%s", str); } #ifdef MALLOC_LOG if (fp) @@ -591,33 +886,9 @@ void safefree(void *ptr) */ #ifdef DEBUG -static FILE *debug_fp = NULL; -static HANDLE debug_hdl = INVALID_HANDLE_VALUE; -static int debug_got_console = 0; +extern void dputs(const char *); /* defined in per-platform *misc.c */ -static void dputs(char *buf) -{ - DWORD dw; - - if (!debug_got_console) { - if (AllocConsole()) { - debug_got_console = 1; - debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE); - } - } - if (!debug_fp) { - debug_fp = fopen("debug.log", "w"); - } - - if (debug_hdl != INVALID_HANDLE_VALUE) { - WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); - } - fputs(buf, debug_fp); - fflush(debug_fp); -} - - -void dprintf(char *fmt, ...) +void debug_printf(const char *fmt, ...) { char *buf; va_list ap; @@ -630,29 +901,29 @@ void dprintf(char *fmt, ...) } -void debug_memdump(void *buf, int len, int L) +void debug_memdump(const void *buf, int len, int L) { int i; - unsigned char *p = buf; + const unsigned char *p = buf; char foo[17]; if (L) { int delta; - dprintf("\t%d (0x%x) bytes:\n", len, len); - delta = 15 & (int) p; + debug_printf("\t%d (0x%x) bytes:\n", len, len); + delta = 15 & (uintptr_t)p; p -= delta; len += delta; } for (; 0 < len; p += 16, len -= 16) { dputs(" "); if (L) - dprintf("%p: ", p); + debug_printf("%p: ", p); strcpy(foo, "................"); /* sixteen dots */ for (i = 0; i < 16 && i < len; ++i) { if (&p[i] < (unsigned char *) buf) { dputs(" "); /* 3 spaces */ foo[i] = ' '; } else { - dprintf("%c%02.2x", + debug_printf("%c%02.2x", &p[i] != (unsigned char *) buf && i % 4 ? '.' : ' ', p[i] ); @@ -661,8 +932,301 @@ void debug_memdump(void *buf, int len, int L) } } foo[i] = '\0'; - dprintf("%*s%s\n", (16 - i) * 3 + 2, "", foo); + debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo); } } #endif /* def DEBUG */ + +/* + * Determine whether or not a Conf represents a session which can + * sensibly be launched right now. + */ +int conf_launchable(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline)[0] != 0; + else + return conf_get_str(conf, CONF_host)[0] != 0; +} + +char const *conf_dest(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline); + else + return conf_get_str(conf, CONF_host); +} + +#ifndef PLATFORM_HAS_SMEMCLR +/* + * Securely wipe memory. + * + * The actual wiping is no different from what memset would do: the + * point of 'securely' is to try to be sure over-clever compilers + * won't optimise away memsets on variables that are about to be freed + * or go out of scope. See + * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html + * + * Some platforms (e.g. Windows) may provide their own version of this + * function. + */ +void smemclr(void *b, size_t n) { + volatile char *vp; + + if (b && n > 0) { + /* + * Zero out the memory. + */ + memset(b, 0, n); + + /* + * Perform a volatile access to the object, forcing the + * compiler to admit that the previous memset was important. + * + * This while loop should in practice run for zero iterations + * (since we know we just zeroed the object out), but in + * theory (as far as the compiler knows) it might range over + * the whole object. (If we had just written, say, '*vp = + * *vp;', a compiler could in principle have 'helpfully' + * optimised the memset into only zeroing out the first byte. + * This should be robust.) + */ + vp = b; + while (*vp) vp++; + } +} +#endif + +/* + * Validate a manual host key specification (either entered in the + * GUI, or via -hostkey). If valid, we return TRUE, and update 'key' + * to contain a canonicalised version of the key string in 'key' + * (which is guaranteed to take up at most as much space as the + * original version), suitable for putting into the Conf. If not + * valid, we return FALSE. + */ +int validate_manual_hostkey(char *key) +{ + char *p, *q, *r, *s; + + /* + * Step through the string word by word, looking for a word that's + * in one of the formats we like. + */ + p = key; + while ((p += strspn(p, " \t"))[0]) { + q = p; + p += strcspn(p, " \t"); + if (*p) *p++ = '\0'; + + /* + * Now q is our word. + */ + + if (strlen(q) == 16*3 - 1 && + q[strspn(q, "0123456789abcdefABCDEF:")] == 0) { + /* + * Might be a key fingerprint. Check the colons are in the + * right places, and if so, return the same fingerprint + * canonicalised into lowercase. + */ + int i; + for (i = 0; i < 16; i++) + if (q[3*i] == ':' || q[3*i+1] == ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 15; i++) + if (q[3*i+2] != ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 16*3 - 1; i++) + key[i] = tolower(q[i]); + key[16*3 - 1] = '\0'; + return TRUE; + } + not_fingerprint:; + + /* + * Before we check for a public-key blob, trim newlines out of + * the middle of the word, in case someone's managed to paste + * in a public-key blob _with_ them. + */ + for (r = s = q; *r; r++) + if (*r != '\n' && *r != '\r') + *s++ = *r; + *s = '\0'; + + if (strlen(q) % 4 == 0 && strlen(q) > 2*4 && + q[strspn(q, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz+/=")] == 0) { + /* + * Might be a base64-encoded SSH-2 public key blob. Check + * that it starts with a sensible algorithm string. No + * canonicalisation is necessary for this string type. + * + * The algorithm string must be at most 64 characters long + * (RFC 4251 section 6). + */ + unsigned char decoded[6]; + unsigned alglen; + int minlen; + int len = 0; + + len += base64_decode_atom(q, decoded+len); + if (len < 3) + goto not_ssh2_blob; /* sorry */ + len += base64_decode_atom(q+4, decoded+len); + if (len < 4) + goto not_ssh2_blob; /* sorry */ + + alglen = GET_32BIT_MSB_FIRST(decoded); + if (alglen > 64) + goto not_ssh2_blob; /* sorry */ + + minlen = ((alglen + 4) + 2) / 3; + if (strlen(q) < minlen) + goto not_ssh2_blob; /* sorry */ + + strcpy(key, q); + return TRUE; + } + not_ssh2_blob:; + } + + return FALSE; +} + +int smemeq(const void *av, const void *bv, size_t len) +{ + const unsigned char *a = (const unsigned char *)av; + const unsigned char *b = (const unsigned char *)bv; + unsigned val = 0; + + while (len-- > 0) { + val |= *a++ ^ *b++; + } + /* Now val is 0 iff we want to return 1, and in the range + * 0x01..0xFF iff we want to return 0. So subtracting from 0x100 + * will clear bit 8 iff we want to return 0, and leave it set iff + * we want to return 1, so then we can just shift down. */ + return (0x100 - val) >> 8; +} + +int match_ssh_id(int stringlen, const void *string, const char *id) +{ + int idlen = strlen(id); + return (idlen == stringlen && !memcmp(string, id, idlen)); +} + +void *get_ssh_string(int *datalen, const void **data, int *stringlen) +{ + void *ret; + unsigned int len; + + if (*datalen < 4) + return NULL; + len = GET_32BIT_MSB_FIRST((const unsigned char *)*data); + if (*datalen - 4 < len) + return NULL; + ret = (void *)((const char *)*data + 4); + *datalen -= len + 4; + *data = (const char *)*data + len + 4; + *stringlen = len; + return ret; +} + +int get_ssh_uint32(int *datalen, const void **data, unsigned *ret) +{ + if (*datalen < 4) + return FALSE; + *ret = GET_32BIT_MSB_FIRST((const unsigned char *)*data); + *datalen -= 4; + *data = (const char *)*data + 4; + return TRUE; +} + +int strstartswith(const char *s, const char *t) +{ + return !memcmp(s, t, strlen(t)); +} + +int strendswith(const char *s, const char *t) +{ + size_t slen = strlen(s), tlen = strlen(t); + return slen >= tlen && !strcmp(s + (slen - tlen), t); +} + +char *buildinfo(const char *newline) +{ + strbuf *buf = strbuf_new(); + extern const char commitid[]; /* in commitid.c */ + + strbuf_catf(buf, "Build platform: %d-bit %s", + (int)(CHAR_BIT * sizeof(void *)), + BUILDINFO_PLATFORM); + +#ifdef __clang_version__ + strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); +#elif defined __GNUC__ && defined __VERSION__ + strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); +#elif defined _MSC_VER + strbuf_catf(buf, "%sCompiler: Visual Studio", newline); +#if _MSC_VER == 1900 + strbuf_catf(buf, " 2015 / MSVC++ 14.0"); +#elif _MSC_VER == 1800 + strbuf_catf(buf, " 2013 / MSVC++ 12.0"); +#elif _MSC_VER == 1700 + strbuf_catf(buf, " 2012 / MSVC++ 11.0"); +#elif _MSC_VER == 1600 + strbuf_catf(buf, " 2010 / MSVC++ 10.0"); +#elif _MSC_VER == 1500 + strbuf_catf(buf, " 2008 / MSVC++ 9.0"); +#elif _MSC_VER == 1400 + strbuf_catf(buf, " 2005 / MSVC++ 8.0"); +#elif _MSC_VER == 1310 + strbuf_catf(buf, " 2003 / MSVC++ 7.1"); +#else + strbuf_catf(buf, ", unrecognised version"); +#endif + strbuf_catf(buf, " (_MSC_VER=%d)", (int)_MSC_VER); +#endif + +#ifdef BUILDINFO_GTK + { + char *gtk_buildinfo = buildinfo_gtk_version(); + if (gtk_buildinfo) { + strbuf_catf(buf, "%sCompiled against GTK version %s", + newline, gtk_buildinfo); + sfree(gtk_buildinfo); + } + } +#endif + +#ifdef NO_SECURITY + strbuf_catf(buf, "%sBuild option: NO_SECURITY", newline); +#endif +#ifdef NO_SECUREZEROMEMORY + strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline); +#endif +#ifdef NO_IPV6 + strbuf_catf(buf, "%sBuild option: NO_IPV6", newline); +#endif +#ifdef NO_GSSAPI + strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline); +#endif +#ifdef STATIC_GSSAPI + strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline); +#endif +#ifdef UNPROTECT + strbuf_catf(buf, "%sBuild option: UNPROTECT", newline); +#endif +#ifdef FUZZING + strbuf_catf(buf, "%sBuild option: FUZZING", newline); +#endif +#ifdef DEBUG + strbuf_catf(buf, "%sBuild option: DEBUG", newline); +#endif + + strbuf_catf(buf, "%sSource commit: %s", newline, commitid); + + return strbuf_to_str(buf); +}