From 3396c97da9d658570a4b80bd3221cefdc0b96dbf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 17 Apr 2021 15:22:20 +0100 Subject: [PATCH] New library-style 'utils' subdirectories. Now that the new CMake build system is encouraging us to lay out the code like a set of libraries, it seems like a good idea to make them look more _like_ libraries, by putting things into separate modules as far as possible. This fixes several previous annoyances in which you had to link against some object in order to get a function you needed, but that object also contained other functions you didn't need which included link-time symbol references you didn't want to have to deal with. The usual offender was subsidiary supporting programs including misc.c for some innocuous function and then finding they had to deal with the requirements of buildinfo(). This big reorganisation introduces three new subdirectories called 'utils', one at the top level and one in each platform subdir. In each case, the directory contains basically the same files that were previously placed in the 'utils' build-time library, except that the ones that were extremely miscellaneous (misc.c, utils.c, uxmisc.c, winmisc.c, winmiscs.c, winutils.c) have been split up into much smaller pieces. --- CMakeLists.txt | 50 +- misc.c | 419 ------ unix/CMakeLists.txt | 25 +- unix/{uxutils.c => utils/arm_arch_queries.c} | 17 +- unix/{uxutils.h => utils/arm_arch_queries.h} | 8 +- unix/utils/block_signal.c | 21 + unix/utils/cloexec.c | 49 + unix/utils/dputs.c | 24 + unix/utils/filename.c | 72 ++ unix/utils/fontspec.c | 35 + unix/utils/get_username.c | 52 + unix/utils/getticks.c | 33 + unix/{xkeysym.c => utils/keysym_to_unicode.c} | 2 +- unix/utils/make_dir_and_check_ours.c | 60 + unix/utils/make_dir_path.c | 39 + unix/utils/nonblock.c | 55 + unix/utils/open_for_write_would_lose_data.c | 44 + unix/utils/pgp_fingerprints.c | 23 + unix/{uxpoll.c => utils/pollwrap.c} | 21 + unix/utils/signal.c | 30 + unix/{x11misc.c => utils/x11_ignore_error.c} | 21 +- unix/uxmisc.c | 371 ------ unix/uxsignal.c | 47 - utils.c | 1122 ----------------- utils/base64_decode_atom.c | 54 + utils/base64_encode_atom.c | 30 + utils/bufchain.c | 173 +++ utils/buildinfo.c | 158 +++ utils/burnstr.c | 15 + utils/chomp.c | 26 + conf.c => utils/conf.c | 0 utils/conf_dest.c | 15 + utils/conf_launchable.c | 14 + utils/ctrlparse.c | 49 + utils/debug.c | 56 + utils/dupcat.c | 48 + utils/dupprintf.c | 100 ++ utils/dupstr.c | 19 + utils/encode_utf8.c | 29 + utils/fgetline.c | 25 + utils/host_strchr.c | 18 + utils/host_strchr_internal.c | 80 ++ utils/host_strcspn.c | 19 + utils/host_strduptrim.c | 51 + utils/host_strrchr.c | 18 + time.c => utils/ltime.c | 0 marshal.c => utils/marshal.c | 0 memory.c => utils/memory.c | 0 utils/memxor.c | 34 + miscucs.c => utils/miscucs.c | 0 utils/null_lp.c | 8 + utils/nullseat.c | 42 + utils/nullstrcmp.c | 21 + utils/out_of_memory.c | 11 + utils/parse_blocksize.c | 40 + utils/prompts.c | 59 + utils/ptrlen.c | 95 ++ utils/read_file_into.c | 19 + utils/seat_connection_fatal.c | 20 + sessprep.c => utils/sessprep.c | 0 utils/sk_free_peer_info.c | 14 + utils/smemclr.c | 42 + utils/smemeq.c | 25 + utils/ssh2_pick_fingerprint.c | 27 + sshutils.c => utils/sshutils.c | 0 utils/strbuf.c | 118 ++ utils/string_length_for_printf.c | 21 + stripctrl.c => utils/stripctrl.c | 0 tree234.c => utils/tree234.c | 0 utils/utils.h | 12 + utils/validate_manual_hostkey.c | 116 ++ version.c => utils/version.c | 0 wcwidth.c => utils/wcwidth.c | 0 wildcard.c => utils/wildcard.c | 0 utils/write_c_string_literal.c | 31 + windows/CMakeLists.txt | 34 +- windows/utils/arm_arch_queries.c | 39 + windows/{wincapi.c => utils/capi.c} | 2 +- windows/{windefs.c => utils/defaults.c} | 2 +- windows/utils/dll_hijacking_protection.c | 43 + windows/utils/dputs.c | 37 + windows/utils/escape_registry_key.c | 48 + windows/utils/filename.c | 54 + windows/utils/fontspec.c | 43 + windows/utils/get_username.c | 77 ++ windows/utils/getdlgitemtext_alloc.c | 20 + windows/utils/is_console_handle.c | 13 + windows/utils/load_system32_dll.c | 26 + windows/{wintime.c => utils/ltime.c} | 3 +- windows/utils/makedlgitemborderless.c | 19 + windows/utils/message_box.c | 49 + windows/{winmiscs.c => utils/minefield.c} | 76 +- .../utils/open_for_write_would_lose_data.c | 38 + windows/utils/pgp_fingerprints_msgbox.c | 25 + windows/utils/platform_get_x_display.c | 12 + windows/utils/registry_get_string.c | 43 + windows/utils/request_file.c | 71 ++ windows/{winsecur.c => utils/security.c} | 2 +- .../{winutils.c => utils/split_into_argv.c} | 197 +-- windows/utils/strtoumax.c | 12 + windows/utils/version.c | 40 + windows/utils/win_strerror.c | 72 ++ windows/winmisc.c | 467 ------- windows/winstuff.h | 6 + 104 files changed, 3251 insertions(+), 2711 deletions(-) delete mode 100644 misc.c rename unix/{uxutils.c => utils/arm_arch_queries.c} (74%) rename unix/{uxutils.h => utils/arm_arch_queries.h} (91%) create mode 100644 unix/utils/block_signal.c create mode 100644 unix/utils/cloexec.c create mode 100644 unix/utils/dputs.c create mode 100644 unix/utils/filename.c create mode 100644 unix/utils/fontspec.c create mode 100644 unix/utils/get_username.c create mode 100644 unix/utils/getticks.c rename unix/{xkeysym.c => utils/keysym_to_unicode.c} (99%) create mode 100644 unix/utils/make_dir_and_check_ours.c create mode 100644 unix/utils/make_dir_path.c create mode 100644 unix/utils/nonblock.c create mode 100644 unix/utils/open_for_write_would_lose_data.c create mode 100644 unix/utils/pgp_fingerprints.c rename unix/{uxpoll.c => utils/pollwrap.c} (81%) create mode 100644 unix/utils/signal.c rename unix/{x11misc.c => utils/x11_ignore_error.c} (84%) delete mode 100644 unix/uxmisc.c delete mode 100644 unix/uxsignal.c delete mode 100644 utils.c create mode 100644 utils/base64_decode_atom.c create mode 100644 utils/base64_encode_atom.c create mode 100644 utils/bufchain.c create mode 100644 utils/buildinfo.c create mode 100644 utils/burnstr.c create mode 100644 utils/chomp.c rename conf.c => utils/conf.c (100%) create mode 100644 utils/conf_dest.c create mode 100644 utils/conf_launchable.c create mode 100644 utils/ctrlparse.c create mode 100644 utils/debug.c create mode 100644 utils/dupcat.c create mode 100644 utils/dupprintf.c create mode 100644 utils/dupstr.c create mode 100644 utils/encode_utf8.c create mode 100644 utils/fgetline.c create mode 100644 utils/host_strchr.c create mode 100644 utils/host_strchr_internal.c create mode 100644 utils/host_strcspn.c create mode 100644 utils/host_strduptrim.c create mode 100644 utils/host_strrchr.c rename time.c => utils/ltime.c (100%) rename marshal.c => utils/marshal.c (100%) rename memory.c => utils/memory.c (100%) create mode 100644 utils/memxor.c rename miscucs.c => utils/miscucs.c (100%) create mode 100644 utils/null_lp.c create mode 100644 utils/nullseat.c create mode 100644 utils/nullstrcmp.c create mode 100644 utils/out_of_memory.c create mode 100644 utils/parse_blocksize.c create mode 100644 utils/prompts.c create mode 100644 utils/ptrlen.c create mode 100644 utils/read_file_into.c create mode 100644 utils/seat_connection_fatal.c rename sessprep.c => utils/sessprep.c (100%) create mode 100644 utils/sk_free_peer_info.c create mode 100644 utils/smemclr.c create mode 100644 utils/smemeq.c create mode 100644 utils/ssh2_pick_fingerprint.c rename sshutils.c => utils/sshutils.c (100%) create mode 100644 utils/strbuf.c create mode 100644 utils/string_length_for_printf.c rename stripctrl.c => utils/stripctrl.c (100%) rename tree234.c => utils/tree234.c (100%) create mode 100644 utils/utils.h create mode 100644 utils/validate_manual_hostkey.c rename version.c => utils/version.c (100%) rename wcwidth.c => utils/wcwidth.c (100%) rename wildcard.c => utils/wildcard.c (100%) create mode 100644 utils/write_c_string_literal.c create mode 100644 windows/utils/arm_arch_queries.c rename windows/{wincapi.c => utils/capi.c} (97%) rename windows/{windefs.c => utils/defaults.c} (90%) create mode 100644 windows/utils/dll_hijacking_protection.c create mode 100644 windows/utils/dputs.c create mode 100644 windows/utils/escape_registry_key.c create mode 100644 windows/utils/filename.c create mode 100644 windows/utils/fontspec.c create mode 100644 windows/utils/get_username.c create mode 100644 windows/utils/getdlgitemtext_alloc.c create mode 100644 windows/utils/is_console_handle.c create mode 100644 windows/utils/load_system32_dll.c rename windows/{wintime.c => utils/ltime.c} (84%) create mode 100644 windows/utils/makedlgitemborderless.c create mode 100644 windows/utils/message_box.c rename windows/{winmiscs.c => utils/minefield.c} (78%) create mode 100644 windows/utils/open_for_write_would_lose_data.c create mode 100644 windows/utils/pgp_fingerprints_msgbox.c create mode 100644 windows/utils/platform_get_x_display.c create mode 100644 windows/utils/registry_get_string.c create mode 100644 windows/utils/request_file.c rename windows/{winsecur.c => utils/security.c} (99%) rename windows/{winutils.c => utils/split_into_argv.c} (77%) create mode 100644 windows/utils/strtoumax.c create mode 100644 windows/utils/version.c create mode 100644 windows/utils/win_strerror.c delete mode 100644 windows/winmisc.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c467760..b1050f2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,54 @@ project(putty LANGUAGES C) include(cmake/setup.cmake) add_library(utils STATIC - memory.c marshal.c utils.c conf.c sshutils.c tree234.c version.c - wildcard.c wcwidth.c misc.c miscucs.c stripctrl.c sessprep.c + utils/base64_decode_atom.c + utils/base64_encode_atom.c + utils/bufchain.c + utils/buildinfo.c + utils/burnstr.c + utils/chomp.c + utils/conf.c + utils/conf_dest.c + utils/conf_launchable.c + utils/ctrlparse.c + utils/debug.c + utils/dupcat.c + utils/dupprintf.c + utils/dupstr.c + utils/encode_utf8.c + utils/fgetline.c + utils/host_strchr.c + utils/host_strchr_internal.c + utils/host_strcspn.c + utils/host_strduptrim.c + utils/host_strrchr.c + utils/marshal.c + utils/memory.c + utils/memxor.c + utils/miscucs.c + utils/null_lp.c + utils/nullseat.c + utils/nullstrcmp.c + utils/out_of_memory.c + utils/parse_blocksize.c + utils/prompts.c + utils/ptrlen.c + utils/read_file_into.c + utils/seat_connection_fatal.c + utils/sessprep.c + utils/sk_free_peer_info.c + utils/smemeq.c + utils/ssh2_pick_fingerprint.c + utils/sshutils.c + utils/strbuf.c + utils/string_length_for_printf.c + utils/stripctrl.c + utils/tree234.c + utils/validate_manual_hostkey.c + utils/version.c + utils/wcwidth.c + utils/wildcard.c + utils/write_c_string_literal.c ${GENERATED_COMMIT_C}) add_library(logging OBJECT diff --git a/misc.c b/misc.c deleted file mode 100644 index f1bb176f..00000000 --- a/misc.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Platform-independent routines shared between all PuTTY programs. - * - * This file contains functions that use the kind of infrastructure - * like conf.c that tends to only live in the main applications, or - * that do things that only something like a main PuTTY application - * would need. So standalone test programs should generally be able to - * avoid linking against it. - * - * More standalone functions that depend on nothing but the C library - * live in utils.c. - */ - -#include -#include -#include -#include -#include -#include - -#include "defs.h" -#include "putty.h" -#include "misc.h" - -#define BASE64_CHARS_NOEQ \ - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" -#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "=" - -void seat_connection_fatal(Seat *seat, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = dupvprintf(fmt, ap); - va_end(ap); - - seat->vt->connection_fatal(seat, msg); - sfree(msg); /* if we return */ -} - -prompts_t *new_prompts(void) -{ - prompts_t *p = snew(prompts_t); - p->prompts = NULL; - p->n_prompts = p->prompts_size = 0; - 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, bool echo) -{ - prompt_t *pr = snew(prompt_t); - pr->prompt = promptstr; - pr->echo = echo; - pr->result = strbuf_new_nm(); - sgrowarray(p->prompts, p->prompts_size, p->n_prompts); - p->prompts[p->n_prompts++] = pr; -} -void prompt_set_result(prompt_t *pr, const char *newstr) -{ - strbuf_clear(pr->result); - put_datapl(pr->result, ptrlen_from_asciz(newstr)); -} -const char *prompt_get_result_ref(prompt_t *pr) -{ - return pr->result->s; -} -char *prompt_get_result(prompt_t *pr) -{ - return dupstr(pr->result->s); -} -void free_prompts(prompts_t *p) -{ - size_t i; - for (i=0; i < p->n_prompts; i++) { - prompt_t *pr = p->prompts[i]; - strbuf_free(pr->result); - sfree(pr->prompt); - sfree(pr); - } - sfree(p->prompts); - sfree(p->name); - sfree(p->instruction); - sfree(p); -} - -/* - * Determine whether or not a Conf represents a session which can - * sensibly be launched right now. - */ -bool 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); -} - -/* - * 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. - */ -bool 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 (strstartswith(q, "SHA256:")) { - /* Test for a valid SHA256 key fingerprint. */ - r = q + 7; - if (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 0) - return true; - } - - r = q; - if (strstartswith(r, "MD5:")) - r += 4; - if (strlen(r) == 16*3 - 1 && - r[strspn(r, "0123456789abcdefABCDEF:")] == 0) { - /* - * Test for a valid MD5 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 (r[3*i] == ':' || r[3*i+1] == ':') - goto not_fingerprint; /* sorry */ - for (i = 0; i < 15; i++) - if (r[3*i+2] != ':') - goto not_fingerprint; /* sorry */ - for (i = 0; i < 16*3 - 1; i++) - key[i] = tolower(r[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, BASE64_CHARS_ALL)] == 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; -} - -char *buildinfo(const char *newline) -{ - strbuf *buf = strbuf_new(); - - strbuf_catf(buf, "Build platform: %d-bit %s", - (int)(CHAR_BIT * sizeof(void *)), - BUILDINFO_PLATFORM); - -#ifdef __clang_version__ -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); -#elif defined __GNUC__ && defined __VERSION__ -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); -#endif - -#if defined _MSC_VER -#ifndef FOUND_COMPILER -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: ", newline); -#else - strbuf_catf(buf, ", emulating "); -#endif - strbuf_catf(buf, "Visual Studio"); - -#if 0 - /* - * List of _MSC_VER values and their translations taken from - * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros - * - * The pointless #if 0 branch containing this comment is there so - * that every real clause can start with #elif and there's no - * anomalous first clause. That way the patch looks nicer when you - * add extra ones. - */ -#elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500 - /* - * 16.9 and 16.8 have the same _MSC_VER value, and have to be - * distinguished by _MSC_FULL_VER. As of 2021-03-04 that is not - * mentioned on the above page, but see e.g. - * https://developercommunity.visualstudio.com/t/the-169-cc-compiler-still-uses-the-same-version-nu/1335194#T-N1337120 - * which says that 16.9 builds will have versions starting at - * 19.28.29500.* and going up. Hence, 19 28 29500 is what we - * compare _MSC_FULL_VER against above. - */ - strbuf_catf(buf, " 2019 (16.9)"); -#elif _MSC_VER == 1928 - strbuf_catf(buf, " 2019 (16.8)"); -#elif _MSC_VER == 1927 - strbuf_catf(buf, " 2019 (16.7)"); -#elif _MSC_VER == 1926 - strbuf_catf(buf, " 2019 (16.6)"); -#elif _MSC_VER == 1925 - strbuf_catf(buf, " 2019 (16.5)"); -#elif _MSC_VER == 1924 - strbuf_catf(buf, " 2019 (16.4)"); -#elif _MSC_VER == 1923 - strbuf_catf(buf, " 2019 (16.3)"); -#elif _MSC_VER == 1922 - strbuf_catf(buf, " 2019 (16.2)"); -#elif _MSC_VER == 1921 - strbuf_catf(buf, " 2019 (16.1)"); -#elif _MSC_VER == 1920 - strbuf_catf(buf, " 2019 (16.0)"); -#elif _MSC_VER == 1916 - strbuf_catf(buf, " 2017 version 15.9"); -#elif _MSC_VER == 1915 - strbuf_catf(buf, " 2017 version 15.8"); -#elif _MSC_VER == 1914 - strbuf_catf(buf, " 2017 version 15.7"); -#elif _MSC_VER == 1913 - strbuf_catf(buf, " 2017 version 15.6"); -#elif _MSC_VER == 1912 - strbuf_catf(buf, " 2017 version 15.5"); -#elif _MSC_VER == 1911 - strbuf_catf(buf, " 2017 version 15.3"); -#elif _MSC_VER == 1910 - strbuf_catf(buf, " 2017 RTW (15.0)"); -#elif _MSC_VER == 1900 - strbuf_catf(buf, " 2015 (14.0)"); -#elif _MSC_VER == 1800 - strbuf_catf(buf, " 2013 (12.0)"); -#elif _MSC_VER == 1700 - strbuf_catf(buf, " 2012 (11.0)"); -#elif _MSC_VER == 1600 - strbuf_catf(buf, " 2010 (10.0)"); -#elif _MSC_VER == 1500 - strbuf_catf(buf, " 2008 (9.0)"); -#elif _MSC_VER == 1400 - strbuf_catf(buf, " 2005 (8.0)"); -#elif _MSC_VER == 1310 - strbuf_catf(buf, " .NET 2003 (7.1)"); -#elif _MSC_VER == 1300 - strbuf_catf(buf, " .NET 2002 (7.0)"); -#elif _MSC_VER == 1200 - strbuf_catf(buf, " 6.0"); -#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 -#if defined _WINDOWS - { - int echm = has_embedded_chm(); - if (echm >= 0) - strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, - echm ? "yes" : "no"); - } -#endif - -#if defined _WINDOWS && defined MINEFIELD - strbuf_catf(buf, "%sBuild option: MINEFIELD", 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); -} - -size_t nullseat_output( - Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } -bool nullseat_eof(Seat *seat) { return true; } -int nullseat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) { return 0; } -void nullseat_notify_remote_exit(Seat *seat) {} -void nullseat_connection_fatal(Seat *seat, const char *message) {} -void nullseat_update_specials_menu(Seat *seat) {} -char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } -void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} -int nullseat_verify_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -int nullseat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -int nullseat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -bool nullseat_is_never_utf8(Seat *seat) { return false; } -bool nullseat_is_always_utf8(Seat *seat) { return true; } -void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} -const char *nullseat_get_x_display(Seat *seat) { return NULL; } -bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } -bool nullseat_get_window_pixel_size( - Seat *seat, int *width, int *height) { return false; } -StripCtrlChars *nullseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} -bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } -bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } -bool nullseat_verbose_no(Seat *seat) { return false; } -bool nullseat_verbose_yes(Seat *seat) { return true; } -bool nullseat_interactive_no(Seat *seat) { return false; } -bool nullseat_interactive_yes(Seat *seat) { return true; } -bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } - -bool null_lp_verbose_no(LogPolicy *lp) { return false; } -bool null_lp_verbose_yes(LogPolicy *lp) { return true; } - -void sk_free_peer_info(SocketPeerInfo *pi) -{ - if (pi) { - sfree((char *)pi->addr_text); - sfree((char *)pi->log_text); - sfree(pi); - } -} - -void out_of_memory(void) -{ - modalfatalbox("Out of memory"); -} diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 2932d853..72bf507c 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -1,8 +1,29 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_platform_sources_to_library(utils - uxutils.c uxsignal.c uxpoll.c xkeysym.c uxmisc.c xpmpucfg.c - xpmputty.c xpmptcfg.c xpmpterm.c x11misc.c ../time.c) + utils/arm_arch_queries.c + utils/block_signal.c + utils/cloexec.c + utils/dputs.c + utils/filename.c + utils/fontspec.c + utils/getticks.c + utils/get_username.c + utils/keysym_to_unicode.c + utils/make_dir_and_check_ours.c + utils/make_dir_path.c + utils/nonblock.c + utils/open_for_write_would_lose_data.c + utils/pgp_fingerprints.c + utils/pollwrap.c + utils/signal.c + utils/x11_ignore_error.c + ../utils/smemclr.c + # Compiled icon pixmap files + xpmpucfg.c xpmputty.c xpmptcfg.c xpmpterm.c + # We want the ISO C implementation of ltime(), because we don't have + # a local better alternative + ../utils/ltime.c) add_platform_sources_to_library(eventloop uxcliloop.c uxsel.c) add_platform_sources_to_library(console diff --git a/unix/uxutils.c b/unix/utils/arm_arch_queries.c similarity index 74% rename from unix/uxutils.c rename to unix/utils/arm_arch_queries.c index 3a04c1be..7c0957fa 100644 --- a/unix/uxutils.c +++ b/unix/utils/arm_arch_queries.c @@ -1,7 +1,12 @@ +/* + * Unix implementation of the OS query functions that detect Arm + * architecture extensions. + */ + #include "putty.h" #include "ssh.h" -#include "uxutils.h" +#include "utils/arm_arch_queries.h" #if defined __arm__ || defined __aarch64__ @@ -62,4 +67,14 @@ bool platform_sha512_hw_available(void) #endif } +#else /* defined __arm__ || defined __aarch64__ */ + +/* + * Include _something_ in this file to prevent an annoying compiler + * warning, and to avoid having to condition out this file in + * CMakeLists. It's in a library, so this variable shouldn't end up in + * any actual program, because nothing will refer to it. + */ +const int arm_arch_queries_dummy_variable = 0; + #endif /* defined __arm__ || defined __aarch64__ */ diff --git a/unix/uxutils.h b/unix/utils/arm_arch_queries.h similarity index 91% rename from unix/uxutils.h rename to unix/utils/arm_arch_queries.h index fcb7e9b7..bd055687 100644 --- a/unix/uxutils.h +++ b/unix/utils/arm_arch_queries.h @@ -1,5 +1,5 @@ /* - * uxutils.h: header included only by uxutils.c. + * Header included only by arm_arch_queries.c. * * The only reason this is a header file instead of a source file is * so that I can define 'static inline' functions which may or may not @@ -7,8 +7,8 @@ * to use them in the subsequent source file. */ -#ifndef PUTTY_UXUTILS_H -#define PUTTY_UXUTILS_H +#ifndef PUTTY_ARM_ARCH_QUERIES_H +#define PUTTY_ARM_ARCH_QUERIES_H #if defined __APPLE__ #if HAVE_SYS_SYSCTL_H @@ -62,4 +62,4 @@ static inline bool test_sysctl_flag(const char *flagname) } #endif /* defined __APPLE__ */ -#endif /* PUTTY_UXUTILS_H */ +#endif /* PUTTY_ARM_ARCH_QUERIES_H */ diff --git a/unix/utils/block_signal.c b/unix/utils/block_signal.c new file mode 100644 index 00000000..918e63a4 --- /dev/null +++ b/unix/utils/block_signal.c @@ -0,0 +1,21 @@ +/* + * Handy function to block or unblock a signal, which does all the + * messing about with sigset_t for you. + */ + +#include +#include + +#include "defs.h" + +void block_signal(int sig, bool block_it) +{ + sigset_t ss; + + sigemptyset(&ss); + sigaddset(&ss, sig); + if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) { + perror("sigprocmask"); + exit(1); + } +} diff --git a/unix/utils/cloexec.c b/unix/utils/cloexec.c new file mode 100644 index 00000000..2fc22289 --- /dev/null +++ b/unix/utils/cloexec.c @@ -0,0 +1,49 @@ +/* + * Set and clear the FD_CLOEXEC fcntl option on a file descriptor. + * + * We don't realistically expect these operations to fail (the most + * plausible error condition is EBADF, but we always believe ourselves + * to be passing a valid fd so even that's an assertion-fail sort of + * response), so we don't make any effort to return sensible error + * codes to the caller - we just log to standard error and die + * unceremoniously. + */ + +#include +#include +#include +#include + +#include + +#include "putty.h" + +void cloexec(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); + exit(1); + } +} + +void noncloexec(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); + exit(1); + } +} diff --git a/unix/utils/dputs.c b/unix/utils/dputs.c new file mode 100644 index 00000000..97a275e0 --- /dev/null +++ b/unix/utils/dputs.c @@ -0,0 +1,24 @@ +/* + * Implementation of dputs() for Unix. + * + * The debug messages are written to standard output, and also into a + * file called debug.log. + */ + +#include + +#include "putty.h" + +static FILE *debug_fp = NULL; + +void dputs(const char *buf) +{ + if (!debug_fp) { + debug_fp = fopen("debug.log", "w"); + } + + if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */ + + fputs(buf, debug_fp); + fflush(debug_fp); +} diff --git a/unix/utils/filename.c b/unix/utils/filename.c new file mode 100644 index 00000000..208483d2 --- /dev/null +++ b/unix/utils/filename.c @@ -0,0 +1,72 @@ +/* + * Implementation of Filename for Unix, including f_open(). + */ + +#include +#include + +#include "putty.h" + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); + return ret; +} + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +bool filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +bool filename_is_null(const Filename *fn) +{ + return !fn->path[0]; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +void filename_serialise(BinarySink *bs, const Filename *f) +{ + put_asciz(bs, f->path); +} +Filename *filename_deserialise(BinarySource *src) +{ + return filename_from_str(get_asciz(src)); +} + +char filename_char_sanitise(char c) +{ + if (c == '/') + return '.'; + return c; +} + +FILE *f_open(const Filename *filename, char const *mode, bool is_private) +{ + if (!is_private) { + return fopen(filename->path, mode); + } else { + int fd; + assert(mode[0] == 'w'); /* is_private is meaningless for read, + and tricky for append */ + fd = open(filename->path, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) + return NULL; + return fdopen(fd, mode); + } +} diff --git a/unix/utils/fontspec.c b/unix/utils/fontspec.c new file mode 100644 index 00000000..7c5a0a2f --- /dev/null +++ b/unix/utils/fontspec.c @@ -0,0 +1,35 @@ +/* + * Implementation of FontSpec for Unix. This is more or less + * degenerate - on this platform a font specification is just a + * string. + */ + +#include "putty.h" + +FontSpec *fontspec_new(const char *name) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + return f; +} + +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name); +} + +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} + +void fontspec_serialise(BinarySink *bs, FontSpec *f) +{ + put_asciz(bs, f->name); +} + +FontSpec *fontspec_deserialise(BinarySource *src) +{ + return fontspec_new(get_asciz(src)); +} diff --git a/unix/utils/get_username.c b/unix/utils/get_username.c new file mode 100644 index 00000000..61e259c0 --- /dev/null +++ b/unix/utils/get_username.c @@ -0,0 +1,52 @@ +/* + * Implementation of get_username() for Unix. + */ + +#include +#include + +#include "putty.h" + +char *get_username(void) +{ + struct passwd *p; + uid_t uid = getuid(); + char *user, *ret = NULL; + + /* + * First, find who we think we are using getlogin. If this + * agrees with our uid, we'll go along with it. This should + * allow sharing of uids between several login names whilst + * coping correctly with people who have su'ed. + */ + user = getlogin(); +#if HAVE_SETPWENT + setpwent(); +#endif + if (user) + p = getpwnam(user); + else + p = NULL; + if (p && p->pw_uid == uid) { + /* + * The result of getlogin() really does correspond to + * our uid. Fine. + */ + ret = user; + } else { + /* + * If that didn't work, for whatever reason, we'll do + * the simpler version: look up our uid in the password + * file and map it straight to a name. + */ + p = getpwuid(uid); + if (!p) + return NULL; + ret = p->pw_name; + } +#if HAVE_ENDPWENT + endpwent(); +#endif + + return dupstr(ret); +} diff --git a/unix/utils/getticks.c b/unix/utils/getticks.c new file mode 100644 index 00000000..9d169816 --- /dev/null +++ b/unix/utils/getticks.c @@ -0,0 +1,33 @@ +/* + * Implement getticks() for Unix. + */ + +#include +#include + +#include "putty.h" + +unsigned long getticks(void) +{ + /* + * We want to use milliseconds rather than the microseconds or + * nanoseconds given by the underlying clock functions, because we + * need a decent number of them to fit into a 32-bit word so it + * can be used for keepalives. + */ +#if HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC + { + /* Use CLOCK_MONOTONIC if available, so as to be unconfused if + * the system clock changes. */ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return ts.tv_sec * TICKSPERSEC + + ts.tv_nsec / (1000000000 / TICKSPERSEC); + } +#endif + { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC); + } +} diff --git a/unix/xkeysym.c b/unix/utils/keysym_to_unicode.c similarity index 99% rename from unix/xkeysym.c rename to unix/utils/keysym_to_unicode.c index aa9f9539..37e419fe 100644 --- a/unix/xkeysym.c +++ b/unix/utils/keysym_to_unicode.c @@ -1,5 +1,5 @@ /* - * xkeysym.c: mapping from X keysyms to Unicode values + * Map X keysyms to Unicode values. * * The basic idea of this is shamelessly cribbed from xterm. The * actual character data is generated from Markus Kuhn's proposed diff --git a/unix/utils/make_dir_and_check_ours.c b/unix/utils/make_dir_and_check_ours.c new file mode 100644 index 00000000..cab4dc20 --- /dev/null +++ b/unix/utils/make_dir_and_check_ours.c @@ -0,0 +1,60 @@ +/* + * Create a directory accessible only to us, and then check afterwards + * that we really did end up with a directory with the right ownership + * and permissions. + * + * The idea is that this is a directory in which we're about to create + * something sensitive, like a listening Unix-domain socket for SSH + * connection sharing or an SSH agent. We want to be protected against + * somebody else previously having created the directory in a way + * that's writable to us, and thus manipulating us into creating the + * actual socket in a directory they can see so that they can connect + * to it and (say) use our authenticated SSH sessions. + * + * NOTE: The strategy used in this function is not safe if the enemy + * has unrestricted write access to the containing directory. In that + * case, they could move our directory out of the way and make a new + * one, after this function returns and before we create our socket + * (or whatever) inside it. + * + * But this should be OK for temp directories (which modify the + * default world-write behaviour by also setting the 't' bit, + * preventing people from renaming or deleting things in there that + * they don't own). And of course it's also safe if the directory is + * writable only by our _own_ uid. + */ + +#include +#include + +#include +#include +#include + +#include "putty.h" + +char *make_dir_and_check_ours(const char *dirname) +{ + struct stat st; + + /* + * Create the directory. We might have created it before, so + * EEXIST is an OK error; but anything else is doom. + */ + if (mkdir(dirname, 0700) < 0 && errno != EEXIST) + return dupprintf("%s: mkdir: %s", dirname, strerror(errno)); + + /* + * Stat the directory and check its ownership and permissions. + */ + if (stat(dirname, &st) < 0) + return dupprintf("%s: stat: %s", dirname, strerror(errno)); + if (st.st_uid != getuid()) + return dupprintf("%s: directory owned by uid %d, not by us", + dirname, st.st_uid); + if ((st.st_mode & 077) != 0) + return dupprintf("%s: directory has overgenerous permissions %03o" + " (expected 700)", dirname, st.st_mode & 0777); + + return NULL; +} diff --git a/unix/utils/make_dir_path.c b/unix/utils/make_dir_path.c new file mode 100644 index 00000000..4d212fe4 --- /dev/null +++ b/unix/utils/make_dir_path.c @@ -0,0 +1,39 @@ +/* + * Make a path of subdirectories, tolerating EEXIST at every step. + */ + +#include +#include + +#include +#include +#include + +#include "putty.h" + +char *make_dir_path(const char *path, mode_t mode) +{ + int pos = 0; + char *prefix; + + while (1) { + pos += strcspn(path + pos, "/"); + + if (pos > 0) { + prefix = dupprintf("%.*s", pos, path); + + if (mkdir(prefix, mode) < 0 && errno != EEXIST) { + char *ret = dupprintf("%s: mkdir: %s", + prefix, strerror(errno)); + sfree(prefix); + return ret; + } + + sfree(prefix); + } + + if (!path[pos]) + return NULL; + pos += strspn(path + pos, "/"); + } +} diff --git a/unix/utils/nonblock.c b/unix/utils/nonblock.c new file mode 100644 index 00000000..cece206c --- /dev/null +++ b/unix/utils/nonblock.c @@ -0,0 +1,55 @@ +/* + * Set and clear the O_NONBLOCK fcntl option on an open file. + * + * We don't realistically expect these operations to fail (the most + * plausible error condition is EBADF, but we always believe ourselves + * to be passing a valid fd so even that's an assertion-fail sort of + * response), so we don't make any effort to return sensible error + * codes to the caller - we just log to standard error and die + * unceremoniously. + * + * Returns the previous state of O_NONBLOCK. + */ + +#include +#include +#include +#include + +#include + +#include "putty.h" + +bool nonblock(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFL); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); + exit(1); + } + + return fdflags & O_NONBLOCK; +} + +bool no_nonblock(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFL); + if (fdflags < 0) { + fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); + exit(1); + } + if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) { + fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); + exit(1); + } + + return fdflags & O_NONBLOCK; +} diff --git a/unix/utils/open_for_write_would_lose_data.c b/unix/utils/open_for_write_would_lose_data.c new file mode 100644 index 00000000..46e695fd --- /dev/null +++ b/unix/utils/open_for_write_would_lose_data.c @@ -0,0 +1,44 @@ +/* + * Unix implementation of open_for_write_would_lose_data(). + */ + +#include +#include + +#include "putty.h" + +bool open_for_write_would_lose_data(const Filename *fn) +{ + struct stat st; + + if (stat(fn->path, &st) < 0) { + /* + * If the file doesn't even exist, we obviously want to return + * false. If we failed to stat it for any other reason, + * ignoring the precise error code and returning false still + * doesn't seem too unreasonable, because then we'll try to + * open the file for writing and report _that_ error, which is + * likely to be more to the point. + */ + return false; + } + + /* + * OK, something exists at this pathname and we've found out + * something about it. But an open-for-write will only + * destructively truncate it if it's a regular file with nonzero + * size. If it's empty, or some other kind of special thing like a + * character device (e.g. /dev/tty) or a named pipe, then opening + * it for write is already non-destructive and it's pointless and + * annoying to warn about it just because the same file can be + * opened for reading. (Indeed, if it's a named pipe, opening it + * for reading actually _causes inconvenience_ in its own right, + * even before the question of whether it gives misleading + * information.) + */ + if (S_ISREG(st.st_mode) && st.st_size > 0) { + return true; + } + + return false; +} diff --git a/unix/utils/pgp_fingerprints.c b/unix/utils/pgp_fingerprints.c new file mode 100644 index 00000000..badedd71 --- /dev/null +++ b/unix/utils/pgp_fingerprints.c @@ -0,0 +1,23 @@ +/* + * Display the fingerprints of the PGP Master Keys to the user. + * + * (This is in its own file rather than in uxcons.c, because it's + * appropriate even for Unix GUI apps.) + */ + +#include "putty.h" + +void pgp_fingerprints(void) +{ + fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n" + "be used to establish a trust path from this executable to another\n" + "one. See the manual for more information.\n" + "(Note: these fingerprints have nothing to do with SSH!)\n" + "\n" + "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR + ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" + " " PGP_PREV_MASTER_KEY_FP "\n", stdout); +} diff --git a/unix/uxpoll.c b/unix/utils/pollwrap.c similarity index 81% rename from unix/uxpoll.c rename to unix/utils/pollwrap.c index 474926bb..51121e36 100644 --- a/unix/uxpoll.c +++ b/unix/utils/pollwrap.c @@ -1,3 +1,24 @@ +/* + * Wrapper system around poll() that lets me treat it more or less + * like select(), but avoiding the inherent limitation of select() + * that it can't handle the full range of fds that are capable of + * existing. + * + * The pollwrapper structure contains the 'struct pollfd' array passed + * to poll() itself, and also a tree234 that maps each fd to its + * location in the list, which makes it convenient to add or remove + * individual fds from the system or change what events you're + * watching for on them. So the API is _shaped_ basically like select, + * even if none of the details are identical: from outside this + * module, a pollwrapper can be used wherever you'd otherwise have had + * an fd_set. + * + * Also, this module translate between the simple select r/w/x + * classification and the richer poll flags. We have to stick to r/w/x + * in this code base, because it ports to other systems where that's + * all you get. + */ + /* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */ #define _XOPEN_SOURCE diff --git a/unix/utils/signal.c b/unix/utils/signal.c new file mode 100644 index 00000000..a56fe6be --- /dev/null +++ b/unix/utils/signal.c @@ -0,0 +1,30 @@ +/* + * PuTTY's wrapper on signal(2). + * + * Calling signal() is non-portable, as it varies in meaning between + * platforms and depending on feature macros, and has stupid semantics + * at least some of the time. + * + * This function provides the same interface as the libc function, but + * provides consistent semantics. It assumes POSIX semantics for + * sigaction() (so you might need to do some more work if you port to + * something ancient like SunOS 4). + */ + +#include + +#include "defs.h" + +void (*putty_signal(int sig, void (*func)(int)))(int) +{ + struct sigaction sa; + struct sigaction old; + + sa.sa_handler = func; + if(sigemptyset(&sa.sa_mask) < 0) + return SIG_ERR; + sa.sa_flags = SA_RESTART; + if(sigaction(sig, &sa, &old) < 0) + return SIG_ERR; + return old.sa_handler; +} diff --git a/unix/x11misc.c b/unix/utils/x11_ignore_error.c similarity index 84% rename from unix/x11misc.c rename to unix/utils/x11_ignore_error.c index e1fd1906..a4165ab5 100644 --- a/unix/x11misc.c +++ b/unix/utils/x11_ignore_error.c @@ -1,5 +1,7 @@ /* - * x11misc.c: miscellaneous stuff for dealing directly with X servers. + * Error handling mechanism which permits us to ignore specific X11 + * errors from particular requests. We maintain a list of upcoming + * potential error events that we want to not treat as fatal errors. */ #include @@ -18,12 +20,6 @@ #include "x11misc.h" -/* ---------------------------------------------------------------------- - * Error handling mechanism which permits us to ignore specific X11 - * errors from particular requests. We maintain a list of upcoming - * potential error events that we want to not treat as fatal errors. - */ - static int (*orig_x11_error_handler)(Display *thisdisp, XErrorEvent *err); struct x11_err_to_ignore { @@ -79,5 +75,14 @@ void x11_ignore_error(Display *disp, unsigned char errcode) nerrs++; } -#endif +#else /* NOT_X_WINDOWS */ +/* + * Include _something_ in this file to prevent an annoying compiler + * warning, and to avoid having to condition out this file in + * CMakeLists. It's in a library, so this variable shouldn't end up in + * any actual program, because nothing will refer to it. + */ +const int x11_ignore_error_dummy_variable = 0; + +#endif /* NOT_X_WINDOWS */ diff --git a/unix/uxmisc.c b/unix/uxmisc.c deleted file mode 100644 index 876725bd..00000000 --- a/unix/uxmisc.c +++ /dev/null @@ -1,371 +0,0 @@ -/* - * PuTTY miscellaneous Unix stuff - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" - -unsigned long getticks(void) -{ - /* - * We want to use milliseconds rather than the microseconds or - * nanoseconds given by the underlying clock functions, because we - * need a decent number of them to fit into a 32-bit word so it - * can be used for keepalives. - */ -#if HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC - { - /* Use CLOCK_MONOTONIC if available, so as to be unconfused if - * the system clock changes. */ - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - return ts.tv_sec * TICKSPERSEC + - ts.tv_nsec / (1000000000 / TICKSPERSEC); - } -#endif - { - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC); - } -} - -Filename *filename_from_str(const char *str) -{ - Filename *ret = snew(Filename); - ret->path = dupstr(str); - return ret; -} - -Filename *filename_copy(const Filename *fn) -{ - return filename_from_str(fn->path); -} - -const char *filename_to_str(const Filename *fn) -{ - return fn->path; -} - -bool filename_equal(const Filename *f1, const Filename *f2) -{ - return !strcmp(f1->path, f2->path); -} - -bool filename_is_null(const Filename *fn) -{ - return !fn->path[0]; -} - -void filename_free(Filename *fn) -{ - sfree(fn->path); - sfree(fn); -} - -void filename_serialise(BinarySink *bs, const Filename *f) -{ - put_asciz(bs, f->path); -} -Filename *filename_deserialise(BinarySource *src) -{ - return filename_from_str(get_asciz(src)); -} - -char filename_char_sanitise(char c) -{ - if (c == '/') - return '.'; - return c; -} - -#ifdef DEBUG -static FILE *debug_fp = NULL; - -void dputs(const char *buf) -{ - if (!debug_fp) { - debug_fp = fopen("debug.log", "w"); - } - - if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */ - - fputs(buf, debug_fp); - fflush(debug_fp); -} -#endif - -char *get_username(void) -{ - struct passwd *p; - uid_t uid = getuid(); - char *user, *ret = NULL; - - /* - * First, find who we think we are using getlogin. If this - * agrees with our uid, we'll go along with it. This should - * allow sharing of uids between several login names whilst - * coping correctly with people who have su'ed. - */ - user = getlogin(); -#if HAVE_SETPWENT - setpwent(); -#endif - if (user) - p = getpwnam(user); - else - p = NULL; - if (p && p->pw_uid == uid) { - /* - * The result of getlogin() really does correspond to - * our uid. Fine. - */ - ret = user; - } else { - /* - * If that didn't work, for whatever reason, we'll do - * the simpler version: look up our uid in the password - * file and map it straight to a name. - */ - p = getpwuid(uid); - if (!p) - return NULL; - ret = p->pw_name; - } -#if HAVE_ENDPWENT - endpwent(); -#endif - - return dupstr(ret); -} - -/* - * Display the fingerprints of the PGP Master Keys to the user. - * (This is here rather than in uxcons because it's appropriate even for - * Unix GUI apps.) - */ -void pgp_fingerprints(void) -{ - fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n" - "be used to establish a trust path from this executable to another\n" - "one. See the manual for more information.\n" - "(Note: these fingerprints have nothing to do with SSH!)\n" - "\n" - "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP "\n", stdout); -} - -/* - * Set and clear fcntl options on a file descriptor. We don't - * realistically expect any of these operations to fail (the most - * plausible error condition is EBADF, but we always believe ourselves - * to be passing a valid fd so even that's an assertion-fail sort of - * response), so we don't make any effort to return sensible error - * codes to the caller - we just log to standard error and die - * unceremoniously. However, nonblock and no_nonblock do return the - * previous state of O_NONBLOCK. - */ -void cloexec(int fd) { - int fdflags; - - fdflags = fcntl(fd, F_GETFD); - if (fdflags < 0) { - fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); - exit(1); - } - if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) { - fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); - exit(1); - } -} -void noncloexec(int fd) { - int fdflags; - - fdflags = fcntl(fd, F_GETFD); - if (fdflags < 0) { - fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno)); - exit(1); - } - if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) { - fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno)); - exit(1); - } -} -bool nonblock(int fd) { - int fdflags; - - fdflags = fcntl(fd, F_GETFL); - if (fdflags < 0) { - fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); - exit(1); - } - if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) { - fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); - exit(1); - } - - return fdflags & O_NONBLOCK; -} -bool no_nonblock(int fd) { - int fdflags; - - fdflags = fcntl(fd, F_GETFL); - if (fdflags < 0) { - fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno)); - exit(1); - } - if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) { - fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno)); - exit(1); - } - - return fdflags & O_NONBLOCK; -} - -FILE *f_open(const Filename *filename, char const *mode, bool is_private) -{ - if (!is_private) { - return fopen(filename->path, mode); - } else { - int fd; - assert(mode[0] == 'w'); /* is_private is meaningless for read, - and tricky for append */ - fd = open(filename->path, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd < 0) - return NULL; - return fdopen(fd, mode); - } -} - -FontSpec *fontspec_new(const char *name) -{ - FontSpec *f = snew(FontSpec); - f->name = dupstr(name); - return f; -} -FontSpec *fontspec_copy(const FontSpec *f) -{ - return fontspec_new(f->name); -} -void fontspec_free(FontSpec *f) -{ - sfree(f->name); - sfree(f); -} -void fontspec_serialise(BinarySink *bs, FontSpec *f) -{ - put_asciz(bs, f->name); -} -FontSpec *fontspec_deserialise(BinarySource *src) -{ - return fontspec_new(get_asciz(src)); -} - -char *make_dir_and_check_ours(const char *dirname) -{ - struct stat st; - - /* - * Create the directory. We might have created it before, so - * EEXIST is an OK error; but anything else is doom. - */ - if (mkdir(dirname, 0700) < 0 && errno != EEXIST) - return dupprintf("%s: mkdir: %s", dirname, strerror(errno)); - - /* - * Now check that that directory is _owned by us_ and not writable - * by anybody else. This protects us against somebody else - * previously having created the directory in a way that's - * writable to us, and thus manipulating us into creating the - * actual socket in a directory they can see so that they can - * connect to it and use our authenticated SSH sessions. - */ - if (stat(dirname, &st) < 0) - return dupprintf("%s: stat: %s", dirname, strerror(errno)); - if (st.st_uid != getuid()) - return dupprintf("%s: directory owned by uid %d, not by us", - dirname, st.st_uid); - if ((st.st_mode & 077) != 0) - return dupprintf("%s: directory has overgenerous permissions %03o" - " (expected 700)", dirname, st.st_mode & 0777); - - return NULL; -} - -char *make_dir_path(const char *path, mode_t mode) -{ - int pos = 0; - char *prefix; - - while (1) { - pos += strcspn(path + pos, "/"); - - if (pos > 0) { - prefix = dupprintf("%.*s", pos, path); - - if (mkdir(prefix, mode) < 0 && errno != EEXIST) { - char *ret = dupprintf("%s: mkdir: %s", - prefix, strerror(errno)); - sfree(prefix); - return ret; - } - - sfree(prefix); - } - - if (!path[pos]) - return NULL; - pos += strspn(path + pos, "/"); - } -} - -bool open_for_write_would_lose_data(const Filename *fn) -{ - struct stat st; - - if (stat(fn->path, &st) < 0) { - /* - * If the file doesn't even exist, we obviously want to return - * false. If we failed to stat it for any other reason, - * ignoring the precise error code and returning false still - * doesn't seem too unreasonable, because then we'll try to - * open the file for writing and report _that_ error, which is - * likely to be more to the point. - */ - return false; - } - - /* - * OK, something exists at this pathname and we've found out - * something about it. But an open-for-write will only - * destructively truncate it if it's a regular file with nonzero - * size. If it's empty, or some other kind of special thing like a - * character device (e.g. /dev/tty) or a named pipe, then opening - * it for write is already non-destructive and it's pointless and - * annoying to warn about it just because the same file can be - * opened for reading. (Indeed, if it's a named pipe, opening it - * for reading actually _causes inconvenience_ in its own right, - * even before the question of whether it gives misleading - * information.) - */ - if (S_ISREG(st.st_mode) && st.st_size > 0) { - return true; - } - - return false; -} diff --git a/unix/uxsignal.c b/unix/uxsignal.c deleted file mode 100644 index d75cce43..00000000 --- a/unix/uxsignal.c +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include -#include - -#include "defs.h" - -/* - * Calling signal() is non-portable, as it varies in meaning - * between platforms and depending on feature macros, and has - * stupid semantics at least some of the time. - * - * This function provides the same interface as the libc function, - * but provides consistent semantics. It assumes POSIX semantics - * for sigaction() (so you might need to do some more work if you - * port to something ancient like SunOS 4) - */ -void (*putty_signal(int sig, void (*func)(int)))(int) { - struct sigaction sa; - struct sigaction old; - - sa.sa_handler = func; - if(sigemptyset(&sa.sa_mask) < 0) - return SIG_ERR; - sa.sa_flags = SA_RESTART; - if(sigaction(sig, &sa, &old) < 0) - return SIG_ERR; - return old.sa_handler; -} - -void block_signal(int sig, bool block_it) -{ - sigset_t ss; - - sigemptyset(&ss); - sigaddset(&ss, sig); - if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) { - perror("sigprocmask"); - exit(1); - } -} - -/* -Local Variables: -c-basic-offset:4 -comment-column:40 -End: -*/ diff --git a/utils.c b/utils.c deleted file mode 100644 index 30b326ee..00000000 --- a/utils.c +++ /dev/null @@ -1,1122 +0,0 @@ -/* - * Platform-independent utility routines used throughout this code base. - * - * This file is linked into stand-alone test utilities which only want - * to include the things they really need, so functions in here should - * avoid depending on any functions outside it. Utility routines that - * are more tightly integrated into the main code should live in - * misc.c. - */ - -#include -#include -#include -#include -#include -#include - -#include "defs.h" -#include "misc.h" -#include "ssh.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, - bool 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, \ - (unsigned)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. */ -static NORETURN void fatal_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "host_string_test: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -void out_of_memory(void) { fatal_error("out of memory"); } - -#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 == '%') { - /* - * This delimiter character introduces an RFC 4007 scope - * id suffix (e.g. suffixing the address literal with - * %eth1 or %2 or some such). There's no syntax - * specification for the scope id, so just accept anything - * except the closing ]. - */ - p += strcspn(p, "]"); - } - if (*p == ']' && !p[1] && colons > 1) { - /* - * This looks like an IPv6 address literal (hex digits and - * at least two colons, plus optional scope id, 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); -} - -/* ---------------------------------------------------------------------- - * String handling routines. - */ - -char *dupstr(const char *s) -{ - char *p = NULL; - if (s) { - int len = strlen(s); - p = snewn(len + 1, char); - strcpy(p, s); - } - return p; -} - -/* Allocate the concatenation of N strings. Terminate arg list with NULL. */ -char *dupcat_fn(const char *s1, ...) -{ - int len; - char *p, *q, *sn; - va_list ap; - - len = strlen(s1); - va_start(ap, s1); - while (1) { - sn = va_arg(ap, char *); - if (!sn) - break; - len += strlen(sn); - } - va_end(ap); - - p = snewn(len + 1, char); - strcpy(p, s1); - q = p + strlen(p); - - va_start(ap, s1); - while (1) { - sn = va_arg(ap, char *); - if (!sn) - break; - strcpy(q, sn); - q += strlen(q); - } - va_end(ap); - - return p; -} - -void burnstr(char *string) /* sfree(str), only clear it first */ -{ - if (string) { - smemclr(string, strlen(string)); - sfree(string); - } -} - -int string_length_for_printf(size_t s) -{ - /* Truncate absurdly long strings (should one show up) to fit - * within a positive 'int', which is what the "%.*s" format will - * expect. */ - if (s > INT_MAX) - return INT_MAX; - return s; -} - -/* Work around lack of va_copy in old MSC */ -#if defined _MSC_VER && !defined va_copy -#define va_copy(a, b) TYPECHECK( \ - (va_list *)0 == &(a) && (va_list *)0 == &(b), \ - memcpy(&a, &b, sizeof(va_list))) -#endif - -/* Also lack of vsnprintf before VS2015 */ -#if defined _WINDOWS && \ - !defined __MINGW32__ && \ - !defined __WINE__ && \ - _MSC_VER < 1900 -#define vsnprintf _vsnprintf -#endif - -/* - * Do an sprintf(), but into a custom-allocated buffer. - * - * 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. - */ -static char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, - const char *fmt, va_list ap) -{ - size_t size = *sizeptr; - sgrowarrayn_nm(buf, size, oldlen, 512); - - while (1) { - va_list aq; - va_copy(aq, ap); - int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq); - va_end(aq); - - if (len >= 0 && len < size) { - /* This is the C99-specified criterion for snprintf to have - * been completely successful. */ - *sizeptr = size; - return buf; - } else if (len > 0) { - /* This is the C99 error condition: the returned length is - * the required buffer size not counting the NUL. */ - sgrowarrayn_nm(buf, size, oldlen + 1, len); - } else { - /* This is the pre-C99 glibc error condition: <0 means the - * buffer wasn't big enough, so we enlarge it a bit and hope. */ - sgrowarray_nm(buf, size, size); - } - } -} - -char *dupvprintf(const char *fmt, va_list ap) -{ - size_t 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_impl { - size_t size; - struct strbuf visible; - bool nm; /* true if we insist on non-moving buffer resizes */ -}; - -#define STRBUF_SET_UPTR(buf) \ - ((buf)->visible.u = (unsigned char *)(buf)->visible.s) -#define STRBUF_SET_PTR(buf, ptr) \ - ((buf)->visible.s = (ptr), STRBUF_SET_UPTR(buf)) - -void *strbuf_append(strbuf *buf_o, size_t len) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - char *toret; - sgrowarray_general( - buf->visible.s, buf->size, buf->visible.len + 1, len, buf->nm); - STRBUF_SET_UPTR(buf); - toret = buf->visible.s + buf->visible.len; - buf->visible.len += len; - buf->visible.s[buf->visible.len] = '\0'; - return toret; -} - -void strbuf_shrink_to(strbuf *buf, size_t new_len) -{ - assert(new_len <= buf->len); - buf->len = new_len; - buf->s[buf->len] = '\0'; -} - -void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove) -{ - assert(amount_to_remove <= buf->len); - buf->len -= amount_to_remove; - buf->s[buf->len] = '\0'; -} - -bool strbuf_chomp(strbuf *buf, char char_to_remove) -{ - if (buf->len > 0 && buf->s[buf->len-1] == char_to_remove) { - strbuf_shrink_by(buf, 1); - return true; - } - return false; -} - -static void strbuf_BinarySink_write( - BinarySink *bs, const void *data, size_t len) -{ - strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf); - memcpy(strbuf_append(buf_o, len), data, len); -} - -static strbuf *strbuf_new_general(bool nm) -{ - struct strbuf_impl *buf = snew(struct strbuf_impl); - BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); - buf->visible.len = 0; - buf->size = 512; - buf->nm = nm; - STRBUF_SET_PTR(buf, snewn(buf->size, char)); - *buf->visible.s = '\0'; - return &buf->visible; -} -strbuf *strbuf_new(void) { return strbuf_new_general(false); } -strbuf *strbuf_new_nm(void) { return strbuf_new_general(true); } -void strbuf_free(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - if (buf->visible.s) { - smemclr(buf->visible.s, buf->size); - sfree(buf->visible.s); - } - sfree(buf); -} -char *strbuf_to_str(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - char *ret = buf->visible.s; - sfree(buf); - return ret; -} -void strbuf_catfv(strbuf *buf_o, const char *fmt, va_list ap) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len, - &buf->size, fmt, ap)); - buf->visible.len += strlen(buf->visible.s + buf->visible.len); -} -void strbuf_catf(strbuf *buf_o, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - strbuf_catfv(buf_o, fmt, ap); - va_end(ap); -} - -strbuf *strbuf_new_for_agent_query(void) -{ - strbuf *buf = strbuf_new(); - strbuf_append(buf, 4); - return buf; -} -void strbuf_finalise_agent_query(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - assert(buf->visible.len >= 5); - PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4); -} - -/* - * 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); - size_t 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 */ - sgrowarrayn_nm(ret, size, len, 512); - } - if (len == 0) { /* first fgets returned NULL */ - sfree(ret); - return NULL; - } - ret[len] = '\0'; - return ret; -} - -/* - * Read an entire file into a BinarySink. - */ -bool read_file_into(BinarySink *bs, FILE *fp) -{ - char buf[4096]; - while (1) { - size_t retd = fread(buf, 1, sizeof(buf), fp); - if (retd == 0) - return !ferror(fp); - put_data(bs, buf, retd); - } -} - -/* - * 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; -} - -/* ---------------------------------------------------------------------- - * Core base64 encoding and decoding routines. - */ - -void base64_encode_atom(const unsigned char *data, int n, char *out) -{ - static const char base64_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - unsigned word; - - word = data[0] << 16; - if (n > 1) - word |= data[1] << 8; - if (n > 2) - word |= data[2]; - out[0] = base64_chars[(word >> 18) & 0x3F]; - out[1] = base64_chars[(word >> 12) & 0x3F]; - if (n > 1) - out[2] = base64_chars[(word >> 6) & 0x3F]; - else - out[2] = '='; - if (n > 2) - out[3] = base64_chars[word & 0x3F]; - else - 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 - * - * - add an arbitrary amount of data to the end of the list - * - remove the first N bytes from the list - * - return a (pointer,length) pair giving some initial data in - * the list, suitable for passing to a send or write system - * call - * - retrieve a larger amount of initial data from the list - * - return the current size of the buffer chain in bytes - */ - -#define BUFFER_MIN_GRANULE 512 - -struct bufchain_granule { - struct bufchain_granule *next; - char *bufpos, *bufend, *bufmax; -}; - -static void uninitialised_queue_idempotent_callback(IdempotentCallback *ic) -{ - unreachable("bufchain callback used while uninitialised"); -} - -void bufchain_init(bufchain *ch) -{ - ch->head = ch->tail = NULL; - ch->buffersize = 0; - ch->ic = NULL; - ch->queue_idempotent_callback = uninitialised_queue_idempotent_callback; -} - -void bufchain_clear(bufchain *ch) -{ - struct bufchain_granule *b; - while (ch->head) { - b = ch->head; - ch->head = ch->head->next; - smemclr(b, sizeof(*b)); - sfree(b); - } - ch->tail = NULL; - ch->buffersize = 0; -} - -size_t bufchain_size(bufchain *ch) -{ - return ch->buffersize; -} - -void bufchain_set_callback_inner( - bufchain *ch, IdempotentCallback *ic, - void (*queue_idempotent_callback)(IdempotentCallback *ic)) -{ - ch->queue_idempotent_callback = queue_idempotent_callback; - ch->ic = ic; -} - -void bufchain_add(bufchain *ch, const void *data, size_t len) -{ - const char *buf = (const char *)data; - - if (len == 0) return; - - ch->buffersize += len; - - while (len > 0) { - if (ch->tail && ch->tail->bufend < ch->tail->bufmax) { - size_t 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) { - size_t 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; - } - } - - if (ch->ic) - ch->queue_idempotent_callback(ch->ic); -} - -void bufchain_consume(bufchain *ch, size_t len) -{ - struct bufchain_granule *tmp; - - assert(ch->buffersize >= len); - while (len > 0) { - int remlen = len; - assert(ch->head != NULL); - if (remlen >= ch->head->bufend - ch->head->bufpos) { - remlen = ch->head->bufend - ch->head->bufpos; - tmp = ch->head; - ch->head = tmp->next; - if (!ch->head) - ch->tail = NULL; - smemclr(tmp, sizeof(*tmp)); - sfree(tmp); - } else - ch->head->bufpos += remlen; - ch->buffersize -= remlen; - len -= remlen; - } -} - -ptrlen bufchain_prefix(bufchain *ch) -{ - return make_ptrlen(ch->head->bufpos, ch->head->bufend - ch->head->bufpos); -} - -void bufchain_fetch(bufchain *ch, void *data, size_t len) -{ - struct bufchain_granule *tmp; - char *data_c = (char *)data; - - tmp = ch->head; - - assert(ch->buffersize >= len); - while (len > 0) { - int remlen = len; - - assert(tmp != NULL); - if (remlen >= tmp->bufend - tmp->bufpos) - remlen = tmp->bufend - tmp->bufpos; - memcpy(data_c, tmp->bufpos, remlen); - - tmp = tmp->next; - len -= remlen; - data_c += remlen; - } -} - -void bufchain_fetch_consume(bufchain *ch, void *data, size_t len) -{ - bufchain_fetch(ch, data, len); - bufchain_consume(ch, len); -} - -bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len) -{ - if (ch->buffersize >= len) { - bufchain_fetch_consume(ch, data, len); - return true; - } else { - return false; - } -} - -size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len) -{ - if (len > ch->buffersize) - len = ch->buffersize; - if (len) - bufchain_fetch_consume(ch, data, len); - return len; -} - -/* ---------------------------------------------------------------------- - * Debugging routines. - */ - -#ifdef DEBUG -extern void dputs(const char *); /* defined in per-platform *misc.c */ - -void debug_printf(const char *fmt, ...) -{ - char *buf; - va_list ap; - - va_start(ap, fmt); - buf = dupvprintf(fmt, ap); - dputs(buf); - sfree(buf); - va_end(ap); -} - -void debug_memdump(const void *buf, int len, bool L) -{ - int i; - const unsigned char *p = buf; - char foo[17]; - if (L) { - int delta; - 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) - 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 { - debug_printf("%c%2.2x", - &p[i] != (unsigned char *) buf - && i % 4 ? '.' : ' ', p[i] - ); - if (p[i] >= ' ' && p[i] <= '~') - foo[i] = (char) p[i]; - } - } - foo[i] = '\0'; - debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo); - } -} - -#endif /* def DEBUG */ - -#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 - -bool 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 nullstrcmp(const char *a, const char *b) -{ - if (a == NULL && b == NULL) - return 0; - if (a == NULL) - return -1; - if (b == NULL) - return +1; - return strcmp(a, b); -} - -bool ptrlen_eq_string(ptrlen pl, const char *str) -{ - size_t len = strlen(str); - return (pl.len == len && !memcmp(pl.ptr, str, len)); -} - -bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2) -{ - return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len)); -} - -int ptrlen_strcmp(ptrlen pl1, ptrlen pl2) -{ - size_t minlen = pl1.len < pl2.len ? pl1.len : pl2.len; - if (minlen) { /* tolerate plX.ptr==NULL as long as plX.len==0 */ - int cmp = memcmp(pl1.ptr, pl2.ptr, minlen); - if (cmp) - return cmp; - } - return pl1.len < pl2.len ? -1 : pl1.len > pl2.len ? +1 : 0; -} - -bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail) -{ - if (whole.len >= prefix.len && - !memcmp(whole.ptr, prefix.ptr, prefix.len)) { - if (tail) { - tail->ptr = (const char *)whole.ptr + prefix.len; - tail->len = whole.len - prefix.len; - } - return true; - } - return false; -} - -bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail) -{ - if (whole.len >= suffix.len && - !memcmp((char *)whole.ptr + (whole.len - suffix.len), - suffix.ptr, suffix.len)) { - if (tail) { - tail->ptr = whole.ptr; - tail->len = whole.len - suffix.len; - } - return true; - } - return false; -} - -ptrlen ptrlen_get_word(ptrlen *input, const char *separators) -{ - const char *p = input->ptr, *end = p + input->len; - ptrlen toret; - - while (p < end && strchr(separators, *p)) - p++; - toret.ptr = p; - while (p < end && !strchr(separators, *p)) - p++; - toret.len = p - (const char *)toret.ptr; - - size_t to_consume = p - (const char *)input->ptr; - assert(to_consume <= input->len); - input->ptr = (const char *)input->ptr + to_consume; - input->len -= to_consume; - - return toret; -} - -char *mkstr(ptrlen pl) -{ - char *p = snewn(pl.len + 1, char); - memcpy(p, pl.ptr, pl.len); - p[pl.len] = '\0'; - return p; -} - -bool strstartswith(const char *s, const char *t) -{ - return !strncmp(s, t, strlen(t)); -} - -bool strendswith(const char *s, const char *t) -{ - size_t slen = strlen(s), tlen = strlen(t); - return slen >= tlen && !strcmp(s + (slen - tlen), t); -} - -size_t encode_utf8(void *output, unsigned long ch) -{ - unsigned char *start = (unsigned char *)output, *p = start; - - if (ch < 0x80) { - *p++ = ch; - } else if (ch < 0x800) { - *p++ = 0xC0 | (ch >> 6); - *p++ = 0x80 | (ch & 0x3F); - } else if (ch < 0x10000) { - *p++ = 0xE0 | (ch >> 12); - *p++ = 0x80 | ((ch >> 6) & 0x3F); - *p++ = 0x80 | (ch & 0x3F); - } else { - *p++ = 0xF0 | (ch >> 18); - *p++ = 0x80 | ((ch >> 12) & 0x3F); - *p++ = 0x80 | ((ch >> 6) & 0x3F); - *p++ = 0x80 | (ch & 0x3F); - } - return p - start; -} - -void write_c_string_literal(FILE *fp, ptrlen str) -{ - for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) { - char c = *p; - - if (c == '\n') - fputs("\\n", fp); - else if (c == '\r') - fputs("\\r", fp); - else if (c == '\t') - fputs("\\t", fp); - else if (c == '\b') - fputs("\\b", fp); - else if (c == '\\') - fputs("\\\\", fp); - else if (c == '"') - fputs("\\\"", fp); - else if (c >= 32 && c <= 126) - fputc(c, fp); - else - fprintf(fp, "\\%03o", (unsigned char)c); - } -} - -void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size) -{ - switch (size & 15) { - case 0: - while (size >= 16) { - size -= 16; - *out++ = *in1++ ^ *in2++; - case 15: *out++ = *in1++ ^ *in2++; - case 14: *out++ = *in1++ ^ *in2++; - case 13: *out++ = *in1++ ^ *in2++; - case 12: *out++ = *in1++ ^ *in2++; - case 11: *out++ = *in1++ ^ *in2++; - case 10: *out++ = *in1++ ^ *in2++; - case 9: *out++ = *in1++ ^ *in2++; - case 8: *out++ = *in1++ ^ *in2++; - case 7: *out++ = *in1++ ^ *in2++; - case 6: *out++ = *in1++ ^ *in2++; - case 5: *out++ = *in1++ ^ *in2++; - case 4: *out++ = *in1++ ^ *in2++; - case 3: *out++ = *in1++ ^ *in2++; - case 2: *out++ = *in1++ ^ *in2++; - case 1: *out++ = *in1++ ^ *in2++; - } - } -} - -FingerprintType ssh2_pick_fingerprint( - char **fingerprints, FingerprintType preferred_type) -{ - /* - * Keys are either SSH-2, in which case we have all fingerprint - * types, or SSH-1, in which case we have only MD5. So we return - * the default type if we can, or MD5 if that's all we have; no - * need for a fully general preference-list system. - */ - FingerprintType fptype = fingerprints[preferred_type] ? - preferred_type : SSH_FPTYPE_MD5; - assert(fingerprints[fptype]); - return fptype; -} - -FingerprintType ssh2_pick_default_fingerprint(char **fingerprints) -{ - return ssh2_pick_fingerprint(fingerprints, SSH_FPTYPE_DEFAULT); -} diff --git a/utils/base64_decode_atom.c b/utils/base64_decode_atom.c new file mode 100644 index 00000000..2a98150e --- /dev/null +++ b/utils/base64_decode_atom.c @@ -0,0 +1,54 @@ +/* + * Core routine to decode a single atomic base64 chunk. + */ + +#include "defs.h" +#include "misc.h" + +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; +} diff --git a/utils/base64_encode_atom.c b/utils/base64_encode_atom.c new file mode 100644 index 00000000..c33476f0 --- /dev/null +++ b/utils/base64_encode_atom.c @@ -0,0 +1,30 @@ +/* + * Core routine to encode a single atomic base64 chunk. + */ + +#include "defs.h" +#include "misc.h" + +void base64_encode_atom(const unsigned char *data, int n, char *out) +{ + static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + unsigned word; + + word = data[0] << 16; + if (n > 1) + word |= data[1] << 8; + if (n > 2) + word |= data[2]; + out[0] = base64_chars[(word >> 18) & 0x3F]; + out[1] = base64_chars[(word >> 12) & 0x3F]; + if (n > 1) + out[2] = base64_chars[(word >> 6) & 0x3F]; + else + out[2] = '='; + if (n > 2) + out[3] = base64_chars[word & 0x3F]; + else + out[3] = '='; +} diff --git a/utils/bufchain.c b/utils/bufchain.c new file mode 100644 index 00000000..60ebc2be --- /dev/null +++ b/utils/bufchain.c @@ -0,0 +1,173 @@ +/* + * Generic routines to deal with send buffers: a linked list of + * smallish blocks, with the operations + * + * - add an arbitrary amount of data to the end of the list + * - remove the first N bytes from the list + * - return a (pointer,length) pair giving some initial data in + * the list, suitable for passing to a send or write system + * call + * - retrieve a larger amount of initial data from the list + * - return the current size of the buffer chain in bytes + */ + +#include "defs.h" +#include "misc.h" + +#define BUFFER_MIN_GRANULE 512 + +struct bufchain_granule { + struct bufchain_granule *next; + char *bufpos, *bufend, *bufmax; +}; + +static void uninitialised_queue_idempotent_callback(IdempotentCallback *ic) +{ + unreachable("bufchain callback used while uninitialised"); +} + +void bufchain_init(bufchain *ch) +{ + ch->head = ch->tail = NULL; + ch->buffersize = 0; + ch->ic = NULL; + ch->queue_idempotent_callback = uninitialised_queue_idempotent_callback; +} + +void bufchain_clear(bufchain *ch) +{ + struct bufchain_granule *b; + while (ch->head) { + b = ch->head; + ch->head = ch->head->next; + smemclr(b, sizeof(*b)); + sfree(b); + } + ch->tail = NULL; + ch->buffersize = 0; +} + +size_t bufchain_size(bufchain *ch) +{ + return ch->buffersize; +} + +void bufchain_set_callback_inner( + bufchain *ch, IdempotentCallback *ic, + void (*queue_idempotent_callback)(IdempotentCallback *ic)) +{ + ch->queue_idempotent_callback = queue_idempotent_callback; + ch->ic = ic; +} + +void bufchain_add(bufchain *ch, const void *data, size_t len) +{ + const char *buf = (const char *)data; + + if (len == 0) return; + + ch->buffersize += len; + + while (len > 0) { + if (ch->tail && ch->tail->bufend < ch->tail->bufmax) { + size_t 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) { + size_t 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; + } + } + + if (ch->ic) + ch->queue_idempotent_callback(ch->ic); +} + +void bufchain_consume(bufchain *ch, size_t len) +{ + struct bufchain_granule *tmp; + + assert(ch->buffersize >= len); + while (len > 0) { + int remlen = len; + assert(ch->head != NULL); + if (remlen >= ch->head->bufend - ch->head->bufpos) { + remlen = ch->head->bufend - ch->head->bufpos; + tmp = ch->head; + ch->head = tmp->next; + if (!ch->head) + ch->tail = NULL; + smemclr(tmp, sizeof(*tmp)); + sfree(tmp); + } else + ch->head->bufpos += remlen; + ch->buffersize -= remlen; + len -= remlen; + } +} + +ptrlen bufchain_prefix(bufchain *ch) +{ + return make_ptrlen(ch->head->bufpos, ch->head->bufend - ch->head->bufpos); +} + +void bufchain_fetch(bufchain *ch, void *data, size_t len) +{ + struct bufchain_granule *tmp; + char *data_c = (char *)data; + + tmp = ch->head; + + assert(ch->buffersize >= len); + while (len > 0) { + int remlen = len; + + assert(tmp != NULL); + if (remlen >= tmp->bufend - tmp->bufpos) + remlen = tmp->bufend - tmp->bufpos; + memcpy(data_c, tmp->bufpos, remlen); + + tmp = tmp->next; + len -= remlen; + data_c += remlen; + } +} + +void bufchain_fetch_consume(bufchain *ch, void *data, size_t len) +{ + bufchain_fetch(ch, data, len); + bufchain_consume(ch, len); +} + +bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len) +{ + if (ch->buffersize >= len) { + bufchain_fetch_consume(ch, data, len); + return true; + } else { + return false; + } +} + +size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len) +{ + if (len > ch->buffersize) + len = ch->buffersize; + if (len) + bufchain_fetch_consume(ch, data, len); + return len; +} diff --git a/utils/buildinfo.c b/utils/buildinfo.c new file mode 100644 index 00000000..1db65a0d --- /dev/null +++ b/utils/buildinfo.c @@ -0,0 +1,158 @@ +/* + * Return a string describing everything we know about how this + * particular binary was built: from what source, for what target + * platform, using what tools, with what settings, etc. + */ + +#include "putty.h" + +char *buildinfo(const char *newline) +{ + strbuf *buf = strbuf_new(); + + strbuf_catf(buf, "Build platform: %d-bit %s", + (int)(CHAR_BIT * sizeof(void *)), + BUILDINFO_PLATFORM); + +#ifdef __clang_version__ +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); +#elif defined __GNUC__ && defined __VERSION__ +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); +#endif + +#if defined _MSC_VER +#ifndef FOUND_COMPILER +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: ", newline); +#else + strbuf_catf(buf, ", emulating "); +#endif + strbuf_catf(buf, "Visual Studio"); + +#if 0 + /* + * List of _MSC_VER values and their translations taken from + * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros + * + * The pointless #if 0 branch containing this comment is there so + * that every real clause can start with #elif and there's no + * anomalous first clause. That way the patch looks nicer when you + * add extra ones. + */ +#elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500 + /* + * 16.9 and 16.8 have the same _MSC_VER value, and have to be + * distinguished by _MSC_FULL_VER. As of 2021-03-04 that is not + * mentioned on the above page, but see e.g. + * https://developercommunity.visualstudio.com/t/the-169-cc-compiler-still-uses-the-same-version-nu/1335194#T-N1337120 + * which says that 16.9 builds will have versions starting at + * 19.28.29500.* and going up. Hence, 19 28 29500 is what we + * compare _MSC_FULL_VER against above. + */ + strbuf_catf(buf, " 2019 (16.9)"); +#elif _MSC_VER == 1928 + strbuf_catf(buf, " 2019 (16.8)"); +#elif _MSC_VER == 1927 + strbuf_catf(buf, " 2019 (16.7)"); +#elif _MSC_VER == 1926 + strbuf_catf(buf, " 2019 (16.6)"); +#elif _MSC_VER == 1925 + strbuf_catf(buf, " 2019 (16.5)"); +#elif _MSC_VER == 1924 + strbuf_catf(buf, " 2019 (16.4)"); +#elif _MSC_VER == 1923 + strbuf_catf(buf, " 2019 (16.3)"); +#elif _MSC_VER == 1922 + strbuf_catf(buf, " 2019 (16.2)"); +#elif _MSC_VER == 1921 + strbuf_catf(buf, " 2019 (16.1)"); +#elif _MSC_VER == 1920 + strbuf_catf(buf, " 2019 (16.0)"); +#elif _MSC_VER == 1916 + strbuf_catf(buf, " 2017 version 15.9"); +#elif _MSC_VER == 1915 + strbuf_catf(buf, " 2017 version 15.8"); +#elif _MSC_VER == 1914 + strbuf_catf(buf, " 2017 version 15.7"); +#elif _MSC_VER == 1913 + strbuf_catf(buf, " 2017 version 15.6"); +#elif _MSC_VER == 1912 + strbuf_catf(buf, " 2017 version 15.5"); +#elif _MSC_VER == 1911 + strbuf_catf(buf, " 2017 version 15.3"); +#elif _MSC_VER == 1910 + strbuf_catf(buf, " 2017 RTW (15.0)"); +#elif _MSC_VER == 1900 + strbuf_catf(buf, " 2015 (14.0)"); +#elif _MSC_VER == 1800 + strbuf_catf(buf, " 2013 (12.0)"); +#elif _MSC_VER == 1700 + strbuf_catf(buf, " 2012 (11.0)"); +#elif _MSC_VER == 1600 + strbuf_catf(buf, " 2010 (10.0)"); +#elif _MSC_VER == 1500 + strbuf_catf(buf, " 2008 (9.0)"); +#elif _MSC_VER == 1400 + strbuf_catf(buf, " 2005 (8.0)"); +#elif _MSC_VER == 1310 + strbuf_catf(buf, " .NET 2003 (7.1)"); +#elif _MSC_VER == 1300 + strbuf_catf(buf, " .NET 2002 (7.0)"); +#elif _MSC_VER == 1200 + strbuf_catf(buf, " 6.0"); +#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 +#if defined _WINDOWS + { + int echm = has_embedded_chm(); + if (echm >= 0) + strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, + echm ? "yes" : "no"); + } +#endif + +#if defined _WINDOWS && defined MINEFIELD + strbuf_catf(buf, "%sBuild option: MINEFIELD", 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); +} diff --git a/utils/burnstr.c b/utils/burnstr.c new file mode 100644 index 00000000..33214d89 --- /dev/null +++ b/utils/burnstr.c @@ -0,0 +1,15 @@ +/* + * 'Burn' a dynamically allocated string, in the sense of destroying + * it beyond recovery: overwrite it with zeroes and then free it. + */ + +#include "defs.h" +#include "misc.h" + +void burnstr(char *string) +{ + if (string) { + smemclr(string, strlen(string)); + sfree(string); + } +} diff --git a/utils/chomp.c b/utils/chomp.c new file mode 100644 index 00000000..866fc652 --- /dev/null +++ b/utils/chomp.c @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#include + +#include "defs.h" +#include "misc.h" + +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; +} diff --git a/conf.c b/utils/conf.c similarity index 100% rename from conf.c rename to utils/conf.c diff --git a/utils/conf_dest.c b/utils/conf_dest.c new file mode 100644 index 00000000..13e4ce65 --- /dev/null +++ b/utils/conf_dest.c @@ -0,0 +1,15 @@ +/* + * Decide whether the 'host name' or 'serial line' field of a Conf is + * important, based on which protocol it has selected. + */ + +#include "putty.h" + +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); +} + diff --git a/utils/conf_launchable.c b/utils/conf_launchable.c new file mode 100644 index 00000000..904ade61 --- /dev/null +++ b/utils/conf_launchable.c @@ -0,0 +1,14 @@ +/* + * Determine whether or not a Conf represents a session which can + * sensibly be launched right now. + */ + +#include "putty.h" + +bool 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; +} diff --git a/utils/ctrlparse.c b/utils/ctrlparse.c new file mode 100644 index 00000000..86f87902 --- /dev/null +++ b/utils/ctrlparse.c @@ -0,0 +1,49 @@ +/* + * 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 + */ + +#include + +#include "defs.h" +#include "misc.h" + +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; +} diff --git a/utils/debug.c b/utils/debug.c new file mode 100644 index 00000000..806b250a --- /dev/null +++ b/utils/debug.c @@ -0,0 +1,56 @@ +/* + * Debugging routines used by the debug() macros, at least if you + * compiled with -DDEBUG (aka the PUTTY_DEBUG cmake option) so that + * those macros don't optimise down to nothing. + */ + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +void debug_printf(const char *fmt, ...) +{ + char *buf; + va_list ap; + + va_start(ap, fmt); + buf = dupvprintf(fmt, ap); + dputs(buf); + sfree(buf); + va_end(ap); +} + +void debug_memdump(const void *buf, int len, bool L) +{ + int i; + const unsigned char *p = buf; + char foo[17]; + if (L) { + int delta; + 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) + 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 { + debug_printf("%c%2.2x", + &p[i] != (unsigned char *) buf + && i % 4 ? '.' : ' ', p[i] + ); + if (p[i] >= ' ' && p[i] <= '~') + foo[i] = (char) p[i]; + } + } + foo[i] = '\0'; + debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo); + } +} diff --git a/utils/dupcat.c b/utils/dupcat.c new file mode 100644 index 00000000..ddd6599e --- /dev/null +++ b/utils/dupcat.c @@ -0,0 +1,48 @@ +/* + * Implementation function behind dupcat() in misc.h. + * + * This function is called with an arbitrary number of 'const char *' + * parameters, of which the last one is a null pointer. The wrapper + * macro puts on the null pointer itself, so normally callers don't + * have to. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" + +char *dupcat_fn(const char *s1, ...) +{ + int len; + char *p, *q, *sn; + va_list ap; + + len = strlen(s1); + va_start(ap, s1); + while (1) { + sn = va_arg(ap, char *); + if (!sn) + break; + len += strlen(sn); + } + va_end(ap); + + p = snewn(len + 1, char); + strcpy(p, s1); + q = p + strlen(p); + + va_start(ap, s1); + while (1) { + sn = va_arg(ap, char *); + if (!sn) + break; + strcpy(q, sn); + q += strlen(q); + } + va_end(ap); + + return p; +} + diff --git a/utils/dupprintf.c b/utils/dupprintf.c new file mode 100644 index 00000000..aa9f330b --- /dev/null +++ b/utils/dupprintf.c @@ -0,0 +1,100 @@ +/* + * Do an sprintf(), but into a custom-allocated buffer. + * + * 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. + */ + +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +/* Work around lack of va_copy in old MSC */ +#if defined _MSC_VER && !defined va_copy +#define va_copy(a, b) TYPECHECK( \ + (va_list *)0 == &(a) && (va_list *)0 == &(b), \ + memcpy(&a, &b, sizeof(va_list))) +#endif + +/* Also lack of vsnprintf before VS2015 */ +#if defined _WINDOWS && \ + !defined __MINGW32__ && \ + !defined __WINE__ && \ + _MSC_VER < 1900 +#define vsnprintf _vsnprintf +#endif + +char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, + const char *fmt, va_list ap) +{ + size_t size = *sizeptr; + sgrowarrayn_nm(buf, size, oldlen, 512); + + while (1) { + va_list aq; + va_copy(aq, ap); + int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq); + va_end(aq); + + if (len >= 0 && len < size) { + /* This is the C99-specified criterion for snprintf to have + * been completely successful. */ + *sizeptr = size; + return buf; + } else if (len > 0) { + /* This is the C99 error condition: the returned length is + * the required buffer size not counting the NUL. */ + sgrowarrayn_nm(buf, size, oldlen + 1, len); + } else { + /* This is the pre-C99 glibc error condition: <0 means the + * buffer wasn't big enough, so we enlarge it a bit and hope. */ + sgrowarray_nm(buf, size, size); + } + } +} + +char *dupvprintf(const char *fmt, va_list ap) +{ + size_t 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; +} diff --git a/utils/dupstr.c b/utils/dupstr.c new file mode 100644 index 00000000..fd79583f --- /dev/null +++ b/utils/dupstr.c @@ -0,0 +1,19 @@ +/* + * Allocate a duplicate of an ordinary C NUL-terminated string. + */ + +#include + +#include "defs.h" +#include "misc.h" + +char *dupstr(const char *s) +{ + char *p = NULL; + if (s) { + int len = strlen(s); + p = snewn(len + 1, char); + strcpy(p, s); + } + return p; +} diff --git a/utils/encode_utf8.c b/utils/encode_utf8.c new file mode 100644 index 00000000..731ab925 --- /dev/null +++ b/utils/encode_utf8.c @@ -0,0 +1,29 @@ +/* + * Encode a single code point as UTF-8. + */ + +#include "defs.h" +#include "misc.h" + +size_t encode_utf8(void *output, unsigned long ch) +{ + unsigned char *start = (unsigned char *)output, *p = start; + + if (ch < 0x80) { + *p++ = ch; + } else if (ch < 0x800) { + *p++ = 0xC0 | (ch >> 6); + *p++ = 0x80 | (ch & 0x3F); + } else if (ch < 0x10000) { + *p++ = 0xE0 | (ch >> 12); + *p++ = 0x80 | ((ch >> 6) & 0x3F); + *p++ = 0x80 | (ch & 0x3F); + } else { + assert(ch <= 0x10FFFF); + *p++ = 0xF0 | (ch >> 18); + *p++ = 0x80 | ((ch >> 12) & 0x3F); + *p++ = 0x80 | ((ch >> 6) & 0x3F); + *p++ = 0x80 | (ch & 0x3F); + } + return p - start; +} diff --git a/utils/fgetline.c b/utils/fgetline.c new file mode 100644 index 00000000..2bb580f1 --- /dev/null +++ b/utils/fgetline.c @@ -0,0 +1,25 @@ +/* + * Read an entire line of text from a file. Return a buffer + * malloced to be as big as necessary (caller must free). + */ + +#include "defs.h" +#include "misc.h" + +char *fgetline(FILE *fp) +{ + char *ret = snewn(512, char); + size_t 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 */ + sgrowarrayn_nm(ret, size, len, 512); + } + if (len == 0) { /* first fgets returned NULL */ + sfree(ret); + return NULL; + } + ret[len] = '\0'; + return ret; +} diff --git a/utils/host_strchr.c b/utils/host_strchr.c new file mode 100644 index 00000000..363a915e --- /dev/null +++ b/utils/host_strchr.c @@ -0,0 +1,18 @@ +/* + * strchr-like wrapper around host_strchr_internal. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +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); +} diff --git a/utils/host_strchr_internal.c b/utils/host_strchr_internal.c new file mode 100644 index 00000000..07aadd37 --- /dev/null +++ b/utils/host_strchr_internal.c @@ -0,0 +1,80 @@ +/* + * 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. + * + * This internal function provides an API that's a bit like strchr (in + * that it returns a pointer to the character it found, or NULL), and + * a bit like strcspn (in that you can give it a set of characters to + * look for, not just one). Also it has an option to return the first + * or last character it finds. Other functions in the utils directory + * provide wrappers on it with APIs more like familiar + * functions. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +const char *host_strchr_internal(const char *s, const char *set, bool 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++; + } +} + +#ifdef TEST + +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, \ + (unsigned)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; +} + +#endif /* TEST */ diff --git a/utils/host_strcspn.c b/utils/host_strcspn.c new file mode 100644 index 00000000..958f47f8 --- /dev/null +++ b/utils/host_strcspn.c @@ -0,0 +1,19 @@ +/* + * strcspn-like wrapper around host_strchr_internal. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +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); +} diff --git a/utils/host_strduptrim.c b/utils/host_strduptrim.c new file mode 100644 index 00000000..94492e64 --- /dev/null +++ b/utils/host_strduptrim.c @@ -0,0 +1,51 @@ +/* + * Trim square brackets off the outside of an IPv6 address literal. + * Leave all other strings unchanged. Returns a fresh dynamically + * allocated string. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" + +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 == '%') { + /* + * This delimiter character introduces an RFC 4007 scope + * id suffix (e.g. suffixing the address literal with + * %eth1 or %2 or some such). There's no syntax + * specification for the scope id, so just accept anything + * except the closing ]. + */ + p += strcspn(p, "]"); + } + if (*p == ']' && !p[1] && colons > 1) { + /* + * This looks like an IPv6 address literal (hex digits and + * at least two colons, plus optional scope id, 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); +} diff --git a/utils/host_strrchr.c b/utils/host_strrchr.c new file mode 100644 index 00000000..a1dd4c94 --- /dev/null +++ b/utils/host_strrchr.c @@ -0,0 +1,18 @@ +/* + * strchr-like wrapper around host_strchr_internal. + */ + +#include +#include + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +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); +} diff --git a/time.c b/utils/ltime.c similarity index 100% rename from time.c rename to utils/ltime.c diff --git a/marshal.c b/utils/marshal.c similarity index 100% rename from marshal.c rename to utils/marshal.c diff --git a/memory.c b/utils/memory.c similarity index 100% rename from memory.c rename to utils/memory.c diff --git a/utils/memxor.c b/utils/memxor.c new file mode 100644 index 00000000..4fd41f41 --- /dev/null +++ b/utils/memxor.c @@ -0,0 +1,34 @@ +/* + * XOR two pieces of memory, copying the result into a third, which + * may precisely alias one of the input pair (but no guarantees if it + * partially overlaps). + */ + +#include "defs.h" +#include "misc.h" + +void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size) +{ + switch (size & 15) { + case 0: + while (size >= 16) { + size -= 16; + *out++ = *in1++ ^ *in2++; + case 15: *out++ = *in1++ ^ *in2++; + case 14: *out++ = *in1++ ^ *in2++; + case 13: *out++ = *in1++ ^ *in2++; + case 12: *out++ = *in1++ ^ *in2++; + case 11: *out++ = *in1++ ^ *in2++; + case 10: *out++ = *in1++ ^ *in2++; + case 9: *out++ = *in1++ ^ *in2++; + case 8: *out++ = *in1++ ^ *in2++; + case 7: *out++ = *in1++ ^ *in2++; + case 6: *out++ = *in1++ ^ *in2++; + case 5: *out++ = *in1++ ^ *in2++; + case 4: *out++ = *in1++ ^ *in2++; + case 3: *out++ = *in1++ ^ *in2++; + case 2: *out++ = *in1++ ^ *in2++; + case 1: *out++ = *in1++ ^ *in2++; + } + } +} diff --git a/miscucs.c b/utils/miscucs.c similarity index 100% rename from miscucs.c rename to utils/miscucs.c diff --git a/utils/null_lp.c b/utils/null_lp.c new file mode 100644 index 00000000..193c3392 --- /dev/null +++ b/utils/null_lp.c @@ -0,0 +1,8 @@ +/* + * Stub methods usable by LogPolicy implementations. + */ + +#include "putty.h" + +bool null_lp_verbose_no(LogPolicy *lp) { return false; } +bool null_lp_verbose_yes(LogPolicy *lp) { return true; } diff --git a/utils/nullseat.c b/utils/nullseat.c new file mode 100644 index 00000000..01aaabea --- /dev/null +++ b/utils/nullseat.c @@ -0,0 +1,42 @@ +/* + * Stub methods usable by Seat implementations. + */ + +#include "putty.h" + +size_t nullseat_output( + Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } +bool nullseat_eof(Seat *seat) { return true; } +int nullseat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input) { return 0; } +void nullseat_notify_remote_exit(Seat *seat) {} +void nullseat_connection_fatal(Seat *seat, const char *message) {} +void nullseat_update_specials_menu(Seat *seat) {} +char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } +void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} +int nullseat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +int nullseat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +int nullseat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +bool nullseat_is_never_utf8(Seat *seat) { return false; } +bool nullseat_is_always_utf8(Seat *seat) { return true; } +void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} +const char *nullseat_get_x_display(Seat *seat) { return NULL; } +bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } +bool nullseat_get_window_pixel_size( + Seat *seat, int *width, int *height) { return false; } +StripCtrlChars *nullseat_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} +bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } +bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } +bool nullseat_verbose_no(Seat *seat) { return false; } +bool nullseat_verbose_yes(Seat *seat) { return true; } +bool nullseat_interactive_no(Seat *seat) { return false; } +bool nullseat_interactive_yes(Seat *seat) { return true; } +bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } diff --git a/utils/nullstrcmp.c b/utils/nullstrcmp.c new file mode 100644 index 00000000..a9e50f40 --- /dev/null +++ b/utils/nullstrcmp.c @@ -0,0 +1,21 @@ +/* + * Compare two strings, just like strcmp, except that we tolerate null + * pointers as a legal input, and define them to compare before any + * non-null string (even the empty string). + */ + +#include + +#include "defs.h" +#include "misc.h" + +int nullstrcmp(const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return 0; + if (a == NULL) + return -1; + if (b == NULL) + return +1; + return strcmp(a, b); +} diff --git a/utils/out_of_memory.c b/utils/out_of_memory.c new file mode 100644 index 00000000..1c9cb259 --- /dev/null +++ b/utils/out_of_memory.c @@ -0,0 +1,11 @@ +/* + * Standard implementation of the out_of_memory function called by our + * malloc wrappers. + */ + +#include "putty.h" + +void out_of_memory(void) +{ + modalfatalbox("Out of memory"); +} diff --git a/utils/parse_blocksize.c b/utils/parse_blocksize.c new file mode 100644 index 00000000..3d559276 --- /dev/null +++ b/utils/parse_blocksize.c @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "defs.h" +#include "misc.h" + +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; +} diff --git a/utils/prompts.c b/utils/prompts.c new file mode 100644 index 00000000..f37bde59 --- /dev/null +++ b/utils/prompts.c @@ -0,0 +1,59 @@ +/* + * Functions for making, destroying, and manipulating prompts_t + * structures. + */ + +#include "putty.h" + +prompts_t *new_prompts(void) +{ + prompts_t *p = snew(prompts_t); + p->prompts = NULL; + p->n_prompts = p->prompts_size = 0; + 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, bool echo) +{ + prompt_t *pr = snew(prompt_t); + pr->prompt = promptstr; + pr->echo = echo; + pr->result = strbuf_new_nm(); + sgrowarray(p->prompts, p->prompts_size, p->n_prompts); + p->prompts[p->n_prompts++] = pr; +} + +void prompt_set_result(prompt_t *pr, const char *newstr) +{ + strbuf_clear(pr->result); + put_datapl(pr->result, ptrlen_from_asciz(newstr)); +} + +const char *prompt_get_result_ref(prompt_t *pr) +{ + return pr->result->s; +} + +char *prompt_get_result(prompt_t *pr) +{ + return dupstr(pr->result->s); +} + +void free_prompts(prompts_t *p) +{ + size_t i; + for (i=0; i < p->n_prompts; i++) { + prompt_t *pr = p->prompts[i]; + strbuf_free(pr->result); + sfree(pr->prompt); + sfree(pr); + } + sfree(p->prompts); + sfree(p->name); + sfree(p->instruction); + sfree(p); +} diff --git a/utils/ptrlen.c b/utils/ptrlen.c new file mode 100644 index 00000000..f2297eb5 --- /dev/null +++ b/utils/ptrlen.c @@ -0,0 +1,95 @@ +/* + * Functions to deal with ptrlens. + */ + +#include "defs.h" +#include "misc.h" +#include "ssh.h" + +bool ptrlen_eq_string(ptrlen pl, const char *str) +{ + size_t len = strlen(str); + return (pl.len == len && !memcmp(pl.ptr, str, len)); +} + +bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2) +{ + return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len)); +} + +int ptrlen_strcmp(ptrlen pl1, ptrlen pl2) +{ + size_t minlen = pl1.len < pl2.len ? pl1.len : pl2.len; + if (minlen) { /* tolerate plX.ptr==NULL as long as plX.len==0 */ + int cmp = memcmp(pl1.ptr, pl2.ptr, minlen); + if (cmp) + return cmp; + } + return pl1.len < pl2.len ? -1 : pl1.len > pl2.len ? +1 : 0; +} + +bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail) +{ + if (whole.len >= prefix.len && + !memcmp(whole.ptr, prefix.ptr, prefix.len)) { + if (tail) { + tail->ptr = (const char *)whole.ptr + prefix.len; + tail->len = whole.len - prefix.len; + } + return true; + } + return false; +} + +bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail) +{ + if (whole.len >= suffix.len && + !memcmp((char *)whole.ptr + (whole.len - suffix.len), + suffix.ptr, suffix.len)) { + if (tail) { + tail->ptr = whole.ptr; + tail->len = whole.len - suffix.len; + } + return true; + } + return false; +} + +ptrlen ptrlen_get_word(ptrlen *input, const char *separators) +{ + const char *p = input->ptr, *end = p + input->len; + ptrlen toret; + + while (p < end && strchr(separators, *p)) + p++; + toret.ptr = p; + while (p < end && !strchr(separators, *p)) + p++; + toret.len = p - (const char *)toret.ptr; + + size_t to_consume = p - (const char *)input->ptr; + assert(to_consume <= input->len); + input->ptr = (const char *)input->ptr + to_consume; + input->len -= to_consume; + + return toret; +} + +char *mkstr(ptrlen pl) +{ + char *p = snewn(pl.len + 1, char); + memcpy(p, pl.ptr, pl.len); + p[pl.len] = '\0'; + return p; +} + +bool strstartswith(const char *s, const char *t) +{ + return !strncmp(s, t, strlen(t)); +} + +bool strendswith(const char *s, const char *t) +{ + size_t slen = strlen(s), tlen = strlen(t); + return slen >= tlen && !strcmp(s + (slen - tlen), t); +} diff --git a/utils/read_file_into.c b/utils/read_file_into.c new file mode 100644 index 00000000..59569484 --- /dev/null +++ b/utils/read_file_into.c @@ -0,0 +1,19 @@ +/* + * Read an entire file into a BinarySink. + */ + +#include + +#include "defs.h" +#include "misc.h" + +bool read_file_into(BinarySink *bs, FILE *fp) +{ + char buf[4096]; + while (1) { + size_t retd = fread(buf, 1, sizeof(buf), fp); + if (retd == 0) + return !ferror(fp); + put_data(bs, buf, retd); + } +} diff --git a/utils/seat_connection_fatal.c b/utils/seat_connection_fatal.c new file mode 100644 index 00000000..9b0186b1 --- /dev/null +++ b/utils/seat_connection_fatal.c @@ -0,0 +1,20 @@ +/* + * Wrapper function for the connection_fatal() method of a Seat, + * providing printf-style formatting. + */ + +#include "putty.h" + +void seat_connection_fatal(Seat *seat, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + msg = dupvprintf(fmt, ap); + va_end(ap); + + seat->vt->connection_fatal(seat, msg); + sfree(msg); /* if we return */ +} + diff --git a/sessprep.c b/utils/sessprep.c similarity index 100% rename from sessprep.c rename to utils/sessprep.c diff --git a/utils/sk_free_peer_info.c b/utils/sk_free_peer_info.c new file mode 100644 index 00000000..a66e09d7 --- /dev/null +++ b/utils/sk_free_peer_info.c @@ -0,0 +1,14 @@ +/* + * Free a SocketPeerInfo, and everything that dangles off it. + */ + +#include "putty.h" + +void sk_free_peer_info(SocketPeerInfo *pi) +{ + if (pi) { + sfree((char *)pi->addr_text); + sfree((char *)pi->log_text); + sfree(pi); + } +} diff --git a/utils/smemclr.c b/utils/smemclr.c new file mode 100644 index 00000000..afe919d1 --- /dev/null +++ b/utils/smemclr.c @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#include "defs.h" +#include "misc.h" + +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++; + } +} diff --git a/utils/smemeq.c b/utils/smemeq.c new file mode 100644 index 00000000..2692d134 --- /dev/null +++ b/utils/smemeq.c @@ -0,0 +1,25 @@ +/* + * Compare two fixed-size regions of memory, in a crypto-safe way, + * i.e. without timing or cache side channels indicating anything + * about what the answer was or where the first difference (if any) + * might have been. + */ + +#include "defs.h" +#include "misc.h" + +bool 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; +} diff --git a/utils/ssh2_pick_fingerprint.c b/utils/ssh2_pick_fingerprint.c new file mode 100644 index 00000000..f81b2f1d --- /dev/null +++ b/utils/ssh2_pick_fingerprint.c @@ -0,0 +1,27 @@ +/* + * Choose an SSH-2 fingerprint type, out of an array of possible ones. + */ + +#include "defs.h" +#include "misc.h" +#include "ssh.h" + +FingerprintType ssh2_pick_fingerprint( + char **fingerprints, FingerprintType preferred_type) +{ + /* + * Keys are either SSH-2, in which case we have all fingerprint + * types, or SSH-1, in which case we have only MD5. So we return + * the default type if we can, or MD5 if that's all we have; no + * need for a fully general preference-list system. + */ + FingerprintType fptype = fingerprints[preferred_type] ? + preferred_type : SSH_FPTYPE_MD5; + assert(fingerprints[fptype]); + return fptype; +} + +FingerprintType ssh2_pick_default_fingerprint(char **fingerprints) +{ + return ssh2_pick_fingerprint(fingerprints, SSH_FPTYPE_DEFAULT); +} diff --git a/sshutils.c b/utils/sshutils.c similarity index 100% rename from sshutils.c rename to utils/sshutils.c diff --git a/utils/strbuf.c b/utils/strbuf.c new file mode 100644 index 00000000..8358a413 --- /dev/null +++ b/utils/strbuf.c @@ -0,0 +1,118 @@ +/* + * Functions to work with strbufs. + */ + +#include "defs.h" +#include "misc.h" +#include "utils/utils.h" + +struct strbuf_impl { + size_t size; + struct strbuf visible; + bool nm; /* true if we insist on non-moving buffer resizes */ +}; + +#define STRBUF_SET_UPTR(buf) \ + ((buf)->visible.u = (unsigned char *)(buf)->visible.s) +#define STRBUF_SET_PTR(buf, ptr) \ + ((buf)->visible.s = (ptr), STRBUF_SET_UPTR(buf)) + +void *strbuf_append(strbuf *buf_o, size_t len) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + char *toret; + sgrowarray_general( + buf->visible.s, buf->size, buf->visible.len + 1, len, buf->nm); + STRBUF_SET_UPTR(buf); + toret = buf->visible.s + buf->visible.len; + buf->visible.len += len; + buf->visible.s[buf->visible.len] = '\0'; + return toret; +} + +void strbuf_shrink_to(strbuf *buf, size_t new_len) +{ + assert(new_len <= buf->len); + buf->len = new_len; + buf->s[buf->len] = '\0'; +} + +void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove) +{ + assert(amount_to_remove <= buf->len); + buf->len -= amount_to_remove; + buf->s[buf->len] = '\0'; +} + +bool strbuf_chomp(strbuf *buf, char char_to_remove) +{ + if (buf->len > 0 && buf->s[buf->len-1] == char_to_remove) { + strbuf_shrink_by(buf, 1); + return true; + } + return false; +} + +static void strbuf_BinarySink_write( + BinarySink *bs, const void *data, size_t len) +{ + strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf); + memcpy(strbuf_append(buf_o, len), data, len); +} + +static strbuf *strbuf_new_general(bool nm) +{ + struct strbuf_impl *buf = snew(struct strbuf_impl); + BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); + buf->visible.len = 0; + buf->size = 512; + buf->nm = nm; + STRBUF_SET_PTR(buf, snewn(buf->size, char)); + *buf->visible.s = '\0'; + return &buf->visible; +} +strbuf *strbuf_new(void) { return strbuf_new_general(false); } +strbuf *strbuf_new_nm(void) { return strbuf_new_general(true); } +void strbuf_free(strbuf *buf_o) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + if (buf->visible.s) { + smemclr(buf->visible.s, buf->size); + sfree(buf->visible.s); + } + sfree(buf); +} +char *strbuf_to_str(strbuf *buf_o) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + char *ret = buf->visible.s; + sfree(buf); + return ret; +} +void strbuf_catfv(strbuf *buf_o, const char *fmt, va_list ap) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len, + &buf->size, fmt, ap)); + buf->visible.len += strlen(buf->visible.s + buf->visible.len); +} +void strbuf_catf(strbuf *buf_o, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + strbuf_catfv(buf_o, fmt, ap); + va_end(ap); +} + +strbuf *strbuf_new_for_agent_query(void) +{ + strbuf *buf = strbuf_new(); + strbuf_append(buf, 4); + return buf; +} +void strbuf_finalise_agent_query(strbuf *buf_o) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + assert(buf->visible.len >= 5); + PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4); +} diff --git a/utils/string_length_for_printf.c b/utils/string_length_for_printf.c new file mode 100644 index 00000000..8b455316 --- /dev/null +++ b/utils/string_length_for_printf.c @@ -0,0 +1,21 @@ +/* + * Convert a size_t value to int, by saturating it at INT_MAX. Useful + * if you want to use the printf idiom "%.*s", where the '*' precision + * specifier expects an int in the variadic argument list, but what + * you have is not an int but a size_t. This method of converting to + * int will at least do something _safe_ with overlong values, even if + * (due to the limitation of printf itself) the whole string still + * won't be printed. + */ + +#include + +#include "defs.h" +#include "misc.h" + +int string_length_for_printf(size_t s) +{ + if (s > INT_MAX) + return INT_MAX; + return s; +} diff --git a/stripctrl.c b/utils/stripctrl.c similarity index 100% rename from stripctrl.c rename to utils/stripctrl.c diff --git a/tree234.c b/utils/tree234.c similarity index 100% rename from tree234.c rename to utils/tree234.c diff --git a/utils/utils.h b/utils/utils.h new file mode 100644 index 00000000..ea4cda0c --- /dev/null +++ b/utils/utils.h @@ -0,0 +1,12 @@ +/* + * Internal header to the utils subdirectory, for definitions shared + * between the library implementations but not intended to be exposed + * further than that. + */ + +void dputs(const char *); + +char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, + const char *fmt, va_list ap); + +const char *host_strchr_internal(const char *s, const char *set, bool first); diff --git a/utils/validate_manual_hostkey.c b/utils/validate_manual_hostkey.c new file mode 100644 index 00000000..386e1039 --- /dev/null +++ b/utils/validate_manual_hostkey.c @@ -0,0 +1,116 @@ +/* + * 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. + */ + +#include +#include + +#include "putty.h" +#include "misc.h" + +#define BASE64_CHARS_NOEQ \ + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" +#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "=" + +bool 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 (strstartswith(q, "SHA256:")) { + /* Test for a valid SHA256 key fingerprint. */ + r = q + 7; + if (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 0) + return true; + } + + r = q; + if (strstartswith(r, "MD5:")) + r += 4; + if (strlen(r) == 16*3 - 1 && + r[strspn(r, "0123456789abcdefABCDEF:")] == 0) { + /* + * Test for a valid MD5 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 (r[3*i] == ':' || r[3*i+1] == ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 15; i++) + if (r[3*i+2] != ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 16*3 - 1; i++) + key[i] = tolower(r[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, BASE64_CHARS_ALL)] == 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; +} diff --git a/version.c b/utils/version.c similarity index 100% rename from version.c rename to utils/version.c diff --git a/wcwidth.c b/utils/wcwidth.c similarity index 100% rename from wcwidth.c rename to utils/wcwidth.c diff --git a/wildcard.c b/utils/wildcard.c similarity index 100% rename from wildcard.c rename to utils/wildcard.c diff --git a/utils/write_c_string_literal.c b/utils/write_c_string_literal.c new file mode 100644 index 00000000..6415c287 --- /dev/null +++ b/utils/write_c_string_literal.c @@ -0,0 +1,31 @@ +/* + * Write data to a file in the form of a C string literal, with any + * non-printable-ASCII character escaped appropriately. + */ + +#include "defs.h" +#include "misc.h" + +void write_c_string_literal(FILE *fp, ptrlen str) +{ + for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) { + char c = *p; + + if (c == '\n') + fputs("\\n", fp); + else if (c == '\r') + fputs("\\r", fp); + else if (c == '\t') + fputs("\\t", fp); + else if (c == '\b') + fputs("\\b", fp); + else if (c == '\\') + fputs("\\\\", fp); + else if (c == '"') + fputs("\\\"", fp); + else if (c >= 32 && c <= 126) + fputc(c, fp); + else + fprintf(fp, "\\%03o", (unsigned char)c); + } +} diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 4db250f8..ff6805f0 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,8 +1,38 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_platform_sources_to_library(utils - wincapi.c winutils.c winucs.c winmisc.c winmiscs.c wintime.c windefs.c - winsecur.c) + utils/arm_arch_queries.c + utils/capi.c + utils/defaults.c + utils/dll_hijacking_protection.c + utils/dputs.c + utils/escape_registry_key.c + utils/filename.c + utils/fontspec.c + utils/getdlgitemtext_alloc.c + utils/get_username.c + utils/is_console_handle.c + utils/load_system32_dll.c + utils/ltime.c + utils/makedlgitemborderless.c + utils/message_box.c + utils/minefield.c + utils/open_for_write_would_lose_data.c + utils/pgp_fingerprints_msgbox.c + utils/platform_get_x_display.c + utils/registry_get_string.c + utils/request_file.c + utils/security.c + utils/split_into_argv.c + utils/version.c + utils/win_strerror.c + winucs.c) +if(NOT HAVE_SECUREZEROMEMORY) + add_platform_sources_to_library(utils ../utils/smemclr.c) +endif() +if(NOT HAVE_STRTOUMAX) + add_platform_sources_to_library(utils utils/strtoumax.c) +endif() add_platform_sources_to_library(eventloop wincliloop.c winhandl.c) add_platform_sources_to_library(console diff --git a/windows/utils/arm_arch_queries.c b/windows/utils/arm_arch_queries.c new file mode 100644 index 00000000..05132b14 --- /dev/null +++ b/windows/utils/arm_arch_queries.c @@ -0,0 +1,39 @@ +/* + * Windows implementation of the OS query functions that detect Arm + * architecture extensions. + */ + +#include "putty.h" + +#if !(defined _M_ARM || defined _M_ARM64) +/* + * For non-Arm, stub out these functions. This module shouldn't be + * _called_ in that situation anyway, but it will still be compiled + * (because that's easier than getting CMake to identify the + * architecture in all cases). + */ +#define IsProcessorFeaturePresent(...) false +#endif + +bool platform_aes_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha256_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha1_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha512_hw_available(void) +{ + /* As of 2020-12-24, as far as I can tell from docs.microsoft.com, + * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the + * SHA-512 architecture extension. */ + return false; +} diff --git a/windows/wincapi.c b/windows/utils/capi.c similarity index 97% rename from windows/wincapi.c rename to windows/utils/capi.c index d90a634f..3d38ae04 100644 --- a/windows/wincapi.c +++ b/windows/utils/capi.c @@ -1,5 +1,5 @@ /* - * wincapi.c: implementation of wincapi.h. + * windows/utils/capi.c: implementation of wincapi.h. */ #include "putty.h" diff --git a/windows/windefs.c b/windows/utils/defaults.c similarity index 90% rename from windows/windefs.c rename to windows/utils/defaults.c index 006e8dc5..1a270009 100644 --- a/windows/windefs.c +++ b/windows/utils/defaults.c @@ -1,5 +1,5 @@ /* - * windefs.c: default settings that are specific to Windows. + * windows/utils/defaults.c: default settings that are specific to Windows. */ #include "putty.h" diff --git a/windows/utils/dll_hijacking_protection.c b/windows/utils/dll_hijacking_protection.c new file mode 100644 index 00000000..fe9ae59c --- /dev/null +++ b/windows/utils/dll_hijacking_protection.c @@ -0,0 +1,43 @@ +/* + * If the OS provides it, call SetDefaultDllDirectories() to prevent + * DLLs from being loaded from the directory containing our own + * binary, and instead only load from system32. + * + * This is a protection against hijacking attacks, if someone runs + * PuTTY directly from their web browser's download directory having + * previously been enticed into clicking on an unwise link that + * downloaded a malicious DLL to the same directory under one of + * various magic names that seem to be things that standard Windows + * DLLs delegate to. + * + * It shouldn't break deliberate loading of user-provided DLLs such as + * GSSAPI providers, because those are specified by their full + * pathname by the user-provided configuration. + */ + +#include "putty.h" + +void dll_hijacking_protection(void) +{ + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); +#if !HAVE_SETDEFAULTDLLDIRECTORIES + /* For older Visual Studio, this function isn't available in + * the header files to type-check */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK( + kernel32_module, SetDefaultDllDirectories); +#else + GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories); +#endif + } + + if (p_SetDefaultDllDirectories) { + /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified + * directories only */ + p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_USER_DIRS); + } +} diff --git a/windows/utils/dputs.c b/windows/utils/dputs.c new file mode 100644 index 00000000..15b0e4db --- /dev/null +++ b/windows/utils/dputs.c @@ -0,0 +1,37 @@ +/* + * Implementation of dputs() for Windows. + * + * The debug messages are written to STD_OUTPUT_HANDLE, except that + * first it has to make sure that handle _exists_, by calling + * AllocConsole first if necessary. + * + * They also go into a file called debug.log. + */ + +#include "putty.h" +#include "utils/utils.h" + +static FILE *debug_fp = NULL; +static HANDLE debug_hdl = INVALID_HANDLE_VALUE; +static int debug_got_console = 0; + +void dputs(const 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); +} diff --git a/windows/utils/escape_registry_key.c b/windows/utils/escape_registry_key.c new file mode 100644 index 00000000..f5c9c19e --- /dev/null +++ b/windows/utils/escape_registry_key.c @@ -0,0 +1,48 @@ +/* + * Escaping/unescaping functions to translate between a saved session + * name, and the key name used to represent it in the Registry area + * where we store saved sessions. + * + * The basic technique is to %-escape characters we can't use in + * Registry keys. + */ + +#include "putty.h" + +void escape_registry_key(const char *in, strbuf *out) +{ + bool candot = false; + static const char hex[16] = "0123456789ABCDEF"; + + while (*in) { + if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || + *in == '%' || *in < ' ' || *in > '~' || (*in == '.' + && !candot)) { + put_byte(out, '%'); + put_byte(out, hex[((unsigned char) *in) >> 4]); + put_byte(out, hex[((unsigned char) *in) & 15]); + } else + put_byte(out, *in); + in++; + candot = true; + } +} + +void unescape_registry_key(const char *in, strbuf *out) +{ + while (*in) { + if (*in == '%' && in[1] && in[2]) { + int i, j; + + i = in[1] - '0'; + i -= (i > 9 ? 7 : 0); + j = in[2] - '0'; + j -= (j > 9 ? 7 : 0); + + put_byte(out, (i << 4) + j); + in += 3; + } else { + put_byte(out, *in++); + } + } +} diff --git a/windows/utils/filename.c b/windows/utils/filename.c new file mode 100644 index 00000000..f4457ecb --- /dev/null +++ b/windows/utils/filename.c @@ -0,0 +1,54 @@ +/* + * Implementation of Filename for Windows. + */ + +#include "putty.h" + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); + return ret; +} + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +bool filename_equal(const Filename *f1, const Filename *f2) +{ + return !strcmp(f1->path, f2->path); +} + +bool filename_is_null(const Filename *fn) +{ + return !*fn->path; +} + +void filename_free(Filename *fn) +{ + sfree(fn->path); + sfree(fn); +} + +void filename_serialise(BinarySink *bs, const Filename *f) +{ + put_asciz(bs, f->path); +} +Filename *filename_deserialise(BinarySource *src) +{ + return filename_from_str(get_asciz(src)); +} + +char filename_char_sanitise(char c) +{ + if (strchr("<>:\"/\\|?*", c)) + return '.'; + return c; +} diff --git a/windows/utils/fontspec.c b/windows/utils/fontspec.c new file mode 100644 index 00000000..7e8d5175 --- /dev/null +++ b/windows/utils/fontspec.c @@ -0,0 +1,43 @@ +/* + * Implementation of FontSpec for Windows. + */ + +#include "putty.h" + +FontSpec *fontspec_new(const char *name, bool bold, int height, int charset) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + f->isbold = bold; + f->height = height; + f->charset = charset; + return f; +} + +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name, f->isbold, f->height, f->charset); +} + +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} + +void fontspec_serialise(BinarySink *bs, FontSpec *f) +{ + put_asciz(bs, f->name); + put_uint32(bs, f->isbold); + put_uint32(bs, f->height); + put_uint32(bs, f->charset); +} + +FontSpec *fontspec_deserialise(BinarySource *src) +{ + const char *name = get_asciz(src); + unsigned isbold = get_uint32(src); + unsigned height = get_uint32(src); + unsigned charset = get_uint32(src); + return fontspec_new(name, isbold, height, charset); +} diff --git a/windows/utils/get_username.c b/windows/utils/get_username.c new file mode 100644 index 00000000..bc5780e4 --- /dev/null +++ b/windows/utils/get_username.c @@ -0,0 +1,77 @@ +/* + * Implementation of get_username() for Windows. + */ + +#include "putty.h" + +#ifndef SECURITY_WIN32 +#define SECURITY_WIN32 +#endif +#include + +char *get_username(void) +{ + DWORD namelen; + char *user; + bool got_username = false; + DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, + (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); + + { + static bool tried_usernameex = false; + if (!tried_usernameex) { + /* Not available on Win9x, so load dynamically */ + HMODULE secur32 = load_system32_dll("secur32.dll"); + /* If MIT Kerberos is installed, the following call to + GET_WINDOWS_FUNCTION makes Windows implicitly load + sspicli.dll WITHOUT proper path sanitizing, so better + load it properly before */ + HMODULE sspicli = load_system32_dll("sspicli.dll"); + (void)sspicli; /* squash compiler warning about unused variable */ + GET_WINDOWS_FUNCTION(secur32, GetUserNameExA); + tried_usernameex = true; + } + } + + if (p_GetUserNameExA) { + /* + * If available, use the principal -- this avoids the problem + * that the local username is case-insensitive but Kerberos + * usernames are case-sensitive. + */ + + /* Get the length */ + namelen = 0; + (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen); + + user = snewn(namelen, char); + got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen); + if (got_username) { + char *p = strchr(user, '@'); + if (p) *p = 0; + } else { + sfree(user); + } + } + + if (!got_username) { + /* Fall back to local user name */ + namelen = 0; + if (!GetUserName(NULL, &namelen)) { + /* + * Apparently this doesn't work at least on Windows XP SP2. + * Thus assume a maximum of 256. It will fail again if it + * doesn't fit. + */ + namelen = 256; + } + + user = snewn(namelen, char); + got_username = GetUserName(user, &namelen); + if (!got_username) { + sfree(user); + } + } + + return got_username ? user : NULL; +} diff --git a/windows/utils/getdlgitemtext_alloc.c b/windows/utils/getdlgitemtext_alloc.c new file mode 100644 index 00000000..f0244d71 --- /dev/null +++ b/windows/utils/getdlgitemtext_alloc.c @@ -0,0 +1,20 @@ +/* + * Handy wrapper around GetDlgItemText which doesn't make you invent + * an arbitrary length limit on the output string. Returned string is + * dynamically allocated; caller must free. + */ + +#include "putty.h" + +char *GetDlgItemText_alloc(HWND hwnd, int id) +{ + char *ret = NULL; + size_t size = 0; + + do { + sgrowarray_nm(ret, size, size); + GetDlgItemText(hwnd, id, ret, size); + } while (!memchr(ret, '\0', size-1)); + + return ret; +} diff --git a/windows/utils/is_console_handle.c b/windows/utils/is_console_handle.c new file mode 100644 index 00000000..887069c9 --- /dev/null +++ b/windows/utils/is_console_handle.c @@ -0,0 +1,13 @@ +/* + * Determine whether a Windows HANDLE points at a console device. + */ + +#include "putty.h" + +bool is_console_handle(HANDLE handle) +{ + DWORD ignored_output; + if (GetConsoleMode(handle, &ignored_output)) + return true; + return false; +} diff --git a/windows/utils/load_system32_dll.c b/windows/utils/load_system32_dll.c new file mode 100644 index 00000000..e00d7c34 --- /dev/null +++ b/windows/utils/load_system32_dll.c @@ -0,0 +1,26 @@ +/* + * Wrapper function to load a DLL out of c:\windows\system32 without + * going through the full DLL search path. (Hence no attack is + * possible by placing a substitute DLL earlier on that path.) + */ + +#include "putty.h" + +HMODULE load_system32_dll(const char *libname) +{ + static char *sysdir = NULL; + static size_t sysdirsize = 0; + char *fullpath; + HMODULE ret; + + if (!sysdir) { + size_t len; + while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) + sgrowarray(sysdir, sysdirsize, len); + } + + fullpath = dupcat(sysdir, "\\", libname); + ret = LoadLibrary(fullpath); + sfree(fullpath); + return ret; +} diff --git a/windows/wintime.c b/windows/utils/ltime.c similarity index 84% rename from windows/wintime.c rename to windows/utils/ltime.c index 5fa3b0de..d4364509 100644 --- a/windows/wintime.c +++ b/windows/utils/ltime.c @@ -1,5 +1,6 @@ /* - * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows. + * Implementation of ltime() that avoids trouble with time() returning + * (time_t)-1 on Windows. */ #include "putty.h" diff --git a/windows/utils/makedlgitemborderless.c b/windows/utils/makedlgitemborderless.c new file mode 100644 index 00000000..53975d06 --- /dev/null +++ b/windows/utils/makedlgitemborderless.c @@ -0,0 +1,19 @@ +/* + * Helper function to remove the border around a dialog item such as + * a read-only edit control. + */ + +#include "putty.h" + +void MakeDlgItemBorderless(HWND parent, int id) +{ + HWND child = GetDlgItem(parent, id); + LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); + LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE); + style &= ~WS_BORDER; + exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE); + SetWindowLongPtr(child, GWL_STYLE, style); + SetWindowLongPtr(child, GWL_EXSTYLE, exstyle); + SetWindowPos(child, NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); +} diff --git a/windows/utils/message_box.c b/windows/utils/message_box.c new file mode 100644 index 00000000..ae78de4a --- /dev/null +++ b/windows/utils/message_box.c @@ -0,0 +1,49 @@ +/* + * Message box with optional context help. + */ + +#include "putty.h" + +static HWND message_box_owner; + +/* Callback function to launch context help. */ +static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) +{ + const char *context = NULL; +#define CHECK_CTX(name) \ + do { \ + if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \ + context = WINHELP_CTX_ ## name; \ + } while (0) + CHECK_CTX(errors_hostkey_absent); + CHECK_CTX(errors_hostkey_changed); + CHECK_CTX(errors_cantloadkey); + CHECK_CTX(option_cleanup); + CHECK_CTX(pgp_fingerprints); +#undef CHECK_CTX + if (context) + launch_help(message_box_owner, context); +} + +int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, + DWORD style, DWORD helpctxid) +{ + MSGBOXPARAMS mbox; + + /* + * We use MessageBoxIndirect() because it allows us to specify a + * callback function for the Help button. + */ + mbox.cbSize = sizeof(mbox); + /* Assumes the globals `hinst' and `hwnd' have sensible values. */ + mbox.hInstance = hinst; + mbox.hwndOwner = message_box_owner = owner; + mbox.lpfnMsgBoxCallback = &message_box_help_callback; + mbox.dwLanguageId = LANG_NEUTRAL; + mbox.lpszText = text; + mbox.lpszCaption = caption; + mbox.dwContextHelpId = helpctxid; + mbox.dwStyle = style; + if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP; + return MessageBoxIndirect(&mbox); +} diff --git a/windows/winmiscs.c b/windows/utils/minefield.c similarity index 78% rename from windows/winmiscs.c rename to windows/utils/minefield.c index 335711b1..de4d4836 100644 --- a/windows/winmiscs.c +++ b/windows/utils/minefield.c @@ -1,26 +1,17 @@ /* - * winmiscs.c: Windows-specific standalone functions. Has the same - * relationship to winmisc.c that utils.c does to misc.c, but the - * corresponding name 'winutils.c' was already taken. + * 'Minefield' - a crude Windows memory debugger, similar in concept + * to the old Unix 'Electric Fence'. The main difference is that + * Electric Fence can be imposed on a program from outside, via + * LD_PRELOAD, whereas this has to be included in the program at + * compile time with its own cooperation. + * + * This module provides the Minefield allocator. Actually enabling it + * is done by a #define in force when the main utils/memory.c is + * compiled. */ #include "putty.h" -#ifndef NO_SECUREZEROMEMORY -/* - * Windows implementation of smemclr (see misc.c) using SecureZeroMemory. - */ -void smemclr(void *b, size_t n) { - if (b && n > 0) - SecureZeroMemory(b, n); -} -#endif - -#ifdef MINEFIELD -/* - * Minefield - a Windows equivalent for Electric Fence - */ - #define PAGESIZE 4096 /* @@ -234,52 +225,3 @@ void *minefield_c_realloc(void *p, size_t size) minefield_free(p); return q; } - -#endif /* MINEFIELD */ - -#if !HAVE_STRTOUMAX - -/* - * Work around lack of strtoumax in older MSVC libraries - */ -uintmax_t strtoumax(const char *nptr, char **endptr, int base) -{ - return _strtoui64(nptr, endptr, base); -} - -#endif - -#if defined _M_ARM || defined _M_ARM64 - -bool platform_aes_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha256_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha1_hw_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha512_hw_available(void) -{ - /* As of 2020-12-24, as far as I can tell from docs.microsoft.com, - * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the - * SHA-512 architecture extension. */ - return false; -} - -#endif - -bool is_console_handle(HANDLE handle) -{ - DWORD ignored_output; - if (GetConsoleMode(handle, &ignored_output)) - return true; - return false; -} diff --git a/windows/utils/open_for_write_would_lose_data.c b/windows/utils/open_for_write_would_lose_data.c new file mode 100644 index 00000000..3891f51e --- /dev/null +++ b/windows/utils/open_for_write_would_lose_data.c @@ -0,0 +1,38 @@ +/* + * Implementation of open_for_write_would_lose_data for Windows. + */ + +#include "putty.h" + +bool open_for_write_would_lose_data(const Filename *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!GetFileAttributesEx(fn->path, GetFileExInfoStandard, &attrs)) { + /* + * Generally, if we don't identify a specific reason why we + * should return true from this function, we return false, and + * let the subsequent attempt to open the file for real give a + * more useful error message. + */ + return false; + } + if (attrs.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE | + FILE_ATTRIBUTE_DIRECTORY)) { + /* + * File is something other than an ordinary disk file, so + * opening it for writing will not cause truncation. (It may + * not _succeed_ either, but that's not our problem here!) + */ + return false; + } + if (attrs.nFileSizeHigh == 0 && attrs.nFileSizeLow == 0) { + /* + * File is zero-length (or may be a named pipe, which + * dwFileAttributes can't tell apart from a regular file), so + * opening it for writing won't truncate any data away because + * there's nothing to truncate anyway. + */ + return false; + } + return true; +} diff --git a/windows/utils/pgp_fingerprints_msgbox.c b/windows/utils/pgp_fingerprints_msgbox.c new file mode 100644 index 00000000..6618de82 --- /dev/null +++ b/windows/utils/pgp_fingerprints_msgbox.c @@ -0,0 +1,25 @@ +/* + * Display the fingerprints of the PGP Master Keys to the user as a + * GUI message box. + */ + +#include "putty.h" + +void pgp_fingerprints_msgbox(HWND owner) +{ + message_box( + owner, + "These are the fingerprints of the PuTTY PGP Master Keys. They can\n" + "be used to establish a trust path from this executable to another\n" + "one. See the manual for more information.\n" + "(Note: these fingerprints have nothing to do with SSH!)\n" + "\n" + "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR + ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" + " " PGP_PREV_MASTER_KEY_FP, + "PGP fingerprints", MB_ICONINFORMATION | MB_OK, + HELPCTXID(pgp_fingerprints)); +} diff --git a/windows/utils/platform_get_x_display.c b/windows/utils/platform_get_x_display.c new file mode 100644 index 00000000..b3d11afc --- /dev/null +++ b/windows/utils/platform_get_x_display.c @@ -0,0 +1,12 @@ +/* + * Implementation of platform_get_x_display for Windows, common to all + * tools. + */ + +#include "putty.h" + +char *platform_get_x_display(void) +{ + /* We may as well check for DISPLAY in case it's useful. */ + return dupstr(getenv("DISPLAY")); +} diff --git a/windows/utils/registry_get_string.c b/windows/utils/registry_get_string.c new file mode 100644 index 00000000..c3745b92 --- /dev/null +++ b/windows/utils/registry_get_string.c @@ -0,0 +1,43 @@ +/* + * Self-contained function to try to fetch a single string value from + * the Registry, and return it as a dynamically allocated C string. + */ + +#include "putty.h" + +char *registry_get_string(HKEY root, const char *path, const char *leaf) +{ + HKEY key = root; + bool need_close_key = false; + char *toret = NULL, *str = NULL; + + if (path) { + if (RegCreateKey(key, path, &key) != ERROR_SUCCESS) + goto out; + need_close_key = true; + } + + DWORD type, size; + if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS) + goto out; + if (type != REG_SZ) + goto out; + + str = snewn(size + 1, char); + DWORD size_got = size; + if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str, + &size_got) != ERROR_SUCCESS) + goto out; + if (type != REG_SZ || size_got > size) + goto out; + str[size_got] = '\0'; + + toret = str; + str = NULL; + + out: + if (need_close_key) + RegCloseKey(key); + sfree(str); + return toret; +} diff --git a/windows/utils/request_file.c b/windows/utils/request_file.c new file mode 100644 index 00000000..dd2cab18 --- /dev/null +++ b/windows/utils/request_file.c @@ -0,0 +1,71 @@ +/* + * GetOpenFileName/GetSaveFileName tend to muck around with the process' + * working directory on at least some versions of Windows. + * Here's a wrapper that gives more control over this, and hides a little + * bit of other grottiness. + */ + +#include "putty.h" + +struct filereq_tag { + TCHAR cwd[MAX_PATH]; +}; + +/* + * `of' is expected to be initialised with most interesting fields, but + * this function does some administrivia. (assume `of' was memset to 0) + * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName + * `state' is optional. + */ +bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) +{ + TCHAR cwd[MAX_PATH]; /* process CWD */ + bool ret; + + /* Get process CWD */ + if (preserve) { + DWORD r = GetCurrentDirectory(lenof(cwd), cwd); + if (r == 0 || r >= lenof(cwd)) + /* Didn't work, oh well. Stop trying to be clever. */ + preserve = false; + } + + /* Open the file requester, maybe setting lpstrInitialDir */ + { +#ifdef OPENFILENAME_SIZE_VERSION_400 + of->lStructSize = OPENFILENAME_SIZE_VERSION_400; +#else + of->lStructSize = sizeof(*of); +#endif + of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL; + /* Actually put up the requester. */ + ret = save ? GetSaveFileName(of) : GetOpenFileName(of); + } + + /* Get CWD left by requester */ + if (state) { + DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd); + if (r == 0 || r >= lenof(state->cwd)) + /* Didn't work, oh well. */ + state->cwd[0] = '\0'; + } + + /* Restore process CWD */ + if (preserve) + /* If it fails, there's not much we can do. */ + (void) SetCurrentDirectory(cwd); + + return ret; +} + +filereq *filereq_new(void) +{ + filereq *ret = snew(filereq); + ret->cwd[0] = '\0'; + return ret; +} + +void filereq_free(filereq *state) +{ + sfree(state); +} diff --git a/windows/winsecur.c b/windows/utils/security.c similarity index 99% rename from windows/winsecur.c rename to windows/utils/security.c index c5251c31..69ac96f1 100644 --- a/windows/winsecur.c +++ b/windows/utils/security.c @@ -1,5 +1,5 @@ /* - * winsecur.c: implementation of winsecur.h. + * windows/utils/security.c: implementation of winsecur.h. */ #include diff --git a/windows/winutils.c b/windows/utils/split_into_argv.c similarity index 77% rename from windows/winutils.c rename to windows/utils/split_into_argv.c index 973c3be0..d1957b48 100644 --- a/windows/winutils.c +++ b/windows/utils/split_into_argv.c @@ -1,197 +1,3 @@ -/* - * winutils.c: miscellaneous Windows utilities for GUI apps - */ - -#include -#include -#include - -#include "putty.h" -#include "misc.h" - -#ifdef TESTMODE -/* Definitions to allow this module to be compiled standalone for testing - * split_into_argv(). */ -#define smalloc malloc -#define srealloc realloc -#define sfree free -#endif - -/* - * GetOpenFileName/GetSaveFileName tend to muck around with the process' - * working directory on at least some versions of Windows. - * Here's a wrapper that gives more control over this, and hides a little - * bit of other grottiness. - */ - -struct filereq_tag { - TCHAR cwd[MAX_PATH]; -}; - -/* - * `of' is expected to be initialised with most interesting fields, but - * this function does some administrivia. (assume `of' was memset to 0) - * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName - * `state' is optional. - */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) -{ - TCHAR cwd[MAX_PATH]; /* process CWD */ - bool ret; - - /* Get process CWD */ - if (preserve) { - DWORD r = GetCurrentDirectory(lenof(cwd), cwd); - if (r == 0 || r >= lenof(cwd)) - /* Didn't work, oh well. Stop trying to be clever. */ - preserve = false; - } - - /* Open the file requester, maybe setting lpstrInitialDir */ - { -#ifdef OPENFILENAME_SIZE_VERSION_400 - of->lStructSize = OPENFILENAME_SIZE_VERSION_400; -#else - of->lStructSize = sizeof(*of); -#endif - of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL; - /* Actually put up the requester. */ - ret = save ? GetSaveFileName(of) : GetOpenFileName(of); - } - - /* Get CWD left by requester */ - if (state) { - DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd); - if (r == 0 || r >= lenof(state->cwd)) - /* Didn't work, oh well. */ - state->cwd[0] = '\0'; - } - - /* Restore process CWD */ - if (preserve) - /* If it fails, there's not much we can do. */ - (void) SetCurrentDirectory(cwd); - - return ret; -} - -filereq *filereq_new(void) -{ - filereq *ret = snew(filereq); - ret->cwd[0] = '\0'; - return ret; -} - -void filereq_free(filereq *state) -{ - sfree(state); -} - -/* - * Message box with optional context help. - */ - -static HWND message_box_owner; - -/* Callback function to launch context help. */ -static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) -{ - const char *context = NULL; -#define CHECK_CTX(name) \ - do { \ - if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \ - context = WINHELP_CTX_ ## name; \ - } while (0) - CHECK_CTX(errors_hostkey_absent); - CHECK_CTX(errors_hostkey_changed); - CHECK_CTX(errors_cantloadkey); - CHECK_CTX(option_cleanup); - CHECK_CTX(pgp_fingerprints); -#undef CHECK_CTX - if (context) - launch_help(message_box_owner, context); -} - -int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, - DWORD style, DWORD helpctxid) -{ - MSGBOXPARAMS mbox; - - /* - * We use MessageBoxIndirect() because it allows us to specify a - * callback function for the Help button. - */ - mbox.cbSize = sizeof(mbox); - /* Assumes the globals `hinst' and `hwnd' have sensible values. */ - mbox.hInstance = hinst; - mbox.hwndOwner = message_box_owner = owner; - mbox.lpfnMsgBoxCallback = &message_box_help_callback; - mbox.dwLanguageId = LANG_NEUTRAL; - mbox.lpszText = text; - mbox.lpszCaption = caption; - mbox.dwContextHelpId = helpctxid; - mbox.dwStyle = style; - if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP; - return MessageBoxIndirect(&mbox); -} - -/* - * Display the fingerprints of the PGP Master Keys to the user. - */ -void pgp_fingerprints_msgbox(HWND owner) -{ - message_box( - owner, - "These are the fingerprints of the PuTTY PGP Master Keys. They can\n" - "be used to establish a trust path from this executable to another\n" - "one. See the manual for more information.\n" - "(Note: these fingerprints have nothing to do with SSH!)\n" - "\n" - "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP, - "PGP fingerprints", MB_ICONINFORMATION | MB_OK, - HELPCTXID(pgp_fingerprints)); -} - -/* - * Helper function to remove the border around a dialog item such as - * a read-only edit control. - */ -void MakeDlgItemBorderless(HWND parent, int id) -{ - HWND child = GetDlgItem(parent, id); - LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); - LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE); - style &= ~WS_BORDER; - exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE); - SetWindowLongPtr(child, GWL_STYLE, style); - SetWindowLongPtr(child, GWL_EXSTYLE, exstyle); - SetWindowPos(child, NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); -} - -/* - * Handy wrapper around GetDlgItemText which doesn't make you invent - * an arbitrary length limit on the output string. Returned string is - * dynamically allocated; caller must free. - */ -char *GetDlgItemText_alloc(HWND hwnd, int id) -{ - char *ret = NULL; - size_t size = 0; - - do { - sgrowarray_nm(ret, size, size); - GetDlgItemText(hwnd, id, ret, size); - } while (!memchr(ret, '\0', size-1)); - - return ret; -} - /* * Split a complete command line into argc/argv, attempting to do it * exactly the same way the Visual Studio C library would do it (so @@ -210,6 +16,9 @@ char *GetDlgItemText_alloc(HWND hwnd, int id) * treat the rest as a raw string, you can. If you don't want to, * `argstart' can be safely left NULL. */ + +#include "putty.h" + void split_into_argv(char *cmdline, int *argc, char ***argv, char ***argstart) { diff --git a/windows/utils/strtoumax.c b/windows/utils/strtoumax.c new file mode 100644 index 00000000..38d00014 --- /dev/null +++ b/windows/utils/strtoumax.c @@ -0,0 +1,12 @@ +/* + * Work around lack of strtoumax in older MSVC libraries. + */ + +#include + +#include "defs.h" + +uintmax_t strtoumax(const char *nptr, char **endptr, int base) +{ + return _strtoui64(nptr, endptr, base); +} diff --git a/windows/utils/version.c b/windows/utils/version.c new file mode 100644 index 00000000..a53c2ad3 --- /dev/null +++ b/windows/utils/version.c @@ -0,0 +1,40 @@ +#include "putty.h" + +DWORD osMajorVersion, osMinorVersion, osPlatformId; + +void init_winver(void) +{ + OSVERSIONINFO osVersion; + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + /* Deliberately don't type-check this function, because that + * would involve using its declaration in a header file which + * triggers a deprecation warning. I know it's deprecated (see + * below) and don't need telling. */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA); + } + + ZeroMemory(&osVersion, sizeof(osVersion)); + osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if (p_GetVersionExA && p_GetVersionExA(&osVersion)) { + osMajorVersion = osVersion.dwMajorVersion; + osMinorVersion = osVersion.dwMinorVersion; + osPlatformId = osVersion.dwPlatformId; + } else { + /* + * GetVersionEx is deprecated, so allow for it perhaps going + * away in future API versions. If it's not there, simply + * assume that's because Windows is too _new_, so fill in the + * variables we care about to a value that will always compare + * higher than any given test threshold. + * + * Normally we should be checking against the presence of a + * specific function if possible in any case. + */ + osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */ + osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */ + } +} diff --git a/windows/utils/win_strerror.c b/windows/utils/win_strerror.c new file mode 100644 index 00000000..5572bc8b --- /dev/null +++ b/windows/utils/win_strerror.c @@ -0,0 +1,72 @@ +/* + * Wrapper around the Windows FormatMessage system for retrieving the + * text of a system error code, with a simple API similar to strerror. + * + * Works by keeping a tree234 containing mappings from system error + * codes to strings. Entries allocated in this tree are simply never + * freed. + * + * Also, the returned string has its trailing newline removed (so it + * can go in places like the Event Log that never want a newline), and + * is prefixed with the error number (so that if a user sends an error + * report containing a translated error message we can't read, we can + * still find out what the error actually was). + */ + +#include "putty.h" + +struct errstring { + int error; + char *text; +}; + +static int errstring_find(void *av, void *bv) +{ + int *a = (int *)av; + struct errstring *b = (struct errstring *)bv; + if (*a < b->error) + return -1; + if (*a > b->error) + return +1; + return 0; +} +static int errstring_compare(void *av, void *bv) +{ + struct errstring *a = (struct errstring *)av; + return errstring_find(&a->error, bv); +} + +static tree234 *errstrings = NULL; + +const char *win_strerror(int error) +{ + struct errstring *es; + + if (!errstrings) + errstrings = newtree234(errstring_compare); + + es = find234(errstrings, &error, errstring_find); + + if (!es) { + char msgtext[65536]; /* maximum size for FormatMessage is 64K */ + + es = snew(struct errstring); + es->error = error; + if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgtext, lenof(msgtext)-1, NULL)) { + sprintf(msgtext, + "(unable to format: FormatMessage returned %u)", + (unsigned int)GetLastError()); + } else { + int len = strlen(msgtext); + if (len > 0 && msgtext[len-1] == '\n') + msgtext[len-1] = '\0'; + } + es->text = dupprintf("Error %d: %s", error, msgtext); + add234(errstrings, es); + } + + return es->text; +} diff --git a/windows/winmisc.c b/windows/winmisc.c deleted file mode 100644 index 4fa55398..00000000 --- a/windows/winmisc.c +++ /dev/null @@ -1,467 +0,0 @@ -/* - * winmisc.c: miscellaneous Windows-specific things - */ - -#include -#include -#include -#include "putty.h" -#ifndef SECURITY_WIN32 -#define SECURITY_WIN32 -#endif -#include - -DWORD osMajorVersion, osMinorVersion, osPlatformId; - -char *platform_get_x_display(void) { - /* We may as well check for DISPLAY in case it's useful. */ - return dupstr(getenv("DISPLAY")); -} - -Filename *filename_from_str(const char *str) -{ - Filename *ret = snew(Filename); - ret->path = dupstr(str); - return ret; -} - -Filename *filename_copy(const Filename *fn) -{ - return filename_from_str(fn->path); -} - -const char *filename_to_str(const Filename *fn) -{ - return fn->path; -} - -bool filename_equal(const Filename *f1, const Filename *f2) -{ - return !strcmp(f1->path, f2->path); -} - -bool filename_is_null(const Filename *fn) -{ - return !*fn->path; -} - -void filename_free(Filename *fn) -{ - sfree(fn->path); - sfree(fn); -} - -void filename_serialise(BinarySink *bs, const Filename *f) -{ - put_asciz(bs, f->path); -} -Filename *filename_deserialise(BinarySource *src) -{ - return filename_from_str(get_asciz(src)); -} - -char filename_char_sanitise(char c) -{ - if (strchr("<>:\"/\\|?*", c)) - return '.'; - return c; -} - -char *get_username(void) -{ - DWORD namelen; - char *user; - bool got_username = false; - DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, - (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); - - { - static bool tried_usernameex = false; - if (!tried_usernameex) { - /* Not available on Win9x, so load dynamically */ - HMODULE secur32 = load_system32_dll("secur32.dll"); - /* If MIT Kerberos is installed, the following call to - GET_WINDOWS_FUNCTION makes Windows implicitly load - sspicli.dll WITHOUT proper path sanitizing, so better - load it properly before */ - HMODULE sspicli = load_system32_dll("sspicli.dll"); - (void)sspicli; /* squash compiler warning about unused variable */ - GET_WINDOWS_FUNCTION(secur32, GetUserNameExA); - tried_usernameex = true; - } - } - - if (p_GetUserNameExA) { - /* - * If available, use the principal -- this avoids the problem - * that the local username is case-insensitive but Kerberos - * usernames are case-sensitive. - */ - - /* Get the length */ - namelen = 0; - (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen); - - user = snewn(namelen, char); - got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen); - if (got_username) { - char *p = strchr(user, '@'); - if (p) *p = 0; - } else { - sfree(user); - } - } - - if (!got_username) { - /* Fall back to local user name */ - namelen = 0; - if (!GetUserName(NULL, &namelen)) { - /* - * Apparently this doesn't work at least on Windows XP SP2. - * Thus assume a maximum of 256. It will fail again if it - * doesn't fit. - */ - namelen = 256; - } - - user = snewn(namelen, char); - got_username = GetUserName(user, &namelen); - if (!got_username) { - sfree(user); - } - } - - return got_username ? user : NULL; -} - -void dll_hijacking_protection(void) -{ - /* - * If the OS provides it, call SetDefaultDllDirectories() to - * prevent DLLs from being loaded from the directory containing - * our own binary, and instead only load from system32. - * - * This is a protection against hijacking attacks, if someone runs - * PuTTY directly from their web browser's download directory - * having previously been enticed into clicking on an unwise link - * that downloaded a malicious DLL to the same directory under one - * of various magic names that seem to be things that standard - * Windows DLLs delegate to. - * - * It shouldn't break deliberate loading of user-provided DLLs - * such as GSSAPI providers, because those are specified by their - * full pathname by the user-provided configuration. - */ - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); -#if !HAVE_SETDEFAULTDLLDIRECTORIES - /* For older Visual Studio, this function isn't available in - * the header files to type-check */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK( - kernel32_module, SetDefaultDllDirectories); -#else - GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories); -#endif - } - - if (p_SetDefaultDllDirectories) { - /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified - * directories only */ - p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } -} - -void init_winver(void) -{ - OSVERSIONINFO osVersion; - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); - /* Deliberately don't type-check this function, because that - * would involve using its declaration in a header file which - * triggers a deprecation warning. I know it's deprecated (see - * below) and don't need telling. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA); - } - - ZeroMemory(&osVersion, sizeof(osVersion)); - osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); - if (p_GetVersionExA && p_GetVersionExA(&osVersion)) { - osMajorVersion = osVersion.dwMajorVersion; - osMinorVersion = osVersion.dwMinorVersion; - osPlatformId = osVersion.dwPlatformId; - } else { - /* - * GetVersionEx is deprecated, so allow for it perhaps going - * away in future API versions. If it's not there, simply - * assume that's because Windows is too _new_, so fill in the - * variables we care about to a value that will always compare - * higher than any given test threshold. - * - * Normally we should be checking against the presence of a - * specific function if possible in any case. - */ - osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */ - osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */ - } -} - -HMODULE load_system32_dll(const char *libname) -{ - /* - * Wrapper function to load a DLL out of c:\windows\system32 - * without going through the full DLL search path. (Hence no - * attack is possible by placing a substitute DLL earlier on that - * path.) - */ - static char *sysdir = NULL; - static size_t sysdirsize = 0; - char *fullpath; - HMODULE ret; - - if (!sysdir) { - size_t len; - while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) - sgrowarray(sysdir, sysdirsize, len); - } - - fullpath = dupcat(sysdir, "\\", libname); - ret = LoadLibrary(fullpath); - sfree(fullpath); - return ret; -} - -/* - * A tree234 containing mappings from system error codes to strings. - */ - -struct errstring { - int error; - char *text; -}; - -static int errstring_find(void *av, void *bv) -{ - int *a = (int *)av; - struct errstring *b = (struct errstring *)bv; - if (*a < b->error) - return -1; - if (*a > b->error) - return +1; - return 0; -} -static int errstring_compare(void *av, void *bv) -{ - struct errstring *a = (struct errstring *)av; - return errstring_find(&a->error, bv); -} - -static tree234 *errstrings = NULL; - -const char *win_strerror(int error) -{ - struct errstring *es; - - if (!errstrings) - errstrings = newtree234(errstring_compare); - - es = find234(errstrings, &error, errstring_find); - - if (!es) { - char msgtext[65536]; /* maximum size for FormatMessage is 64K */ - - es = snew(struct errstring); - es->error = error; - if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - msgtext, lenof(msgtext)-1, NULL)) { - sprintf(msgtext, - "(unable to format: FormatMessage returned %u)", - (unsigned int)GetLastError()); - } else { - int len = strlen(msgtext); - if (len > 0 && msgtext[len-1] == '\n') - msgtext[len-1] = '\0'; - } - es->text = dupprintf("Error %d: %s", error, msgtext); - add234(errstrings, es); - } - - return es->text; -} - -FontSpec *fontspec_new(const char *name, bool bold, int height, int charset) -{ - FontSpec *f = snew(FontSpec); - f->name = dupstr(name); - f->isbold = bold; - f->height = height; - f->charset = charset; - return f; -} -FontSpec *fontspec_copy(const FontSpec *f) -{ - return fontspec_new(f->name, f->isbold, f->height, f->charset); -} -void fontspec_free(FontSpec *f) -{ - sfree(f->name); - sfree(f); -} -void fontspec_serialise(BinarySink *bs, FontSpec *f) -{ - put_asciz(bs, f->name); - put_uint32(bs, f->isbold); - put_uint32(bs, f->height); - put_uint32(bs, f->charset); -} -FontSpec *fontspec_deserialise(BinarySource *src) -{ - const char *name = get_asciz(src); - unsigned isbold = get_uint32(src); - unsigned height = get_uint32(src); - unsigned charset = get_uint32(src); - return fontspec_new(name, isbold, height, charset); -} - -bool open_for_write_would_lose_data(const Filename *fn) -{ - WIN32_FILE_ATTRIBUTE_DATA attrs; - if (!GetFileAttributesEx(fn->path, GetFileExInfoStandard, &attrs)) { - /* - * Generally, if we don't identify a specific reason why we - * should return true from this function, we return false, and - * let the subsequent attempt to open the file for real give a - * more useful error message. - */ - return false; - } - if (attrs.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE | - FILE_ATTRIBUTE_DIRECTORY)) { - /* - * File is something other than an ordinary disk file, so - * opening it for writing will not cause truncation. (It may - * not _succeed_ either, but that's not our problem here!) - */ - return false; - } - if (attrs.nFileSizeHigh == 0 && attrs.nFileSizeLow == 0) { - /* - * File is zero-length (or may be a named pipe, which - * dwFileAttributes can't tell apart from a regular file), so - * opening it for writing won't truncate any data away because - * there's nothing to truncate anyway. - */ - return false; - } - return true; -} - -void escape_registry_key(const char *in, strbuf *out) -{ - bool candot = false; - static const char hex[16] = "0123456789ABCDEF"; - - while (*in) { - if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || - *in == '%' || *in < ' ' || *in > '~' || (*in == '.' - && !candot)) { - put_byte(out, '%'); - put_byte(out, hex[((unsigned char) *in) >> 4]); - put_byte(out, hex[((unsigned char) *in) & 15]); - } else - put_byte(out, *in); - in++; - candot = true; - } -} - -void unescape_registry_key(const char *in, strbuf *out) -{ - while (*in) { - if (*in == '%' && in[1] && in[2]) { - int i, j; - - i = in[1] - '0'; - i -= (i > 9 ? 7 : 0); - j = in[2] - '0'; - j -= (j > 9 ? 7 : 0); - - put_byte(out, (i << 4) + j); - in += 3; - } else { - put_byte(out, *in++); - } - } -} - -#ifdef DEBUG -static FILE *debug_fp = NULL; -static HANDLE debug_hdl = INVALID_HANDLE_VALUE; -static int debug_got_console = 0; - -void dputs(const 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); -} -#endif - -char *registry_get_string(HKEY root, const char *path, const char *leaf) -{ - HKEY key = root; - bool need_close_key = false; - char *toret = NULL, *str = NULL; - - if (path) { - if (RegCreateKey(key, path, &key) != ERROR_SUCCESS) - goto out; - need_close_key = true; - } - - DWORD type, size; - if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS) - goto out; - if (type != REG_SZ) - goto out; - - str = snewn(size + 1, char); - DWORD size_got = size; - if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str, - &size_got) != ERROR_SUCCESS) - goto out; - if (type != REG_SZ || size_got > size) - goto out; - str[size_got] = '\0'; - - toret = str; - str = NULL; - - out: - if (need_close_key) - RegCloseKey(key); - sfree(str); - return toret; -} diff --git a/windows/winstuff.h b/windows/winstuff.h index ddd3277a..5f2557dd 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -103,6 +103,12 @@ struct FontSpec *fontspec_new( #define LONG_PTR LONG #endif +#if !HAVE_STRTOUMAX +/* Work around lack of strtoumax in older MSVC libraries */ +static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) +{ return _strtoui64(nptr, endptr, base); } +#endif + #define BOXFLAGS DLGWINDOWEXTRA #define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) #define DF_END 0x0001