浏览代码

Merge branch 'thirdparty_dev' into dev

# Conflicts:
#	source/putty/crypto/ecc-ssh.c
#	source/putty/crypto/rsa.c

#	source/putty/errsock.c
#	source/putty/network.h
#	source/putty/proxy/proxy.c
#	source/putty/putty.h
#	source/putty/ssh/mainchan.c
#	source/putty/ssh/portfwd.c
#	source/putty/ssh/ssh.c
#	source/putty/sshpubk.c
#	source/putty/utils/conf.c
#	source/putty/utils/tempseat.c
#	source/putty/windows/gss.c
#	source/putty/windows/handle-socket.c
#	source/putty/windows/network.c
#	source/putty/windows/platform.h
#	source/putty/windows/storage.c
#	source/putty/windows/unicode.c
#	source/putty/windows/utils/filename.c

Source commit: bf2485b229a3855694c35c518f72482b97ca47c5
Martin Prikryl 10 月之前
父节点
当前提交
a561deeac4
共有 65 个文件被更改,包括 3591 次插入1545 次删除
  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. 1 7
      source/putty/errsock.c
  8. 58 58
      source/putty/import.c
  9. 0 20
      source/putty/logging.c
  10. 24 0
      source/putty/marshal.h
  11. 48 13
      source/putty/misc.h
  12. 24 16
      source/putty/network.h
  13. 2 1
      source/putty/proxy/local.c
  14. 23 10
      source/putty/proxy/proxy.c
  15. 2 1
      source/putty/proxy/telnet.c
  16. 207 365
      source/putty/putty.h
  17. 211 465
      source/putty/settings.c
  18. 52 0
      source/putty/specials.h
  19. 5 5
      source/putty/ssh.h
  20. 2 2
      source/putty/ssh/connection2-client.c
  21. 2 2
      source/putty/ssh/connection2.c
  22. 2 2
      source/putty/ssh/connection2.h
  23. 1 1
      source/putty/ssh/kex2-client.c
  24. 7 3
      source/putty/ssh/mainchan.c
  25. 4 16
      source/putty/ssh/portfwd.c
  26. 3 3
      source/putty/ssh/sharing.c
  27. 7 4
      source/putty/ssh/ssh.c
  28. 7 2
      source/putty/ssh/userauth2-client.c
  29. 5 5
      source/putty/sshcr.h
  30. 13 11
      source/putty/sshpubk.c
  31. 3 1
      source/putty/storage.h
  32. 1 1
      source/putty/stubs/null-plug.c
  33. 1 0
      source/putty/stubs/null-seat.c
  34. 12 0
      source/putty/stubs/null-socket.c
  35. 11 1
      source/putty/utils/backend_socket_log.c
  36. 18 0
      source/putty/utils/burnwcs.c
  37. 179 102
      source/putty/utils/conf.c
  38. 53 0
      source/putty/utils/conf_data.c
  39. 264 0
      source/putty/utils/decode_utf8.c
  40. 21 0
      source/putty/utils/decode_utf8_to_wchar.c
  41. 35 0
      source/putty/utils/decode_utf8_to_wide_string.c
  42. 16 15
      source/putty/utils/dup_mb_to_wc.c
  43. 28 0
      source/putty/utils/dup_wc_to_mb.c
  44. 19 0
      source/putty/utils/dupwcs.c
  45. 26 0
      source/putty/utils/encode_utf8.c
  46. 23 0
      source/putty/utils/encode_wide_string_as_utf8.c
  47. 3 3
      source/putty/utils/log_proxy_stderr.c
  48. 32 0
      source/putty/utils/logeventf.c
  49. 20 0
      source/putty/utils/marshal.c
  50. 1 0
      source/putty/utils/prompts.c
  51. 6 6
      source/putty/utils/sk_free_peer_info.c
  52. 2 9
      source/putty/utils/stripctrl.c
  53. 12 0
      source/putty/utils/tempseat.c
  54. 5 5
      source/putty/utils/tree234.c
  55. 4 4
      source/putty/version.h
  56. 64 72
      source/putty/windows/gss.c
  57. 12 7
      source/putty/windows/handle-socket.c
  58. 132 125
      source/putty/windows/network.c
  59. 97 23
      source/putty/windows/platform.h
  60. 29 13
      source/putty/windows/storage.c
  61. 141 107
      source/putty/windows/unicode.c
  62. 1 1
      source/putty/windows/utils/defaults.c
  63. 58 10
      source/putty/windows/utils/filename.c
  64. 5 0
      source/putty/windows/utils/fontspec.c
  65. 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

