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