瀏覽代碼

PuTTY 0.82 + Giving up on trying to not-link PuTTY unicode implementation

Source commit: 510b30d0a40b051c780d3d3e69b3b57cf4e25a05
Martin Prikryl 10 月之前
父節點
當前提交
6c12cb97d7
共有 77 個文件被更改,包括 4421 次插入1634 次删除
  1. 197 0
      source/putty/conf-enums.h
  2. 1288 0
      source/putty/conf.h
  3. 1 1
      source/putty/crypto/aesgcm-footer.h
  4. 7 14
      source/putty/crypto/ecc-ssh.c
  5. 34 5
      source/putty/crypto/rsa.c
  6. 13 1
      source/putty/defs.h
  7. 28 0
      source/putty/doc/config.but
  8. 12 2
      source/putty/doc/faq.but
  9. 11 31
      source/putty/doc/feedback.but
  10. 3 0
      source/putty/doc/index.but
  11. 22 2
      source/putty/doc/man-pageant.but
  12. 3 0
      source/putty/doc/pgpkeys.but
  13. 1 1
      source/putty/doc/plink.but
  14. 246 0
      source/putty/doc/privacy.but
  15. 1 1
      source/putty/doc/pscp.but
  16. 420 62
      source/putty/doc/puttydoc.txt
  17. 85 0
      source/putty/doc/using.but
  18. 1 1
      source/putty/doc/version.but
  19. 1 6
      source/putty/errsock.c
  20. 58 58
      source/putty/import.c
  21. 0 20
      source/putty/logging.c
  22. 24 0
      source/putty/marshal.h
  23. 48 13
      source/putty/misc.h
  24. 24 16
      source/putty/network.h
  25. 2 1
      source/putty/proxy/local.c
  26. 23 10
      source/putty/proxy/proxy.c
  27. 2 1
      source/putty/proxy/telnet.c
  28. 207 358
      source/putty/putty.h
  29. 211 465
      source/putty/settings.c
  30. 52 0
      source/putty/specials.h
  31. 5 5
      source/putty/ssh.h
  32. 2 2
      source/putty/ssh/connection2-client.c
  33. 2 2
      source/putty/ssh/connection2.c
  34. 2 2
      source/putty/ssh/connection2.h
  35. 1 1
      source/putty/ssh/kex2-client.c
  36. 7 3
      source/putty/ssh/mainchan.c
  37. 4 16
      source/putty/ssh/portfwd.c
  38. 3 3
      source/putty/ssh/sharing.c
  39. 7 4
      source/putty/ssh/ssh.c
  40. 7 2
      source/putty/ssh/userauth2-client.c
  41. 5 5
      source/putty/sshcr.h
  42. 13 11
      source/putty/sshpubk.c
  43. 3 1
      source/putty/storage.h
  44. 1 1
      source/putty/stubs/null-plug.c
  45. 1 0
      source/putty/stubs/null-seat.c
  46. 12 0
      source/putty/stubs/null-socket.c
  47. 11 1
      source/putty/utils/backend_socket_log.c
  48. 18 0
      source/putty/utils/burnwcs.c
  49. 179 102
      source/putty/utils/conf.c
  50. 53 0
      source/putty/utils/conf_data.c
  51. 264 0
      source/putty/utils/decode_utf8.c
  52. 21 0
      source/putty/utils/decode_utf8_to_wchar.c
  53. 35 0
      source/putty/utils/decode_utf8_to_wide_string.c
  54. 16 15
      source/putty/utils/dup_mb_to_wc.c
  55. 28 0
      source/putty/utils/dup_wc_to_mb.c
  56. 19 0
      source/putty/utils/dupwcs.c
  57. 26 0
      source/putty/utils/encode_utf8.c
  58. 23 0
      source/putty/utils/encode_wide_string_as_utf8.c
  59. 3 3
      source/putty/utils/log_proxy_stderr.c
  60. 32 0
      source/putty/utils/logeventf.c
  61. 20 0
      source/putty/utils/marshal.c
  62. 1 0
      source/putty/utils/prompts.c
  63. 6 6
      source/putty/utils/sk_free_peer_info.c
  64. 2 9
      source/putty/utils/stripctrl.c
  65. 12 0
      source/putty/utils/tempseat.c
  66. 5 5
      source/putty/utils/tree234.c
  67. 4 4
      source/putty/version.h
  68. 64 72
      source/putty/windows/gss.c
  69. 12 7
      source/putty/windows/handle-socket.c
  70. 131 124
      source/putty/windows/network.c
  71. 96 22
      source/putty/windows/platform.h
  72. 29 13
      source/putty/windows/storage.c
  73. 140 106
      source/putty/windows/unicode.c
  74. 1 1
      source/putty/windows/utils/defaults.c
  75. 58 10
      source/putty/windows/utils/filename.c
  76. 5 0
      source/putty/windows/utils/fontspec.c
  77. 7 7
      source/putty/windows/utils/open_for_write_would_lose_data.c

+ 197 - 0
source/putty/conf-enums.h

@@ -0,0 +1,197 @@
+/*
+ * Define various enumerations used in the storage format of PuTTY's
+ * settings.
+ *
+ * Each CONF_ENUM here defines an identifier that config options can
+ * cite using their STORAGE_ENUM property. Within each enum, the
+ * VALUE(x,y) records indicate that if the internal value of the
+ * integer Conf property in question is x, then it's stored in the
+ * saved settings as y.
+ *
+ * This allows the internal enumerations to be rearranged without
+ * breaking storage compatibility, and also allows for the fact that
+ * (for embarrassing historical reasons) the tri-state FORCE_OFF /
+ * FORCE_ON / AUTO enumeration has several different storage
+ * representations depending on the Conf option.
+ *
+ * VALUE_OBSOLETE(x,y) records only work one way: they indicate how a
+ * legacy value in saved session data should be interpreted, but also
+ * say that that value should no longer be generated in newly saved
+ * data. That is, they indicate that y maps to x, but not that x maps
+ * to y.
+ */
+
+CONF_ENUM(addressfamily,
+    VALUE(ADDRTYPE_UNSPEC, 0),
+    VALUE(ADDRTYPE_IPV4, 1),
+    VALUE(ADDRTYPE_IPV6, 2),
+)
+
+CONF_ENUM(on_off_auto,
+    VALUE(FORCE_ON, 0),
+    VALUE(FORCE_OFF, 1),
+    VALUE(AUTO, 2),
+)
+
+CONF_ENUM(off_auto_on,
+    VALUE(FORCE_OFF, 0),
+    VALUE(AUTO, 1),
+    VALUE(FORCE_ON, 2),
+)
+
+CONF_ENUM(auto_off_on,
+    VALUE(AUTO, 0),
+    VALUE(FORCE_OFF, 1),
+    VALUE(FORCE_ON, 2),
+)
+
+CONF_ENUM(off1_on2,
+    VALUE(FORCE_OFF, 1),
+    VALUE(FORCE_ON, 2),
+)
+
+CONF_ENUM(ssh_protocol,
+    VALUE(0, 0),
+    VALUE(3, 3),
+    /* Save value 1 used to mean 'prefer SSH-1 but allow 2'; 2 vice versa.
+     * We no longer support SSH-1 vs SSH-2 agnosticism, so we translate each
+     * to the preferred version only. */
+    VALUE_OBSOLETE(0, 1),
+    VALUE_OBSOLETE(3, 2),
+)
+
+CONF_ENUM(serparity,
+    VALUE(SER_PAR_NONE, 0),
+    VALUE(SER_PAR_ODD, 1),
+    VALUE(SER_PAR_EVEN, 2),
+    VALUE(SER_PAR_MARK, 3),
+    VALUE(SER_PAR_SPACE, 4),
+)
+
+CONF_ENUM(serflow,
+    VALUE(SER_FLOW_NONE, 0),
+    VALUE(SER_FLOW_XONXOFF, 1),
+    VALUE(SER_FLOW_RTSCTS, 2),
+    VALUE(SER_FLOW_DSRDTR, 3),
+)
+
+CONF_ENUM(supdup_charset,
+    VALUE(SUPDUP_CHARSET_ASCII, 0),
+    VALUE(SUPDUP_CHARSET_ITS, 1),
+    VALUE(SUPDUP_CHARSET_WAITS, 2),
+)
+
+CONF_ENUM(proxy_type,
+    VALUE(PROXY_NONE, 0),
+    VALUE(PROXY_SOCKS4, 1),
+    VALUE(PROXY_SOCKS5, 2),
+    VALUE(PROXY_HTTP, 3),
+    VALUE(PROXY_TELNET, 4),
+    VALUE(PROXY_CMD, 5),
+    VALUE(PROXY_SSH_TCPIP, 6),
+    VALUE(PROXY_SSH_EXEC, 7),
+    VALUE(PROXY_SSH_SUBSYSTEM, 8),
+)
+
+CONF_ENUM(old_proxy_type,
+    VALUE(PROXY_NONE, 0),
+    VALUE(PROXY_HTTP, 1),
+    VALUE(PROXY_SOCKS5, 2), /* really, both SOCKS 4 and 5 */
+    VALUE(PROXY_TELNET, 3),
+    VALUE(PROXY_CMD, 4),
+)
+
+CONF_ENUM(funky_type,
+    VALUE(FUNKY_TILDE, 0),
+    VALUE(FUNKY_LINUX, 1),
+    VALUE(FUNKY_XTERM, 2),
+    VALUE(FUNKY_VT400, 3),
+    VALUE(FUNKY_VT100P, 4),
+    VALUE(FUNKY_SCO, 5),
+    VALUE(FUNKY_XTERM_216, 6),
+)
+
+CONF_ENUM(sharrow_type,
+    VALUE(SHARROW_APPLICATION, 0),
+    VALUE(SHARROW_BITMAP, 1),
+)
+
+CONF_ENUM(remote_qtitle_action,
+    VALUE(TITLE_NONE, 0),
+    VALUE(TITLE_EMPTY, 1),
+    VALUE(TITLE_REAL, 2),
+)
+
+CONF_ENUM(cursor_type,
+    VALUE(CURSOR_BLOCK, 0),
+    VALUE(CURSOR_UNDERLINE, 1),
+    VALUE(CURSOR_VERTICAL_LINE, 2),
+)
+
+CONF_ENUM(beep,
+    VALUE(BELL_DISABLED, 0),
+    VALUE(BELL_DEFAULT, 1),
+    VALUE(BELL_VISUAL, 2),
+    VALUE(BELL_WAVEFILE, 3),
+    VALUE(BELL_PCSPEAKER, 4),
+)
+
+CONF_ENUM(beep_indication,
+    VALUE(B_IND_DISABLED, 0),
+    VALUE(B_IND_FLASH, 1),
+    VALUE(B_IND_STEADY, 2),
+)
+
+CONF_ENUM(resize_effect,
+    VALUE(RESIZE_TERM, 0),
+    VALUE(RESIZE_DISABLED, 1),
+    VALUE(RESIZE_FONT, 2),
+    VALUE(RESIZE_EITHER, 3),
+)
+
+CONF_ENUM(font_quality,
+    VALUE(FQ_DEFAULT, 0),
+    VALUE(FQ_ANTIALIASED, 1),
+    VALUE(FQ_NONANTIALIASED, 2),
+    VALUE(FQ_CLEARTYPE, 3),
+)
+
+CONF_ENUM(log_type,
+    VALUE(LGTYP_NONE, 0),
+    VALUE(LGTYP_ASCII, 1),
+    VALUE(LGTYP_DEBUG, 2),
+    VALUE(LGTYP_PACKETS, 3),
+    VALUE(LGTYP_SSHRAW, 4),
+)
+
+CONF_ENUM(log_to_existing_file,
+    VALUE(LGXF_OVR, 1),
+    VALUE(LGXF_APN, 0),
+    VALUE(LGXF_ASK, -1),
+)
+
+CONF_ENUM(bold_style,
+    VALUE(BOLD_STYLE_FONT, 0),
+    VALUE(BOLD_STYLE_COLOUR, 1),
+    VALUE(BOLD_STYLE_FONT | BOLD_STYLE_COLOUR, 2),
+)
+
+CONF_ENUM(mouse_buttons,
+    VALUE(MOUSE_COMPROMISE, 0),
+    VALUE(MOUSE_XTERM, 1),
+    VALUE(MOUSE_WINDOWS, 2),
+)
+
+CONF_ENUM(line_drawing,
+    VALUE(VT_XWINDOWS, 0),
+    VALUE(VT_OEMANSI, 1),
+    VALUE(VT_OEMONLY, 2),
+    VALUE(VT_POORMAN, 3),
+    VALUE(VT_UNICODE, 4),
+)
+
+CONF_ENUM(x11_auth,
+    VALUE(X11_NO_AUTH, 0),
+    VALUE(X11_MIT, 1),
+    VALUE(X11_XDM, 2),
+)

+ 1288 - 0
source/putty/conf.h

@@ -0,0 +1,1288 @@
+/*
+ * Master list of configuration options living in the Conf data
+ * structure.
+ *
+ * Each CONF_OPTION directive defines a single CONF_foo primary key in
+ * Conf, and can be equipped with the following properties:
+ *
+ *  - VALUE_TYPE: the type of data associated with that key
+ *  - SUBKEY_TYPE: if the primary key goes with a subkey (that is, the
+ *    primary key identifies some mapping from subkeys to values), the
+ *    data type of the subkey
+ *  - DEFAULT_INT, DEFAULT_STR, DEFAULT_BOOL: the default value for
+ *    the key, if no save data is available. Must match VALUE_TYPE, if
+ *    the key has no subkey. Otherwise, no default is permitted, and
+ *    the default value of the mapping is assumed to be empty (and if
+ *    not, then LOAD_CUSTOM code must override that).
+ *  - SAVE_KEYWORD: the keyword used for the option in the Windows
+ *    registry or ~/.putty/sessions save files.
+ *  - STORAGE_ENUM: for int-typed settings with no subkeys, this
+ *    identifies an enumeration in conf-enums.h which maps internal
+ *    values of the setting in the Conf to values in the saved data.
+ *  - LOAD_CUSTOM, SAVE_CUSTOM: suppress automated loading or saving
+ *    (respectively) of this setting, in favour of manual code in
+ *    settings.c load_open_settings() or save_open_settings()
+ *    respectively.
+ *  - NOT_SAVED: indicate that this setting is not part of saved
+ *    session data at all.
+ */
+
+CONF_OPTION(host,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("HostName"),
+)
+CONF_OPTION(port,
+    VALUE_TYPE(INT),
+    SAVE_KEYWORD("PortNumber"),
+    LOAD_CUSTOM, /* default value depends on the value of CONF_protocol */
+)
+CONF_OPTION(protocol,
+    VALUE_TYPE(INT), /* PROT_SSH, PROT_TELNET etc */
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * Notionally SAVE_KEYWORD("Protocol"), but saving/loading is handled by
+     * custom code because the stored value is a string representation
+     * of the protocol name.
+     */
+)
+CONF_OPTION(addressfamily,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(ADDRTYPE_UNSPEC),
+    SAVE_KEYWORD("AddressFamily"),
+    STORAGE_ENUM(addressfamily),
+)
+CONF_OPTION(close_on_exit,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("CloseOnExit"),
+    STORAGE_ENUM(off_auto_on),
+)
+CONF_OPTION(warn_on_close,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("WarnOnClose"),
+)
+CONF_OPTION(ping_interval,
+    VALUE_TYPE(INT), /* in seconds */
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * Saving/loading is handled by custom code because for historical
+     * reasons this value corresponds to two save keywords,
+     * "PingInterval" (measured in minutes) and "PingIntervalSecs"
+     * (measured in seconds), which are added together on loading.
+     * Rationale: the value was once measured in minutes, and the
+     * seconds field was added later.
+     */
+)
+CONF_OPTION(tcp_nodelay,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("TCPNoDelay"),
+)
+CONF_OPTION(tcp_keepalives,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("TCPKeepalives"),
+)
+CONF_OPTION(loghost, /* logical host being contacted, for host key check */
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("LogHost"),
+)
+
+/* Proxy options */
+CONF_OPTION(proxy_exclude_list,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("ProxyExcludeList"),
+)
+CONF_OPTION(proxy_dns,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("ProxyDNS"),
+    STORAGE_ENUM(off_auto_on),
+)
+CONF_OPTION(even_proxy_localhost,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ProxyLocalhost"),
+)
+CONF_OPTION(proxy_type,
+    VALUE_TYPE(INT), /* PROXY_NONE, PROXY_SOCKS4, ... */
+    STORAGE_ENUM(proxy_type),
+    SAVE_KEYWORD("ProxyMethod"),
+    LOAD_CUSTOM,
+    /*
+     * Custom load code: there was an earlier keyword "ProxyType"
+     * using a different enumeration, in which SOCKS4 and SOCKS5
+     * shared a value, and a second keyword "ProxySOCKSVersion"
+     * disambiguated.
+     */
+)
+CONF_OPTION(proxy_host,
+    VALUE_TYPE(STR),
+    DEFAULT_STR("proxy"),
+    SAVE_KEYWORD("ProxyHost"),
+)
+CONF_OPTION(proxy_port,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(80),
+    SAVE_KEYWORD("ProxyPort"),
+)
+CONF_OPTION(proxy_username,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("ProxyUsername"),
+)
+CONF_OPTION(proxy_password,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("ProxyPassword"),
+)
+CONF_OPTION(proxy_telnet_command,
+    VALUE_TYPE(STR),
+    DEFAULT_STR("connect %host %port\\n"),
+    SAVE_KEYWORD("ProxyTelnetCommand"),
+)
+CONF_OPTION(proxy_log_to_term,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(FORCE_OFF),
+    SAVE_KEYWORD("ProxyLogToTerm"),
+    STORAGE_ENUM(on_off_auto),
+)
+
+/* SSH options */
+CONF_OPTION(remote_cmd,
+    VALUE_TYPE(STR_AMBI),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("RemoteCommand"),
+)
+CONF_OPTION(remote_cmd2,
+    /*
+     * Fallback command to try to run if remote_cmd fails. Only set
+     * internally by PSCP and PSFTP (so that they can try multiple
+     * methods of running an SFTP server at the remote end); never set
+     * by user configuration, or loaded or saved.
+     */
+    VALUE_TYPE(STR_AMBI),
+    DEFAULT_STR(""),
+    NOT_SAVED,
+)
+CONF_OPTION(nopty,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoPTY"),
+)
+CONF_OPTION(compression,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("Compression"),
+)
+CONF_OPTION(ssh_kexlist,
+    SUBKEY_TYPE(INT), /* indices in preference order: 0,...,KEX_MAX-1
+                       * (lower is more preferred) */
+    VALUE_TYPE(INT),  /* KEX_* enum values */
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for preference lists */
+)
+CONF_OPTION(ssh_hklist,
+    SUBKEY_TYPE(INT), /* indices in preference order: 0,...,HK_MAX-1
+                       * (lower is more preferred) */
+    VALUE_TYPE(INT),  /* HK_* enum values */
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for preference lists */
+)
+CONF_OPTION(ssh_prefer_known_hostkeys,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("PreferKnownHostKeys"),
+)
+CONF_OPTION(ssh_rekey_time,
+    VALUE_TYPE(INT), /* in minutes */
+    DEFAULT_INT(60),
+    SAVE_KEYWORD("RekeyTime"),
+)
+CONF_OPTION(ssh_rekey_data,
+    VALUE_TYPE(STR), /* string encoding e.g. "100K", "2M", "1G" */
+    DEFAULT_STR("1G"),
+    SAVE_KEYWORD("RekeyBytes"),
+)
+CONF_OPTION(tryagent,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("TryAgent"),
+)
+CONF_OPTION(agentfwd,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("AgentFwd"),
+)
+CONF_OPTION(change_username, /* allow username switching in SSH-2 */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ChangeUsername"),
+)
+CONF_OPTION(ssh_cipherlist,
+    SUBKEY_TYPE(INT), /* indices in preference order: 0,...,CIPHER_MAX-1
+                       * (lower is more preferred) */
+    VALUE_TYPE(INT),  /* CIPHER_* enum values */
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for preference lists */
+)
+CONF_OPTION(keyfile,
+    VALUE_TYPE(FILENAME),
+    SAVE_KEYWORD("PublicKeyFile"),
+)
+CONF_OPTION(detached_cert,
+    VALUE_TYPE(FILENAME),
+    SAVE_KEYWORD("DetachedCertificate"),
+)
+CONF_OPTION(auth_plugin,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("AuthPlugin"),
+)
+CONF_OPTION(sshprot,
+    /*
+     * Which SSH protocol to use.
+     *
+     * For historical reasons, the current legal values for CONF_sshprot
+     * are:
+     *  0 = SSH-1 only
+     *  3 = SSH-2 only
+     *
+     * We used to also support
+     *  1 = SSH-1 with fallback to SSH-2
+     *  2 = SSH-2 with fallback to SSH-1
+     *
+     * and we continue to use 0/3 in storage formats rather than the more
+     * obvious 1/2 to avoid surprises if someone saves a session and later
+     * downgrades PuTTY. So it's easier to use these numbers internally too.
+     */
+    VALUE_TYPE(INT),
+    DEFAULT_INT(3),
+    SAVE_KEYWORD("SshProt"),
+    STORAGE_ENUM(ssh_protocol),
+)
+CONF_OPTION(ssh_simple,
+    /*
+     * This means that we promise never to open any channel other
+     * than the main one, which means it can safely use a very large
+     * window in SSH-2.
+     *
+     * Only ever set internally by file transfer tools; never set by
+     * user configuration, or loaded or saved.
+     */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    NOT_SAVED,
+)
+CONF_OPTION(ssh_connection_sharing,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ConnectionSharing"),
+)
+CONF_OPTION(ssh_connection_sharing_upstream,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("ConnectionSharingUpstream"),
+)
+CONF_OPTION(ssh_connection_sharing_downstream,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("ConnectionSharingDownstream"),
+)
+CONF_OPTION(ssh_manual_hostkeys,
+    /*
+     * Manually configured host keys to accept regardless of the state
+     * of the host key cache.
+     *
+     * This is conceptually a set rather than a dictionary: every
+     * value in this map is the empty string, and the set of subkeys
+     * that exist is the important data.
+     */
+    SUBKEY_TYPE(STR),
+    VALUE_TYPE(STR),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */
+)
+CONF_OPTION(ssh2_des_cbc, /* "des-cbc" unrecommended SSH-2 cipher */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("SSH2DES"),
+)
+CONF_OPTION(ssh_no_userauth, /* bypass "ssh-userauth" (SSH-2 only) */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("SshNoAuth"),
+)
+CONF_OPTION(ssh_no_trivial_userauth, /* disable trivial types of auth */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("SshNoTrivialAuth"),
+)
+CONF_OPTION(ssh_show_banner, /* show USERAUTH_BANNERs (SSH-2 only) */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("SshBanner"),
+)
+CONF_OPTION(try_tis_auth,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("AuthTIS"),
+)
+CONF_OPTION(try_ki_auth,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("AuthKI"),
+)
+CONF_OPTION(try_gssapi_auth, /* attempt gssapi via ssh userauth */
+    VALUE_TYPE(BOOL),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */
+)
+CONF_OPTION(try_gssapi_kex, /* attempt gssapi via ssh kex */
+    VALUE_TYPE(BOOL),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */
+)
+CONF_OPTION(gssapifwd, /* forward tgt via gss */
+    VALUE_TYPE(BOOL),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */
+)
+CONF_OPTION(gssapirekey, /* KEXGSS refresh interval (mins) */
+    VALUE_TYPE(INT),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */
+)
+CONF_OPTION(ssh_gsslist,
+    SUBKEY_TYPE(INT), /* indices in preference order: 0,...,ngsslibs
+                       * (lower is more preferred; ngsslibs is a platform-
+                       * dependent value) */
+    VALUE_TYPE(INT),  /* indices of GSSAPI lib types (platform-dependent) */
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for preference lists, also this
+                               * setting is under #ifndef NO_GSSAPI */
+)
+CONF_OPTION(ssh_gss_custom,
+    VALUE_TYPE(FILENAME),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */
+)
+CONF_OPTION(ssh_subsys, /* run a subsystem rather than a command */
+    /*
+     * Only set internally by PSCP and PSFTP; never set by user
+     * configuration, or loaded or saved.
+     */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    NOT_SAVED,
+)
+CONF_OPTION(ssh_subsys2, /* fallback to go with remote_cmd2 */
+    /*
+     * Only set internally by PSCP and PSFTP; never set by user
+     * configuration, or loaded or saved.
+     */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    NOT_SAVED,
+)
+CONF_OPTION(ssh_no_shell, /* avoid running a shell */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("SshNoShell"),
+)
+CONF_OPTION(ssh_nc_host, /* host to connect to in `nc' mode */
+    /*
+     * Only set by the '-nc' command-line option and by the SSH proxy
+     * code. There's no GUI config option for this, and therefore it's
+     * also never loaded or saved.
+     */
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    NOT_SAVED,
+)
+CONF_OPTION(ssh_nc_port, /* port to connect to in `nc' mode */
+    /*
+     * Only set by the '-nc' command-line option and by the SSH proxy
+     * code. There's no GUI config option for this, and therefore it's
+     * also never loaded or saved.
+     */
+    VALUE_TYPE(INT),
+    DEFAULT_INT(0),
+    NOT_SAVED,
+)
+
+/* Telnet options */
+CONF_OPTION(termtype,
+    VALUE_TYPE(STR),
+    DEFAULT_STR("xterm"),
+    SAVE_KEYWORD("TerminalType"),
+)
+CONF_OPTION(termspeed,
+    VALUE_TYPE(STR),
+    DEFAULT_STR("38400,38400"),
+    SAVE_KEYWORD("TerminalSpeed"),
+)
+CONF_OPTION(ttymodes,
+    /*
+     * The full set of permitted subkeys is listed in
+     * ssh/ttymode-list.h, as the first parameter of each TTYMODE_CHAR
+     * or TTYMODE_FLAG macro.
+     *
+     * The permitted value strings are:
+     *
+     *  - "N" means do not include a record for this mode at all in
+     *    the terminal mode data in the "pty-req" channel request.
+     *    Corresponds to setting the mode to 'Nothing' in the GUI.
+     *  - "A" means use PuTTY's automatic default, matching the
+     *    settings for GUI PuTTY's terminal window or Unix Plink's
+     *    controlling tty. Corresponds to setting 'Auto' in the GUI.
+     *  - "V" followed by further string data means send a custom
+     *    value to the SSH server. Values are as documented in the
+     *    manual.
+     */
+    SUBKEY_TYPE(STR),
+    VALUE_TYPE(STR),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */
+)
+CONF_OPTION(environmt,
+    SUBKEY_TYPE(STR), /* environment variable name */
+    VALUE_TYPE(STR),  /* environment variable value */
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */
+)
+CONF_OPTION(username,
+    VALUE_TYPE(STR_AMBI),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("UserName"),
+)
+CONF_OPTION(username_from_env,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("UserNameFromEnvironment"),
+)
+CONF_OPTION(localusername,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("LocalUserName"),
+)
+CONF_OPTION(rfc_environ,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("RFCEnviron"),
+)
+CONF_OPTION(passive_telnet,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("PassiveTelnet"),
+)
+
+/* Serial port options */
+CONF_OPTION(serline,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("SerialLine"),
+)
+CONF_OPTION(serspeed,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(9600),
+    SAVE_KEYWORD("SerialSpeed"),
+)
+CONF_OPTION(serdatabits,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(8),
+    SAVE_KEYWORD("SerialDataBits"),
+)
+CONF_OPTION(serstopbits,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(2),
+    SAVE_KEYWORD("SerialStopHalfbits"),
+)
+CONF_OPTION(serparity,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(SER_PAR_NONE),
+    SAVE_KEYWORD("SerialParity"),
+    STORAGE_ENUM(serparity),
+)
+CONF_OPTION(serflow,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(SER_FLOW_XONXOFF),
+    SAVE_KEYWORD("SerialFlowControl"),
+    STORAGE_ENUM(serflow),
+)
+
+/* SUPDUP options */
+CONF_OPTION(supdup_location,
+    VALUE_TYPE(STR),
+    DEFAULT_STR("The Internet"),
+    SAVE_KEYWORD("SUPDUPLocation"),
+)
+CONF_OPTION(supdup_ascii_set,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(SUPDUP_CHARSET_ASCII),
+    SAVE_KEYWORD("SUPDUPCharset"),
+    STORAGE_ENUM(supdup_charset),
+)
+CONF_OPTION(supdup_more,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("SUPDUPMoreProcessing"),
+)
+CONF_OPTION(supdup_scroll,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("SUPDUPScrolling"),
+)
+
+/* Keyboard options */
+CONF_OPTION(bksp_is_delete,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("BackspaceIsDelete"),
+)
+CONF_OPTION(rxvt_homeend,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("RXVTHomeEnd"),
+)
+CONF_OPTION(funky_type,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(FUNKY_TILDE),
+    SAVE_KEYWORD("LinuxFunctionKeys"),
+    STORAGE_ENUM(funky_type),
+)
+CONF_OPTION(sharrow_type,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(SHARROW_APPLICATION),
+    SAVE_KEYWORD("ShiftedArrowKeys"),
+    STORAGE_ENUM(sharrow_type),
+)
+CONF_OPTION(no_applic_c, /* totally disable app cursor keys */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoApplicationCursors"),
+)
+CONF_OPTION(no_applic_k, /* totally disable app keypad */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoApplicationKeys"),
+)
+CONF_OPTION(no_mouse_rep, /* totally disable mouse reporting */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoMouseReporting"),
+)
+CONF_OPTION(no_remote_resize, /* disable remote resizing */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoRemoteResize"),
+)
+CONF_OPTION(no_alt_screen, /* disable alternate screen */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoAltScreen"),
+)
+CONF_OPTION(no_remote_wintitle, /* disable remote retitling */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoRemoteWinTitle"),
+)
+CONF_OPTION(no_remote_clearscroll, /* disable ESC[3J */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoRemoteClearScroll"),
+)
+CONF_OPTION(no_dbackspace, /* disable destructive backspace */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoDBackspace"),
+)
+CONF_OPTION(no_remote_charset, /* disable remote charset config */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NoRemoteCharset"),
+)
+CONF_OPTION(remote_qtitle_action, /* handling of remote window title queries */
+    VALUE_TYPE(INT),
+    STORAGE_ENUM(remote_qtitle_action),
+    SAVE_KEYWORD("RemoteQTitleAction"),
+    LOAD_CUSTOM, /* older versions had a boolean "NoRemoteQTitle"
+                  * before we ended up with three options */
+)
+CONF_OPTION(app_cursor,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ApplicationCursorKeys"),
+)
+CONF_OPTION(app_keypad,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ApplicationKeypad"),
+)
+CONF_OPTION(nethack_keypad,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("NetHackKeypad"),
+)
+CONF_OPTION(telnet_keyboard,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("TelnetKey"),
+)
+CONF_OPTION(telnet_newline,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("TelnetRet"),
+)
+CONF_OPTION(alt_f4, /* is it special? */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("AltF4"),
+)
+CONF_OPTION(alt_space, /* is it special? */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("AltSpace"),
+)
+CONF_OPTION(alt_only, /* is it special? */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("AltOnly"),
+)
+CONF_OPTION(localecho,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("LocalEcho"),
+    STORAGE_ENUM(on_off_auto),
+)
+CONF_OPTION(localedit,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("LocalEdit"),
+    STORAGE_ENUM(on_off_auto),
+)
+CONF_OPTION(alwaysontop,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("AlwaysOnTop"),
+)
+CONF_OPTION(fullscreenonaltenter,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("FullScreenOnAltEnter"),
+)
+CONF_OPTION(scroll_on_key,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ScrollOnKey"),
+)
+CONF_OPTION(scroll_on_disp,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("ScrollOnDisp"),
+)
+CONF_OPTION(erase_to_scrollback,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("EraseToScrollback"),
+)
+CONF_OPTION(compose_key,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ComposeKey"),
+)
+CONF_OPTION(ctrlaltkeys,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("CtrlAltKeys"),
+)
+CONF_OPTION(osx_option_meta,
+    VALUE_TYPE(BOOL),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifdef OSX_META_KEY_CONFIG */
+)
+CONF_OPTION(osx_command_meta,
+    VALUE_TYPE(BOOL),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifdef OSX_META_KEY_CONFIG */
+)
+CONF_OPTION(wintitle, /* initial window title */
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("WinTitle"),
+)
+/* Terminal options */
+CONF_OPTION(savelines,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(2000),
+    SAVE_KEYWORD("ScrollbackLines"),
+)
+CONF_OPTION(dec_om,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("DECOriginMode"),
+)
+CONF_OPTION(wrap_mode,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("AutoWrapMode"),
+)
+CONF_OPTION(lfhascr,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("LFImpliesCR"),
+)
+CONF_OPTION(cursor_type,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(0),
+    SAVE_KEYWORD("CurType"),
+    STORAGE_ENUM(cursor_type),
+)
+CONF_OPTION(blink_cur,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("BlinkCur"),
+)
+CONF_OPTION(beep,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(BELL_DEFAULT),
+    SAVE_KEYWORD("Beep"),
+    STORAGE_ENUM(beep),
+)
+CONF_OPTION(beep_ind,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(B_IND_DISABLED),
+    SAVE_KEYWORD("BeepInd"),
+    STORAGE_ENUM(beep_indication),
+)
+CONF_OPTION(bellovl, /* bell overload protection active? */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("BellOverload"),
+)
+CONF_OPTION(bellovl_n, /* number of bells to cause overload */
+    VALUE_TYPE(INT),
+    DEFAULT_INT(5),
+    SAVE_KEYWORD("BellOverloadN"),
+)
+CONF_OPTION(bellovl_t, /* time interval for overload (ticks) */
+    VALUE_TYPE(INT),
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * Loading and saving is done in custom code because the format is
+     * platform-dependent for historical reasons: on Unix, the stored
+     * value is multiplied by 1000. (And since TICKSPERSEC=1000 on
+     * that platform, it means the stored value is interpreted in
+     * microseconds.)
+     */
+)
+CONF_OPTION(bellovl_s, /* period of silence to re-enable bell (s) */
+    VALUE_TYPE(INT),
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * Loading and saving is done in custom code because the format is
+     * platform-dependent for historical reasons: on Unix, the stored
+     * value is multiplied by 1000. (And since TICKSPERSEC=1000 on
+     * that platform, it means the stored value is interpreted in
+     * microseconds.)
+     */
+)
+CONF_OPTION(bell_wavefile,
+    VALUE_TYPE(FILENAME),
+    SAVE_KEYWORD("BellWaveFile"),
+)
+CONF_OPTION(scrollbar,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("ScrollBar"),
+)
+CONF_OPTION(scrollbar_in_fullscreen,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ScrollBarFullScreen"),
+)
+CONF_OPTION(resize_action,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(RESIZE_TERM),
+    SAVE_KEYWORD("LockSize"),
+    STORAGE_ENUM(resize_effect),
+)
+CONF_OPTION(bce,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("BCE"),
+)
+CONF_OPTION(blinktext,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("BlinkText"),
+)
+CONF_OPTION(win_name_always,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("WinNameAlways"),
+)
+CONF_OPTION(width,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(80),
+    SAVE_KEYWORD("TermWidth"),
+)
+CONF_OPTION(height,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(24),
+    SAVE_KEYWORD("TermHeight"),
+)
+CONF_OPTION(font,
+    VALUE_TYPE(FONT),
+    SAVE_KEYWORD("Font"),
+)
+CONF_OPTION(font_quality,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(FQ_DEFAULT),
+    SAVE_KEYWORD("FontQuality"),
+    STORAGE_ENUM(font_quality),
+)
+CONF_OPTION(logfilename,
+    VALUE_TYPE(FILENAME),
+    SAVE_KEYWORD("LogFileName"),
+)
+CONF_OPTION(logtype,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(LGTYP_NONE),
+    SAVE_KEYWORD("LogType"),
+    STORAGE_ENUM(log_type),
+)
+CONF_OPTION(logxfovr,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(LGXF_ASK),
+    SAVE_KEYWORD("LogFileClash"),
+    STORAGE_ENUM(log_to_existing_file),
+)
+CONF_OPTION(logflush,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("LogFlush"),
+)
+CONF_OPTION(logheader,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("LogHeader"),
+)
+CONF_OPTION(logomitpass,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("SSHLogOmitPasswords"),
+)
+CONF_OPTION(logomitdata,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("SSHLogOmitData"),
+)
+CONF_OPTION(hide_mouseptr,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("HideMousePtr"),
+)
+CONF_OPTION(sunken_edge,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("SunkenEdge"),
+)
+CONF_OPTION(window_border,
+    VALUE_TYPE(INT), /* in pixels */
+    DEFAULT_INT(1),
+    SAVE_KEYWORD("WindowBorder"),
+)
+CONF_OPTION(answerback,
+    VALUE_TYPE(STR),
+    DEFAULT_STR("PuTTY"),
+    SAVE_KEYWORD("Answerback"),
+)
+CONF_OPTION(printer,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("Printer"),
+)
+CONF_OPTION(no_arabicshaping,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("DisableArabicShaping"),
+)
+CONF_OPTION(no_bidi,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("DisableBidi"),
+)
+CONF_OPTION(no_bracketed_paste,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("DisableBracketedPaste"),
+)
+
+/* Colour options */
+CONF_OPTION(ansi_colour,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("ANSIColour"),
+)
+CONF_OPTION(xterm_256_colour,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("Xterm256Colour"),
+)
+CONF_OPTION(true_colour,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("TrueColour"),
+)
+CONF_OPTION(system_colour,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("UseSystemColours"),
+)
+CONF_OPTION(try_palette,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("TryPalette"),
+)
+CONF_OPTION(bold_style,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(2),
+    SAVE_KEYWORD("BoldAsColour"),
+    STORAGE_ENUM(bold_style),
+)
+CONF_OPTION(colours,
+    /*
+     * Subkeys in this setting are indexed based on the CONF_COLOUR_*
+     * enum values in putty.h. But each subkey identifies just one
+     * component of the RGB value. Subkey 3*a+b identifies colour #a,
+     * channel #b, where channels 0,1,2 mean R,G,B respectively.
+     *
+     * Values are 8-bit integers.
+     */
+    SUBKEY_TYPE(INT),
+    VALUE_TYPE(INT),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */
+)
+
+/* Selection options */
+CONF_OPTION(mouse_is_xterm,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(0),
+    SAVE_KEYWORD("MouseIsXterm"),
+    STORAGE_ENUM(mouse_buttons),
+)
+CONF_OPTION(rect_select,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("RectSelect"),
+)
+CONF_OPTION(paste_controls,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("PasteControls"),
+)
+CONF_OPTION(rawcnp,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("RawCNP"),
+)
+CONF_OPTION(utf8linedraw,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("UTF8linedraw"),
+)
+CONF_OPTION(rtf_paste,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("PasteRTF"),
+)
+CONF_OPTION(mouse_override,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("MouseOverride"),
+)
+CONF_OPTION(wordness,
+    SUBKEY_TYPE(INT), /* ASCII character codes (literally, just 00-7F) */
+    VALUE_TYPE(INT),  /* arbitrary equivalence-class value for that char */
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */
+)
+CONF_OPTION(mouseautocopy,
+    /*
+     * What clipboard (if any) to copy text to as soon as it's
+     * selected with the mouse.
+     */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(CLIPUI_DEFAULT_AUTOCOPY), /* platform-dependent bool-valued
+                                            * macro */
+    SAVE_KEYWORD("MouseAutocopy"),
+)
+CONF_OPTION(mousepaste, /* clipboard used by one-mouse-click paste actions */
+    VALUE_TYPE(INT),
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * SAVE_KEYWORD("MousePaste"), but loading and saving is done by
+     * custom code, because the saved value is a string, and also sets
+     * CONF_mousepaste_custom
+     */
+)
+CONF_OPTION(ctrlshiftins, /* clipboard used by Ctrl+Ins and Shift+Ins */
+    VALUE_TYPE(INT),
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * SAVE_KEYWORD("CtrlShiftIns"), but loading and saving is done by
+     * custom code, because the saved value is a string, and also sets
+     * CONF_ctrlshiftins_custom
+     */
+)
+CONF_OPTION(ctrlshiftcv, /* clipboard used by Ctrl+Shift+C and Ctrl+Shift+V */
+    VALUE_TYPE(INT),
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * SAVE_KEYWORD("CtrlShiftCV"), but loading and saving is done by
+     * custom code, because the saved value is a string, and also sets
+     * CONF_ctrlshiftcv_custom
+     */
+)
+CONF_OPTION(mousepaste_custom,
+    /* Custom clipboard name if CONF_mousepaste is set to CLIPUI_CUSTOM */
+    VALUE_TYPE(STR),
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * Loading and saving is handled by custom code in conjunction
+     * with CONF_mousepaste
+     */
+)
+CONF_OPTION(ctrlshiftins_custom,
+    /* Custom clipboard name if CONF_ctrlshiftins is set to CLIPUI_CUSTOM */
+    VALUE_TYPE(STR),
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * Loading and saving is handled by custom code in conjunction
+     * with CONF_ctrlshiftins
+     */
+)
+CONF_OPTION(ctrlshiftcv_custom,
+    /* Custom clipboard name if CONF_ctrlshiftcv is set to CLIPUI_CUSTOM */
+    VALUE_TYPE(STR),
+    LOAD_CUSTOM, SAVE_CUSTOM,
+    /*
+     * Loading and saving is handled by custom code in conjunction
+     * with CONF_ctrlshiftcv
+     */
+)
+
+/* Character-set translation */
+CONF_OPTION(vtmode,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(VT_UNICODE),
+    SAVE_KEYWORD("FontVTMode"),
+    STORAGE_ENUM(line_drawing),
+)
+CONF_OPTION(line_codepage,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("LineCodePage"),
+)
+CONF_OPTION(cjk_ambig_wide,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("CJKAmbigWide"),
+)
+CONF_OPTION(utf8_override,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("UTF8Override"),
+)
+CONF_OPTION(xlat_capslockcyr,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("CapsLockCyr"),
+)
+
+/* X11 forwarding */
+CONF_OPTION(x11_forward,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("X11Forward"),
+)
+CONF_OPTION(x11_display,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("X11Display"),
+)
+CONF_OPTION(x11_auth,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(X11_MIT),
+    SAVE_KEYWORD("X11AuthType"),
+    STORAGE_ENUM(x11_auth),
+)
+CONF_OPTION(xauthfile,
+    VALUE_TYPE(FILENAME),
+    SAVE_KEYWORD("X11AuthFile"),
+)
+
+/* Port forwarding */
+CONF_OPTION(lport_acceptall, /* accept conns from hosts other than localhost */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("LocalPortAcceptAll"),
+)
+CONF_OPTION(rport_acceptall, /* same for remote forwarded ports */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("RemotePortAcceptAll"),
+)
+CONF_OPTION(portfwd,
+    /*
+     * Subkeys for 'portfwd' can have the following forms:
+     *
+     *   [LR]localport
+     *   [LR]localaddr:localport
+     *
+     * Dynamic forwardings are indicated by an 'L' key, and the
+     * special value "D". For all other forwardings, the value should
+     * be of the form 'host:port'.
+     */
+    SUBKEY_TYPE(STR),
+    VALUE_TYPE(STR),
+    LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */
+)
+
+/* SSH bug compatibility modes. All FORCE_ON/FORCE_OFF/AUTO */
+CONF_OPTION(sshbug_ignore1,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugIgnore1"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_plainpw1,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugPlainPW1"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_rsa1,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugRSA1"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_ignore2,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugIgnore2"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_derivekey2,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugDeriveKey2"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_rsapad2,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugRSAPad2"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_pksessid2,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugPKSessID2"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_rekey2,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugRekey2"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_maxpkt2,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugMaxPkt2"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_oldgex2,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugOldGex2"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_winadj,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugWinadj"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_chanreq,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugChanReq"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_dropstart,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(FORCE_OFF),
+    SAVE_KEYWORD("BugDropStart"),
+    STORAGE_ENUM(off1_on2),
+)
+CONF_OPTION(sshbug_filter_kexinit,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(FORCE_OFF),
+    SAVE_KEYWORD("BugFilterKexinit"),
+    STORAGE_ENUM(off1_on2),
+)
+CONF_OPTION(sshbug_rsa_sha2_cert_userauth,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugRSASHA2CertUserauth"),
+    STORAGE_ENUM(auto_off_on),
+)
+CONF_OPTION(sshbug_hmac2,
+    VALUE_TYPE(INT),
+    DEFAULT_INT(AUTO),
+    SAVE_KEYWORD("BugHMAC2"),
+    STORAGE_ENUM(auto_off_on),
+    LOAD_CUSTOM, /* there was an earlier keyword called "BuggyMAC" */
+)
+
+/* Options for Unix. Should split out into platform-dependent part. */
+CONF_OPTION(stamp_utmp, /* used by Unix pterm */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("StampUtmp"),
+)
+CONF_OPTION(login_shell, /* used by Unix pterm */
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(true),
+    SAVE_KEYWORD("LoginShell"),
+)
+CONF_OPTION(scrollbar_on_left,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ScrollbarOnLeft"),
+)
+CONF_OPTION(shadowbold,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("ShadowBold"),
+)
+CONF_OPTION(boldfont,
+    VALUE_TYPE(FONT),
+    SAVE_KEYWORD("BoldFont"),
+)
+CONF_OPTION(widefont,
+    VALUE_TYPE(FONT),
+    SAVE_KEYWORD("WideFont"),
+)
+CONF_OPTION(wideboldfont,
+    VALUE_TYPE(FONT),
+    SAVE_KEYWORD("WideBoldFont"),
+)
+CONF_OPTION(shadowboldoffset,
+    VALUE_TYPE(INT), /* in pixels */
+    DEFAULT_INT(1),
+    SAVE_KEYWORD("ShadowBoldOffset"),
+)
+CONF_OPTION(crhaslf,
+    VALUE_TYPE(BOOL),
+    DEFAULT_BOOL(false),
+    SAVE_KEYWORD("CRImpliesLF"),
+)
+CONF_OPTION(winclass,
+    VALUE_TYPE(STR),
+    DEFAULT_STR(""),
+    SAVE_KEYWORD("WindowClass"),
+)

+ 1 - 1
source/putty/crypto/aesgcm-footer.h

@@ -9,7 +9,7 @@
  * administrative block containing the lengths of both) is done in the
  * same way no matter what technique is used for the evaluation, so
  * that's centralised into this file, along with as much of the other
- * functionality as posible.
+ * functionality as possible.
  *
  * This footer file is #included by each implementation, but each one
  * will define its own struct type for the state, so that each alloc

+ 7 - 14
source/putty/crypto/ecc-ssh.c

@@ -90,8 +90,7 @@ static struct ec_curve *ec_p256(void)
     static struct ec_curve curve = { 0 };
     static bool initialised = false;
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff);
         mp_int *a = MP_LITERAL(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc);
         mp_int *b = MP_LITERAL(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b);
@@ -122,8 +121,7 @@ static struct ec_curve *ec_p384(void)
     static struct ec_curve curve = { 0 };
     static bool initialised = false;
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff);
         mp_int *a = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc);
         mp_int *b = MP_LITERAL(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef);
@@ -154,8 +152,7 @@ static struct ec_curve *ec_p521(void)
     static struct ec_curve curve = { 0 };
     static bool initialised = false;
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
         mp_int *a = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc);
         mp_int *b = MP_LITERAL(0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00);
@@ -186,8 +183,7 @@ static struct ec_curve *ec_curve25519(void)
     static struct ec_curve curve = { 0 };
     static bool initialised = false;
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed);
         mp_int *a = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000076d06);
         mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000001);
@@ -215,8 +211,7 @@ static struct ec_curve *ec_curve448(void)
     static struct ec_curve curve = { 0 };
     static bool initialised = false;
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
         mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6);
         mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001);
@@ -244,8 +239,7 @@ static struct ec_curve *ec_ed25519(void)
     static struct ec_curve curve = { 0 };
     static bool initialised = false;
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed);
         mp_int *d = MP_LITERAL(0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3);
         mp_int *a = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec); /* == p-1 */
@@ -281,8 +275,7 @@ static struct ec_curve *ec_ed448(void)
     static struct ec_curve curve = { 0 };
     static bool initialised = false;
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
         mp_int *d = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756); /* = p - 39081 */
         mp_int *a = MP_LITERAL(0x1);

+ 34 - 5
source/putty/crypto/rsa.c

@@ -340,11 +340,11 @@ char *rsa_ssh1_fingerprint(RSAKey *key)
  */
 char **rsa_ssh1_fake_all_fingerprints(RSAKey *key)
 {
-    char **ret = snewn(SSH_N_FPTYPES, char *);
+    char **fingerprints = snewn(SSH_N_FPTYPES, char *);
     for (unsigned i = 0; i < SSH_N_FPTYPES; i++)
-        ret[i] = NULL;
-    ret[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key);
-    return ret;
+        fingerprints[i] = NULL;
+    fingerprints[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key);
+    return fingerprints;
 }
 
 /*
@@ -837,7 +837,36 @@ static void rsa2_sign(ssh_key *key, ptrlen data,
     mp_free(in);
 
     put_stringz(bs, sign_alg_name);
-    nbytes = (mp_get_nbits(out) + 7) / 8;
+    if (flags == 0) {
+        /*
+         * Original "ssh-rsa", per RFC 4253 section 6.6, stores the
+         * signature integer in a string without padding - not even
+         * the leading zero byte that an ordinary SSH-2 mpint would
+         * require to avoid looking like two's complement.
+         *
+         * "The value for 'rsa_signature_blob' is encoded as a string
+         * containing s (which is an integer, without lengths or
+         * padding, unsigned, and in network byte order)."
+         */
+        nbytes = (mp_get_nbits(out) + 7) / 8;
+    } else {
+        /*
+         * The SHA-256 and SHA-512 signature systems, per RFC 8332
+         * section 3, should be padded to the length of the key
+         * modulus.
+         *
+         * "The value for 'rsa_signature_blob' is encoded as a string
+         * that contains an octet string S (which is the output of
+         * RSASSA-PKCS1-v1_5) and that has the same length (in octets)
+         * as the RSA modulus."
+         *
+         * Awkwardly, RFC 8332 doesn't say whether that means the
+         * 'raw' length of the RSA modulus (that is, ceil(n/8) for an
+         * n-bit key) or the length it would occupy as an SSH-2 mpint.
+         * My interpretation is the former.
+         */
+        nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8;
+    }
     put_uint32(bs, nbytes);
     for (size_t i = 0; i < nbytes; i++)
         put_byte(bs, mp_get_byte(out, nbytes - 1 - i));

+ 13 - 1
source/putty/defs.h

@@ -74,6 +74,12 @@ uintmax_t strtoumax(const char *nptr, char **endptr, int base);
 #endif /* __GNUC__ */
 
 typedef struct conf_tag Conf;
+typedef struct ConfKeyInfo ConfKeyInfo;
+typedef struct ConfSaveEnumValue ConfSaveEnumValue;
+typedef struct ConfSaveEnumType ConfSaveEnumType;
+typedef struct CmdlineArgList CmdlineArgList;
+typedef struct CmdlineArg CmdlineArg;
+
 typedef struct terminal_tag Terminal;
 typedef struct term_utf8_decode term_utf8_decode;
 
@@ -91,6 +97,7 @@ typedef struct BinarySink BinarySink;
 typedef struct BinarySource BinarySource;
 typedef struct stdio_sink stdio_sink;
 typedef struct bufchain_sink bufchain_sink;
+typedef struct buffer_sink buffer_sink;
 typedef struct handle_sink handle_sink;
 
 typedef struct IdempotentCallback IdempotentCallback;
@@ -99,7 +106,7 @@ typedef struct SockAddr SockAddr;
 
 typedef struct Socket Socket;
 typedef struct Plug Plug;
-typedef struct SocketPeerInfo SocketPeerInfo;
+typedef struct SocketEndpointInfo SocketEndpointInfo;
 typedef struct DeferredSocketOpener DeferredSocketOpener;
 typedef struct DeferredSocketOpenerVtable DeferredSocketOpenerVtable;
 
@@ -114,6 +121,11 @@ typedef struct LogContext LogContext;
 typedef struct LogPolicy LogPolicy;
 typedef struct LogPolicyVtable LogPolicyVtable;
 
+typedef struct TermLineEditor TermLineEditor;
+typedef struct TermLineEditorCallbackReceiver TermLineEditorCallbackReceiver;
+typedef struct TermLineEditorCallbackReceiverVtable
+    TermLineEditorCallbackReceiverVtable;
+
 typedef struct Seat Seat;
 typedef struct SeatVtable SeatVtable;
 typedef struct SeatDialogText SeatDialogText;

+ 28 - 0
source/putty/doc/config.but

@@ -984,6 +984,34 @@ right in all situations.
 You may also find you need to disable Arabic text shaping;
 see \k{config-features-shaping}.
 
+\S{config-features-bracketed paste} Disabling \i{bracketed paste} mode
+
+By default, when you paste text into the terminal window, it's sent to
+the server as terminal input, exactly as if you'd typed the same text
+into the terminal window using the keyboard (except that it's all sent
+at once, much faster than you could type it).
+
+However, a terminal application can change that, by asking the
+terminal to enable \q{bracketed paste mode}. In this mode, pasted data
+is marked in the input stream, by sending a special control sequence
+before the paste, and another one at the end.
+
+A terminal application can use this information to treat pasted data
+differently from keyboard input. For example, a terminal-based text
+editor can treat the input as literal data, even if some of its
+characters would normally trigger special editor functions. A shell
+can treat pasted input as less trusted, in case another application
+somehow sneaked a malicious shell command into your clipboard: modern
+versions of \cw{bash} will highlight pasted data on the command line,
+and not run it until you've confirmed it by pressing Return, even if
+the pasted data contained a newline character.
+
+In edge cases, it's possible that bracketed paste mode introduces
+bigger problems than the ones it solves. So you can use this checkbox
+to turn it off completely. If you do that, then PuTTY will always send
+your paste data exactly as if it had been typed at the keyboard,
+whether or not the server asked for bracketed paste mode.
+
 \H{config-window} The Window panel
 
 The Window configuration panel allows you to control aspects of the

+ 12 - 2
source/putty/doc/faq.but

@@ -1198,6 +1198,17 @@ don't advise anyone to use it in preference to our own site.
 The real PuTTY web site, run by the PuTTY team, has always been at
 \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/}.
 
+\S{faq-the}{Question} Why do the download links point to
+\cw{the.earth.li} and not chiark? Has your website been hacked?
+
+We haven't been hacked: links to \cw{the.earth.li} are legit. The
+files for released versions of PuTTY are hosted on a different server
+from the web pages, for bandwidth reasons.
+
+The download site \cw{the.earth.li} is hosted by
+\W{https://www.mythic-beasts.com/}{Mythic Beasts}, and we're very
+grateful to them!
+
 \S{faq-domain}{Question} Would you like me to register you a nicer
 domain name?
 
@@ -1297,8 +1308,7 @@ There isn't one, that we know of.
 If someone else wants to set up a mailing list or other forum for
 PuTTY users to help each other with common problems, that would be
 fine with us, though the PuTTY team would almost certainly not have the
-time to read it.  It's probably better to use one of the established
-newsgroups for this purpose (see \k{feedback-other-fora}).
+time to read it.
 
 \S{faq-donations}{Question} How can I donate to PuTTY development?
 

+ 11 - 31
source/putty/doc/feedback.but

@@ -13,16 +13,15 @@ reports and feature requests.
 
 The PuTTY development team gets a \e{lot} of mail. If you can
 possibly solve your own problem by reading the manual, reading the
-FAQ, reading the web site, asking a fellow user, perhaps posting to a
-newsgroup (see \k{feedback-other-fora}), or some other means, then it
-would make our lives much easier.
+FAQ, reading the web site, asking a fellow user, or some other
+means, then it would make our lives much easier.
 
 We get so much e-mail that we literally do not have time to answer
 it all. We regret this, but there's nothing we can do about it. So
 if you can \e{possibly} avoid sending mail to the PuTTY team, we
 recommend you do so. In particular, support requests
-(\k{feedback-support}) are probably better sent to newsgroups, or
-passed to a local expert if possible.
+(\k{feedback-support}) are probably better sent to some public
+forum, or passed to a local expert if possible.
 
 The PuTTY contact email address is a private \i{mailing list} containing
 four or five core developers. Don't be put off by it being a mailing
@@ -90,23 +89,7 @@ modification. If you've only changed 10 lines, we'd prefer to
 receive a mail that's 30 lines long than one containing multiple
 megabytes of data we already have.
 
-\S{feedback-other-fora} Other places to ask for help
-
-There are two Usenet newsgroups that are particularly relevant to the
-PuTTY tools:
-
-\b \W{news:comp.security.ssh}\c{comp.security.ssh}, for questions
-specific to using the SSH protocol;
-
-\b \W{news:comp.terminals}\c{comp.terminals}, for issues relating to
-terminal emulation (for instance, keyboard problems).
-
-Please use the newsgroup most appropriate to your query, and remember
-that these are general newsgroups, not specifically about PuTTY.
-
-If you don't have direct access to Usenet, you can access these
-newsgroups through Google Groups
-(\W{http://groups.google.com/}\cw{groups.google.com}).
+\# \S{feedback-other-fora} Other places to ask for help
 
 \H{feedback-bugs} Reporting bugs
 
@@ -200,8 +183,7 @@ think the documentation is unclear or unhelpful. But we do need to
 be given exact details of \e{what} you think the documentation has
 failed to tell you, or \e{how} you think it could be made clearer.
 If your problem is simply that you don't \e{understand} the
-documentation, we suggest posting to a newsgroup (see
-\k{feedback-other-fora}) and seeing if someone
+documentation, we suggest asking around and seeing if someone
 will explain what you need to know. \e{Then}, if you think the
 documentation could usefully have told you that, send us a bug
 report and explain how you think we should change it.
@@ -356,17 +338,15 @@ include:
 
 \b If you want to do something with PuTTY but have no idea where to
 start, and reading the manual hasn't helped, try posting to a
-newsgroup (see \k{feedback-other-fora}) and see if someone can explain
-it to you.
+public forum and see if someone can explain it to you.
 
 \b If you have tried to do something with PuTTY but it hasn't
 worked, and you aren't sure whether it's a bug in PuTTY or a bug in
 your SSH server or simply that you're not doing it right, then try
-posting to a newsgroup (see \k{feedback-other-fora}) and see
-if someone can solve your problem. Or try doing the same thing with
-a different SSH client and see if it works with that. Please do not
-report it as a PuTTY bug unless you are really sure it \e{is} a bug
-in PuTTY.
+posting to some public forum and see if someone can solve your
+problem. Or try doing the same thing with a different SSH client
+and see if it works with that. Please do not report it as a PuTTY
+bug unless you are really sure it \e{is} a bug in PuTTY.
 
 \b If someone else installed PuTTY for you, or you're using PuTTY on
 someone else's computer, try asking them for help first.  They're more

+ 3 - 0
source/putty/doc/index.but

@@ -77,6 +77,9 @@ from other protocols
 \IM{copy and paste} cut and paste
 \IM{copy and paste} paste, copy and
 
+\IM{bracketed paste} bracketed paste
+\IM{bracketed paste} paste, bracketed
+
 \IM{three-button mouse} three-button mouse
 \IM{three-button mouse} mouse, three-button
 

+ 22 - 2
source/putty/doc/man-pageant.but

@@ -8,8 +8,8 @@
 
 \S{pageant-manpage-synopsis} SYNOPSIS
 
-\c pageant ( -X | -T | --permanent | --debug ) [ [ --encrypted ] key-file... ]
-\e bbbbbbb   bb   bb   bbbbbbbbbbb   bbbbbbb       bbbbbbbbbbb   iiiiiiii
+\c pageant ( -X | -T | --permanent | --debug | --foreground ) [ [ --encrypted ] key-file... ]
+\e bbbbbbb   bb   bb   bbbbbbbbbbb   bbbbbbb   bbbbbbbbbbbb       bbbbbbbbbbb   iiiiiiii
 \c pageant [ [ --encrypted ] key-file... ] --exec command [ args... ]
 \e bbbbbbb       bbbbbbbbb   iiiiiiii      bbbbbb iiiiiii   iiii
 \c pageant -a [ --encrypted ] key-file...
@@ -183,6 +183,26 @@ prompts will need to be answered on standard input. This is useful
 for debugging what Pageant itself is doing, or what another process is
 doing to it.
 
+\dt \cw{--foreground}
+
+\dd Like \cw{--debug}, Pageant will run in the foreground, without
+forking. It will print its environment variable setup commands on
+standard output. Unlike \cw{--debug}, Pageant will not automatically log
+agent activity to standard output, nor will it force passphrase prompts
+to standard input. This is useful if Pageant is spawned by a parent
+process that controls or otherwise programmatically interfaces with
+Pageant.
+
+\lcont{
+
+After Pageant prints its environment setup commands, it closes its
+standard output. So if the parent process has run it in a pipe to
+retrieve the environment setup commands, it can simply read until it
+receives EOF, instead of having to know how many lines of output to
+expect.
+
+}
+
 \S{pageant-manpage-client} CLIENT OPTIONS
 
 The following options tell Pageant to operate in client mode,

+ 3 - 0
source/putty/doc/pgpkeys.but

@@ -19,6 +19,9 @@ built-in signatures that are automatically verified by Windows' own
 mechanism (\q{\i{Authenticode}}). The keys used for that are different,
 and are not covered here.
 
+See \k{faq-checksums} in the FAQ for some gotchas when verifying
+checksums and signatures.
+
 (Note that none of the keys, signatures, etc mentioned here have
 anything to do with keys used with SSH - they are purely for verifying
 the origin of files distributed by the PuTTY team.)

+ 1 - 1
source/putty/doc/plink.but

@@ -41,7 +41,7 @@ use Plink:
 
 \c C:\>plink
 \c Plink: command-line connection utility
-\c Release 0.81
+\c Release 0.82
 \c Usage: plink [options] [user@]host [command]
 \c        ("host" can also be a PuTTY saved session name)
 \c Options:

+ 246 - 0
source/putty/doc/privacy.but

@@ -0,0 +1,246 @@
+\A{privacy} PuTTY privacy considerations
+
+This appendix lists the implications of using PuTTY for your privacy
+and personal data.
+
+The short summary: PuTTY never \q{phones home} to us, the developers.
+It does store data on your own computer, and it does transmit data
+over the network, but in both cases, only as necessary to do its job.
+In particular, data is only transmitted over the network to the server
+you told PuTTY to connect to.
+
+But if you're concerned about exactly \e{what} information is stored
+or transmitted, then here's a more detailed description.
+
+\H{privacy-local}Information that PuTTY stores locally
+
+When you use PuTTY, it stores a small amount of information on your
+computer, necessary for doing its own job. This information is stored
+in the user account of the user who runs PuTTY, so it is under your
+control: you can view it, change it, or delete it.
+
+If you need to delete all of this data, you can use the \c{-cleanup}
+command-line option, as described in \k{using-cleanup}.
+
+PuTTY does not transmit your saved session data to any other site.
+However, you may need to be aware of the fact that it is stored on
+\e{your} computer. (For example, somebody else accessing your computer
+might be able to find a list of sites you have connected to, if you
+have saved details of them.)
+
+\S{privacy-hostkeys} Host key cache
+
+If you use the SSH protocol, then PuTTY stores a list of the SSH
+servers you have connected to, together with their host keys.
+
+This is known as the \q{host key cache}. It is used to detect network
+attacks, by notifying you if a server you've connected to before
+doesn't look like the same one you thought it was. (See \k{gs-hostkey}
+for a basic introduction to host keys.)
+
+The host key cache is optional. An entry is only saved in the host key
+cache if you select the \q{Accept} action at one of the PuTTY suite's
+host key verification prompts. So if you want to make an SSH
+connection without PuTTY saving any trace of where you connected to,
+you can press \q{Connect Once} instead of \q{Accept}, which does not
+store the host key in the cache.
+
+However, if you do this, PuTTY can't automatically detect the host key
+changing in the future, so you should check the key fingerprint
+yourself every time you connect. \s{This is vitally important.} If you
+don't let PuTTY cache host keys \e{and} don't check them yourself,
+then it becomes easy for an attacker to interpose a listener between
+you and the server you're connecting to. The entire cryptographic
+system of SSH depends on making sure the host key is right.
+
+The host key cache is only used by SSH. No other protocol supported
+by PuTTY has any analogue of it.
+
+\S{privacy-savedsessions} Saved sessions
+
+After you set up PuTTY's configuration for a particular network
+connection, you can choose to save it as a \q{saved session}, so that
+you can make the same connection again later without having to
+re-enter all the details.
+
+PuTTY will not do this unless you use the \q{Save} button in its
+configuration box. It never saves session configuration automatically.
+
+So if you want to make an SSH connection without leaving any trace of
+where you connected to, you should not make a saved session for that
+connection. Instead, re-enter the details by hand every time you do
+it.
+
+\S{privacy-jumplist} Jump list
+
+On Windows, the operating system provides a feature called a \q{jump
+list}. This is a menu that pops up from an application's icon in the
+Windows taskbar, and the application can configure entries that appear
+in it. Applications typically include menu items to re-launch recently
+used documents or configurations.
+
+PuTTY updates its jump list whenever a saved session is loaded, either
+to launch it immediately or to load it within the configuration dialog
+box. So if you have a collection of saved sessions, the jump list will
+contain a record of which ones you have recently used.
+
+An exception is that saved sessions are not included in the jump list
+if they are not \q{launchable}, meaning that they actually specify a
+host name or serial port to connect to. A non-launchable session can
+specify all the other configuration details (such as fonts, window
+size, keyboard setup, SSH features, etc), but leave out the hostname.
+
+If you want to avoid leaving any evidence of having made a particular
+connection, then make the connection without creating a launchable
+saved session for it: either make no saved session at all, or create a
+non-launchable one which sets up every detail \e{except} the
+destination host name. Then it won't appear in the jump list.
+
+(The saved session itself would also be evidence, of course, as
+discussed in the previous section.)
+
+\S{privacy-logfiles} Log files
+
+PuTTY can be configured to save a log file of your entire session to
+the computer you run it on. By default it does not do so: the content
+of your session is not saved.
+
+See \k{config-logging} for details of the logging features. Some
+logging modes store only output sent by the server and printed in
+PuTTY's terminal window. Other more thorough modes also store your
+input that PuTTY sends \e{to} the server.
+
+If the logging feature is enabled, then by default, PuTTY will avoid
+saving data in the log file that it knows to be sensitive, such as
+passwords. However, it cannot reliably identify \e{all} passwords. If
+you use a password for your initial login to an SSH server, PuTTY
+knows that is a password, and will omit it from the log file. But if
+after login you type a password into an application on the server,
+then PuTTY will not know that \e{that} is a password, so it will
+appear in the log file, if PuTTY is writing a type that includes
+keyboard input.
+
+PuTTY can also be configured to include all passwords in its log
+files, even the ones it would normally leave out. This is intended for
+debugging purposes, for example if a server is refusing your password
+and you need to check whether the password is being sent correctly. We
+do not recommend enabling this option routinely.
+
+\S{privacy-randomseed} Random seed file
+
+PuTTY stores a small file of random bytes under the name
+\cq{putty.rnd}, which is reloaded the next time it is run and used to
+seed its random number generator. These bytes are meaningless and
+random, and do not contain an encrypted version of anything.
+
+\H{privacy-network} Sending information over the network
+
+PuTTY is a communications tool. Its \e{purpose} is to connect to
+another computer, over a network or a serial port, and send
+information. However it only makes the network connections that its
+configuration instructs it to.
+
+\S{privacy-nophonehome} PuTTY only connects to the specified destination host
+
+No PuTTY tool will \q{phone home} to any site under the control of us
+(the development team), or to any other site apart from the
+destination host or proxy host in its configuration, and any DNS
+server that is needed to look up the IP addresses corresponding to
+those host names.
+
+No information about your network sessions, and no information from
+the computer you run PuTTY on, is collected or recorded by the PuTTY
+developers.
+
+Information you provide to PuTTY (via keyboard input, the command
+line, or files loaded by the file transfer tools) is sent to the
+server that PuTTY's configuration tells it to connect to. It is not
+sent anywhere else.
+
+\S{privacy-whatdata} What data is sent to the destination host
+
+When you log in to a server, PuTTY will send your username. If you use
+a password to authenticate to the server, PuTTY will send it that
+password as well.
+
+(Therefore, the server is told what your password is during login.
+This means that if you use the same password on two servers, the
+administrator of one could find out your password and log in to your
+account on the other.)
+
+If you use an SSH private key to authenticate, PuTTY will send the
+\e{public} key, but not the private key. If you typed a passphrase to
+decrypt the private key, PuTTY will not send the passphrase either.
+
+(Therefore, it is safer to use the same \e{public key} to authenticate
+to two SSH servers. Neither server gains the ability to impersonate
+you to the other server. However, if the server maintainers talked to
+each other, they would at least be able to find out that your accounts
+on the two machines were owned by the same person, if they didn't
+already know.)
+
+When PuTTY prompts for a private key passphrase, a small copy of the
+PuTTY icon appears to the left of the prompt, to indicate that the
+prompt was genuinely from PuTTY. (We call this a \q{trust sigil}.)
+That icon never appears next to text sent from the server. So if a
+server tries to mimic that prompt to trick you into telling it your
+private key passphrase, it won't be able to fake that trust sigil, and
+you can tell the difference.
+
+If you're running Pageant, and you haven't configured a specific
+public key to authenticate to this server, then PuTTY will try all the
+keys in Pageant one after the other, sending each public key to the
+server to see if it's acceptable. This can lead to the server finding
+out about other public keys you own. However, if you configure PuTTY
+to use a specific public key, then it will ignore all the other keys
+in Pageant.
+
+Once you have logged in, keystrokes you type in the PuTTY terminal
+window, and data you paste in with the mouse, are sent to the
+destination host. That is PuTTY's primary job.
+
+The server can request PuTTY to send details of mouse movements in the
+terminal window, in order to implement mouse-controlled user
+interfaces on the server. If you consider this to be a privacy
+intrusion, you can turn off that terminal feature in the Features
+configuration panel (\q{Disable xterm-style mouse reporting}, as
+described in \k{config-features-mouse}).
+
+\H{privacy-config} Configuration
+
+The operation of a PuTTY network tool is controlled by its
+configuration. This configuration is obtained from:
+
+\b the command line used to run the tool
+
+\b settings configured in the GUI before opening a network session
+
+\b optionally, the contents of a saved session, if the command line
+or a GUI action instructed PuTTY to load one
+
+\b the special saved session called \q{Default Settings}, which
+applies if no other saved session is loaded
+
+\b defaults built in to PuTTY itself.
+
+The defaults built in to PuTTY do not tell it to save log files, or
+specify the name of any network site to connect to.
+
+However, if PuTTY has been installed for you by somebody else, such as
+an organisation, then that organisation may have provided their own
+default configuration. In that situation you may wish to check that
+the defaults they have set are compatible with your privacy needs. For
+example, an organisation providing your PuTTY configuration might
+configure PuTTY to save log files of your sessions, even though
+PuTTY's own default is not to do so.
+
+\H{privacy-modified} Modified versions of PuTTY
+
+PuTTY is free software. Its source code is available, so anyone can
+make a modified version of it. The modified version can behave
+differently from the original in any way it likes.
+
+This list of privacy considerations only applies to the original
+version of PuTTY, as distributed by its development team. We cannot
+make any promises about the behaviour of modified versions distributed
+by other people.

+ 1 - 1
source/putty/doc/pscp.but

@@ -39,7 +39,7 @@ use PSCP:
 
 \c C:\>pscp
 \c PuTTY Secure Copy client
-\c Release 0.81
+\c Release 0.82
 \c Usage: pscp [options] [user@]host:source target
 \c        pscp [options] source [source...] [user@]host:target
 \c        pscp [options] -ls [user@]host:filespec

+ 420 - 62
source/putty/doc/puttydoc.txt

@@ -1413,6 +1413,92 @@ Chapter 3: Using PuTTY
        section 4.19.4. When you dismiss that dialog box, PuTTY will
        terminate.
 
+3.11.3.30 `-legacy-stdio-prompts': handle Windows console prompts like
+       older versions of PuTTY
+
+       This option applies to all of PSCP, PSFTP and Plink on Windows: all
+       the tools in the PuTTY suite that run in a Windows console and make
+       SSH connections.
+
+       These tools use the Windows console to prompt for various
+       information: usernames, passwords, answers to questions about host
+       keys, and so on.
+
+       In current versions of PuTTY, these prompts work by direct access
+       to the Windows console. This means that even if you redirect the
+       standard input or output of the tool, prompts will _still_ be sent
+       to the console (and not where you've redirected your output), and
+       the user's responses will be read from the console (and not from
+       where you've redirected your input).
+
+       Another advantage of reading directly from the Windows console is
+       that the tools can read input as Unicode. So this also allows you
+       to enter usernames and passwords that contain characters not in the
+       Windows system's default character set.
+
+       In versions of the PuTTY tools up to and including 0.81, the prompts
+       used the tool's ordinary I/O handles, so prompt output and user
+       responses could be redirected.
+
+       We think the new behaviour is more likely to be useful. For example,
+       if you have a local command that generates output, and you want to
+       pipe that output into a command running remotely via Plink, you can
+       run a command line such as
+
+         local_command | plink hostname remote_command
+
+       and the data piped into the remote command will be the same whether
+       or not Plink has to stop to ask for a password. With the old
+       behaviour you would have had to include the password in Plink's
+       input, which is more awkward.
+
+       However, we recognise that people may have customised complicated
+       workflows around the old behaviour. So if you need to switch back
+       to it, you can do so by specifying `-legacy-stdio-prompts' on the
+       command-line.
+
+       To fully revert to the previous behaviour, you'd also need to
+       specify `-legacy-charset-handling' (see the next section). (Even
+       without that option, prompt handling with `-legacy-stdio-prompts'
+       may not be fully Unicode-clean.)
+
+3.11.3.31 `-legacy-charset-handling': handle character set in prompts like
+       older versions of PuTTY
+
+       This option applies to PuTTY (on all platforms), and also to all of
+       PSCP, PSFTP and Plink on Windows.
+
+       In current versions of PuTTY, when you are prompted in the terminal
+       window for things like SSH usernames and passwords, the responses
+       you type are interpreted as Unicode, and transmitted to the server
+       as such, even if the terminal is otherwise configured to use a
+       different character encoding (see section 4.10.1). Similarly, the
+       same prompts from the Windows console tools will unconditionally
+       interpret their input as Unicode.
+
+       This behaviour is in line with the SSH standards; it allows things
+       like usernames to use the full character set of the user's native
+       language, and ensures that different keystrokes you type for your
+       password are actually treated distinctly.
+
+       However, if you are used to the behaviour of the PuTTY tools up to
+       version 0.81, this could cause a previously working username and/or
+       password not to work as you expected. For instance, if you had
+       set a password including some accented characters, this change in
+       behaviour could cause the same keystrokes you've always entered to
+       start sending a different sequence of bytes to the server, denying
+       you access (and you wouldn't even be able to see the difference,
+       since the password is not shown when you type it).
+
+       `-legacy-charset-handling' reverts the PuTTY tools' behaviour to
+       how it was previously: what you type at these prompts will be
+       interpreted according to the `Remote character set' (for PuTTY) or
+       Windows' default character set (for the Windows console tools).
+
+       (For example, this could allow you to log in to change your password
+       to make using this option unnecessary in future. But if you're doing
+       that, make sure the terminal is configured as UTF-8!)
+
 Chapter 4: Configuring PuTTY
 ----------------------------
 
@@ -2378,6 +2464,34 @@ Chapter 4: Configuring PuTTY
        You may also find you need to disable Arabic text shaping; see
        section 4.6.10.
 
+4.6.12 Disabling bracketed paste mode
+
+       By default, when you paste text into the terminal window, it's sent
+       to the server as terminal input, exactly as if you'd typed the same
+       text into the terminal window using the keyboard (except that it's
+       all sent at once, much faster than you could type it).
+
+       However, a terminal application can change that, by asking the
+       terminal to enable `bracketed paste mode'. In this mode, pasted data
+       is marked in the input stream, by sending a special control sequence
+       before the paste, and another one at the end.
+
+       A terminal application can use this information to treat pasted data
+       differently from keyboard input. For example, a terminal-based text
+       editor can treat the input as literal data, even if some of its
+       characters would normally trigger special editor functions. A shell
+       can treat pasted input as less trusted, in case another application
+       somehow sneaked a malicious shell command into your clipboard:
+       modern versions of bash will highlight pasted data on the command
+       line, and not run it until you've confirmed it by pressing Return,
+       even if the pasted data contained a newline character.
+
+       In edge cases, it's possible that bracketed paste mode introduces
+       bigger problems than the ones it solves. So you can use this
+       checkbox to turn it off completely. If you do that, then PuTTY will
+       always send your paste data exactly as if it had been typed at the
+       keyboard, whether or not the server asked for bracketed paste mode.
+
    4.7 The Window panel
 
        The Window configuration panel allows you to control aspects of the
@@ -5530,7 +5644,7 @@ Chapter 5: Using PSCP to transfer files securely
 
          C:\>pscp
          PuTTY Secure Copy client
-         Release 0.81
+         Release 0.82
          Usage: pscp [options] [user@]host:source target
                 pscp [options] source [source...] [user@]host:target
                 pscp [options] -ls [user@]host:filespec
@@ -6452,7 +6566,7 @@ Chapter 7: Using the command-line connection tool Plink
 
          C:\>plink
          Plink: command-line connection utility
-         Release 0.81
+         Release 0.82
          Usage: plink [options] [user@]host [command]
                 ("host" can also be a PuTTY saved session name)
          Options:
@@ -9490,7 +9604,17 @@ A.7.24 Since 0.78, I can't find where to configure my SSH private key.
        The real PuTTY web site, run by the PuTTY team, has always been at
        https://www.chiark.greenend.org.uk/~sgtatham/putty/.
 
- A.9.2 Would you like me to register you a nicer domain name?
+ A.9.2 Why do the download links point to the.earth.li and not chiark? Has
+       your website been hacked?
+
+       We haven't been hacked: links to the.earth.li are legit. The files
+       for released versions of PuTTY are hosted on a different server from
+       the web pages, for bandwidth reasons.
+
+       The download site the.earth.li is hosted by Mythic Beasts, and we're
+       very grateful to them!
+
+ A.9.3 Would you like me to register you a nicer domain name?
 
        No, thank you. Even if you can find one (most of them seem to
        have been registered already, by people who didn't ask whether we
@@ -9506,11 +9630,11 @@ A.7.24 Since 0.78, I can't find where to configure my SSH private key.
        things. Having it registered for us by a third party who we don't
        even know is not the best way to achieve this.
 
- A.9.3 Would you like free web hosting for the PuTTY web site?
+ A.9.4 Would you like free web hosting for the PuTTY web site?
 
        We already have some, thanks.
 
- A.9.4 Would you link to my web site from the PuTTY web site?
+ A.9.5 Would you link to my web site from the PuTTY web site?
 
        Only if the content of your web page is of definite direct interest
        to PuTTY users. If your content is unrelated, or only tangentially
@@ -9547,10 +9671,10 @@ A.7.24 Since 0.78, I can't find where to configure my SSH private key.
        of the PuTTY web site, we might be interested in linking to you from
        our Mirrors page.
 
- A.9.5 Why don't you move PuTTY to SourceForge?
+ A.9.6 Why don't you move PuTTY to SourceForge?
 
        Partly, because we don't want to move the web site location (see
-       question A.9.2).
+       question A.9.3).
 
        Also, security reasons. PuTTY is a security product, and as such it
        is particularly important to guard the code and the web site against
@@ -9565,7 +9689,7 @@ A.7.24 Since 0.78, I can't find where to configure my SSH private key.
        they're not ideal for everyone, and in particular they're not ideal
        for us.
 
- A.9.6 Why can't I subscribe to the putty-bugs mailing list?
+ A.9.7 Why can't I subscribe to the putty-bugs mailing list?
 
        Because you're not a member of the PuTTY core development team. The
        putty-bugs mailing list is not a general newsgroup-like discussion
@@ -9576,17 +9700,16 @@ A.7.24 Since 0.78, I can't find where to configure my SSH private key.
        overwhelmed by the volume of traffic. It's hard enough to keep up
        with the list as it is.
 
- A.9.7 If putty-bugs isn't a general-subscription mailing list, what is?
+ A.9.8 If putty-bugs isn't a general-subscription mailing list, what is?
 
        There isn't one, that we know of.
 
        If someone else wants to set up a mailing list or other forum for
-       PuTTY users to help each other with common problems, that would
-       be fine with us, though the PuTTY team would almost certainly not
-       have the time to read it. It's probably better to use one of the
-       established newsgroups for this purpose (see section B.1.2).
+       PuTTY users to help each other with common problems, that would be
+       fine with us, though the PuTTY team would almost certainly not have
+       the time to read it.
 
- A.9.8 How can I donate to PuTTY development?
+ A.9.9 How can I donate to PuTTY development?
 
        Please, _please_ don't feel you have to. PuTTY is completely free
        software, and not shareware. We think it's very important that
@@ -9612,7 +9735,7 @@ A.7.24 Since 0.78, I can't find where to configure my SSH private key.
        something worthwhile, ask us first. If you don't like these terms,
        feel perfectly free not to donate. We don't mind.
 
- A.9.9 Can I have permission to put PuTTY on a cover disk / distribute it
+A.9.10 Can I have permission to put PuTTY on a cover disk / distribute it
        with other software / etc?
 
        Yes. For most things, you need not bother asking us explicitly for
@@ -9620,7 +9743,7 @@ A.7.24 Since 0.78, I can't find where to configure my SSH private key.
 
        See section B.9 for more details.
 
-A.9.10 Can you sign an agreement indemnifying us against security problems
+A.9.11 Can you sign an agreement indemnifying us against security problems
        in PuTTY?
 
        No!
@@ -9681,13 +9804,13 @@ A.9.10 Can you sign an agreement indemnifying us against security problems
        then get our code reviewed by a security engineer they're happy
        with.
 
-A.9.11 Can you sign this form granting us permission to use/distribute
+A.9.12 Can you sign this form granting us permission to use/distribute
        PuTTY?
 
        If your form contains any clause along the lines of `the undersigned
        represents and warrants', we're not going to sign it. This is
        particularly true if it asks us to warrant that PuTTY is secure;
-       see question A.9.10 for more discussion of this. But it doesn't
+       see question A.9.11 for more discussion of this. But it doesn't
        really matter what we're supposed to be warranting: even if it's
        something we already believe is true, such as that we don't infringe
        any third-party copyright, we will not sign a document accepting
@@ -9702,10 +9825,10 @@ A.9.11 Can you sign this form granting us permission to use/distribute
        involve pretending you wrote it or suing us if it goes wrong. We
        think that really ought to be enough for anybody.
 
-       See also question A.9.13 for another reason why we don't want to do
+       See also question A.9.14 for another reason why we don't want to do
        this sort of thing.
 
-A.9.12 Can you write us a formal notice of permission to use PuTTY?
+A.9.13 Can you write us a formal notice of permission to use PuTTY?
 
        We could, in principle, but it isn't clear what use it would be. If
        you think there's a serious chance of one of the PuTTY copyright
@@ -9718,10 +9841,10 @@ A.9.12 Can you write us a formal notice of permission to use PuTTY?
        document, which wouldn't guarantee you that some other copyright
        holder might not sue.
 
-       See also question A.9.13 for another reason why we don't want to do
+       See also question A.9.14 for another reason why we don't want to do
        this sort of thing.
 
-A.9.13 Can you sign _anything_ for us?
+A.9.14 Can you sign _anything_ for us?
 
        Not unless there's an incredibly good reason.
 
@@ -9740,7 +9863,7 @@ A.9.13 Can you sign _anything_ for us?
        is simply not well suited to using popular free software, and we
        urge you to consider this as a flaw in your policy.
 
-A.9.14 If you won't sign anything, can you give us some sort of assurance
+A.9.15 If you won't sign anything, can you give us some sort of assurance
        that you won't make PuTTY closed-source in future?
 
        Yes and no.
@@ -9782,7 +9905,7 @@ A.9.14 If you won't sign anything, can you give us some sort of assurance
        would switch to the latter. Therefore, it would be pretty stupid of
        us to try it.)
 
-A.9.15 Can you provide us with export control information / FIPS
+A.9.16 Can you provide us with export control information / FIPS
        certification for PuTTY?
 
        Some people have asked us for an Export Control Classification
@@ -9799,7 +9922,7 @@ A.9.15 Can you provide us with export control information / FIPS
        PuTTY tools. Unless someone else is prepared to do the necessary
        work and pay any costs, we can't provide this.
 
-A.9.16 As one of our existing software vendors, can you just fill in this
+A.9.17 As one of our existing software vendors, can you just fill in this
        questionnaire for us?
 
        We periodically receive requests like this, from organisations
@@ -9850,7 +9973,7 @@ A.9.16 As one of our existing software vendors, can you just fill in this
        about you from free software developers who don't have any idea who
        you are. Then, only send out these mass mailings to the former.
 
-A.9.17 The `sha1sums' / `sha256sums' / etc files on your download page
+A.9.18 The `sha1sums' / `sha256sums' / etc files on your download page
        don't match the binaries.
 
        People report this every so often, and usually the reason turns out
@@ -9936,16 +10059,15 @@ Appendix B: Feedback and bug reporting
 
        The PuTTY development team gets a _lot_ of mail. If you can possibly
        solve your own problem by reading the manual, reading the FAQ,
-       reading the web site, asking a fellow user, perhaps posting to a
-       newsgroup (see section B.1.2), or some other means, then it would
-       make our lives much easier.
+       reading the web site, asking a fellow user, or some other means,
+       then it would make our lives much easier.
 
        We get so much e-mail that we literally do not have time to answer
        it all. We regret this, but there's nothing we can do about it.
        So if you can _possibly_ avoid sending mail to the PuTTY team, we
        recommend you do so. In particular, support requests (section B.7)
-       are probably better sent to newsgroups, or passed to a local expert
-       if possible.
+       are probably better sent to some public forum, or passed to a local
+       expert if possible.
 
        The PuTTY contact email address is a private mailing list containing
        four or five core developers. Don't be put off by it being a mailing
@@ -10014,24 +10136,6 @@ Appendix B: Feedback and bug reporting
        receive a mail that's 30 lines long than one containing multiple
        megabytes of data we already have.
 
- B.1.2 Other places to ask for help
-
-       There are two Usenet newsgroups that are particularly relevant to
-       the PuTTY tools:
-
-        -  `comp.security.ssh', for questions specific to using the SSH
-           protocol;
-
-        -  `comp.terminals', for issues relating to terminal emulation (for
-           instance, keyboard problems).
-
-       Please use the newsgroup most appropriate to your query, and
-       remember that these are general newsgroups, not specifically about
-       PuTTY.
-
-       If you don't have direct access to Usenet, you can access these
-       newsgroups through Google Groups (groups.google.com).
-
    B.2 Reporting bugs
 
        If you think you have found a bug in PuTTY, your first steps should
@@ -10119,11 +10223,11 @@ Appendix B: Feedback and bug reporting
        think the documentation is unclear or unhelpful. But we do need
        to be given exact details of _what_ you think the documentation
        has failed to tell you, or _how_ you think it could be made
-       clearer. If your problem is simply that you don't _understand_ the
-       documentation, we suggest posting to a newsgroup (see section B.1.2)
-       and seeing if someone will explain what you need to know. _Then_, if
-       you think the documentation could usefully have told you that, send
-       us a bug report and explain how you think we should change it.
+       clearer. If your problem is simply that you don't _understand_
+       the documentation, we suggest asking around and seeing if someone
+       will explain what you need to know. _Then_, if you think the
+       documentation could usefully have told you that, send us a bug
+       report and explain how you think we should change it.
 
    B.3 Reporting security vulnerabilities
 
@@ -10275,17 +10379,16 @@ Appendix B: Feedback and bug reporting
 
         -  If you want to do something with PuTTY but have no idea where to
            start, and reading the manual hasn't helped, try posting to a
-           newsgroup (see section B.1.2) and see if someone can explain it
-           to you.
+           public forum and see if someone can explain it to you.
 
         -  If you have tried to do something with PuTTY but it hasn't
            worked, and you aren't sure whether it's a bug in PuTTY or a bug
            in your SSH server or simply that you're not doing it right,
-           then try posting to a newsgroup (see section B.1.2) and see if
-           someone can solve your problem. Or try doing the same thing with
-           a different SSH client and see if it works with that. Please do
-           not report it as a PuTTY bug unless you are really sure it _is_
-           a bug in PuTTY.
+           then try posting to some public forum and see if someone can
+           solve your problem. Or try doing the same thing with a different
+           SSH client and see if it works with that. Please do not report
+           it as a PuTTY bug unless you are really sure it _is_ a bug in
+           PuTTY.
 
         -  If someone else installed PuTTY for you, or you're using PuTTY
            on someone else's computer, try asking them for help first.
@@ -11586,6 +11689,9 @@ Appendix F: PuTTY download keys and signatures
        Windows' own mechanism (`Authenticode'). The keys used for that are
        different, and are not covered here.
 
+       See question A.9.18 in the FAQ for some gotchas when verifying
+       checksums and signatures.
+
        (Note that none of the keys, signatures, etc mentioned here have
        anything to do with keys used with SSH - they are purely for
        verifying the origin of files distributed by the PuTTY team.)
@@ -12479,4 +12585,256 @@ H.6.12 PLUGIN_AUTH_FAILURE
        Secure Shell Protocol (SSH)' (better known by its wire id `keyboard-
        interactive').
 
-[PuTTY release 0.81]
+Appendix I: PuTTY privacy considerations
+----------------------------------------
+
+       This appendix lists the implications of using PuTTY for your privacy
+       and personal data.
+
+       The short summary: PuTTY never `phones home' to us, the developers.
+       It does store data on your own computer, and it does transmit data
+       over the network, but in both cases, only as necessary to do its
+       job. In particular, data is only transmitted over the network to the
+       server you told PuTTY to connect to.
+
+       But if you're concerned about exactly _what_ information is stored
+       or transmitted, then here's a more detailed description.
+
+   I.1 Information that PuTTY stores locally
+
+       When you use PuTTY, it stores a small amount of information on
+       your computer, necessary for doing its own job. This information
+       is stored in the user account of the user who runs PuTTY, so it is
+       under your control: you can view it, change it, or delete it.
+
+       If you need to delete all of this data, you can use the `-cleanup'
+       command-line option, as described in section 3.11.2.
+
+       PuTTY does not transmit your saved session data to any other site.
+       However, you may need to be aware of the fact that it is stored on
+       _your_ computer. (For example, somebody else accessing your computer
+       might be able to find a list of sites you have connected to, if you
+       have saved details of them.)
+
+ I.1.1 Host key cache
+
+       If you use the SSH protocol, then PuTTY stores a list of the SSH
+       servers you have connected to, together with their host keys.
+
+       This is known as the `host key cache'. It is used to detect network
+       attacks, by notifying you if a server you've connected to before
+       doesn't look like the same one you thought it was. (See section 2.2
+       for a basic introduction to host keys.)
+
+       The host key cache is optional. An entry is only saved in the host
+       key cache if you select the `Accept' action at one of the PuTTY
+       suite's host key verification prompts. So if you want to make an SSH
+       connection without PuTTY saving any trace of where you connected to,
+       you can press `Connect Once' instead of `Accept', which does not
+       store the host key in the cache.
+
+       However, if you do this, PuTTY can't automatically detect the host
+       key changing in the future, so you should check the key fingerprint
+       yourself every time you connect. *This is vitally important.* If you
+       don't let PuTTY cache host keys _and_ don't check them yourself,
+       then it becomes easy for an attacker to interpose a listener between
+       you and the server you're connecting to. The entire cryptographic
+       system of SSH depends on making sure the host key is right.
+
+       The host key cache is only used by SSH. No other protocol supported
+       by PuTTY has any analogue of it.
+
+ I.1.2 Saved sessions
+
+       After you set up PuTTY's configuration for a particular network
+       connection, you can choose to save it as a `saved session', so that
+       you can make the same connection again later without having to re-
+       enter all the details.
+
+       PuTTY will not do this unless you use the `Save' button in
+       its configuration box. It never saves session configuration
+       automatically.
+
+       So if you want to make an SSH connection without leaving any trace
+       of where you connected to, you should not make a saved session for
+       that connection. Instead, re-enter the details by hand every time
+       you do it.
+
+ I.1.3 Jump list
+
+       On Windows, the operating system provides a feature called a `jump
+       list'. This is a menu that pops up from an application's icon in
+       the Windows taskbar, and the application can configure entries that
+       appear in it. Applications typically include menu items to re-launch
+       recently used documents or configurations.
+
+       PuTTY updates its jump list whenever a saved session is loaded,
+       either to launch it immediately or to load it within the
+       configuration dialog box. So if you have a collection of saved
+       sessions, the jump list will contain a record of which ones you have
+       recently used.
+
+       An exception is that saved sessions are not included in the jump
+       list if they are not `launchable', meaning that they actually
+       specify a host name or serial port to connect to. A non-launchable
+       session can specify all the other configuration details (such as
+       fonts, window size, keyboard setup, SSH features, etc), but leave
+       out the hostname.
+
+       If you want to avoid leaving any evidence of having made a
+       particular connection, then make the connection without creating
+       a launchable saved session for it: either make no saved session
+       at all, or create a non-launchable one which sets up every detail
+       _except_ the destination host name. Then it won't appear in the jump
+       list.
+
+       (The saved session itself would also be evidence, of course, as
+       discussed in the previous section.)
+
+ I.1.4 Log files
+
+       PuTTY can be configured to save a log file of your entire session
+       to the computer you run it on. By default it does not do so: the
+       content of your session is not saved.
+
+       See section 4.2 for details of the logging features. Some logging
+       modes store only output sent by the server and printed in PuTTY's
+       terminal window. Other more thorough modes also store your input
+       that PuTTY sends _to_ the server.
+
+       If the logging feature is enabled, then by default, PuTTY will avoid
+       saving data in the log file that it knows to be sensitive, such as
+       passwords. However, it cannot reliably identify _all_ passwords. If
+       you use a password for your initial login to an SSH server, PuTTY
+       knows that is a password, and will omit it from the log file. But if
+       after login you type a password into an application on the server,
+       then PuTTY will not know that _that_ is a password, so it will
+       appear in the log file, if PuTTY is writing a type that includes
+       keyboard input.
+
+       PuTTY can also be configured to include all passwords in its log
+       files, even the ones it would normally leave out. This is intended
+       for debugging purposes, for example if a server is refusing your
+       password and you need to check whether the password is being sent
+       correctly. We do not recommend enabling this option routinely.
+
+ I.1.5 Random seed file
+
+       PuTTY stores a small file of random bytes under the name
+       `putty.rnd', which is reloaded the next time it is run and used to
+       seed its random number generator. These bytes are meaningless and
+       random, and do not contain an encrypted version of anything.
+
+   I.2 Sending information over the network
+
+       PuTTY is a communications tool. Its _purpose_ is to connect to
+       another computer, over a network or a serial port, and send
+       information. However it only makes the network connections that its
+       configuration instructs it to.
+
+ I.2.1 PuTTY only connects to the specified destination host
+
+       No PuTTY tool will `phone home' to any site under the control of
+       us (the development team), or to any other site apart from the
+       destination host or proxy host in its configuration, and any DNS
+       server that is needed to look up the IP addresses corresponding to
+       those host names.
+
+       No information about your network sessions, and no information from
+       the computer you run PuTTY on, is collected or recorded by the PuTTY
+       developers.
+
+       Information you provide to PuTTY (via keyboard input, the command
+       line, or files loaded by the file transfer tools) is sent to the
+       server that PuTTY's configuration tells it to connect to. It is not
+       sent anywhere else.
+
+ I.2.2 What data is sent to the destination host
+
+       When you log in to a server, PuTTY will send your username. If you
+       use a password to authenticate to the server, PuTTY will send it
+       that password as well.
+
+       (Therefore, the server is told what your password is during login.
+       This means that if you use the same password on two servers, the
+       administrator of one could find out your password and log in to your
+       account on the other.)
+
+       If you use an SSH private key to authenticate, PuTTY will send the
+       _public_ key, but not the private key. If you typed a passphrase to
+       decrypt the private key, PuTTY will not send the passphrase either.
+
+       (Therefore, it is safer to use the same _public key_ to authenticate
+       to two SSH servers. Neither server gains the ability to impersonate
+       you to the other server. However, if the server maintainers talked
+       to each other, they would at least be able to find out that your
+       accounts on the two machines were owned by the same person, if they
+       didn't already know.)
+
+       When PuTTY prompts for a private key passphrase, a small copy of the
+       PuTTY icon appears to the left of the prompt, to indicate that the
+       prompt was genuinely from PuTTY. (We call this a `trust sigil'.)
+       That icon never appears next to text sent from the server. So if a
+       server tries to mimic that prompt to trick you into telling it your
+       private key passphrase, it won't be able to fake that trust sigil,
+       and you can tell the difference.
+
+       If you're running Pageant, and you haven't configured a specific
+       public key to authenticate to this server, then PuTTY will try
+       all the keys in Pageant one after the other, sending each public
+       key to the server to see if it's acceptable. This can lead to the
+       server finding out about other public keys you own. However, if you
+       configure PuTTY to use a specific public key, then it will ignore
+       all the other keys in Pageant.
+
+       Once you have logged in, keystrokes you type in the PuTTY terminal
+       window, and data you paste in with the mouse, are sent to the
+       destination host. That is PuTTY's primary job.
+
+       The server can request PuTTY to send details of mouse movements in
+       the terminal window, in order to implement mouse-controlled user
+       interfaces on the server. If you consider this to be a privacy
+       intrusion, you can turn off that terminal feature in the Features
+       configuration panel (`Disable xterm-style mouse reporting', as
+       described in section 4.6.2).
+
+   I.3 Configuration
+
+       The operation of a PuTTY network tool is controlled by its
+       configuration. This configuration is obtained from:
+
+        -  the command line used to run the tool
+
+        -  settings configured in the GUI before opening a network session
+
+        -  optionally, the contents of a saved session, if the command line
+           or a GUI action instructed PuTTY to load one
+
+        -  the special saved session called `Default Settings', which
+           applies if no other saved session is loaded
+
+        -  defaults built in to PuTTY itself.
+
+       The defaults built in to PuTTY do not tell it to save log files, or
+       specify the name of any network site to connect to.
+
+       However, if PuTTY has been installed for you by somebody else,
+       such as an organisation, then that organisation may have provided
+       their own default configuration. In that situation you may wish
+       to check that the defaults they have set are compatible with your
+       privacy needs. For example, an organisation providing your PuTTY
+       configuration might configure PuTTY to save log files of your
+       sessions, even though PuTTY's own default is not to do so.
+
+   I.4 Modified versions of PuTTY
+
+       PuTTY is free software. Its source code is available, so anyone
+       can make a modified version of it. The modified version can behave
+       differently from the original in any way it likes.
+
+       This list of privacy considerations only applies to the original
+       version of PuTTY, as distributed by its development team. We
+       cannot make any promises about the behaviour of modified versions
+       distributed by other people.
+
+[PuTTY release 0.82]

+ 85 - 0
source/putty/doc/using.but

@@ -1184,3 +1184,88 @@ session at all. Instead, it will just display the configuration dialog
 box for host certification authorities, as described in
 \k{config-ssh-kex-cert}. When you dismiss that dialog box, PuTTY will
 terminate.
+
+\S2{using-cmdline-legacy-stdio-prompts} \i{\c{-legacy-stdio-prompts}}:
+handle Windows console prompts like older versions of PuTTY
+
+This option applies to all of PSCP, PSFTP and Plink on Windows: all
+the tools in the PuTTY suite that run in a Windows console and make
+SSH connections.
+
+These tools use the Windows console to prompt for various information:
+usernames, passwords, answers to questions about host keys, and so on.
+
+In current versions of PuTTY, these prompts work by direct access to
+the Windows console. This means that even if you redirect the standard
+input or output of the tool, prompts will \e{still} be sent to the
+console (and not where you've redirected your output), and the user's
+responses will be read from the console (and not from where you've
+redirected your input).
+
+Another advantage of reading directly from the Windows console is that
+the tools can read input as \i{Unicode}. So this also allows you to
+enter usernames and passwords that contain characters not in the
+Windows system's default character set.
+
+In versions of the PuTTY tools up to and including 0.81, the prompts
+used the tool's ordinary I/O handles, so prompt output and user
+responses could be redirected.
+
+We think the new behaviour is more likely to be useful. For example,
+if you have a local command that generates output, and you want to
+pipe that output into a command running remotely via Plink, you can
+run a command line such as
+
+\c local_command | plink hostname remote_command
+
+and the data piped into the remote command will be the same whether or
+not Plink has to stop to ask for a password. With the old behaviour
+you would have had to include the password in Plink's input, which is
+more awkward.
+
+However, we recognise that people may have customised complicated
+workflows around the old behaviour. So if you need to switch back to
+it, you can do so by specifying \c{-legacy-stdio-prompts} on the
+command-line.
+
+To fully revert to the previous behaviour, you'd also need to specify
+\c{-legacy-charset-handling} (see the next section). (Even without
+that option, prompt handling with \c{-legacy-stdio-prompts} may not be
+fully Unicode-clean.)
+
+\S2{using-cmdline-legacy-charset-handling} \i{\c{-legacy-charset-handling}}:
+handle character set in prompts like older versions of PuTTY
+
+This option applies to PuTTY (on all platforms), and also to all of
+PSCP, PSFTP and Plink on Windows.
+
+In current versions of PuTTY, when you are prompted in the terminal
+window for things like SSH usernames and passwords, the responses you
+type are interpreted as \i{Unicode}, and transmitted to the server as
+such, even if the terminal is otherwise configured to use a different
+character encoding (see \k{config-charset}). Similarly, the same
+prompts from the Windows console tools will unconditionally interpret
+their input as Unicode.
+
+This behaviour is in line with the SSH standards; it allows things
+like usernames to use the full character set of the user's native
+language, and ensures that different keystrokes you type for your
+password are actually treated distinctly.
+
+However, if you are used to the behaviour of the PuTTY tools up to
+version 0.81, this could cause a previously working username and/or
+password not to work as you expected. For instance, if you had set a
+password including some \i{accented characters}, this change in
+behaviour could cause the same keystrokes you've always entered to
+start sending a different sequence of bytes to the server, denying you
+access (and you wouldn't even be able to see the difference, since the
+password is not shown when you type it).
+
+\c{-legacy-charset-handling} reverts the PuTTY tools' behaviour to how
+it was previously: what you type at these prompts will be interpreted
+according to the \q{Remote character set} (for PuTTY) or Windows'
+default character set (for the Windows console tools).
+
+(For example, this could allow you to log in to change your password
+to make using this option unnecessary in future. But if you're doing
+that, make sure the terminal is configured as UTF-8!)

+ 1 - 1
source/putty/doc/version.but

@@ -1 +1 @@
-\versionid PuTTY release 0.81
+\versionid PuTTY release 0.82

+ 1 - 6
source/putty/errsock.c

@@ -39,16 +39,11 @@ static const char *sk_error_socket_error(Socket *s)
     return es->error;
 }
 
-static SocketPeerInfo *sk_error_peer_info(Socket *s)
-{
-    return NULL;
-}
-
 static const SocketVtable ErrorSocket_sockvt = {
     .plug = sk_error_plug,
     .close = sk_error_close,
     .socket_error = sk_error_socket_error,
-    .peer_info = sk_error_peer_info,
+    .endpoint_info = nullsock_endpoint_info,
     /* other methods are NULL */
 };
 

+ 58 - 58
source/putty/import.c

@@ -339,7 +339,7 @@ static void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str)
 static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
                                                     const char **errmsg_p)
 {
-    struct openssh_pem_key *ret;
+    struct openssh_pem_key *key;
     char *line = NULL;
     const char *errmsg;
     char *p;
@@ -347,8 +347,8 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
     char base64_bit[4];
     int base64_chars = 0;
 
-    ret = snew(struct openssh_pem_key);
-    ret->keyblob = strbuf_new_nm();
+    key = snew(struct openssh_pem_key);
+    key->keyblob = strbuf_new_nm();
 
     if (!(line = bsgetline(src))) {
         errmsg = "unexpected end of file";
@@ -366,11 +366,11 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
      * base64.
      */
     if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) {
-        ret->keytype = OP_RSA;
+        key->keytype = OP_RSA;
     } else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) {
-        ret->keytype = OP_DSA;
+        key->keytype = OP_DSA;
     } else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----")) {
-        ret->keytype = OP_ECDSA;
+        key->keytype = OP_ECDSA;
     } else if (!strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) {
         errmsg = "this is a new-style OpenSSH key";
         goto error;
@@ -382,8 +382,8 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
     sfree(line);
     line = NULL;
 
-    ret->encrypted = false;
-    memset(ret->iv, 0, sizeof(ret->iv));
+    key->encrypted = false;
+    memset(key->iv, 0, sizeof(key->iv));
 
     headers_done = false;
     while (1) {
@@ -411,15 +411,15 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
                 }
                 p += 2;
                 if (!strcmp(p, "ENCRYPTED"))
-                    ret->encrypted = true;
+                    key->encrypted = true;
             } else if (!strcmp(line, "DEK-Info")) {
                 int i, ivlen;
 
                 if (!strncmp(p, "DES-EDE3-CBC,", 13)) {
-                    ret->encryption = OP_E_3DES;
+                    key->encryption = OP_E_3DES;
                     ivlen = 8;
                 } else if (!strncmp(p, "AES-128-CBC,", 12)) {
-                    ret->encryption = OP_E_AES;
+                    key->encryption = OP_E_AES;
                     ivlen = 16;
                 } else {
                     errmsg = "unsupported cipher";
@@ -432,7 +432,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
                         errmsg = "expected more iv data in DEK-Info";
                         goto error;
                     }
-                    ret->iv[i] = j;
+                    key->iv[i] = j;
                     p += 2;
                 }
                 if (*p) {
@@ -459,7 +459,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
                         goto error;
                     }
 
-                    put_data(ret->keyblob, out, len);
+                    put_data(key->keyblob, out, len);
 
                     smemclr(out, sizeof(out));
                 }
@@ -472,12 +472,12 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
         line = NULL;
     }
 
-    if (!ret->keyblob || ret->keyblob->len == 0) {
+    if (!key->keyblob || key->keyblob->len == 0) {
         errmsg = "key body not present";
         goto error;
     }
 
-    if (ret->encrypted && ret->keyblob->len % 8 != 0) {
+    if (key->encrypted && key->keyblob->len % 8 != 0) {
         errmsg = "encrypted key blob is not a multiple of "
             "cipher block size";
         goto error;
@@ -485,7 +485,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
 
     smemclr(base64_bit, sizeof(base64_bit));
     if (errmsg_p) *errmsg_p = NULL;
-    return ret;
+    return key;
 
   error:
     if (line) {
@@ -494,11 +494,11 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
         line = NULL;
     }
     smemclr(base64_bit, sizeof(base64_bit));
-    if (ret) {
-        if (ret->keyblob)
-            strbuf_free(ret->keyblob);
-        smemclr(ret, sizeof(*ret));
-        sfree(ret);
+    if (key) {
+        if (key->keyblob)
+            strbuf_free(key->keyblob);
+        smemclr(key, sizeof(*key));
+        sfree(key);
     }
     if (errmsg_p) *errmsg_p = errmsg;
     return NULL;
@@ -1119,7 +1119,7 @@ struct openssh_new_key {
 static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
                                                     const char **errmsg_p)
 {
-    struct openssh_new_key *ret;
+    struct openssh_new_key *key;
     char *line = NULL;
     const char *errmsg;
     char *p;
@@ -1129,8 +1129,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
     ptrlen str;
     unsigned key_index;
 
-    ret = snew(struct openssh_new_key);
-    ret->keyblob = strbuf_new_nm();
+    key = snew(struct openssh_new_key);
+    key->keyblob = strbuf_new_nm();
 
     if (!(line = bsgetline(filesrc))) {
         errmsg = "unexpected end of file";
@@ -1171,7 +1171,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
                     goto error;
                 }
 
-                put_data(ret->keyblob, out, len);
+                put_data(key->keyblob, out, len);
 
                 smemclr(out, sizeof(out));
             }
@@ -1183,12 +1183,12 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
         line = NULL;
     }
 
-    if (ret->keyblob->len == 0) {
+    if (key->keyblob->len == 0) {
         errmsg = "key body not present";
         goto error;
     }
 
-    BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ret->keyblob));
+    BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->keyblob));
 
     if (strcmp(get_asciz(src), "openssh-key-v1") != 0) {
         errmsg = "new-style OpenSSH magic number missing\n";
@@ -1198,11 +1198,11 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
     /* Cipher name */
     str = get_string(src);
     if (ptrlen_eq_string(str, "none")) {
-        ret->cipher = ON_E_NONE;
+        key->cipher = ON_E_NONE;
     } else if (ptrlen_eq_string(str, "aes256-cbc")) {
-        ret->cipher = ON_E_AES256CBC;
+        key->cipher = ON_E_AES256CBC;
     } else if (ptrlen_eq_string(str, "aes256-ctr")) {
-        ret->cipher = ON_E_AES256CTR;
+        key->cipher = ON_E_AES256CTR;
     } else {
         errmsg = get_err(src) ? "no cipher name found" :
             "unrecognised cipher name\n";
@@ -1212,9 +1212,9 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
     /* Key derivation function name */
     str = get_string(src);
     if (ptrlen_eq_string(str, "none")) {
-        ret->kdf = ON_K_NONE;
+        key->kdf = ON_K_NONE;
     } else if (ptrlen_eq_string(str, "bcrypt")) {
-        ret->kdf = ON_K_BCRYPT;
+        key->kdf = ON_K_BCRYPT;
     } else {
         errmsg = get_err(src) ? "no kdf name found" :
             "unrecognised kdf name\n";
@@ -1223,7 +1223,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
 
     /* KDF extra options */
     str = get_string(src);
-    switch (ret->kdf) {
+    switch (key->kdf) {
       case ON_K_NONE:
         if (str.len != 0) {
             errmsg = "expected empty options string for 'none' kdf";
@@ -1234,8 +1234,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
         BinarySource opts[1];
 
         BinarySource_BARE_INIT_PL(opts, str);
-        ret->kdfopts.bcrypt.salt = get_string(opts);
-        ret->kdfopts.bcrypt.rounds = get_uint32(opts);
+        key->kdfopts.bcrypt.salt = get_string(opts);
+        key->kdfopts.bcrypt.rounds = get_uint32(opts);
 
         if (get_err(opts)) {
             errmsg = "failed to parse bcrypt options string";
@@ -1257,23 +1257,23 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
      * 'key_wanted' field is set to a value in the range [0,
      * nkeys) by some mechanism.
      */
-    ret->nkeys = toint(get_uint32(src));
-    if (ret->nkeys != 1) {
+    key->nkeys = toint(get_uint32(src));
+    if (key->nkeys != 1) {
         errmsg = get_err(src) ? "no key count found" :
             "multiple keys in new-style OpenSSH key file not supported\n";
         goto error;
     }
-    ret->key_wanted = 0;
+    key->key_wanted = 0;
 
     /* Read and ignore a string per public key. */
-    for (key_index = 0; key_index < ret->nkeys; key_index++)
+    for (key_index = 0; key_index < key->nkeys; key_index++)
         str = get_string(src);
 
     /*
      * Now we expect a string containing the encrypted part of the
      * key file.
      */
-    ret->private = get_string(src);
+    key->private = get_string(src);
     if (get_err(src)) {
         errmsg = "no private key container string found\n";
         goto error;
@@ -1285,7 +1285,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
 
     smemclr(base64_bit, sizeof(base64_bit));
     if (errmsg_p) *errmsg_p = NULL;
-    return ret;
+    return key;
 
   error:
     if (line) {
@@ -1294,10 +1294,10 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
         line = NULL;
     }
     smemclr(base64_bit, sizeof(base64_bit));
-    if (ret) {
-        strbuf_free(ret->keyblob);
-        smemclr(ret, sizeof(*ret));
-        sfree(ret);
+    if (key) {
+        strbuf_free(key->keyblob);
+        smemclr(key, sizeof(*key));
+        sfree(key);
     }
     if (errmsg_p) *errmsg_p = errmsg;
     return NULL;
@@ -1725,7 +1725,7 @@ struct sshcom_key {
 static struct sshcom_key *load_sshcom_key(BinarySource *src,
                                           const char **errmsg_p)
 {
-    struct sshcom_key *ret;
+    struct sshcom_key *key;
     char *line = NULL;
     int hdrstart, len;
     const char *errmsg;
@@ -1734,9 +1734,9 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src,
     char base64_bit[4];
     int base64_chars = 0;
 
-    ret = snew(struct sshcom_key);
-    ret->comment[0] = '\0';
-    ret->keyblob = strbuf_new_nm();
+    key = snew(struct sshcom_key);
+    key->comment[0] = '\0';
+    key->keyblob = strbuf_new_nm();
 
     if (!(line = bsgetline(src))) {
         errmsg = "unexpected end of file";
@@ -1803,8 +1803,8 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src,
                     p++;
                     p[strlen(p)-1] = '\0';
                 }
-                strncpy(ret->comment, p, sizeof(ret->comment));
-                ret->comment[sizeof(ret->comment)-1] = '\0';
+                strncpy(key->comment, p, sizeof(key->comment));
+                key->comment[sizeof(key->comment)-1] = '\0';
             }
         } else {
             headers_done = true;
@@ -1824,7 +1824,7 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src,
                         goto error;
                     }
 
-                    put_data(ret->keyblob, out, len);
+                    put_data(key->keyblob, out, len);
                 }
 
                 p++;
@@ -1835,13 +1835,13 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src,
         line = NULL;
     }
 
-    if (ret->keyblob->len == 0) {
+    if (key->keyblob->len == 0) {
         errmsg = "key body not present";
         goto error;
     }
 
     if (errmsg_p) *errmsg_p = NULL;
-    return ret;
+    return key;
 
   error:
     if (line) {
@@ -1849,10 +1849,10 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src,
         sfree(line);
         line = NULL;
     }
-    if (ret) {
-        strbuf_free(ret->keyblob);
-        smemclr(ret, sizeof(*ret));
-        sfree(ret);
+    if (key) {
+        strbuf_free(key->keyblob);
+        smemclr(key, sizeof(*key));
+        sfree(key);
     }
     if (errmsg_p) *errmsg_p = errmsg;
     return NULL;

+ 0 - 20
source/putty/logging.c

@@ -254,26 +254,6 @@ void logevent(LogContext *ctx, const char *event)
     }
 }
 
-void logevent_and_free(LogContext *ctx, char *event)
-{
-    logevent(ctx, event);
-    sfree(event);
-}
-
-void logeventvf(LogContext *ctx, const char *fmt, va_list ap)
-{
-    logevent_and_free(ctx, dupvprintf(fmt, ap));
-}
-
-void logeventf(LogContext *ctx, const char *fmt, ...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    logeventvf(ctx, fmt, ap);
-    va_end(ap);
-}
-
 /*
  * Log an SSH packet.
  * If n_blanks != 0, blank or omit some parts.

+ 24 - 0
source/putty/marshal.h

@@ -156,6 +156,16 @@ struct BinarySink {
 #define put_c_string_literal(bs, str) \
     BinarySink_put_c_string_literal(BinarySink_UPCAST(bs), str)
 
+/* More complicated function implemented in encode_utf8.c */
+#define put_utf8_char(bs, c) \
+    BinarySink_put_utf8_char(BinarySink_UPCAST(bs), c)
+
+/* More complicated functions still implemented in <platform>/unicode.c */
+#define put_mb_to_wc(bs, codepage, mbstr, mblen) \
+    BinarySink_put_mb_to_wc(BinarySink_UPCAST(bs), codepage, mbstr, mblen)
+#define put_wc_to_mb(bs, codepage, wcstr, wclen, def) \
+    BinarySink_put_wc_to_mb(BinarySink_UPCAST(bs), codepage, wcstr, wclen, def)
+
 /*
  * The underlying real C functions that implement most of those
  * macros. Generally you won't want to call these directly, because
@@ -185,6 +195,13 @@ void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x);
 void BinarySink_put_fmt(BinarySink *, const char *fmt, ...) PRINTF_LIKE(2, 3);
 void BinarySink_put_fmtv(BinarySink *, const char *fmt, va_list ap);
 void BinarySink_put_c_string_literal(BinarySink *, ptrlen);
+void BinarySink_put_utf8_char(BinarySink *, unsigned);
+/* put_mb_to_wc / put_wc_to_mb return false if the codepage is invalid */
+bool BinarySink_put_mb_to_wc(
+    BinarySink *bs, int codepage, const char *mbstr, int mblen);
+bool BinarySink_put_wc_to_mb(
+    BinarySink *bs, int codepage, const wchar_t *wcstr, int wclen,
+    const char *defchr);
 
 /* ---------------------------------------------------------------------- */
 
@@ -353,7 +370,14 @@ struct bufchain_sink {
     bufchain *ch;
     BinarySink_IMPLEMENTATION;
 };
+struct buffer_sink {
+    char *out;
+    size_t space;
+    bool overflowed;
+    BinarySink_IMPLEMENTATION;
+};
 void stdio_sink_init(stdio_sink *sink, FILE *fp);
 void bufchain_sink_init(bufchain_sink *sink, bufchain *ch);
+void buffer_sink_init(buffer_sink *sink, void *buffer, size_t len);
 
 #endif /* PUTTY_MARSHAL_H */

+ 48 - 13
source/putty/misc.h

@@ -26,11 +26,13 @@ char *host_strrchr(const char *s, int c);
 char *host_strduptrim(const char *s);
 
 char *dupstr(const char *s);
+wchar_t *dupwcs(const wchar_t *s);
 char *dupcat_fn(const char *s1, ...);
 #define dupcat(...) dupcat_fn(__VA_ARGS__, (const char *)NULL)
 char *dupprintf(const char *fmt, ...) PRINTF_LIKE(1, 2);
 char *dupvprintf(const char *fmt, va_list ap);
 void burnstr(char *string);
+void burnwcs(wchar_t *string);
 
 /*
  * The visible part of a strbuf structure. There's a surrounding
@@ -69,11 +71,12 @@ void strbuf_finalise_agent_query(strbuf *buf);
 
 /* String-to-Unicode converters that auto-allocate the destination and
  * work around the rather deficient interface of mb_to_wc. */
-wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len);
-wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string);
-char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len,
-                     const char *defchr);
-char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string,
+wchar_t *dup_mb_to_wc_c(int codepage, const char *string,
+                        size_t len, size_t *outlen_p);
+wchar_t *dup_mb_to_wc(int codepage, const char *string);
+char *dup_wc_to_mb_c(int codepage, const wchar_t *string,
+                     size_t len, const char *defchr, size_t *outlen_p);
+char *dup_wc_to_mb(int codepage, const wchar_t *string,
                    const char *defchr);
 
 static inline int toint(unsigned u)
@@ -248,26 +251,53 @@ void smemclr(void *b, size_t len);
  * by the 'eq' in the name. */
 unsigned smemeq(const void *av, const void *bv, size_t len);
 
-/* Encode a single UTF-8 character. Assumes that illegal characters
- * (such as things in the surrogate range, or > 0x10FFFF) have already
- * been removed. */
-size_t encode_utf8(void *output, unsigned long ch);
-
 /* Encode a wide-character string into UTF-8. Tolerates surrogates if
  * sizeof(wchar_t) == 2, assuming that in that case the wide string is
  * encoded in UTF-16. */
 char *encode_wide_string_as_utf8(const wchar_t *wstr);
 
+/* Decode UTF-8 to a wide-character string, emitting UTF-16 surrogates
+ * if sizeof(wchar_t) == 2. */
+wchar_t *decode_utf8_to_wide_string(const char *ustr);
+
 /* Decode a single UTF-8 character. Returns U+FFFD for any of the
- * illegal cases. */
-unsigned long decode_utf8(const char **utf8);
+ * illegal cases. If the source is empty, returns L'\0' (and sets the
+ * error indicator on the source, of course). */
+#define DECODE_UTF8_FAILURE_LIST(X) \
+    X(DUTF8_SUCCESS, "success")                                      \
+    X(DUTF8_SPURIOUS_CONTINUATION, "spurious continuation byte")     \
+    X(DUTF8_ILLEGAL_BYTE, "illegal UTF-8 byte value")                \
+    X(DUTF8_E_OUT_OF_DATA, "unfinished multibyte encoding at end of string") \
+    X(DUTF8_TRUNCATED_SEQUENCE, "multibyte encoding interrupted by " \
+      "non-continuation byte")                                       \
+    X(DUTF8_OVERLONG_ENCODING, "overlong encoding")                  \
+    X(DUTF8_ENCODED_SURROGATE, "Unicode surrogate character encoded in " \
+      "UTF-8")                                                       \
+    X(DUTF8_CODE_POINT_TOO_BIG, "code point outside the Unicode range") \
+    /* end of list */
+typedef enum DecodeUTF8Failure {
+    #define ENUM_DECL(sym, string) sym,
+    DECODE_UTF8_FAILURE_LIST(ENUM_DECL)
+    #undef ENUM_DECL
+    DUTF8_N_FAILURE_CODES
+} DecodeUTF8Failure;
+unsigned decode_utf8(BinarySource *src, DecodeUTF8Failure *err);
+extern const char *const decode_utf8_error_strings[DUTF8_N_FAILURE_CODES];
 
 /* Decode a single UTF-8 character to an output buffer of the
  * platform's wchar_t. May write a pair of surrogates if
  * sizeof(wchar_t) == 2, assuming that in that case the wide string is
  * encoded in UTF-16. Otherwise, writes one character. Returns the
  * number written. */
-size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out);
+size_t decode_utf8_to_wchar(BinarySource *src, wchar_t *out,
+                            DecodeUTF8Failure *err);
+
+/* Normalise a UTF-8 string into Normalisation Form C. */
+strbuf *utf8_to_nfc(ptrlen input);
+
+/* Determine if a UTF-8 string contains any characters unknown to our
+ * supported version of Unicode. */
+char *utf8_unknown_char(ptrlen input);
 
 /* Write a string out in C string-literal format. */
 void write_c_string_literal(FILE *fp, ptrlen str);
@@ -309,6 +339,11 @@ void debug_memdump(const void *buf, int len, bool L);
 #define debug(...) (debug_printf(__VA_ARGS__))
 #define dmemdump(buf,len) (debug_memdump(buf, len, false))
 #define dmemdumpl(buf,len) (debug_memdump(buf, len, true))
+
+/* Functions used only for debugging, not declared unless
+ * defined(DEBUG) to avoid accidentally linking them in production */
+const char *conf_id(int key);
+
 #else
 #define debug(...) ((void)0)
 #define dmemdump(buf,len) ((void)0)

+ 24 - 16
source/putty/network.h

@@ -34,7 +34,7 @@ struct SocketVtable {
     void (*set_frozen) (Socket *s, bool is_frozen);
     /* ignored by tcp, but vital for ssl */
     const char *(*socket_error) (Socket *s);
-    SocketPeerInfo *(*peer_info) (Socket *s);
+    SocketEndpointInfo *(*endpoint_info) (Socket *s, bool peer);
 };
 
 typedef union { void *p; int i; } accept_ctx_t;
@@ -91,7 +91,7 @@ struct PlugVtable {
      * all Plugs must implement this method, even if only to ignore
      * the logged events.
      */
-    void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port,
+    void (*log)(Plug *p, Socket *s, PlugLogType type, SockAddr *addr, int port,
                 const char *error_msg, int error_code);
 
     /*
@@ -245,8 +245,9 @@ static inline void sk_write_eof(Socket *s)
 { s->vt->write_eof(s); }
 
 static inline void plug_log(
-    Plug *p, int type, SockAddr *addr, int port, const char *msg, int code)
-{ p->vt->log(p, type, addr, port, msg, code); }
+    Plug *p, Socket *s, int type, SockAddr *addr, int port,
+    const char *msg, int code)
+{ p->vt->log(p, s, type, addr, port, msg, code); }
 static inline void plug_closing(Plug *p, PlugCloseType type, const char *msg)
 { p->vt->closing(p, type, msg); }
 static inline void plug_closing_normal(Plug *p)
@@ -292,23 +293,25 @@ static inline void sk_set_frozen(Socket *s, bool is_frozen)
 { s->vt->set_frozen(s, is_frozen); }
 
 /*
- * Return a structure giving some information about the other end of
+ * Return a structure giving some information about one end of
  * the socket. May be NULL, if nothing is available at all. If it is
  * not NULL, then it is dynamically allocated, and should be freed by
- * a call to sk_free_peer_info(). See below for the definition.
+ * a call to sk_free_endpoint_info(). See below for the definition.
  */
-static inline SocketPeerInfo *sk_peer_info(Socket *s)
-{ return s->vt->peer_info(s); }
+static inline SocketEndpointInfo *sk_endpoint_info(Socket *s, bool peer)
+{ return s->vt->endpoint_info(s, peer); }
+static inline SocketEndpointInfo *sk_peer_info(Socket *s)
+{ return sk_endpoint_info(s, true); }
 
 /*
- * The structure returned from sk_peer_info, and a function to free
+ * The structure returned from sk_endpoint_info, and a function to free
  * one (in utils).
  */
-struct SocketPeerInfo {
+struct SocketEndpointInfo {
     int addressfamily;
 
     /*
-     * Text form of the IPv4 or IPv6 address of the other end of the
+     * Text form of the IPv4 or IPv6 address of the specified end of the
      * socket, if available, in the standard text representation.
      */
     const char *addr_text;
@@ -337,7 +340,7 @@ struct SocketPeerInfo {
      */
     const char *log_text;
 };
-void sk_free_peer_info(SocketPeerInfo *pi);
+void sk_free_endpoint_info(SocketEndpointInfo *ei);
 
 /*
  * Simple wrapper on getservbyname(), needed by portfwd.c. Returns the
@@ -377,19 +380,24 @@ extern Plug *const nullplug;
  * In particular, nullplug_log is useful to Plugs that don't need to
  * worry about logging.
  */
-void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr,
+void nullplug_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr,
                   int port, const char *err_msg, int err_code);
 void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg);
 void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len);
 void nullplug_sent(Plug *plug, size_t bufsize);
 
+/*
+ * Similar no-op socket function.
+ */
+SocketEndpointInfo *nullsock_endpoint_info(Socket *s, bool peer);
+
 /* ----------------------------------------------------------------------
  * Functions defined outside the network code, which have to be
  * declared in this header file rather than the main putty.h because
  * they use types defined here.
  */
 
-void backend_socket_log(Seat *seat, LogContext *logctx,
+void backend_socket_log(Seat *seat, LogContext *logctx, Socket *sock,
                         PlugLogType type, SockAddr *addr, int port,
                         const char *error_msg, int error_code, Conf *conf,
                         bool session_started);
@@ -401,8 +409,8 @@ typedef struct ProxyStderrBuf {
 } ProxyStderrBuf;
 void psb_init(ProxyStderrBuf *psb);
 void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix);
-void log_proxy_stderr(
-    Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len);
+void log_proxy_stderr(Plug *plug, Socket *sock, ProxyStderrBuf *psb,
+                      const void *vdata, size_t len);
 
 /* ----------------------------------------------------------------------
  * The DeferredSocketOpener trait. This is a thing that some Socket

+ 2 - 1
source/putty/proxy/local.c

@@ -209,7 +209,8 @@ static void local_proxy_opener_coroutine(void *vctx)
         put_datapl(logmsg, PTRLEN_LITERAL("Starting local proxy command: "));
         put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd));
 
-        plug_log(lp->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0);
+        plug_log(lp->plug, lp->socket, PLUGLOG_PROXY_MSG, NULL, 0,
+                 logmsg->s, 0);
         strbuf_free(logmsg);
         sfree(censored_cmd);
     }

+ 23 - 10
source/putty/proxy/proxy.c

@@ -41,7 +41,7 @@ static void proxy_activate(ProxySocket *ps)
 
     proxy_negotiator_cleanup(ps);
 
-    plug_log(ps->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0);
+    plug_log(ps->plug, &ps->sock, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0);
 
     /* we want to ignore new receive events until we have sent
      * all of our buffered receive data.
@@ -189,12 +189,13 @@ static const char *sk_proxy_socket_error (Socket *s)
 
 /* basic proxy plug functions */
 
-static void plug_proxy_log(Plug *plug, PlugLogType type, SockAddr *addr,
-                           int port, const char *error_msg, int error_code)
+static void plug_proxy_log(Plug *plug, Socket *s, PlugLogType type,
+                           SockAddr *addr, int port,
+                           const char *error_msg, int error_code)
 {
     ProxySocket *ps = container_of(plug, ProxySocket, plugimpl);
 
-    plug_log(ps->plug, type, addr, port, error_msg, error_code);
+    plug_log(ps->plug, &ps->sock, type, addr, port, error_msg, error_code);
 }
 
 static void plug_proxy_closing(Plug *p, PlugCloseType type,
@@ -415,6 +416,19 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname,
     }
 }
 
+static SocketEndpointInfo *sk_proxy_endpoint_info(Socket *s, bool peer)
+{
+    ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+    /* We can't reliably find out where we ended up connecting _to_:
+     * that's at the far end of the proxy, and might be anything. */
+    if (peer)
+        return NULL;
+
+    /* But we can at least tell where we're coming _from_. */
+    return sk_endpoint_info(ps->sub_socket, false);
+}
+
 static const SocketVtable ProxySocket_sockvt = {
     .plug = sk_proxy_plug,
     .close = sk_proxy_close,
@@ -423,7 +437,7 @@ static const SocketVtable ProxySocket_sockvt = {
     .write_eof = sk_proxy_write_eof,
     .set_frozen = sk_proxy_set_frozen,
     .socket_error = sk_proxy_socket_error,
-    .peer_info = NULL,
+    .endpoint_info = sk_proxy_endpoint_info,
 };
 
 static const PlugVtable ProxySocket_plugvt = {
@@ -499,8 +513,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
     int type = conf_get_int(conf, CONF_proxy_type);
 
     if (type != PROXY_NONE &&
-        proxy_for_destination(addr, hostname, port, conf))
-    {
+        proxy_for_destination(addr, hostname, port, conf)) {
         ProxySocket *ps;
         SockAddr *proxy_addr;
         char *proxy_canonical_name;
@@ -585,7 +598,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
                                      conf_get_str(conf, CONF_proxy_host),
                                      conf_get_int(conf, CONF_proxy_port),
                                      hostname, port);
-            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
+            plug_log(plug, &ps->sock, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             sfree(logmsg);
         }
 
@@ -593,7 +606,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
             char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host),
                                        conf_get_int(conf, CONF_addressfamily),
                                        "proxy");
-            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
+            plug_log(plug, &ps->sock, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             sfree(logmsg);
         }
 
@@ -614,7 +627,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
             logmsg = dupprintf("Connecting to %s proxy at %s port %d",
                                vt->type, addrbuf,
                                conf_get_int(conf, CONF_proxy_port));
-            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
+            plug_log(plug, &ps->sock, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
             sfree(logmsg);
         }
 

+ 2 - 1
source/putty/proxy/telnet.c

@@ -339,7 +339,8 @@ static void proxy_telnet_process_queue(ProxyNegotiator *pn)
         put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: "));
         put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd));
 
-        plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0);
+        plug_log(pn->ps->plug, &pn->ps->sock, PLUGLOG_PROXY_MSG, NULL, 0,
+                 logmsg->s, 0);
         strbuf_free(logmsg);
         sfree(censored_cmd);
     }

+ 207 - 358
source/putty/putty.h

@@ -286,64 +286,21 @@ struct unicode_data {
 #define LGTYP_PACKETS 3                /* logmode: SSH data packets */
 #define LGTYP_SSHRAW 4                 /* logmode: SSH raw data */
 
+/* Platform-generic function to set up a struct unicode_data. This is
+ * only likely to be useful to test programs; real clients will want
+ * to use the more flexible per-platform setup functions. */
+void init_ucs_generic(Conf *conf, struct unicode_data *ucsdata);
+
 /*
  * Enumeration of 'special commands' that can be sent during a
  * session, separately from the byte stream of ordinary session data.
  */
 typedef enum {
-    /*
-     * Commands that are generally useful in multiple backends.
-     */
-    SS_BRK,    /* serial-line break */
-    SS_EOF,    /* end-of-file on session input */
-    SS_NOP,    /* transmit data with no effect */
-    SS_PING,   /* try to keep the session alive (probably, but not
-                * necessarily, implemented as SS_NOP) */
-
-    /*
-     * Commands specific to Telnet.
-     */
-    SS_AYT,    /* Are You There */
-    SS_SYNCH,  /* Synch */
-    SS_EC,     /* Erase Character */
-    SS_EL,     /* Erase Line */
-    SS_GA,     /* Go Ahead */
-    SS_ABORT,  /* Abort Process */
-    SS_AO,     /* Abort Output */
-    SS_IP,     /* Interrupt Process */
-    SS_SUSP,   /* Suspend Process */
-    SS_EOR,    /* End Of Record */
-    SS_EOL,    /* Telnet end-of-line sequence (CRLF, as opposed to CR
-                * NUL that escapes a literal CR) */
-
-    /*
-     * Commands specific to SSH.
-     */
-    SS_REKEY,  /* trigger an immediate repeat key exchange */
-    SS_XCERT,  /* cross-certify another host key ('arg' indicates which) */
-
-    /*
-     * Send a POSIX-style signal. (Useful in SSH and also pterm.)
-     *
-     * We use the master list in ssh/signal-list.h to define these enum
-     * values, which will come out looking like names of the form
-     * SS_SIGABRT, SS_SIGINT etc.
-     */
-    #define SIGNAL_MAIN(name, text) SS_SIG ## name,
-    #define SIGNAL_SUB(name) SS_SIG ## name,
-    #include "ssh/signal-list.h"
-    #undef SIGNAL_MAIN
-    #undef SIGNAL_SUB
-
-    /*
-     * These aren't really special commands, but they appear in the
-     * enumeration because the list returned from
-     * backend_get_specials() will use them to specify the structure
-     * of the GUI specials menu.
-     */
-    SS_SEP,         /* Separator */
-    SS_SUBMENU,     /* Start a new submenu with specified name */
-    SS_EXITMENU,    /* Exit current submenu, or end of entire specials list */
+    /* The list of enum constants is defined in a separate header so
+     * they can be reused in other contexts */
+    #define SPECIAL(x) SS_ ## x,
+    #include "specials.h"
+    #undef SPECIAL
 } SessionSpecialCode;
 
 /*
@@ -530,6 +487,13 @@ enum {
     RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER
 };
 
+enum {
+    /* Mouse-button assignments */
+    MOUSE_COMPROMISE, /* xterm-ish but with paste on RB in case no MB exists */
+    MOUSE_XTERM, /* xterm-style: MB pastes, RB extends selection */
+    MOUSE_WINDOWS /* Windows-style: RB brings up menu. MB still extends. */
+};
+
 enum {
     /* Function key types (CONF_funky_type) */
     FUNKY_TILDE,
@@ -551,6 +515,16 @@ enum {
     FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE
 };
 
+enum {
+    CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_VERTICAL_LINE
+};
+
+enum {
+    /* these are really bit flags */
+    BOLD_STYLE_FONT = 1,
+    BOLD_STYLE_COLOUR = 2,
+};
+
 enum {
     SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE
 };
@@ -996,6 +970,17 @@ struct prompts_t {
                          * seat_get_userpass_input(); initially NULL */
     SeatPromptResult spr; /* some implementations need to cache one of these */
 
+    /*
+     * Set this flag to indicate that the caller has encoded the
+     * prompts in UTF-8, and expects the responses to be UTF-8 too.
+     *
+     * Ideally this flag would be unnecessary because it would always
+     * be true, but for legacy reasons, we have to switch over a bit
+     * at a time from the old behaviour, and may never manage to get
+     * rid of it completely.
+     */
+    bool utf8;
+
     /*
      * Callback you can fill in to be notified when all the prompts'
      * responses are available. After you receive this notification, a
@@ -1215,9 +1200,15 @@ struct SeatVtable {
     void (*notify_remote_disconnect)(Seat *seat);
 
     /*
-     * Notify the seat that the connection has suffered a fatal error.
+     * Notify the seat that the connection has suffered an error,
+     * either fatal to the whole connection or not.
+     *
+     * The latter kind of error is expected to be things along the
+     * lines of 'I/O error storing the new host key', which has
+     * traditionally been presented via a dialog box or similar.
      */
     void (*connection_fatal)(Seat *seat, const char *message);
+    void (*nonfatal)(Seat *seat, const char *message);
 
     /*
      * Notify the seat that the list of special commands available
@@ -1481,10 +1472,11 @@ static inline bool seat_interactive(Seat *seat)
 static inline bool seat_get_cursor_position(Seat *seat, int *x, int *y)
 { return  seat->vt->get_cursor_position(seat, x, y); }
 
-/* Unlike the seat's actual method, the public entry point
- * seat_connection_fatal is a wrapper function with a printf-like API,
- * defined in utils. */
+/* Unlike the seat's actual method, the public entry points
+ * seat_connection_fatal and seat_nonfatal are wrapper functions with
+ * a printf-like API, defined in utils. */
 void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3);
+void seat_nonfatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3);
 
 /* Handy aliases for seat_output which set is_stderr to a fixed value. */
 static inline size_t seat_stdout(Seat *seat, const void *data, size_t len)
@@ -1529,6 +1521,7 @@ void nullseat_notify_session_started(Seat *seat);
 void nullseat_notify_remote_exit(Seat *seat);
 void nullseat_notify_remote_disconnect(Seat *seat);
 void nullseat_connection_fatal(Seat *seat, const char *message);
+void nullseat_nonfatal(Seat *seat, const char *message);
 void nullseat_update_specials_menu(Seat *seat);
 char *nullseat_get_ttymode(Seat *seat, const char *mode);
 void nullseat_set_busy_status(Seat *seat, BusyStatus status);
@@ -1568,6 +1561,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y);
  */
 
 void console_connection_fatal(Seat *seat, const char *message);
+void console_nonfatal(Seat *seat, const char *message);
 SeatPromptResult console_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     char *keystr, SeatDialogText *text, HelpCtx helpctx,
@@ -1793,275 +1787,111 @@ NORETURN void cleanup_exit(int);
  * Exports from conf.c, and a big enum (via parametric macro) of
  * configuration option keys.
  */
-#define CONFIG_OPTIONS(X) \
-    /* X(value-type, subkey-type, keyword) */ \
-    X(STR, NONE, host) \
-    X(INT, NONE, port) \
-    X(INT, NONE, protocol) /* PROT_SSH, PROT_TELNET etc */ \
-    X(INT, NONE, addressfamily) /* ADDRTYPE_IPV[46] or ADDRTYPE_UNSPEC */ \
-    X(INT, NONE, close_on_exit) /* FORCE_ON, FORCE_OFF, AUTO */ \
-    X(BOOL, NONE, warn_on_close) \
-    X(INT, NONE, ping_interval) /* in seconds */ \
-    X(BOOL, NONE, tcp_nodelay) \
-    X(BOOL, NONE, tcp_keepalives) \
-    X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \
-    /* Proxy options */ \
-    X(STR, NONE, proxy_exclude_list) \
-    X(INT, NONE, proxy_dns) /* FORCE_ON, FORCE_OFF, AUTO */ \
-    X(BOOL, NONE, even_proxy_localhost) \
-    X(INT, NONE, proxy_type) /* PROXY_NONE, PROXY_SOCKS4, ... */ \
-    X(STR, NONE, proxy_host) \
-    X(INT, NONE, proxy_port) \
-    X(STR, NONE, proxy_username) \
-    X(STR, NONE, proxy_password) \
-    X(STR, NONE, proxy_telnet_command) \
-    X(INT, NONE, proxy_log_to_term) /* FORCE_ON, FORCE_OFF, AUTO */ \
-    /* SSH options */ \
-    X(STR, NONE, remote_cmd) \
-    X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \
-    X(BOOL, NONE, nopty) \
-    X(BOOL, NONE, compression) \
-    X(INT, INT, ssh_kexlist) \
-    X(INT, INT, ssh_hklist) \
-    X(BOOL, NONE, ssh_prefer_known_hostkeys) \
-    X(INT, NONE, ssh_rekey_time) /* in minutes */ \
-    X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \
-    X(BOOL, NONE, tryagent) \
-    X(BOOL, NONE, agentfwd) \
-    X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \
-    X(INT, INT, ssh_cipherlist) \
-    X(FILENAME, NONE, keyfile) \
-    X(FILENAME, NONE, detached_cert) \
-    X(STR, NONE, auth_plugin) \
-    /* \
-     * Which SSH protocol to use. \
-     * For historical reasons, the current legal values for CONF_sshprot \
-     * are: \
-     *  0 = SSH-1 only \
-     *  3 = SSH-2 only \
-     * We used to also support \
-     *  1 = SSH-1 with fallback to SSH-2 \
-     *  2 = SSH-2 with fallback to SSH-1 \
-     * and we continue to use 0/3 in storage formats rather than the more \
-     * obvious 1/2 to avoid surprises if someone saves a session and later \
-     * downgrades PuTTY. So it's easier to use these numbers internally too. \
-     */ \
-    X(INT, NONE, sshprot) \
-    X(BOOL, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \
-    X(BOOL, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \
-    X(BOOL, NONE, ssh_no_trivial_userauth) /* disable trivial types of auth */ \
-    X(BOOL, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \
-    X(BOOL, NONE, try_tis_auth) \
-    X(BOOL, NONE, try_ki_auth) \
-    X(BOOL, NONE, try_gssapi_auth) /* attempt gssapi auth via ssh userauth */ \
-    X(BOOL, NONE, try_gssapi_kex) /* attempt gssapi auth via ssh kex */ \
-    X(BOOL, NONE, gssapifwd) /* forward tgt via gss */ \
-    X(INT, NONE, gssapirekey) /* KEXGSS refresh interval (mins) */ \
-    X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \
-    X(FILENAME, NONE, ssh_gss_custom) \
-    X(BOOL, NONE, ssh_subsys) /* run a subsystem rather than a command */ \
-    X(BOOL, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \
-    X(BOOL, NONE, ssh_no_shell) /* avoid running a shell */ \
-    X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \
-    X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \
-    /* Telnet options */ \
-    X(STR, NONE, termtype) \
-    X(STR, NONE, termspeed) \
-    X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \
-    X(STR, STR, environmt) \
-    X(STR, NONE, username) \
-    X(BOOL, NONE, username_from_env) \
-    X(STR, NONE, localusername) \
-    X(BOOL, NONE, rfc_environ) \
-    X(BOOL, NONE, passive_telnet) \
-    /* Serial port options */ \
-    X(STR, NONE, serline) \
-    X(INT, NONE, serspeed) \
-    X(INT, NONE, serdatabits) \
-    X(INT, NONE, serstopbits) \
-    X(INT, NONE, serparity) /* SER_PAR_NONE, SER_PAR_ODD, ... */ \
-    X(INT, NONE, serflow) /* SER_FLOW_NONE, SER_FLOW_XONXOFF, ... */ \
-    /* Supdup options */ \
-    X(STR, NONE, supdup_location) \
-    X(INT, NONE, supdup_ascii_set) \
-    X(BOOL, NONE, supdup_more) \
-    X(BOOL, NONE, supdup_scroll) \
-    /* Keyboard options */ \
-    X(BOOL, NONE, bksp_is_delete) \
-    X(BOOL, NONE, rxvt_homeend) \
-    X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \
-    X(INT, NONE, sharrow_type) /* SHARROW_APPLICATION, SHARROW_BITMAP, ... */ \
-    X(BOOL, NONE, no_applic_c) /* totally disable app cursor keys */ \
-    X(BOOL, NONE, no_applic_k) /* totally disable app keypad */ \
-    X(BOOL, NONE, no_mouse_rep) /* totally disable mouse reporting */ \
-    X(BOOL, NONE, no_remote_resize) /* disable remote resizing */ \
-    X(BOOL, NONE, no_alt_screen) /* disable alternate screen */ \
-    X(BOOL, NONE, no_remote_wintitle) /* disable remote retitling */ \
-    X(BOOL, NONE, no_remote_clearscroll) /* disable ESC[3J */ \
-    X(BOOL, NONE, no_dbackspace) /* disable destructive backspace */ \
-    X(BOOL, NONE, no_remote_charset) /* disable remote charset config */ \
-    X(INT, NONE, remote_qtitle_action) /* remote win title query action
-                                       * (TITLE_NONE, TITLE_EMPTY, ...) */ \
-    X(BOOL, NONE, app_cursor) \
-    X(BOOL, NONE, app_keypad) \
-    X(BOOL, NONE, nethack_keypad) \
-    X(BOOL, NONE, telnet_keyboard) \
-    X(BOOL, NONE, telnet_newline) \
-    X(BOOL, NONE, alt_f4) /* is it special? */ \
-    X(BOOL, NONE, alt_space) /* is it special? */ \
-    X(BOOL, NONE, alt_only) /* is it special? */ \
-    X(INT, NONE, localecho) /* FORCE_ON, FORCE_OFF, AUTO */ \
-    X(INT, NONE, localedit) /* FORCE_ON, FORCE_OFF, AUTO */ \
-    X(BOOL, NONE, alwaysontop) \
-    X(BOOL, NONE, fullscreenonaltenter) \
-    X(BOOL, NONE, scroll_on_key) \
-    X(BOOL, NONE, scroll_on_disp) \
-    X(BOOL, NONE, erase_to_scrollback) \
-    X(BOOL, NONE, compose_key) \
-    X(BOOL, NONE, ctrlaltkeys) \
-    X(BOOL, NONE, osx_option_meta) \
-    X(BOOL, NONE, osx_command_meta) \
-    X(STR, NONE, wintitle) /* initial window title */ \
-    /* Terminal options */ \
-    X(INT, NONE, savelines) \
-    X(BOOL, NONE, dec_om) \
-    X(BOOL, NONE, wrap_mode) \
-    X(BOOL, NONE, lfhascr) \
-    X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \
-    X(BOOL, NONE, blink_cur) \
-    X(INT, NONE, beep) /* BELL_DISABLED, BELL_DEFAULT, ... */ \
-    X(INT, NONE, beep_ind) /* B_IND_DISABLED, B_IND_FLASH, ... */ \
-    X(BOOL, NONE, bellovl) /* bell overload protection active? */ \
-    X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \
-    X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \
-    X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \
-    X(FILENAME, NONE, bell_wavefile) \
-    X(BOOL, NONE, scrollbar) \
-    X(BOOL, NONE, scrollbar_in_fullscreen) \
-    X(INT, NONE, resize_action) /* RESIZE_TERM, RESIZE_DISABLED, ... */ \
-    X(BOOL, NONE, bce) \
-    X(BOOL, NONE, blinktext) \
-    X(BOOL, NONE, win_name_always) \
-    X(INT, NONE, width) \
-    X(INT, NONE, height) \
-    X(FONT, NONE, font) \
-    X(INT, NONE, font_quality) /* FQ_DEFAULT, FQ_ANTIALIASED, ... */ \
-    X(FILENAME, NONE, logfilename) \
-    X(INT, NONE, logtype) /* LGTYP_NONE, LGTYPE_ASCII, ... */ \
-    X(INT, NONE, logxfovr) /* LGXF_OVR, LGXF_APN, LGXF_ASK */ \
-    X(BOOL, NONE, logflush) \
-    X(BOOL, NONE, logheader) \
-    X(BOOL, NONE, logomitpass) \
-    X(BOOL, NONE, logomitdata) \
-    X(BOOL, NONE, hide_mouseptr) \
-    X(BOOL, NONE, sunken_edge) \
-    X(INT, NONE, window_border) /* in pixels */ \
-    X(STR, NONE, answerback) \
-    X(STR, NONE, printer) \
-    X(BOOL, NONE, no_arabicshaping) \
-    X(BOOL, NONE, no_bidi) \
-    /* Colour options */ \
-    X(BOOL, NONE, ansi_colour) \
-    X(BOOL, NONE, xterm_256_colour) \
-    X(BOOL, NONE, true_colour) \
-    X(BOOL, NONE, system_colour) \
-    X(BOOL, NONE, try_palette) \
-    X(INT, NONE, bold_style) /* 1=font 2=colour (3=both) */ \
-    X(INT, INT, colours) /* indexed by the CONF_COLOUR_* enum encoding */ \
-    /* Selection options */ \
-    X(INT, NONE, mouse_is_xterm) /* 0=compromise 1=xterm 2=Windows */ \
-    X(BOOL, NONE, rect_select) \
-    X(BOOL, NONE, paste_controls) \
-    X(BOOL, NONE, rawcnp) \
-    X(BOOL, NONE, utf8linedraw) \
-    X(BOOL, NONE, rtf_paste) \
-    X(BOOL, NONE, mouse_override) \
-    X(INT, INT, wordness) \
-    X(BOOL, NONE, mouseautocopy) \
-    X(INT, NONE, mousepaste) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \
-    X(INT, NONE, ctrlshiftins) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \
-    X(INT, NONE, ctrlshiftcv) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \
-    X(STR, NONE, mousepaste_custom) \
-    X(STR, NONE, ctrlshiftins_custom) \
-    X(STR, NONE, ctrlshiftcv_custom) \
-    /* translations */ \
-    X(INT, NONE, vtmode) /* VT_XWINDOWS, VT_OEMANSI, ... */ \
-    X(STR, NONE, line_codepage) \
-    X(BOOL, NONE, cjk_ambig_wide) \
-    X(BOOL, NONE, utf8_override) \
-    X(BOOL, NONE, xlat_capslockcyr) \
-    /* X11 forwarding */ \
-    X(BOOL, NONE, x11_forward) \
-    X(STR, NONE, x11_display) \
-    X(INT, NONE, x11_auth) /* X11_NO_AUTH, X11_MIT, X11_XDM */ \
-    X(FILENAME, NONE, xauthfile) \
-    /* port forwarding */ \
-    X(BOOL, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \
-    X(BOOL, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \
-    /*                                                                \
-     * Subkeys for 'portfwd' can have the following forms:            \
-     *                                                                \
-     *   [LR]localport                                                \
-     *   [LR]localaddr:localport                                      \
-     *                                                                \
-     * Dynamic forwardings are indicated by an 'L' key, and the       \
-     * special value "D". For all other forwardings, the value        \
-     * should be of the form 'host:port'.                             \
-     */ \
-    X(STR, STR, portfwd) \
-    /* SSH bug compatibility modes. All FORCE_ON/FORCE_OFF/AUTO */ \
-    X(INT, NONE, sshbug_ignore1) \
-    X(INT, NONE, sshbug_plainpw1) \
-    X(INT, NONE, sshbug_rsa1) \
-    X(INT, NONE, sshbug_hmac2) \
-    X(INT, NONE, sshbug_derivekey2) \
-    X(INT, NONE, sshbug_rsapad2) \
-    X(INT, NONE, sshbug_pksessid2) \
-    X(INT, NONE, sshbug_rekey2) \
-    X(INT, NONE, sshbug_maxpkt2) \
-    X(INT, NONE, sshbug_ignore2) \
-    X(INT, NONE, sshbug_oldgex2) \
-    X(INT, NONE, sshbug_winadj) \
-    X(INT, NONE, sshbug_chanreq) \
-    X(INT, NONE, sshbug_dropstart) \
-    X(INT, NONE, sshbug_filter_kexinit) \
-    X(INT, NONE, sshbug_rsa_sha2_cert_userauth) \
-    /*                                                                \
-     * ssh_simple means that we promise never to open any channel     \
-     * other than the main one, which means it can safely use a very  \
-     * large window in SSH-2.                                         \
-     */ \
-    X(BOOL, NONE, ssh_simple) \
-    X(BOOL, NONE, ssh_connection_sharing) \
-    X(BOOL, NONE, ssh_connection_sharing_upstream) \
-    X(BOOL, NONE, ssh_connection_sharing_downstream) \
+
+/* The master list of option keywords lives in conf.h */
+enum config_primary_key {
+    #define CONF_OPTION(keyword, ...) CONF_ ## keyword,
+    #include "conf.h"
+    #undef CONF_OPTION
+
+    N_CONFIG_OPTIONS
+};
+
+/* Types that appear in Conf keys and values. */
+enum {
     /*
-     * ssh_manual_hostkeys is conceptually a set rather than a
-     * dictionary: the string subkeys are the important thing, and the
-     * actual values to which those subkeys map are all "".
-     */ \
-    X(STR, STR, ssh_manual_hostkeys) \
-    /* Options for pterm. Should split out into platform-dependent part. */ \
-    X(BOOL, NONE, stamp_utmp) \
-    X(BOOL, NONE, login_shell) \
-    X(BOOL, NONE, scrollbar_on_left) \
-    X(BOOL, NONE, shadowbold) \
-    X(FONT, NONE, boldfont) \
-    X(FONT, NONE, widefont) \
-    X(FONT, NONE, wideboldfont) \
-    X(INT, NONE, shadowboldoffset) /* in pixels */ \
-    X(BOOL, NONE, crhaslf) \
-    X(STR, NONE, winclass) \
-    /* end of list */
+     * CONF_TYPE_NONE is included in this enum because sometimes you
+     * need a placeholder for 'no type found'. (In Rust you'd leave it
+     * out, and use Option<ConfType> for those situations.)
+     *
+     * In particular, it's used as the subkey type for options that
+     * don't have subkeys.
+     */
+    CONF_TYPE_NONE,
+
+    /* Booleans, accessed via conf_get_bool and conf_set_bool */
+    CONF_TYPE_BOOL,
+
+    /* Integers, accessed via conf_get_int and conf_set_int */
+    CONF_TYPE_INT,
+
+    /*
+     * NUL-terminated char strings, accessed via conf_get_str and
+     * conf_set_str.
+     *
+     * Where character encoding is relevant, these are generally
+     * expected to be in the host system's default character encoding.
+     *
+     * (Character encoding might not be relevant at all: for example,
+     * if the string is going to be used as a shell command on Unix,
+     * then the exec system call will want a char string anyway.)
+     */
+    CONF_TYPE_STR,
+
+    /* NUL-terminated char strings encoded in UTF-8, accessed via
+     * conf_get_utf8 and conf_set_utf8. */
+    CONF_TYPE_UTF8,
 
-/* Now define the actual enum of option keywords using that macro. */
-#define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword,
-enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS };
-#undef CONF_ENUM_DEF
+    /*
+     * A type that can be _either_ a char string in system encoding
+     * (aka CONF_TYPE_STR), _or_ a char string in UTF-8 (aka
+     * CONF_TYPE_UTF8). You can set it to be one or the other via
+     * conf_set_str or conf_set_utf8. To read it, you must use
+     * conf_get_str_ambi(), which returns a char string and a boolean
+     * telling you whether it's UTF-8.
+     *
+     * These can't be used as _keys_ in Conf, only as values. (If you
+     * used them as keys, you'd have to answer the difficult question
+     * of whether a UTF-8 and a non-UTF-8 string should be considered
+     * equal.)
+     */
+    CONF_TYPE_STR_AMBI,
+
+    /* PuTTY's OS-specific 'Filename' data type, accessed via
+     * conf_get_filename and conf_set_filename */
+    CONF_TYPE_FILENAME,
+
+    /* PuTTY's GUI-specific 'FontSpec' data type, accessed via
+     * conf_get_fontspec and conf_set_fontspec */
+    CONF_TYPE_FONT,
+};
+
+struct ConfKeyInfo {
+    int subkey_type;
+    int value_type;
+
+    union {
+        bool bval;
+        int ival;
+        const char *sval;
+    } default_value;
+
+    bool save_custom : 1;
+    bool load_custom : 1;
+    bool not_saved : 1;
+
+    const char *save_keyword;
+    const ConfSaveEnumType *storage_enum;
+};
+struct ConfSaveEnumType {
+    const ConfSaveEnumValue *values;
+    size_t nvalues;
+};
+struct ConfSaveEnumValue {
+    int confval, storageval;
+    bool obsolete;
+};
+
+extern const ConfKeyInfo conf_key_info[];
+bool conf_enum_map_to_storage(const ConfSaveEnumType *etype,
+                              int confval, int *storageval_out);
+bool conf_enum_map_from_storage(const ConfSaveEnumType *etype,
+                                int storageval, int *confval_out);
 
 /* Functions handling configuration structures. */
 Conf *conf_new(void);                  /* create an empty configuration */
 void conf_free(Conf *conf);
+void conf_clear(Conf *conf);    /* likely only useful for test programs */
 Conf *conf_copy(Conf *oldconf);
 void conf_copy_into(Conf *dest, Conf *src);
 /* Mandatory accessor functions: enforce by assertion that keys exist. */
@@ -2069,6 +1899,9 @@ bool conf_get_bool(Conf *conf, int key);
 int conf_get_int(Conf *conf, int key);
 int conf_get_int_int(Conf *conf, int key, int subkey);
 char *conf_get_str(Conf *conf, int key);   /* result still owned by conf */
+char *conf_get_utf8(Conf *conf, int key);   /* result still owned by conf */
+char *conf_get_str_ambi( /* result still owned by conf; 'utf8' may be NULL */
+    Conf *conf, int key, bool *utf8);
 char *conf_get_str_str(Conf *conf, int key, const char *subkey);
 Filename *conf_get_filename(Conf *conf, int key);
 FontSpec *conf_get_fontspec(Conf *conf, int key); /* still owned by conf */
@@ -2086,6 +1919,9 @@ void conf_set_bool(Conf *conf, int key, bool value);
 void conf_set_int(Conf *conf, int key, int value);
 void conf_set_int_int(Conf *conf, int key, int subkey, int value);
 void conf_set_str(Conf *conf, int key, const char *value);
+void conf_set_utf8(Conf *conf, int key, const char *value);
+bool conf_try_set_str(Conf *conf, int key, const char *value);
+bool conf_try_set_utf8(Conf *conf, int key, const char *value);
 void conf_set_str_str(Conf *conf, int key,
                       const char *subkey, const char *val);
 void conf_del_str_str(Conf *conf, int key, const char *subkey);
@@ -2099,7 +1935,15 @@ bool conf_deserialise(Conf *conf, BinarySource *src);/*returns true on success*/
  * Functions to copy, free, serialise and deserialise FontSpecs.
  * Provided per-platform, to go with the platform's idea of a
  * FontSpec's contents.
- */
+ *
+ * The full fontspec_new is declared in the platform header, because
+ * each platform may need it to have a different prototype, due to
+ * constructing fonts in different ways. But fontspec_new_default()
+ * will at least produce _some_ kind of a FontSpec, for use in
+ * situations where one needs to exist (e.g. to put in a Conf) and be
+ * freeable but won't actually be used for anything important.
+ */
+FontSpec *fontspec_new_default(void);
 FontSpec *fontspec_copy(const FontSpec *f);
 void fontspec_free(FontSpec *f);
 void fontspec_serialise(BinarySink *bs, FontSpec *f);
@@ -2202,7 +2046,7 @@ void term_lost_clipboard_ownership(Terminal *, int clipboard);
 void term_update(Terminal *);
 void term_invalidate(Terminal *);
 void term_blink(Terminal *, bool set_cursor);
-void term_do_paste(Terminal *, const wchar_t *, int);
+void term_do_paste(Terminal *, const wchar_t *, size_t);
 void term_nopaste(Terminal *);
 void term_copyall(Terminal *, const int *, int);
 void term_pre_reconfig(Terminal *, Conf *);
@@ -2385,28 +2229,7 @@ void ldisc_configure(Ldisc *, Conf *);
 void ldisc_free(Ldisc *);
 void ldisc_send(Ldisc *, const void *buf, int len, bool interactive);
 void ldisc_echoedit_update(Ldisc *);
-typedef struct LdiscInputToken {
-    /*
-     * Structure that encodes any single item of data that Ldisc can
-     * buffer: either a single character of raw data, or a session
-     * special.
-     */
-    bool is_special;
-    union {
-        struct {
-            /* if is_special == false */
-            char chr;
-        };
-        struct {
-            /* if is_special == true */
-            SessionSpecialCode code;
-            int arg;
-        };
-    };
-} LdiscInputToken;
-bool ldisc_has_input_buffered(Ldisc *);
-LdiscInputToken ldisc_get_input_token(Ldisc *); /* asserts there is input */
-void ldisc_enable_prompt_callback(Ldisc *, prompts_t *);
+void ldisc_provide_userpass_le(Ldisc *, TermLineEditor *);
 void ldisc_check_sendok(Ldisc *);
 
 /*
@@ -2473,15 +2296,9 @@ extern const char commitid[];
 /*
  * Exports from unicode.c in platform subdirs.
  */
-#ifndef CP_UTF8
-#define CP_UTF8 65001
-#endif
 /* void init_ucs(void); -- this is now in platform-specific headers */
 bool is_dbcs_leadbyte(int codepage, char byte);
-int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
-             wchar_t *wcstr, int wclen);
-int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
-             char *mbstr, int mblen, const char *defchr);
+/* For put_mb_to_wc / put_wc_to_mb, see marshal.h */
 wchar_t xlat_uskbd2cyrllic(int ch);
 int check_compose(int first, int second);
 int decode_codepage(const char *cp_name);
@@ -2554,6 +2371,8 @@ bool have_ssh_host_key(const char *host, int port, const char *keytype);
  * that aren't equivalents to things in windlg.c et al.
  */
 extern bool console_batch_mode, console_antispoof_prompt;
+extern bool console_set_batch_mode(bool);
+extern bool console_set_stdio_prompts(bool);
 SeatPromptResult console_get_userpass_input(prompts_t *p);
 bool is_interactive(void);
 void console_print_error_msg(const char *prefix, const char *msg);
@@ -2562,6 +2381,11 @@ void console_print_error_msg_fmt_v(
 void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...)
     PRINTF_LIKE(2, 3);
 
+/*
+ * Exports from either console frontends or terminal.c.
+ */
+extern bool set_legacy_charset_handling(bool);
+
 /*
  * Exports from printing.c in platform subdirs.
  */
@@ -2578,17 +2402,11 @@ void printer_finish_job(printer_job *);
  * Exports from cmdline.c (and also cmdline_error(), which is
  * defined differently in various places and required _by_
  * cmdline.c).
- *
- * Note that cmdline_process_param takes a const option string, but a
- * writable argument string. That's not a mistake - that's so it can
- * zero out password arguments in the hope of not having them show up
- * avoidably in Unix 'ps'.
  */
 struct cmdline_get_passwd_input_state { bool tried; };
 #define CMDLINE_GET_PASSWD_INPUT_STATE_INIT { .tried = false }
 extern const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new;
-
-int cmdline_process_param(const char *, char *, int, Conf *);
+int cmdline_process_param(CmdlineArg *, CmdlineArg *, int, Conf *);
 void cmdline_run_saved(Conf *);
 void cmdline_cleanup(void);
 SeatPromptResult cmdline_get_passwd_input(
@@ -2597,6 +2415,33 @@ bool cmdline_host_ok(Conf *);
 bool cmdline_verbose(void);
 bool cmdline_loaded_session(void);
 
+/*
+ * Abstraction provided by each platform to represent a command-line
+ * argument. May not be as simple as a default-encoded string: on
+ * Windows, command lines can be Unicode representing characters not
+ * in the system codepage, so you might need to retrieve the argument
+ * in a richer form.
+ */
+struct CmdlineArgList {
+    /* args[0], args[1], ... represent the original arguments in the
+     * command line. Then there's a null pointer. Further arguments
+     * can be invented to add to the array after that, in which case
+     * they'll be freed with the rest of the CmdlineArgList, but
+     * aren't logically part of the original command line. */
+    CmdlineArg **args;
+    size_t nargs, argssize;
+};
+struct CmdlineArg {
+    CmdlineArgList *list;
+};
+const char *cmdline_arg_to_utf8(CmdlineArg *arg); /* may fail */
+const char *cmdline_arg_to_str(CmdlineArg *arg);  /* must not fail */
+Filename *cmdline_arg_to_filename(CmdlineArg *arg);  /* caller must free */
+void cmdline_arg_wipe(CmdlineArg *arg);
+CmdlineArg *cmdline_arg_from_str(CmdlineArgList *list, const char *string);
+/* Platforms provide their own constructors for CmdlineArgList */
+void cmdline_arg_list_free(CmdlineArgList *list);
+
 /*
  * Here we have a flags word provided by each tool, which describes
  * the capabilities of that tool that cmdline.c needs to know about.
@@ -2619,6 +2464,7 @@ extern const unsigned cmdline_tooltype;
     X(TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD)   \
     X(TOOLTYPE_PORT_ARG)                        \
     X(TOOLTYPE_NO_VERBOSE_OPTION)               \
+    X(TOOLTYPE_GUI)                             \
     /* end of list */
 #define BITFLAG_INDEX(val) val ## _bitflag_index,
 enum { TOOLTYPE_LIST(BITFLAG_INDEX) };
@@ -2925,6 +2771,9 @@ Socket *platform_start_subprocess(const char *cmd, Plug *plug,
 #define LOW_SURROGATE_END 0xdfff
 #endif
 
+/* REGIONAL INDICATOR SYMBOL LETTER A-Z */
+#define IS_REGIONAL_INDICATOR_LETTER(wc) ((unsigned)(wc) - 0x1F1E6U < 26)
+
 /* These macros exist in the Windows API, so the environment may
  * provide them. If not, define them in terms of the above. */
 #ifndef IS_HIGH_SURROGATE

+ 211 - 465
source/putty/settings.c

@@ -65,7 +65,7 @@ static const struct keyvalwhere hknames[] = {
  * This is currently precisely the same as the set in
  * ssh/ttymode-list.h, but could in principle differ if other backends
  * started to support tty modes (e.g., the pty backend).
- * The set of modes in in this array is currently significant for
+ * The set of modes in this array is currently significant for
  * settings migration from old versions; if they change, review the
  * gppmap() invocation for "TerminalModes".
  */
@@ -112,7 +112,9 @@ const struct BackendVtable *backend_vt_from_proto(int proto)
 
 char *get_remote_username(Conf *conf)
 {
-    char *username = conf_get_str(conf, CONF_username);
+    /* We don't worry about whether the username is stored as UTF-8,
+     * because SSH wants it as UTF-8 */
+    char *username = conf_get_str_ambi(conf, CONF_username, NULL);
     if (*username) {
         return dupstr(username);
     } else if (conf_get_bool(conf, CONF_username_from_env)) {
@@ -168,7 +170,7 @@ static void gppfile(settings_r *sesskey, const char *name,
 static bool gppb_raw(settings_r *sesskey, const char *name, bool def)
 {
     def = platform_default_b(name, def);
-    return sesskey ? read_setting_i(sesskey, name, def) != 0 : def;
+    return read_setting_i(sesskey, name, def) != 0;
 }
 
 static void gppb(settings_r *sesskey, const char *name, bool def,
@@ -267,19 +269,10 @@ static bool gppmap(settings_r *sesskey, const char *name,
 static void wmap(settings_w *sesskey, char const *outkey, Conf *conf,
                  int primary, bool include_values)
 {
-    char *buf, *p, *key, *realkey;
+    char *key, *realkey;
     const char *val, *q;
-    int len;
 
-    len = 1;                           /* allow for NUL */
-
-    for (val = conf_get_str_strs(conf, primary, NULL, &key);
-         val != NULL;
-         val = conf_get_str_strs(conf, primary, key, &key))
-        len += 2 + 2 * (strlen(key) + strlen(val));   /* allow for escaping */
-
-    buf = snewn(len, char);
-    p = buf;
+    strbuf *sb = strbuf_new();
 
     for (val = conf_get_str_strs(conf, primary, NULL, &key);
          val != NULL;
@@ -304,19 +297,19 @@ static void wmap(settings_w *sesskey, char const *outkey, Conf *conf,
             realkey = NULL;
         }
 
-        if (p != buf)
-            *p++ = ',';
+        if (sb->len)
+            put_byte(sb, ',');
         for (q = key; *q; q++) {
             if (*q == '=' || *q == ',' || *q == '\\')
-                *p++ = '\\';
-            *p++ = *q;
+                put_byte(sb, '\\');
+            put_byte(sb, *q);
         }
         if (include_values) {
-            *p++ = '=';
+            put_byte(sb, '=');
             for (q = val; *q; q++) {
                 if (*q == '=' || *q == ',' || *q == '\\')
-                    *p++ = '\\';
-                *p++ = *q;
+                    put_byte(sb, '\\');
+                put_byte(sb, *q);
             }
         }
 
@@ -325,9 +318,8 @@ static void wmap(settings_w *sesskey, char const *outkey, Conf *conf,
             key = realkey;
         }
     }
-    *p = '\0';
-    write_setting_s(sesskey, outkey, buf);
-    sfree(buf);
+    write_setting_s(sesskey, outkey, sb->s);
+    strbuf_free(sb);
 }
 
 static int key2val(const struct keyvalwhere *mapping,
@@ -456,34 +448,18 @@ static void wprefs(settings_w *sesskey, const char *name,
                    const struct keyvalwhere *mapping, int nvals,
                    Conf *conf, int primary)
 {
-    char *buf, *p;
-    int i, maxlen;
+    strbuf *sb = strbuf_new();
 
-    for (maxlen = i = 0; i < nvals; i++) {
+    for (int i = 0; i < nvals; i++) {
         const char *s = val2key(mapping, nvals,
                                 conf_get_int_int(conf, primary, i));
-        if (s) {
-            maxlen += (maxlen > 0 ? 1 : 0) + strlen(s);
-        }
+        if (s)
+            put_fmt(sb, "%s%s", (sb->len ? "," : ""), s);
     }
 
-    buf = snewn(maxlen + 1, char);
-    p = buf;
-
-    for (i = 0; i < nvals; i++) {
-        const char *s = val2key(mapping, nvals,
-                                conf_get_int_int(conf, primary, i));
-        if (s) {
-            p += sprintf(p, "%s%s", (p > buf ? "," : ""), s);
-        }
-    }
-
-    assert(p - buf == maxlen);
-    *p = '\0';
-
-    write_setting_s(sesskey, name, buf);
+    write_setting_s(sesskey, name, sb->s);
 
-    sfree(buf);
+    strbuf_free(sb);
 }
 
 static void write_setting_b(settings_w *handle, const char *key, bool value)
@@ -551,20 +527,92 @@ char *save_settings(const char *section, Conf *conf)
     return NULL;
 }
 
+/* Declare extern references to conf_enum_* types */
+#define CONF_ENUM(name, ...) extern const ConfSaveEnumType conf_enum_##name;
+#include "conf-enums.h"
+#undef CONF_ENUM
+
 void save_open_settings(settings_w *sesskey, Conf *conf)
 {
     int i;
     const char *p;
 
+    /* Save the settings simple enough to handle automatically */
+    for (size_t key = 0; key < N_CONFIG_OPTIONS; key++) {
+        const ConfKeyInfo *info = &conf_key_info[key];
+        if (!info->save_custom && !info->not_saved) {
+            /* Mappings are handled individually below */
+            assert(info->subkey_type == CONF_TYPE_NONE);
+            switch (info->value_type) {
+              case CONF_TYPE_STR:
+                write_setting_s(sesskey, info->save_keyword,
+                                conf_get_str(conf, key));
+                break;
+              case CONF_TYPE_STR_AMBI: {
+                bool orig_is_utf8;
+                const char *orig = conf_get_str_ambi(conf, key, &orig_is_utf8);
+
+                int cp_from, cp_to;
+                if (orig_is_utf8) {
+                    cp_from = CP_UTF8;
+                    cp_to = DEFAULT_CODEPAGE;
+                } else {
+                    cp_from = DEFAULT_CODEPAGE;
+                    cp_to = CP_UTF8;
+                }
+
+                size_t wlen;
+                wchar_t *wide = dup_mb_to_wc_c(
+                    cp_from, orig, strlen(orig), &wlen);
+
+                size_t clen;
+                char *converted = dup_wc_to_mb_c(
+                    cp_to, wide, wlen, "", &clen);
+
+                const char *native, *utf8;
+                if (orig_is_utf8) {
+                    utf8 = orig;
+                    native = converted;
+                } else {
+                    native = orig;
+                    utf8 = converted;
+                }
+                write_setting_s(sesskey, info->save_keyword, native);
+                (void)utf8; /* FIXME: also save the UTF-8 version */
+
+                burnwcs(wide);
+                burnstr(converted);
+                break;
+              }
+              case CONF_TYPE_INT: {
+                int ival = conf_get_int(conf, key);
+                if (info->storage_enum) {
+                    bool success = conf_enum_map_to_storage(
+                        info->storage_enum, ival, &ival);
+                    assert(success && "unmapped integer value");
+                }
+                write_setting_i(sesskey, info->save_keyword, ival);
+                break;
+              }
+              case CONF_TYPE_BOOL:
+                write_setting_b(sesskey, info->save_keyword,
+                                conf_get_bool(conf, key));
+                break;
+              case CONF_TYPE_FILENAME:
+                write_setting_filename(sesskey, info->save_keyword,
+                                       conf_get_filename(conf, key));
+                break;
+              case CONF_TYPE_FONT:
+                write_setting_fontspec(sesskey, info->save_keyword,
+                                       conf_get_fontspec(conf, key));
+                break;
+              default:
+                unreachable("bad key type in save_open_settings");
+            }
+        }
+    }
+
     write_setting_i(sesskey, "Present", 1);
-    write_setting_s(sesskey, "HostName", conf_get_str(conf, CONF_host));
-    write_setting_filename(sesskey, "LogFileName", conf_get_filename(conf, CONF_logfilename));
-    write_setting_i(sesskey, "LogType", conf_get_int(conf, CONF_logtype));
-    write_setting_i(sesskey, "LogFileClash", conf_get_int(conf, CONF_logxfovr));
-    write_setting_b(sesskey, "LogFlush", conf_get_bool(conf, CONF_logflush));
-    write_setting_b(sesskey, "LogHeader", conf_get_bool(conf, CONF_logheader));
-    write_setting_b(sesskey, "SSHLogOmitPasswords", conf_get_bool(conf, CONF_logomitpass));
-    write_setting_b(sesskey, "SSHLogOmitData", conf_get_bool(conf, CONF_logomitdata));
     p = "raw";
     {
         const struct BackendVtable *vt =
@@ -573,118 +621,31 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
             p = vt->id;
     }
     write_setting_s(sesskey, "Protocol", p);
-    write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port));
-    /* The CloseOnExit numbers are arranged in a different order from
-     * the standard FORCE_ON / FORCE_OFF / AUTO. */
-    write_setting_i(sesskey, "CloseOnExit", (conf_get_int(conf, CONF_close_on_exit)+2)%3);
-    write_setting_b(sesskey, "WarnOnClose", !!conf_get_bool(conf, CONF_warn_on_close));
     write_setting_i(sesskey, "PingInterval", conf_get_int(conf, CONF_ping_interval) / 60);      /* minutes */
     write_setting_i(sesskey, "PingIntervalSecs", conf_get_int(conf, CONF_ping_interval) % 60);  /* seconds */
-    write_setting_b(sesskey, "TCPNoDelay", conf_get_bool(conf, CONF_tcp_nodelay));
-    write_setting_b(sesskey, "TCPKeepalives", conf_get_bool(conf, CONF_tcp_keepalives));
-    write_setting_s(sesskey, "TerminalType", conf_get_str(conf, CONF_termtype));
-    write_setting_s(sesskey, "TerminalSpeed", conf_get_str(conf, CONF_termspeed));
     wmap(sesskey, "TerminalModes", conf, CONF_ttymodes, true);
 
-    /* Address family selection */
-    write_setting_i(sesskey, "AddressFamily", conf_get_int(conf, CONF_addressfamily));
-
     /* proxy settings */
-    write_setting_s(sesskey, "ProxyExcludeList", conf_get_str(conf, CONF_proxy_exclude_list));
-    write_setting_i(sesskey, "ProxyDNS", (conf_get_int(conf, CONF_proxy_dns)+2)%3);
-    write_setting_b(sesskey, "ProxyLocalhost", conf_get_bool(conf, CONF_even_proxy_localhost));
-    write_setting_i(sesskey, "ProxyMethod", conf_get_int(conf, CONF_proxy_type));
-    write_setting_s(sesskey, "ProxyHost", conf_get_str(conf, CONF_proxy_host));
-    write_setting_i(sesskey, "ProxyPort", conf_get_int(conf, CONF_proxy_port));
-    write_setting_s(sesskey, "ProxyUsername", conf_get_str(conf, CONF_proxy_username));
-    write_setting_s(sesskey, "ProxyPassword", conf_get_str(conf, CONF_proxy_password));
-    write_setting_s(sesskey, "ProxyTelnetCommand", conf_get_str(conf, CONF_proxy_telnet_command));
-    write_setting_i(sesskey, "ProxyLogToTerm", conf_get_int(conf, CONF_proxy_log_to_term));
     wmap(sesskey, "Environment", conf, CONF_environmt, true);
-    write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username));
-    write_setting_b(sesskey, "UserNameFromEnvironment", conf_get_bool(conf, CONF_username_from_env));
-    write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername));
-    write_setting_b(sesskey, "NoPTY", conf_get_bool(conf, CONF_nopty));
-    write_setting_b(sesskey, "Compression", conf_get_bool(conf, CONF_compression));
-    write_setting_b(sesskey, "TryAgent", conf_get_bool(conf, CONF_tryagent));
-    write_setting_b(sesskey, "AgentFwd", conf_get_bool(conf, CONF_agentfwd));
 #ifndef NO_GSSAPI
     write_setting_b(sesskey, "GssapiFwd", conf_get_bool(conf, CONF_gssapifwd));
 #endif
-    write_setting_b(sesskey, "ChangeUsername", conf_get_bool(conf, CONF_change_username));
     wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
     wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
     wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist);
-    write_setting_b(sesskey, "PreferKnownHostKeys", conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys));
-    write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time));
 #ifndef NO_GSSAPI
     write_setting_i(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey));
 #endif
-    write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data));
-    write_setting_b(sesskey, "SshNoAuth", conf_get_bool(conf, CONF_ssh_no_userauth));
-    write_setting_b(sesskey, "SshNoTrivialAuth", conf_get_bool(conf, CONF_ssh_no_trivial_userauth));
-    write_setting_b(sesskey, "SshBanner", conf_get_bool(conf, CONF_ssh_show_banner));
-    write_setting_b(sesskey, "AuthTIS", conf_get_bool(conf, CONF_try_tis_auth));
-    write_setting_b(sesskey, "AuthKI", conf_get_bool(conf, CONF_try_ki_auth));
 #ifndef NO_GSSAPI
     write_setting_b(sesskey, "AuthGSSAPI", conf_get_bool(conf, CONF_try_gssapi_auth));
     write_setting_b(sesskey, "AuthGSSAPIKEX", conf_get_bool(conf, CONF_try_gssapi_kex));
     wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist);
     write_setting_filename(sesskey, "GSSCustom", conf_get_filename(conf, CONF_ssh_gss_custom));
 #endif
-    write_setting_b(sesskey, "SshNoShell", conf_get_bool(conf, CONF_ssh_no_shell));
-    write_setting_i(sesskey, "SshProt", conf_get_int(conf, CONF_sshprot));
-    write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost));
-    write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc));
-    write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile));
-    write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert));
-    write_setting_s(sesskey, "AuthPlugin", conf_get_str(conf, CONF_auth_plugin));
-    write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd));
-    write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ));
-    write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet));
-    write_setting_b(sesskey, "BackspaceIsDelete", conf_get_bool(conf, CONF_bksp_is_delete));
-    write_setting_b(sesskey, "RXVTHomeEnd", conf_get_bool(conf, CONF_rxvt_homeend));
-    write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type));
-    write_setting_i(sesskey, "ShiftedArrowKeys", conf_get_int(conf, CONF_sharrow_type));
-    write_setting_b(sesskey, "NoApplicationKeys", conf_get_bool(conf, CONF_no_applic_k));
-    write_setting_b(sesskey, "NoApplicationCursors", conf_get_bool(conf, CONF_no_applic_c));
-    write_setting_b(sesskey, "NoMouseReporting", conf_get_bool(conf, CONF_no_mouse_rep));
-    write_setting_b(sesskey, "NoRemoteResize", conf_get_bool(conf, CONF_no_remote_resize));
-    write_setting_b(sesskey, "NoAltScreen", conf_get_bool(conf, CONF_no_alt_screen));
-    write_setting_b(sesskey, "NoRemoteWinTitle", conf_get_bool(conf, CONF_no_remote_wintitle));
-    write_setting_b(sesskey, "NoRemoteClearScroll", conf_get_bool(conf, CONF_no_remote_clearscroll));
-    write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action));
-    write_setting_b(sesskey, "NoDBackspace", conf_get_bool(conf, CONF_no_dbackspace));
-    write_setting_b(sesskey, "NoRemoteCharset", conf_get_bool(conf, CONF_no_remote_charset));
-    write_setting_b(sesskey, "ApplicationCursorKeys", conf_get_bool(conf, CONF_app_cursor));
-    write_setting_b(sesskey, "ApplicationKeypad", conf_get_bool(conf, CONF_app_keypad));
-    write_setting_b(sesskey, "NetHackKeypad", conf_get_bool(conf, CONF_nethack_keypad));
-    write_setting_b(sesskey, "AltF4", conf_get_bool(conf, CONF_alt_f4));
-    write_setting_b(sesskey, "AltSpace", conf_get_bool(conf, CONF_alt_space));
-    write_setting_b(sesskey, "AltOnly", conf_get_bool(conf, CONF_alt_only));
-    write_setting_b(sesskey, "ComposeKey", conf_get_bool(conf, CONF_compose_key));
-    write_setting_b(sesskey, "CtrlAltKeys", conf_get_bool(conf, CONF_ctrlaltkeys));
 #ifdef OSX_META_KEY_CONFIG
     write_setting_b(sesskey, "OSXOptionMeta", conf_get_bool(conf, CONF_osx_option_meta));
     write_setting_b(sesskey, "OSXCommandMeta", conf_get_bool(conf, CONF_osx_command_meta));
 #endif
-    write_setting_b(sesskey, "TelnetKey", conf_get_bool(conf, CONF_telnet_keyboard));
-    write_setting_b(sesskey, "TelnetRet", conf_get_bool(conf, CONF_telnet_newline));
-    write_setting_i(sesskey, "LocalEcho", conf_get_int(conf, CONF_localecho));
-    write_setting_i(sesskey, "LocalEdit", conf_get_int(conf, CONF_localedit));
-    write_setting_s(sesskey, "Answerback", conf_get_str(conf, CONF_answerback));
-    write_setting_b(sesskey, "AlwaysOnTop", conf_get_bool(conf, CONF_alwaysontop));
-    write_setting_b(sesskey, "FullScreenOnAltEnter", conf_get_bool(conf, CONF_fullscreenonaltenter));
-    write_setting_b(sesskey, "HideMousePtr", conf_get_bool(conf, CONF_hide_mouseptr));
-    write_setting_b(sesskey, "SunkenEdge", conf_get_bool(conf, CONF_sunken_edge));
-    write_setting_i(sesskey, "WindowBorder", conf_get_int(conf, CONF_window_border));
-    write_setting_i(sesskey, "CurType", conf_get_int(conf, CONF_cursor_type));
-    write_setting_b(sesskey, "BlinkCur", conf_get_bool(conf, CONF_blink_cur));
-    write_setting_i(sesskey, "Beep", conf_get_int(conf, CONF_beep));
-    write_setting_i(sesskey, "BeepInd", conf_get_int(conf, CONF_beep_ind));
-    write_setting_filename(sesskey, "BellWaveFile", conf_get_filename(conf, CONF_bell_wavefile));
-    write_setting_b(sesskey, "BellOverload", conf_get_bool(conf, CONF_bellovl));
-    write_setting_i(sesskey, "BellOverloadN", conf_get_int(conf, CONF_bellovl_n));
     write_setting_i(sesskey, "BellOverloadT", conf_get_int(conf, CONF_bellovl_t)
 #ifdef PUTTY_UNIX_PLATFORM_H
                     * 1000
@@ -695,26 +656,6 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
                     * 1000
 #endif
                     );
-    write_setting_i(sesskey, "ScrollbackLines", conf_get_int(conf, CONF_savelines));
-    write_setting_b(sesskey, "DECOriginMode", conf_get_bool(conf, CONF_dec_om));
-    write_setting_b(sesskey, "AutoWrapMode", conf_get_bool(conf, CONF_wrap_mode));
-    write_setting_b(sesskey, "LFImpliesCR", conf_get_bool(conf, CONF_lfhascr));
-    write_setting_b(sesskey, "CRImpliesLF", conf_get_bool(conf, CONF_crhaslf));
-    write_setting_b(sesskey, "DisableArabicShaping", conf_get_bool(conf, CONF_no_arabicshaping));
-    write_setting_b(sesskey, "DisableBidi", conf_get_bool(conf, CONF_no_bidi));
-    write_setting_b(sesskey, "WinNameAlways", conf_get_bool(conf, CONF_win_name_always));
-    write_setting_s(sesskey, "WinTitle", conf_get_str(conf, CONF_wintitle));
-    write_setting_i(sesskey, "TermWidth", conf_get_int(conf, CONF_width));
-    write_setting_i(sesskey, "TermHeight", conf_get_int(conf, CONF_height));
-    write_setting_fontspec(sesskey, "Font", conf_get_fontspec(conf, CONF_font));
-    write_setting_i(sesskey, "FontQuality", conf_get_int(conf, CONF_font_quality));
-    write_setting_i(sesskey, "FontVTMode", conf_get_int(conf, CONF_vtmode));
-    write_setting_b(sesskey, "UseSystemColours", conf_get_bool(conf, CONF_system_colour));
-    write_setting_b(sesskey, "TryPalette", conf_get_bool(conf, CONF_try_palette));
-    write_setting_b(sesskey, "ANSIColour", conf_get_bool(conf, CONF_ansi_colour));
-    write_setting_b(sesskey, "Xterm256Colour", conf_get_bool(conf, CONF_xterm_256_colour));
-    write_setting_b(sesskey, "TrueColour", conf_get_bool(conf, CONF_true_colour));
-    write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_style)-1);
 
     for (i = 0; i < 22; i++) {
         char buf[20], buf2[30];
@@ -725,13 +666,6 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
                 conf_get_int_int(conf, CONF_colours, i*3+2));
         write_setting_s(sesskey, buf, buf2);
     }
-    write_setting_b(sesskey, "RawCNP", conf_get_bool(conf, CONF_rawcnp));
-    write_setting_b(sesskey, "UTF8linedraw", conf_get_bool(conf, CONF_utf8linedraw));
-    write_setting_b(sesskey, "PasteRTF", conf_get_bool(conf, CONF_rtf_paste));
-    write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm));
-    write_setting_b(sesskey, "RectSelect", conf_get_bool(conf, CONF_rect_select));
-    write_setting_b(sesskey, "PasteControls", conf_get_bool(conf, CONF_paste_controls));
-    write_setting_b(sesskey, "MouseOverride", conf_get_bool(conf, CONF_mouse_override));
     for (i = 0; i < 256; i += 32) {
         char buf[20], buf2[256];
         int j;
@@ -744,77 +678,14 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
         }
         write_setting_s(sesskey, buf, buf2);
     }
-    write_setting_b(sesskey, "MouseAutocopy",
-                    conf_get_bool(conf, CONF_mouseautocopy));
     write_clip_setting(sesskey, "MousePaste", conf,
                        CONF_mousepaste, CONF_mousepaste_custom);
     write_clip_setting(sesskey, "CtrlShiftIns", conf,
                        CONF_ctrlshiftins, CONF_ctrlshiftins_custom);
     write_clip_setting(sesskey, "CtrlShiftCV", conf,
                        CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom);
-    write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage));
-    write_setting_b(sesskey, "CJKAmbigWide", conf_get_bool(conf, CONF_cjk_ambig_wide));
-    write_setting_b(sesskey, "UTF8Override", conf_get_bool(conf, CONF_utf8_override));
-    write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer));
-    write_setting_b(sesskey, "CapsLockCyr", conf_get_bool(conf, CONF_xlat_capslockcyr));
-    write_setting_b(sesskey, "ScrollBar", conf_get_bool(conf, CONF_scrollbar));
-    write_setting_b(sesskey, "ScrollBarFullScreen", conf_get_bool(conf, CONF_scrollbar_in_fullscreen));
-    write_setting_b(sesskey, "ScrollOnKey", conf_get_bool(conf, CONF_scroll_on_key));
-    write_setting_b(sesskey, "ScrollOnDisp", conf_get_bool(conf, CONF_scroll_on_disp));
-    write_setting_b(sesskey, "EraseToScrollback", conf_get_bool(conf, CONF_erase_to_scrollback));
-    write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action));
-    write_setting_b(sesskey, "BCE", conf_get_bool(conf, CONF_bce));
-    write_setting_b(sesskey, "BlinkText", conf_get_bool(conf, CONF_blinktext));
-    write_setting_b(sesskey, "X11Forward", conf_get_bool(conf, CONF_x11_forward));
-    write_setting_s(sesskey, "X11Display", conf_get_str(conf, CONF_x11_display));
-    write_setting_i(sesskey, "X11AuthType", conf_get_int(conf, CONF_x11_auth));
-    write_setting_filename(sesskey, "X11AuthFile", conf_get_filename(conf, CONF_xauthfile));
-    write_setting_b(sesskey, "LocalPortAcceptAll", conf_get_bool(conf, CONF_lport_acceptall));
-    write_setting_b(sesskey, "RemotePortAcceptAll", conf_get_bool(conf, CONF_rport_acceptall));
     wmap(sesskey, "PortForwardings", conf, CONF_portfwd, true);
-    write_setting_i(sesskey, "BugIgnore1", 2-conf_get_int(conf, CONF_sshbug_ignore1));
-    write_setting_i(sesskey, "BugPlainPW1", 2-conf_get_int(conf, CONF_sshbug_plainpw1));
-    write_setting_i(sesskey, "BugRSA1", 2-conf_get_int(conf, CONF_sshbug_rsa1));
-    write_setting_i(sesskey, "BugIgnore2", 2-conf_get_int(conf, CONF_sshbug_ignore2));
-    write_setting_i(sesskey, "BugHMAC2", 2-conf_get_int(conf, CONF_sshbug_hmac2));
-    write_setting_i(sesskey, "BugDeriveKey2", 2-conf_get_int(conf, CONF_sshbug_derivekey2));
-    write_setting_i(sesskey, "BugRSAPad2", 2-conf_get_int(conf, CONF_sshbug_rsapad2));
-    write_setting_i(sesskey, "BugPKSessID2", 2-conf_get_int(conf, CONF_sshbug_pksessid2));
-    write_setting_i(sesskey, "BugRekey2", 2-conf_get_int(conf, CONF_sshbug_rekey2));
-    write_setting_i(sesskey, "BugMaxPkt2", 2-conf_get_int(conf, CONF_sshbug_maxpkt2));
-    write_setting_i(sesskey, "BugOldGex2", 2-conf_get_int(conf, CONF_sshbug_oldgex2));
-    write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj));
-    write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq));
-    write_setting_i(sesskey, "BugRSASHA2CertUserauth", 2-conf_get_int(conf, CONF_sshbug_rsa_sha2_cert_userauth));
-    write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart));
-    write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit));
-    write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp));
-    write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell));
-    write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left));
-    write_setting_fontspec(sesskey, "BoldFont", conf_get_fontspec(conf, CONF_boldfont));
-    write_setting_fontspec(sesskey, "WideFont", conf_get_fontspec(conf, CONF_widefont));
-    write_setting_fontspec(sesskey, "WideBoldFont", conf_get_fontspec(conf, CONF_wideboldfont));
-    write_setting_b(sesskey, "ShadowBold", conf_get_bool(conf, CONF_shadowbold));
-    write_setting_i(sesskey, "ShadowBoldOffset", conf_get_int(conf, CONF_shadowboldoffset));
-    write_setting_s(sesskey, "SerialLine", conf_get_str(conf, CONF_serline));
-    write_setting_i(sesskey, "SerialSpeed", conf_get_int(conf, CONF_serspeed));
-    write_setting_i(sesskey, "SerialDataBits", conf_get_int(conf, CONF_serdatabits));
-    write_setting_i(sesskey, "SerialStopHalfbits", conf_get_int(conf, CONF_serstopbits));
-    write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity));
-    write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow));
-    write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass));
-    write_setting_b(sesskey, "ConnectionSharing", conf_get_bool(conf, CONF_ssh_connection_sharing));
-    write_setting_b(sesskey, "ConnectionSharingUpstream", conf_get_bool(conf, CONF_ssh_connection_sharing_upstream));
-    write_setting_b(sesskey, "ConnectionSharingDownstream", conf_get_bool(conf, CONF_ssh_connection_sharing_downstream));
     wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, false);
-
-    /*
-     * SUPDUP settings
-     */
-    write_setting_s(sesskey, "SUPDUPLocation", conf_get_str(conf, CONF_supdup_location));
-    write_setting_i(sesskey, "SUPDUPCharset", conf_get_int(conf, CONF_supdup_ascii_set));
-    write_setting_b(sesskey, "SUPDUPMoreProcessing", conf_get_bool(conf, CONF_supdup_more));
-    write_setting_b(sesskey, "SUPDUPScrolling", conf_get_bool(conf, CONF_supdup_scroll));
 }
 
 bool load_settings(const char *section, Conf *conf)
@@ -837,19 +708,83 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
     int i;
     char *prot;
 
-    conf_set_bool(conf, CONF_ssh_subsys, false); /* FIXME: load this properly */
-    conf_set_str(conf, CONF_remote_cmd, "");
-    conf_set_str(conf, CONF_remote_cmd2, "");
-    conf_set_str(conf, CONF_ssh_nc_host, "");
-
-    gpps(sesskey, "HostName", "", conf, CONF_host);
-    gppfile(sesskey, "LogFileName", conf, CONF_logfilename);
-    gppi(sesskey, "LogType", 0, conf, CONF_logtype);
-    gppi(sesskey, "LogFileClash", LGXF_ASK, conf, CONF_logxfovr);
-    gppb(sesskey, "LogFlush", true, conf, CONF_logflush);
-    gppb(sesskey, "LogHeader", true, conf, CONF_logheader);
-    gppb(sesskey, "SSHLogOmitPasswords", true, conf, CONF_logomitpass);
-    gppb(sesskey, "SSHLogOmitData", false, conf, CONF_logomitdata);
+    /* Load the settings simple enough to handle automatically */
+    for (size_t key = 0; key < N_CONFIG_OPTIONS; key++) {
+        const ConfKeyInfo *info = &conf_key_info[key];
+        if (info->not_saved) {
+            /* Mappings are assumed to default to empty */
+            if (info->subkey_type == CONF_TYPE_NONE) {
+                switch (info->value_type) {
+                  case CONF_TYPE_STR:
+                  case CONF_TYPE_STR_AMBI:
+                    conf_set_str(conf, key, info->default_value.sval);
+                    break;
+                  case CONF_TYPE_INT:
+                    conf_set_int(conf, key, info->default_value.ival);
+                    break;
+                  case CONF_TYPE_BOOL:
+                    conf_set_bool(conf, key, info->default_value.bval);
+                    break;
+                  default:
+                    unreachable("bad key type in load_open_settings");
+                }
+            }
+        } else if (!info->load_custom) {
+            /* Mappings are handled individually below */
+            assert(info->subkey_type == CONF_TYPE_NONE);
+            switch (info->value_type) {
+              case CONF_TYPE_STR:
+              case CONF_TYPE_STR_AMBI:
+                gpps(sesskey, info->save_keyword, info->default_value.sval,
+                     conf, key);
+                break;
+              case CONF_TYPE_INT:
+                if (!info->storage_enum) {
+                    gppi(sesskey, info->save_keyword,
+                         info->default_value.ival, conf, key);
+                } else {
+                    /*
+                     * Because our internal defaults are stored as the
+                     * value we want in Conf, but our API for
+                     * retrieving integers from storage requires a
+                     * default value to fill in if no record is found,
+                     * we must first figure out the default _storage_
+                     * value, ugh.
+                     */
+                    int defstorage;
+                    bool success = conf_enum_map_to_storage(
+                        info->storage_enum, info->default_value.ival,
+                        &defstorage);
+                    assert(success && "unmapped default");
+
+                    /* Now retrieve the stored value */
+                    int storageval = gppi_raw(sesskey, info->save_keyword,
+                                              defstorage);
+
+                    /* And translate it back to Conf representation,
+                     * replacing it with our Conf-rep default on failure */
+                    int confval;
+                    if (!conf_enum_map_from_storage(
+                            info->storage_enum, storageval, &confval))
+                        confval = info->default_value.ival;
+                    conf_set_int(conf, key, confval);
+                }
+                break;
+              case CONF_TYPE_BOOL:
+                gppb(sesskey, info->save_keyword, info->default_value.bval,
+                     conf, key);
+                break;
+              case CONF_TYPE_FILENAME:
+                gppfile(sesskey, info->save_keyword, conf, key);
+                break;
+              case CONF_TYPE_FONT:
+                gppfont(sesskey, info->save_keyword, conf, key);
+                break;
+              default:
+                unreachable("bad key type in load_open_settings");
+            }
+        }
+    }
 
     prot = gpps_raw(sesskey, "Protocol", "default");
     conf_set_int(conf, CONF_protocol, default_protocol);
@@ -863,13 +798,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
     }
     sfree(prot);
 
-    /* Address family selection */
-    gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, conf, CONF_addressfamily);
-
-    /* The CloseOnExit numbers are arranged in a different order from
-     * the standard FORCE_ON / FORCE_OFF / AUTO. */
-    i = gppi_raw(sesskey, "CloseOnExit", 1); conf_set_int(conf, CONF_close_on_exit, (i+1)%3);
-    gppb(sesskey, "WarnOnClose", true, conf, CONF_warn_on_close);
     {
         /* This is two values for backward compatibility with 0.50/0.51 */
         int pingmin, pingsec;
@@ -877,10 +805,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
         pingsec = gppi_raw(sesskey, "PingIntervalSecs", 0);
         conf_set_int(conf, CONF_ping_interval, pingmin * 60 + pingsec);
     }
-    gppb(sesskey, "TCPNoDelay", true, conf, CONF_tcp_nodelay);
-    gppb(sesskey, "TCPKeepalives", false, conf, CONF_tcp_keepalives);
-    gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype);
-    gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed);
     if (gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) {
         /*
          * Backwards compatibility with old saved settings.
@@ -933,46 +857,26 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
     }
 
     /* proxy settings */
-    gpps(sesskey, "ProxyExcludeList", "", conf, CONF_proxy_exclude_list);
-    i = gppi_raw(sesskey, "ProxyDNS", 1); conf_set_int(conf, CONF_proxy_dns, (i+1)%3);
-    gppb(sesskey, "ProxyLocalhost", false, conf, CONF_even_proxy_localhost);
-    gppi(sesskey, "ProxyMethod", -1, conf, CONF_proxy_type);
-    if (conf_get_int(conf, CONF_proxy_type) == -1) {
-        int i;
-        i = gppi_raw(sesskey, "ProxyType", 0);
-        if (i == 0)
-            conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
-        else if (i == 1)
-            conf_set_int(conf, CONF_proxy_type, PROXY_HTTP);
-        else if (i == 3)
-            conf_set_int(conf, CONF_proxy_type, PROXY_TELNET);
-        else if (i == 4)
-            conf_set_int(conf, CONF_proxy_type, PROXY_CMD);
-        else {
-            i = gppi_raw(sesskey, "ProxySOCKSVersion", 5);
-            if (i == 5)
-                conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS5);
-            else
-                conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS4);
+    {
+        int storageval = gppi_raw(sesskey, "ProxyMethod", -1);
+        int confval;
+        if (!conf_enum_map_from_storage(&conf_enum_proxy_type,
+                                        storageval, &confval)) {
+            /*
+             * Fall back to older ProxyType and ProxySOCKSVersion format
+             */
+            storageval = gppi_raw(sesskey, "ProxyType", 0);
+            if (conf_enum_map_from_storage(&conf_enum_old_proxy_type,
+                                           storageval, &confval)) {
+                if (confval == PROXY_SOCKS5 &&
+                    gppi_raw(sesskey, "ProxySOCKSVersion", 5) == 4)
+                    confval = PROXY_SOCKS4;
+            }
         }
+        conf_set_int(conf, CONF_proxy_type, confval);
     }
-    gpps(sesskey, "ProxyHost", "proxy", conf, CONF_proxy_host);
-    gppi(sesskey, "ProxyPort", 80, conf, CONF_proxy_port);
-    gpps(sesskey, "ProxyUsername", "", conf, CONF_proxy_username);
-    gpps(sesskey, "ProxyPassword", "", conf, CONF_proxy_password);
-    gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n",
-         conf, CONF_proxy_telnet_command);
-    gppi(sesskey, "ProxyLogToTerm", FORCE_OFF, conf, CONF_proxy_log_to_term);
+
     gppmap(sesskey, "Environment", conf, CONF_environmt);
-    gpps(sesskey, "UserName", "", conf, CONF_username);
-    gppb(sesskey, "UserNameFromEnvironment", false,
-         conf, CONF_username_from_env);
-    gpps(sesskey, "LocalUserName", "", conf, CONF_localusername);
-    gppb(sesskey, "NoPTY", false, conf, CONF_nopty);
-    gppb(sesskey, "Compression", false, conf, CONF_compression);
-    gppb(sesskey, "TryAgent", true, conf, CONF_tryagent);
-    gppb(sesskey, "AgentFwd", false, conf, CONF_agentfwd);
-    gppb(sesskey, "ChangeUsername", false, conf, CONF_change_username);
 #ifndef NO_GSSAPI
     gppb(sesskey, "GssapiFwd", false, conf, CONF_gssapifwd);
 #endif
@@ -1027,98 +931,34 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
     }
     gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN",
            hknames, HK_MAX, conf, CONF_ssh_hklist);
-    gppb(sesskey, "PreferKnownHostKeys", true, conf, CONF_ssh_prefer_known_hostkeys);
-    gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time);
 #ifndef NO_GSSAPI
     gppi(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey);
-#endif
-    gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data);
-    {
-        /* SSH-2 only by default */
-        int sshprot = gppi_raw(sesskey, "SshProt", 3);
-        /* Old sessions may contain the values corresponding to the fallbacks
-         * we used to allow; migrate them */
-        if (sshprot == 1)      sshprot = 0; /* => "SSH-1 only" */
-        else if (sshprot == 2) sshprot = 3; /* => "SSH-2 only" */
-        conf_set_int(conf, CONF_sshprot, sshprot);
-    }
-    gpps(sesskey, "LogHost", "", conf, CONF_loghost);
-    gppb(sesskey, "SSH2DES", false, conf, CONF_ssh2_des_cbc);
-    gppb(sesskey, "SshNoAuth", false, conf, CONF_ssh_no_userauth);
-    gppb(sesskey, "SshNoTrivialAuth", false, conf, CONF_ssh_no_trivial_userauth);
-    gppb(sesskey, "SshBanner", true, conf, CONF_ssh_show_banner);
-    gppb(sesskey, "AuthTIS", false, conf, CONF_try_tis_auth);
-    gppb(sesskey, "AuthKI", true, conf, CONF_try_ki_auth);
-#ifndef NO_GSSAPI
     gppb(sesskey, "AuthGSSAPI", true, conf, CONF_try_gssapi_auth);
     gppb(sesskey, "AuthGSSAPIKEX", true, conf, CONF_try_gssapi_kex);
     gprefs(sesskey, "GSSLibs", "\0",
            gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist);
     gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom);
 #endif
-    gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell);
-    gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile);
-    gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert);
-    gpps(sesskey, "AuthPlugin", "", conf, CONF_auth_plugin);
-    gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd);
-    gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ);
-    gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet);
-    gppb(sesskey, "BackspaceIsDelete", true, conf, CONF_bksp_is_delete);
-    gppb(sesskey, "RXVTHomeEnd", false, conf, CONF_rxvt_homeend);
-    gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type);
-    gppi(sesskey, "ShiftedArrowKeys", SHARROW_APPLICATION, conf,
-         CONF_sharrow_type);
-    gppb(sesskey, "NoApplicationKeys", false, conf, CONF_no_applic_k);
-    gppb(sesskey, "NoApplicationCursors", false, conf, CONF_no_applic_c);
-    gppb(sesskey, "NoMouseReporting", false, conf, CONF_no_mouse_rep);
-    gppb(sesskey, "NoRemoteResize", false, conf, CONF_no_remote_resize);
-    gppb(sesskey, "NoAltScreen", false, conf, CONF_no_alt_screen);
-    gppb(sesskey, "NoRemoteWinTitle", false, conf, CONF_no_remote_wintitle);
-    gppb(sesskey, "NoRemoteClearScroll", false,
-         conf, CONF_no_remote_clearscroll);
     {
-        /* Backward compatibility */
-        int no_remote_qtitle = gppi_raw(sesskey, "NoRemoteQTitle", 1);
-        /* We deliberately interpret the old setting of "no response" as
-         * "empty string". This changes the behaviour, but hopefully for
-         * the better; the user can always recover the old behaviour. */
-        gppi(sesskey, "RemoteQTitleAction",
-             no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL,
-             conf, CONF_remote_qtitle_action);
+        int storageval = gppi_raw(sesskey, "RemoteQTitleAction", -1);
+        int confval;
+        if (!conf_enum_map_from_storage(&conf_enum_remote_qtitle_action,
+                                        storageval, &confval)) {
+            /*
+             * Fall back to older NoRemoteQTitle format
+             */
+            storageval = gppi_raw(sesskey, "NoRemoteQTitle", 1);
+            /* We deliberately interpret the old setting of "no response" as
+             * "empty string". This changes the behaviour, but hopefully for
+             * the better; the user can always recover the old behaviour. */
+            confval = storageval ? TITLE_EMPTY : TITLE_REAL;
+        }
+        conf_set_int(conf, CONF_remote_qtitle_action, confval);
     }
-    gppb(sesskey, "NoDBackspace", false, conf, CONF_no_dbackspace);
-    gppb(sesskey, "NoRemoteCharset", false, conf, CONF_no_remote_charset);
-    gppb(sesskey, "ApplicationCursorKeys", false, conf, CONF_app_cursor);
-    gppb(sesskey, "ApplicationKeypad", false, conf, CONF_app_keypad);
-    gppb(sesskey, "NetHackKeypad", false, conf, CONF_nethack_keypad);
-    gppb(sesskey, "AltF4", true, conf, CONF_alt_f4);
-    gppb(sesskey, "AltSpace", false, conf, CONF_alt_space);
-    gppb(sesskey, "AltOnly", false, conf, CONF_alt_only);
-    gppb(sesskey, "ComposeKey", false, conf, CONF_compose_key);
-    gppb(sesskey, "CtrlAltKeys", true, conf, CONF_ctrlaltkeys);
 #ifdef OSX_META_KEY_CONFIG
     gppb(sesskey, "OSXOptionMeta", true, conf, CONF_osx_option_meta);
     gppb(sesskey, "OSXCommandMeta", false, conf, CONF_osx_command_meta);
 #endif
-    gppb(sesskey, "TelnetKey", false, conf, CONF_telnet_keyboard);
-    gppb(sesskey, "TelnetRet", true, conf, CONF_telnet_newline);
-    gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho);
-    gppi(sesskey, "LocalEdit", AUTO, conf, CONF_localedit);
-    gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback);
-    gppb(sesskey, "AlwaysOnTop", false, conf, CONF_alwaysontop);
-    gppb(sesskey, "FullScreenOnAltEnter", false,
-         conf, CONF_fullscreenonaltenter);
-    gppb(sesskey, "HideMousePtr", false, conf, CONF_hide_mouseptr);
-    gppb(sesskey, "SunkenEdge", false, conf, CONF_sunken_edge);
-    gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border);
-    gppi(sesskey, "CurType", 0, conf, CONF_cursor_type);
-    gppb(sesskey, "BlinkCur", false, conf, CONF_blink_cur);
-    /* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */
-    gppi(sesskey, "Beep", 1, conf, CONF_beep);
-    gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind);
-    gppfile(sesskey, "BellWaveFile", conf, CONF_bell_wavefile);
-    gppb(sesskey, "BellOverload", true, conf, CONF_bellovl);
-    gppi(sesskey, "BellOverloadN", 5, conf, CONF_bellovl_n);
     i = gppi_raw(sesskey, "BellOverloadT", 2*TICKSPERSEC
 #ifdef PUTTY_UNIX_PLATFORM_H
                                    *1000
@@ -1139,26 +979,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
                  / 1000
 #endif
                  );
-    gppi(sesskey, "ScrollbackLines", 2000, conf, CONF_savelines);
-    gppb(sesskey, "DECOriginMode", false, conf, CONF_dec_om);
-    gppb(sesskey, "AutoWrapMode", true, conf, CONF_wrap_mode);
-    gppb(sesskey, "LFImpliesCR", false, conf, CONF_lfhascr);
-    gppb(sesskey, "CRImpliesLF", false, conf, CONF_crhaslf);
-    gppb(sesskey, "DisableArabicShaping", false, conf, CONF_no_arabicshaping);
-    gppb(sesskey, "DisableBidi", false, conf, CONF_no_bidi);
-    gppb(sesskey, "WinNameAlways", true, conf, CONF_win_name_always);
-    gpps(sesskey, "WinTitle", "", conf, CONF_wintitle);
-    gppi(sesskey, "TermWidth", 80, conf, CONF_width);
-    gppi(sesskey, "TermHeight", 24, conf, CONF_height);
-    gppfont(sesskey, "Font", conf, CONF_font);
-    gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality);
-    gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode);
-    gppb(sesskey, "UseSystemColours", false, conf, CONF_system_colour);
-    gppb(sesskey, "TryPalette", false, conf, CONF_try_palette);
-    gppb(sesskey, "ANSIColour", true, conf, CONF_ansi_colour);
-    gppb(sesskey, "Xterm256Colour", true, conf, CONF_xterm_256_colour);
-    gppb(sesskey, "TrueColour", true, conf, CONF_true_colour);
-    i = gppi_raw(sesskey, "BoldAsColour", 1); conf_set_int(conf, CONF_bold_style, i+1);
 
     for (i = 0; i < 22; i++) {
         static const char *const defaults[] = {
@@ -1179,13 +999,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
         }
         sfree(buf2);
     }
-    gppb(sesskey, "RawCNP", false, conf, CONF_rawcnp);
-    gppb(sesskey, "UTF8linedraw", false, conf, CONF_utf8linedraw);
-    gppb(sesskey, "PasteRTF", false, conf, CONF_rtf_paste);
-    gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm);
-    gppb(sesskey, "RectSelect", false, conf, CONF_rect_select);
-    gppb(sesskey, "PasteControls", false, conf, CONF_paste_controls);
-    gppb(sesskey, "MouseOverride", true, conf, CONF_mouse_override);
     for (i = 0; i < 256; i += 32) {
         static const char *const defaults[] = {
             "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
@@ -1212,8 +1025,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
         }
         sfree(buf2);
     }
-    gppb(sesskey, "MouseAutocopy", CLIPUI_DEFAULT_AUTOCOPY,
-         conf, CONF_mouseautocopy);
     read_clip_setting(sesskey, "MousePaste", CLIPUI_DEFAULT_MOUSE,
                       conf, CONF_mousepaste, CONF_mousepaste_custom);
     read_clip_setting(sesskey, "CtrlShiftIns", CLIPUI_DEFAULT_INS,
@@ -1224,32 +1035,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
      * The empty default for LineCodePage will be converted later
      * into a plausible default for the locale.
      */
-    gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage);
-    gppb(sesskey, "CJKAmbigWide", false, conf, CONF_cjk_ambig_wide);
-    gppb(sesskey, "UTF8Override", true, conf, CONF_utf8_override);
-    gpps(sesskey, "Printer", "", conf, CONF_printer);
-    gppb(sesskey, "CapsLockCyr", false, conf, CONF_xlat_capslockcyr);
-    gppb(sesskey, "ScrollBar", true, conf, CONF_scrollbar);
-    gppb(sesskey, "ScrollBarFullScreen", false,
-         conf, CONF_scrollbar_in_fullscreen);
-    gppb(sesskey, "ScrollOnKey", false, conf, CONF_scroll_on_key);
-    gppb(sesskey, "ScrollOnDisp", true, conf, CONF_scroll_on_disp);
-    gppb(sesskey, "EraseToScrollback", true, conf, CONF_erase_to_scrollback);
-    gppi(sesskey, "LockSize", 0, conf, CONF_resize_action);
-    gppb(sesskey, "BCE", true, conf, CONF_bce);
-    gppb(sesskey, "BlinkText", false, conf, CONF_blinktext);
-    gppb(sesskey, "X11Forward", false, conf, CONF_x11_forward);
-    gpps(sesskey, "X11Display", "", conf, CONF_x11_display);
-    gppi(sesskey, "X11AuthType", X11_MIT, conf, CONF_x11_auth);
-    gppfile(sesskey, "X11AuthFile", conf, CONF_xauthfile);
-
-    gppb(sesskey, "LocalPortAcceptAll", false, conf, CONF_lport_acceptall);
-    gppb(sesskey, "RemotePortAcceptAll", false, conf, CONF_rport_acceptall);
+
     gppmap(sesskey, "PortForwardings", conf, CONF_portfwd);
-    i = gppi_raw(sesskey, "BugIgnore1", 0); conf_set_int(conf, CONF_sshbug_ignore1, 2-i);
-    i = gppi_raw(sesskey, "BugPlainPW1", 0); conf_set_int(conf, CONF_sshbug_plainpw1, 2-i);
-    i = gppi_raw(sesskey, "BugRSA1", 0); conf_set_int(conf, CONF_sshbug_rsa1, 2-i);
-    i = gppi_raw(sesskey, "BugIgnore2", 0); conf_set_int(conf, CONF_sshbug_ignore2, 2-i);
     {
         int i;
         i = gppi_raw(sesskey, "BugHMAC2", 0); conf_set_int(conf, CONF_sshbug_hmac2, 2-i);
@@ -1259,48 +1046,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
                 conf_set_int(conf, CONF_sshbug_hmac2, FORCE_ON);
         }
     }
-    i = gppi_raw(sesskey, "BugDeriveKey2", 0); conf_set_int(conf, CONF_sshbug_derivekey2, 2-i);
-    i = gppi_raw(sesskey, "BugRSAPad2", 0); conf_set_int(conf, CONF_sshbug_rsapad2, 2-i);
-    i = gppi_raw(sesskey, "BugPKSessID2", 0); conf_set_int(conf, CONF_sshbug_pksessid2, 2-i);
-    i = gppi_raw(sesskey, "BugRekey2", 0); conf_set_int(conf, CONF_sshbug_rekey2, 2-i);
-    i = gppi_raw(sesskey, "BugMaxPkt2", 0); conf_set_int(conf, CONF_sshbug_maxpkt2, 2-i);
-    i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i);
-    i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i);
-    i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i);
-    i = gppi_raw(sesskey, "BugRSASHA2CertUserauth", 0); conf_set_int(conf, CONF_sshbug_rsa_sha2_cert_userauth, 2-i);
-    i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i);
-    i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i);
-    conf_set_bool(conf, CONF_ssh_simple, false);
-    gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp);
-    gppb(sesskey, "LoginShell", true, conf, CONF_login_shell);
-    gppb(sesskey, "ScrollbarOnLeft", false, conf, CONF_scrollbar_on_left);
-    gppb(sesskey, "ShadowBold", false, conf, CONF_shadowbold);
-    gppfont(sesskey, "BoldFont", conf, CONF_boldfont);
-    gppfont(sesskey, "WideFont", conf, CONF_widefont);
-    gppfont(sesskey, "WideBoldFont", conf, CONF_wideboldfont);
-    gppi(sesskey, "ShadowBoldOffset", 1, conf, CONF_shadowboldoffset);
-    gpps(sesskey, "SerialLine", "", conf, CONF_serline);
-    gppi(sesskey, "SerialSpeed", 9600, conf, CONF_serspeed);
-    gppi(sesskey, "SerialDataBits", 8, conf, CONF_serdatabits);
-    gppi(sesskey, "SerialStopHalfbits", 2, conf, CONF_serstopbits);
-    gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity);
-    gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow);
-    gpps(sesskey, "WindowClass", "", conf, CONF_winclass);
-    gppb(sesskey, "ConnectionSharing", false,
-         conf, CONF_ssh_connection_sharing);
-    gppb(sesskey, "ConnectionSharingUpstream", true,
-         conf, CONF_ssh_connection_sharing_upstream);
-    gppb(sesskey, "ConnectionSharingDownstream", true,
-         conf, CONF_ssh_connection_sharing_downstream);
     gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys);
-
-    /*
-     * SUPDUP settings
-     */
-    gpps(sesskey, "SUPDUPLocation", "The Internet", conf, CONF_supdup_location);
-    gppi(sesskey, "SUPDUPCharset", false, conf, CONF_supdup_ascii_set);
-    gppb(sesskey, "SUPDUPMoreProcessing", false, conf, CONF_supdup_more);
-    gppb(sesskey, "SUPDUPScrolling", false, conf, CONF_supdup_scroll);
 }
 
 bool do_defaults(const char *session, Conf *conf)

+ 52 - 0
source/putty/specials.h

@@ -0,0 +1,52 @@
+/*
+ * Commands that are generally useful in multiple backends.
+ */
+SPECIAL(BRK)    /* serial-line break */
+SPECIAL(EOF)    /* end-of-file on session input */
+SPECIAL(NOP)    /* transmit data with no effect */
+SPECIAL(PING)   /* try to keep the session alive (probably, but not
+                 * necessarily, implemented as SS_NOP) */
+
+/*
+ * Commands specific to Telnet.
+ */
+SPECIAL(AYT)    /* Are You There */
+SPECIAL(SYNCH)  /* Synch */
+SPECIAL(EC)     /* Erase Character */
+SPECIAL(EL)     /* Erase Line */
+SPECIAL(GA)     /* Go Ahead */
+SPECIAL(ABORT)  /* Abort Process */
+SPECIAL(AO)     /* Abort Output */
+SPECIAL(IP)     /* Interrupt Process */
+SPECIAL(SUSP)   /* Suspend Process */
+SPECIAL(EOR)    /* End Of Record */
+SPECIAL(EOL)    /* Telnet end-of-line sequence (CRLF, as opposed to
+                 * CR NUL that escapes a literal CR) */
+
+/*
+ * Commands specific to SSH.
+ */
+SPECIAL(REKEY)  /* trigger an immediate repeat key exchange */
+SPECIAL(XCERT)  /* cross-certify another host key ('arg' indicates which) */
+
+/*
+ * Send a POSIX-style signal. (Useful in SSH and also pterm.)
+ *
+ * We use the master list in ssh/signal-list.h to define these enum
+ * values, which will come out looking like names of the form
+ * SS_SIGABRT, SS_SIGINT etc.
+ */
+#define SIGNAL_MAIN(name, text) SPECIAL(SIG ## name)
+#define SIGNAL_SUB(name) SPECIAL(SIG ## name)
+#include "ssh/signal-list.h"
+#undef SIGNAL_MAIN
+#undef SIGNAL_SUB
+
+/*
+ * These aren't really special commands, but they appear in the
+ * enumeration because the list returned from backend_get_specials()
+ * will use them to specify the structure of the GUI specials menu.
+ */
+SPECIAL(SEP)      /* Separator */
+SPECIAL(SUBMENU)  /* Start a new submenu with specified name */
+SPECIAL(EXITMENU) /* Exit current submenu, or end of entire specials list */

+ 5 - 5
source/putty/ssh.h

@@ -225,7 +225,7 @@ struct ConnectionLayerVtable {
      * PortFwdManager */
     SshChannel *(*lportfwd_open)(
         ConnectionLayer *cl, const char *hostname, int port,
-        const char *description, const SocketPeerInfo *peerinfo,
+        const char *description, const SocketEndpointInfo *peerinfo,
         Channel *chan);
 
     /* Initiate opening of a 'session'-type channel */
@@ -234,7 +234,7 @@ struct ConnectionLayerVtable {
     /* Open outgoing channels for X and agent forwarding. (Used in the
      * SSH server.) */
     SshChannel *(*serverside_x11_open)(ConnectionLayer *cl, Channel *chan,
-                                       const SocketPeerInfo *pi);
+                                       const SocketEndpointInfo *pi);
     SshChannel *(*serverside_agent_open)(ConnectionLayer *cl, Channel *chan);
 
     /* Add an X11 display for ordinary X forwarding */
@@ -324,12 +324,12 @@ static inline void ssh_rportfwd_remove(
 { cl->vt->rportfwd_remove(cl, rpf); }
 static inline SshChannel *ssh_lportfwd_open(
     ConnectionLayer *cl, const char *host, int port,
-    const char *desc, const SocketPeerInfo *pi, Channel *chan)
+    const char *desc, const SocketEndpointInfo *pi, Channel *chan)
 { return cl->vt->lportfwd_open(cl, host, port, desc, pi, chan); }
 static inline SshChannel *ssh_session_open(ConnectionLayer *cl, Channel *chan)
 { return cl->vt->session_open(cl, chan); }
 static inline SshChannel *ssh_serverside_x11_open(
-    ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
+    ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi)
 { return cl->vt->serverside_x11_open(cl, chan, pi); }
 static inline SshChannel *ssh_serverside_agent_open(
     ConnectionLayer *cl, Channel *chan)
@@ -1383,7 +1383,7 @@ char *platform_get_x_display(void);
  * calling this function to do the rest of the work.
  */
 void x11_get_auth_from_authfile(struct X11Display *display,
-                                const char *authfilename);
+                                Filename *authfilename);
 void x11_format_auth_for_authfile(
     BinarySink *bs, SockAddr *addr, int display_no,
     ptrlen authproto, ptrlen authdata);

+ 2 - 2
source/putty/ssh/connection2-client.c

@@ -156,7 +156,7 @@ bool ssh2_connection_parse_global_request(
 PktOut *ssh2_portfwd_chanopen(
     struct ssh2_connection_state *s, struct ssh2_channel *c,
     const char *hostname, int port,
-    const char *description, const SocketPeerInfo *peerinfo)
+    const char *description, const SocketEndpointInfo *peerinfo)
 {
     PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
     PktOut *pktout;
@@ -321,7 +321,7 @@ SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
 }
 
 SshChannel *ssh2_serverside_x11_open(
-    ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
+    ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi)
 {
     unreachable("Should never be called in the client");
 }

+ 2 - 2
source/putty/ssh/connection2.c

@@ -33,7 +33,7 @@ static const PacketProtocolLayerVtable ssh2_connection_vtable = {
 
 static SshChannel *ssh2_lportfwd_open(
     ConnectionLayer *cl, const char *hostname, int port,
-    const char *description, const SocketPeerInfo *pi, Channel *chan);
+    const char *description, const SocketEndpointInfo *pi, Channel *chan);
 static struct X11FakeAuth *ssh2_add_x11_display(
     ConnectionLayer *cl, int authtype, struct X11Display *x11disp);
 static struct X11FakeAuth *ssh2_add_sharing_x11_display(
@@ -1461,7 +1461,7 @@ static void ssh2channel_hint_channel_is_simple(SshChannel *sc)
 
 static SshChannel *ssh2_lportfwd_open(
     ConnectionLayer *cl, const char *hostname, int port,
-    const char *description, const SocketPeerInfo *pi, Channel *chan)
+    const char *description, const SocketEndpointInfo *pi, Channel *chan)
 {
     struct ssh2_connection_state *s =
         container_of(cl, struct ssh2_connection_state, cl);

+ 2 - 2
source/putty/ssh/connection2.h

@@ -159,7 +159,7 @@ PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type);
 PktOut *ssh2_portfwd_chanopen(
     struct ssh2_connection_state *s, struct ssh2_channel *c,
     const char *hostname, int port,
-    const char *description, const SocketPeerInfo *peerinfo);
+    const char *description, const SocketEndpointInfo *peerinfo);
 
 struct ssh_rportfwd *ssh2_rportfwd_alloc(
     ConnectionLayer *cl,
@@ -170,7 +170,7 @@ void ssh2_rportfwd_remove(
     ConnectionLayer *cl, struct ssh_rportfwd *rpf);
 SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan);
 SshChannel *ssh2_serverside_x11_open(
-    ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi);
+    ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi);
 SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan);
 
 void ssh2channel_send_exit_status(SshChannel *c, int status);

+ 1 - 1
source/putty/ssh/kex2-client.c

@@ -1021,7 +1021,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             ppl_logevent("%s", fingerprint);
             sfree(fingerprint);
 
-            store_host_key(s->savedhost, s->savedport,
+            store_host_key(s->ppl.seat, s->savedhost, s->savedport,
                            ssh_key_cache_id(s->hkey), s->keystr);
             /*
              * Don't forget to store the new key as the one we'll be

+ 7 - 3
source/putty/ssh/mainchan.c

@@ -182,7 +182,9 @@ static void mainchan_open_confirmation(Channel *chan)
         if (mc->n_req_env)
             ppl_logevent("Sent %d environment variables", mc->n_req_env);
 
-        cmd = conf_get_str(mc->conf, CONF_remote_cmd);
+        /* Ignore encoding of CONF_remote_cmd so as not to disturb
+         * legacy handling of non-UTF-8 commands */
+        cmd = conf_get_str_ambi(mc->conf, CONF_remote_cmd, NULL);
         if (conf_get_bool(mc->conf, CONF_ssh_subsys)) {
             retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd);
         } else if (*cmd) {
@@ -205,7 +207,9 @@ static void mainchan_open_confirmation(Channel *chan)
 
 static void mainchan_try_fallback_command(mainchan *mc)
 {
-    const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2);
+    /* Ignore encoding of CONF_remote_cmd2 so as not to disturb legacy
+     * handling of non-UTF-8 commands */
+    const char *cmd = conf_get_str_ambi(mc->conf, CONF_remote_cmd2, NULL);
     if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) {
         sshfwd_start_subsystem(mc->sc, true, cmd);
     } else {
@@ -288,7 +292,7 @@ static void mainchan_request_response(Channel *chan, bool success)
         if (success) {
             ppl_logevent("Started a shell/command");
             mainchan_ready(mc);
-        } else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) {
+        } else if (*conf_get_str_ambi(mc->conf, CONF_remote_cmd2, NULL)) {
             ppl_logevent("Primary command failed; attempting fallback");
             mainchan_try_fallback_command(mc);
         } else {

+ 4 - 16
source/putty/ssh/portfwd.c

@@ -96,18 +96,6 @@ static void free_portlistener_state(struct PortListener *pl)
     sfree(pl);
 }
 
-static void pfd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
-                    const char *error_msg, int error_code)
-{
-    /* we have to dump these since we have no interface to logging.c */
-}
-
-static void pfl_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
-                    const char *error_msg, int error_code)
-{
-    /* we have to dump these since we have no interface to logging.c */
-}
-
 static void pfd_close(struct PortForwarding *pf);
 
 static void pfd_closing(Plug *plug, PlugCloseType type, const char *error_msg)
@@ -152,7 +140,7 @@ static SshChannel *wrap_lportfwd_open(
     ConnectionLayer *cl, const char *hostname, int port,
     Socket *s, Channel *chan)
 {
-    SocketPeerInfo *pi;
+    SocketEndpointInfo *pi;
     char *description;
     SshChannel *toret;
 
@@ -163,7 +151,7 @@ static SshChannel *wrap_lportfwd_open(
         description = dupstr("forwarding");
     }
     toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan);
-    sk_free_peer_info(pi);
+    sk_free_endpoint_info(pi);
 
     sfree(description);
     return toret;
@@ -431,7 +419,7 @@ static void pfd_sent(Plug *plug, size_t bufsize)
 }
 
 static const PlugVtable PortForwarding_plugvt = {
-    .log = pfd_log,
+    .log = nullplug_log,
     .closing = pfd_closing,
     .receive = pfd_receive,
     .sent = pfd_sent,
@@ -554,7 +542,7 @@ static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
 }
 
 static const PlugVtable PortListener_plugvt = {
-    .log = pfl_log,
+    .log = nullplug_log,
     .closing = pfl_closing,
     .accepting = pfl_accepting,
 };

+ 3 - 3
source/putty/ssh/sharing.c

@@ -1742,7 +1742,7 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
 #define crGetChar(c) do                                         \
     {                                                           \
         while (len == 0) {                                      \
-            *crLine =__LINE__; return; case __LINE__:;          \
+            *crLine = __LINE__; return; case __LINE__:;         \
         }                                                       \
         len--;                                                  \
         (c) = (unsigned char)*data++;                           \
@@ -1909,7 +1909,7 @@ static int share_listen_accepting(Plug *plug,
         plug, struct ssh_sharing_state, plug);
     struct ssh_sharing_connstate *cs;
     const char *err;
-    SocketPeerInfo *peerinfo;
+    SocketEndpointInfo *peerinfo;
 
     /*
      * A new downstream has connected to us.
@@ -1956,7 +1956,7 @@ static int share_listen_accepting(Plug *plug,
     log_downstream(cs, "connected%s%s",
                    (peerinfo && peerinfo->log_text ? " from " : ""),
                    (peerinfo && peerinfo->log_text ? peerinfo->log_text : ""));
-    sk_free_peer_info(peerinfo);
+    sk_free_endpoint_info(peerinfo);
 
     return 0;
 }

+ 7 - 4
source/putty/ssh/ssh.c

@@ -600,8 +600,9 @@ void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...)
     }
 }
 
-static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr,
-                           int port, const char *error_msg, int error_code)
+static void ssh_socket_log(Plug *plug, Socket *s, PlugLogType type,
+                           SockAddr *addr, int port,
+                           const char *error_msg, int error_code)
 {
     Ssh *ssh = container_of(plug, Ssh, plug);
 
@@ -615,7 +616,7 @@ static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr,
      */
 
     if (!ssh->attempting_connshare)
-        backend_socket_log(ssh->seat, ssh->logctx, type, addr, port,
+        backend_socket_log(ssh->seat, ssh->logctx, s, type, addr, port,
                            error_msg, error_code, ssh->conf,
                            ssh->session_started);
 }
@@ -839,10 +840,12 @@ static char *connect_to_host(
                                 false, true, nodelay, keepalive,
                                 &ssh->plug, ssh->conf, &ssh->interactor);
         if ((err = sk_socket_error(ssh->s)) != NULL) {
+            char *toret = dupstr(err);
+            sk_close(ssh->s);
             ssh->s = NULL;
             seat_notify_remote_exit(ssh->seat);
             seat_notify_remote_disconnect(ssh->seat);
-            return dupstr(err);
+            return toret;
         }
     }
 

+ 7 - 2
source/putty/ssh/userauth2-client.c

@@ -318,8 +318,9 @@ static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s,
     return true;
 }
 
-static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr,
-                                int port, const char *err_msg, int err_code)
+static void authplugin_plug_log(Plug *plug, Socket *sock, PlugLogType type,
+                                SockAddr *addr, int port,
+                                const char *err_msg, int err_code)
 {
     struct ssh2_userauth_state *s = container_of(
         plug, struct ssh2_userauth_state, authplugin_plug);
@@ -763,6 +764,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
              */
         } else if ((s->username = s->default_username) == NULL) {
             s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+            s->cur_prompt->utf8 = true;
             s->cur_prompt->to_server = true;
             s->cur_prompt->from_server = false;
             s->cur_prompt->name = dupstr("SSH login name");
@@ -1799,6 +1801,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                 s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD;
 
                 s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+                s->cur_prompt->utf8 = true;
                 s->cur_prompt->to_server = true;
                 s->cur_prompt->from_server = false;
                 s->cur_prompt->name = dupstr("SSH password");
@@ -1887,6 +1890,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     prompt = get_string(pktin);
 
                     s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+                    s->cur_prompt->utf8 = true;
                     s->cur_prompt->to_server = true;
                     s->cur_prompt->from_server = false;
                     s->cur_prompt->name = dupstr("New SSH password");
@@ -2111,6 +2115,7 @@ static bool ssh2_userauth_ki_setup_prompts(
     inst = get_string(src);
     get_string(src); /* skip language tag */
     s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+    s->cur_prompt->utf8 = true;
     s->cur_prompt->to_server = true;
     s->cur_prompt->from_server = true;
 

+ 5 - 5
source/putty/sshcr.h

@@ -39,7 +39,7 @@
 #define crFinishFreeV   } sfree(s); return; } while (0)
 #define crReturn(z)     \
         do {\
-            *crLine =__LINE__; return (z); case __LINE__:;\
+            *crLine = __LINE__; return (z); case __LINE__:;\
         } while (0)
 #define crReturnV       \
         do {\
@@ -64,22 +64,22 @@
  */
 #define crMaybeWaitUntil(c)                     \
     do {                                        \
-        *crLine =__LINE__;                      \
+        *crLine = __LINE__;                     \
         case __LINE__: if (!(c)) return 0;      \
     } while (0)
 #define crMaybeWaitUntilV(c)                    \
     do {                                        \
-        *crLine =__LINE__;                      \
+        *crLine = __LINE__;                     \
         case __LINE__: if (!(c)) return;        \
     } while (0)
 #define crWaitUntil(c)                          \
     do {                                        \
-        *crLine =__LINE__; return;              \
+        *crLine = __LINE__; return;             \
         case __LINE__: if (!(c)) return 0;      \
     } while (0)
 #define crWaitUntilV(c)                         \
     do {                                        \
-        *crLine =__LINE__; return;              \
+        *crLine = __LINE__; return;             \
         case __LINE__: if (!(c)) return;        \
     } while (0)
 

+ 13 - 11
source/putty/sshpubk.c

@@ -705,7 +705,7 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase,
 {
     char header[40], *b, *encryption, *comment, *mac;
     const ssh_keyalg *alg;
-    ssh2_userkey *ret;
+    ssh2_userkey *ukey;
     strbuf *public_blob, *private_blob, *cipher_mac_keys_blob;
     strbuf *passphrase_salt = strbuf_new();
     ptrlen cipherkey, cipheriv, mackey;
@@ -716,7 +716,7 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase,
     const char *error = NULL;
     ppk_save_parameters params;
 
-    ret = NULL;                        /* return NULL for most errors */
+    ukey = NULL;                        /* return NULL for most errors */
     encryption = comment = mac = NULL;
     public_blob = private_blob = cipher_mac_keys_blob = NULL;
 
@@ -952,10 +952,10 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase,
              * unencrypted. Otherwise, it means Wrong Passphrase. */
             if (ciphertype->keylen != 0) {
                 error = "wrong passphrase";
-                ret = SSH2_WRONG_PASSPHRASE;
+                ukey = SSH2_WRONG_PASSPHRASE;
             } else {
                 error = "MAC failed";
-                ret = NULL;
+                ukey = NULL;
             }
             goto error;
         }
@@ -964,15 +964,15 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase,
     /*
      * Create and return the key.
      */
-    ret = snew(ssh2_userkey);
-    ret->comment = comment;
+    ukey = snew(ssh2_userkey);
+    ukey->comment = comment;
     comment = NULL;
-    ret->key = ssh_key_new_priv(
+    ukey->key = ssh_key_new_priv(
         alg, ptrlen_from_strbuf(public_blob),
         ptrlen_from_strbuf(private_blob));
-    if (!ret->key) {
-        sfree(ret);
-        ret = NULL;
+    if (!ukey->key) {
+        sfree(ukey);
+        ukey = NULL;
         error = "createkey failed";
         goto error;
     }
@@ -997,7 +997,7 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase,
     strbuf_free(passphrase_salt);
     if (errorstr)
         *errorstr = error;
-    return ret;
+    return ukey;
 }
 
 ssh2_userkey *ppk_load_f(const Filename *filename, const char *passphrase,
@@ -1096,6 +1096,8 @@ static bool rfc4716_loadpub(BinarySource *src, char **algorithm,
             }
         }
         sfree(line); line = NULL;
+        if (!get_avail(src))
+            break;
         line = mkstr(get_chomped_line(src));
     }
 

+ 3 - 1
source/putty/storage.h

@@ -89,8 +89,10 @@ int check_stored_host_key(const char *hostname, int port,
 /*
  * Write a host key into the database, overwriting any previous
  * entry that might have been there.
+ *
+ * A Seat is provided for error-reporting purposes.
  */
-void store_host_key(const char *hostname, int port,
+void store_host_key(Seat *seat, const char *hostname, int port,
                     const char *keytype, const char *key);
 
 /* ----------------------------------------------------------------------

+ 1 - 1
source/putty/stubs/null-plug.c

@@ -7,7 +7,7 @@
 
 #include "putty.h"
 
-void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr,
+void nullplug_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr,
                   int port, const char *err_msg, int err_code)
 {
 }

+ 1 - 0
source/putty/stubs/null-seat.c

@@ -17,6 +17,7 @@ void nullseat_notify_session_started(Seat *seat) {}
 void nullseat_notify_remote_exit(Seat *seat) {}
 void nullseat_notify_remote_disconnect(Seat *seat) {}
 void nullseat_connection_fatal(Seat *seat, const char *message) {}
+void nullseat_nonfatal(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) {}

+ 12 - 0
source/putty/stubs/null-socket.c

@@ -0,0 +1,12 @@
+/*
+ * null-socket.c: provide a null implementation of any Socket vtable
+ * method that might otherwise need to be reimplemented in multiple
+ * places as a no-op.
+ */
+
+#include "putty.h"
+
+SocketEndpointInfo *nullsock_endpoint_info(Socket *s, bool peer)
+{
+    return NULL;
+}

+ 11 - 1
source/putty/utils/backend_socket_log.c

@@ -4,7 +4,7 @@
 #include "putty.h"
 #include "network.h"
 
-void backend_socket_log(Seat *seat, LogContext *logctx,
+void backend_socket_log(Seat *seat, LogContext *logctx, Socket *sock,
                         PlugLogType type, SockAddr *addr, int port,
                         const char *error_msg, int error_code, Conf *conf,
                         bool session_started)
@@ -30,6 +30,16 @@ void backend_socket_log(Seat *seat, LogContext *logctx,
         else /* fallback if address unavailable */
             sprintf(addrbuf, "remote host");
         msg = dupprintf("Connected to %s", addrbuf);
+        if (sock) {
+            SocketEndpointInfo *local_end = sk_endpoint_info(sock, false);
+            if (local_end) {
+                char *newmsg = dupprintf("%s (from %s)", msg,
+                                         local_end->log_text);
+                sfree(msg);
+                msg = newmsg;
+                sk_free_endpoint_info(local_end);
+            }
+        }
         break;
       case PLUGLOG_PROXY_MSG: {
         /* Proxy-related log messages have their own identifying

+ 18 - 0
source/putty/utils/burnwcs.c

@@ -0,0 +1,18 @@
+/*
+ * 'Burn' a dynamically allocated wide string, in the sense of
+ * destroying it beyond recovery: overwrite it with zeroes and then
+ * free it.
+ */
+
+#include <wchar.h>
+
+#include "defs.h"
+#include "misc.h"
+
+void burnwcs(wchar_t *string)
+{
+    if (string) {
+        smemclr(string, sizeof(*string) * wcslen(string));
+        sfree(string);
+    }
+}

+ 179 - 102
source/putty/utils/conf.c

@@ -10,22 +10,6 @@
 #include "tree234.h"
 #include "putty.h"
 
-/*
- * Enumeration of types used in keys and values.
- */
-typedef enum {
-    TYPE_NONE, TYPE_BOOL, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT
-} Type;
-
-/*
- * Arrays which allow us to look up the subkey and value types for a
- * given primary key id.
- */
-#define CONF_SUBKEYTYPE_DEF(valtype, keytype, keyword) TYPE_ ## keytype,
-static int subkeytypes[] = { CONFIG_OPTIONS(CONF_SUBKEYTYPE_DEF) };
-#define CONF_VALUETYPE_DEF(valtype, keytype, keyword) TYPE_ ## valtype,
-static int valuetypes[] = { CONFIG_OPTIONS(CONF_VALUETYPE_DEF) };
-
 /*
  * Configuration keys are primarily integers (big enum of all the
  * different configurable options); some keys have string-designated
@@ -55,7 +39,10 @@ struct value {
     union {
         bool boolval;
         int intval;
-        char *stringval;
+        struct {
+            char *str;
+            bool utf8;
+        } stringval;
         Filename *fileval;
         FontSpec *fontval;
     } u;
@@ -87,17 +74,20 @@ static int conf_cmp(void *av, void *bv)
         return -1;
     else if (a->primary > b->primary)
         return +1;
-    switch (subkeytypes[a->primary]) {
-      case TYPE_INT:
+    switch (conf_key_info[a->primary].subkey_type) {
+      case CONF_TYPE_INT:
         if (a->secondary.i < b->secondary.i)
             return -1;
         else if (a->secondary.i > b->secondary.i)
             return +1;
         return 0;
-      case TYPE_STR:
+      case CONF_TYPE_STR:
+      case CONF_TYPE_UTF8:
         return strcmp(a->secondary.s, b->secondary.s);
-      default:
+      case CONF_TYPE_NONE:
         return 0;
+      default:
+        unreachable("Unsupported subkey type");
     }
 }
 
@@ -110,17 +100,20 @@ static int conf_cmp_constkey(void *av, void *bv)
         return -1;
     else if (a->primary > b->primary)
         return +1;
-    switch (subkeytypes[a->primary]) {
-      case TYPE_INT:
+    switch (conf_key_info[a->primary].subkey_type) {
+      case CONF_TYPE_INT:
         if (a->secondary.i < b->secondary.i)
             return -1;
         else if (a->secondary.i > b->secondary.i)
             return +1;
         return 0;
-      case TYPE_STR:
+      case CONF_TYPE_STR:
+      case CONF_TYPE_UTF8:
         return strcmp(a->secondary.s, b->secondary.s);
-      default:
+      case CONF_TYPE_NONE:
         return 0;
+      default:
+        unreachable("Unsupported subkey type");
     }
 }
 
@@ -131,7 +124,8 @@ static int conf_cmp_constkey(void *av, void *bv)
  */
 static void free_key(struct key *key)
 {
-    if (subkeytypes[key->primary] == TYPE_STR)
+    if (conf_key_info[key->primary].subkey_type == CONF_TYPE_STR ||
+        conf_key_info[key->primary].subkey_type == CONF_TYPE_UTF8)
         sfree(key->secondary.s);
 }
 
@@ -142,11 +136,12 @@ static void free_key(struct key *key)
 static void copy_key(struct key *to, struct key *from)
 {
     to->primary = from->primary;
-    switch (subkeytypes[to->primary]) {
-      case TYPE_INT:
+    switch (conf_key_info[to->primary].subkey_type) {
+      case CONF_TYPE_INT:
         to->secondary.i = from->secondary.i;
         break;
-      case TYPE_STR:
+      case CONF_TYPE_STR:
+      case CONF_TYPE_UTF8:
         to->secondary.s = dupstr(from->secondary.s);
         break;
     }
@@ -159,11 +154,12 @@ static void copy_key(struct key *to, struct key *from)
  */
 static void free_value(struct value *val, int type)
 {
-    if (type == TYPE_STR)
-        sfree(val->u.stringval);
-    else if (type == TYPE_FILENAME)
+    if (type == CONF_TYPE_STR || type == CONF_TYPE_UTF8 ||
+        type == CONF_TYPE_STR_AMBI)
+        sfree(val->u.stringval.str);
+    else if (type == CONF_TYPE_FILENAME)
         filename_free(val->u.fileval);
-    else if (type == TYPE_FONT)
+    else if (type == CONF_TYPE_FONT)
         fontspec_free(val->u.fontval);
 }
 
@@ -174,19 +170,22 @@ static void free_value(struct value *val, int type)
 static void copy_value(struct value *to, struct value *from, int type)
 {
     switch (type) {
-      case TYPE_BOOL:
+      case CONF_TYPE_BOOL:
         to->u.boolval = from->u.boolval;
         break;
-      case TYPE_INT:
+      case CONF_TYPE_INT:
         to->u.intval = from->u.intval;
         break;
-      case TYPE_STR:
-        to->u.stringval = dupstr(from->u.stringval);
+      case CONF_TYPE_STR:
+      case CONF_TYPE_UTF8:
+      case CONF_TYPE_STR_AMBI:
+        to->u.stringval.str = dupstr(from->u.stringval.str);
+        to->u.stringval.utf8 = from->u.stringval.utf8;
         break;
-      case TYPE_FILENAME:
+      case CONF_TYPE_FILENAME:
         to->u.fileval = filename_copy(from->u.fileval);
         break;
-      case TYPE_FONT:
+      case CONF_TYPE_FONT:
         to->u.fontval = fontspec_copy(from->u.fontval);
         break;
     }
@@ -198,7 +197,7 @@ static void copy_value(struct value *to, struct value *from, int type)
 static void free_entry(struct conf_entry *entry)
 {
     free_key(&entry->key);
-    free_value(&entry->value, valuetypes[entry->key.primary]);
+    free_value(&entry->value, conf_key_info[entry->key.primary].value_type);
     sfree(entry);
 }
 
@@ -211,7 +210,7 @@ Conf *conf_new(void)
     return conf;
 }
 
-static void conf_clear(Conf *conf)
+void conf_clear(Conf *conf)
 {
     struct conf_entry *entry;
 
@@ -248,7 +247,7 @@ void conf_copy_into(Conf *newconf, Conf *oldconf)
         entry2 = snew(struct conf_entry);
         copy_key(&entry2->key, &entry->key);
         copy_value(&entry2->value, &entry->value,
-                   valuetypes[entry->key.primary]);
+                   conf_key_info[entry->key.primary].value_type);
         add234(newconf->tree, entry2);
     }
 }
@@ -267,8 +266,8 @@ bool conf_get_bool(Conf *conf, int primary)
     struct key key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_BOOL);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_BOOL);
     key.primary = primary;
     entry = find234(conf->tree, &key, NULL);
     assert(entry);
@@ -280,8 +279,8 @@ int conf_get_int(Conf *conf, int primary)
     struct key key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_INT);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_INT);
     key.primary = primary;
     entry = find234(conf->tree, &key, NULL);
     assert(entry);
@@ -293,8 +292,8 @@ int conf_get_int_int(Conf *conf, int primary, int secondary)
     struct key key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_INT);
-    assert(valuetypes[primary] == TYPE_INT);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_INT);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_INT);
     key.primary = primary;
     key.secondary.i = secondary;
     entry = find234(conf->tree, &key, NULL);
@@ -307,12 +306,42 @@ char *conf_get_str(Conf *conf, int primary)
     struct key key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_STR);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_STR);
+    key.primary = primary;
+    entry = find234(conf->tree, &key, NULL);
+    assert(entry);
+    return entry->value.u.stringval.str;
+}
+
+char *conf_get_utf8(Conf *conf, int primary)
+{
+    struct key key;
+    struct conf_entry *entry;
+
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_UTF8);
+    key.primary = primary;
+    entry = find234(conf->tree, &key, NULL);
+    assert(entry);
+    return entry->value.u.stringval.str;
+}
+
+char *conf_get_str_ambi(Conf *conf, int primary, bool *utf8)
+{
+    struct key key;
+    struct conf_entry *entry;
+
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_STR ||
+           conf_key_info[primary].value_type == CONF_TYPE_UTF8 ||
+           conf_key_info[primary].value_type == CONF_TYPE_STR_AMBI);
     key.primary = primary;
     entry = find234(conf->tree, &key, NULL);
     assert(entry);
-    return entry->value.u.stringval;
+    if (utf8)
+        *utf8 = entry->value.u.stringval.utf8;
+    return entry->value.u.stringval.str;
 }
 
 char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary)
@@ -320,12 +349,12 @@ char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary)
     struct key key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_STR);
-    assert(valuetypes[primary] == TYPE_STR);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_STR);
     key.primary = primary;
     key.secondary.s = (char *)secondary;
     entry = find234(conf->tree, &key, NULL);
-    return entry ? entry->value.u.stringval : NULL;
+    return entry ? entry->value.u.stringval.str : NULL;
 }
 
 char *conf_get_str_str(Conf *conf, int primary, const char *secondary)
@@ -341,8 +370,8 @@ char *conf_get_str_strs(Conf *conf, int primary,
     struct constkey key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_STR);
-    assert(valuetypes[primary] == TYPE_STR);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_STR);
     key.primary = primary;
     if (subkeyin) {
         key.secondary.s = subkeyin;
@@ -354,7 +383,7 @@ char *conf_get_str_strs(Conf *conf, int primary,
     if (!entry || entry->key.primary != primary)
         return NULL;
     *subkeyout = entry->key.secondary.s;
-    return entry->value.u.stringval;
+    return entry->value.u.stringval.str;
 }
 
 char *conf_get_str_nthstrkey(Conf *conf, int primary, int n)
@@ -363,8 +392,8 @@ char *conf_get_str_nthstrkey(Conf *conf, int primary, int n)
     struct conf_entry *entry;
     int index;
 
-    assert(subkeytypes[primary] == TYPE_STR);
-    assert(valuetypes[primary] == TYPE_STR);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_STR);
     key.primary = primary;
     key.secondary.s = "";
     entry = findrelpos234(conf->tree, &key, conf_cmp_constkey,
@@ -382,8 +411,8 @@ Filename *conf_get_filename(Conf *conf, int primary)
     struct key key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_FILENAME);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_FILENAME);
     key.primary = primary;
     entry = find234(conf->tree, &key, NULL);
     assert(entry);
@@ -395,8 +424,8 @@ FontSpec *conf_get_fontspec(Conf *conf, int primary)
     struct key key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_FONT);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_FONT);
     key.primary = primary;
     entry = find234(conf->tree, &key, NULL);
     assert(entry);
@@ -407,8 +436,8 @@ void conf_set_bool(Conf *conf, int primary, bool value)
 {
     struct conf_entry *entry = snew(struct conf_entry);
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_BOOL);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_BOOL);
     entry->key.primary = primary;
     entry->value.u.boolval = value;
     conf_insert(conf, entry);
@@ -418,8 +447,8 @@ void conf_set_int(Conf *conf, int primary, int value)
 {
     struct conf_entry *entry = snew(struct conf_entry);
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_INT);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_INT);
     entry->key.primary = primary;
     entry->value.u.intval = value;
     conf_insert(conf, entry);
@@ -430,23 +459,56 @@ void conf_set_int_int(Conf *conf, int primary,
 {
     struct conf_entry *entry = snew(struct conf_entry);
 
-    assert(subkeytypes[primary] == TYPE_INT);
-    assert(valuetypes[primary] == TYPE_INT);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_INT);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_INT);
     entry->key.primary = primary;
     entry->key.secondary.i = secondary;
     entry->value.u.intval = value;
     conf_insert(conf, entry);
 }
 
-void conf_set_str(Conf *conf, int primary, const char *value)
+bool conf_try_set_str(Conf *conf, int primary, const char *value)
 {
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    if (conf_key_info[primary].value_type == CONF_TYPE_UTF8)
+        return false;
+    assert(conf_key_info[primary].value_type == CONF_TYPE_STR ||
+           conf_key_info[primary].value_type == CONF_TYPE_STR_AMBI);
+
     struct conf_entry *entry = snew(struct conf_entry);
+    entry->key.primary = primary;
+    entry->value.u.stringval.str = dupstr(value);
+    entry->value.u.stringval.utf8 = false;
+    conf_insert(conf, entry);
+    return true;
+}
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_STR);
+void conf_set_str(Conf *conf, int primary, const char *value)
+{
+    bool success = conf_try_set_str(conf, primary, value);
+    assert(success && "conf_set_str on CONF_TYPE_UTF8");
+}
+
+bool conf_try_set_utf8(Conf *conf, int primary, const char *value)
+{
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    if (conf_key_info[primary].value_type == CONF_TYPE_STR)
+        return false;
+    assert(conf_key_info[primary].value_type == CONF_TYPE_UTF8 ||
+           conf_key_info[primary].value_type == CONF_TYPE_STR_AMBI);
+
+    struct conf_entry *entry = snew(struct conf_entry);
     entry->key.primary = primary;
-    entry->value.u.stringval = dupstr(value);
+    entry->value.u.stringval.str = dupstr(value);
+    entry->value.u.stringval.utf8 = true;
     conf_insert(conf, entry);
+    return true;
+}
+
+void conf_set_utf8(Conf *conf, int primary, const char *value)
+{
+    bool success = conf_try_set_utf8(conf, primary, value);
+    assert(success && "conf_set_utf8 on CONF_TYPE_STR");
 }
 
 void conf_set_str_str(Conf *conf, int primary, const char *secondary,
@@ -454,11 +516,12 @@ void conf_set_str_str(Conf *conf, int primary, const char *secondary,
 {
     struct conf_entry *entry = snew(struct conf_entry);
 
-    assert(subkeytypes[primary] == TYPE_STR);
-    assert(valuetypes[primary] == TYPE_STR);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_STR);
     entry->key.primary = primary;
     entry->key.secondary.s = dupstr(secondary);
-    entry->value.u.stringval = dupstr(value);
+    entry->value.u.stringval.str = dupstr(value);
+    entry->value.u.stringval.utf8 = false;
     conf_insert(conf, entry);
 }
 
@@ -467,8 +530,8 @@ void conf_del_str_str(Conf *conf, int primary, const char *secondary)
     struct key key;
     struct conf_entry *entry;
 
-    assert(subkeytypes[primary] == TYPE_STR);
-    assert(valuetypes[primary] == TYPE_STR);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_STR);
     key.primary = primary;
     key.secondary.s = (char *)secondary;
     entry = find234(conf->tree, &key, NULL);
@@ -482,8 +545,8 @@ void conf_set_filename(Conf *conf, int primary, const Filename *value)
 {
     struct conf_entry *entry = snew(struct conf_entry);
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_FILENAME);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_FILENAME);
     entry->key.primary = primary;
     entry->value.u.fileval = filename_copy(value);
     conf_insert(conf, entry);
@@ -493,8 +556,8 @@ void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value)
 {
     struct conf_entry *entry = snew(struct conf_entry);
 
-    assert(subkeytypes[primary] == TYPE_NONE);
-    assert(valuetypes[primary] == TYPE_FONT);
+    assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE);
+    assert(conf_key_info[primary].value_type == CONF_TYPE_FONT);
     entry->key.primary = primary;
     entry->value.u.fontval = fontspec_copy(value);
     conf_insert(conf, entry);
@@ -508,28 +571,33 @@ void conf_serialise(BinarySink *bs, Conf *conf)
     for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) {
         put_uint32(bs, entry->key.primary);
 
-        switch (subkeytypes[entry->key.primary]) {
-          case TYPE_INT:
+        switch (conf_key_info[entry->key.primary].subkey_type) {
+          case CONF_TYPE_INT:
             put_uint32(bs, entry->key.secondary.i);
             break;
-          case TYPE_STR:
+          case CONF_TYPE_STR:
             put_asciz(bs, entry->key.secondary.s);
             break;
         }
-        switch (valuetypes[entry->key.primary]) {
-          case TYPE_BOOL:
+        switch (conf_key_info[entry->key.primary].value_type) {
+          case CONF_TYPE_BOOL:
             put_bool(bs, entry->value.u.boolval);
             break;
-          case TYPE_INT:
+          case CONF_TYPE_INT:
             put_uint32(bs, entry->value.u.intval);
             break;
-          case TYPE_STR:
-            put_asciz(bs, entry->value.u.stringval);
+          case CONF_TYPE_STR:
+          case CONF_TYPE_UTF8:
+            put_asciz(bs, entry->value.u.stringval.str);
+            break;
+          case CONF_TYPE_STR_AMBI:
+            put_asciz(bs, entry->value.u.stringval.str);
+            put_bool(bs, entry->value.u.stringval.utf8);
             break;
-          case TYPE_FILENAME:
+          case CONF_TYPE_FILENAME:
             filename_serialise(bs, entry->value.u.fileval);
             break;
-          case TYPE_FONT:
+          case CONF_TYPE_FONT:
             fontspec_serialise(bs, entry->value.u.fontval);
             break;
         }
@@ -556,29 +624,38 @@ bool conf_deserialise(Conf *conf, BinarySource *src)
         entry = snew(struct conf_entry);
         entry->key.primary = primary;
 
-        switch (subkeytypes[entry->key.primary]) {
-          case TYPE_INT:
+        switch (conf_key_info[entry->key.primary].subkey_type) {
+          case CONF_TYPE_INT:
             entry->key.secondary.i = toint(get_uint32(src));
             break;
-          case TYPE_STR:
+          case CONF_TYPE_STR:
             entry->key.secondary.s = dupstr(get_asciz(src));
             break;
         }
 
-        switch (valuetypes[entry->key.primary]) {
-          case TYPE_BOOL:
+        switch (conf_key_info[entry->key.primary].value_type) {
+          case CONF_TYPE_BOOL:
             entry->value.u.boolval = get_bool(src);
             break;
-          case TYPE_INT:
+          case CONF_TYPE_INT:
             entry->value.u.intval = toint(get_uint32(src));
             break;
-          case TYPE_STR:
-            entry->value.u.stringval = dupstr(get_asciz(src));
+          case CONF_TYPE_STR:
+            entry->value.u.stringval.str = dupstr(get_asciz(src));
+            entry->value.u.stringval.utf8 = false;
+            break;
+          case CONF_TYPE_UTF8:
+            entry->value.u.stringval.str = dupstr(get_asciz(src));
+            entry->value.u.stringval.utf8 = true;
+            break;
+          case CONF_TYPE_STR_AMBI:
+            entry->value.u.stringval.str = dupstr(get_asciz(src));
+            entry->value.u.stringval.utf8 = get_bool(src);
             break;
-          case TYPE_FILENAME:
+          case CONF_TYPE_FILENAME:
             entry->value.u.fileval = filename_deserialise(src);
             break;
-          case TYPE_FONT:
+          case CONF_TYPE_FONT:
             entry->value.u.fontval = fontspec_deserialise(src);
             break;
         }

+ 53 - 0
source/putty/utils/conf_data.c

@@ -0,0 +1,53 @@
+#include "putty.h"
+
+#define CONF_ENUM(name, ...)                                            \
+    static const ConfSaveEnumValue conf_enum_values_##name[] = {        \
+        __VA_ARGS__                                                     \
+    }; const ConfSaveEnumType conf_enum_##name = {                      \
+        .values = conf_enum_values_##name,                              \
+        .nvalues = lenof(conf_enum_values_##name),                      \
+    };
+
+#define VALUE(eval, sval) { eval, sval, false }
+#define VALUE_OBSOLETE(eval, sval) { eval, sval, true }
+
+#include "conf-enums.h"
+
+bool conf_enum_map_to_storage(const ConfSaveEnumType *etype,
+                              int confval, int *storageval_out)
+{
+    for (size_t i = 0; i < etype->nvalues; i++)
+        if (!etype->values[i].obsolete &&
+            etype->values[i].confval == confval) {
+            *storageval_out = etype->values[i].storageval;
+            return true;
+        }
+    return false;
+}
+
+bool conf_enum_map_from_storage(const ConfSaveEnumType *etype,
+                                int storageval, int *confval_out)
+{
+    for (size_t i = 0; i < etype->nvalues; i++)
+        if (etype->values[i].storageval == storageval) {
+            *confval_out = etype->values[i].confval;
+            return true;
+        }
+    return false;
+}
+
+#define CONF_OPTION(id, ...) { __VA_ARGS__ },
+#define VALUE_TYPE(x) .value_type = CONF_TYPE_ ## x
+#define SUBKEY_TYPE(x) .subkey_type = CONF_TYPE_ ## x
+#define DEFAULT_INT(x) .default_value.ival = x
+#define DEFAULT_STR(x) .default_value.sval = x
+#define DEFAULT_BOOL(x) .default_value.bval = x
+#define SAVE_KEYWORD(x) .save_keyword = x
+#define STORAGE_ENUM(x) .storage_enum = &conf_enum_ ## x
+#define SAVE_CUSTOM .save_custom = true
+#define LOAD_CUSTOM .load_custom = true
+#define NOT_SAVED .not_saved = true
+
+const ConfKeyInfo conf_key_info[] = {
+    #include "conf.h"
+};

+ 264 - 0
source/putty/utils/decode_utf8.c

@@ -0,0 +1,264 @@
+/*
+ * Decode a single UTF-8 character.
+ */
+
+#include "putty.h"
+#include "misc.h"
+
+unsigned decode_utf8(BinarySource *src, DecodeUTF8Failure *err)
+{
+    /* Permit user to pass NULL as the err pointer */
+    DecodeUTF8Failure dummy;
+    if (!err) err = &dummy;
+
+    /* If the source has no byte available, this will return 0, which
+     * we'll return immediately and is a reasonable error return anyway */
+    unsigned char c = get_byte(src);
+
+    /* One-byte cases. */
+    if (c < 0x80) {
+        *err = DUTF8_SUCCESS;
+        return c;
+    } else if (c < 0xC0) {
+        *err = DUTF8_SPURIOUS_CONTINUATION;
+        return 0xFFFD;
+    }
+
+    unsigned long wc, min;
+    size_t ncont;
+    if (c < 0xE0) {
+        wc = c & 0x1F; ncont = 1; min = 0x80;
+    } else if (c < 0xF0) {
+        wc = c & 0x0F; ncont = 2; min = 0x800;
+    } else if (c < 0xF8) {
+        wc = c & 0x07; ncont = 3; min = 0x10000;
+    } else if (c < 0xFC) {
+        wc = c & 0x03; ncont = 4; min = 0x200000;
+    } else if (c < 0xFE) {
+        wc = c & 0x01; ncont = 5; min = 0x4000000;
+    } else {
+        *err = DUTF8_ILLEGAL_BYTE; /* FE or FF */
+        return 0xFFFD;
+    }
+
+    while (ncont-- > 0) {
+        if (!get_avail(src)) {
+            *err = DUTF8_E_OUT_OF_DATA;
+            return 0xFFFD;
+        }
+        unsigned char cont = get_byte(src);
+        if (!(0x80 <= cont && cont < 0xC0)) {
+            BinarySource_REWIND_TO(src, src->pos - 1);
+            *err = DUTF8_TRUNCATED_SEQUENCE;
+            return 0xFFFD;
+        }
+
+        wc = (wc << 6) | (cont & 0x3F);
+    }
+
+    if (wc < min) {
+        *err = DUTF8_OVERLONG_ENCODING;
+        return 0xFFFD;
+    }
+    if (0xD800 <= wc && wc < 0xE000) {
+        *err = DUTF8_ENCODED_SURROGATE;
+        return 0xFFFD;
+    }
+    if (wc > 0x10FFFF) {
+        *err = DUTF8_CODE_POINT_TOO_BIG;
+        return 0xFFFD;                 /* outside Unicode range */
+    }
+    *err = DUTF8_SUCCESS;
+    return wc;
+}
+
+const char *const decode_utf8_error_strings[DUTF8_N_FAILURE_CODES] = {
+    #define MSG_ENTRY(sym, string) string,
+    DECODE_UTF8_FAILURE_LIST(MSG_ENTRY)
+    #undef MSG_ENTRY
+};
+
+#ifdef TEST
+
+#include <stdio.h>
+
+void out_of_memory(void)
+{
+    fprintf(stderr, "out of memory!\n");
+    exit(2);
+}
+
+static const char *const decode_utf8_error_syms[DUTF8_N_FAILURE_CODES] = {
+    #define SYM_ENTRY(sym, string) #sym,
+    DECODE_UTF8_FAILURE_LIST(SYM_ENTRY)
+    #undef SYM_ENTRY
+};
+
+bool dotest(const char *file, int line, const char *input, size_t ninput,
+            const unsigned long *chars, size_t nchars)
+{
+    BinarySource src[1];
+    BinarySource_BARE_INIT(src, input, ninput);
+    size_t noutput = 0;
+
+    printf("%s:%d: test start\n", file, line);
+
+    while (get_avail(src)) {
+        size_t before = src->pos;
+        DecodeUTF8Failure err;
+        unsigned long wc = decode_utf8(src, &err);
+
+        printf("%s:%d in+%"SIZEu" out+%"SIZEu":", file, line, before, noutput);
+        while (before < src->pos)
+            printf(" %02x", (unsigned)(unsigned char)(input[before++]));
+        printf(" -> U-%08lx %s\n", wc, decode_utf8_error_syms[err]);
+
+        if (noutput >= nchars) {
+            printf("%s:%d: FAIL: expected no further output\n", file, line);
+            return false;
+        }
+
+        if (chars[noutput] != wc) {
+            printf("%s:%d: FAIL: expected U-%08lx\n",
+                   file, line, chars[noutput]);
+            return false;
+        }
+
+        noutput++;
+
+        DecodeUTF8Failure expected_err;
+        if (wc == 0xFFFD) {
+            /* In the 'chars' array, any occurrence of 0xFFFD is followed
+             * by the expected error code */
+            assert(noutput < nchars && "bad test data");
+            expected_err = chars[noutput++];
+        } else {
+            /* Expect success status to go with any non-FFFD character */
+            expected_err = DUTF8_SUCCESS;
+        }
+        if (err != expected_err) {
+            printf("%s:%d: FAIL: expected %s\n", file, line,
+                   decode_utf8_error_syms[expected_err]);
+            return false;
+        }
+    }
+
+    if (noutput < nchars) {
+        printf("%s:%d: FAIL: expected further output\n", file, line);
+        return false;
+    }
+
+    printf("%s:%d: pass\n", file, line);
+    return true;
+}
+
+#define DOTEST(input, ...) do {                                         \
+        static const unsigned long chars[] = { __VA_ARGS__ };           \
+        ntest++;                                                        \
+        if (dotest(__FILE__, __LINE__, input, sizeof(input)-1,          \
+                   chars, lenof(chars)))                                \
+            npass++;                                                    \
+    } while (0)
+
+int main(void)
+{
+    int ntest = 0, npass = 0;
+
+    DOTEST("\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5",
+           0x03BA, 0x1F79, 0x03C3, 0x03BC, 0x03B5);
+
+    /* First sequence of each length */
+    DOTEST("\x00", 0x0000);
+    DOTEST("\xC2\x80", 0x0080);
+    DOTEST("\xE0\xA0\x80", 0x0800);
+    DOTEST("\xF0\x90\x80\x80", 0x00010000);
+    DOTEST("\xF8\x88\x80\x80\x80",
+           0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x00200000 */
+    DOTEST("\xFC\x84\x80\x80\x80\x80",
+           0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x04000000 */
+
+    /* Last sequence of each length */
+    DOTEST("\x7F", 0x007F);
+    DOTEST("\xDF\xBF", 0x07FF);
+    DOTEST("\xEF\xBF\xBF", 0xFFFF);
+    DOTEST("\xF7\xBF\xBF\xBF",
+           0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x001FFFFF */
+    DOTEST("\xFB\xBF\xBF\xBF\xBF",
+           0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x03FFFFFF */
+    DOTEST("\xFD\xBF\xBF\xBF\xBF\xBF",
+           0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x7FFFFFFF */
+
+    /* Endpoints of the surrogate range */
+    DOTEST("\xED\x9F\xBF", 0xD7FF);
+    DOTEST("\xED\xA0\x80", 0xFFFD, DUTF8_ENCODED_SURROGATE); /* 0xD800 */
+    DOTEST("\xED\xBF\xBF", 0xFFFD, DUTF8_ENCODED_SURROGATE); /* 0xDFFF */
+    DOTEST("\xEE\x80\x80", 0xE000);
+
+    /* REPLACEMENT CHARACTER itself */
+    DOTEST("\xEF\xBF\xBD", 0xFFFD, DUTF8_SUCCESS); /* FFFD but no error! */
+
+    /* Endpoints of the legal Unicode range */
+    DOTEST("\xF4\x8F\xBF\xBF", 0x0010FFFF);
+    DOTEST("\xF4\x90\x80\x80", 0xFFFD,
+           DUTF8_CODE_POINT_TOO_BIG); /* would be 0x00110000 */
+
+    /* Spurious continuation bytes, each shown as a separate failure */
+    DOTEST("\x80 \x81\x82 \xBD\xBE\xBF",
+           0xFFFD, DUTF8_SPURIOUS_CONTINUATION,
+           0x0020,
+           0xFFFD, DUTF8_SPURIOUS_CONTINUATION,
+           0xFFFD, DUTF8_SPURIOUS_CONTINUATION,
+           0x0020,
+           0xFFFD, DUTF8_SPURIOUS_CONTINUATION,
+           0xFFFD, DUTF8_SPURIOUS_CONTINUATION,
+           0xFFFD, DUTF8_SPURIOUS_CONTINUATION);
+
+    /* Truncated sequences, each shown as just one failure. The last
+     * one gets a different error code because the sequence is
+     * interrupted by the end of the string instead of another
+     * character, so that if the string were a prefix of a longer
+     * chunk of data then that would not _necessarily_ indicate an
+     * error */
+    DOTEST("\xC2\xE0\xA0\xF0\x90\x80\xF8\x88\x80\x80\xFC\x84\x80\x80\x80",
+           0xFFFD, DUTF8_TRUNCATED_SEQUENCE,
+           0xFFFD, DUTF8_TRUNCATED_SEQUENCE,
+           0xFFFD, DUTF8_TRUNCATED_SEQUENCE,
+           0xFFFD, DUTF8_TRUNCATED_SEQUENCE,
+           0xFFFD, DUTF8_E_OUT_OF_DATA);
+    DOTEST("\xC2 \xE0\xA0 \xF0\x90\x80 \xF8\x88\x80\x80 \xFC\x84\x80\x80\x80",
+           0xFFFD, DUTF8_TRUNCATED_SEQUENCE,
+           0x0020,
+           0xFFFD, DUTF8_TRUNCATED_SEQUENCE,
+           0x0020,
+           0xFFFD, DUTF8_TRUNCATED_SEQUENCE,
+           0x0020,
+           0xFFFD, DUTF8_TRUNCATED_SEQUENCE,
+           0x0020,
+           0xFFFD, DUTF8_E_OUT_OF_DATA);
+
+    /* Illegal bytes */
+    DOTEST("\xFE\xFF", 0xFFFD, DUTF8_ILLEGAL_BYTE, 0xFFFD, DUTF8_ILLEGAL_BYTE);
+
+    /* Overlong sequences */
+    DOTEST("\xC1\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+    DOTEST("\xE0\x9F\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+    DOTEST("\xF0\x8F\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+    DOTEST("\xF8\x87\xBF\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+    DOTEST("\xFC\x83\xBF\xBF\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+
+    DOTEST("\xC0\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+    DOTEST("\xE0\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+    DOTEST("\xF0\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+    DOTEST("\xF8\x80\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+    DOTEST("\xFC\x80\x80\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING);
+
+    printf("%d tests %d passed", ntest, npass);
+    if (npass < ntest) {
+        printf(" %d FAILED\n", ntest-npass);
+        return 1;
+    } else {
+        printf("\n");
+        return 0;
+    }
+}
+#endif

+ 21 - 0
source/putty/utils/decode_utf8_to_wchar.c

@@ -0,0 +1,21 @@
+/*
+ * Decode a single UTF-8 character to the platform's local wchar_t.
+ */
+
+#include "putty.h"
+#include "misc.h"
+
+size_t decode_utf8_to_wchar(BinarySource *src, wchar_t *out,
+                            DecodeUTF8Failure *err)
+{
+    size_t outlen = 0;
+    unsigned wc = decode_utf8(src, err);
+    if (sizeof(wchar_t) > 2 || wc < 0x10000) {
+        out[outlen++] = wc;
+    } else {
+        unsigned wcoff = wc - 0x10000;
+        out[outlen++] = 0xD800 | (0x3FF & (wcoff >> 10));
+        out[outlen++] = 0xDC00 | (0x3FF & wcoff);
+    }
+    return outlen;
+}

+ 35 - 0
source/putty/utils/decode_utf8_to_wide_string.c

@@ -0,0 +1,35 @@
+/*
+ * Decode a string of UTF-8 to a wchar_t string.
+ */
+
+#include "misc.h"
+
+wchar_t *decode_utf8_to_wide_string(const char *s)
+{
+    wchar_t *ws = NULL;
+    size_t wlen = 0, wsize = 0;
+
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, ptrlen_from_asciz(s));
+
+    while (get_avail(src) > 0) {
+        /*
+         * decode_utf8_to_wchar might emit up to 2 wchar_t if wchar_t
+         * is 16 bits (because of UTF-16 surrogates), but will emit at
+         * most one if wchar_t is 32-bit
+         */
+        sgrowarrayn(ws, wsize, wlen, 1 + (sizeof(wchar_t) < 4));
+
+        /* We ignore 'err': if it is set, then the character decode
+         * function will have emitted U+FFFD REPLACEMENT CHARACTER,
+         * which is what we'd have done in response anyway. */
+        DecodeUTF8Failure err;
+        wlen += decode_utf8_to_wchar(src, ws + wlen, &err);
+    }
+
+    /* Reallocate to the final size and append the trailing NUL */
+    ws = sresize(ws, wlen + 1, wchar_t);
+    ws[wlen] = L'\0';
+
+    return ws;
+}

+ 16 - 15
source/putty/utils/dup_mb_to_wc.c

@@ -2,28 +2,29 @@
  * dup_mb_to_wc: memory-allocating wrapper on mb_to_wc.
  *
  * Also dup_mb_to_wc_c: same but you already know the length of the
- * string.
+ * string, and you get told the length of the returned wide string.
+ * (But it's still NUL-terminated, for convenience.)
  */
 
 #include "putty.h"
 #include "misc.h"
 
-wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len)
+wchar_t *dup_mb_to_wc_c(int codepage, const char *string,
+                        size_t inlen, size_t *outlen_p)
 {
-    int mult;
-    for (mult = 1 ;; mult++) {
-        wchar_t *ret = snewn(mult*len + 2, wchar_t);
-        int outlen;
-        outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1);
-        if (outlen < mult*len+1) {
-            ret[outlen] = L'\0';
-            return ret;
-        }
-        sfree(ret);
-    }
+    strbuf *sb = strbuf_new();
+    put_mb_to_wc(sb, codepage, string, inlen);
+    if (outlen_p)
+        *outlen_p = sb->len / sizeof(wchar_t);
+
+    /* Append a trailing L'\0'. For this we only need to write one
+     * byte _fewer_ than sizeof(wchar_t), because strbuf will append a
+     * byte '\0' for us. */
+    put_padding(sb, sizeof(wchar_t) - 1, 0);
+    return (wchar_t *)strbuf_to_str(sb);
 }
 
-wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string)
+wchar_t *dup_mb_to_wc(int codepage, const char *string)
 {
-    return dup_mb_to_wc_c(codepage, flags, string, strlen(string));
+    return dup_mb_to_wc_c(codepage, string, strlen(string), NULL);
 }

+ 28 - 0
source/putty/utils/dup_wc_to_mb.c

@@ -0,0 +1,28 @@
+/*
+ * dup_wc_to_mb: memory-allocating wrapper on wc_to_mb.
+ *
+ * Also dup_wc_to_mb_c: same but you already know the length of the
+ * wide string, and you get told the length of the returned string.
+ * (But it's still NUL-terminated, for convenience.).
+ */
+
+#include <wchar.h>
+
+#include "putty.h"
+#include "misc.h"
+
+char *dup_wc_to_mb_c(int codepage, const wchar_t *string,
+                     size_t inlen, const char *defchr, size_t *outlen_p)
+{
+    strbuf *sb = strbuf_new();
+    put_wc_to_mb(sb, codepage, string, inlen, defchr);
+    if (outlen_p)
+        *outlen_p = sb->len;
+    return strbuf_to_str(sb);
+}
+
+char *dup_wc_to_mb(int codepage, const wchar_t *string,
+                   const char *defchr)
+{
+    return dup_wc_to_mb_c(codepage, string, wcslen(string), defchr, NULL);
+}

+ 19 - 0
source/putty/utils/dupwcs.c

@@ -0,0 +1,19 @@
+/*
+ * Allocate a duplicate of a NUL-terminated wchar_t string.
+ */
+
+#include <wchar.h>
+
+#include "defs.h"
+#include "misc.h"
+
+wchar_t *dupwcs(const wchar_t *s)
+{
+    wchar_t *p = NULL;
+    if (s) {
+        int len = wcslen(s);
+        p = snewn(len + 1, wchar_t);
+        wcscpy(p, s);
+    }
+    return p;
+}

+ 26 - 0
source/putty/utils/encode_utf8.c

@@ -0,0 +1,26 @@
+/*
+ * Encode a single code point as UTF-8.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+void BinarySink_put_utf8_char(BinarySink *output, unsigned ch)
+{
+    if (ch < 0x80) {
+        put_byte(output, ch);
+    } else if (ch < 0x800) {
+        put_byte(output, 0xC0 | (ch >> 6));
+        put_byte(output, 0x80 | (ch & 0x3F));
+    } else if (ch < 0x10000) {
+        put_byte(output, 0xE0 | (ch >> 12));
+        put_byte(output, 0x80 | ((ch >> 6) & 0x3F));
+        put_byte(output, 0x80 | (ch & 0x3F));
+    } else {
+        assert(ch <= 0x10FFFF);
+        put_byte(output, 0xF0 | (ch >> 18));
+        put_byte(output, 0x80 | ((ch >> 12) & 0x3F));
+        put_byte(output, 0x80 | ((ch >> 6) & 0x3F));
+        put_byte(output, 0x80 | (ch & 0x3F));
+    }
+}

+ 23 - 0
source/putty/utils/encode_wide_string_as_utf8.c

@@ -0,0 +1,23 @@
+/*
+ * Encode a string of wchar_t as UTF-8.
+ */
+
+#include "putty.h"
+#include "misc.h"
+
+char *encode_wide_string_as_utf8(const wchar_t *ws)
+{
+    strbuf *sb = strbuf_new();
+    while (*ws) {
+        unsigned long ch = *ws++;
+        if (sizeof(wchar_t) == 2 && IS_HIGH_SURROGATE(ch) &&
+            IS_LOW_SURROGATE(*ws)) {
+            ch = FROM_SURROGATES(ch, *ws);
+            ws++;
+        } else if (IS_SURROGATE(ch)) {
+            ch = 0xfffd; /* illegal UTF-16 -> REPLACEMENT CHARACTER */
+        }
+        put_utf8_char(sb, ch);
+    }
+    return strbuf_to_str(sb);
+}

+ 3 - 3
source/putty/utils/log_proxy_stderr.c

@@ -15,7 +15,7 @@ void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix)
     psb->prefix = prefix;
 }
 
-void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
+void log_proxy_stderr(Plug *plug, Socket *sock, ProxyStderrBuf *psb,
                       const void *vdata, size_t len)
 {
     const char *data = (const char *)vdata;
@@ -65,7 +65,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
                 endpos--;
             char *msg = dupprintf(
                 "%s: %.*s", psb->prefix, (int)(endpos - pos), psb->buf + pos);
-            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
+            plug_log(plug, sock, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             sfree(msg);
 
             pos = nlpos - psb->buf + 1;
@@ -81,7 +81,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
             char *msg = dupprintf(
                 "%s (partial line): %.*s", psb->prefix, (int)psb->size,
                 psb->buf);
-            plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
+            plug_log(plug, sock, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
             sfree(msg);
 
             pos = psb->size = 0;

+ 32 - 0
source/putty/utils/logeventf.c

@@ -0,0 +1,32 @@
+/*
+ * Helpful wrapper functions around the raw logevent().
+ *
+ * This source file lives in 'utils' because it's conceptually a
+ * convenience utility rather than core functionality. But it can't
+ * live in the utils _library_, because then it might refer to
+ * logevent() in an earlier library after Unix ld had already finished
+ * searching that library, and cause a link failure. So it must live
+ * alongside logging.c.
+ */
+
+#include "putty.h"
+
+void logevent_and_free(LogContext *ctx, char *event)
+{
+    logevent(ctx, event);
+    sfree(event);
+}
+
+void logeventvf(LogContext *ctx, const char *fmt, va_list ap)
+{
+    logevent_and_free(ctx, dupvprintf(fmt, ap));
+}
+
+void logeventf(LogContext *ctx, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    logeventvf(ctx, fmt, ap);
+    va_end(ap);
+}

+ 20 - 0
source/putty/utils/marshal.c

@@ -336,3 +336,23 @@ void bufchain_sink_init(bufchain_sink *sink, bufchain *ch)
     sink->ch = ch;
     BinarySink_INIT(sink, bufchain_sink_write);
 }
+
+static void buffer_sink_write(BinarySink *bs, const void *data, size_t len)
+{
+    buffer_sink *sink = BinarySink_DOWNCAST(bs, buffer_sink);
+    if (len > sink->space) {
+        len = sink->space;
+        sink->overflowed = true;
+    }
+    memcpy(sink->out, data, len);
+    sink->space -= len;
+    sink->out += len;
+}
+
+void buffer_sink_init(buffer_sink *sink, void *buffer, size_t len)
+{
+    sink->out = buffer;
+    sink->space = len;
+    sink->overflowed = false;
+    BinarySink_INIT(sink, buffer_sink_write);
+}

+ 1 - 0
source/putty/utils/prompts.c

@@ -18,6 +18,7 @@ prompts_t *new_prompts(void)
     p->callback = NULL;
     p->callback_ctx = NULL;
     p->ldisc_ptr_to_us = NULL;
+    p->utf8 = false;
     return p;
 }
 

+ 6 - 6
source/putty/utils/sk_free_peer_info.c

@@ -1,14 +1,14 @@
 /*
- * Free a SocketPeerInfo, and everything that dangles off it.
+ * Free a SocketEndpointInfo, and everything that dangles off it.
  */
 
 #include "putty.h"
 
-void sk_free_peer_info(SocketPeerInfo *pi)
+void sk_free_endpoint_info(SocketEndpointInfo *ei)
 {
-    if (pi) {
-        sfree((char *)pi->addr_text);
-        sfree((char *)pi->log_text);
-        sfree(pi);
+    if (ei) {
+        sfree((char *)ei->addr_text);
+        sfree((char *)ei->log_text);
+        sfree(ei);
     }
 }

+ 2 - 9
source/putty/utils/stripctrl.c

@@ -217,9 +217,6 @@ static inline void stripctrl_term_put_wc(
     if (prefix.len)
         put_datapl(scc->bs_out, prefix);
 
-    char outbuf[6];
-    size_t produced;
-
     /*
      * The Terminal implementation encodes 7-bit ASCII characters in
      * UTF-8 mode, and all printing characters in non-UTF-8 (i.e.
@@ -232,14 +229,10 @@ static inline void stripctrl_term_put_wc(
         wc &= 0xFF;
 
     if (in_utf(scc->term)) {
-        produced = encode_utf8(outbuf, wc);
+        put_utf8_char(scc->bs_out, wc);
     } else {
-        outbuf[0] = wc;
-        produced = 1;
+        put_byte(scc->bs_out, wc);
     }
-
-    if (produced > 0)
-        put_data(scc->bs_out, outbuf, produced);
 }
 
 static inline size_t stripctrl_locale_try_consume(

+ 12 - 0
source/putty/utils/tempseat.c

@@ -288,6 +288,17 @@ static void tempseat_connection_fatal(Seat *seat, const char *message)
     unreachable("connection_fatal should never be called on TempSeat");
 }
 
+static void tempseat_nonfatal(Seat *seat, const char *message)
+{
+    /*
+     * Non-fatal errors specific to a Seat should also not occur,
+     * because those will be for things like I/O errors writing the
+     * host key collection, and a backend's not _doing_ that when we
+     * haven't connected it to the host yet.
+     */
+    unreachable("nonfatal should never be called on TempSeat");
+}
+
 static bool tempseat_eof(Seat *seat)
 {
     /*
@@ -327,6 +338,7 @@ static const struct SeatVtable tempseat_vt = {
     .notify_remote_exit = tempseat_notify_remote_exit,
     .notify_remote_disconnect = tempseat_notify_remote_disconnect,
     .connection_fatal = tempseat_connection_fatal,
+    .nonfatal = tempseat_nonfatal,
     .update_specials_menu = tempseat_update_specials_menu,
     .get_ttymode = tempseat_get_ttymode,
     .set_busy_status = tempseat_set_busy_status,

+ 5 - 5
source/putty/utils/tree234.c

@@ -63,11 +63,11 @@ struct node234_Tag {
  */
 tree234 *newtree234(cmpfn234 cmp)
 {
-    tree234 *ret = snew(tree234);
-    LOG(("created tree %p\n", ret));
-    ret->root = NULL;
-    ret->cmp = cmp;
-    return ret;
+    tree234 *t = snew(tree234);
+    LOG(("created tree %p\n", t));
+    t->root = NULL;
+    t->cmp = cmp;
+    return t;
 }
 
 /*

+ 4 - 4
source/putty/version.h

@@ -1,5 +1,5 @@
 /* Generated by automated build script */
-#define RELEASE 0.81
-#define TEXTVER "Release 0.81"
-#define SSHVER "-Release-0.81"
-#define BINARY_VERSION 0,81,0,0
+#define RELEASE 0.82
+#define TEXTVER "Release 0.82"
+#define SSHVER "-Release-0.82"
+#define BINARY_VERSION 0,82,0,0

+ 64 - 72
source/putty/windows/gss.c

@@ -26,7 +26,7 @@
     if (uli.QuadPart != 0) \
         uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \
     (t) = (time_t) uli.QuadPart; \
-} while(0)
+} while (0)
 
 /* Windows code to set up the GSSAPI library list. */
 
@@ -118,9 +118,7 @@ static void add_library_to_never_unload_tree(HMODULE module)
 struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
 {
     HMODULE module;
-    HKEY regkey;
     struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
-    char *path;
     static HMODULE kernel32_module;
     if (!kernel32_module) {
         kernel32_module = load_system32_dll("kernel32.dll");
@@ -137,55 +135,47 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
 
     /* MIT Kerberos GSSAPI implementation */
     module = NULL;
-    if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", &regkey)
-        == ERROR_SUCCESS) {
-        DWORD type, size;
-        LONG ret;
-        char *buffer;
-
-        /* Find out the string length */
-        ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, NULL, &size);
-
-        if (ret == ERROR_SUCCESS && type == REG_SZ) {
-            buffer = snewn(size + 20, char);
-            ret = RegQueryValueEx(regkey, "InstallDir", NULL,
-                                  &type, (LPBYTE)buffer, &size);
-            if (ret == ERROR_SUCCESS && type == REG_SZ) {
-                strcat (buffer, "\\bin");
-                if(p_AddDllDirectory) {
-                    /* Add MIT Kerberos' path to the DLL search path,
-                     * it loads its own DLLs further down the road */
-                    wchar_t *dllPath =
-                        dup_mb_to_wc(DEFAULT_CODEPAGE, 0, buffer);
-                    p_AddDllDirectory(dllPath);
-                    sfree(dllPath);
-                }
-                strcat (buffer, "\\gssapi"MIT_KERB_SUFFIX".dll");
-                module = LoadLibraryEx (buffer, NULL,
-                                        LOAD_LIBRARY_SEARCH_SYSTEM32 |
-                                        LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR |
-                                        LOAD_LIBRARY_SEARCH_USER_DIRS);
-
-                /*
-                 * The MIT Kerberos DLL suffers an internal segfault
-                 * for some reason if you unload and reload one within
-                 * the same process. So, make sure that after we load
-                 * this library, we never free it.
-                 *
-                 * Or rather: after we've loaded it once, if any
-                 * _further_ load returns the same module handle, we
-                 * immediately free it again (to prevent the Windows
-                 * API's internal reference count growing without
-                 * bound). But on the other hand we never free it in
-                 * ssh_gss_cleanup.
-                 */
-                if (library_is_in_never_unload_tree(module))
-                    FreeLibrary(module);
-                add_library_to_never_unload_tree(module);
+    HKEY regkey = open_regkey_ro(HKEY_LOCAL_MACHINE,
+                                 "SOFTWARE\\MIT\\Kerberos");
+    if (regkey) {
+        char *installdir = get_reg_sz(regkey, "InstallDir");
+        if (installdir) {
+            char *bindir = dupcat(installdir, "\\bin");
+            if (p_AddDllDirectory) {
+                /* Add MIT Kerberos' path to the DLL search path,
+                 * it loads its own DLLs further down the road */
+                wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, bindir);
+                p_AddDllDirectory(dllPath);
+                sfree(dllPath);
             }
-            sfree(buffer);
+
+            char *dllfile = dupcat(bindir, "\\gssapi"MIT_KERB_SUFFIX".dll");
+            module = LoadLibraryEx(dllfile, NULL,
+                                   LOAD_LIBRARY_SEARCH_SYSTEM32 |
+                                   LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR |
+                                   LOAD_LIBRARY_SEARCH_USER_DIRS);
+
+            /*
+             * The MIT Kerberos DLL suffers an internal segfault for
+             * some reason if you unload and reload one within the
+             * same process. So, make sure that after we load this
+             * library, we never free it.
+             *
+             * Or rather: after we've loaded it once, if any _further_
+             * load returns the same module handle, we immediately
+             * free it again (to prevent the Windows API's internal
+             * reference count growing without bound). But on the
+             * other hand we never free it in ssh_gss_cleanup.
+             */
+            if (library_is_in_never_unload_tree(module))
+                FreeLibrary(module);
+            add_library_to_never_unload_tree(module);
+
+            sfree(dllfile);
+            sfree(bindir);
+            sfree(installdir);
         }
-        RegCloseKey(regkey);
+        close_regkey(regkey);
     }
     if (module) {
         struct ssh_gss_library *lib =
@@ -242,34 +232,36 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
      * Custom GSSAPI DLL.
      */
     module = NULL;
-    path = conf_get_filename(conf, CONF_ssh_gss_custom)->path;
-    if (*path) {
-        if(p_AddDllDirectory) {
+    Filename *customlib = conf_get_filename(conf, CONF_ssh_gss_custom);
+    if (!filename_is_null(customlib)) {
+        const wchar_t *path = customlib->wpath;
+        if (p_AddDllDirectory) {
+
             /* Add the custom directory as well in case it chainloads
              * some other DLLs (e.g a non-installed MIT Kerberos
              * instance) */
-            int pathlen = strlen(path);
+            int pathlen = wcslen(path);
 
-            while (pathlen > 0 && path[pathlen-1] != ':' &&
-                   path[pathlen-1] != '\\')
+            while (pathlen > 0 && path[pathlen-1] != L':' &&
+                   path[pathlen-1] != L'\\')
                 pathlen--;
 
-            if (pathlen > 0 && path[pathlen-1] != '\\')
+            if (pathlen > 0 && path[pathlen-1] != L'\\')
                 pathlen--;
 
             if (pathlen > 0) {
-                char *dirpath = dupprintf("%.*s", pathlen, path);
-                wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, dirpath);
-                p_AddDllDirectory(dllPath);
-                sfree(dllPath);
+                wchar_t *dirpath = snewn(pathlen + 1, wchar_t);
+                memcpy(dirpath, path, pathlen * sizeof(wchar_t));
+                dirpath[pathlen] = L'\0';
+                p_AddDllDirectory(dirpath);
                 sfree(dirpath);
             }
         }
 
-        module = LoadLibraryEx(path, NULL,
-                               LOAD_LIBRARY_SEARCH_SYSTEM32 |
-                               LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR |
-                               LOAD_LIBRARY_SEARCH_USER_DIRS);
+        module = LoadLibraryExW(path, NULL,
+                                LOAD_LIBRARY_SEARCH_SYSTEM32 |
+                                LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR |
+                                LOAD_LIBRARY_SEARCH_USER_DIRS);
     }
     if (module) {
         struct ssh_gss_library *lib =
@@ -277,7 +269,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
 
         lib->id = 2;
         lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified"
-                                   " library '%s'", path);
+                                   " library '%s'", customlib->cpath);
         lib->handle = (void *)module;
 
 #define BIND_GSS_FN(name) \
@@ -460,8 +452,8 @@ static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib,
     winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx;
     SecBuffer wsend_tok = {send_tok->length,SECBUFFER_TOKEN,send_tok->value};
     SecBuffer wrecv_tok = {recv_tok->length,SECBUFFER_TOKEN,recv_tok->value};
-    SecBufferDesc output_desc={SECBUFFER_VERSION,1,&wsend_tok};
-    SecBufferDesc input_desc ={SECBUFFER_VERSION,1,&wrecv_tok};
+    SecBufferDesc output_desc = {SECBUFFER_VERSION,1,&wsend_tok};
+    SecBufferDesc input_desc  = {SECBUFFER_VERSION,1,&wrecv_tok};
     unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT|
         ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY;
     ULONG ret_flags=0;
@@ -512,7 +504,7 @@ static Ssh_gss_stat ssh_sspi_free_tok(struct ssh_gss_library *lib,
 static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib,
                                           Ssh_gss_ctx *ctx)
 {
-    winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx;
+    winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx;
 
     /* check input */
     if (winctx == NULL) return SSH_GSS_FAILURE;
@@ -532,7 +524,7 @@ static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib,
 static Ssh_gss_stat ssh_sspi_release_name(struct ssh_gss_library *lib,
                                           Ssh_gss_name *srv_name)
 {
-    char *pStr= (char *) *srv_name;
+    char *pStr = (char *) *srv_name;
 
     if (pStr == NULL) return SSH_GSS_FAILURE;
     sfree(pStr);
@@ -596,7 +588,7 @@ static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib,
                                      Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
                                      Ssh_gss_buf *hash)
 {
-    winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx;
+    winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx;
     SecPkgContext_Sizes ContextSizes;
     SecBufferDesc InputBufferDescriptor;
     SecBuffer InputSecurityToken[2];
@@ -643,7 +635,7 @@ static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib,
                                         Ssh_gss_buf *buf,
                                         Ssh_gss_buf *mic)
 {
-    winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx;
+    winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx;
     SecBufferDesc InputBufferDescriptor;
     SecBuffer InputSecurityToken[2];
     ULONG qop;

+ 12 - 7
source/putty/windows/handle-socket.c

@@ -107,7 +107,7 @@ static size_t handle_stderr(
     HandleSocket *hs = (HandleSocket *)handle_get_privdata(h);
 
     if (!err && len > 0)
-        log_proxy_stderr(hs->plug, &hs->psb, data, len);
+        log_proxy_stderr(hs->plug, &hs->sock, &hs->psb, data, len);
 
     return 0;
 }
@@ -296,7 +296,7 @@ static const char *sk_handle_socket_error(Socket *s)
     return hs->error;
 }
 
-static SocketPeerInfo *sk_handle_peer_info(Socket *s)
+static SocketEndpointInfo *sk_handle_endpoint_info(Socket *s, bool peer)
 {
     HandleSocket *hs = container_of(s, HandleSocket, sock);
     ULONG pid;
@@ -304,6 +304,9 @@ static SocketPeerInfo *sk_handle_peer_info(Socket *s)
     DECL_WINDOWS_FUNCTION(static, BOOL, GetNamedPipeClientProcessId,
                           (HANDLE, PULONG));
 
+    if (!peer)
+        return NULL;
+
     if (!kernel32_module) {
         kernel32_module = load_system32_dll("kernel32.dll");
 #if !HAVE_GETNAMEDPIPECLIENTPROCESSID
@@ -326,7 +329,7 @@ static SocketPeerInfo *sk_handle_peer_info(Socket *s)
      */
     if (p_GetNamedPipeClientProcessId &&
         p_GetNamedPipeClientProcessId(hs->send_H, &pid)) {
-        SocketPeerInfo *pi = snew(SocketPeerInfo);
+        SocketEndpointInfo *pi = snew(SocketEndpointInfo);
         pi->addressfamily = ADDRTYPE_LOCAL;
         pi->addr_text = NULL;
         pi->port = -1;
@@ -345,13 +348,14 @@ static const SocketVtable HandleSocket_sockvt = {
     .write_eof = sk_handle_write_eof,
     .set_frozen = sk_handle_set_frozen,
     .socket_error = sk_handle_socket_error,
-    .peer_info = sk_handle_peer_info,
+    .endpoint_info = sk_handle_endpoint_info,
 };
 
 static void sk_handle_connect_success_callback(void *ctx)
 {
     HandleSocket *hs = (HandleSocket *)ctx;
-    plug_log(hs->plug, PLUGLOG_CONNECT_SUCCESS, hs->addr, hs->port, NULL, 0);
+    plug_log(hs->plug, &hs->sock, PLUGLOG_CONNECT_SUCCESS, hs->addr, hs->port,
+             NULL, 0);
 }
 
 Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
@@ -431,7 +435,8 @@ static void sk_handle_deferred_set_frozen(Socket *s, bool is_frozen)
     hs->frozen = is_frozen;
 }
 
-static SocketPeerInfo *sk_handle_deferred_peer_info(Socket *s)
+static SocketEndpointInfo *sk_handle_deferred_endpoint_info(
+    Socket *s, bool peer)
 {
     return NULL;
 }
@@ -444,7 +449,7 @@ static const SocketVtable HandleSocket_deferred_sockvt = {
     .write_eof = sk_handle_deferred_write_eof,
     .set_frozen = sk_handle_deferred_set_frozen,
     .socket_error = sk_handle_socket_error,
-    .peer_info = sk_handle_deferred_peer_info,
+    .endpoint_info = sk_handle_deferred_endpoint_info,
 };
 
 Socket *make_deferred_handle_socket(DeferredSocketOpener *opener,

+ 131 - 124
source/putty/windows/network.c

@@ -209,6 +209,8 @@ DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
                       (SOCKET, struct sockaddr FAR *, int FAR *));
 DECL_WINDOWS_FUNCTION(static, int, getpeername,
                       (SOCKET, struct sockaddr FAR *, int FAR *));
+DECL_WINDOWS_FUNCTION(static, int, getsockname,
+                      (SOCKET, struct sockaddr FAR *, int FAR *));
 DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int));
 DECL_WINDOWS_FUNCTION(static, int, WSAIoctl,
                       (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD,
@@ -332,6 +334,7 @@ void sk_init(void)
     GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket);
     GET_WINDOWS_FUNCTION(winsock_module, accept);
     GET_WINDOWS_FUNCTION(winsock_module, getpeername);
+    GET_WINDOWS_FUNCTION(winsock_module, getsockname);
     GET_WINDOWS_FUNCTION(winsock_module, recv);
     GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl);
 
@@ -547,18 +550,18 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname,
 
 static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name)
 {
-    SockAddr *ret = snew(SockAddr);
-    ret->error = NULL;
-    ret->superfamily = superfamily;
+    SockAddr *addr = snew(SockAddr);
+    addr->error = NULL;
+    addr->superfamily = superfamily;
 #ifndef NO_IPV6
-    ret->ais = NULL;
+    addr->ais = NULL;
 #endif
-    ret->addresses = NULL;
-    ret->naddresses = 0;
-    ret->refcount = 1;
-    strncpy(ret->hostname, name, lenof(ret->hostname));
-    ret->hostname[lenof(ret->hostname)-1] = '\0';
-    return ret;
+    addr->addresses = NULL;
+    addr->naddresses = 0;
+    addr->refcount = 1;
+    strncpy(addr->hostname, name, lenof(addr->hostname));
+    addr->hostname[lenof(addr->hostname)-1] = '\0';
+    return addr;
 }
 
 SockAddr *sk_nonamelookup(const char *host)
@@ -821,7 +824,7 @@ static size_t sk_net_write_oob(Socket *s, const void *data, size_t len);
 static void sk_net_write_eof(Socket *s);
 static void sk_net_set_frozen(Socket *s, bool is_frozen);
 static const char *sk_net_socket_error(Socket *s);
-static SocketPeerInfo *sk_net_peer_info(Socket *s);
+static SocketEndpointInfo *sk_net_endpoint_info(Socket *s, bool peer);
 
 static const SocketVtable NetSocket_sockvt = {
     .plug = sk_net_plug,
@@ -831,54 +834,54 @@ static const SocketVtable NetSocket_sockvt = {
     .write_eof = sk_net_write_eof,
     .set_frozen = sk_net_set_frozen,
     .socket_error = sk_net_socket_error,
-    .peer_info = sk_net_peer_info,
+    .endpoint_info = sk_net_endpoint_info,
 };
 
 static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug)
 {
     DWORD err;
     const char *errstr;
-    NetSocket *ret;
+    NetSocket *s;
 
     /*
      * Create NetSocket structure.
      */
-    ret = snew(NetSocket);
-    ret->sock.vt = &NetSocket_sockvt;
-    ret->error = NULL;
-    ret->plug = plug;
-    bufchain_init(&ret->output_data);
-    ret->writable = true;              /* to start with */
-    ret->sending_oob = 0;
-    ret->outgoingeof = EOF_NO;
-    ret->frozen = true;
-    ret->frozen_readable = false;
-    ret->localhost_only = false;    /* unused, but best init anyway */
-    ret->pending_error = 0;
-    ret->parent = ret->child = NULL;
-    ret->addr = NULL;
-
-    ret->s = (SOCKET)ctx.p;
-
-    if (ret->s == INVALID_SOCKET) {
+    s = snew(NetSocket);
+    s->sock.vt = &NetSocket_sockvt;
+    s->error = NULL;
+    s->plug = plug;
+    bufchain_init(&s->output_data);
+    s->writable = true;              /* to start with */
+    s->sending_oob = 0;
+    s->outgoingeof = EOF_NO;
+    s->frozen = true;
+    s->frozen_readable = false;
+    s->localhost_only = false;    /* unused, but best init anyway */
+    s->pending_error = 0;
+    s->parent = s->child = NULL;
+    s->addr = NULL;
+
+    s->s = (SOCKET)ctx.p;
+
+    if (s->s == INVALID_SOCKET) {
         err = p_WSAGetLastError();
-        ret->error = winsock_error_string(err);
-        return &ret->sock;
+        s->error = winsock_error_string(err);
+        return &s->sock;
     }
 
-    ret->oobinline = false;
+    s->oobinline = false;
 
     /* Set up a select mechanism. This could be an AsyncSelect on a
      * window, or an EventSelect on an event object. */
-    errstr = do_select(ret->s, true);
+    errstr = do_select(s->s, true);
     if (errstr) {
-        ret->error = errstr;
-        return &ret->sock;
+        s->error = errstr;
+        return &s->sock;
     }
 
-    add234(sktree, ret);
+    add234(sktree, s);
 
-    return &ret->sock;
+    return &s->sock;
 }
 
 static DWORD try_connect(NetSocket *sock)
@@ -901,7 +904,7 @@ static DWORD try_connect(NetSocket *sock)
     {
         SockAddr thisaddr = sk_extractaddr_tmp(
             sock->addr, &sock->step);
-        plug_log(sock->plug, PLUGLOG_CONNECT_TRYING,
+        plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_TRYING,
                  &thisaddr, sock->port, NULL, 0);
     }
 
@@ -1062,7 +1065,7 @@ static DWORD try_connect(NetSocket *sock)
          */
         sock->writable = true;
         SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step);
-        plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS,
+        plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_SUCCESS,
                  &thisaddr, sock->port, NULL, 0);
     }
 
@@ -1078,7 +1081,7 @@ static DWORD try_connect(NetSocket *sock)
     if (err) {
         SockAddr thisaddr = sk_extractaddr_tmp(
             sock->addr, &sock->step);
-        plug_log(sock->plug, PLUGLOG_CONNECT_FAILED,
+        plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_FAILED,
                  &thisaddr, sock->port, sock->error, err);
     }
     return err;
@@ -1087,48 +1090,48 @@ static DWORD try_connect(NetSocket *sock)
 Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
                bool nodelay, bool keepalive, Plug *plug)
 {
-    NetSocket *ret;
+    NetSocket *s;
     DWORD err;
 
     /*
      * Create NetSocket structure.
      */
-    ret = snew(NetSocket);
-    ret->sock.vt = &NetSocket_sockvt;
-    ret->error = NULL;
-    ret->plug = plug;
-    bufchain_init(&ret->output_data);
-    ret->connected = false;            /* to start with */
-    ret->writable = false;             /* to start with */
-    ret->sending_oob = 0;
-    ret->outgoingeof = EOF_NO;
-    ret->frozen = false;
-    ret->frozen_readable = false;
-    ret->localhost_only = false;    /* unused, but best init anyway */
-    ret->pending_error = 0;
-    ret->parent = ret->child = NULL;
-    ret->oobinline = oobinline;
-    ret->nodelay = nodelay;
-    ret->keepalive = keepalive;
-    ret->privport = privport;
-    ret->port = port;
-    ret->addr = addr;
-    START_STEP(ret->addr, ret->step);
-    ret->s = INVALID_SOCKET;
+    s = snew(NetSocket);
+    s->sock.vt = &NetSocket_sockvt;
+    s->error = NULL;
+    s->plug = plug;
+    bufchain_init(&s->output_data);
+    s->connected = false;            /* to start with */
+    s->writable = false;             /* to start with */
+    s->sending_oob = 0;
+    s->outgoingeof = EOF_NO;
+    s->frozen = false;
+    s->frozen_readable = false;
+    s->localhost_only = false;    /* unused, but best init anyway */
+    s->pending_error = 0;
+    s->parent = s->child = NULL;
+    s->oobinline = oobinline;
+    s->nodelay = nodelay;
+    s->keepalive = keepalive;
+    s->privport = privport;
+    s->port = port;
+    s->addr = addr;
+    START_STEP(s->addr, s->step);
+    s->s = INVALID_SOCKET;
 
     err = 0;
     do {
-        err = try_connect(ret);
-    } while (err && sk_nextaddr(ret->addr, &ret->step));
+        err = try_connect(s);
+    } while (err && sk_nextaddr(s->addr, &s->step));
 
-    return &ret->sock;
+    return &s->sock;
 }
 
 static Socket *sk_newlistener_internal(
     const char *srcaddr, int port, Plug *plug,
     bool local_host_only, int orig_address_family)
 {
-    SOCKET s;
+    SOCKET sk;
     SOCKADDR_IN a;
 #ifndef NO_IPV6
     SOCKADDR_IN6 a6;
@@ -1141,7 +1144,7 @@ static Socket *sk_newlistener_internal(
 
     DWORD err;
     const char *errstr;
-    NetSocket *ret;
+    NetSocket *s;
     int retcode;
 
     int address_family = orig_address_family;
@@ -1149,20 +1152,20 @@ static Socket *sk_newlistener_internal(
     /*
      * Create NetSocket structure.
      */
-    ret = snew(NetSocket);
-    ret->sock.vt = &NetSocket_sockvt;
-    ret->error = NULL;
-    ret->plug = plug;
-    bufchain_init(&ret->output_data);
-    ret->writable = false;             /* to start with */
-    ret->sending_oob = 0;
-    ret->outgoingeof = EOF_NO;
-    ret->frozen = false;
-    ret->frozen_readable = false;
-    ret->localhost_only = local_host_only;
-    ret->pending_error = 0;
-    ret->parent = ret->child = NULL;
-    ret->addr = NULL;
+    s = snew(NetSocket);
+    s->sock.vt = &NetSocket_sockvt;
+    s->error = NULL;
+    s->plug = plug;
+    bufchain_init(&s->output_data);
+    s->writable = false;             /* to start with */
+    s->sending_oob = 0;
+    s->outgoingeof = EOF_NO;
+    s->frozen = false;
+    s->frozen_readable = false;
+    s->localhost_only = local_host_only;
+    s->pending_error = 0;
+    s->parent = s->child = NULL;
+    s->addr = NULL;
 
     /*
      * Our default, if passed the `don't care' value
@@ -1176,25 +1179,25 @@ static Socket *sk_newlistener_internal(
     /*
      * Open socket.
      */
-    s = p_socket(address_family, SOCK_STREAM, 0);
-    ret->s = s;
+    sk = p_socket(address_family, SOCK_STREAM, 0);
+    s->s = sk;
 
-    if (s == INVALID_SOCKET) {
+    if (sk == INVALID_SOCKET) {
         err = p_WSAGetLastError();
-        ret->error = winsock_error_string(err);
-        return &ret->sock;
+        s->error = winsock_error_string(err);
+        return &s->sock;
     }
 
-    SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
+    SetHandleInformation((HANDLE)sk, HANDLE_FLAG_INHERIT, 0);
 
-    ret->oobinline = false;
+    s->oobinline = false;
 
 #if HAVE_AFUNIX_H
     if (address_family != AF_UNIX)
 #endif
     {
         BOOL on = true;
-        p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+        p_setsockopt(sk, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
                      (const char *)&on, sizeof(on));
     }
 
@@ -1244,7 +1247,7 @@ static Socket *sk_newlistener_internal(
             a.sin_addr.s_addr = p_inet_addr(srcaddr);
             if (a.sin_addr.s_addr != INADDR_NONE) {
                 /* Override localhost_only with specified listen addr. */
-                ret->localhost_only = ipv4_is_loopback(a.sin_addr);
+                s->localhost_only = ipv4_is_loopback(a.sin_addr);
                 got_addr = true;
             }
         }
@@ -1277,7 +1280,7 @@ static Socket *sk_newlistener_internal(
         unreachable("bad address family in sk_newlistener_internal");
     }
 
-    retcode = p_bind(s, bindaddr, bindsize);
+    retcode = p_bind(sk, bindaddr, bindsize);
     if (retcode != SOCKET_ERROR) {
         err = 0;
     } else {
@@ -1285,28 +1288,28 @@ static Socket *sk_newlistener_internal(
     }
 
     if (err) {
-        p_closesocket(s);
-        ret->error = winsock_error_string(err);
-        return &ret->sock;
+        p_closesocket(sk);
+        s->error = winsock_error_string(err);
+        return &s->sock;
     }
 
 
-    if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) {
-        p_closesocket(s);
-        ret->error = winsock_error_string(p_WSAGetLastError());
-        return &ret->sock;
+    if (p_listen(sk, SOMAXCONN) == SOCKET_ERROR) {
+        p_closesocket(sk);
+        s->error = winsock_error_string(p_WSAGetLastError());
+        return &s->sock;
     }
 
     /* Set up a select mechanism. This could be an AsyncSelect on a
      * window, or an EventSelect on an event object. */
-    errstr = do_select(s, true);
+    errstr = do_select(sk, true);
     if (errstr) {
-        p_closesocket(s);
-        ret->error = errstr;
-        return &ret->sock;
+        p_closesocket(sk);
+        s->error = errstr;
+        return &s->sock;
     }
 
-    add234(sktree, ret);
+    add234(sktree, s);
 
 #ifndef NO_IPV6
     /*
@@ -1320,8 +1323,8 @@ static Socket *sk_newlistener_internal(
         if (other) {
             NetSocket *ns = container_of(other, NetSocket, sock);
             if (!ns->error) {
-                ns->parent = ret;
-                ret->child = ns;
+                ns->parent = s;
+                s->child = ns;
             } else {
                 sfree(ns);
             }
@@ -1329,7 +1332,7 @@ static Socket *sk_newlistener_internal(
     }
 #endif
 
-    return &ret->sock;
+    return &s->sock;
 }
 
 Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
@@ -1575,8 +1578,8 @@ void select_result(WPARAM wParam, LPARAM lParam)
         if (s->addr) {
             SockAddr thisaddr = sk_extractaddr_tmp(
                 s->addr, &s->step);
-            plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port,
-                     winsock_error_string(err), err);
+            plug_log(s->plug, &s->sock, PLUGLOG_CONNECT_FAILED, &thisaddr,
+                     s->port, winsock_error_string(err), err);
             while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
                 err = try_connect(s);
             }
@@ -1601,7 +1604,7 @@ void select_result(WPARAM wParam, LPARAM lParam)
         if (s->addr) {
             SockAddr thisaddr = sk_extractaddr_tmp(
                 s->addr, &s->step);
-            plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS,
+            plug_log(s->plug, &s->sock, PLUGLOG_CONNECT_SUCCESS,
                      &thisaddr, s->port, NULL, 0);
 
             sk_addr_free(s->addr);
@@ -1707,8 +1710,7 @@ void select_result(WPARAM wParam, LPARAM lParam)
         memset(&isa, 0, sizeof(isa));
         err = 0;
         t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
-        if (t == INVALID_SOCKET)
-        {
+        if (t == INVALID_SOCKET) {
             err = p_WSAGetLastError();
             if (err == WSATRY_AGAIN)
                 break;
@@ -1748,7 +1750,7 @@ static const char *sk_net_socket_error(Socket *sock)
     return s->error;
 }
 
-static SocketPeerInfo *sk_net_peer_info(Socket *sock)
+static SocketEndpointInfo *sk_net_endpoint_info(Socket *sock, bool peer)
 {
     NetSocket *s = container_of(sock, NetSocket, sock);
 #ifdef NO_IPV6
@@ -1758,12 +1760,17 @@ static SocketPeerInfo *sk_net_peer_info(Socket *sock)
     char buf[INET6_ADDRSTRLEN];
 #endif
     int addrlen = sizeof(addr);
-    SocketPeerInfo *pi;
+    SocketEndpointInfo *pi;
 
-    if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0)
-        return NULL;
+    {
+        int retd = (peer ?
+                    p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) :
+                    p_getsockname(s->s, (struct sockaddr *)&addr, &addrlen));
+        if (retd < 0)
+            return NULL;
+    }
 
-    pi = snew(SocketPeerInfo);
+    pi = snew(SocketEndpointInfo);
     pi->addressfamily = ADDRTYPE_UNSPEC;
     pi->addr_text = NULL;
     pi->port = -1;
@@ -1871,9 +1878,9 @@ char *get_hostname(void)
 
 SockAddr *platform_get_x11_unix_address(const char *display, int displaynum)
 {
-    SockAddr *ret = snew(SockAddr);
-    memset(ret, 0, sizeof(SockAddr));
-    ret->error = "unix sockets for X11 not supported on this platform";
-    ret->refcount = 1;
-    return ret;
+    SockAddr *addr = snew(SockAddr);
+    memset(addr, 0, sizeof(SockAddr));
+    addr->error = "unix sockets for X11 not supported on this platform";
+    addr->refcount = 1;
+    return addr;
 }

+ 96 - 22
source/putty/windows/platform.h

@@ -48,14 +48,27 @@
 #define AGENT_COPYDATA_ID 0x804e50ba
 
 struct Filename {
-    char *path;
+    /*
+     * A Windows Filename stores a path in three formats:
+     *
+     *  - wchar_t (in Windows UTF-16 encoding). The best format to use
+     *    for actual file API functions, because all legal Windows
+     *    file names are representable.
+     *
+     *  - char, in the system default codepage. A fallback to use if
+     *    necessary, e.g. in diagnostics written to somewhere that is
+     *    unavoidably encoded _in_ the system codepage.
+     *
+     *  - char, in UTF-8. An equally general representation to wpath,
+     *    but suitable for keeping in char-typed strings.
+     */
+    wchar_t *wpath;
+    char *cpath, *utf8path;
 };
-static inline FILE *f_open(const Filename *filename, const char *mode,
-                           bool isprivate)
-{
-    return fopen(filename->path, mode);
-}
+Filename *filename_from_wstr(const wchar_t *str);
+FILE *f_open(const Filename *filename, const char *mode, bool isprivate);
 
+#ifndef SUPERSEDE_FONTSPEC_FOR_TESTING
 struct FontSpec {
     char *name;
     bool isbold;
@@ -64,6 +77,7 @@ struct FontSpec {
 };
 struct FontSpec *fontspec_new(
     const char *name, bool bold, int height, int charset);
+#endif
 
 #ifndef CLEARTYPE_QUALITY
 #define CLEARTYPE_QUALITY 5
@@ -194,6 +208,9 @@ void centre_window(HWND hwnd);
 
 #define DEFAULT_CODEPAGE CP_ACP
 #define USES_VTLINE_HACK
+#define CP_UTF8 65001
+#define CP_437 437                     /* used for test suites */
+#define CP_ISO8859_1 0x10001           /* used for test suites */
 
 #ifndef NO_GSSAPI
 /*
@@ -249,7 +266,7 @@ const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat);
  * which takes the data string in the system code page instead of
  * Unicode.
  */
-void write_aclip(int clipboard, char *, int, bool);
+void write_aclip(HWND hwnd, int clipboard, char *, int);
 
 #define WM_NETEVENT  (WM_APP + 5)
 
@@ -283,12 +300,21 @@ void write_aclip(int clipboard, char *, int, bool);
  * these strings are of exactly the type needed to go in
  * `lpstrFilter' in an OPENFILENAME structure.
  */
-#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \
-                              "All Files (*.*)\0*\0\0\0")
-#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \
+typedef const wchar_t *FILESELECT_FILTER_TYPE;
+#define FILTER_KEY_FILES (L"PuTTY Private Key Files (*.ppk)\0*.ppk\0" \
+                          L"All Files (*.*)\0*\0\0\0")
+#define FILTER_WAVE_FILES (L"Wave Files (*.wav)\0*.WAV\0"       \
+                           L"All Files (*.*)\0*\0\0\0")
+#define FILTER_DYNLIB_FILES (L"Dynamic Library Files (*.dll)\0*.dll\0" \
+                             L"All Files (*.*)\0*\0\0\0")
+
+/* char-based versions of the above, for outlying uses of file selectors. */
+#define FILTER_KEY_FILES_C ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \
+                            "All Files (*.*)\0*\0\0\0")
+#define FILTER_WAVE_FILES_C ("Wave Files (*.wav)\0*.WAV\0" \
+                             "All Files (*.*)\0*\0\0\0")
+#define FILTER_DYNLIB_FILES_C ("Dynamic Library Files (*.dll)\0*.dll\0" \
                                "All Files (*.*)\0*\0\0\0")
-#define FILTER_DYNLIB_FILES ("Dynamic Library Files (*.dll)\0*.dll\0" \
-                                 "All Files (*.*)\0*\0\0\0")
 
 /*
  * Exports from network.c.
@@ -392,14 +418,53 @@ void init_common_controls(void);       /* also does some DLL-loading */
  */
 typedef struct filereq_tag filereq; /* cwd for file requester */
 bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save);
+bool request_file_w(filereq *state, OPENFILENAMEW *of,
+                    bool preserve, bool save);
 filereq *filereq_new(void);
 void filereq_free(filereq *state);
 void pgp_fingerprints_msgbox(HWND owner);
-int message_box(HWND owner, LPCTSTR text, LPCTSTR caption,
-                DWORD style, DWORD helpctxid);
+int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, DWORD style,
+                bool utf8, DWORD helpctxid);
 void MakeDlgItemBorderless(HWND parent, int id);
 char *GetDlgItemText_alloc(HWND hwnd, int id);
-void split_into_argv(char *, int *, char ***, char ***);
+wchar_t *GetDlgItemTextW_alloc(HWND hwnd, int id);
+/*
+ * The split_into_argv functions take a single string 'cmdline' (char
+ * or wide) to split up into arguments. They return an argc and argv
+ * pair, and also 'argstart', an array of pointers into the original
+ * command line, pointing at the place where each output argument
+ * begins. (Useful for retrieving the tail of the original command
+ * line corresponding to a certain argument onwards, or identifying a
+ * section of the original command line to blank out for privacy.)
+ *
+ * If the command line includes the program name (e.g. if it was
+ * returned from GetCommandLine()), set includes_program_name=true. If
+ * it doesn't (e.g. it was the arguments string received by WinMain),
+ * set that flag to false. This affects the rules for argument
+ * splitting, which is done differently in the program name
+ * (specifically, \ isn't special, and won't escape ").
+ *
+ * Mutability: the argv[] words are in fresh dynamically allocated
+ * memory, so you can write into them safely. The original cmdline is
+ * passed in as a const pointer, and not modified in this function.
+ * But the pointers into that string written into argstart have the
+ * type of a mutable char *. Similarly to strchr, this is due to the
+ * limitation of C that you can't specify argstart as having the same
+ * constness as cmdline: the idea is that you either pass a
+ * non-mutable cmdline and promise not to write through the argstart
+ * pointers, of you pass a mutable one and are free to write through
+ * it.
+ *
+ * Allocation: argv and argstart are dynamically allocated. There's
+ * also a dynamically allocated string behind the scenes storing the
+ * actual strings. argv[0] guarantees to point at the first character
+ * of that. So to free all the memory allocated by this function, you
+ * must free argv[0], then argv, and also argstart.
+ */
+void split_into_argv(const char *cmdline, bool includes_program_name,
+                     int *argc, char ***argv, char ***argstart);
+void split_into_argv_w(const wchar_t *cmdline, bool includes_program_name,
+                       int *argc, wchar_t ***argv, wchar_t ***argstart);
 
 /*
  * Private structure for prefslist state. Only in the header file
@@ -783,18 +848,27 @@ void unlock_interprocess_mutex(HANDLE mutex);
 
 typedef void (*aux_opt_error_fn_t)(const char *, ...);
 typedef struct AuxMatchOpt {
-    int index, argc;
-    char **argv;
+    CmdlineArgList *arglist;
+    size_t index;
     bool doing_opts;
     aux_opt_error_fn_t error;
 } AuxMatchOpt;
-AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index,
-                               aux_opt_error_fn_t opt_error);
-bool aux_match_arg(AuxMatchOpt *amo, char **val);
-bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...);
+AuxMatchOpt aux_match_opt_init(aux_opt_error_fn_t opt_error);
+bool aux_match_arg(AuxMatchOpt *amo, CmdlineArg **val);
+bool aux_match_opt(AuxMatchOpt *amo, CmdlineArg **val,
+                   const char *optname, ...);
 bool aux_match_done(AuxMatchOpt *amo);
 
-char *save_screenshot(HWND hwnd, const char *outfile);
+char *save_screenshot(HWND hwnd, Filename *outfile);
 void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend);
 
+void setup_gui_timing(void);
+
+/* Windows-specific extra functions in cmdline_arg.c */
+CmdlineArgList *cmdline_arg_list_from_GetCommandLineW(void);
+const wchar_t *cmdline_arg_remainder_wide(CmdlineArg *);
+char *cmdline_arg_remainder_acp(CmdlineArg *);
+char *cmdline_arg_remainder_utf8(CmdlineArg *);
+CmdlineArg *cmdline_arg_from_utf8(CmdlineArgList *list, const char *string);
+
 #endif /* PUTTY_WINDOWS_PLATFORM_H */

+ 29 - 13
source/putty/windows/storage.c

@@ -51,9 +51,9 @@ settings_w *open_settings_w(const char *sessionname, char **errmsg)
     }
     strbuf_free(sb);
 
-    settings_w *toret = snew(settings_w);
-    toret->sesskey = sesskey;
-    return toret;
+    settings_w *handle = snew(settings_w);
+    handle->sesskey = sesskey;
+    return handle;
 }
 
 void write_setting_s(settings_w *handle, const char *key, const char *value)
@@ -91,9 +91,9 @@ settings_r *open_settings_r(const char *sessionname)
     if (!sesskey)
         return NULL;
 
-    settings_r *toret = snew(settings_r);
-    toret->sesskey = sesskey;
-    return toret;
+    settings_r *handle = snew(settings_r);
+    handle->sesskey = sesskey;
+    return handle;
 }
 
 char *read_setting_s(settings_r *handle, const char *key)
@@ -183,7 +183,23 @@ Filename *read_setting_filename(settings_r *handle, const char *name)
 void write_setting_filename(settings_w *handle,
                             const char *name, Filename *result)
 {
-    write_setting_s(handle, name, result->path);
+    /*
+     * When saving a session involving a Filename, we use the 'cpath'
+     * member of the Filename structure, because otherwise we break
+     * backwards compatibility with existing saved sessions.
+     *
+     * This means that 'exotic' filenames - those including Unicode
+     * characters outside the host system's CP_ACP default code page -
+     * cannot be represented faithfully, and saving and reloading a
+     * Conf including one will break it.
+     *
+     * This can't be fixed without breaking backwards compatibility,
+     * and if we're going to break compatibility then we should break
+     * it good and hard (the Nanny Ogg principle), and devise a
+     * completely fresh storage representation that fixes as many
+     * other legacy problems as possible at the same time.
+     */
+    write_setting_s(handle, name, result->cpath); /* FIXME */
 }
 
 void close_settings_r(settings_r *handle)
@@ -221,13 +237,13 @@ settings_e *enum_settings_start(void)
     if (!key)
         return NULL;
 
-    settings_e *ret = snew(settings_e);
-    if (ret) {
-        ret->key = key;
-        ret->i = 0;
+    settings_e *e = snew(settings_e);
+    if (e) {
+        e->key = key;
+        e->i = 0;
     }
 
-    return ret;
+    return e;
 }
 
 bool enum_settings_next(settings_e *e, strbuf *sb)
@@ -357,7 +373,7 @@ bool have_ssh_host_key(const char *hostname, int port,
     return check_stored_host_key(hostname, port, keytype, "") != 1;
 }
 
-void store_host_key(const char *hostname, int port,
+void store_host_key(Seat *seat, const char *hostname, int port,
                     const char *keytype, const char *key)
 {
     strbuf *regname = strbuf_new();

+ 140 - 106
source/putty/windows/unicode.c

@@ -682,20 +682,25 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata)
             if (!DIRECT_FONT(ucsdata->unitab_xterm[i]))
                 ucsdata->unitab_xterm[i] =
                     (WCHAR) (CSET_ACP + poorman_vt100[i - 96]);
-        for(i=128;i<256;i++)
+        for (i = 128; i < 256; i++)
             if (!DIRECT_FONT(ucsdata->unitab_scoacs[i]))
                 ucsdata->unitab_scoacs[i] =
                     (WCHAR) (CSET_ACP + poorman_scoacs[i - 128]);
     }
 }
 
+void init_ucs_generic(Conf *conf, struct unicode_data *ucsdata)
+{
+    init_ucs(conf, ucsdata);
+}
+
 static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr)
 {
     int font_index, line_index, i;
     for (line_index = 0; line_index < 256; line_index++) {
         if (DIRECT_FONT(line_tbl[line_index]))
             continue;
-        for(i = 0; i < 256; i++) {
+        for (i = 0; i < 256; i++) {
             font_index = ((32 + i) & 0xFF);
             if (line_tbl[line_index] == font_tbl[font_index]) {
                 line_tbl[line_index] = (WCHAR) (attr + font_index);
@@ -1227,8 +1232,7 @@ void get_unitab(int codepage, wchar_t *unitab, int ftype)
         for (i = 0; i < max; i++) {
             tbuf[0] = i;
 
-            if (mb_to_wc(codepage, flg, tbuf, 1, unitab + i, 1)
-                != 1)
+            if (MultiByteToWideChar(codepage, flg, tbuf, 1, unitab+i, 1) != 1)
                 unitab[i] = 0xFFFD;
         }
     } else {
@@ -1240,162 +1244,192 @@ void get_unitab(int codepage, wchar_t *unitab, int ftype)
     }
 }
 
-int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
-             char *mbstr, int mblen, const char *defchr)
+bool BinarySink_put_wc_to_mb(
+    BinarySink *bs, int codepage, const wchar_t *wcstr, int wclen,
+    const char *defchr)
 {
+    if (!wclen)
+        return true;
+
     reverse_mapping *rmap = get_reverse_mapping(codepage);
 
     if (rmap) {
+        size_t defchr_len = 0;
+        bool defchr_len_known = false;
+
         /* Do this by array lookup if we can. */
-        if (wclen < 0) {
-            for (wclen = 0; wcstr[wclen++] ;);   /* will include the NUL */
-        }
-        char *p;
-        int i;
-        for (p = mbstr, i = 0; i < wclen; i++) {
+        for (size_t i = 0; i < wclen; i++) {
             wchar_t ch = wcstr[i];
             int by;
-            const char *p1;
-
-            #define WRITECH(chr) do             \
-            {                                   \
-                assert(p - mbstr < mblen);      \
-                *p++ = (char)(chr);             \
-            } while (0)
+            const char *blk;
 
-            if ((p1 = rmap->blocks[(ch >> 8) & 0xFF]) != NULL &&
-                (by = p1[ch & 0xFF]) != '\0')
-                WRITECH(by);
+            if ((blk = rmap->blocks[(ch >> 8) & 0xFF]) != NULL &&
+                (by = blk[ch & 0xFF]) != '\0')
+                put_byte(bs, by);
             else if (ch < 0x80)
-                WRITECH(ch);
-            else if (defchr)
-                for (const char *q = defchr; *q; q++)
-                    WRITECH(*q);
-#if 1
-            else
-                WRITECH('.');
-#endif
+                put_byte(bs, ch);
+            else if (defchr) {
+                if (!defchr_len_known) {
+                    defchr_len = strlen(defchr);
+                    defchr_len_known = true;
+                }
+                put_data(bs, defchr, defchr_len);
+            }
+        }
+        return true;
+    }
 
-            #undef WRITECH
+    {
+        char internalbuf[2048];
+        char *allocbuf = NULL;
+        size_t allocsize = 0;
+        char *currbuf = internalbuf;
+        size_t currsize = lenof(internalbuf);
+        bool success;
+
+        BOOL defused = false;
+        BOOL *defusedp = &defused;
+
+        if (codepage == CP_UTF8 || !defchr[0]) {
+            /*
+             * The Win32 API spec says that defchr and defused must be
+             * NULL when doing a UTF-8 conversion, on pain of
+             * ERROR_INVALID_PARAMETER.
+             *
+             * Also, translate defchr="" on input to NULL in the Win32
+             * API.
+             */
+            defchr = NULL;
+            defusedp = NULL;
         }
-        return p - mbstr;
-    } else {
-        int defused, ret;
-        ret = WideCharToMultiByte(codepage, flags, wcstr, wclen,
-                                  mbstr, mblen, defchr, &defused);
-        if (ret)
-            return ret;
 
-#ifdef LEGACY_WINDOWS
-        /*
-         * Fallback for legacy platforms too old to support UTF-8: if
-         * the codepage is UTF-8, we can do the translation ourselves.
-         */
-        if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) {
-            size_t remaining = mblen;
-            char *p = mbstr;
-
-            while (wclen > 0) {
-                unsigned long wc = (wclen--, *wcstr++);
-                if (wclen > 0 && IS_SURROGATE_PAIR(wc, *wcstr)) {
-                    wc = FROM_SURROGATES(wc, *wcstr);
-                    wclen--, wcstr++;
-                }
+        while (true) {
+            int ret = WideCharToMultiByte(
+                codepage, 0, wcstr, wclen, currbuf, currsize,
+                defchr, defusedp);
 
-                char utfbuf[6];
-                size_t utflen = encode_utf8(utfbuf, wc);
-                if (utflen <= remaining) {
-                    memcpy(p, utfbuf, utflen);
-                    p += utflen;
-                    remaining -= utflen;
-                } else {
-                    return p - mbstr;
-                }
+            if (ret) {
+                put_data(bs, currbuf, ret);
+                success = true;
+                break;
+            } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+                success = false;
+                break;
+            } else {
+                sgrowarray_nm(allocbuf, allocsize, currsize);
+                currbuf = allocbuf;
+                currsize = allocsize;
             }
+        }
+
+        smemclr(allocbuf, allocsize);
+        if (success)
+            return true;
+    }
 
-            return p - mbstr;
+#ifdef LEGACY_WINDOWS
+    /*
+     * Fallback for legacy platforms too old to support UTF-8: if
+     * the codepage is UTF-8, we can do the translation ourselves.
+     */
+    if (codepage == CP_UTF8 && wclen > 0) {
+        while (wclen > 0) {
+            unsigned long wc = (wclen--, *wcstr++);
+            if (wclen > 0 && IS_SURROGATE_PAIR(wc, *wcstr)) {
+                wc = FROM_SURROGATES(wc, *wcstr);
+                wclen--, wcstr++;
+            }
+            put_utf8_char(bs, wc);
         }
-#endif
 
-        /* No other fallbacks are available */
-        return 0;
+        return true;
     }
+#endif
+
+    /* No other fallbacks are available */
+    return false;
 }
 
-int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
-             wchar_t *wcstr, int wclen)
+bool BinarySink_put_mb_to_wc(
+    BinarySink *bs, int codepage, const char *mbstr, int mblen)
 {
+    if (!mblen)
+        return true;
+
     if (codepage >= 65536) {
         /* Character set not known to Windows, so we'll have to
          * translate it ourself */
         size_t index = codepage - 65536;
         if (index >= lenof(cp_list))
-            return 0;
+            return false;
         const struct cp_list_item *cp = &cp_list[index];
         if (!cp->cp_table)
-            return 0;
+            return false;
 
-        size_t remaining = wclen;
-        wchar_t *p = wcstr;
         unsigned tablebase = 256 - cp->cp_size;
 
         while (mblen > 0) {
             mblen--;
             unsigned c = 0xFF & *mbstr++;
             wchar_t wc = (c < tablebase ? c : cp->cp_table[c - tablebase]);
-            if (remaining > 0) {
-                remaining--;
-                *p++ = wc;
+            put_data(bs, &wc, sizeof(wc));
+        }
+
+        return true;
+    }
+
+    {
+        wchar_t internalbuf[1024];
+        wchar_t *allocbuf = NULL;
+        size_t allocsize = 0;
+        wchar_t *currbuf = internalbuf;
+        size_t currsize = lenof(internalbuf);
+        bool success;
+
+        while (true) {
+            int ret = MultiByteToWideChar(
+                codepage, 0, mbstr, mblen, currbuf, currsize);
+
+            if (ret > 0) {
+                put_data(bs, currbuf, ret * sizeof(wchar_t));
+                success = true;
+                break;
+            } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+                success = false;
+                break;
             } else {
-                return p - wcstr;
+                sgrowarray_nm(allocbuf, allocsize, currsize);
+                currbuf = allocbuf;
+                currsize = allocsize;
             }
         }
 
-        return p - wcstr;
+        smemclr(allocbuf, allocsize * sizeof(wchar_t));
+        if (success)
+            return true;
     }
 
-    int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen);
-    if (ret)
-        return ret;
-
 #ifdef LEGACY_WINDOWS
     /*
      * Fallback for legacy platforms too old to support UTF-8: if the
      * codepage is UTF-8, we can do the translation ourselves.
      */
-    if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) {
-        size_t remaining = wclen;
-        wchar_t *p = wcstr;
+    if (codepage == CP_UTF8 && mblen > 0) {
+        BinarySource src[1];
+        BinarySource_BARE_INIT(src, mbstr, mblen);
 
-        while (mblen > 0) {
-            char utfbuf[7];
-            int thissize = mblen < 6 ? mblen : 6;
-            memcpy(utfbuf, mbstr, thissize);
-            utfbuf[thissize] = '\0';
-
-            const char *utfptr = utfbuf;
+        while (get_avail(src)) {
             wchar_t wcbuf[2];
-            size_t nwc = decode_utf8_to_wchar(&utfptr, wcbuf);
-
-            for (size_t i = 0; i < nwc; i++) {
-                if (remaining > 0) {
-                    remaining--;
-                    *p++ = wcbuf[i];
-                } else {
-                    return p - wcstr;
-                }
-            }
-
-            mbstr += (utfptr - utfbuf);
-            mblen -= (utfptr - utfbuf);
+            size_t nwc = decode_utf8_to_wchar(src, wcbuf, NULL);
+            put_data(bs, wcbuf, nwc * sizeof(wchar_t));
         }
 
-        return p - wcstr;
+        return true;
     }
 #endif
 
     /* No other fallbacks are available */
-    return 0;
+    return false;
 }
 
 bool is_dbcs_leadbyte(int codepage, char byte)

+ 1 - 1
source/putty/windows/utils/defaults.c

@@ -11,7 +11,7 @@ FontSpec *platform_default_fontspec(const char *name)
     if (!strcmp(name, "Font"))
         return fontspec_new("Courier New", false, 10, ANSI_CHARSET);
     else
-        return fontspec_new("", false, 0, 0);
+        return fontspec_new_default();
 }
 
 Filename *platform_default_filename(const char *name)

+ 58 - 10
source/putty/windows/utils/filename.c

@@ -2,48 +2,79 @@
  * Implementation of Filename for Windows.
  */
 
+#include <wchar.h>
+
 #include "putty.h"
 
 Filename *filename_from_str(const char *str)
 {
-    Filename *ret = snew(Filename);
-    ret->path = dupstr(str);
-    return ret;
+    Filename *fn = snew(Filename);
+    fn->cpath = dupstr(str);
+    fn->wpath = dup_mb_to_wc(DEFAULT_CODEPAGE, fn->cpath);
+    fn->utf8path = encode_wide_string_as_utf8(fn->wpath);
+    return fn;
+}
+
+Filename *filename_from_wstr(const wchar_t *str)
+{
+    Filename *fn = snew(Filename);
+    fn->wpath = dupwcs(str);
+    fn->cpath = dup_wc_to_mb(DEFAULT_CODEPAGE, fn->wpath, "?");
+    fn->utf8path = encode_wide_string_as_utf8(fn->wpath);
+    return fn;
+}
+
+Filename *filename_from_utf8(const char *ustr)
+{
+    Filename *fn = snew(Filename);
+    fn->utf8path = dupstr(ustr);
+    fn->wpath = decode_utf8_to_wide_string(fn->utf8path);
+    fn->cpath = dup_wc_to_mb(DEFAULT_CODEPAGE, fn->wpath, "?");
+    return fn;
 }
 
 Filename *filename_copy(const Filename *fn)
 {
-    return filename_from_str(fn->path);
+    Filename *newfn = snew(Filename);
+    newfn->cpath = dupstr(fn->cpath);
+    newfn->wpath = dupwcs(fn->wpath);
+    newfn->utf8path = dupstr(fn->utf8path);
+    return newfn;
 }
 
 const char *filename_to_str(const Filename *fn)
 {
-    return fn->path;
+    return fn->cpath;                  /* FIXME */
 }
 
 bool filename_equal(const Filename *f1, const Filename *f2)
 {
-    return !strcmp(f1->path, f2->path);
+    /* wpath is primary: two filenames refer to the same file if they
+     * have the same wpath */
+    return !wcscmp(f1->wpath, f2->wpath);
 }
 
 bool filename_is_null(const Filename *fn)
 {
-    return !*fn->path;
+    return !*fn->wpath;
 }
 
 void filename_free(Filename *fn)
 {
-    sfree(fn->path);
+    sfree(fn->wpath);
+    sfree(fn->cpath);
+    sfree(fn->utf8path);
     sfree(fn);
 }
 
 void filename_serialise(BinarySink *bs, const Filename *f)
 {
-    put_asciz(bs, f->path);
+    put_asciz(bs, f->utf8path);
 }
 Filename *filename_deserialise(BinarySource *src)
 {
-    return filename_from_str(get_asciz(src));
+    const char *utf8 = get_asciz(src);
+    return filename_from_utf8(utf8);
 }
 
 char filename_char_sanitise(char c)
@@ -52,3 +83,20 @@ char filename_char_sanitise(char c)
         return '.';
     return c;
 }
+
+FILE *f_open(const Filename *fn, const char *mode, bool isprivate)
+{
+#ifdef LEGACY_WINDOWS
+    /* Fallback for legacy pre-NT windows, where as far as I can see
+     * _wfopen just doesn't work at all */
+    init_winver();
+    if (osPlatformId == VER_PLATFORM_WIN32_WINDOWS ||
+        osPlatformId == VER_PLATFORM_WIN32s)
+        return fopen(fn->cpath, mode);
+#endif
+
+    wchar_t *wmode = dup_mb_to_wc(DEFAULT_CODEPAGE, mode);
+    FILE *fp = _wfopen(fn->wpath, wmode);
+    sfree(wmode);
+    return fp;
+}

+ 5 - 0
source/putty/windows/utils/fontspec.c

@@ -14,6 +14,11 @@ FontSpec *fontspec_new(const char *name, bool bold, int height, int charset)
     return f;
 }
 
+FontSpec *fontspec_new_default(void)
+{
+    return fontspec_new("", false, 0, 0);
+}
+
 FontSpec *fontspec_copy(const FontSpec *f)
 {
     return fontspec_new(f->name, f->isbold, f->height, f->charset);

+ 7 - 7
source/putty/windows/utils/open_for_write_would_lose_data.c

@@ -39,17 +39,17 @@ static inline bool open_for_write_would_lose_data_impl(
 bool open_for_write_would_lose_data(const Filename *fn)
 {
     static HMODULE kernel32_module;
-    DECL_WINDOWS_FUNCTION(static, BOOL, GetFileAttributesExA,
-                          (LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID));
+    DECL_WINDOWS_FUNCTION(static, BOOL, GetFileAttributesExW,
+                          (LPCWSTR, GET_FILEEX_INFO_LEVELS, LPVOID));
 
     if (!kernel32_module) {
         kernel32_module = load_system32_dll("kernel32.dll");
-        GET_WINDOWS_FUNCTION(kernel32_module, GetFileAttributesExA);
+        GET_WINDOWS_FUNCTION(kernel32_module, GetFileAttributesExW);
     }
 
-    if (p_GetFileAttributesExA) {
+    if (p_GetFileAttributesExW) {
         WIN32_FILE_ATTRIBUTE_DATA attrs;
-        if (!p_GetFileAttributesExA(fn->path, GetFileExInfoStandard, &attrs)) {
+        if (!p_GetFileAttributesExW(fn->wpath, GetFileExInfoStandard, &attrs)) {
             /*
              * Generally, if we don't identify a specific reason why we
              * should return true from this function, we return false, and
@@ -61,8 +61,8 @@ bool open_for_write_would_lose_data(const Filename *fn)
         return open_for_write_would_lose_data_impl(
             attrs.dwFileAttributes, attrs.nFileSizeHigh, attrs.nFileSizeLow);
     } else {
-        WIN32_FIND_DATA fd;
-        HANDLE h = FindFirstFile(fn->path, &fd);
+        WIN32_FIND_DATAW fd;
+        HANDLE h = FindFirstFileW(fn->wpath, &fd);
         if (h == INVALID_HANDLE_VALUE) {
             /*
              * As above, if we can't find the file at all, return false.