@@ -132,8 +132,7 @@ static struct ec_curve *ec_p256(void)
 
     WINSCP_CURVE_CLEANUP(w);
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff);
         mp_int *a = MP_LITERAL(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc);
         mp_int *b = MP_LITERAL(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b);
@@ -166,8 +165,7 @@ static struct ec_curve *ec_p384(void)
 
     WINSCP_CURVE_CLEANUP(w);
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff);
         mp_int *a = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc);
         mp_int *b = MP_LITERAL(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef);
@@ -200,8 +198,7 @@ static struct ec_curve *ec_p521(void)
 
     WINSCP_CURVE_CLEANUP(w);
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
         mp_int *a = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc);
         mp_int *b = MP_LITERAL(0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00);
@@ -234,8 +231,7 @@ static struct ec_curve *ec_curve25519(void)
 
     WINSCP_CURVE_CLEANUP(m);
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed);
         mp_int *a = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000076d06);
         mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000001);
@@ -265,8 +261,7 @@ static struct ec_curve *ec_curve448(void)
 
     WINSCP_CURVE_CLEANUP(m);
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
         mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6);
         mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001);
@@ -296,8 +291,7 @@ static struct ec_curve *ec_ed25519(void)
 
     WINSCP_CURVE_CLEANUP(e);
 
-    if (!initialised)
-    {
+    if (!initialised) {
         mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed);
         mp_int *d = MP_LITERAL(0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3);
         mp_int *a = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec); /* == p-1 */
@@ -333,8 +327,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

@@ -356,12 +356,12 @@ 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 *);
     unsigned i; // WINSCP
     for (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;
 }
 
 /*
@@ -862,7 +862,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);
     { // WINSCP
     size_t i; // WINSCP

+ 13 - 1
source/putty/defs.h

@@ -79,6 +79,12 @@
 #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;
 
@@ -96,6 +102,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;
@@ -104,7 +111,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;
 
@@ -119,6 +126,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;

+ 1 - 7
source/putty/errsock.c

@@ -39,18 +39,12 @@ 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 = {
     // WINSCP
     /*.plug =*/ sk_error_plug,
     /*.close =*/ sk_error_close,
-    NULL, NULL, NULL, NULL, // WINSCP
     /*.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

@@ -345,7 +345,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;
@@ -353,8 +353,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";
@@ -372,11 +372,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;
@@ -388,8 +388,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) {
@@ -417,15 +417,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";
@@ -438,7 +438,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) {
@@ -465,7 +465,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));
                 }
@@ -478,12 +478,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;
@@ -491,7 +491,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) {
@@ -500,11 +500,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;
@@ -1127,7 +1127,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;
@@ -1137,8 +1137,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";
@@ -1179,7 +1179,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));
             }
@@ -1191,12 +1191,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";
@@ -1206,11 +1206,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";
@@ -1220,9 +1220,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";
@@ -1231,7 +1231,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";
@@ -1242,8 +1242,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";
@@ -1265,23 +1265,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;
@@ -1293,7 +1293,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) {
@@ -1302,10 +1302,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;
@@ -1733,7 +1733,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;
@@ -1742,9 +1742,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";
@@ -1811,8 +1811,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;
@@ -1832,7 +1832,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++;
@@ -1843,13 +1843,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) {
@@ -1857,10 +1857,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)
@@ -250,26 +253,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);
@@ -316,6 +346,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);
 
     /*
@@ -252,8 +252,9 @@ static inline void sk_write_eof(Socket *s)
 
 #pragma option push -w-bei // WINSCP
 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); }
 #pragma option pop // WINSCP
 static inline void plug_closing(Plug *p, PlugCloseType type, const char *msg)
 { p->vt->closing(p, type, msg); }
@@ -300,23 +301,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;
@@ -345,7 +348,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
@@ -385,19 +388,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);
@@ -409,8 +417,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

@@ -212,7 +212,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,
@@ -422,6 +423,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 = {
     // WINSCP
     /*.plug =*/ sk_proxy_plug,
@@ -431,7 +445,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 = {
@@ -509,8 +523,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;
@@ -596,7 +609,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);
         }
 
@@ -604,7 +617,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);
         }
 
@@ -625,7 +638,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 - 365
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
@@ -1487,10 +1478,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)
@@ -1535,6 +1527,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);
@@ -1577,6 +1570,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y);
 
 #ifndef WINSCP
 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,
@@ -1803,282 +1797,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) \
-    /* MPEXT BEGIN */ \
-    X(INT, NONE, connect_timeout) \
-    X(INT, NONE, sndbuf) \
-    X(STR, NONE, srcaddr) \
-    X(BOOL, NONE, force_remote_cmd2) \
-    X(BOOL, NONE, change_password) \
-    /* MPEXT END */ \
-    /* 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. */
@@ -2086,6 +1909,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 */
@@ -2103,6 +1929,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);
@@ -2116,7 +1945,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);
@@ -2219,7 +2056,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 *);
@@ -2402,28 +2239,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 *);
 
 /*
@@ -2490,15 +2306,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);
@@ -2571,6 +2381,8 @@ bool have_ssh_host_key(Seat *seat, const char *host, int port, const char *keyty
  * 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);
@@ -2579,6 +2391,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.
  */
@@ -2595,17 +2412,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(
@@ -2614,6 +2425,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.
@@ -2636,6 +2474,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) };
@@ -2975,6 +2814,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)
@@ -839,19 +710,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);
@@ -865,13 +800,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;
@@ -879,10 +807,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.
@@ -935,46 +859,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
@@ -1029,98 +933,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
@@ -1141,26 +981,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[] = {
@@ -1181,13 +1001,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",
@@ -1214,8 +1027,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,
@@ -1226,32 +1037,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);
@@ -1261,48 +1048,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

@@ -231,7 +231,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 */
@@ -240,7 +240,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 */
@@ -330,12 +330,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)
@@ -1425,7 +1425,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

@@ -164,7 +164,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;
@@ -333,7 +333,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

@@ -38,7 +38,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(
@@ -1487,7 +1487,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

@@ -160,7 +160,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,
@@ -171,7 +171,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

@@ -1064,7 +1064,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

@@ -187,7 +187,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) {
@@ -210,7 +212,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 if (*cmd) {
@@ -295,7 +299,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) || conf_get_bool(mc->conf, CONF_force_remote_cmd2)) { // WINSCP
+        } else if (*conf_get_str_ambi(mc->conf, CONF_remote_cmd2, NULL) || conf_get_bool(mc->conf, CONF_force_remote_cmd2)) { // WINSCP
             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;
@@ -435,7 +423,7 @@ static void pfd_sent(Plug *plug, size_t bufsize)
 
 static const PlugVtable PortForwarding_plugvt = {
     // WINSCP
-    /*.log =*/ pfd_log,
+    /*.log =*/ nullplug_log,
     /*.closing =*/ pfd_closing,
     /*.receive =*/ pfd_receive,
     /*.sent =*/ pfd_sent,
@@ -561,7 +549,7 @@ static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
 
 static const PlugVtable PortListener_plugvt = {
     // WINSCP
-    /*.log =*/ pfl_log,
+    /*.log =*/ nullplug_log,
     /*.closing =*/ pfl_closing,
     NULL,
     NULL,

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

@@ -1746,7 +1746,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++;                           \
@@ -1917,7 +1917,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.
@@ -1964,7 +1964,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

@@ -594,8 +594,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);
 
@@ -609,7 +610,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);
 }
@@ -845,10 +846,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

@@ -336,8 +336,9 @@ static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s,
     } // WINSCP
 }
 
-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);
@@ -806,6 +807,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");
@@ -1880,6 +1882,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                 // no indentation to ease merges
                 // /WINSCP
                 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");
@@ -1973,6 +1976,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");
@@ -2200,6 +2204,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

@@ -767,7 +767,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;
@@ -778,7 +778,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;
 
@@ -1017,10 +1017,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;
         }
@@ -1029,15 +1029,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;
     }
@@ -1062,7 +1062,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,
@@ -1161,6 +1161,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

@@ -94,8 +94,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;
@@ -68,7 +68,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
             { // WINSCP
             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;
@@ -86,7 +86,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

@@ -338,3 +338,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

@@ -226,9 +226,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.
@@ -241,14 +238,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

@@ -290,6 +290,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)
 {
     /*
@@ -330,6 +341,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. */
 
@@ -121,9 +121,7 @@ static void add_library_to_never_unload_tree(HMODULE module)
 struct ssh_gss_liblist *ssh_gss_setup(Conf *conf, LogContext *logctx) // MPEXT
 {
     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");
@@ -140,55 +138,47 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf, LogContext *logctx) // MPEXT
 
     /* 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 =
@@ -245,34 +235,36 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf, LogContext *logctx) // MPEXT
      * 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);
         // MPEXT
         if (!module && logctx) {
             char *buf = dupprintf("Cannot load GSSAPI from user-specified library '%s': %s", path, win_strerror(GetLastError()));
@@ -286,7 +278,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf, LogContext *logctx) // MPEXT
 
         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) \
@@ -502,8 +494,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 = MPEXT_INIT_SEC_BUFFER(send_tok->length,SECBUFFER_TOKEN,send_tok->value);
     SecBuffer wrecv_tok = MPEXT_INIT_SEC_BUFFER(recv_tok->length,SECBUFFER_TOKEN,recv_tok->value);
-    SecBufferDesc output_desc= MPEXT_INIT_SEC_BUFFERDESC(SECBUFFER_VERSION,1,&wsend_tok);
-    SecBufferDesc input_desc = MPEXT_INIT_SEC_BUFFERDESC(SECBUFFER_VERSION,1,&wrecv_tok);
+    SecBufferDesc output_desc = MPEXT_INIT_SEC_BUFFERDESC(SECBUFFER_VERSION,1,&wsend_tok);
+    SecBufferDesc input_desc  = MPEXT_INIT_SEC_BUFFERDESC(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;
@@ -554,7 +546,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;
@@ -577,7 +569,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);
@@ -641,7 +633,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];
@@ -688,7 +680,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

@@ -111,7 +111,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;
 }
@@ -317,7 +317,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;
@@ -325,6 +325,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
@@ -347,7 +350,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;
@@ -367,13 +370,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,
@@ -460,7 +464,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;
 }
@@ -474,7 +479,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,

+ 132 - 125
source/putty/windows/network.c

@@ -213,6 +213,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,
@@ -341,6 +343,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);
 
@@ -568,18 +571,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)
@@ -842,7 +845,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 = {
     // WINSCP
@@ -853,57 +856,57 @@ 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. */
 #ifdef MPEXT
-    errstr = do_select(plug, ret->s, true);
+    errstr = do_select(plug, s->s, true);
 #else
 #endif
     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,
@@ -939,7 +942,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);
     }
 
@@ -1138,7 +1141,7 @@ static DWORD try_connect(NetSocket *sock,
         sock->writable = true;
         { // WINSCP
         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);
         } // WINSCP
     }
@@ -1172,7 +1175,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;
@@ -1187,55 +1190,55 @@ Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
 #endif
               )
 {
-    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 {
 #ifdef MPEXT
         ret->error = NULL;
 #endif
-        err = try_connect(ret
+        err = try_connect(s
 #ifdef MPEXT
             , timeout, sndbuf, srcaddr
 #endif
         );
-    } while (err && sk_nextaddr(ret->addr, &ret->step));
+    } 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;
@@ -1248,7 +1251,7 @@ static Socket *sk_newlistener_internal(
 
     DWORD err;
     const char *errstr;
-    NetSocket *ret;
+    NetSocket *s;
     int retcode;
 
     int address_family = orig_address_family;
@@ -1256,20 +1259,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
@@ -1283,25 +1286,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));
     }
 
@@ -1351,7 +1354,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;
             }
         }
@@ -1384,7 +1387,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 {
@@ -1392,32 +1395,32 @@ 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. */
 #ifdef MPEXT
-    errstr = do_select(plug, s, true);
+    errstr = do_select(plug, sk, true);
 #else
-    errstr = do_select(s, true);
+    errstr = do_select(sk, true);
 #endif
     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
     /*
@@ -1431,8 +1434,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);
             }
@@ -1440,7 +1443,7 @@ static Socket *sk_newlistener_internal(
     }
 #endif
 
-    return &ret->sock;
+    return &s->sock;
 }
 
 Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
@@ -1692,8 +1695,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
 #ifdef MPEXT
@@ -1724,7 +1727,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);
@@ -1832,8 +1835,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;
@@ -1873,7 +1875,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
@@ -1883,12 +1885,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;
@@ -2004,9 +2011,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;
 }

+ 97 - 23
source/putty/windows/platform.h

@@ -57,15 +57,28 @@ typedef const char *HelpCtx;
 #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;
 };
-FILE * mp_wfopen(const char *filename, const char *mode); // WINSCP
-static inline FILE *f_open(const Filename *filename, const char *mode,
-                           bool isprivate)
-{
-    return mp_wfopen(filename->path, mode);
-}
+void ptrace(const char* msg);
+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;
@@ -74,6 +87,7 @@ struct FontSpec {
 };
 struct FontSpec *fontspec_new(
     const char *name, bool bold, int height, int charset);
+#endif
 
 #ifndef CLEARTYPE_QUALITY
 #define CLEARTYPE_QUALITY 5
@@ -210,6 +224,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
 /*
@@ -267,7 +284,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)
 
@@ -301,12 +318,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.
@@ -410,14 +436,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
@@ -808,18 +873,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

@@ -54,9 +54,9 @@ settings_w *open_settings_w(const char *sessionname, char **errmsg)
     strbuf_free(sb);
 
     { // WINSCP
-    settings_w *toret = snew(settings_w);
-    toret->sesskey = sesskey;
-    return toret;
+    settings_w *handle = snew(settings_w);
+    handle->sesskey = sesskey;
+    return handle;
     } // WINSCP
     } // WINSCP
     } // WINSCP
@@ -100,9 +100,9 @@ settings_r *open_settings_r(const char *sessionname)
         return NULL;
 
     { // WINSCP
-    settings_r *toret = snew(settings_r);
-    toret->sesskey = sesskey;
-    return toret;
+    settings_r *handle = snew(settings_r);
+    handle->sesskey = sesskey;
+    return handle;
     } // WINSCP
     } // WINSCP
     } // WINSCP
@@ -195,7 +195,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)
@@ -237,13 +253,13 @@ settings_e *enum_settings_start(void)
         return NULL;
 
     { // WINSCP
-    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;
     } // WINSCP
 }
 
@@ -400,7 +416,7 @@ bool have_ssh_host_key(const char *hostname, int port,
 }
 #endif
 
-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();

+ 141 - 107
source/putty/windows/unicode.c

@@ -685,20 +685,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);
@@ -1230,8 +1235,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 {
@@ -1243,168 +1247,198 @@ 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;
             }
+        }
 
-            return p - mbstr;
+        smemclr(allocbuf, allocsize);
+        if (success)
+            return true;
+    }
+
+#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;
 }
 
 #endif
 
-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)
 {
     #ifdef WINSCP
     pinitassert(codepage == DEFAULT_CODEPAGE);
     #else
+    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;
-            } else {
-                return p - wcstr;
-            }
+            put_data(bs, &wc, sizeof(wc));
         }
 
-        return p - wcstr;
+        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 {
+                sgrowarray_nm(allocbuf, allocsize, currsize);
+                currbuf = allocbuf;
+                currsize = allocsize;
+            }
+        }
     #endif
 
-    int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen);
-    if (ret)
-        return ret;
+        smemclr(allocbuf, allocsize * sizeof(wchar_t));
+        if (success)
+            return true;
+    }
 
 #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,6 +2,8 @@
  * Implementation of Filename for Windows.
  */
 
+#include <wchar.h>
+
 #include "putty.h"
 #ifdef MPEXT
 #include <assert.h>
@@ -9,14 +11,38 @@
 
 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;
 }
 
 #ifdef WINSCP
@@ -58,32 +84,37 @@ const char *filename_to_str(const Filename *fn)
     #ifdef WINSCP
     if (in_memory_key_data(fn) != NULL) return "in-memory";
     #endif
-    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)
@@ -93,6 +124,23 @@ char filename_char_sanitise(char c)
     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;
+}
+
 #ifdef WINSCP
 
 FILE * mp_wfopen(const char *filename, const char *mode)

+ 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.