瀏覽代碼

Updating code to PuTTY 0.77

Source commit: da47a0c8a6347ce05fd02167b644c05cef61dc28
Martin Prikryl 3 年之前
父節點
當前提交
6c8cf9ae8d
共有 100 個文件被更改,包括 40734 次插入235 次删除
  1. 8 5
      libs/puttyvs/PuTTYVS.vcxproj
  2. 345 182
      source/Putty.cbproj
  3. 40 23
      source/core/PuttyIntf.cpp
  4. 1 1
      source/core/PuttyIntf.h
  5. 34 16
      source/core/SecureShell.cpp
  6. 14 0
      source/putty/crypto/aes-common.c
  7. 316 0
      source/putty/crypto/aes-ni.c
  8. 99 0
      source/putty/crypto/aes-select.c
  9. 1111 0
      source/putty/crypto/aes-sw.c
  10. 126 0
      source/putty/crypto/aes.h
  11. 0 0
      source/putty/crypto/aesold.c
  12. 147 0
      source/putty/crypto/arcfour.c
  13. 623 0
      source/putty/crypto/argon2.c
  14. 118 0
      source/putty/crypto/bcrypt.c
  15. 242 0
      source/putty/crypto/blake2.c
  16. 708 0
      source/putty/crypto/blowfish.c
  17. 14 0
      source/putty/crypto/blowfish.h
  18. 1066 0
      source/putty/crypto/chacha20-poly1305.c
  19. 1092 0
      source/putty/crypto/des.c
  20. 263 0
      source/putty/crypto/diffie-hellman.c
  21. 525 0
      source/putty/crypto/dsa.c
  22. 1210 0
      source/putty/crypto/ecc-arithmetic.c
  23. 1941 0
      source/putty/crypto/ecc-ssh.c
  24. 243 0
      source/putty/crypto/ecc.h
  25. 13 0
      source/putty/crypto/hash_simple.c
  26. 272 0
      source/putty/crypto/hmac.c
  27. 43 0
      source/putty/crypto/mac.c
  28. 16 0
      source/putty/crypto/mac_simple.c
  29. 256 0
      source/putty/crypto/md5.c
  30. 3019 0
      source/putty/crypto/mpint.c
  31. 324 0
      source/putty/crypto/mpint_i.h
  32. 307 0
      source/putty/crypto/prng.c
  33. 32 0
      source/putty/crypto/pubkey-pem.c
  34. 29 0
      source/putty/crypto/pubkey-ppk.c
  35. 1147 0
      source/putty/crypto/rsa.c
  36. 10 0
      source/putty/crypto/sha1-common.c
  37. 53 0
      source/putty/crypto/sha1-select.c
  38. 176 0
      source/putty/crypto/sha1-sw.c
  39. 115 0
      source/putty/crypto/sha1.h
  40. 30 0
      source/putty/crypto/sha256-common.c
  41. 53 0
      source/putty/crypto/sha256-select.c
  42. 170 0
      source/putty/crypto/sha256-sw.c
  43. 119 0
      source/putty/crypto/sha256.h
  44. 357 0
      source/putty/crypto/sha3.c
  45. 71 0
      source/putty/crypto/sha512-common.c
  46. 66 0
      source/putty/crypto/sha512-select.c
  47. 173 0
      source/putty/crypto/sha512-sw.c
  48. 137 0
      source/putty/crypto/sha512.h
  49. 55 0
      source/putty/crypto/xdmauth.c
  50. 7 1
      source/putty/defs.h
  51. 197 0
      source/putty/proxy/cproxy.c
  52. 99 0
      source/putty/proxy/cproxy.h
  53. 715 0
      source/putty/proxy/http_p.c
  54. 129 0
      source/putty/proxy/interactor.c
  55. 281 0
      source/putty/proxy/local.c
  56. 16 0
      source/putty/proxy/nosshproxy.c
  57. 652 0
      source/putty/proxy/proxy.c
  58. 123 0
      source/putty/proxy/proxy.h
  59. 72 0
      source/putty/proxy/socks.h
  60. 137 0
      source/putty/proxy/socks4.c
  61. 508 0
      source/putty/proxy/socks5.c
  62. 369 0
      source/putty/proxy/telnet.c
  63. 8 3
      source/putty/putty.h
  64. 2 4
      source/putty/puttyexp.h
  65. 252 0
      source/putty/ssh/agentf.c
  66. 207 0
      source/putty/ssh/bpp-bare.c
  67. 186 0
      source/putty/ssh/bpp.h
  68. 1011 0
      source/putty/ssh/bpp2.c
  69. 107 0
      source/putty/ssh/censor2.c
  70. 316 0
      source/putty/ssh/channel.h
  71. 1056 0
      source/putty/ssh/common_p.c
  72. 522 0
      source/putty/ssh/connection2-client.c
  73. 1814 0
      source/putty/ssh/connection2.c
  74. 237 0
      source/putty/ssh/connection2.h
  75. 217 0
      source/putty/ssh/gss.h
  76. 297 0
      source/putty/ssh/gssc.c
  77. 24 0
      source/putty/ssh/gssc.h
  78. 935 0
      source/putty/ssh/kex2-client.c
  79. 548 0
      source/putty/ssh/mainchan.c
  80. 25 0
      source/putty/ssh/nosharing.c
  81. 105 0
      source/putty/ssh/pgssapi.c
  82. 333 0
      source/putty/ssh/pgssapi.h
  83. 1225 0
      source/putty/ssh/portfwd.c
  84. 179 0
      source/putty/ssh/ppl.h
  85. 2190 0
      source/putty/ssh/sharing.c
  86. 53 0
      source/putty/ssh/signal-list.h
  87. 1391 0
      source/putty/ssh/ssh.c
  88. 126 0
      source/putty/ssh/transient-hostkey-cache.c
  89. 2240 0
      source/putty/ssh/transport2.c
  90. 246 0
      source/putty/ssh/transport2.h
  91. 179 0
      source/putty/ssh/ttymode-list.h
  92. 1958 0
      source/putty/ssh/userauth2-client.c
  93. 637 0
      source/putty/ssh/verstring.c
  94. 1254 0
      source/putty/ssh/zlib.c
  95. 4 0
      source/putty/sshpubk.c
  96. 42 0
      source/putty/stubs/nullplug.c
  97. 28 0
      source/putty/utils/antispoof.c
  98. 62 0
      source/putty/utils/backend_socket_log.c
  99. 54 0
      source/putty/utils/base64_decode_atom.c
  100. 30 0
      source/putty/utils/base64_encode_atom.c

+ 8 - 5
libs/puttyvs/PuTTYVS.vcxproj

@@ -48,9 +48,10 @@
       <Optimization>MaxSpeed</Optimization>
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
-      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;SECURITY_WIN32;WINSCP_VS;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>WINSCP;WIN32;NDEBUG;_LIB;SECURITY_WIN32;WINSCP_VS;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
       <BasicRuntimeChecks>Default</BasicRuntimeChecks>
+      <AdditionalIncludeDirectories>..\..\source\putty</AdditionalIncludeDirectories>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -65,10 +66,11 @@
       <Optimization>MaxSpeed</Optimization>
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
-      <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;SECURITY_WIN32;WINSCP_VS;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>WINSCP;WIN32;NDEBUG;_LIB;SECURITY_WIN32;WINSCP_VS;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BufferSecurityCheck>false</BufferSecurityCheck>
       <ExceptionHandling>false</ExceptionHandling>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
+      <AdditionalIncludeDirectories>..\..\source\putty</AdditionalIncludeDirectories>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -78,9 +80,10 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="..\..\source\putty\sshaes.c" />
-    <ClCompile Include="..\..\source\putty\sshargon2.c" />
-    <ClCompile Include="..\..\source\putty\sshsh256.c" />
+    <ClCompile Include="..\..\source\putty\crypto\aes-ni.c" />
+    <ClCompile Include="..\..\source\putty\crypto\aes-sw.c" />
+    <ClCompile Include="..\..\source\putty\crypto\argon2.c" />
+    <ClCompile Include="..\..\source\putty\crypto\sha256-sw.c" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">

+ 345 - 182
source/Putty.cbproj

@@ -59,14 +59,14 @@
 		<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;$(DCC_Namespace)</DCC_Namespace>
 		<Defines>SECURITY_WIN32;WINSCP;MPEXT;PLATFORM_HAS_SMEMCLR;_WINDOWS;$(Defines)</Defines>
 		<FinalOutputDir>$(INTERM_PATH)\$(Platform)\$(Config)</FinalOutputDir>
-		<IncludePath>putty\;putty\windows\;$(BDS)\include;$(IncludePath)</IncludePath>
+		<IncludePath>putty\windows\utils\;putty\utils\;putty\stubs\;putty\ssh\;putty\proxy\;putty\crypto\;putty\;putty\windows\;$(BDS)\include;$(IncludePath)</IncludePath>
 		<IntermediateOutputDir>$(INTERM_PATH)\$(Platform)\$(Config)</IntermediateOutputDir>
 		<Manifest_File>None</Manifest_File>
 		<Multithreaded>true</Multithreaded>
 		<OutputExt>lib</OutputExt>
 		<ProjectType>CppStaticLibrary</ProjectType>
 		<SanitizedProjectName>Putty</SanitizedProjectName>
-		<TLIB_PageSize>128</TLIB_PageSize>
+		<TLIB_PageSize>256</TLIB_PageSize>
 		<VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
 		<VerInfo_Locale>1033</VerInfo_Locale>
 	</PropertyGroup>
@@ -94,289 +94,452 @@
 		<TASM_Debugging>None</TASM_Debugging>
 	</PropertyGroup>
 	<ItemGroup>
-		<CppCompile Include="putty\agentf.c">
-			<BuildOrder>60</BuildOrder>
-		</CppCompile>
-		<CppCompile Include="putty\be_misc.c">
-			<BuildOrder>47</BuildOrder>
-		</CppCompile>
-		<CppCompile Include="putty\be_ssh.c">
-			<BuildOrder>84</BuildOrder>
+		<CppCompile Include="putty\be_list.c">
+			<BuildOrder>0</BuildOrder>
 		</CppCompile>
 		<CppCompile Include="putty\callback.c">
-			<BuildOrder>5</BuildOrder>
-			<BuildOrder>25</BuildOrder>
+			<BuildOrder>1</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\conf.c">
-			<BuildOrder>5</BuildOrder>
-			<BuildOrder>25</BuildOrder>
+		<CppCompile Include="putty\crypto\aes-common.c">
+			<BuildOrder>9</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\cproxy.c">
-			<BuildOrder>5</BuildOrder>
-			<BuildOrder>25</BuildOrder>
+		<CppCompile Include="putty\crypto\aesold.c">
+			<BuildOrder>150</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ecc.c">
-			<BuildOrder>73</BuildOrder>
+		<CppCompile Include="putty\crypto\aes-select.c">
+			<BuildOrder>11</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\errsock.c">
-			<BuildOrder>44</BuildOrder>
+		<CppCompile Include="putty\crypto\aes-sw.c">
+			<BuildOrder>12</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\import.c">
-			<BuildOrder>46</BuildOrder>
+		<CppCompile Include="putty\crypto\arcfour.c">
+			<BuildOrder>13</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\logging.c">
-			<BuildOrder>27</BuildOrder>
-			<BuildOrder>11</BuildOrder>
+		<CppCompile Include="putty\crypto\argon2.c">
+			<BuildOrder>14</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\mainchan.c">
-			<BuildOrder>69</BuildOrder>
+		<CppCompile Include="putty\crypto\bcrypt.c">
+			<BuildOrder>15</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\marshal.c">
-			<BuildOrder>55</BuildOrder>
+		<CppCompile Include="putty\crypto\blake2.c">
+			<BuildOrder>16</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\memory.c">
-			<BuildOrder>73</BuildOrder>
+		<CppCompile Include="putty\crypto\blowfish.c">
+			<BuildOrder>17</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\misc.c">
-			<BuildOrder>26</BuildOrder>
-			<BuildOrder>14</BuildOrder>
+		<CppCompile Include="putty\crypto\chacha20-poly1305.c">
+			<BuildOrder>24</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\miscucs.c">
-			<BuildOrder>51</BuildOrder>
+		<CppCompile Include="putty\crypto\des.c">
+			<BuildOrder>18</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\mpint.c">
-			<BuildOrder>74</BuildOrder>
+		<CppCompile Include="putty\crypto\diffie-hellman.c">
+			<BuildOrder>19</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\noshare.c">
-			<BuildOrder>41</BuildOrder>
+		<CppCompile Include="putty\crypto\dsa.c">
+			<BuildOrder>20</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\nullplug.c">
-			<BuildOrder>56</BuildOrder>
+		<CppCompile Include="putty\crypto\ecc-arithmetic.c">
+			<BuildOrder>21</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\pgssapi.c">
-			<BuildOrder>37</BuildOrder>
+		<CppCompile Include="putty\crypto\ecc-ssh.c">
+			<BuildOrder>22</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\portfwd.c">
+		<CppCompile Include="putty\crypto\hash_simple.c">
+			<BuildOrder>150</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\crypto\hmac.c">
 			<BuildOrder>23</BuildOrder>
-			<BuildOrder>17</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\proxy.c">
-			<BuildOrder>20</BuildOrder>
+		<CppCompile Include="putty\crypto\mac.c">
+			<BuildOrder>25</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\settings.c">
-			<BuildOrder>82</BuildOrder>
+		<CppCompile Include="putty\crypto\mac_simple.c">
+			<BuildOrder>26</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh.c">
-			<BuildOrder>23</BuildOrder>
-			<BuildOrder>19</BuildOrder>
+		<CppCompile Include="putty\crypto\md5.c">
+			<BuildOrder>27</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2bpp.c">
-			<BuildOrder>54</BuildOrder>
+		<CppCompile Include="putty\crypto\mpint.c">
+			<BuildOrder>28</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2bpp-bare.c">
-			<BuildOrder>59</BuildOrder>
+		<CppCompile Include="putty\crypto\prng.c">
+			<BuildOrder>29</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2censor.c">
-			<BuildOrder>58</BuildOrder>
+		<CppCompile Include="putty\crypto\pubkey-pem.c">
+			<BuildOrder>30</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2connection.c">
-			<BuildOrder>66</BuildOrder>
+		<CppCompile Include="putty\crypto\pubkey-ppk.c">
+			<BuildOrder>31</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2connection-client.c">
-			<BuildOrder>72</BuildOrder>
+		<CppCompile Include="putty\crypto\rsa.c">
+			<BuildOrder>32</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2kex-client.c">
-			<BuildOrder>73</BuildOrder>
+		<CppCompile Include="putty\crypto\sha1-common.c">
+			<BuildOrder>33</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2transhk.c">
-			<BuildOrder>70</BuildOrder>
+		<CppCompile Include="putty\crypto\sha1-select.c">
+			<BuildOrder>34</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2transport.c">
-			<BuildOrder>67</BuildOrder>
+		<CppCompile Include="putty\crypto\sha1-sw.c">
+			<BuildOrder>35</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssh2userauth.c">
-			<BuildOrder>68</BuildOrder>
+		<CppCompile Include="putty\crypto\sha256-common.c">
+			<BuildOrder>37</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshaes.c">
-			<BuildOrder>26</BuildOrder>
-			<BuildOrder>22</BuildOrder>
+		<CppCompile Include="putty\crypto\sha256-select.c">
+			<BuildOrder>38</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshaesold.c">
-			<BuildOrder>77</BuildOrder>
+		<CppCompile Include="putty\crypto\sha256-sw.c">
+			<BuildOrder>39</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\ssharcf.c">
-			<BuildOrder>29</BuildOrder>
-			<BuildOrder>21</BuildOrder>
+		<CppCompile Include="putty\crypto\sha3.c">
+			<BuildOrder>36</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshargon2.c">
-			<BuildOrder>29</BuildOrder>
+		<CppCompile Include="putty\crypto\sha512-common.c">
+			<BuildOrder>40</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshauxcrypt.c">
-			<BuildOrder>78</BuildOrder>
+		<CppCompile Include="putty\crypto\sha512-select.c">
+			<BuildOrder>41</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshbcrypt.c">
-			<BuildOrder>50</BuildOrder>
+		<CppCompile Include="putty\crypto\sha512-sw.c">
+			<BuildOrder>42</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshblake2.c">
-			<BuildOrder>50</BuildOrder>
+		<CppCompile Include="putty\crypto\xdmauth.c">
+			<BuildOrder>43</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshblowf.c">
-			<BuildOrder>34</BuildOrder>
-			<BuildOrder>32</BuildOrder>
+		<CppCompile Include="putty\errsock.c">
+			<BuildOrder>2</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshccp.c">
-			<BuildOrder>49</BuildOrder>
+		<CppCompile Include="putty\import.c">
+			<BuildOrder>3</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshcommon.c">
-			<BuildOrder>62</BuildOrder>
+		<CppCompile Include="putty\logging.c">
+			<BuildOrder>4</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshdes.c">
+		<CppCompile Include="putty\proxy\cproxy.c">
 			<BuildOrder>44</BuildOrder>
-			<BuildOrder>32</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshdh.c">
-			<BuildOrder>47</BuildOrder>
-			<BuildOrder>29</BuildOrder>
+		<CppCompile Include="putty\proxy\http_p.c">
+			<BuildOrder>45</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshdss.c">
-			<BuildOrder>50</BuildOrder>
-			<BuildOrder>28</BuildOrder>
+		<CppCompile Include="putty\proxy\interactor.c">
+			<BuildOrder>46</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshecc.c">
+		<CppCompile Include="putty\proxy\local.c">
+			<BuildOrder>47</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\proxy\nosshproxy.c">
 			<BuildOrder>48</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshgssc.c">
-			<BuildOrder>38</BuildOrder>
+		<CppCompile Include="putty\proxy\proxy.c">
+			<BuildOrder>50</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshhmac.c">
-			<BuildOrder>79</BuildOrder>
+		<CppCompile Include="putty\proxy\socks4.c">
+			<BuildOrder>51</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshmac.c">
-			<BuildOrder>61</BuildOrder>
+		<CppCompile Include="putty\proxy\socks5.c">
+			<BuildOrder>52</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshmd5.c">
-			<BuildOrder>53</BuildOrder>
-			<BuildOrder>31</BuildOrder>
+		<CppCompile Include="putty\proxy\telnet.c">
+			<BuildOrder>54</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshprng.c">
-			<BuildOrder>80</BuildOrder>
+		<CppCompile Include="putty\settings.c">
+			<BuildOrder>5</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshpubk.c">
+		<CppCompile Include="putty\ssh\agentf.c">
+			<BuildOrder>55</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\bpp2.c">
 			<BuildOrder>56</BuildOrder>
-			<BuildOrder>30</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshrand.c">
+		<CppCompile Include="putty\ssh\bpp-bare.c">
+			<BuildOrder>57</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\censor2.c">
+			<BuildOrder>58</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\common_p.c">
 			<BuildOrder>59</BuildOrder>
-			<BuildOrder>18</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshrsa.c">
+		<CppCompile Include="putty\ssh\connection2.c">
+			<BuildOrder>60</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\connection2-client.c">
+			<BuildOrder>61</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\gssc.c">
 			<BuildOrder>62</BuildOrder>
-			<BuildOrder>6</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshsh256.c">
+		<CppCompile Include="putty\ssh\kex2-client.c">
+			<BuildOrder>63</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\mainchan.c">
+			<BuildOrder>64</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\nosharing.c">
 			<BuildOrder>65</BuildOrder>
-			<BuildOrder>5</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshsh512.c">
-			<BuildOrder>8</BuildOrder>
+		<CppCompile Include="putty\ssh\pgssapi.c">
+			<BuildOrder>66</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\portfwd.c">
+			<BuildOrder>67</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\sharing.c">
 			<BuildOrder>68</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshsha.c">
+		<CppCompile Include="putty\ssh\ssh.c">
+			<BuildOrder>69</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\transient-hostkey-cache.c">
+			<BuildOrder>70</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\transport2.c">
 			<BuildOrder>71</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\userauth2-client.c">
+			<BuildOrder>72</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\verstring.c">
+			<BuildOrder>73</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\ssh\zlib.c">
+			<BuildOrder>75</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\sshpubk.c">
 			<BuildOrder>7</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshsha3.c">
-			<BuildOrder>42</BuildOrder>
+		<CppCompile Include="putty\sshrand.c">
+			<BuildOrder>8</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshshare.c">
-			<BuildOrder>42</BuildOrder>
+		<CppCompile Include="putty\stubs\nullplug.c">
+			<BuildOrder>76</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshutils.c">
-			<BuildOrder>63</BuildOrder>
+		<CppCompile Include="putty\utils\antispoof.c">
+			<BuildOrder>77</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshverstring.c">
-			<BuildOrder>63</BuildOrder>
+		<CppCompile Include="putty\utils\backend_socket_log.c">
+			<BuildOrder>78</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\sshzlib.c">
-			<BuildOrder>74</BuildOrder>
-			<BuildOrder>4</BuildOrder>
+		<CppCompile Include="putty\utils\base64_decode_atom.c">
+			<BuildOrder>79</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\stripctrl.c">
+		<CppCompile Include="putty\utils\base64_encode_atom.c">
+			<BuildOrder>80</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\bufchain.c">
 			<BuildOrder>81</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\tree234.c">
-			<BuildOrder>77</BuildOrder>
-			<BuildOrder>1</BuildOrder>
+		<CppCompile Include="putty\utils\burnstr.c">
+			<BuildOrder>151</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\utils.c">
-			<BuildOrder>75</BuildOrder>
+		<CppCompile Include="putty\utils\conf.c">
+			<BuildOrder>82</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\wildcard.c">
+		<CppCompile Include="putty\utils\conf_launchable.c">
 			<BuildOrder>83</BuildOrder>
-			<BuildOrder>3</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\wincapi.c">
-			<BuildOrder>83</BuildOrder>
+		<CppCompile Include="putty\utils\ctrlparse.c">
+			<BuildOrder>84</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\windefs.c">
-			<BuildOrder>83</BuildOrder>
+		<CppCompile Include="putty\utils\default_description.c">
+			<BuildOrder>85</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\wingss.c">
+		<CppCompile Include="putty\utils\dup_mb_to_wc.c">
 			<BuildOrder>86</BuildOrder>
-			<BuildOrder>2</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winhandl.c">
-			<BuildOrder>43</BuildOrder>
+		<CppCompile Include="putty\utils\dupcat.c">
+			<BuildOrder>148</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winhsock.c">
-			<BuildOrder>45</BuildOrder>
+		<CppCompile Include="putty\utils\dupprintf.c">
+			<BuildOrder>87</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\dupstr.c">
+			<BuildOrder>152</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winmisc.c">
+		<CppCompile Include="putty\utils\encode_utf8.c">
+			<BuildOrder>89</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\host_strchr.c">
 			<BuildOrder>92</BuildOrder>
-			<BuildOrder>14</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winmiscs.c">
-			<BuildOrder>76</BuildOrder>
+		<CppCompile Include="putty\utils\host_strchr_internal.c">
+			<BuildOrder>93</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winnet.c">
+		<CppCompile Include="putty\utils\host_strcspn.c">
+			<BuildOrder>90</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\host_strduptrim.c">
+			<BuildOrder>91</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\host_strrchr.c">
+			<BuildOrder>94</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\log_proxy_stderr.c">
 			<BuildOrder>95</BuildOrder>
-			<BuildOrder>17</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winnoise.c">
+		<CppCompile Include="putty\utils\make_spr_sw_abort_static.c">
+			<BuildOrder>153</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\marshal.c">
+			<BuildOrder>96</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\memory.c">
+			<BuildOrder>97</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\memxor.c">
 			<BuildOrder>98</BuildOrder>
-			<BuildOrder>16</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winnojmp.c">
-			<BuildOrder>39</BuildOrder>
+		<CppCompile Include="putty\utils\null_lp.c">
+			<BuildOrder>99</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winnpc.c">
-			<BuildOrder>39</BuildOrder>
+		<CppCompile Include="putty\utils\nullseat.c">
+			<BuildOrder>100</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winpgntc.c">
-			<BuildOrder>13</BuildOrder>
+		<CppCompile Include="putty\utils\nullstrcmp.c">
 			<BuildOrder>101</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winproxy.c">
+		<CppCompile Include="putty\utils\out_of_memory.c">
+			<BuildOrder>102</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\parse_blocksize.c">
+			<BuildOrder>103</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\prompts.c">
 			<BuildOrder>104</BuildOrder>
-			<BuildOrder>10</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winsecur.c">
-			<BuildOrder>43</BuildOrder>
+		<CppCompile Include="putty\utils\ptrlen.c">
+			<BuildOrder>105</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winstore.c">
-			<BuildOrder>9</BuildOrder>
+		<CppCompile Include="putty\utils\read_file_into.c">
+			<BuildOrder>106</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\seat_connection_fatal.c">
 			<BuildOrder>107</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\wintime.c">
-			<BuildOrder>12</BuildOrder>
+		<CppCompile Include="putty\utils\sk_free_peer_info.c">
+			<BuildOrder>108</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\smemclr.c">
+			<BuildOrder>109</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\smemeq.c">
 			<BuildOrder>110</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\windows\winucs.c">
-			<BuildOrder>52</BuildOrder>
+		<CppCompile Include="putty\utils\spr_get_error_message.c">
+			<BuildOrder>111</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\ssh2_pick_fingerprint.c">
+			<BuildOrder>112</BuildOrder>
 		</CppCompile>
-		<CppCompile Include="putty\x11fwd.c">
+		<CppCompile Include="putty\utils\sshutils.c">
 			<BuildOrder>113</BuildOrder>
-			<BuildOrder>11</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\strbuf.c">
+			<BuildOrder>114</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\string_length_for_printf.c">
+			<BuildOrder>154</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\stripctrl.c">
+			<BuildOrder>115</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\tempseat.c">
+			<BuildOrder>116</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\tree234.c">
+			<BuildOrder>117</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\version.c">
+			<BuildOrder>119</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\wildcard.c">
+			<BuildOrder>120</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\utils\write_c_string_literal.c">
+			<BuildOrder>121</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\agent-client.c">
+			<BuildOrder>127</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\gss.c">
+			<BuildOrder>128</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\handle-io.c">
+			<BuildOrder>129</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\handle-socket.c">
+			<BuildOrder>130</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\handle-wait.c">
+			<BuildOrder>151</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\local-proxy.c">
+			<BuildOrder>131</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\named-pipe-client.c">
+			<BuildOrder>132</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\network.c">
+			<BuildOrder>133</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\noise.c">
+			<BuildOrder>134</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\no-jump-list.c">
+			<BuildOrder>135</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\storage.c">
+			<BuildOrder>136</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\unicode.c">
+			<BuildOrder>137</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\agent_named_pipe_name.c">
+			<BuildOrder>138</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\cryptoapi.c">
+			<BuildOrder>139</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\defaults.c">
+			<BuildOrder>140</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\dll_hijacking_protection.c">
+			<BuildOrder>141</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\escape_registry_key.c">
+			<BuildOrder>142</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\filename.c">
+			<BuildOrder>143</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\fontspec.c">
+			<BuildOrder>144</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\get_system_dir.c">
+			<BuildOrder>147</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\get_username.c">
+			<BuildOrder>145</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\load_system32_dll.c">
+			<BuildOrder>154</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\ltime.c">
+			<BuildOrder>147</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\minefield.c">
+			<BuildOrder>148</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\open_for_write_would_lose_data.c">
+			<BuildOrder>149</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\security_p.c">
+			<BuildOrder>150</BuildOrder>
+		</CppCompile>
+		<CppCompile Include="putty\windows\utils\win_strerror.c">
+			<BuildOrder>152</BuildOrder>
 		</CppCompile>
 		<BuildConfiguration Include="Base">
 			<Key>Base</Key>

+ 40 - 23
source/core/PuttyIntf.cpp

@@ -25,7 +25,7 @@ THierarchicalStorage * PuttyStorage = NULL;
 //---------------------------------------------------------------------------
 extern "C"
 {
-#include <winstuff.h>
+#include <windows\platform.h>
 }
 const UnicodeString OriginalPuttyRegistryStorageKey(_T(PUTTY_REG_POS));
 const UnicodeString KittyRegistryStorageKey(L"Software\\9bis.com\\KiTTY");
@@ -147,14 +147,14 @@ extern "C" const char * do_select(Plug * plug, SOCKET skt, bool enable)
   return NULL;
 }
 //---------------------------------------------------------------------------
-static size_t output(Seat * seat, bool is_stderr, const void * data, size_t len)
+static size_t output(Seat * seat, SeatOutputType type, const void * data, size_t len)
 {
   TSecureShell * SecureShell = static_cast<ScpSeat *>(seat)->SecureShell;
-  if (static_cast<int>(static_cast<char>(is_stderr)) == -1)
+  if (static_cast<int>(static_cast<char>(type)) == -1)
   {
     SecureShell->CWrite(reinterpret_cast<const char *>(data), len);
   }
-  else if (!is_stderr)
+  else if (type != SEAT_OUTPUT_STDERR)
   {
     SecureShell->FromBackend(reinterpret_cast<const unsigned char *>(data), len);
   }
@@ -170,13 +170,13 @@ static bool eof(Seat *)
   return false;
 }
 //---------------------------------------------------------------------------
-static int get_userpass_input(Seat * seat, prompts_t * p, bufchain * DebugUsedArg(input))
+static SeatPromptResult get_userpass_input(Seat * seat, prompts_t * p)
 {
   DebugAssert(p != NULL);
   TSecureShell * SecureShell = static_cast<ScpSeat *>(seat)->SecureShell;
   DebugAssert(SecureShell != NULL);
 
-  int Result;
+  SeatPromptResult Result;
   TStrings * Prompts = new TStringList();
   TStrings * Results = new TStringList();
   try
@@ -223,11 +223,11 @@ static int get_userpass_input(Seat * seat, prompts_t * p, bufchain * DebugUsedAr
         }
         prompt_set_result(Prompt, S.c_str());
       }
-      Result = 1;
+      Result = SPR_OK;
     }
     else
     {
-      Result = 0;
+      Result = SPR_USER_ABORT;
     }
   }
   __finally
@@ -246,9 +246,9 @@ static void connection_fatal(Seat * seat, const char * message)
   SecureShell->PuttyFatalError(UnicodeString(AnsiString(message)));
 }
 //---------------------------------------------------------------------------
-int verify_ssh_host_key(Seat * seat, const char * host, int port, const char * keytype,
-  char * keystr, const char * DebugUsedArg(keydisp), char ** key_fingerprints,
-  void (*DebugUsedArg(callback))(void *ctx, int result), void * DebugUsedArg(ctx))
+SeatPromptResult confirm_ssh_host_key(Seat * seat, const char * host, int port, const char * keytype,
+  char * keystr, const char * DebugUsedArg(keydisp), char ** key_fingerprints, bool DebugUsedArg(mismatch),
+  void (*DebugUsedArg(callback))(void *ctx, SeatPromptResult result), void * DebugUsedArg(ctx))
 {
   UnicodeString FingerprintSHA256, FingerprintMD5;
   if (key_fingerprints[SSH_FPTYPE_SHA256] != NULL)
@@ -263,7 +263,7 @@ int verify_ssh_host_key(Seat * seat, const char * host, int port, const char * k
   SecureShell->VerifyHostKey(host, port, keytype, keystr, FingerprintSHA256, FingerprintMD5);
 
   // We should return 0 when key was not confirmed, we throw exception instead.
-  return 1;
+  return SPR_OK;
 }
 //---------------------------------------------------------------------------
 bool have_ssh_host_key(Seat * seat, const char * hostname, int port,
@@ -273,20 +273,20 @@ bool have_ssh_host_key(Seat * seat, const char * hostname, int port,
   return SecureShell->HaveHostKey(hostname, port, keytype) ? 1 : 0;
 }
 //---------------------------------------------------------------------------
-int confirm_weak_crypto_primitive(Seat * seat, const char * algtype, const char * algname,
-  void (*/*callback*/)(void * ctx, int result), void * /*ctx*/)
+SeatPromptResult confirm_weak_crypto_primitive(Seat * seat, const char * algtype, const char * algname,
+  void (*/*callback*/)(void * ctx, SeatPromptResult result), void * /*ctx*/)
 {
   TSecureShell * SecureShell = static_cast<ScpSeat *>(seat)->SecureShell;
   SecureShell->AskAlg(algtype, algname);
 
   // We should return 0 when alg was not confirmed, we throw exception instead.
-  return 1;
+  return SPR_OK;
 }
 //---------------------------------------------------------------------------
-int confirm_weak_cached_hostkey(Seat *, const char * /*algname*/, const char * /*betteralgs*/,
-  void (*/*callback*/)(void *ctx, int result), void * /*ctx*/)
+SeatPromptResult confirm_weak_cached_hostkey(Seat *, const char * /*algname*/, const char * /*betteralgs*/,
+  void (*/*callback*/)(void *ctx, SeatPromptResult result), void * /*ctx*/)
 {
-  return 1;
+  return SPR_OK;
 }
 //---------------------------------------------------------------------------
 void old_keyfile_warning(void)
@@ -294,11 +294,22 @@ void old_keyfile_warning(void)
   // no reference to TSecureShell instance available
 }
 //---------------------------------------------------------------------------
-void display_banner(Seat * seat, const char * banner, int size)
+size_t banner(Seat * seat, const void * data, size_t len)
 {
   TSecureShell * SecureShell = static_cast<ScpSeat *>(seat)->SecureShell;
-  UnicodeString Banner(UTF8String(banner, size));
+  UnicodeString Banner(UTF8String(static_cast<const char *>(data), len));
   SecureShell->DisplayBanner(Banner);
+  return 0; // PuTTY never uses the value
+}
+//---------------------------------------------------------------------------
+uintmax_t strtoumax(const char *nptr, char **endptr, int base)
+{
+  if (DebugAlwaysFalse(endptr != NULL) ||
+      DebugAlwaysFalse(base == 10))
+  {
+    Abort();
+  }
+  return StrToInt64(UnicodeString(AnsiString(nptr)));
 }
 //---------------------------------------------------------------------------
 static void SSHFatalError(const char * Format, va_list Param)
@@ -384,13 +395,17 @@ static const SeatVtable ScpSeatVtable =
   {
     output,
     eof,
+    nullseat_sent,
+    banner,
     get_userpass_input,
+    nullseat_notify_session_started,
     nullseat_notify_remote_exit,
+    nullseat_notify_remote_disconnect,
     connection_fatal,
     nullseat_update_specials_menu,
     nullseat_get_ttymode,
     nullseat_set_busy_status,
-    verify_ssh_host_key,
+    confirm_ssh_host_key,
     confirm_weak_crypto_primitive,
     confirm_weak_cached_hostkey,
     nullseat_is_always_utf8,
@@ -399,7 +414,9 @@ static const SeatVtable ScpSeatVtable =
     nullseat_get_windowid,
     nullseat_get_window_pixel_size,
     nullseat_stripctrl_new,
-    nullseat_set_trust_status_vacuously,
+    nullseat_set_trust_status,
+    nullseat_can_set_trust_status_yes,
+    nullseat_has_mixed_input_stream_yes,
     nullseat_verbose_yes,
     nullseat_interactive_no,
     nullseat_get_cursor_position,
@@ -989,7 +1006,7 @@ UnicodeString __fastcall ParseOpenSshPubLine(const UnicodeString & Line, const s
 UnicodeString __fastcall GetKeyTypeHuman(const UnicodeString & KeyType)
 {
   UnicodeString Result;
-  if (KeyType == ssh_dss.cache_id)
+  if (KeyType == ssh_dsa.cache_id)
   {
     Result = L"DSA";
   }

+ 1 - 1
source/core/PuttyIntf.h

@@ -18,7 +18,7 @@ extern "C"
 #include <ssh.h>
 #undef new
 #include <puttyexp.h>
-#include <proxy.h>
+#include <proxy\proxy.h>
 #include <storage.h>
 // Defined in misc.h - Conflicts with std::min/max
 #undef min

+ 34 - 16
source/core/SecureShell.cpp

@@ -77,13 +77,11 @@ __fastcall TSecureShell::TSecureShell(TSessionUI* UI,
   FWaitingForData = 0;
   FCallbackSet.reset(new callback_set());
   memset(FCallbackSet.get(), 0, sizeof(callback_set));
-  FCallbackSet->handles_by_evtomain = new_handles_by_evtomain();
+  FCallbackSet->ready_event = INVALID_HANDLE_VALUE;
 }
 //---------------------------------------------------------------------------
 __fastcall TSecureShell::~TSecureShell()
 {
-  freetree234(FCallbackSet->handles_by_evtomain);
-  FCallbackSet->handles_by_evtomain = NULL;
   DebugAssert(FWaiting == 0);
   Active = false;
   ResetConnection();
@@ -1339,7 +1337,8 @@ void __fastcall TSecureShell::DispatchSendBuffer(int BufSize)
 void __fastcall TSecureShell::Send(const unsigned char * Buf, Integer Len)
 {
   CheckConnection();
-  int BufSize = backend_send(FBackendHandle, const_cast<char *>(reinterpret_cast<const char *>(Buf)), Len);
+  backend_send(FBackendHandle, const_cast<char *>(reinterpret_cast<const char *>(Buf)), Len);
+  int BufSize = backend_sendbuffer(FBackendHandle);
   if (Configuration->ActualLogProtocol >= 1)
   {
     LogEvent(FORMAT(L"Sent %u bytes", (static_cast<int>(Len))));
@@ -1697,6 +1696,25 @@ void __fastcall TSecureShell::FreeBackend()
       sfree(FCallbackSet->pktin_freeq_head);
       FCallbackSet->pktin_freeq_head = NULL;
     }
+
+    if (FCallbackSet->handlewaits_tree_real != NULL)
+    {
+      DebugAssert(count234(FCallbackSet->handlewaits_tree_real) <= 1);
+      while (count234(FCallbackSet->handlewaits_tree_real) > 0)
+      {
+        HandleWait * AHandleWait = static_cast<HandleWait *>(index234(FCallbackSet->handlewaits_tree_real, 0));
+        delete_handle_wait(FCallbackSet.get(), AHandleWait);
+      }
+
+      freetree234(FCallbackSet->handlewaits_tree_real);
+      FCallbackSet->handlewaits_tree_real = NULL;
+    }
+
+    if (FCallbackSet->ready_event != INVALID_HANDLE_VALUE)
+    {
+      CloseHandle(FCallbackSet->ready_event);
+      FCallbackSet->ready_event = INVALID_HANDLE_VALUE;
+    }
   }
 }
 //---------------------------------------------------------------------------
@@ -1982,8 +2000,8 @@ bool __fastcall TSecureShell::EventSelectLoop(unsigned int MSec, bool ReadEventR
       LogEvent(L"Looking for network events");
     }
     unsigned int TicksBefore = GetTickCount();
-    int HandleCount;
-    HANDLE * Handles = NULL;
+    HandleWaitList * WaitList = NULL;
+
     try
     {
       unsigned int Timeout = MSec;
@@ -1998,15 +2016,15 @@ bool __fastcall TSecureShell::EventSelectLoop(unsigned int MSec, bool ReadEventR
           TimeoutStep = 0;
         }
         Timeout -= TimeoutStep;
-        if (Handles != NULL)
+        if (WaitList != NULL)
         {
-          sfree(Handles);
+          handle_wait_list_free(WaitList);
         }
         // It returns only busy handles, so the set can change with every call to run_toplevel_callbacks.
-        Handles = handle_get_events(FCallbackSet->handles_by_evtomain, &HandleCount);
-        Handles = sresize(Handles, HandleCount + 1, HANDLE);
-        Handles[HandleCount] = FSocketEvent;
-        WaitResult = WaitForMultipleObjects(HandleCount + 1, Handles, FALSE, TimeoutStep);
+        WaitList = get_handle_wait_list(FCallbackSet.get());
+        DebugAssert(WaitList->nhandles < MAXIMUM_WAIT_OBJECTS);
+        WaitList->handles[WaitList->nhandles] = FSocketEvent;
+        WaitResult = WaitForMultipleObjects(WaitList->nhandles + 1, WaitList->handles, FALSE, TimeoutStep);
         FUI->ProcessGUI();
         // run_toplevel_callbacks can cause processing of pending raw data, so:
         // 1) Check for changes in our pending buffer - wait criteria in Receive()
@@ -2022,14 +2040,14 @@ bool __fastcall TSecureShell::EventSelectLoop(unsigned int MSec, bool ReadEventR
         }
       } while ((WaitResult == WAIT_TIMEOUT) && (Timeout > 0) && !Result);
 
-      if (WaitResult < WAIT_OBJECT_0 + HandleCount)
+      if (WaitResult < WAIT_OBJECT_0 + WaitList->nhandles)
       {
-        if (handle_got_event(FCallbackSet->handles_by_evtomain, Handles[WaitResult - WAIT_OBJECT_0]))
+        if (handle_wait_activate(FCallbackSet.get(), WaitList, WaitResult - WAIT_OBJECT_0))
         {
           Result = true;
         }
       }
-      else if (WaitResult == WAIT_OBJECT_0 + HandleCount)
+      else if (WaitResult == WAIT_OBJECT_0 + WaitList->nhandles)
       {
         if (Configuration->ActualLogProtocol >= 1)
         {
@@ -2081,7 +2099,7 @@ bool __fastcall TSecureShell::EventSelectLoop(unsigned int MSec, bool ReadEventR
     }
     __finally
     {
-      sfree(Handles);
+      handle_wait_list_free(WaitList);
     }
 
 

+ 14 - 0
source/putty/crypto/aes-common.c

@@ -0,0 +1,14 @@
+/*
+ * Common variable definitions across all the AES implementations.
+ */
+
+#include "ssh.h"
+#include "aes.h"
+
+const uint8_t aes_key_setup_round_constants[10] = {
+    /* The first few powers of X in GF(2^8), used during key setup.
+     * This can safely be a lookup table without side channel risks,
+     * because key setup iterates through it once in a standard way
+     * regardless of the key. */
+    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
+};

+ 316 - 0
source/putty/crypto/aes-ni.c

@@ -0,0 +1,316 @@
+/*
+ * Hardware-accelerated implementation of AES using x86 AES-NI.
+ */
+
+#include "ssh.h"
+#include "aes.h"
+
+#ifndef WINSCP_VS
+bool aes_ni_available(void);
+ssh_cipher *aes_ni_new(const ssh_cipheralg *alg);
+void aes_ni_free(ssh_cipher *ciph);
+void aes_ni_setiv_cbc(ssh_cipher *ciph, const void *iv);
+void aes_ni_setkey(ssh_cipher *ciph, const void *vkey);
+void aes_ni_setiv_sdctr(ssh_cipher *ciph, const void *iv);
+#define NI_ENC_DEC_H(len)                                               \
+    void aes##len##_ni_cbc_encrypt(                                     \
+        ssh_cipher *ciph, void *vblk, int blklen);                      \
+    void aes##len##_ni_cbc_decrypt(                                     \
+        ssh_cipher *ciph, void *vblk, int blklen);                      \
+    void aes##len##_ni_sdctr(                                           \
+        ssh_cipher *ciph, void *vblk, int blklen);                      \
+
+NI_ENC_DEC_H(128)
+NI_ENC_DEC_H(192)
+NI_ENC_DEC_H(256)
+#else
+
+#include <wmmintrin.h>
+#include <smmintrin.h>
+
+#if defined(__clang__) || defined(__GNUC__)
+#include <cpuid.h>
+#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3])
+#else
+#define GET_CPU_ID(out) __cpuid(out, 1)
+#endif
+
+/*static WINSCP*/ bool aes_ni_available(void)
+{
+    /*
+     * Determine if AES is available on this CPU, by checking that
+     * both AES itself and SSE4.1 are supported.
+     */
+    unsigned int CPUInfo[4];
+    GET_CPU_ID(CPUInfo);
+    return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19));
+}
+
+/*
+ * Core AES-NI encrypt/decrypt functions, one per length and direction.
+ */
+
+#define NI_CIPHER(len, dir, dirlong, repmacro)                          \
+    static inline __m128i aes_ni_##len##_##dir(                         \
+        __m128i v, const __m128i *keysched)                             \
+    {                                                                   \
+        v = _mm_xor_si128(v, *keysched++);                              \
+        repmacro(v = _mm_aes##dirlong##_si128(v, *keysched++););        \
+        return _mm_aes##dirlong##last_si128(v, *keysched);              \
+    }
+
+NI_CIPHER(128, e, enc, REP9)
+NI_CIPHER(128, d, dec, REP9)
+NI_CIPHER(192, e, enc, REP11)
+NI_CIPHER(192, d, dec, REP11)
+NI_CIPHER(256, e, enc, REP13)
+NI_CIPHER(256, d, dec, REP13)
+
+/*
+ * The main key expansion.
+ */
+static void aes_ni_key_expand(
+    const unsigned char *key, size_t key_words,
+    __m128i *keysched_e, __m128i *keysched_d)
+{
+    size_t rounds = key_words + 6;
+    size_t sched_words = (rounds + 1) * 4;
+
+    /*
+     * Store the key schedule as 32-bit integers during expansion, so
+     * that it's easy to refer back to individual previous words. We
+     * collect them into the final __m128i form at the end.
+     */
+    uint32_t sched[MAXROUNDKEYS * 4];
+
+    unsigned rconpos = 0;
+
+    for (size_t i = 0; i < sched_words; i++) {
+        if (i < key_words) {
+            sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i);
+        } else {
+            uint32_t temp = sched[i - 1];
+
+            bool rotate_and_round_constant = (i % key_words == 0);
+            bool only_sub = (key_words == 8 && i % 8 == 4);
+
+            if (rotate_and_round_constant) {
+                __m128i v = _mm_setr_epi32(0,temp,0,0);
+                v = _mm_aeskeygenassist_si128(v, 0);
+                temp = _mm_extract_epi32(v, 1);
+
+                assert(rconpos < lenof(aes_key_setup_round_constants));
+                temp ^= aes_key_setup_round_constants[rconpos++];
+            } else if (only_sub) {
+                __m128i v = _mm_setr_epi32(0,temp,0,0);
+                v = _mm_aeskeygenassist_si128(v, 0);
+                temp = _mm_extract_epi32(v, 0);
+            }
+
+            sched[i] = sched[i - key_words] ^ temp;
+        }
+    }
+
+    /*
+     * Combine the key schedule words into __m128i vectors and store
+     * them in the output context.
+     */
+    for (size_t round = 0; round <= rounds; round++)
+        keysched_e[round] = _mm_setr_epi32(
+            sched[4*round  ], sched[4*round+1],
+            sched[4*round+2], sched[4*round+3]);
+
+    smemclr(sched, sizeof(sched));
+
+    /*
+     * Now prepare the modified keys for the inverse cipher.
+     */
+    for (size_t eround = 0; eround <= rounds; eround++) {
+        size_t dround = rounds - eround;
+        __m128i rkey = keysched_e[eround];
+        if (eround && dround)      /* neither first nor last */
+            rkey = _mm_aesimc_si128(rkey);
+        keysched_d[dround] = rkey;
+    }
+}
+
+// WINSCP
+// WORKAROUND
+// Cannot use _mm_setr_epi* - it results in the constant being stored in .rdata segment.
+// objconv reports:
+// Warning 1060: Different alignments specified for same segment, %s. Using highest alignment.rdata
+// Despite that the code crashes.
+// This macro is based on:
+// Based on https://stackoverflow.com/q/35268036/850848
+#define _MM_SETR_EPI8(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af) \
+    { (char)a0, (char)a1, (char)a2, (char)a3, (char)a4, (char)a5, (char)a6, (char)a7, \
+      (char)a8, (char)a9, (char)aa, (char)ab, (char)ac, (char)ad, (char)ae, (char)af }
+
+/*
+ * Auxiliary routine to increment the 128-bit counter used in SDCTR
+ * mode.
+ */
+static inline __m128i aes_ni_sdctr_increment(__m128i v)
+{
+    const __m128i ONE = _MM_SETR_EPI8(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); // WINSCP
+    const __m128i ZERO = _mm_setzero_si128();
+
+    /* Increment the low-order 64 bits of v */
+    v  = _mm_add_epi64(v, ONE);
+    /* Check if they've become zero */
+    __m128i cmp = _mm_cmpeq_epi64(v, ZERO);
+    /* If so, the low half of cmp is all 1s. Pack that into the high
+     * half of addend with zero in the low half. */
+    __m128i addend = _mm_unpacklo_epi64(ZERO, cmp);
+    /* And subtract that from v, which increments the high 64 bits iff
+     * the low 64 wrapped round. */
+    v = _mm_sub_epi64(v, addend);
+
+    return v;
+}
+
+/*
+ * Auxiliary routine to reverse the byte order of a vector, so that
+ * the SDCTR IV can be made big-endian for feeding to the cipher.
+ */
+static inline __m128i aes_ni_sdctr_reverse(__m128i v)
+{
+    const __m128i R = _MM_SETR_EPI8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0); // WINSCP
+    v = _mm_shuffle_epi8(
+        v, R); // WINSCP
+    return v;
+}
+
+/*
+ * The SSH interface and the cipher modes.
+ */
+
+typedef struct aes_ni_context aes_ni_context;
+struct aes_ni_context {
+    __m128i keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv;
+
+    void *pointer_to_free;
+    ssh_cipher ciph;
+};
+
+/*static WINSCP*/ ssh_cipher *aes_ni_new(const ssh_cipheralg *alg)
+{
+    const struct aes_extra *extra = (const struct aes_extra *)alg->extra;
+    if (!check_availability(extra))
+        return NULL;
+
+    /*
+     * The __m128i variables in the context structure need to be
+     * 16-byte aligned, but not all malloc implementations that this
+     * code has to work with will guarantee to return a 16-byte
+     * aligned pointer. So we over-allocate, manually realign the
+     * pointer ourselves, and store the original one inside the
+     * context so we know how to free it later.
+     */
+    void *allocation = smalloc(sizeof(aes_ni_context) + 15);
+    uintptr_t alloc_address = (uintptr_t)allocation;
+    uintptr_t aligned_address = (alloc_address + 15) & ~15;
+    aes_ni_context *ctx = (aes_ni_context *)aligned_address;
+
+    ctx->ciph.vt = alg;
+    ctx->pointer_to_free = allocation;
+    return &ctx->ciph;
+}
+
+/*static WINSCP*/ void aes_ni_free(ssh_cipher *ciph)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+    void *allocation = ctx->pointer_to_free;
+    smemclr(ctx, sizeof(*ctx));
+    sfree(allocation);
+}
+
+/*static WINSCP*/ void aes_ni_setkey(ssh_cipher *ciph, const void *vkey)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+    const unsigned char *key = (const unsigned char *)vkey;
+
+    aes_ni_key_expand(key, ctx->ciph.vt->real_keybits / 32,
+                      ctx->keysched_e, ctx->keysched_d);
+}
+
+/*static WINSCP*/ void aes_ni_setiv_cbc(ssh_cipher *ciph, const void *iv)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+    ctx->iv = _mm_loadu_si128(iv);
+}
+
+/*static WINSCP*/ void aes_ni_setiv_sdctr(ssh_cipher *ciph, const void *iv)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+    __m128i counter = _mm_loadu_si128(iv);
+    ctx->iv = aes_ni_sdctr_reverse(counter);
+}
+
+typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched);
+
+static inline void aes_cbc_ni_encrypt(
+    ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+
+    for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+         blk < finish; blk += 16) {
+        __m128i plaintext = _mm_loadu_si128((const __m128i *)blk);
+        __m128i cipher_input = _mm_xor_si128(plaintext, ctx->iv);
+        __m128i ciphertext = encrypt(cipher_input, ctx->keysched_e);
+        _mm_storeu_si128((__m128i *)blk, ciphertext);
+        ctx->iv = ciphertext;
+    }
+}
+
+static inline void aes_cbc_ni_decrypt(
+    ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn decrypt)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+
+    for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+         blk < finish; blk += 16) {
+        __m128i ciphertext = _mm_loadu_si128((const __m128i *)blk);
+        __m128i decrypted = decrypt(ciphertext, ctx->keysched_d);
+        __m128i plaintext = _mm_xor_si128(decrypted, ctx->iv);
+        _mm_storeu_si128((__m128i *)blk, plaintext);
+        ctx->iv = ciphertext;
+    }
+}
+
+static inline void aes_sdctr_ni(
+    ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt)
+{
+    aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+
+    for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+         blk < finish; blk += 16) {
+        __m128i counter = aes_ni_sdctr_reverse(ctx->iv);
+        __m128i keystream = encrypt(counter, ctx->keysched_e);
+        __m128i input = _mm_loadu_si128((const __m128i *)blk);
+        __m128i output = _mm_xor_si128(input, keystream);
+        _mm_storeu_si128((__m128i *)blk, output);
+        ctx->iv = aes_ni_sdctr_increment(ctx->iv);
+    }
+}
+
+#define NI_ENC_DEC(len)                                                 \
+    /*static WINSCP*/ void aes##len##_ni_cbc_encrypt(                              \
+        ssh_cipher *ciph, void *vblk, int blklen)                       \
+    { aes_cbc_ni_encrypt(ciph, vblk, blklen, aes_ni_##len##_e); }       \
+    /*static WINSCP*/ void aes##len##_ni_cbc_decrypt(                              \
+        ssh_cipher *ciph, void *vblk, int blklen)                       \
+    { aes_cbc_ni_decrypt(ciph, vblk, blklen, aes_ni_##len##_d); }       \
+    /*static WINSCP*/ void aes##len##_ni_sdctr(                                    \
+        ssh_cipher *ciph, void *vblk, int blklen)                       \
+    { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); }             \
+
+NI_ENC_DEC(128)
+NI_ENC_DEC(192)
+NI_ENC_DEC(256)
+
+#endif // WINSCP_VS
+
+AES_EXTRA(_ni);
+AES_ALL_VTABLES(_ni, "AES-NI accelerated");

+ 99 - 0
source/putty/crypto/aes-select.c

@@ -0,0 +1,99 @@
+/*
+ * Top-level vtables to select an AES implementation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "aes.h"
+
+static ssh_cipher *aes_select(const ssh_cipheralg *alg)
+{
+    const ssh_cipheralg *const *real_algs = (const ssh_cipheralg **)alg->extra;
+
+    { // WINSCP
+    size_t i;
+    for (i = 0; real_algs[i]; i++) {
+        const ssh_cipheralg *alg = real_algs[i];
+        const struct aes_extra *alg_extra =
+            (const struct aes_extra *)alg->extra;
+        if (check_availability(alg_extra))
+            return ssh_cipher_new(alg);
+    }
+
+    /* We should never reach the NULL at the end of the list, because
+     * the last non-NULL entry should be software-only AES, which is
+     * always available. */
+    unreachable("aes_select ran off the end of its list");
+    } // WINSCP
+}
+
+#if HAVE_AES_NI
+#define IF_NI(...) __VA_ARGS__
+#else
+#define IF_NI(...)
+#endif
+
+#if HAVE_NEON_CRYPTO
+#define IF_NEON(...) __VA_ARGS__
+#else
+#define IF_NEON(...)
+#endif
+
+#define AES_SELECTOR_VTABLE(mode_c, mode_protocol, mode_display, bits)  \
+    static const ssh_cipheralg *                                        \
+    ssh_aes ## bits ## _ ## mode_c ## _impls[] = {                      \
+        IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,)                  \
+        IF_NEON(&ssh_aes ## bits ## _ ## mode_c ## _neon,)              \
+        &ssh_aes ## bits ## _ ## mode_c ## _sw,                         \
+        NULL,                                                           \
+    };                                                                  \
+    const ssh_cipheralg ssh_aes ## bits ## _ ## mode_c = {              \
+        /* WINSCP */ \
+        /*.new =*/ aes_select,                                              \
+        NULL, NULL, NULL, NULL, NULL, NULL, NULL, \
+        /*.ssh2_id =*/ "aes" #bits "-" mode_protocol,                       \
+        /*.blksize =*/ 16,                                                  \
+        /*.real_keybits =*/ bits,                                           \
+        /*.padded_keybytes =*/ bits/8,                                      \
+        0, \
+        /*.text_name =*/ "AES-" #bits " " mode_display                      \
+        " (dummy selector vtable)",                                     \
+        NULL, \
+        /*.extra =*/ ssh_aes ## bits ## _ ## mode_c ## _impls,              \
+    }
+
+AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 128);
+AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 192);
+AES_SELECTOR_VTABLE(cbc, "cbc", "CBC", 256);
+AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 128);
+AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 192);
+AES_SELECTOR_VTABLE(sdctr, "ctr", "SDCTR", 256);
+
+static const ssh_cipheralg ssh_rijndael_lysator = {
+    /* Same as aes256_cbc, but with a different protocol ID */
+    /*.new =*/ aes_select,
+    NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+    /*.ssh2_id =*/ "[email protected]",
+    /*.blksize =*/ 16,
+    /*.real_keybits =*/ 256,
+    /*.padded_keybytes =*/ 256/8,
+    0,
+    /*.text_name =*/ "AES-256 CBC (dummy selector vtable)",
+    NULL,
+    /*.extra =*/ ssh_aes256_cbc_impls,
+};
+
+static const ssh_cipheralg *const aes_list[] = {
+    &ssh_aes256_sdctr,
+    &ssh_aes256_cbc,
+    &ssh_rijndael_lysator,
+    &ssh_aes192_sdctr,
+    &ssh_aes192_cbc,
+    &ssh_aes128_sdctr,
+    &ssh_aes128_cbc,
+};
+
+const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list };

+ 1111 - 0
source/putty/crypto/aes-sw.c

@@ -0,0 +1,1111 @@
+/*
+ * Software implementation of AES.
+ *
+ * This implementation uses a bit-sliced representation. Instead of
+ * the obvious approach of storing the cipher state so that each byte
+ * (or field element, or entry in the cipher matrix) occupies 8
+ * contiguous bits in a machine integer somewhere, we organise the
+ * cipher state as an array of 8 integers, in such a way that each
+ * logical byte of the cipher state occupies one bit in each integer,
+ * all at the same position. This allows us to do parallel logic on
+ * all bytes of the state by doing bitwise operations between the 8
+ * integers; in particular, the S-box (SubBytes) lookup is done this
+ * way, which takes about 110 operations - but for those 110 bitwise
+ * ops you get 64 S-box lookups, not just one.
+ */
+
+#include "ssh.h"
+#include "aes.h"
+#include "mpint_i.h"               /* we reuse the BignumInt system */
+
+static bool aes_sw_available(void)
+{
+    /* Software AES is always available */
+    return true;
+}
+
+#define SLICE_PARALLELISM (BIGNUM_INT_BYTES / 2)
+
+#ifdef WINSCP_VS
+
+#ifdef BITSLICED_DEBUG
+/* Dump function that undoes the bitslicing transform, so you can see
+ * the logical data represented by a set of slice words. */
+static inline void dumpslices_uint16_t(
+    const char *prefix, const uint16_t slices[8])
+{
+    printf("%-30s", prefix);
+    for (unsigned byte = 0; byte < 16; byte++) {
+        unsigned byteval = 0;
+        for (unsigned bit = 0; bit < 8; bit++)
+            byteval |= (1 & (slices[bit] >> byte)) << bit;
+        printf("%02x", byteval);
+    }
+    printf("\n");
+}
+
+static inline void dumpslices_BignumInt(
+    const char *prefix, const BignumInt slices[8])
+{
+    printf("%-30s", prefix);
+    for (unsigned iter = 0; iter < SLICE_PARALLELISM; iter++) {
+        for (unsigned byte = 0; byte < 16; byte++) {
+            unsigned byteval = 0;
+            for (unsigned bit = 0; bit < 8; bit++)
+                byteval |= (1 & (slices[bit] >> (iter*16+byte))) << bit;
+            printf("%02x", byteval);
+        }
+        if (iter+1 < SLICE_PARALLELISM)
+            printf(" ");
+    }
+    printf("\n");
+}
+#else
+#define dumpslices_uintN_t(prefix, slices) ((void)0)
+#define dumpslices_BignumInt(prefix, slices) ((void)0)
+#endif
+
+/* -----
+ * Bit-slicing transformation: convert between an array of 16 uint8_t
+ * and an array of 8 uint16_t, so as to interchange the bit index
+ * within each element and the element index within the array. (That
+ * is, bit j of input[i] == bit i of output[j].
+ */
+
+#define SWAPWORDS(shift) do                                     \
+    {                                                           \
+        uint64_t mask = ~(uint64_t)0 / ((1ULL << shift) + 1);   \
+        uint64_t diff = ((i0 >> shift) ^ i1) & mask;            \
+        i0 ^= diff << shift;                                    \
+        i1 ^= diff;                                             \
+    } while (0)
+
+#define SWAPINWORD(i, bigshift, smallshift) do                  \
+    {                                                           \
+        uint64_t mask = ~(uint64_t)0;                           \
+        mask /= ((1ULL << bigshift) + 1);                       \
+        mask /= ((1ULL << smallshift) + 1);                     \
+        mask <<= smallshift;                                    \
+        unsigned shift = bigshift - smallshift;                 \
+        uint64_t diff = ((i >> shift) ^ i) & mask;              \
+        i ^= diff ^ (diff << shift);                            \
+    } while (0)
+
+#define TO_BITSLICES(slices, bytes, uintN_t, assign_op, shift) do       \
+    {                                                                   \
+        uint64_t i0 = GET_64BIT_LSB_FIRST(bytes);                       \
+        uint64_t i1 = GET_64BIT_LSB_FIRST(bytes + 8);                   \
+        SWAPINWORD(i0, 8, 1);                                           \
+        SWAPINWORD(i1, 8, 1);                                           \
+        SWAPINWORD(i0, 16, 2);                                          \
+        SWAPINWORD(i1, 16, 2);                                          \
+        SWAPINWORD(i0, 32, 4);                                          \
+        SWAPINWORD(i1, 32, 4);                                          \
+        SWAPWORDS(8);                                                   \
+        slices[0] assign_op (uintN_t)((i0 >>  0) & 0xFFFF) << (shift);  \
+        slices[2] assign_op (uintN_t)((i0 >> 16) & 0xFFFF) << (shift);  \
+        slices[4] assign_op (uintN_t)((i0 >> 32) & 0xFFFF) << (shift);  \
+        slices[6] assign_op (uintN_t)((i0 >> 48) & 0xFFFF) << (shift);  \
+        slices[1] assign_op (uintN_t)((i1 >>  0) & 0xFFFF) << (shift);  \
+        slices[3] assign_op (uintN_t)((i1 >> 16) & 0xFFFF) << (shift);  \
+        slices[5] assign_op (uintN_t)((i1 >> 32) & 0xFFFF) << (shift);  \
+        slices[7] assign_op (uintN_t)((i1 >> 48) & 0xFFFF) << (shift);  \
+    } while (0)
+
+#define FROM_BITSLICES(bytes, slices, shift) do                 \
+    {                                                           \
+        uint64_t i1 = ((slices[7] >> (shift)) & 0xFFFF);        \
+        i1 = (i1 << 16) | ((slices[5] >> (shift)) & 0xFFFF);    \
+        i1 = (i1 << 16) | ((slices[3] >> (shift)) & 0xFFFF);    \
+        i1 = (i1 << 16) | ((slices[1] >> (shift)) & 0xFFFF);    \
+        uint64_t i0 = ((slices[6] >> (shift)) & 0xFFFF);        \
+        i0 = (i0 << 16) | ((slices[4] >> (shift)) & 0xFFFF);    \
+        i0 = (i0 << 16) | ((slices[2] >> (shift)) & 0xFFFF);    \
+        i0 = (i0 << 16) | ((slices[0] >> (shift)) & 0xFFFF);    \
+        SWAPWORDS(8);                                           \
+        SWAPINWORD(i0, 32, 4);                                  \
+        SWAPINWORD(i1, 32, 4);                                  \
+        SWAPINWORD(i0, 16, 2);                                  \
+        SWAPINWORD(i1, 16, 2);                                  \
+        SWAPINWORD(i0, 8, 1);                                   \
+        SWAPINWORD(i1, 8, 1);                                   \
+        PUT_64BIT_LSB_FIRST(bytes, i0);                         \
+        PUT_64BIT_LSB_FIRST((bytes) + 8, i1);                   \
+    } while (0)
+
+/* -----
+ * Some macros that will be useful repeatedly.
+ */
+
+/* Iterate a unary transformation over all 8 slices. */
+#define ITERATE(MACRO, output, input, uintN_t) do       \
+    {                                                   \
+        MACRO(output[0], input[0], uintN_t);            \
+        MACRO(output[1], input[1], uintN_t);            \
+        MACRO(output[2], input[2], uintN_t);            \
+        MACRO(output[3], input[3], uintN_t);            \
+        MACRO(output[4], input[4], uintN_t);            \
+        MACRO(output[5], input[5], uintN_t);            \
+        MACRO(output[6], input[6], uintN_t);            \
+        MACRO(output[7], input[7], uintN_t);            \
+    } while (0)
+
+/* Simply add (i.e. XOR) two whole sets of slices together. */
+#define BITSLICED_ADD(output, lhs, rhs) do      \
+    {                                           \
+        output[0] = lhs[0] ^ rhs[0];            \
+        output[1] = lhs[1] ^ rhs[1];            \
+        output[2] = lhs[2] ^ rhs[2];            \
+        output[3] = lhs[3] ^ rhs[3];            \
+        output[4] = lhs[4] ^ rhs[4];            \
+        output[5] = lhs[5] ^ rhs[5];            \
+        output[6] = lhs[6] ^ rhs[6];            \
+        output[7] = lhs[7] ^ rhs[7];            \
+    } while (0)
+
+/* -----
+ * The AES S-box, in pure bitwise logic so that it can be run in
+ * parallel on whole words full of bit-sliced field elements.
+ *
+ * Source: 'A new combinational logic minimization technique with
+ * applications to cryptology', https://eprint.iacr.org/2009/191
+ *
+ * As a minor speed optimisation, I use a modified version of the
+ * S-box which omits the additive constant 0x63, i.e. this S-box
+ * consists of only the field inversion and linear map components.
+ * Instead, the addition of the constant is deferred until after the
+ * subsequent ShiftRows and MixColumns stages, so that it happens at
+ * the same time as adding the next round key - and then we just make
+ * it _part_ of the round key, so it doesn't cost any extra
+ * instructions to add.
+ *
+ * (Obviously adding a constant to each byte commutes with ShiftRows,
+ * which only permutes the bytes. It also commutes with MixColumns:
+ * that's not quite so obvious, but since the effect of MixColumns is
+ * to multiply a constant polynomial M into each column, it is obvious
+ * that adding some polynomial K and then multiplying by M is
+ * equivalent to multiplying by M and then adding the product KM. And
+ * in fact, since the coefficients of M happen to sum to 1, it turns
+ * out that KM = K, so we don't even have to change the constant when
+ * we move it to the far side of MixColumns.)
+ *
+ * Of course, one knock-on effect of this is that the use of the S-box
+ * *during* key setup has to be corrected by manually adding on the
+ * constant afterwards!
+ */
+
+/* Initial linear transformation for the forward S-box, from Fig 2 of
+ * the paper. */
+#define SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t)      \
+        uintN_t y14 = input[4] ^ input[2];              \
+        uintN_t y13 = input[7] ^ input[1];              \
+        uintN_t y9 = input[7] ^ input[4];               \
+        uintN_t y8 = input[7] ^ input[2];               \
+        uintN_t t0 = input[6] ^ input[5];               \
+        uintN_t y1 = t0 ^ input[0];                     \
+        uintN_t y4 = y1 ^ input[4];                     \
+        uintN_t y12 = y13 ^ y14;                        \
+        uintN_t y2 = y1 ^ input[7];                     \
+        uintN_t y5 = y1 ^ input[1];                     \
+        uintN_t y3 = y5 ^ y8;                           \
+        uintN_t t1 = input[3] ^ y12;                    \
+        uintN_t y15 = t1 ^ input[2];                    \
+        uintN_t y20 = t1 ^ input[6];                    \
+        uintN_t y6 = y15 ^ input[0];                    \
+        uintN_t y10 = y15 ^ t0;                         \
+        uintN_t y11 = y20 ^ y9;                         \
+        uintN_t y7 = input[0] ^ y11;                    \
+        uintN_t y17 = y10 ^ y11;                        \
+        uintN_t y19 = y10 ^ y8;                         \
+        uintN_t y16 = t0 ^ y11;                         \
+        uintN_t y21 = y13 ^ y16;                        \
+        uintN_t y18 = input[7] ^ y16;                   \
+        /* Make a copy of input[0] under a new name, because the core
+         * will refer to it, and in the inverse version of the S-box
+         * the corresponding value will be one of the calculated ones
+         * and not in input[0] itself. */               \
+        uintN_t i0 = input[0];                          \
+        /* end */
+
+/* Core nonlinear component, from Fig 3 of the paper. */
+#define SBOX_CORE(uintN_t)                              \
+        uintN_t t2 = y12 & y15;                         \
+        uintN_t t3 = y3 & y6;                           \
+        uintN_t t4 = t3 ^ t2;                           \
+        uintN_t t5 = y4 & i0;                           \
+        uintN_t t6 = t5 ^ t2;                           \
+        uintN_t t7 = y13 & y16;                         \
+        uintN_t t8 = y5 & y1;                           \
+        uintN_t t9 = t8 ^ t7;                           \
+        uintN_t t10 = y2 & y7;                          \
+        uintN_t t11 = t10 ^ t7;                         \
+        uintN_t t12 = y9 & y11;                         \
+        uintN_t t13 = y14 & y17;                        \
+        uintN_t t14 = t13 ^ t12;                        \
+        uintN_t t15 = y8 & y10;                         \
+        uintN_t t16 = t15 ^ t12;                        \
+        uintN_t t17 = t4 ^ t14;                         \
+        uintN_t t18 = t6 ^ t16;                         \
+        uintN_t t19 = t9 ^ t14;                         \
+        uintN_t t20 = t11 ^ t16;                        \
+        uintN_t t21 = t17 ^ y20;                        \
+        uintN_t t22 = t18 ^ y19;                        \
+        uintN_t t23 = t19 ^ y21;                        \
+        uintN_t t24 = t20 ^ y18;                        \
+        uintN_t t25 = t21 ^ t22;                        \
+        uintN_t t26 = t21 & t23;                        \
+        uintN_t t27 = t24 ^ t26;                        \
+        uintN_t t28 = t25 & t27;                        \
+        uintN_t t29 = t28 ^ t22;                        \
+        uintN_t t30 = t23 ^ t24;                        \
+        uintN_t t31 = t22 ^ t26;                        \
+        uintN_t t32 = t31 & t30;                        \
+        uintN_t t33 = t32 ^ t24;                        \
+        uintN_t t34 = t23 ^ t33;                        \
+        uintN_t t35 = t27 ^ t33;                        \
+        uintN_t t36 = t24 & t35;                        \
+        uintN_t t37 = t36 ^ t34;                        \
+        uintN_t t38 = t27 ^ t36;                        \
+        uintN_t t39 = t29 & t38;                        \
+        uintN_t t40 = t25 ^ t39;                        \
+        uintN_t t41 = t40 ^ t37;                        \
+        uintN_t t42 = t29 ^ t33;                        \
+        uintN_t t43 = t29 ^ t40;                        \
+        uintN_t t44 = t33 ^ t37;                        \
+        uintN_t t45 = t42 ^ t41;                        \
+        uintN_t z0 = t44 & y15;                         \
+        uintN_t z1 = t37 & y6;                          \
+        uintN_t z2 = t33 & i0;                          \
+        uintN_t z3 = t43 & y16;                         \
+        uintN_t z4 = t40 & y1;                          \
+        uintN_t z5 = t29 & y7;                          \
+        uintN_t z6 = t42 & y11;                         \
+        uintN_t z7 = t45 & y17;                         \
+        uintN_t z8 = t41 & y10;                         \
+        uintN_t z9 = t44 & y12;                         \
+        uintN_t z10 = t37 & y3;                         \
+        uintN_t z11 = t33 & y4;                         \
+        uintN_t z12 = t43 & y13;                        \
+        uintN_t z13 = t40 & y5;                         \
+        uintN_t z14 = t29 & y2;                         \
+        uintN_t z15 = t42 & y9;                         \
+        uintN_t z16 = t45 & y14;                        \
+        uintN_t z17 = t41 & y8;                         \
+        /* end */
+
+/* Final linear transformation for the forward S-box, from Fig 4 of
+ * the paper. */
+#define SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t)   \
+        uintN_t t46 = z15 ^ z16;                        \
+        uintN_t t47 = z10 ^ z11;                        \
+        uintN_t t48 = z5 ^ z13;                         \
+        uintN_t t49 = z9 ^ z10;                         \
+        uintN_t t50 = z2 ^ z12;                         \
+        uintN_t t51 = z2 ^ z5;                          \
+        uintN_t t52 = z7 ^ z8;                          \
+        uintN_t t53 = z0 ^ z3;                          \
+        uintN_t t54 = z6 ^ z7;                          \
+        uintN_t t55 = z16 ^ z17;                        \
+        uintN_t t56 = z12 ^ t48;                        \
+        uintN_t t57 = t50 ^ t53;                        \
+        uintN_t t58 = z4 ^ t46;                         \
+        uintN_t t59 = z3 ^ t54;                         \
+        uintN_t t60 = t46 ^ t57;                        \
+        uintN_t t61 = z14 ^ t57;                        \
+        uintN_t t62 = t52 ^ t58;                        \
+        uintN_t t63 = t49 ^ t58;                        \
+        uintN_t t64 = z4 ^ t59;                         \
+        uintN_t t65 = t61 ^ t62;                        \
+        uintN_t t66 = z1 ^ t63;                         \
+        output[7] = t59 ^ t63;                          \
+        output[1] = t56 ^ t62;                          \
+        output[0] = t48 ^ t60;                          \
+        uintN_t t67 = t64 ^ t65;                        \
+        output[4] = t53 ^ t66;                          \
+        output[3] = t51 ^ t66;                          \
+        output[2] = t47 ^ t65;                          \
+        output[6] = t64 ^ output[4];                    \
+        output[5] = t55 ^ t67;                          \
+        /* end */
+
+#define BITSLICED_SUBBYTES(output, input, uintN_t) do { \
+        SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t);      \
+        SBOX_CORE(uintN_t);                             \
+        SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t);  \
+    } while (0)
+
+/*
+ * Initial and final linear transformations for the backward S-box. I
+ * generated these myself, by implementing the linear-transform
+ * optimisation algorithm in the paper, and applying it to the
+ * matrices calculated by _their_ top and bottom transformations, pre-
+ * and post-multiplied as appropriate by the linear map in the inverse
+ * S_box.
+ */
+#define SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t)     \
+    uintN_t y5 = input[4] ^ input[6];                   \
+    uintN_t y19 = input[3] ^ input[0];                  \
+    uintN_t itmp8 = y5 ^ input[0];                      \
+    uintN_t y4 = itmp8 ^ input[1];                      \
+    uintN_t y9 = input[4] ^ input[3];                   \
+    uintN_t y2 = y9 ^ y4;                               \
+    uintN_t itmp9 = y2 ^ input[7];                      \
+    uintN_t y1 = y9 ^ input[0];                         \
+    uintN_t y6 = y5 ^ input[7];                         \
+    uintN_t y18 = y9 ^ input[5];                        \
+    uintN_t y7 = y18 ^ y2;                              \
+    uintN_t y16 = y7 ^ y1;                              \
+    uintN_t y21 = y7 ^ input[1];                        \
+    uintN_t y3 = input[4] ^ input[7];                   \
+    uintN_t y13 = y16 ^ y21;                            \
+    uintN_t y8 = input[4] ^ y6;                         \
+    uintN_t y10 = y8 ^ y19;                             \
+    uintN_t y14 = y8 ^ y9;                              \
+    uintN_t y20 = itmp9 ^ input[2];                     \
+    uintN_t y11 = y9 ^ y20;                             \
+    uintN_t i0 = y11 ^ y7;                              \
+    uintN_t y15 = i0 ^ y6;                              \
+    uintN_t y17 = y16 ^ y15;                            \
+    uintN_t y12 = itmp9 ^ input[3];                     \
+    /* end */
+#define SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t) \
+    uintN_t otmp18 = z15 ^ z6;                          \
+    uintN_t otmp19 = z13 ^ otmp18;                      \
+    uintN_t otmp20 = z12 ^ otmp19;                      \
+    uintN_t otmp21 = z16 ^ otmp20;                      \
+    uintN_t otmp22 = z8 ^ otmp21;                       \
+    uintN_t otmp23 = z0 ^ otmp22;                       \
+    uintN_t otmp24 = otmp22 ^ z3;                       \
+    uintN_t otmp25 = otmp24 ^ z4;                       \
+    uintN_t otmp26 = otmp25 ^ z2;                       \
+    uintN_t otmp27 = z1 ^ otmp26;                       \
+    uintN_t otmp28 = z14 ^ otmp27;                      \
+    uintN_t otmp29 = otmp28 ^ z10;                      \
+    output[4] = z2 ^ otmp23;                            \
+    output[7] = z5 ^ otmp24;                            \
+    uintN_t otmp30 = z11 ^ otmp29;                      \
+    output[5] = z13 ^ otmp30;                           \
+    uintN_t otmp31 = otmp25 ^ z8;                       \
+    output[1] = z7 ^ otmp31;                            \
+    uintN_t otmp32 = z11 ^ z9;                          \
+    uintN_t otmp33 = z17 ^ otmp32;                      \
+    uintN_t otmp34 = otmp30 ^ otmp33;                   \
+    output[0] = z15 ^ otmp33;                           \
+    uintN_t otmp35 = z12 ^ otmp34;                      \
+    output[6] = otmp35 ^ z16;                           \
+    uintN_t otmp36 = z1 ^ otmp23;                       \
+    uintN_t otmp37 = z5 ^ otmp36;                       \
+    output[2] = z4 ^ otmp37;                            \
+    uintN_t otmp38 = z11 ^ output[1];                   \
+    uintN_t otmp39 = z2 ^ otmp38;                       \
+    uintN_t otmp40 = z17 ^ otmp39;                      \
+    uintN_t otmp41 = z0 ^ otmp40;                       \
+    uintN_t otmp42 = z5 ^ otmp41;                       \
+    uintN_t otmp43 = otmp42 ^ z10;                      \
+    uintN_t otmp44 = otmp43 ^ z3;                       \
+    output[3] = otmp44 ^ z16;                           \
+    /* end */
+
+#define BITSLICED_INVSUBBYTES(output, input, uintN_t) do {      \
+        SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t);             \
+        SBOX_CORE(uintN_t);                                     \
+        SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t);         \
+    } while (0)
+
+
+/* -----
+ * The ShiftRows transformation. This operates independently on each
+ * bit slice.
+ */
+
+#define SINGLE_BITSLICE_SHIFTROWS(output, input, uintN_t) do            \
+    {                                                                   \
+        uintN_t mask, mask2, mask3, diff, x = (input);                  \
+        /* Rotate rows 2 and 3 by 16 bits */                            \
+        mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF);              \
+        diff = ((x >> 8) ^ x) & mask;                                   \
+        x ^= diff ^ (diff << 8);                                        \
+        /* Rotate rows 1 and 3 by 8 bits */                             \
+        mask  = 0x0AAA * (((uintN_t)~(uintN_t)0) / 0xFFFF);             \
+        mask2 = 0xA000 * (((uintN_t)~(uintN_t)0) / 0xFFFF);             \
+        mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF);             \
+        x = ((x >> 4) & mask) | ((x << 12) & mask2) | (x & mask3);      \
+        /* Write output */                                              \
+        (output) = x;                                                   \
+    } while (0)
+
+#define SINGLE_BITSLICE_INVSHIFTROWS(output, input, uintN_t) do         \
+    {                                                                   \
+        uintN_t mask, mask2, mask3, diff, x = (input);                  \
+        /* Rotate rows 2 and 3 by 16 bits */                            \
+        mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF);              \
+        diff = ((x >> 8) ^ x) & mask;                                   \
+        x ^= diff ^ (diff << 8);                                        \
+        /* Rotate rows 1 and 3 by 8 bits, the opposite way to ShiftRows */ \
+        mask  = 0x000A * (((uintN_t)~(uintN_t)0) / 0xFFFF);             \
+        mask2 = 0xAAA0 * (((uintN_t)~(uintN_t)0) / 0xFFFF);             \
+        mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF);             \
+        x = ((x >> 12) & mask) | ((x << 4) & mask2) | (x & mask3);      \
+        /* Write output */                                              \
+        (output) = x;                                                   \
+    } while (0)
+
+#define BITSLICED_SHIFTROWS(output, input, uintN_t) do                  \
+    {                                                                   \
+        ITERATE(SINGLE_BITSLICE_SHIFTROWS, output, input, uintN_t);     \
+    } while (0)
+
+#define BITSLICED_INVSHIFTROWS(output, input, uintN_t) do               \
+    {                                                                   \
+        ITERATE(SINGLE_BITSLICE_INVSHIFTROWS, output, input, uintN_t);  \
+    } while (0)
+
+/* -----
+ * The MixColumns transformation. This has to operate on all eight bit
+ * slices at once, and also passes data back and forth between the
+ * bits in an adjacent group of 4 within each slice.
+ *
+ * Notation: let F = GF(2)[X]/<X^8+X^4+X^3+X+1> be the finite field
+ * used in AES, and let R = F[Y]/<Y^4+1> be the ring whose elements
+ * represent the possible contents of a column of the matrix. I use X
+ * and Y below in those senses, i.e. X is the value in F that
+ * represents the byte 0x02, and Y is the value in R that cycles the
+ * four bytes around by one if you multiply by it.
+ */
+
+/* Multiply every column by Y^3, i.e. cycle it round one place to the
+ * right. Operates on one bit slice at a time; you have to wrap it in
+ * ITERATE to affect all the data at once. */
+#define BITSLICED_MUL_BY_Y3(output, input, uintN_t) do          \
+    {                                                           \
+        uintN_t mask, mask2, x;                                 \
+        mask  = 0x8 * (((uintN_t)~(uintN_t)0) / 0xF);           \
+        mask2 = 0x7 * (((uintN_t)~(uintN_t)0) / 0xF);           \
+        x = input;                                              \
+        output = ((x << 3) & mask) ^ ((x >> 1) & mask2);        \
+    } while (0)
+
+/* Multiply every column by Y^2. */
+#define BITSLICED_MUL_BY_Y2(output, input, uintN_t) do          \
+    {                                                           \
+        uintN_t mask, mask2, x;                                 \
+        mask  = 0xC * (((uintN_t)~(uintN_t)0) / 0xF);           \
+        mask2 = 0x3 * (((uintN_t)~(uintN_t)0) / 0xF);           \
+        x = input;                                              \
+        output = ((x << 2) & mask) ^ ((x >> 2) & mask2);        \
+    } while (0)
+
+#define BITSLICED_MUL_BY_1_Y3(output, input, uintN_t) do        \
+    {                                                           \
+        uintN_t tmp = input;                                    \
+        BITSLICED_MUL_BY_Y3(tmp, input, uintN_t);               \
+        output = input ^ tmp;                                   \
+    } while (0)
+
+/* Multiply every column by 1+Y^2. */
+#define BITSLICED_MUL_BY_1_Y2(output, input, uintN_t) do        \
+    {                                                           \
+        uintN_t tmp = input;                                    \
+        BITSLICED_MUL_BY_Y2(tmp, input, uintN_t);               \
+        output = input ^ tmp;                                   \
+    } while (0)
+
+/* Multiply every field element by X. This has to feed data between
+ * slices, so it does the whole job in one go without needing ITERATE. */
+#define BITSLICED_MUL_BY_X(output, input, uintN_t) do   \
+    {                                                   \
+        uintN_t bit7 = input[7];                        \
+        output[7] = input[6];                           \
+        output[6] = input[5];                           \
+        output[5] = input[4];                           \
+        output[4] = input[3] ^ bit7;                    \
+        output[3] = input[2] ^ bit7;                    \
+        output[2] = input[1];                           \
+        output[1] = input[0] ^ bit7;                    \
+        output[0] =            bit7;                    \
+    } while (0)
+
+/*
+ * The MixColumns constant is
+ *   M = X + Y + Y^2 + (X+1)Y^3
+ * which we construct by rearranging it into
+ *   M = 1 + (1+Y^3) [ X + (1+Y^2) ]
+ */
+#define BITSLICED_MIXCOLUMNS(output, input, uintN_t) do         \
+    {                                                           \
+        uintN_t a[8], aX[8], b[8];                              \
+        /* a = input * (1+Y^3) */                               \
+        ITERATE(BITSLICED_MUL_BY_1_Y3, a, input, uintN_t);      \
+        /* aX = a * X */                                        \
+        BITSLICED_MUL_BY_X(aX, a, uintN_t);                     \
+        /* b = a * (1+Y^2) = input * (1+Y+Y^2+Y^3) */           \
+        ITERATE(BITSLICED_MUL_BY_1_Y2, b, a, uintN_t);          \
+        /* output = input + aX + b (reusing a as a temp */      \
+        BITSLICED_ADD(a, aX, b);                                \
+        BITSLICED_ADD(output, input, a);                        \
+    } while (0)
+
+/*
+ * The InvMixColumns constant, written out longhand, is
+ *   I = (X^3+X^2+X) + (X^3+1)Y + (X^3+X^2+1)Y^2 + (X^3+X+1)Y^3
+ * We represent this as
+ *   I = (X^3+X^2+X+1)(Y^3+Y^2+Y+1) + 1 + X(Y+Y^2) + X^2(Y+Y^3)
+ */
+#define BITSLICED_INVMIXCOLUMNS(output, input, uintN_t) do      \
+    {                                                           \
+        /* We need input * X^i for i=1,...,3 */                 \
+        uintN_t X[8], X2[8], X3[8];                             \
+        BITSLICED_MUL_BY_X(X, input, uintN_t);                  \
+        BITSLICED_MUL_BY_X(X2, X, uintN_t);                     \
+        BITSLICED_MUL_BY_X(X3, X2, uintN_t);                    \
+        /* Sum them all and multiply by 1+Y+Y^2+Y^3. */         \
+        uintN_t S[8];                                           \
+        BITSLICED_ADD(S, input, X);                             \
+        BITSLICED_ADD(S, S, X2);                                \
+        BITSLICED_ADD(S, S, X3);                                \
+        ITERATE(BITSLICED_MUL_BY_1_Y3, S, S, uintN_t);          \
+        ITERATE(BITSLICED_MUL_BY_1_Y2, S, S, uintN_t);          \
+        /* Compute the X(Y+Y^2) term. */                        \
+        uintN_t A[8];                                           \
+        ITERATE(BITSLICED_MUL_BY_1_Y3, A, X, uintN_t);          \
+        ITERATE(BITSLICED_MUL_BY_Y2, A, A, uintN_t);            \
+        /* Compute the X^2(Y+Y^3) term. */                      \
+        uintN_t B[8];                                           \
+        ITERATE(BITSLICED_MUL_BY_1_Y2, B, X2, uintN_t);         \
+        ITERATE(BITSLICED_MUL_BY_Y3, B, B, uintN_t);            \
+        /* And add all the pieces together. */                  \
+        BITSLICED_ADD(S, S, input);                             \
+        BITSLICED_ADD(S, S, A);                                 \
+        BITSLICED_ADD(output, S, B);                            \
+    } while (0)
+
+/* -----
+ * Put it all together into a cipher round.
+ */
+
+/* Dummy macro to get rid of the MixColumns in the final round. */
+#define NO_MIXCOLUMNS(out, in, uintN_t) do {} while (0)
+
+#define ENCRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro)                 \
+    static void aes_sliced_round_e_##suffix(                            \
+        uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \
+    {                                                                   \
+        BITSLICED_SUBBYTES(output, input, uintN_t);                     \
+        BITSLICED_SHIFTROWS(output, output, uintN_t);                   \
+        mixcol_macro(output, output, uintN_t);                          \
+        BITSLICED_ADD(output, output, roundkey);                        \
+    }
+
+ENCRYPT_ROUND_FN(serial, uint16_t, BITSLICED_MIXCOLUMNS)
+ENCRYPT_ROUND_FN(serial_last, uint16_t, NO_MIXCOLUMNS)
+ENCRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_MIXCOLUMNS)
+ENCRYPT_ROUND_FN(parallel_last, BignumInt, NO_MIXCOLUMNS)
+
+#define DECRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro)                 \
+    static void aes_sliced_round_d_##suffix(                            \
+        uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \
+    {                                                                   \
+        BITSLICED_ADD(output, input, roundkey);                         \
+        mixcol_macro(output, output, uintN_t);                          \
+        BITSLICED_INVSUBBYTES(output, output, uintN_t);                 \
+        BITSLICED_INVSHIFTROWS(output, output, uintN_t);                \
+    }
+
+#if 0 /* no cipher mode we support requires serial decryption */
+DECRYPT_ROUND_FN(serial, uint16_t, BITSLICED_INVMIXCOLUMNS)
+DECRYPT_ROUND_FN(serial_first, uint16_t, NO_MIXCOLUMNS)
+#endif
+DECRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_INVMIXCOLUMNS)
+DECRYPT_ROUND_FN(parallel_first, BignumInt, NO_MIXCOLUMNS)
+
+#endif // WINSCP_VS
+
+/* -----
+ * Key setup function.
+ */
+
+typedef struct aes_sliced_key aes_sliced_key;
+struct aes_sliced_key {
+    BignumInt roundkeys_parallel[MAXROUNDKEYS * 8];
+    uint16_t roundkeys_serial[MAXROUNDKEYS * 8];
+    unsigned rounds;
+};
+
+/*WINSCP static*/ void aes_sliced_key_setup(
+    aes_sliced_key *sk, const void *vkey, size_t keybits)
+#ifndef WINSCP_VS
+;
+#else
+{
+    const unsigned char *key = (const unsigned char *)vkey;
+
+    size_t key_words = keybits / 32;
+    sk->rounds = key_words + 6;
+    size_t sched_words = (sk->rounds + 1) * 4;
+
+    unsigned rconpos = 0;
+
+    uint16_t *outslices = sk->roundkeys_serial;
+    unsigned outshift = 0;
+
+    memset(sk->roundkeys_serial, 0, sizeof(sk->roundkeys_serial));
+
+    uint8_t inblk[16];
+    memset(inblk, 0, 16);
+    uint16_t slices[8];
+
+    for (size_t i = 0; i < sched_words; i++) {
+        /*
+         * Prepare a word of round key in the low 4 bits of each
+         * integer in slices[].
+         */
+        if (i < key_words) {
+            memcpy(inblk, key + 4*i, 4);
+            TO_BITSLICES(slices, inblk, uint16_t, =, 0);
+        } else {
+            unsigned wordindex, bitshift;
+            uint16_t *prevslices;
+
+            /* Fetch the (i-1)th key word */
+            wordindex = i-1;
+            bitshift = 4 * (wordindex & 3);
+            prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2);
+            for (size_t i = 0; i < 8; i++)
+                slices[i] = prevslices[i] >> bitshift;
+
+            /* Decide what we're doing in this expansion stage */
+            bool rotate_and_round_constant = (i % key_words == 0);
+            bool sub = rotate_and_round_constant ||
+                (key_words == 8 && i % 8 == 4);
+
+            if (rotate_and_round_constant) {
+                for (size_t i = 0; i < 8; i++)
+                    slices[i] = ((slices[i] << 3) | (slices[i] >> 1)) & 0xF;
+            }
+
+            if (sub) {
+                /* Apply the SubBytes transform to the key word. But
+                 * here we need to apply the _full_ SubBytes from the
+                 * spec, including the constant which our S-box leaves
+                 * out. */
+                BITSLICED_SUBBYTES(slices, slices, uint16_t);
+                slices[0] ^= 0xFFFF;
+                slices[1] ^= 0xFFFF;
+                slices[5] ^= 0xFFFF;
+                slices[6] ^= 0xFFFF;
+            }
+
+            if (rotate_and_round_constant) {
+                assert(rconpos < lenof(aes_key_setup_round_constants));
+                uint8_t rcon = aes_key_setup_round_constants[rconpos++];
+                for (size_t i = 0; i < 8; i++)
+                    slices[i] ^= 1 & (rcon >> i);
+            }
+
+            /* Combine with the (i-Nk)th key word */
+            wordindex = i - key_words;
+            bitshift = 4 * (wordindex & 3);
+            prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2);
+            for (size_t i = 0; i < 8; i++)
+                slices[i] ^= prevslices[i] >> bitshift;
+        }
+
+        /*
+         * Now copy it into sk.
+         */
+        for (unsigned b = 0; b < 8; b++)
+            outslices[b] |= (slices[b] & 0xF) << outshift;
+        outshift += 4;
+        if (outshift == 16) {
+            outshift = 0;
+            outslices += 8;
+        }
+    }
+
+    smemclr(inblk, sizeof(inblk));
+    smemclr(slices, sizeof(slices));
+
+    /*
+     * Add the S-box constant to every round key after the first one,
+     * compensating for it being left out in the main cipher.
+     */
+    for (size_t i = 8; i < 8 * (sched_words/4); i += 8) {
+        sk->roundkeys_serial[i+0] ^= 0xFFFF;
+        sk->roundkeys_serial[i+1] ^= 0xFFFF;
+        sk->roundkeys_serial[i+5] ^= 0xFFFF;
+        sk->roundkeys_serial[i+6] ^= 0xFFFF;
+    }
+
+    /*
+     * Replicate that set of round keys into larger integers for the
+     * parallel versions of the cipher.
+     */
+    for (size_t i = 0; i < 8 * (sched_words / 4); i++) {
+        sk->roundkeys_parallel[i] = sk->roundkeys_serial[i] *
+            ((BignumInt)~(BignumInt)0 / 0xFFFF);
+    }
+}
+#endif
+
+#ifdef WINSCP_VS
+/* -----
+ * The full cipher primitive, including transforming the input and
+ * output to/from bit-sliced form.
+ */
+
+#define ENCRYPT_FN(suffix, uintN_t, nblocks)                            \
+    static void aes_sliced_e_##suffix(                                  \
+        uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \
+    {                                                                   \
+        uintN_t state[8];                                               \
+        TO_BITSLICES(state, input, uintN_t, =, 0);                      \
+        for (unsigned i = 1; i < nblocks; i++) {                        \
+            input += 16;                                                \
+            TO_BITSLICES(state, input, uintN_t, |=, i*16);              \
+        }                                                               \
+        const uintN_t *keys = sk->roundkeys_##suffix;                   \
+        BITSLICED_ADD(state, state, keys);                              \
+        keys += 8;                                                      \
+        for (unsigned i = 0; i < sk->rounds-1; i++) {                   \
+            aes_sliced_round_e_##suffix(state, state, keys);            \
+            keys += 8;                                                  \
+        }                                                               \
+        aes_sliced_round_e_##suffix##_last(state, state, keys);         \
+        for (unsigned i = 0; i < nblocks; i++) {                        \
+            FROM_BITSLICES(output, state, i*16);                        \
+            output += 16;                                               \
+        }                                                               \
+    }
+
+#define DECRYPT_FN(suffix, uintN_t, nblocks)                            \
+    static void aes_sliced_d_##suffix(                                  \
+        uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \
+    {                                                                   \
+        uintN_t state[8];                                               \
+        TO_BITSLICES(state, input, uintN_t, =, 0);                      \
+        for (unsigned i = 1; i < nblocks; i++) {                        \
+            input += 16;                                                \
+            TO_BITSLICES(state, input, uintN_t, |=, i*16);              \
+        }                                                               \
+        const uintN_t *keys = sk->roundkeys_##suffix + 8*sk->rounds;    \
+        aes_sliced_round_d_##suffix##_first(state, state, keys);        \
+        keys -= 8;                                                      \
+        for (unsigned i = 0; i < sk->rounds-1; i++) {                   \
+            aes_sliced_round_d_##suffix(state, state, keys);            \
+            keys -= 8;                                                  \
+        }                                                               \
+        BITSLICED_ADD(state, state, keys);                              \
+        for (unsigned i = 0; i < nblocks; i++) {                        \
+            FROM_BITSLICES(output, state, i*16);                        \
+            output += 16;                                               \
+        }                                                               \
+    }
+
+ENCRYPT_FN(serial, uint16_t, 1)
+#if 0 /* no cipher mode we support requires serial decryption */
+DECRYPT_FN(serial, uint16_t, 1)
+#endif
+ENCRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM)
+DECRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM)
+
+#endif // WINSCP_VS
+
+/* -----
+ * The SSH interface and the cipher modes.
+ */
+
+#define SDCTR_WORDS (16 / BIGNUM_INT_BYTES)
+
+typedef struct aes_sw_context aes_sw_context;
+struct aes_sw_context {
+    aes_sliced_key sk;
+    union {
+        struct {
+            /* In CBC mode, the IV is just a copy of the last seen
+             * cipher block. */
+            uint8_t prevblk[16];
+        } cbc;
+        struct {
+            /* In SDCTR mode, we keep the counter itself in a form
+             * that's easy to increment. We also use the parallel
+             * version of the core AES function, so we'll encrypt
+             * multiple counter values in one go. That won't align
+             * nicely with the sizes of data we're asked to encrypt,
+             * so we must also store a cache of the last set of
+             * keystream blocks we generated, and our current position
+             * within that cache. */
+            BignumInt counter[SDCTR_WORDS];
+            uint8_t keystream[SLICE_PARALLELISM * 16];
+            uint8_t *keystream_pos;
+        } sdctr;
+    } iv;
+    ssh_cipher ciph;
+};
+
+#ifndef WINSCP_VS
+
+static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg)
+{
+    aes_sw_context *ctx = snew(aes_sw_context);
+    ctx->ciph.vt = alg;
+    return &ctx->ciph;
+}
+
+static void aes_sw_free(ssh_cipher *ciph)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+}
+
+static void aes_sw_setkey(ssh_cipher *ciph, const void *vkey)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+    aes_sliced_key_setup(&ctx->sk, vkey, ctx->ciph.vt->real_keybits);
+}
+
+static void aes_sw_setiv_cbc(ssh_cipher *ciph, const void *iv)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+    memcpy(ctx->iv.cbc.prevblk, iv, 16);
+}
+
+static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+    const uint8_t *iv = (const uint8_t *)viv;
+
+    /* Import the initial counter value into the internal representation */
+    unsigned i; // WINSCP
+    for (i = 0; i < SDCTR_WORDS; i++)
+        ctx->iv.sdctr.counter[i] =
+            GET_BIGNUMINT_MSB_FIRST(
+                iv + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES);
+
+    /* Set keystream_pos to indicate that the keystream cache is
+     * currently empty */
+    ctx->iv.sdctr.keystream_pos =
+        ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream);
+}
+
+#endif
+
+typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched);
+
+#ifdef WINSCP_VS
+
+static inline void memxor16(void *vout, const void *vlhs, const void *vrhs)
+{
+    uint8_t *out = (uint8_t *)vout;
+    const uint8_t *lhs = (const uint8_t *)vlhs, *rhs = (const uint8_t *)vrhs;
+    uint64_t w;
+
+    w = GET_64BIT_LSB_FIRST(lhs);
+    w ^= GET_64BIT_LSB_FIRST(rhs);
+    PUT_64BIT_LSB_FIRST(out, w);
+    w = GET_64BIT_LSB_FIRST(lhs + 8);
+    w ^= GET_64BIT_LSB_FIRST(rhs + 8);
+    PUT_64BIT_LSB_FIRST(out + 8, w);
+}
+
+static inline void aes_cbc_sw_encrypt(
+    ssh_cipher *ciph, void *vblk, int blklen)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+
+    /*
+     * CBC encryption has to be done serially, because the input to
+     * each run of the cipher includes the output from the previous
+     * run.
+     */
+
+    for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+         blk < finish; blk += 16) {
+        /*
+         * We use the IV array itself as the location for the
+         * encryption, because there's no reason not to.
+         */
+
+        /* XOR the new plaintext block into the previous cipher block */
+        memxor16(ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, blk);
+
+        /* Run the cipher over the result, which leaves it
+         * conveniently already stored in ctx->iv */
+        aes_sliced_e_serial(
+            ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, &ctx->sk);
+
+        /* Copy it to the output location */
+        memcpy(blk, ctx->iv.cbc.prevblk, 16);
+    }
+}
+
+static inline void aes_cbc_sw_decrypt(
+    ssh_cipher *ciph, void *vblk, int blklen)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+    uint8_t *blk = (uint8_t *)vblk;
+
+    /*
+     * CBC decryption can run in parallel, because all the
+     * _ciphertext_ blocks are already available.
+     */
+
+    size_t blocks_remaining = blklen / 16;
+
+    uint8_t data[SLICE_PARALLELISM * 16];
+    /* Zeroing the data array is probably overcautious, but it avoids
+     * technically undefined behaviour from leaving it uninitialised
+     * if our very first iteration doesn't include enough cipher
+     * blocks to populate it fully */
+    memset(data, 0, sizeof(data));
+
+    while (blocks_remaining > 0) {
+        /* Number of blocks we'll handle in this iteration. If we're
+         * dealing with fewer than the maximum, it doesn't matter -
+         * it's harmless to run the full parallel cipher function
+         * anyway. */
+        size_t blocks = (blocks_remaining < SLICE_PARALLELISM ?
+                         blocks_remaining : SLICE_PARALLELISM);
+
+        /* Parallel-decrypt the input, in a separate array so we still
+         * have the cipher stream available for XORing. */
+        memcpy(data, blk, 16 * blocks);
+        aes_sliced_d_parallel(data, data, &ctx->sk);
+
+        /* Write the output and update the IV */
+        for (size_t i = 0; i < blocks; i++) {
+            uint8_t *decrypted = data + 16*i;
+            uint8_t *output = blk + 16*i;
+
+            memxor16(decrypted, decrypted, ctx->iv.cbc.prevblk);
+            memcpy(ctx->iv.cbc.prevblk, output, 16);
+            memcpy(output, decrypted, 16);
+        }
+
+        /* Advance the input pointer. */
+        blk += 16 * blocks;
+        blocks_remaining -= blocks;
+    }
+
+    smemclr(data, sizeof(data));
+}
+
+static inline void aes_sdctr_sw(
+    ssh_cipher *ciph, void *vblk, int blklen)
+{
+    aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+
+    /*
+     * SDCTR encrypt/decrypt loops round one block at a time XORing
+     * the keystream into the user's data, and periodically has to run
+     * a parallel encryption operation to get more keystream.
+     */
+
+    uint8_t *keystream_end =
+        ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream);
+
+    for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+         blk < finish; blk += 16) {
+
+        if (ctx->iv.sdctr.keystream_pos == keystream_end) {
+            /*
+             * Generate some keystream.
+             */
+            for (uint8_t *block = ctx->iv.sdctr.keystream;
+                 block < keystream_end; block += 16) {
+                /* Format the counter value into the buffer. */
+                for (unsigned i = 0; i < SDCTR_WORDS; i++)
+                    PUT_BIGNUMINT_MSB_FIRST(
+                        block + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES,
+                        ctx->iv.sdctr.counter[i]);
+
+                /* Increment the counter. */
+                BignumCarry carry = 1;
+                for (unsigned i = 0; i < SDCTR_WORDS; i++)
+                    BignumADC(ctx->iv.sdctr.counter[i], carry,
+                              ctx->iv.sdctr.counter[i], 0, carry);
+            }
+
+            /* Encrypt all those counter blocks. */
+            aes_sliced_e_parallel(ctx->iv.sdctr.keystream,
+                                  ctx->iv.sdctr.keystream, &ctx->sk);
+
+            /* Reset keystream_pos to the start of the buffer. */
+            ctx->iv.sdctr.keystream_pos = ctx->iv.sdctr.keystream;
+        }
+
+        memxor16(blk, blk, ctx->iv.sdctr.keystream_pos);
+        ctx->iv.sdctr.keystream_pos += 16;
+    }
+}
+
+#define SW_ENC_DEC(len)                                 \
+    /*WINSCP static*/ void aes##len##_sw_cbc_encrypt(              \
+        ssh_cipher *ciph, void *vblk, int blklen)       \
+    { aes_cbc_sw_encrypt(ciph, vblk, blklen); }         \
+    /*WINSCP static*/ void aes##len##_sw_cbc_decrypt(              \
+        ssh_cipher *ciph, void *vblk, int blklen)       \
+    { aes_cbc_sw_decrypt(ciph, vblk, blklen); }         \
+    /*WINSCP static*/ void aes##len##_sw_sdctr(                    \
+        ssh_cipher *ciph, void *vblk, int blklen)       \
+    { aes_sdctr_sw(ciph, vblk, blklen); }
+
+#else // WINSCP_VS
+
+#define SW_ENC_DEC(len)                                 \
+    void aes##len##_sw_cbc_encrypt(                     \
+        ssh_cipher *ciph, void *vblk, int blklen);      \
+    void aes##len##_sw_cbc_decrypt(                     \
+        ssh_cipher *ciph, void *vblk, int blklen);      \
+    void aes##len##_sw_sdctr(                           \
+        ssh_cipher *ciph, void *vblk, int blklen);      \
+
+#endif // WINSCP_VS
+
+SW_ENC_DEC(128)
+SW_ENC_DEC(192)
+SW_ENC_DEC(256)
+
+#ifndef WINSCP_VS
+
+AES_EXTRA(_sw);
+AES_ALL_VTABLES(_sw, "unaccelerated");
+
+#ifdef MPEXT
+
+#include "puttyexp.h"
+
+AESContext * aes_make_context()
+{
+  ssh_cipher * cipher = ssh_cipher_new(&ssh_aes256_sdctr);
+  return cipher;
+}
+
+void aes_free_context(AESContext * ctx)
+{
+  ssh_cipher * cipher = (ssh_cipher *)ctx;
+  ssh_cipher_free(cipher);
+}
+
+void aes_iv(AESContext * ctx, const void * iv)
+{
+  ssh_cipher * cipher = (ssh_cipher *)ctx;
+  ssh_cipher_setiv(cipher, iv);
+}
+
+void call_aes_setup(AESContext * ctx, unsigned char * key, int keylen)
+{
+  ssh_cipher * cipher = (ssh_cipher *)ctx;
+  assert(keylen == 32);
+  ssh_cipher_setkey(cipher, key);
+}
+
+void call_aes_sdctr(unsigned char *blk, int len, void *ctx)
+{
+  ssh_cipher * cipher = (ssh_cipher *)ctx;
+  ssh_cipher_encrypt(cipher, blk, len);
+}
+
+#endif
+
+#endif // WINSCP_VS

+ 126 - 0
source/putty/crypto/aes.h

@@ -0,0 +1,126 @@
+/*
+ * Definitions likely to be helpful to multiple AES implementations.
+ */
+
+/*
+ * The 'extra' structure used by AES implementations is used to
+ * include information about how to check if a given implementation is
+ * available at run time, and whether we've already checked.
+ */
+struct aes_extra_mutable;
+struct aes_extra {
+    /* Function to check availability. Might be expensive, so we don't
+     * want to call it more than once. */
+    bool (*check_available)(void);
+
+    /* Point to a writable substructure. */
+    struct aes_extra_mutable *mut;
+};
+struct aes_extra_mutable {
+    bool checked_availability;
+    bool is_available;
+};
+static inline bool check_availability(const struct aes_extra *extra)
+{
+    if (!extra->mut->checked_availability) {
+        extra->mut->is_available = extra->check_available();
+        extra->mut->checked_availability = true;
+    }
+
+    return extra->mut->is_available;
+}
+
+/*
+ * Macros to define vtables for AES variants. There are a lot of
+ * these, because of the cross product between cipher modes, key
+ * sizes, and assorted HW/SW implementations, so it's worth spending
+ * some effort here to reduce the boilerplate in the sub-files.
+ */
+
+#define AES_EXTRA(impl_c)                                               \
+    static struct aes_extra_mutable aes ## impl_c ## _extra_mut;        \
+    static const struct aes_extra aes ## impl_c ## _extra = {           \
+        /* WINSCP */ \
+        /*.check_available =*/ aes ## impl_c ## _available,                 \
+        /*.mut =*/ &aes ## impl_c ## _extra_mut,                            \
+    }
+
+// WINSCP string constants are for avoiding 
+// Warning 1060: Different alignments specified for same segment, %s. Using highest alignment.rdata
+// in objconv
+
+#define AES_CBC_VTABLE(impl_c, impl_display, bits)                      \
+    const char ssh_aes ## bits ## _cbc ## impl_c ## ssh2_id[] = "aes" #bits "-cbc"; /*WINSCP*/ \
+    const char ssh_aes ## bits ## _cbc ## impl_c ## text_name[] = "AES-" #bits " CBC (" impl_display ")"; /*WINSCP*/ \
+    const ssh_cipheralg ssh_aes ## bits ## _cbc ## impl_c = {           \
+        /*WINSCP*/ \
+        /*.new =*/ aes ## impl_c ## _new,                                   \
+        /*.free =*/ aes ## impl_c ## _free,                                 \
+        /*.setiv =*/ aes ## impl_c ## _setiv_cbc,                           \
+        /*.setkey =*/ aes ## impl_c ## _setkey,                             \
+        /*.encrypt =*/ aes ## bits ## impl_c ## _cbc_encrypt,               \
+        /*.decrypt =*/ aes ## bits ## impl_c ## _cbc_decrypt,               \
+        NULL, \
+        NULL, \
+        /*.ssh2_id =*/ ssh_aes ## bits ## _cbc ## impl_c ## ssh2_id, /*WINSCP*/ \
+        /*.blksize =*/ 16,                                                  \
+        /*.real_keybits =*/ bits,                                           \
+        /*.padded_keybytes =*/ bits/8,                                      \
+        /*.flags =*/ SSH_CIPHER_IS_CBC,                                     \
+        /*.text_name =*/ ssh_aes ## bits ## _cbc ## impl_c ## text_name, /*WINSCP*/ \
+        NULL, \
+        /*.extra =*/ &aes ## impl_c ## _extra,                              \
+    }
+
+#define AES_SDCTR_VTABLE(impl_c, impl_display, bits)                    \
+    const char ssh_aes ## bits ## _sdctr ## impl_c ## ssh2_id[] = "aes" #bits "-ctr"; /*WINSCP*/ \
+    const char ssh_aes ## bits ## _sdctr ## impl_c ## text_name[] = "AES-" #bits " SDCTR (" impl_display ")"; /*WINSCP*/ \
+    const ssh_cipheralg ssh_aes ## bits ## _sdctr ## impl_c = {         \
+        /*WINSCP*/ \
+        /*.new =*/ aes ## impl_c ## _new,                                   \
+        /*.free =*/ aes ## impl_c ## _free,                                 \
+        /*.setiv =*/ aes ## impl_c ## _setiv_sdctr,                         \
+        /*.setkey =*/ aes ## impl_c ## _setkey,                             \
+        /*.encrypt =*/ aes ## bits ## impl_c ## _sdctr,                     \
+        /*.decrypt =*/ aes ## bits ## impl_c ## _sdctr,                     \
+        NULL, \
+        NULL, \
+        /*.ssh2_id =*/ ssh_aes ## bits ## _sdctr ## impl_c ## ssh2_id, /*WINSCP*/ \
+        /*.blksize =*/ 16,                                                  \
+        /*.real_keybits =*/ bits,                                           \
+        /*.padded_keybytes =*/ bits/8,                                      \
+        /*.flags =*/ 0,                                                     \
+        /*.text_name =*/ ssh_aes ## bits ## _sdctr ## impl_c ## text_name, /*WINSCP*/ \
+        NULL, \
+        /*.extra =*/ &aes ## impl_c ## _extra,                              \
+    }
+
+#define AES_ALL_VTABLES(impl_c, impl_display)           \
+    AES_CBC_VTABLE(impl_c, impl_display, 128);          \
+    AES_CBC_VTABLE(impl_c, impl_display, 192);          \
+    AES_CBC_VTABLE(impl_c, impl_display, 256);          \
+    AES_SDCTR_VTABLE(impl_c, impl_display, 128);        \
+    AES_SDCTR_VTABLE(impl_c, impl_display, 192);        \
+    AES_SDCTR_VTABLE(impl_c, impl_display, 256)
+
+/*
+ * Macros to repeat a piece of code particular numbers of times that
+ * correspond to 1 fewer than the number of AES rounds. (Because the
+ * last round is different.)
+ */
+#define REP2(x) x x
+#define REP4(x) REP2(REP2(x))
+#define REP8(x) REP2(REP4(x))
+#define REP9(x) REP8(x) x
+#define REP11(x) REP8(x) REP2(x) x
+#define REP13(x) REP8(x) REP4(x) x
+
+/*
+ * The round constants used in key schedule expansion.
+ */
+extern const uint8_t aes_key_setup_round_constants[10];
+
+/*
+ * The largest number of round keys ever needed.
+ */
+#define MAXROUNDKEYS 15

+ 0 - 0
source/putty/sshaesold.c → source/putty/crypto/aesold.c


+ 147 - 0
source/putty/crypto/arcfour.c

@@ -0,0 +1,147 @@
+/*
+ * Arcfour (RC4) implementation for PuTTY.
+ *
+ * Coded from Schneier.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+
+typedef struct {
+    unsigned char i, j, s[256];
+    ssh_cipher ciph;
+} ArcfourContext;
+
+static void arcfour_block(void *handle, void *vblk, int len)
+{
+    unsigned char *blk = (unsigned char *)vblk;
+    ArcfourContext *ctx = (ArcfourContext *)handle;
+    unsigned k;
+    unsigned char tmp, i, j, *s;
+
+    s = ctx->s;
+    i = ctx->i; j = ctx->j;
+    for (k = 0; (int)k < len; k++) {
+        i  = (i + 1) & 0xff;
+        j  = (j + s[i]) & 0xff;
+        tmp = s[i]; s[i] = s[j]; s[j] = tmp;
+        blk[k] ^= s[(s[i]+s[j]) & 0xff];
+    }
+    ctx->i = i; ctx->j = j;
+}
+
+static void arcfour_setkey(ArcfourContext *ctx, unsigned char const *key,
+                           unsigned keybytes)
+{
+    unsigned char tmp, k[256], *s;
+    unsigned i, j;
+
+    s = ctx->s;
+    assert(keybytes <= 256);
+    ctx->i = ctx->j = 0;
+    for (i = 0; i < 256; i++) {
+        s[i] = i;
+        k[i] = key[i % keybytes];
+    }
+    j = 0;
+    for (i = 0; i < 256; i++) {
+        j = (j + s[i] + k[i]) & 0xff;
+        tmp = s[i]; s[i] = s[j]; s[j] = tmp;
+    }
+}
+
+/* -- Interface with PuTTY -- */
+
+/*
+ * We don't implement Arcfour in SSH-1 because it's utterly insecure in
+ * several ways.  See CERT Vulnerability Notes VU#25309, VU#665372,
+ * and VU#565052.
+ *
+ * We don't implement the "arcfour" algorithm in SSH-2 because it doesn't
+ * stir the cipher state before emitting keystream, and hence is likely
+ * to leak data about the key.
+ */
+
+static ssh_cipher *arcfour_new(const ssh_cipheralg *alg)
+{
+    ArcfourContext *ctx = snew(ArcfourContext);
+    ctx->ciph.vt = alg;
+    return &ctx->ciph;
+}
+
+static void arcfour_free(ssh_cipher *cipher)
+{
+    ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph);
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+}
+
+static void arcfour_stir(ArcfourContext *ctx)
+{
+    unsigned char *junk = snewn(1536, unsigned char);
+    memset(junk, 0, 1536);
+    arcfour_block(ctx, junk, 1536);
+    smemclr(junk, 1536);
+    sfree(junk);
+}
+
+static void arcfour_ssh2_setiv(ssh_cipher *cipher, const void *key)
+{
+    /* As a pure stream cipher, Arcfour has no IV separate from the key */
+}
+
+static void arcfour_ssh2_setkey(ssh_cipher *cipher, const void *key)
+{
+    ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph);
+    arcfour_setkey(ctx, key, ctx->ciph.vt->padded_keybytes);
+    arcfour_stir(ctx);
+}
+
+static void arcfour_ssh2_block(ssh_cipher *cipher, void *blk, int len)
+{
+    ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph);
+    arcfour_block(ctx, blk, len);
+}
+
+const ssh_cipheralg ssh_arcfour128_ssh2 = {
+    // WINSCP
+    /*.new =*/ arcfour_new,
+    /*.free =*/ arcfour_free,
+    /*.setiv =*/ arcfour_ssh2_setiv,
+    /*.setkey =*/ arcfour_ssh2_setkey,
+    /*.encrypt =*/ arcfour_ssh2_block,
+    /*.decrypt =*/ arcfour_ssh2_block,
+    NULL, NULL, // WINSCP
+    /*.ssh2_id =*/ "arcfour128",
+    /*.blksize =*/ 1,
+    /*.real_keybits =*/ 128,
+    /*.padded_keybytes =*/ 16,
+    /*.flags =*/ 0,
+    /*.text_name =*/ "Arcfour-128",
+    NULL, NULL, // WINSCP
+};
+
+const ssh_cipheralg ssh_arcfour256_ssh2 = {
+    // WINSCP
+    /*.new =*/ arcfour_new,
+    /*.free =*/ arcfour_free,
+    /*.setiv =*/ arcfour_ssh2_setiv,
+    /*.setkey =*/ arcfour_ssh2_setkey,
+    /*.encrypt =*/ arcfour_ssh2_block,
+    /*.decrypt =*/ arcfour_ssh2_block,
+    NULL, NULL, // WINSCP
+    /*.ssh2_id =*/ "arcfour256",
+    /*.blksize =*/ 1,
+    /*.real_keybits =*/ 256,
+    /*.padded_keybytes =*/ 32,
+    /*.flags =*/ 0,
+    /*.text_name =*/ "Arcfour-256",
+    NULL, NULL, // WINSCP
+};
+
+static const ssh_cipheralg *const arcfour_list[] = {
+    &ssh_arcfour256_ssh2,
+    &ssh_arcfour128_ssh2,
+};
+
+const ssh2_ciphers ssh2_arcfour = { lenof(arcfour_list), arcfour_list };

+ 623 - 0
source/putty/crypto/argon2.c

@@ -0,0 +1,623 @@
+/*
+ * Implementation of the Argon2 password hash function.
+ *
+ * My sources for the algorithm description and test vectors (the latter in
+ * test/cryptsuite.py) were the reference implementation on Github, and also
+ * the Internet-Draft description:
+ *
+ *   https://github.com/P-H-C/phc-winner-argon2
+ *   https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-argon2-13
+ */
+
+#include <assert.h>
+
+#ifndef WINSCP_VS
+#include "putty.h"
+#endif
+#include "ssh.h"
+#include "marshal.h"
+
+#ifndef WINSCP_VS
+/* ----------------------------------------------------------------------
+ * Argon2 uses data marshalling rules similar to SSH but with 32-bit integers
+ * stored little-endian. Start with some local BinarySink routines for storing
+ * a uint32 and a string in that fashion.
+ */
+
+static void BinarySink_put_uint32_le(BinarySink *bs, unsigned long val)
+{
+    unsigned char data[4];
+    PUT_32BIT_LSB_FIRST(data, val);
+    bs->write(bs, data, sizeof(data));
+}
+
+static void BinarySink_put_stringpl_le(BinarySink *bs, ptrlen pl)
+{
+    /* Check that the string length fits in a uint32, without doing a
+     * potentially implementation-defined shift of more than 31 bits */
+    assert((pl.len >> 31) < 2);
+
+    BinarySink_put_uint32_le(bs, pl.len);
+    bs->write(bs, pl.ptr, pl.len);
+}
+
+#define put_uint32_le(bs, val) \
+    BinarySink_put_uint32_le(BinarySink_UPCAST(bs), val)
+#define put_stringpl_le(bs, val) \
+    BinarySink_put_stringpl_le(BinarySink_UPCAST(bs), val)
+
+/* ----------------------------------------------------------------------
+ * Argon2 defines a hash-function family that's an extension of BLAKE2b to
+ * generate longer output digests, by repeatedly outputting half of a BLAKE2
+ * hash output and then re-hashing the whole thing until there are 64 or fewer
+ * bytes left to output. The spec calls this H' (a variant of the original
+ * hash it calls H, which is the unmodified BLAKE2b).
+ */
+
+static ssh_hash *hprime_new(unsigned length)
+{
+    ssh_hash *h = blake2b_new_general(length > 64 ? 64 : length);
+    put_uint32_le(h, length);
+    return h;
+}
+
+static void hprime_final(ssh_hash *h, unsigned length, void *vout)
+{
+    uint8_t *out = (uint8_t *)vout;
+
+    while (length > 64) {
+        uint8_t hashbuf[64];
+        ssh_hash_final(h, hashbuf);
+
+        memcpy(out, hashbuf, 32);
+        out += 32;
+        length -= 32;
+
+        h = blake2b_new_general(length > 64 ? 64 : length);
+        put_data(h, hashbuf, 64);
+
+        smemclr(hashbuf, sizeof(hashbuf));
+    }
+
+    ssh_hash_final(h, out);
+}
+
+/* Externally visible entry point for the long hash function. This is only
+ * used by testcrypt, so it would be overkill to set it up like a proper
+ * ssh_hash. */
+strbuf *argon2_long_hash(unsigned length, ptrlen data)
+{
+    ssh_hash *h = hprime_new(length);
+    put_datapl(h, data);
+    { // WINSCP
+    strbuf *out = strbuf_new();
+    hprime_final(h, length, strbuf_append(out, length));
+    return out;
+    } // WINSCP
+}
+#endif
+
+/* ----------------------------------------------------------------------
+ * Argon2's own mixing function G, which operates on 1Kb blocks of data.
+ *
+ * The definition of G in the spec takes two 1Kb blocks as input and produces
+ * a 1Kb output block. The first thing that happens to the input blocks is
+ * that they get XORed together, and then only the XOR output is used, so you
+ * could perfectly well regard G as a 1Kb->1Kb function.
+ */
+
+#ifdef WINSCP_VS
+static inline uint64_t ror(uint64_t x, unsigned rotation)
+{
+    #pragma warning(suppress: 4068)
+    #pragma warning(suppress: 4146)
+    unsigned lshift = 63 & -rotation, rshift = 63 & rotation;
+    return (x << lshift) | (x >> rshift);
+}
+
+static inline uint64_t trunc32(uint64_t x)
+{
+    return x & 0xFFFFFFFF;
+}
+
+/* Internal function similar to the BLAKE2b round, which mixes up four 64-bit
+ * words */
+static inline void GB(uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d)
+{
+    *a += *b + 2 * trunc32(*a) * trunc32(*b);
+    *d = ror(*d ^ *a, 32);
+    *c += *d + 2 * trunc32(*c) * trunc32(*d);
+    *b = ror(*b ^ *c, 24);
+    *a += *b + 2 * trunc32(*a) * trunc32(*b);
+    *d = ror(*d ^ *a, 16);
+    *c += *d + 2 * trunc32(*c) * trunc32(*d);
+    *b = ror(*b ^ *c, 63);
+}
+
+/* Higher-level internal function which mixes up sixteen 64-bit words. This is
+ * applied to different subsets of the 128 words in a kilobyte block, and the
+ * API here is designed to make it easy to apply in the circumstances the spec
+ * requires. In every call, the sixteen words form eight pairs adjacent in
+ * memory, whose addresses are in arithmetic progression. So the 16 input
+ * words are in[0], in[1], in[instep], in[instep+1], ..., in[7*instep],
+ * in[7*instep+1], and the 16 output words similarly. */
+static inline void P(uint64_t *out, unsigned outstep,
+                     uint64_t *in, unsigned instep)
+{
+    unsigned i; // WINSCP
+    for (i = 0; i < 8; i++) {
+        out[i*outstep] = in[i*instep];
+        out[i*outstep+1] = in[i*instep+1];
+    }
+
+    GB(out+0*outstep+0, out+2*outstep+0, out+4*outstep+0, out+6*outstep+0);
+    GB(out+0*outstep+1, out+2*outstep+1, out+4*outstep+1, out+6*outstep+1);
+    GB(out+1*outstep+0, out+3*outstep+0, out+5*outstep+0, out+7*outstep+0);
+    GB(out+1*outstep+1, out+3*outstep+1, out+5*outstep+1, out+7*outstep+1);
+
+    GB(out+0*outstep+0, out+2*outstep+1, out+5*outstep+0, out+7*outstep+1);
+    GB(out+0*outstep+1, out+3*outstep+0, out+5*outstep+1, out+6*outstep+0);
+    GB(out+1*outstep+0, out+3*outstep+1, out+4*outstep+0, out+6*outstep+1);
+    GB(out+1*outstep+1, out+2*outstep+0, out+4*outstep+1, out+7*outstep+0);
+}
+#endif
+
+/* The full G function, taking input blocks X and Y. The result of G is most
+ * often XORed into an existing output block, so this API is designed with
+ * that in mind: the mixing function's output is always XORed into whatever
+ * 1Kb of data is already at 'out'. */
+/*static*/ void G_xor(uint8_t *out, const uint8_t *X, const uint8_t *Y)
+#ifndef WINSCP_VS
+;
+#else
+{
+    uint64_t R[128], Q[128], Z[128];
+
+    unsigned i; // WINSCP
+    for (i = 0; i < 128; i++)
+        R[i] = GET_64BIT_LSB_FIRST(X + 8*i) ^ GET_64BIT_LSB_FIRST(Y + 8*i);
+
+    for (i = 0; i < 8; i++) // WINSCP
+        P(Q+16*i, 2, R+16*i, 2);
+
+    for (i = 0; i < 8; i++) // WINSCP
+        P(Z+2*i, 16, Q+2*i, 16);
+
+    for (i = 0; i < 128; i++) // WINSCP
+        PUT_64BIT_LSB_FIRST(out + 8*i,
+                            GET_64BIT_LSB_FIRST(out + 8*i) ^ R[i] ^ Z[i]);
+
+    smemclr(R, sizeof(R));
+    smemclr(Q, sizeof(Q));
+    smemclr(Z, sizeof(Z));
+}
+#endif
+
+struct blk { uint8_t data[1024]; };
+
+#ifndef WINSCP_VS
+void argon2_internal_vs(size_t jstart, size_t SL, size_t q, unsigned slice, bool d_mode, struct blk *B, size_t i, uint32_t p, struct blk * pin2i, size_t pass, size_t mprime, uint32_t t, uint32_t y, struct blk * ptmp2i, struct blk * pout2i);
+/* ----------------------------------------------------------------------
+ * The main Argon2 function.
+ */
+
+static void argon2_internal(uint32_t p, uint32_t T, uint32_t m, uint32_t t,
+                            uint32_t y, ptrlen P, ptrlen S, ptrlen K, ptrlen X,
+                            uint8_t *out)
+{
+    /*
+     * Start by hashing all the input data together: the four string arguments
+     * (password P, salt S, optional secret key K, optional associated data
+     * X), plus all the parameters for the function's memory and time usage.
+     *
+     * The output of this hash is the sole input to the subsequent mixing
+     * step: Argon2 does not preserve any more entropy from the inputs, it
+     * just makes it extra painful to get the final answer.
+     */
+    uint8_t h0[64];
+    {
+        ssh_hash *h = blake2b_new_general(64);
+        put_uint32_le(h, p);
+        put_uint32_le(h, T);
+        put_uint32_le(h, m);
+        put_uint32_le(h, t);
+        put_uint32_le(h, 0x13);        /* hash function version number */
+        put_uint32_le(h, y);
+        put_stringpl_le(h, P);
+        put_stringpl_le(h, S);
+        put_stringpl_le(h, K);
+        put_stringpl_le(h, X);
+        ssh_hash_final(h, h0);
+    }
+
+    { // WINSCP
+    // WINSCP struct blk { uint8_t data[1024]; };
+
+    /*
+     * Array of 1Kb blocks. The total size is (approximately) m, the
+     * caller-specified parameter for how much memory to use; the blocks are
+     * regarded as a rectangular array of p rows ('lanes') by q columns, where
+     * p is the 'parallelism' input parameter (the lanes can be processed
+     * concurrently up to a point) and q is whatever makes the product pq come
+     * to m.
+     *
+     * Additionally, each row is divided into four equal 'segments', which are
+     * important to the way the algorithm decides which blocks to use as input
+     * to each step of the function.
+     *
+     * The term 'slice' refers to a whole set of vertically aligned segments,
+     * i.e. slice 0 is the whole left quarter of the array, and slice 3 the
+     * whole right quarter.
+     */
+    size_t SL = m / (4*p); /* segment length: # of 1Kb blocks in a segment */
+    size_t q = 4 * SL;     /* width of the array: 4 segments times SL */
+    size_t mprime = q * p; /* total size of the array, approximately m */
+
+    /* Allocate the memory. */
+    struct blk *B = snewn(mprime, struct blk);
+    memset(B, 0, mprime * sizeof(struct blk));
+
+    /*
+     * Initial setup: fill the first two full columns of the array with data
+     * expanded from the starting hash h0. Each block is the result of using
+     * the long-output hash function H' to hash h0 itself plus the block's
+     * coordinates in the array.
+     */
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < p; i++) {
+        ssh_hash *h = hprime_new(1024);
+        put_data(h, h0, 64);
+        put_uint32_le(h, 0);
+        put_uint32_le(h, i);
+        hprime_final(h, 1024, B[i].data);
+    }
+    for (i = 0; i < p; i++) { // WINSCP
+        ssh_hash *h = hprime_new(1024);
+        put_data(h, h0, 64);
+        put_uint32_le(h, 1);
+        put_uint32_le(h, i);
+        hprime_final(h, 1024, B[i+p].data);
+    }
+
+    /*
+     * Declarations for the main loop.
+     *
+     * The basic structure of the main loop is going to involve processing the
+     * array one whole slice (vertically divided quarter) at a time. Usually
+     * we'll write a new value into every single block in the slice, except
+     * that in the initial slice on the first pass, we've already written
+     * values into the first two columns during the initial setup above. So
+     * 'jstart' indicates the starting index in each segment we process; it
+     * starts off as 2 so that we don't overwrite the initial setup, and then
+     * after the first slice is done, we set it to 0, and it stays there.
+     *
+     * d_mode indicates whether we're being data-dependent (true) or
+     * data-independent (false). In the hybrid Argon2id mode, we start off
+     * independent, and then once we've mixed things up enough, switch over to
+     * dependent mode to force long serial chains of computation.
+     */
+    { // WINSCP
+    size_t jstart = 2;
+    bool d_mode = (y == 0);
+    struct blk out2i, tmp2i, in2i;
+
+    /* Outermost loop: t whole passes from left to right over the array */
+    size_t pass; // WINSCP
+    for (pass = 0; pass < t; pass++) {
+
+        /* Within that, we process the array in its four main slices */
+        unsigned slice; // WINSCP
+        for (slice = 0; slice < 4; slice++) {
+
+            /* In Argon2id mode, if we're half way through the first pass,
+             * this is the moment to switch d_mode from false to true */
+            if (pass == 0 && slice == 2 && y == 2)
+                d_mode = true;
+
+            /* Loop over every segment in the slice (i.e. every row). So i is
+             * the y-coordinate of each block we process. */
+            { // WINSCP
+            size_t i; // WINSCP
+            for (i = 0; i < p; i++) {
+
+                argon2_internal_vs(jstart, SL, q, slice, d_mode, B, i, p, &in2i, pass, mprime, t, y, &tmp2i, &out2i); // WINSCP
+
+#endif
+#ifdef WINSCP_VS
+void argon2_internal_vs(size_t jstart, size_t SL, size_t q, unsigned slice, bool d_mode, struct blk *B, size_t i, uint32_t p, struct blk * pin2i, size_t pass, size_t mprime, uint32_t t, uint32_t y, struct blk * ptmp2i, struct blk * pout2i)
+{
+#define in2i (*pin2i)
+#define tmp2i (*ptmp2i)
+#define out2i (*pout2i)
+                /* And within that segment, process the blocks from left to
+                 * right, starting at 'jstart' (usually 0, but 2 in the first
+                 * slice). */
+                size_t jpre; // WINSCP
+                for (jpre = jstart; jpre < SL; jpre++) {
+
+                    /* j is the x-coordinate of each block we process, made up
+                     * of the slice number and the index 'jpre' within the
+                     * segment. */
+                    size_t j = slice * SL + jpre;
+
+                    /* jm1 is j-1 (mod q) */
+                    uint32_t jm1 = (j == 0 ? q-1 : j-1);
+
+                    /*
+                     * Construct two 32-bit pseudorandom integers J1 and J2.
+                     * This is the part of the algorithm that varies between
+                     * the data-dependent and independent modes.
+                     */
+                    uint32_t J1, J2;
+                    if (d_mode) {
+                        /*
+                         * Data-dependent: grab the first 64 bits of the block
+                         * to the left of this one.
+                         */
+                        J1 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data);
+                        J2 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data + 4);
+                    } else {
+                        /*
+                         * Data-independent: generate pseudorandom data by
+                         * hashing a sequence of preimage blocks that include
+                         * all our input parameters, plus the coordinates of
+                         * this point in the algorithm (array position and
+                         * pass number) to make all the hash outputs distinct.
+                         *
+                         * The hash we use is G itself, applied twice. So we
+                         * generate 1Kb of data at a time, which is enough for
+                         * 128 (J1,J2) pairs. Hence we only need to do the
+                         * hashing if our index within the segment is a
+                         * multiple of 128, or if we're at the very start of
+                         * the algorithm (in which case we started at 2 rather
+                         * than 0). After that we can just keep picking data
+                         * out of our most recent hash output.
+                         */
+                        if (jpre == jstart || jpre % 128 == 0) {
+                            /*
+                             * Hash preimage is mostly zeroes, with a
+                             * collection of assorted integer values we had
+                             * anyway.
+                             */
+                            memset(in2i.data, 0, sizeof(in2i.data));
+                            PUT_64BIT_LSB_FIRST(in2i.data +  0, pass);
+                            PUT_64BIT_LSB_FIRST(in2i.data +  8, i);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 16, slice);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 24, mprime);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 32, t);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 40, y);
+                            PUT_64BIT_LSB_FIRST(in2i.data + 48, jpre / 128 + 1);
+
+                            /*
+                             * Now apply G twice to generate the hash output
+                             * in out2i.
+                             */
+                            memset(tmp2i.data, 0, sizeof(tmp2i.data));
+                            G_xor(tmp2i.data, tmp2i.data, in2i.data);
+                            memset(out2i.data, 0, sizeof(out2i.data));
+                            G_xor(out2i.data, out2i.data, tmp2i.data);
+                        }
+
+                        /*
+                         * Extract J1 and J2 from the most recent hash output
+                         * (whether we've just computed it or not).
+                         */
+                        J1 = GET_32BIT_LSB_FIRST(
+                            out2i.data + 8 * (jpre % 128));
+                        J2 = GET_32BIT_LSB_FIRST(
+                            out2i.data + 8 * (jpre % 128) + 4);
+                    }
+
+                    /*
+                     * Now convert J1 and J2 into the index of an existing
+                     * block of the array to use as input to this step. This
+                     * is fairly fiddly.
+                     *
+                     * The easy part: the y-coordinate of the input block is
+                     * obtained by reducing J2 mod p, except that at the very
+                     * start of the algorithm (processing the first slice on
+                     * the first pass) we simply use the same y-coordinate as
+                     * our output block.
+                     *
+                     * Note that it's safe to use the ordinary % operator
+                     * here, without any concern for timing side channels: in
+                     * data-independent mode J2 is not correlated to any
+                     * secrets, and in data-dependent mode we're going to be
+                     * giving away side-channel data _anyway_ when we use it
+                     * as an array index (and by assumption we don't care,
+                     * because it's already massively randomised from the real
+                     * inputs).
+                     */
+                    { // WINSCP
+                    uint32_t index_l = (pass == 0 && slice == 0) ? i : J2 % p;
+
+                    /*
+                     * The hard part: which block in this array row do we use?
+                     *
+                     * First, we decide what the possible candidates are. This
+                     * requires some case analysis, and depends on whether the
+                     * array row is the same one we're writing into or not.
+                     *
+                     * If it's not the same row: we can't use any block from
+                     * the current slice (because the segments within a slice
+                     * have to be processable in parallel, so in a concurrent
+                     * implementation those blocks are potentially in the
+                     * process of being overwritten by other threads). But the
+                     * other three slices are fair game, except that in the
+                     * first pass, slices to the right of us won't have had
+                     * any values written into them yet at all.
+                     *
+                     * If it is the same row, we _are_ allowed to use blocks
+                     * from the current slice, but only the ones before our
+                     * current position.
+                     *
+                     * In both cases, we also exclude the individual _column_
+                     * just to the left of the current one. (The block
+                     * immediately to our left is going to be the _other_
+                     * input to G, but the spec also says that we avoid that
+                     * column even in a different row.)
+                     *
+                     * All of this means that we end up choosing from a
+                     * cyclically contiguous interval of blocks within this
+                     * lane, but the start and end points require some thought
+                     * to get them right.
+                     */
+
+                    /* Start position is the beginning of the _next_ slice
+                     * (containing data from the previous pass), unless we're
+                     * on pass 0, where the start position has to be 0. */
+                    uint32_t Wstart = (pass == 0 ? 0 : (slice + 1) % 4 * SL);
+
+                    /* End position splits up by cases. */
+                    uint32_t Wend;
+                    if (index_l == i) {
+                        /* Same lane as output: we can use anything up to (but
+                         * not including) the block immediately left of us. */
+                        Wend = jm1;
+                    } else {
+                        /* Different lane from output: we can use anything up
+                         * to the previous slice boundary, or one less than
+                         * that if we're at the very left edge of our slice
+                         * right now. */
+                        Wend = SL * slice;
+                        if (jpre == 0)
+                            Wend = (Wend + q-1) % q;
+                    }
+
+                    /* Total number of blocks available to choose from */
+                    { // WINSCP
+                    uint32_t Wsize = (Wend + q - Wstart) % q;
+
+                    /* Fiddly computation from the spec that chooses from the
+                     * available blocks, in a deliberately non-uniform
+                     * fashion, using J1 as pseudorandom input data. Output is
+                     * zz which is the index within our contiguous interval. */
+                    uint32_t x = ((uint64_t)J1 * J1) >> 32;
+                    uint32_t y = ((uint64_t)Wsize * x) >> 32;
+                    uint32_t zz = Wsize - 1 - y;
+
+                    /* And index_z is the actual x coordinate of the block we
+                     * want. */
+                    uint32_t index_z = (Wstart + zz) % q;
+
+                    /* Phew! Combine that block with the one immediately to
+                     * our left, and XOR over the top of whatever is already
+                     * in our current output block. */
+                    G_xor(B[i + p * j].data, B[i + p * jm1].data,
+                          B[index_l + p * index_z].data);
+                    } // WINSCP
+                    } // WINSCP
+                }
+#undef in2i
+#undef tmp2i
+#undef out2i
+}
+#endif
+#ifndef WINSCP_VS
+            }
+
+            /* We've finished processing a slice. Reset jstart to 0. It will
+             * onily _not_ have been 0 if this was pass 0 slice 0, in which
+             * case it still had its initial value of 2 to avoid the starting
+             * data. */
+            jstart = 0;
+            } // WINSCP
+        }
+    }
+
+    /*
+     * The main output is all done. Final output works by taking the XOR of
+     * all the blocks in the rightmost column of the array, and then using
+     * that as input to our long hash H'. The output of _that_ is what we
+     * deliver to the caller.
+     */
+
+    { // WINSCP
+    struct blk C = B[p * (q-1)];
+    size_t i; // WINSCP
+    for (i = 1; i < p; i++)
+        memxor(C.data, C.data, B[i + p * (q-1)].data, 1024);
+
+    {
+        ssh_hash *h = hprime_new(T);
+        put_data(h, C.data, 1024);
+        hprime_final(h, T, out);
+    }
+
+    /*
+     * Clean up.
+     */
+    smemclr(out2i.data, sizeof(out2i.data));
+    smemclr(tmp2i.data, sizeof(tmp2i.data));
+    smemclr(in2i.data, sizeof(in2i.data));
+    smemclr(C.data, sizeof(C.data));
+    smemclr(B, mprime * sizeof(struct blk));
+    sfree(B);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+/*
+ * Wrapper function that appends to a strbuf (which sshpubk.c will want).
+ */
+void argon2(Argon2Flavour flavour, uint32_t mem, uint32_t passes,
+            uint32_t parallel, uint32_t taglen,
+            ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out)
+{
+    argon2_internal(parallel, taglen, mem, passes, flavour,
+                    P, S, K, X, strbuf_append(out, taglen));
+}
+
+/*
+ * Wrapper function which dynamically chooses the number of passes to run in
+ * order to hit an approximate total amount of CPU time. Writes the result
+ * into 'passes'.
+ */
+void argon2_choose_passes(
+    Argon2Flavour flavour, uint32_t mem,
+    uint32_t milliseconds, uint32_t *passes,
+    uint32_t parallel, uint32_t taglen,
+    ptrlen P, ptrlen S, ptrlen K, ptrlen X,
+    strbuf *out)
+{
+    unsigned long desired_time = (TICKSPERSEC * milliseconds) / 1000 /*WINSCP*/ * 3;
+
+    /*
+     * We only need the time taken to be approximately right, so we
+     * scale up the number of passes geometrically, which avoids
+     * taking O(t^2) time to find a pass count taking time t.
+     *
+     * Using the Fibonacci numbers is slightly nicer than the obvious
+     * approach of powers of 2, because it's still very easy to
+     * compute, and grows less fast (powers of 1.6 instead of 2), so
+     * you get just a touch more precision.
+     */
+    uint32_t a = 1, b = 1;
+
+    while (true) {
+        unsigned long start_time = GETTICKCOUNT();
+        argon2(flavour, mem, b, parallel, taglen, P, S, K, X, out);
+        { // WINSCP
+        unsigned long ticks = GETTICKCOUNT() - start_time;
+
+        /* But just in case computers get _too_ fast, we have to cap
+         * the growth before it gets past the uint32_t upper bound! So
+         * if computing a+b would overflow, stop here. */
+
+        if (ticks >= desired_time || a > (uint32_t)~b) {
+            *passes = b;
+            return;
+        } else {
+            strbuf_clear(out);
+
+            /* Next Fibonacci number: replace (a, b) with (b, a+b) */
+            b += a;
+            a = b - a;
+        }
+        } // WINSCP
+    }
+}
+#endif

+ 118 - 0
source/putty/crypto/bcrypt.c

@@ -0,0 +1,118 @@
+/*
+ * 'bcrypt' password hash function, for PuTTY's import/export of
+ * OpenSSH encrypted private key files.
+ *
+ * This is not really the same as the original bcrypt; OpenSSH has
+ * modified it in various ways, and of course we have to do the same.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include "ssh.h"
+#include "blowfish.h"
+
+BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes,
+                              const unsigned char *salt, int saltbytes)
+{
+    int i;
+    BlowfishContext *ctx;
+
+    ctx = blowfish_make_context();
+    blowfish_initkey(ctx);
+    blowfish_expandkey(ctx, key, keybytes, salt, saltbytes);
+
+    /* Original bcrypt replaces this fixed loop count with the
+     * variable cost. OpenSSH instead iterates the whole thing more
+     * than once if it wants extra rounds. */
+    for (i = 0; i < 64; i++) {
+        blowfish_expandkey(ctx, salt, saltbytes, NULL, 0);
+        blowfish_expandkey(ctx, key, keybytes, NULL, 0);
+    }
+
+    return ctx;
+}
+
+void bcrypt_hash(const unsigned char *key, int keybytes,
+                 const unsigned char *salt, int saltbytes,
+                 unsigned char output[32])
+{
+    BlowfishContext *ctx;
+    int i;
+
+    ctx = bcrypt_setup(key, keybytes, salt, saltbytes);
+    /* This was quite a nice starting string until it ran into
+     * little-endian Blowfish :-/ */
+    memcpy(output, "cyxOmorhcitawolBhsiftawSanyDetim", 32);
+    for (i = 0; i < 64; i++) {
+        blowfish_lsb_encrypt_ecb(output, 32, ctx);
+    }
+    blowfish_free_context(ctx);
+}
+
+void bcrypt_genblock(int counter,
+                     const unsigned char hashed_passphrase[64],
+                     const unsigned char *salt, int saltbytes,
+                     unsigned char output[32])
+{
+    unsigned char hashed_salt[64];
+
+    /* Hash the input salt with the counter value optionally suffixed
+     * to get our real 32-byte salt */
+    ssh_hash *h = ssh_hash_new(&ssh_sha512);
+    put_data(h, salt, saltbytes);
+    if (counter)
+        put_uint32(h, counter);
+    ssh_hash_final(h, hashed_salt);
+
+    bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output);
+
+    smemclr(&hashed_salt, sizeof(hashed_salt));
+}
+
+void openssh_bcrypt(ptrlen passphrase, ptrlen salt,
+                    int rounds, unsigned char *out, int outbytes)
+{
+    unsigned char hashed_passphrase[64];
+    unsigned char block[32], outblock[32];
+    const unsigned char *thissalt;
+    int thissaltbytes;
+    int modulus, residue, i, j, round;
+
+    /* Hash the passphrase to get the bcrypt key material */
+    hash_simple(&ssh_sha512, passphrase, hashed_passphrase);
+
+    /* We output key bytes in a scattered fashion to meld all output
+     * key blocks into all parts of the output. To do this, we pick a
+     * modulus, and we output the key bytes to indices of out[] in the
+     * following order: first the indices that are multiples of the
+     * modulus, then the ones congruent to 1 mod modulus, etc. Each of
+     * those passes consumes exactly one block output from
+     * bcrypt_genblock, so we must pick a modulus large enough that at
+     * most 32 bytes are used in the pass. */
+    modulus = (outbytes + 31) / 32;
+
+    for (residue = 0; residue < modulus; residue++) {
+        /* Our output block of data is the XOR of all blocks generated
+         * by bcrypt in the following loop */
+        memset(outblock, 0, sizeof(outblock));
+
+        thissalt = salt.ptr;
+        thissaltbytes = salt.len;
+        for (round = 0; round < rounds; round++) {
+            bcrypt_genblock(round == 0 ? residue+1 : 0,
+                            hashed_passphrase,
+                            thissalt, thissaltbytes, block);
+            /* Each subsequent bcrypt call reuses the previous one's
+             * output as its salt */
+            thissalt = block;
+            thissaltbytes = 32;
+
+            for (i = 0; i < 32; i++)
+                outblock[i] ^= block[i];
+        }
+
+        for (i = residue, j = 0; i < outbytes; i += modulus, j++)
+            out[i] = outblock[j];
+    }
+    smemclr(&hashed_passphrase, sizeof(hashed_passphrase));
+}

+ 242 - 0
source/putty/crypto/blake2.c

@@ -0,0 +1,242 @@
+/*
+ * BLAKE2 (RFC 7693) implementation for PuTTY.
+ *
+ * The BLAKE2 hash family includes BLAKE2s, in which the hash state is
+ * operated on as a collection of 32-bit integers, and BLAKE2b, based
+ * on 64-bit integers. At present this code implements BLAKE2b only.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+
+static inline uint64_t ror(uint64_t x, unsigned rotation)
+{
+#pragma option push -w-ngu // WINSCP
+    unsigned lshift = 63 & -rotation, rshift = 63 & rotation;
+#pragma option pop // WINSCP
+    return (x << lshift) | (x >> rshift);
+}
+
+/* RFC 7963 section 2.1 */
+enum { R1 = 32, R2 = 24, R3 = 16, R4 = 63 };
+
+/* RFC 7693 section 2.6 */
+static const uint64_t iv[] = {
+    // WINSCP (ULL)
+    0x6a09e667f3bcc908ULL,                /* floor(2^64 * frac(sqrt(2)))  */
+    0xbb67ae8584caa73bULL,                /* floor(2^64 * frac(sqrt(3)))  */
+    0x3c6ef372fe94f82bULL,                /* floor(2^64 * frac(sqrt(5)))  */
+    0xa54ff53a5f1d36f1ULL,                /* floor(2^64 * frac(sqrt(7)))  */
+    0x510e527fade682d1ULL,                /* floor(2^64 * frac(sqrt(11))) */
+    0x9b05688c2b3e6c1fULL,                /* floor(2^64 * frac(sqrt(13))) */
+    0x1f83d9abfb41bd6bULL,                /* floor(2^64 * frac(sqrt(17))) */
+    0x5be0cd19137e2179ULL,                /* floor(2^64 * frac(sqrt(19))) */
+};
+
+/* RFC 7693 section 2.7 */
+static const unsigned char sigma[][16] = {
+    { 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15},
+    {14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3},
+    {11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4},
+    { 7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8},
+    { 9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13},
+    { 2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9},
+    {12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11},
+    {13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10},
+    { 6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5},
+    {10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13,  0},
+    /* This array recycles if you have more than 10 rounds. BLAKE2b
+     * has 12, so we repeat the first two rows again. */
+    { 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15},
+    {14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3},
+};
+
+static inline void g_half(uint64_t v[16], unsigned a, unsigned b, unsigned c,
+                          unsigned d, uint64_t x, unsigned r1, unsigned r2)
+{
+    v[a] += v[b] + x;
+    v[d] ^= v[a];
+    v[d] = ror(v[d], r1);
+    v[c] += v[d];
+    v[b] ^= v[c];
+    v[b] = ror(v[b], r2);
+}
+
+static inline void g(uint64_t v[16], unsigned a, unsigned b, unsigned c,
+                     unsigned d, uint64_t x, uint64_t y)
+{
+    g_half(v, a, b, c, d, x, R1, R2);
+    g_half(v, a, b, c, d, y, R3, R4);
+}
+
+static inline void f(uint64_t h[8], uint64_t m[16], uint64_t offset_hi,
+                     uint64_t offset_lo, unsigned final)
+{
+    uint64_t v[16];
+    memcpy(v, h, 8 * sizeof(*v));
+    memcpy(v + 8, iv, 8 * sizeof(*v));
+    v[12] ^= offset_lo;
+    v[13] ^= offset_hi;
+    v[14] ^= -(uint64_t)final;
+    { // WINSCP
+    unsigned round; // WINSCP
+    for (round = 0; round < 12; round++) {
+        const unsigned char *s = sigma[round];
+        g(v,  0,  4,  8, 12, m[s[ 0]], m[s[ 1]]);
+        g(v,  1,  5,  9, 13, m[s[ 2]], m[s[ 3]]);
+        g(v,  2,  6, 10, 14, m[s[ 4]], m[s[ 5]]);
+        g(v,  3,  7, 11, 15, m[s[ 6]], m[s[ 7]]);
+        g(v,  0,  5, 10, 15, m[s[ 8]], m[s[ 9]]);
+        g(v,  1,  6, 11, 12, m[s[10]], m[s[11]]);
+        g(v,  2,  7,  8, 13, m[s[12]], m[s[13]]);
+        g(v,  3,  4,  9, 14, m[s[14]], m[s[15]]);
+    }
+    { // WINSCP
+    unsigned i; // WINSCP
+    for (i = 0; i < 8; i++)
+        h[i] ^= v[i] ^ v[i+8];
+    smemclr(v, sizeof(v));
+    } // WINSCP
+    } // WINSCP
+}
+
+static inline void f_outer(uint64_t h[8], uint8_t blk[128], uint64_t offset_hi,
+                           uint64_t offset_lo, unsigned final)
+{
+    uint64_t m[16];
+    unsigned i; // WINSCP
+    for (i = 0; i < 16; i++)
+        m[i] = GET_64BIT_LSB_FIRST(blk + 8*i);
+    f(h, m, offset_hi, offset_lo, final);
+    smemclr(m, sizeof(m));
+}
+
+typedef struct blake2b {
+    uint64_t h[8];
+    unsigned hashlen;
+
+    uint8_t block[128];
+    size_t used;
+    uint64_t lenhi, lenlo;
+
+    BinarySink_IMPLEMENTATION;
+    ssh_hash hash;
+} blake2b;
+
+static void blake2b_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *blake2b_new_inner(unsigned hashlen)
+{
+    assert(hashlen <= ssh_blake2b.hlen);
+
+    { // WINSCP
+    blake2b *s = snew(blake2b);
+    s->hash.vt = &ssh_blake2b;
+    s->hashlen = hashlen;
+    BinarySink_INIT(s, blake2b_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+    } // WINSCP
+}
+
+static ssh_hash *blake2b_new(const ssh_hashalg *alg)
+{
+    return blake2b_new_inner(alg->hlen);
+}
+
+ssh_hash *blake2b_new_general(unsigned hashlen)
+{
+    ssh_hash *h = blake2b_new_inner(hashlen);
+    ssh_hash_reset(h);
+    return h;
+}
+
+static void blake2b_reset(ssh_hash *hash)
+{
+    blake2b *s = container_of(hash, blake2b, hash);
+
+    /* Initialise the hash to the standard IV */
+    memcpy(s->h, iv, sizeof(s->h));
+
+    /* XOR in the parameters: secret key length (here always 0) in
+     * byte 1, and hash length in byte 0. */
+    s->h[0] ^= 0x01010000 ^ s->hashlen;
+
+    s->used = 0;
+    s->lenhi = s->lenlo = 0;
+}
+
+static void blake2b_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    blake2b *copy = container_of(hcopy, blake2b, hash);
+    blake2b *orig = container_of(horig, blake2b, hash);
+
+    memcpy(copy, orig, sizeof(*copy));
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void blake2b_free(ssh_hash *hash)
+{
+    blake2b *s = container_of(hash, blake2b, hash);
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void blake2b_write(BinarySink *bs, const void *vp, size_t len)
+{
+    blake2b *s = BinarySink_DOWNCAST(bs, blake2b);
+    const uint8_t *p = vp;
+
+    while (len > 0) {
+        if (s->used == sizeof(s->block)) {
+            f_outer(s->h, s->block, s->lenhi, s->lenlo, 0);
+            s->used = 0;
+        }
+
+        { // WINSCP
+        size_t chunk = sizeof(s->block) - s->used;
+        if (chunk > len)
+            chunk = len;
+
+        memcpy(s->block + s->used, p, chunk);
+        s->used += chunk;
+        p += chunk;
+        len -= chunk;
+
+        s->lenlo += chunk;
+        s->lenhi += (s->lenlo < chunk);
+        } // WINSCP
+    }
+}
+
+static void blake2b_digest(ssh_hash *hash, uint8_t *digest)
+{
+    blake2b *s = container_of(hash, blake2b, hash);
+
+    memset(s->block + s->used, 0, sizeof(s->block) - s->used);
+    f_outer(s->h, s->block, s->lenhi, s->lenlo, 1);
+
+    { // WINSCP
+    uint8_t hash_pre[128];
+    unsigned i; // WINSCP
+    for (i = 0; i < 8; i++)
+        PUT_64BIT_LSB_FIRST(hash_pre + 8*i, s->h[i]);
+    memcpy(digest, hash_pre, s->hashlen);
+    smemclr(hash_pre, sizeof(hash_pre));
+    } // WINSCP
+}
+
+const ssh_hashalg ssh_blake2b = {
+    // WINSCP
+    /*.new =*/ blake2b_new,
+    /*.reset =*/ blake2b_reset,
+    /*.copyfrom =*/ blake2b_copyfrom,
+    /*.digest =*/ blake2b_digest,
+    /*.free =*/ blake2b_free,
+    /*.hlen =*/ 64,
+    /*.blocklen =*/ 128,
+    HASHALG_NAMES_BARE("BLAKE2b-64"),
+    NULL, // WINSCP
+};

+ 708 - 0
source/putty/crypto/blowfish.c

@@ -0,0 +1,708 @@
+/*
+ * Blowfish implementation for PuTTY.
+ *
+ * Coded from scratch from the algorithm description.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include "ssh.h"
+#include "blowfish.h"
+
+struct BlowfishContext {
+    uint32_t S0[256], S1[256], S2[256], S3[256], P[18];
+    uint32_t iv0, iv1;                 /* for CBC mode */
+};
+
+/*
+ * The Blowfish init data: hex digits of the fractional part of pi.
+ * (ie pi as a hex fraction is 3.243F6A8885A308D3...)
+ *
+ * If you have Simon Tatham's 'spigot' exact real calculator
+ * available, or any other method of generating 8336 fractional hex
+ * digits of pi on standard output, you can regenerate these tables
+ * exactly as below using the following Perl script (adjusting the
+ * first line or two if your pi-generator is not spigot).
+
+open my $spig, "spigot -n -B16 -d8336 pi |";
+read $spig, $ignore, 2; # throw away the leading "3."
+for my $name ("parray", "sbox0".."sbox3") {
+    print "static const uint32_t ${name}[] = {\n";
+    my $len = $name eq "parray" ? 18 : 256;
+    for my $i (1..$len) {
+        read $spig, $word, 8;
+        printf "%s0x%s,", ($i%6==1 ? "    " : " "), uc $word;
+        print "\n" if ($i == $len || $i%6 == 0);
+    }
+    print "};\n\n";
+}
+close $spig;
+
+ */
+static const uint32_t parray[] = {
+    0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0,
+    0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C,
+    0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B,
+};
+
+static const uint32_t sbox0[] = {
+    0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96,
+    0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16,
+    0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658,
+    0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013,
+    0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0, 0x603A180E,
+    0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
+    0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6,
+    0xB4CC5C34, 0x1141E8CE, 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A,
+    0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C,
+    0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193,
+    0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 0xEF845D5D, 0xE98575B1,
+    0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
+    0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A,
+    0x670C9C61, 0xABD388F0, 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3,
+    0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176,
+    0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE,
+    0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 0x4ED3AA62, 0x363F7706,
+    0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
+    0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B,
+    0x976CE0BD, 0x04C006BA, 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463,
+    0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C,
+    0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3,
+    0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A,
+    0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
+    0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760,
+    0x53317B48, 0x3E00DF82, 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB,
+    0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8,
+    0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B,
+    0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33,
+    0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
+    0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0,
+    0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C,
+    0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777,
+    0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299,
+    0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 0x165FA266, 0x80957705,
+    0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
+    0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E,
+    0x226800BB, 0x57B8E0AF, 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA,
+    0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9,
+    0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915,
+    0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 0x08BA6FB5, 0x571BE91F,
+    0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
+    0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A,
+};
+
+static const uint32_t sbox1[] = {
+    0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D,
+    0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1,
+    0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65,
+    0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1,
+    0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9,
+    0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
+    0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D,
+    0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD,
+    0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC,
+    0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41,
+    0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 0x4E548B38, 0x4F6DB908,
+    0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
+    0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124,
+    0x501ADDE6, 0x9F84CD87, 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C,
+    0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908,
+    0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD,
+    0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 0x043556F1, 0xD7A3C76B,
+    0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
+    0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA,
+    0x2965DCB9, 0x99E71D0F, 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A,
+    0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D,
+    0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66,
+    0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5,
+    0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
+    0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96,
+    0x0334FE1E, 0xAA0363CF, 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14,
+    0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA,
+    0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7,
+    0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 0xF837889A, 0x97E32D77,
+    0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
+    0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054,
+    0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73,
+    0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA,
+    0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105,
+    0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 0xCF62A1F2, 0x5B8D2646,
+    0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
+    0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA,
+    0x1DADF43E, 0x233F7061, 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB,
+    0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E,
+    0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC,
+    0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD,
+    0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
+    0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7,
+};
+
+static const uint32_t sbox2[] = {
+    0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7,
+    0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF,
+    0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF,
+    0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504,
+    0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 0x28507825, 0x530429F4,
+    0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
+    0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC,
+    0xCE78A399, 0x406B2A42, 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B,
+    0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332,
+    0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527,
+    0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 0x55A867BC, 0xA1159A58,
+    0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
+    0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22,
+    0x48C1133F, 0xC70F86DC, 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17,
+    0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60,
+    0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115,
+    0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99,
+    0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
+    0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74,
+    0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D,
+    0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3,
+    0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3,
+    0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 0x37392EB3, 0xCC115979,
+    0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
+    0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA,
+    0x3D25BDD8, 0xE2E1C3C9, 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A,
+    0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086,
+    0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC,
+    0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 0x77A057BE, 0xBDE8AE24,
+    0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
+    0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84,
+    0x846A0E79, 0x915F95E2, 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C,
+    0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09,
+    0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10,
+    0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 0xDCB7DA83, 0x573906FE,
+    0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
+    0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0,
+    0x006058AA, 0x30DC7D62, 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634,
+    0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188,
+    0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC,
+    0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8,
+    0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
+    0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0,
+};
+
+static const uint32_t sbox3[] = {
+    0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742,
+    0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B,
+    0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79,
+    0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6,
+    0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A,
+    0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
+    0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1,
+    0x4BA99586, 0xEF5562E9, 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59,
+    0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797,
+    0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28,
+    0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 0xE029AC71, 0xE019A5E6,
+    0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,
+    0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA,
+    0x03A16125, 0x0564F0BD, 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A,
+    0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5,
+    0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F,
+    0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE,
+    0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
+    0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD,
+    0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB,
+    0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB,
+    0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370,
+    0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC,
+    0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
+    0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC,
+    0xBB3A792B, 0x344525BD, 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9,
+    0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A,
+    0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F,
+    0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 0xBF97222C, 0x15E6FC2A,
+    0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
+    0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B,
+    0x4C98A0BE, 0x3278E964, 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E,
+    0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E,
+    0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F,
+    0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 0xF523F357, 0xA6327623,
+    0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
+    0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A,
+    0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6,
+    0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3,
+    0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060,
+    0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C,
+    0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
+    0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6,
+};
+
+#define Fprime(a,b,c,d) ( ( (S0[a] + S1[b]) ^ S2[c] ) + S3[d] )
+#define F(x) Fprime( ((x>>24)&0xFF), ((x>>16)&0xFF), ((x>>8)&0xFF), (x&0xFF) )
+#define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t )
+
+static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output,
+                             BlowfishContext * ctx)
+{
+    uint32_t *S0 = ctx->S0;
+    uint32_t *S1 = ctx->S1;
+    uint32_t *S2 = ctx->S2;
+    uint32_t *S3 = ctx->S3;
+    uint32_t *P = ctx->P;
+    uint32_t t;
+
+    ROUND(0);
+    ROUND(1);
+    ROUND(2);
+    ROUND(3);
+    ROUND(4);
+    ROUND(5);
+    ROUND(6);
+    ROUND(7);
+    ROUND(8);
+    ROUND(9);
+    ROUND(10);
+    ROUND(11);
+    ROUND(12);
+    ROUND(13);
+    ROUND(14);
+    ROUND(15);
+    xL ^= P[16];
+    xR ^= P[17];
+
+    output[0] = xR;
+    output[1] = xL;
+}
+
+static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output,
+                             BlowfishContext * ctx)
+{
+    uint32_t *S0 = ctx->S0;
+    uint32_t *S1 = ctx->S1;
+    uint32_t *S2 = ctx->S2;
+    uint32_t *S3 = ctx->S3;
+    uint32_t *P = ctx->P;
+    uint32_t t;
+
+    ROUND(17);
+    ROUND(16);
+    ROUND(15);
+    ROUND(14);
+    ROUND(13);
+    ROUND(12);
+    ROUND(11);
+    ROUND(10);
+    ROUND(9);
+    ROUND(8);
+    ROUND(7);
+    ROUND(6);
+    ROUND(5);
+    ROUND(4);
+    ROUND(3);
+    ROUND(2);
+    xL ^= P[1];
+    xR ^= P[0];
+
+    output[0] = xR;
+    output[1] = xL;
+}
+
+static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len,
+                                     BlowfishContext * ctx)
+{
+    uint32_t xL, xR, out[2], iv0, iv1;
+
+    assert((len & 7) == 0);
+
+    iv0 = ctx->iv0;
+    iv1 = ctx->iv1;
+
+    while (len > 0) {
+        xL = GET_32BIT_LSB_FIRST(blk);
+        xR = GET_32BIT_LSB_FIRST(blk + 4);
+        iv0 ^= xL;
+        iv1 ^= xR;
+        blowfish_encrypt(iv0, iv1, out, ctx);
+        iv0 = out[0];
+        iv1 = out[1];
+        PUT_32BIT_LSB_FIRST(blk, iv0);
+        PUT_32BIT_LSB_FIRST(blk + 4, iv1);
+        blk += 8;
+        len -= 8;
+    }
+
+    ctx->iv0 = iv0;
+    ctx->iv1 = iv1;
+}
+
+void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx)
+{
+    unsigned char *blk = (unsigned char *)vblk;
+    uint32_t xL, xR, out[2];
+
+    assert((len & 7) == 0);
+
+    while (len > 0) {
+        xL = GET_32BIT_LSB_FIRST(blk);
+        xR = GET_32BIT_LSB_FIRST(blk + 4);
+        blowfish_encrypt(xL, xR, out, ctx);
+        PUT_32BIT_LSB_FIRST(blk, out[0]);
+        PUT_32BIT_LSB_FIRST(blk + 4, out[1]);
+        blk += 8;
+        len -= 8;
+    }
+}
+
+static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len,
+                                     BlowfishContext * ctx)
+{
+    uint32_t xL, xR, out[2], iv0, iv1;
+
+    assert((len & 7) == 0);
+
+    iv0 = ctx->iv0;
+    iv1 = ctx->iv1;
+
+    while (len > 0) {
+        xL = GET_32BIT_LSB_FIRST(blk);
+        xR = GET_32BIT_LSB_FIRST(blk + 4);
+        blowfish_decrypt(xL, xR, out, ctx);
+        iv0 ^= out[0];
+        iv1 ^= out[1];
+        PUT_32BIT_LSB_FIRST(blk, iv0);
+        PUT_32BIT_LSB_FIRST(blk + 4, iv1);
+        iv0 = xL;
+        iv1 = xR;
+        blk += 8;
+        len -= 8;
+    }
+
+    ctx->iv0 = iv0;
+    ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len,
+                                     BlowfishContext * ctx)
+{
+    uint32_t xL, xR, out[2], iv0, iv1;
+
+    assert((len & 7) == 0);
+
+    iv0 = ctx->iv0;
+    iv1 = ctx->iv1;
+
+    while (len > 0) {
+        xL = GET_32BIT_MSB_FIRST(blk);
+        xR = GET_32BIT_MSB_FIRST(blk + 4);
+        iv0 ^= xL;
+        iv1 ^= xR;
+        blowfish_encrypt(iv0, iv1, out, ctx);
+        iv0 = out[0];
+        iv1 = out[1];
+        PUT_32BIT_MSB_FIRST(blk, iv0);
+        PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+        blk += 8;
+        len -= 8;
+    }
+
+    ctx->iv0 = iv0;
+    ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len,
+                                     BlowfishContext * ctx)
+{
+    uint32_t xL, xR, out[2], iv0, iv1;
+
+    assert((len & 7) == 0);
+
+    iv0 = ctx->iv0;
+    iv1 = ctx->iv1;
+
+    while (len > 0) {
+        xL = GET_32BIT_MSB_FIRST(blk);
+        xR = GET_32BIT_MSB_FIRST(blk + 4);
+        blowfish_decrypt(xL, xR, out, ctx);
+        iv0 ^= out[0];
+        iv1 ^= out[1];
+        PUT_32BIT_MSB_FIRST(blk, iv0);
+        PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+        iv0 = xL;
+        iv1 = xR;
+        blk += 8;
+        len -= 8;
+    }
+
+    ctx->iv0 = iv0;
+    ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_sdctr(unsigned char *blk, int len,
+                                     BlowfishContext * ctx)
+{
+    uint32_t b[2], iv0, iv1, tmp;
+
+    assert((len & 7) == 0);
+
+    iv0 = ctx->iv0;
+    iv1 = ctx->iv1;
+
+    while (len > 0) {
+        blowfish_encrypt(iv0, iv1, b, ctx);
+        tmp = GET_32BIT_MSB_FIRST(blk);
+        PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]);
+        tmp = GET_32BIT_MSB_FIRST(blk + 4);
+        PUT_32BIT_MSB_FIRST(blk + 4, tmp ^ b[1]);
+        if ((iv1 = (iv1 + 1) & 0xffffffff) == 0)
+            iv0 = (iv0 + 1) & 0xffffffff;
+        blk += 8;
+        len -= 8;
+    }
+
+    ctx->iv0 = iv0;
+    ctx->iv1 = iv1;
+}
+
+void blowfish_initkey(BlowfishContext *ctx)
+{
+    int i;
+
+    for (i = 0; i < 18; i++) {
+        ctx->P[i] = parray[i];
+    }
+
+    for (i = 0; i < 256; i++) {
+        ctx->S0[i] = sbox0[i];
+        ctx->S1[i] = sbox1[i];
+        ctx->S2[i] = sbox2[i];
+        ctx->S3[i] = sbox3[i];
+    }
+}
+
+void blowfish_expandkey(BlowfishContext * ctx,
+                        const void *vkey, short keybytes,
+                        const void *vsalt, short saltbytes)
+{
+    const unsigned char *key = (const unsigned char *)vkey;
+    const unsigned char *salt = (const unsigned char *)vsalt;
+    uint32_t *S0 = ctx->S0;
+    uint32_t *S1 = ctx->S1;
+    uint32_t *S2 = ctx->S2;
+    uint32_t *S3 = ctx->S3;
+    uint32_t *P = ctx->P;
+    uint32_t str[2];
+    int i, j;
+    int saltpos;
+    unsigned char dummysalt[1];
+
+    saltpos = 0;
+    if (!salt) {
+        saltbytes = 1;
+        salt = dummysalt;
+        dummysalt[0] = 0;
+    }
+
+    for (i = 0; i < 18; i++) {
+        P[i] ^=
+            ((uint32_t) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24;
+        P[i] ^=
+            ((uint32_t) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16;
+        P[i] ^=
+            ((uint32_t) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8;
+        P[i] ^= ((uint32_t) (unsigned char) (key[(i * 4 + 3) % keybytes]));
+    }
+
+    str[0] = str[1] = 0;
+
+    for (i = 0; i < 18; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+
+        blowfish_encrypt(str[0], str[1], str, ctx);
+        P[i] = str[0];
+        P[i + 1] = str[1];
+    }
+
+    for (i = 0; i < 256; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+        blowfish_encrypt(str[0], str[1], str, ctx);
+        S0[i] = str[0];
+        S0[i + 1] = str[1];
+    }
+    for (i = 0; i < 256; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+        blowfish_encrypt(str[0], str[1], str, ctx);
+        S1[i] = str[0];
+        S1[i + 1] = str[1];
+    }
+    for (i = 0; i < 256; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+        blowfish_encrypt(str[0], str[1], str, ctx);
+        S2[i] = str[0];
+        S2[i + 1] = str[1];
+    }
+    for (i = 0; i < 256; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+        blowfish_encrypt(str[0], str[1], str, ctx);
+        S3[i] = str[0];
+        S3[i + 1] = str[1];
+    }
+}
+
+static void blowfish_setkey(BlowfishContext *ctx,
+                            const unsigned char *key, short keybytes)
+{
+    blowfish_initkey(ctx);
+    blowfish_expandkey(ctx, key, keybytes, NULL, 0);
+}
+
+/* -- Interface with PuTTY -- */
+
+#define SSH1_SESSION_KEY_LENGTH 32
+
+BlowfishContext *blowfish_make_context(void)
+{
+    return snew(BlowfishContext);
+}
+
+void blowfish_free_context(BlowfishContext *ctx)
+{
+    sfree(ctx);
+}
+
+static void blowfish_iv_be(BlowfishContext *ctx, const void *viv)
+{
+    const unsigned char *iv = (const unsigned char *)viv;
+    ctx->iv0 = GET_32BIT_MSB_FIRST(iv);
+    ctx->iv1 = GET_32BIT_MSB_FIRST(iv + 4);
+}
+
+static void blowfish_iv_le(BlowfishContext *ctx, const void *viv)
+{
+    const unsigned char *iv = (const unsigned char *)viv;
+    ctx->iv0 = GET_32BIT_LSB_FIRST(iv);
+    ctx->iv1 = GET_32BIT_LSB_FIRST(iv + 4);
+}
+
+struct blowfish_ctx {
+    BlowfishContext context;
+    ssh_cipher ciph;
+};
+
+static ssh_cipher *blowfish_new(const ssh_cipheralg *alg)
+{
+    struct blowfish_ctx *ctx = snew(struct blowfish_ctx);
+    ctx->ciph.vt = alg;
+    return &ctx->ciph;
+}
+
+static void blowfish_free(ssh_cipher *cipher)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+}
+
+static void blowfish_ssh_setkey(ssh_cipher *cipher, const void *key)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    blowfish_setkey(&ctx->context, key, ctx->ciph.vt->padded_keybytes);
+}
+
+static void blowfish_ssh1_setiv(ssh_cipher *cipher, const void *iv)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    blowfish_iv_le(&ctx->context, iv);
+}
+
+static void blowfish_ssh2_setiv(ssh_cipher *cipher, const void *iv)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    blowfish_iv_be(&ctx->context, iv);
+}
+
+static void blowfish_ssh1_encrypt_blk(ssh_cipher *cipher, void *blk, int len)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    blowfish_lsb_encrypt_cbc(blk, len, &ctx->context);
+}
+
+static void blowfish_ssh1_decrypt_blk(ssh_cipher *cipher, void *blk, int len)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    blowfish_lsb_decrypt_cbc(blk, len, &ctx->context);
+}
+
+static void blowfish_ssh2_encrypt_blk(ssh_cipher *cipher, void *blk, int len)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    blowfish_msb_encrypt_cbc(blk, len, &ctx->context);
+}
+
+static void blowfish_ssh2_decrypt_blk(ssh_cipher *cipher, void *blk, int len)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    blowfish_msb_decrypt_cbc(blk, len, &ctx->context);
+}
+
+static void blowfish_ssh2_sdctr(ssh_cipher *cipher, void *blk, int len)
+{
+    struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+    blowfish_msb_sdctr(blk, len, &ctx->context);
+}
+
+const ssh_cipheralg ssh_blowfish_ssh1 = {
+    // WINSCP
+    /*.new =*/ blowfish_new,
+    /*.free =*/ blowfish_free,
+    /*.setiv =*/ blowfish_ssh1_setiv,
+    /*.setkey =*/ blowfish_ssh_setkey,
+    /*.encrypt =*/ blowfish_ssh1_encrypt_blk,
+    /*.decrypt =*/ blowfish_ssh1_decrypt_blk,
+    NULL, NULL, NULL, // WINSCP
+    /*.blksize =*/ 8,
+    /*.real_keybits =*/ 128,
+    /*.padded_keybytes =*/ SSH1_SESSION_KEY_LENGTH,
+    /*.flags =*/ SSH_CIPHER_IS_CBC,
+    /*.text_name =*/ "Blowfish-256 CBC",
+    NULL, NULL, // WINSCP
+};
+
+const ssh_cipheralg ssh_blowfish_ssh2 = {
+    // WINSCP
+    /*.new =*/ blowfish_new,
+    /*.free =*/ blowfish_free,
+    /*.setiv =*/ blowfish_ssh2_setiv,
+    /*.setkey =*/ blowfish_ssh_setkey,
+    /*.encrypt =*/ blowfish_ssh2_encrypt_blk,
+    /*.decrypt =*/ blowfish_ssh2_decrypt_blk,
+    NULL, NULL, // WINSCP
+    /*.ssh2_id =*/ "blowfish-cbc",
+    /*.blksize =*/ 8,
+    /*.real_keybits =*/ 128,
+    /*.padded_keybytes =*/ 16,
+    /*.flags =*/ SSH_CIPHER_IS_CBC,
+    /*.text_name =*/ "Blowfish-128 CBC",
+    NULL, NULL, // WINSCP
+};
+
+const ssh_cipheralg ssh_blowfish_ssh2_ctr = {
+    // WINSCP
+    /*.new =*/ blowfish_new,
+    /*.free =*/ blowfish_free,
+    /*.setiv =*/ blowfish_ssh2_setiv,
+    /*.setkey =*/ blowfish_ssh_setkey,
+    /*.encrypt =*/ blowfish_ssh2_sdctr,
+    /*.decrypt =*/ blowfish_ssh2_sdctr,
+    NULL, NULL, // WINSCP
+    /*.ssh2_id =*/ "blowfish-ctr",
+    /*.blksize =*/ 8,
+    /*.real_keybits =*/ 256,
+    /*.padded_keybytes =*/ 32,
+    /*.flags =*/ 0,
+    /*.text_name =*/ "Blowfish-256 SDCTR",
+    NULL, NULL, // WINSCP
+};
+
+static const ssh_cipheralg *const blowfish_list[] = {
+    &ssh_blowfish_ssh2_ctr,
+    &ssh_blowfish_ssh2
+};
+
+const ssh2_ciphers ssh2_blowfish = { lenof(blowfish_list), blowfish_list };

+ 14 - 0
source/putty/crypto/blowfish.h

@@ -0,0 +1,14 @@
+/*
+ * Header file shared between blowfish.c and bcrypt.c. Exposes the
+ * internal Blowfish routines needed by bcrypt.
+ */
+
+typedef struct BlowfishContext BlowfishContext;
+
+BlowfishContext *blowfish_make_context(void);
+void blowfish_free_context(BlowfishContext *ctx);
+void blowfish_initkey(BlowfishContext *ctx);
+void blowfish_expandkey(BlowfishContext *ctx,
+                        const void *key, short keybytes,
+                        const void *salt, short saltbytes);
+void blowfish_lsb_encrypt_ecb(void *blk, int len, BlowfishContext *ctx);

+ 1066 - 0
source/putty/crypto/chacha20-poly1305.c

@@ -0,0 +1,1066 @@
+/*
+ * ChaCha20-Poly1305 Implementation for SSH-2
+ *
+ * Protocol spec:
+ *  http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?rev=1.2&content-type=text/x-cvsweb-markup
+ *
+ * ChaCha20 spec:
+ *  http://cr.yp.to/chacha/chacha-20080128.pdf
+ *
+ * Salsa20 spec:
+ *  http://cr.yp.to/snuffle/spec.pdf
+ *
+ * Poly1305-AES spec:
+ *  http://cr.yp.to/mac/poly1305-20050329.pdf
+ *
+ * The nonce for the Poly1305 is the second part of the key output
+ * from the first round of ChaCha20. This removes the AES requirement.
+ * This is undocumented!
+ *
+ * This has an intricate link between the cipher and the MAC. The
+ * keying of both is done in by the cipher and setting of the IV is
+ * done by the MAC. One cannot operate without the other. The
+ * configuration of the ssh_cipheralg structure ensures that the MAC is
+ * set (and others ignored) if this cipher is chosen.
+ *
+ * This cipher also encrypts the length using a different
+ * instantiation of the cipher using a different key and IV made from
+ * the sequence number which is passed in addition when calling
+ * encrypt/decrypt on it.
+ */
+
+#include "ssh.h"
+#include "mpint_i.h"
+
+#ifndef INLINE
+#define INLINE
+#endif
+
+/* ChaCha20 implementation, only supporting 256-bit keys */
+
+/* State for each ChaCha20 instance */
+struct chacha20 {
+    /* Current context, usually with the count incremented
+     * 0-3 are the static constant
+     * 4-11 are the key
+     * 12-13 are the counter
+     * 14-15 are the IV */
+    uint32_t state[16];
+    /* The output of the state above ready to xor */
+    unsigned char current[64];
+    /* The index of the above currently used to allow a true streaming cipher */
+    int currentIndex;
+};
+
+static INLINE void chacha20_round(struct chacha20 *ctx)
+{
+    int i;
+    uint32_t copy[16];
+
+    /* Take a copy */
+    memcpy(copy, ctx->state, sizeof(copy));
+
+    /* A circular rotation for a 32bit number */
+#define rotl(x, shift) x = ((x << shift) | (x >> (32 - shift)))
+
+    /* What to do for each quarter round operation */
+#define qrop(a, b, c, d)                        \
+    copy[a] += copy[b];                         \
+    copy[c] ^= copy[a];                         \
+    rotl(copy[c], d)
+
+    /* A quarter round */
+#define quarter(a, b, c, d)                     \
+    qrop(a, b, d, 16);                          \
+    qrop(c, d, b, 12);                          \
+    qrop(a, b, d, 8);                           \
+    qrop(c, d, b, 7)
+
+    /* Do 20 rounds, in pairs because every other is different */
+    for (i = 0; i < 20; i += 2) {
+        /* A round */
+        quarter(0, 4, 8, 12);
+        quarter(1, 5, 9, 13);
+        quarter(2, 6, 10, 14);
+        quarter(3, 7, 11, 15);
+        /* Another slightly different round */
+        quarter(0, 5, 10, 15);
+        quarter(1, 6, 11, 12);
+        quarter(2, 7, 8, 13);
+        quarter(3, 4, 9, 14);
+    }
+
+    /* Dump the macros, don't need them littering */
+#undef rotl
+#undef qrop
+#undef quarter
+
+    /* Add the initial state */
+    for (i = 0; i < 16; ++i) {
+        copy[i] += ctx->state[i];
+    }
+
+    /* Update the content of the xor buffer */
+    for (i = 0; i < 16; ++i) {
+        ctx->current[i * 4 + 0] = copy[i] >> 0;
+        ctx->current[i * 4 + 1] = copy[i] >> 8;
+        ctx->current[i * 4 + 2] = copy[i] >> 16;
+        ctx->current[i * 4 + 3] = copy[i] >> 24;
+    }
+    /* State full, reset pointer to beginning */
+    ctx->currentIndex = 0;
+    smemclr(copy, sizeof(copy));
+
+    /* Increment round counter */
+    ++ctx->state[12];
+    /* Check for overflow, not done in one line so the 32 bits are chopped by the type */
+    if (!(uint32_t)(ctx->state[12])) {
+        ++ctx->state[13];
+    }
+}
+
+/* Initialise context with 256bit key */
+static void chacha20_key(struct chacha20 *ctx, const unsigned char *key)
+{
+    static const char constant[16] = "expand 32-byte k";
+
+    /* Add the fixed string to the start of the state */
+    ctx->state[0] = GET_32BIT_LSB_FIRST(constant + 0);
+    ctx->state[1] = GET_32BIT_LSB_FIRST(constant + 4);
+    ctx->state[2] = GET_32BIT_LSB_FIRST(constant + 8);
+    ctx->state[3] = GET_32BIT_LSB_FIRST(constant + 12);
+
+    /* Add the key */
+    ctx->state[4]  = GET_32BIT_LSB_FIRST(key + 0);
+    ctx->state[5]  = GET_32BIT_LSB_FIRST(key + 4);
+    ctx->state[6]  = GET_32BIT_LSB_FIRST(key + 8);
+    ctx->state[7]  = GET_32BIT_LSB_FIRST(key + 12);
+    ctx->state[8]  = GET_32BIT_LSB_FIRST(key + 16);
+    ctx->state[9]  = GET_32BIT_LSB_FIRST(key + 20);
+    ctx->state[10] = GET_32BIT_LSB_FIRST(key + 24);
+    ctx->state[11] = GET_32BIT_LSB_FIRST(key + 28);
+
+    /* New key, dump context */
+    ctx->currentIndex = 64;
+}
+
+static void chacha20_iv(struct chacha20 *ctx, const unsigned char *iv)
+{
+    ctx->state[12] = 0;
+    ctx->state[13] = 0;
+    ctx->state[14] = GET_32BIT_MSB_FIRST(iv);
+    ctx->state[15] = GET_32BIT_MSB_FIRST(iv + 4);
+
+    /* New IV, dump context */
+    ctx->currentIndex = 64;
+}
+
+static void chacha20_encrypt(struct chacha20 *ctx, unsigned char *blk, int len)
+{
+    while (len) {
+        /* If we don't have any state left, then cycle to the next */
+        if (ctx->currentIndex >= 64) {
+            chacha20_round(ctx);
+        }
+
+        /* Do the xor while there's some state left and some plaintext left */
+        while (ctx->currentIndex < 64 && len) {
+            *blk++ ^= ctx->current[ctx->currentIndex++];
+            --len;
+        }
+    }
+}
+
+/* Decrypt is encrypt... It's xor against a PRNG... */
+static INLINE void chacha20_decrypt(struct chacha20 *ctx,
+                                    unsigned char *blk, int len)
+{
+    chacha20_encrypt(ctx, blk, len);
+}
+
+/* Poly1305 implementation (no AES, nonce is not encrypted) */
+
+#define NWORDS ((130 + BIGNUM_INT_BITS-1) / BIGNUM_INT_BITS)
+typedef struct bigval {
+    BignumInt w[NWORDS];
+} bigval;
+
+static void bigval_clear(bigval *r)
+{
+    int i;
+    for (i = 0; i < NWORDS; i++)
+        r->w[i] = 0;
+}
+
+static void bigval_import_le(bigval *r, const void *vdata, int len)
+{
+    const unsigned char *data = (const unsigned char *)vdata;
+    int i;
+    bigval_clear(r);
+    for (i = 0; i < len; i++)
+        r->w[i / BIGNUM_INT_BYTES] |=
+            (BignumInt)data[i] << (8 * (i % BIGNUM_INT_BYTES));
+}
+
+static void bigval_export_le(const bigval *r, void *vdata, int len)
+{
+    unsigned char *data = (unsigned char *)vdata;
+    int i;
+    for (i = 0; i < len; i++)
+        data[i] = r->w[i / BIGNUM_INT_BYTES] >> (8 * (i % BIGNUM_INT_BYTES));
+}
+
+/*
+ * Core functions to do arithmetic mod p = 2^130-5. The whole
+ * collection of these, up to and including the surrounding #if, are
+ * generated automatically for various sizes of BignumInt by
+ * contrib/make1305.py.
+ */
+
+#if BIGNUM_INT_BITS == 16
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = a->w[3];
+    v4 = a->w[4];
+    v5 = a->w[5];
+    v6 = a->w[6];
+    v7 = a->w[7];
+    v8 = a->w[8];
+    v9 = b->w[0];
+    v10 = b->w[1];
+    v11 = b->w[2];
+    v12 = b->w[3];
+    v13 = b->w[4];
+    v14 = b->w[5];
+    v15 = b->w[6];
+    v16 = b->w[7];
+    v17 = b->w[8];
+    BignumADC(v18, carry, v0, v9, 0);
+    BignumADC(v19, carry, v1, v10, carry);
+    BignumADC(v20, carry, v2, v11, carry);
+    BignumADC(v21, carry, v3, v12, carry);
+    BignumADC(v22, carry, v4, v13, carry);
+    BignumADC(v23, carry, v5, v14, carry);
+    BignumADC(v24, carry, v6, v15, carry);
+    BignumADC(v25, carry, v7, v16, carry);
+    v26 = v8 + v17 + carry;
+    r->w[0] = v18;
+    r->w[1] = v19;
+    r->w[2] = v20;
+    r->w[3] = v21;
+    r->w[4] = v22;
+    r->w[5] = v23;
+    r->w[6] = v24;
+    r->w[7] = v25;
+    r->w[8] = v26;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27;
+    BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40;
+    BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53;
+    BignumInt v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66;
+    BignumInt v67, v68, v69, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79;
+    BignumInt v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92;
+    BignumInt v93, v94, v95, v96, v97, v98, v99, v100, v101, v102, v103, v104;
+    BignumInt v105, v106, v107, v108, v109, v110, v111, v112, v113, v114;
+    BignumInt v115, v116, v117, v118, v119, v120, v121, v122, v123, v124;
+    BignumInt v125, v126, v127, v128, v129, v130, v131, v132, v133, v134;
+    BignumInt v135, v136, v137, v138, v139, v140, v141, v142, v143, v144;
+    BignumInt v145, v146, v147, v148, v149, v150, v151, v152, v153, v154;
+    BignumInt v155, v156, v157, v158, v159, v160, v161, v162, v163, v164;
+    BignumInt v165, v166, v167, v168, v169, v170, v171, v172, v173, v174;
+    BignumInt v175, v176, v177, v178, v180, v181, v182, v183, v184, v185;
+    BignumInt v186, v187, v188, v189, v190, v191, v192, v193, v194, v195;
+    BignumInt v196, v197, v198, v199, v200, v201, v202, v203, v204, v205;
+    BignumInt v206, v207, v208, v210, v212, v213, v214, v215, v216, v217;
+    BignumInt v218, v219, v220, v221, v222, v223, v224, v225, v226, v227;
+    BignumInt v228, v229;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = a->w[3];
+    v4 = a->w[4];
+    v5 = a->w[5];
+    v6 = a->w[6];
+    v7 = a->w[7];
+    v8 = a->w[8];
+    v9 = b->w[0];
+    v10 = b->w[1];
+    v11 = b->w[2];
+    v12 = b->w[3];
+    v13 = b->w[4];
+    v14 = b->w[5];
+    v15 = b->w[6];
+    v16 = b->w[7];
+    v17 = b->w[8];
+    BignumMUL(v19, v18, v0, v9);
+    BignumMULADD(v21, v20, v0, v10, v19);
+    BignumMULADD(v23, v22, v0, v11, v21);
+    BignumMULADD(v25, v24, v0, v12, v23);
+    BignumMULADD(v27, v26, v0, v13, v25);
+    BignumMULADD(v29, v28, v0, v14, v27);
+    BignumMULADD(v31, v30, v0, v15, v29);
+    BignumMULADD(v33, v32, v0, v16, v31);
+    BignumMULADD(v35, v34, v0, v17, v33);
+    BignumMULADD(v37, v36, v1, v9, v20);
+    BignumMULADD2(v39, v38, v1, v10, v22, v37);
+    BignumMULADD2(v41, v40, v1, v11, v24, v39);
+    BignumMULADD2(v43, v42, v1, v12, v26, v41);
+    BignumMULADD2(v45, v44, v1, v13, v28, v43);
+    BignumMULADD2(v47, v46, v1, v14, v30, v45);
+    BignumMULADD2(v49, v48, v1, v15, v32, v47);
+    BignumMULADD2(v51, v50, v1, v16, v34, v49);
+    BignumMULADD2(v53, v52, v1, v17, v35, v51);
+    BignumMULADD(v55, v54, v2, v9, v38);
+    BignumMULADD2(v57, v56, v2, v10, v40, v55);
+    BignumMULADD2(v59, v58, v2, v11, v42, v57);
+    BignumMULADD2(v61, v60, v2, v12, v44, v59);
+    BignumMULADD2(v63, v62, v2, v13, v46, v61);
+    BignumMULADD2(v65, v64, v2, v14, v48, v63);
+    BignumMULADD2(v67, v66, v2, v15, v50, v65);
+    BignumMULADD2(v69, v68, v2, v16, v52, v67);
+    BignumMULADD2(v71, v70, v2, v17, v53, v69);
+    BignumMULADD(v73, v72, v3, v9, v56);
+    BignumMULADD2(v75, v74, v3, v10, v58, v73);
+    BignumMULADD2(v77, v76, v3, v11, v60, v75);
+    BignumMULADD2(v79, v78, v3, v12, v62, v77);
+    BignumMULADD2(v81, v80, v3, v13, v64, v79);
+    BignumMULADD2(v83, v82, v3, v14, v66, v81);
+    BignumMULADD2(v85, v84, v3, v15, v68, v83);
+    BignumMULADD2(v87, v86, v3, v16, v70, v85);
+    BignumMULADD2(v89, v88, v3, v17, v71, v87);
+    BignumMULADD(v91, v90, v4, v9, v74);
+    BignumMULADD2(v93, v92, v4, v10, v76, v91);
+    BignumMULADD2(v95, v94, v4, v11, v78, v93);
+    BignumMULADD2(v97, v96, v4, v12, v80, v95);
+    BignumMULADD2(v99, v98, v4, v13, v82, v97);
+    BignumMULADD2(v101, v100, v4, v14, v84, v99);
+    BignumMULADD2(v103, v102, v4, v15, v86, v101);
+    BignumMULADD2(v105, v104, v4, v16, v88, v103);
+    BignumMULADD2(v107, v106, v4, v17, v89, v105);
+    BignumMULADD(v109, v108, v5, v9, v92);
+    BignumMULADD2(v111, v110, v5, v10, v94, v109);
+    BignumMULADD2(v113, v112, v5, v11, v96, v111);
+    BignumMULADD2(v115, v114, v5, v12, v98, v113);
+    BignumMULADD2(v117, v116, v5, v13, v100, v115);
+    BignumMULADD2(v119, v118, v5, v14, v102, v117);
+    BignumMULADD2(v121, v120, v5, v15, v104, v119);
+    BignumMULADD2(v123, v122, v5, v16, v106, v121);
+    BignumMULADD2(v125, v124, v5, v17, v107, v123);
+    BignumMULADD(v127, v126, v6, v9, v110);
+    BignumMULADD2(v129, v128, v6, v10, v112, v127);
+    BignumMULADD2(v131, v130, v6, v11, v114, v129);
+    BignumMULADD2(v133, v132, v6, v12, v116, v131);
+    BignumMULADD2(v135, v134, v6, v13, v118, v133);
+    BignumMULADD2(v137, v136, v6, v14, v120, v135);
+    BignumMULADD2(v139, v138, v6, v15, v122, v137);
+    BignumMULADD2(v141, v140, v6, v16, v124, v139);
+    BignumMULADD2(v143, v142, v6, v17, v125, v141);
+    BignumMULADD(v145, v144, v7, v9, v128);
+    BignumMULADD2(v147, v146, v7, v10, v130, v145);
+    BignumMULADD2(v149, v148, v7, v11, v132, v147);
+    BignumMULADD2(v151, v150, v7, v12, v134, v149);
+    BignumMULADD2(v153, v152, v7, v13, v136, v151);
+    BignumMULADD2(v155, v154, v7, v14, v138, v153);
+    BignumMULADD2(v157, v156, v7, v15, v140, v155);
+    BignumMULADD2(v159, v158, v7, v16, v142, v157);
+    BignumMULADD2(v161, v160, v7, v17, v143, v159);
+    BignumMULADD(v163, v162, v8, v9, v146);
+    BignumMULADD2(v165, v164, v8, v10, v148, v163);
+    BignumMULADD2(v167, v166, v8, v11, v150, v165);
+    BignumMULADD2(v169, v168, v8, v12, v152, v167);
+    BignumMULADD2(v171, v170, v8, v13, v154, v169);
+    BignumMULADD2(v173, v172, v8, v14, v156, v171);
+    BignumMULADD2(v175, v174, v8, v15, v158, v173);
+    BignumMULADD2(v177, v176, v8, v16, v160, v175);
+    v178 = v8 * v17 + v161 + v177;
+    v180 = (v162) & ((((BignumInt)1) << 2)-1);
+    v181 = ((v162) >> 2) | ((v164) << 14);
+    v182 = ((v164) >> 2) | ((v166) << 14);
+    v183 = ((v166) >> 2) | ((v168) << 14);
+    v184 = ((v168) >> 2) | ((v170) << 14);
+    v185 = ((v170) >> 2) | ((v172) << 14);
+    v186 = ((v172) >> 2) | ((v174) << 14);
+    v187 = ((v174) >> 2) | ((v176) << 14);
+    v188 = ((v176) >> 2) | ((v178) << 14);
+    v189 = (v178) >> 2;
+    v190 = (v189) & ((((BignumInt)1) << 2)-1);
+    v191 = (v178) >> 4;
+    BignumMUL(v193, v192, 5, v181);
+    BignumMULADD(v195, v194, 5, v182, v193);
+    BignumMULADD(v197, v196, 5, v183, v195);
+    BignumMULADD(v199, v198, 5, v184, v197);
+    BignumMULADD(v201, v200, 5, v185, v199);
+    BignumMULADD(v203, v202, 5, v186, v201);
+    BignumMULADD(v205, v204, 5, v187, v203);
+    BignumMULADD(v207, v206, 5, v188, v205);
+    v208 = 5 * v190 + v207;
+    v210 = 25 * v191;
+    BignumADC(v212, carry, v18, v192, 0);
+    BignumADC(v213, carry, v36, v194, carry);
+    BignumADC(v214, carry, v54, v196, carry);
+    BignumADC(v215, carry, v72, v198, carry);
+    BignumADC(v216, carry, v90, v200, carry);
+    BignumADC(v217, carry, v108, v202, carry);
+    BignumADC(v218, carry, v126, v204, carry);
+    BignumADC(v219, carry, v144, v206, carry);
+    v220 = v180 + v208 + carry;
+    BignumADC(v221, carry, v212, v210, 0);
+    BignumADC(v222, carry, v213, 0, carry);
+    BignumADC(v223, carry, v214, 0, carry);
+    BignumADC(v224, carry, v215, 0, carry);
+    BignumADC(v225, carry, v216, 0, carry);
+    BignumADC(v226, carry, v217, 0, carry);
+    BignumADC(v227, carry, v218, 0, carry);
+    BignumADC(v228, carry, v219, 0, carry);
+    v229 = v220 + 0 + carry;
+    r->w[0] = v221;
+    r->w[1] = v222;
+    r->w[2] = v223;
+    r->w[3] = v224;
+    r->w[4] = v225;
+    r->w[5] = v226;
+    r->w[6] = v227;
+    r->w[7] = v228;
+    r->w[8] = v229;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v13, v14, v15;
+    BignumInt v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28;
+    BignumInt v29, v30, v31, v32, v34, v35, v36, v37, v38, v39, v40, v41, v42;
+    BignumInt v43;
+    BignumCarry carry;
+
+    v0 = n->w[0];
+    v1 = n->w[1];
+    v2 = n->w[2];
+    v3 = n->w[3];
+    v4 = n->w[4];
+    v5 = n->w[5];
+    v6 = n->w[6];
+    v7 = n->w[7];
+    v8 = n->w[8];
+    v9 = (v8) >> 2;
+    v10 = (v8) & ((((BignumInt)1) << 2)-1);
+    v11 = 5 * v9;
+    BignumADC(v13, carry, v0, v11, 0);
+    BignumADC(v14, carry, v1, 0, carry);
+    BignumADC(v15, carry, v2, 0, carry);
+    BignumADC(v16, carry, v3, 0, carry);
+    BignumADC(v17, carry, v4, 0, carry);
+    BignumADC(v18, carry, v5, 0, carry);
+    BignumADC(v19, carry, v6, 0, carry);
+    BignumADC(v20, carry, v7, 0, carry);
+    v21 = v10 + 0 + carry;
+    BignumADC(v22, carry, v13, 5, 0);
+    (void)v22;
+    BignumADC(v23, carry, v14, 0, carry);
+    (void)v23;
+    BignumADC(v24, carry, v15, 0, carry);
+    (void)v24;
+    BignumADC(v25, carry, v16, 0, carry);
+    (void)v25;
+    BignumADC(v26, carry, v17, 0, carry);
+    (void)v26;
+    BignumADC(v27, carry, v18, 0, carry);
+    (void)v27;
+    BignumADC(v28, carry, v19, 0, carry);
+    (void)v28;
+    BignumADC(v29, carry, v20, 0, carry);
+    (void)v29;
+    v30 = v21 + 0 + carry;
+    v31 = (v30) >> 2;
+    v32 = 5 * v31;
+    BignumADC(v34, carry, v13, v32, 0);
+    BignumADC(v35, carry, v14, 0, carry);
+    BignumADC(v36, carry, v15, 0, carry);
+    BignumADC(v37, carry, v16, 0, carry);
+    BignumADC(v38, carry, v17, 0, carry);
+    BignumADC(v39, carry, v18, 0, carry);
+    BignumADC(v40, carry, v19, 0, carry);
+    BignumADC(v41, carry, v20, 0, carry);
+    v42 = v21 + 0 + carry;
+    v43 = (v42) & ((((BignumInt)1) << 2)-1);
+    n->w[0] = v34;
+    n->w[1] = v35;
+    n->w[2] = v36;
+    n->w[3] = v37;
+    n->w[4] = v38;
+    n->w[5] = v39;
+    n->w[6] = v40;
+    n->w[7] = v41;
+    n->w[8] = v43;
+}
+
+#elif BIGNUM_INT_BITS == 32
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = a->w[3];
+    v4 = a->w[4];
+    v5 = b->w[0];
+    v6 = b->w[1];
+    v7 = b->w[2];
+    v8 = b->w[3];
+    v9 = b->w[4];
+    BignumADC(v10, carry, v0, v5, 0);
+    BignumADC(v11, carry, v1, v6, carry);
+    BignumADC(v12, carry, v2, v7, carry);
+    BignumADC(v13, carry, v3, v8, carry);
+    v14 = v4 + v9 + carry;
+    r->w[0] = v10;
+    r->w[1] = v11;
+    r->w[2] = v12;
+    r->w[3] = v13;
+    r->w[4] = v14;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27;
+    BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40;
+    BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53;
+    BignumInt v54, v55, v56, v57, v58, v60, v61, v62, v63, v64, v65, v66, v67;
+    BignumInt v68, v69, v70, v71, v72, v73, v74, v75, v76, v78, v80, v81, v82;
+    BignumInt v83, v84, v85, v86, v87, v88, v89;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = a->w[3];
+    v4 = a->w[4];
+    v5 = b->w[0];
+    v6 = b->w[1];
+    v7 = b->w[2];
+    v8 = b->w[3];
+    v9 = b->w[4];
+    BignumMUL(v11, v10, v0, v5);
+    BignumMULADD(v13, v12, v0, v6, v11);
+    BignumMULADD(v15, v14, v0, v7, v13);
+    BignumMULADD(v17, v16, v0, v8, v15);
+    BignumMULADD(v19, v18, v0, v9, v17);
+    BignumMULADD(v21, v20, v1, v5, v12);
+    BignumMULADD2(v23, v22, v1, v6, v14, v21);
+    BignumMULADD2(v25, v24, v1, v7, v16, v23);
+    BignumMULADD2(v27, v26, v1, v8, v18, v25);
+    BignumMULADD2(v29, v28, v1, v9, v19, v27);
+    BignumMULADD(v31, v30, v2, v5, v22);
+    BignumMULADD2(v33, v32, v2, v6, v24, v31);
+    BignumMULADD2(v35, v34, v2, v7, v26, v33);
+    BignumMULADD2(v37, v36, v2, v8, v28, v35);
+    BignumMULADD2(v39, v38, v2, v9, v29, v37);
+    BignumMULADD(v41, v40, v3, v5, v32);
+    BignumMULADD2(v43, v42, v3, v6, v34, v41);
+    BignumMULADD2(v45, v44, v3, v7, v36, v43);
+    BignumMULADD2(v47, v46, v3, v8, v38, v45);
+    BignumMULADD2(v49, v48, v3, v9, v39, v47);
+    BignumMULADD(v51, v50, v4, v5, v42);
+    BignumMULADD2(v53, v52, v4, v6, v44, v51);
+    BignumMULADD2(v55, v54, v4, v7, v46, v53);
+    BignumMULADD2(v57, v56, v4, v8, v48, v55);
+    v58 = v4 * v9 + v49 + v57;
+    v60 = (v50) & ((((BignumInt)1) << 2)-1);
+    v61 = ((v50) >> 2) | ((v52) << 30);
+    v62 = ((v52) >> 2) | ((v54) << 30);
+    v63 = ((v54) >> 2) | ((v56) << 30);
+    v64 = ((v56) >> 2) | ((v58) << 30);
+    v65 = (v58) >> 2;
+    v66 = (v65) & ((((BignumInt)1) << 2)-1);
+    v67 = (v58) >> 4;
+    BignumMUL(v69, v68, 5, v61);
+    BignumMULADD(v71, v70, 5, v62, v69);
+    BignumMULADD(v73, v72, 5, v63, v71);
+    BignumMULADD(v75, v74, 5, v64, v73);
+    v76 = 5 * v66 + v75;
+    v78 = 25 * v67;
+    BignumADC(v80, carry, v10, v68, 0);
+    BignumADC(v81, carry, v20, v70, carry);
+    BignumADC(v82, carry, v30, v72, carry);
+    BignumADC(v83, carry, v40, v74, carry);
+    v84 = v60 + v76 + carry;
+    BignumADC(v85, carry, v80, v78, 0);
+    BignumADC(v86, carry, v81, 0, carry);
+    BignumADC(v87, carry, v82, 0, carry);
+    BignumADC(v88, carry, v83, 0, carry);
+    v89 = v84 + 0 + carry;
+    r->w[0] = v85;
+    r->w[1] = v86;
+    r->w[2] = v87;
+    r->w[3] = v88;
+    r->w[4] = v89;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v22, v23, v24, v25, v26, v27;
+    BignumCarry carry;
+
+    v0 = n->w[0];
+    v1 = n->w[1];
+    v2 = n->w[2];
+    v3 = n->w[3];
+    v4 = n->w[4];
+    v5 = (v4) >> 2;
+    v6 = (v4) & ((((BignumInt)1) << 2)-1);
+    v7 = 5 * v5;
+    BignumADC(v9, carry, v0, v7, 0);
+    BignumADC(v10, carry, v1, 0, carry);
+    BignumADC(v11, carry, v2, 0, carry);
+    BignumADC(v12, carry, v3, 0, carry);
+    v13 = v6 + 0 + carry;
+    BignumADC(v14, carry, v9, 5, 0);
+    (void)v14;
+    BignumADC(v15, carry, v10, 0, carry);
+    (void)v15;
+    BignumADC(v16, carry, v11, 0, carry);
+    (void)v16;
+    BignumADC(v17, carry, v12, 0, carry);
+    (void)v17;
+    v18 = v13 + 0 + carry;
+    v19 = (v18) >> 2;
+    v20 = 5 * v19;
+    BignumADC(v22, carry, v9, v20, 0);
+    BignumADC(v23, carry, v10, 0, carry);
+    BignumADC(v24, carry, v11, 0, carry);
+    BignumADC(v25, carry, v12, 0, carry);
+    v26 = v13 + 0 + carry;
+    v27 = (v26) & ((((BignumInt)1) << 2)-1);
+    n->w[0] = v22;
+    n->w[1] = v23;
+    n->w[2] = v24;
+    n->w[3] = v25;
+    n->w[4] = v27;
+}
+
+#elif BIGNUM_INT_BITS == 64
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = b->w[0];
+    v4 = b->w[1];
+    v5 = b->w[2];
+    BignumADC(v6, carry, v0, v3, 0);
+    BignumADC(v7, carry, v1, v4, carry);
+    v8 = v2 + v5 + carry;
+    r->w[0] = v6;
+    r->w[1] = v7;
+    r->w[2] = v8;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v24, v25, v26, v27, v28;
+    BignumInt v29, v30, v31, v32, v33, v34, v36, v38, v39, v40, v41, v42, v43;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = b->w[0];
+    v4 = b->w[1];
+    v5 = b->w[2];
+    BignumMUL(v7, v6, v0, v3);
+    BignumMULADD(v9, v8, v0, v4, v7);
+    BignumMULADD(v11, v10, v0, v5, v9);
+    BignumMULADD(v13, v12, v1, v3, v8);
+    BignumMULADD2(v15, v14, v1, v4, v10, v13);
+    BignumMULADD2(v17, v16, v1, v5, v11, v15);
+    BignumMULADD(v19, v18, v2, v3, v14);
+    BignumMULADD2(v21, v20, v2, v4, v16, v19);
+    v22 = v2 * v5 + v17 + v21;
+    v24 = (v18) & ((((BignumInt)1) << 2)-1);
+    v25 = ((v18) >> 2) | ((v20) << 62);
+    v26 = ((v20) >> 2) | ((v22) << 62);
+    v27 = (v22) >> 2;
+    v28 = (v27) & ((((BignumInt)1) << 2)-1);
+    v29 = (v22) >> 4;
+    BignumMUL(v31, v30, 5, v25);
+    BignumMULADD(v33, v32, 5, v26, v31);
+    v34 = 5 * v28 + v33;
+    v36 = 25 * v29;
+    BignumADC(v38, carry, v6, v30, 0);
+    BignumADC(v39, carry, v12, v32, carry);
+    v40 = v24 + v34 + carry;
+    BignumADC(v41, carry, v38, v36, 0);
+    BignumADC(v42, carry, v39, 0, carry);
+    v43 = v40 + 0 + carry;
+    r->w[0] = v41;
+    r->w[1] = v42;
+    r->w[2] = v43;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v16, v17, v18, v19;
+    BignumCarry carry;
+
+    v0 = n->w[0];
+    v1 = n->w[1];
+    v2 = n->w[2];
+    v3 = (v2) >> 2;
+    v4 = (v2) & ((((BignumInt)1) << 2)-1);
+    v5 = 5 * v3;
+    BignumADC(v7, carry, v0, v5, 0);
+    BignumADC(v8, carry, v1, 0, carry);
+    v9 = v4 + 0 + carry;
+    BignumADC(v10, carry, v7, 5, 0);
+    (void)v10;
+    BignumADC(v11, carry, v8, 0, carry);
+    (void)v11;
+    v12 = v9 + 0 + carry;
+    v13 = (v12) >> 2;
+    v14 = 5 * v13;
+    BignumADC(v16, carry, v7, v14, 0);
+    BignumADC(v17, carry, v8, 0, carry);
+    v18 = v9 + 0 + carry;
+    v19 = (v18) & ((((BignumInt)1) << 2)-1);
+    n->w[0] = v16;
+    n->w[1] = v17;
+    n->w[2] = v19;
+}
+
+#else
+#error Add another bit count to contrib/make1305.py and rerun it
+#endif
+
+struct poly1305 {
+    unsigned char nonce[16];
+    bigval r;
+    bigval h;
+
+    /* Buffer in case we get less that a multiple of 16 bytes */
+    unsigned char buffer[16];
+    int bufferIndex;
+};
+
+static void poly1305_init(struct poly1305 *ctx)
+{
+    memset(ctx->nonce, 0, 16);
+    ctx->bufferIndex = 0;
+    bigval_clear(&ctx->h);
+}
+
+static void poly1305_key(struct poly1305 *ctx, ptrlen key)
+{
+    pinitassert(key.len == 32);             /* Takes a 256 bit key */
+
+    unsigned char key_copy[16];
+    memcpy(key_copy, key.ptr, 16);
+
+    /* Key the MAC itself
+     * bytes 4, 8, 12 and 16 are required to have their top four bits clear */
+    key_copy[3] &= 0x0f;
+    key_copy[7] &= 0x0f;
+    key_copy[11] &= 0x0f;
+    key_copy[15] &= 0x0f;
+    /* bytes 5, 9 and 13 are required to have their bottom two bits clear */
+    key_copy[4] &= 0xfc;
+    key_copy[8] &= 0xfc;
+    key_copy[12] &= 0xfc;
+    bigval_import_le(&ctx->r, key_copy, 16);
+    smemclr(key_copy, sizeof(key_copy));
+
+    /* Use second 128 bits as the nonce */
+    memcpy(ctx->nonce, (const char *)key.ptr + 16, 16);
+}
+
+/* Feed up to 16 bytes (should only be less for the last chunk) */
+static void poly1305_feed_chunk(struct poly1305 *ctx,
+                                const unsigned char *chunk, int len)
+{
+    bigval c;
+    bigval_import_le(&c, chunk, len);
+    c.w[len / BIGNUM_INT_BYTES] |=
+        (BignumInt)1 << (8 * (len % BIGNUM_INT_BYTES));
+    bigval_add(&c, &c, &ctx->h);
+    bigval_mul_mod_p(&ctx->h, &c, &ctx->r);
+}
+
+static void poly1305_feed(struct poly1305 *ctx,
+                          const unsigned char *buf, int len)
+{
+    /* Check for stuff left in the buffer from last time */
+    if (ctx->bufferIndex) {
+        /* Try to fill up to 16 */
+        while (ctx->bufferIndex < 16 && len) {
+            ctx->buffer[ctx->bufferIndex++] = *buf++;
+            --len;
+        }
+        if (ctx->bufferIndex == 16) {
+            poly1305_feed_chunk(ctx, ctx->buffer, 16);
+            ctx->bufferIndex = 0;
+        }
+    }
+
+    /* Process 16 byte whole chunks */
+    while (len >= 16) {
+        poly1305_feed_chunk(ctx, buf, 16);
+        len -= 16;
+        buf += 16;
+    }
+
+    /* Cache stuff that's left over */
+    if (len) {
+        memcpy(ctx->buffer, buf, len);
+        ctx->bufferIndex = len;
+    }
+}
+
+/* Finalise and populate buffer with 16 byte with MAC */
+static void poly1305_finalise(struct poly1305 *ctx, unsigned char *mac)
+{
+    bigval tmp;
+
+    if (ctx->bufferIndex) {
+        poly1305_feed_chunk(ctx, ctx->buffer, ctx->bufferIndex);
+    }
+
+    bigval_import_le(&tmp, ctx->nonce, 16);
+    bigval_final_reduce(&ctx->h);
+    bigval_add(&tmp, &tmp, &ctx->h);
+    bigval_export_le(&tmp, mac, 16);
+}
+
+/* SSH-2 wrapper */
+
+struct ccp_context {
+    struct chacha20 a_cipher; /* Used for length */
+    struct chacha20 b_cipher; /* Used for content */
+
+    /* Cache of the first 4 bytes because they are the sequence number */
+    /* Kept in 8 bytes with the top as zero to allow easy passing to setiv */
+    int mac_initialised; /* Where we have got to in filling mac_iv */
+    unsigned char mac_iv[8];
+
+    struct poly1305 mac;
+
+    BinarySink_IMPLEMENTATION;
+    ssh_cipher ciph;
+    ssh2_mac mac_if;
+};
+
+static ssh2_mac *poly_ssh2_new(
+    const ssh2_macalg *alg, ssh_cipher *cipher)
+{
+    struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+    ctx->mac_if.vt = alg;
+    BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx);
+    return &ctx->mac_if;
+}
+
+static void poly_ssh2_free(ssh2_mac *mac)
+{
+    /* Not allocated, just forwarded, no need to free */
+}
+
+static void poly_setkey(ssh2_mac *mac, ptrlen key)
+{
+    /* Uses the same context as ChaCha20, so ignore */
+}
+
+static void poly_start(ssh2_mac *mac)
+{
+    struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if);
+
+    ctx->mac_initialised = 0;
+    memset(ctx->mac_iv, 0, 8);
+    poly1305_init(&ctx->mac);
+}
+
+static void poly_BinarySink_write(BinarySink *bs, const void *blkv, size_t len)
+{
+    struct ccp_context *ctx = BinarySink_DOWNCAST(bs, struct ccp_context);
+    const unsigned char *blk = (const unsigned char *)blkv;
+
+    /* First 4 bytes are the IV */
+    while (ctx->mac_initialised < 4 && len) {
+        ctx->mac_iv[7 - ctx->mac_initialised] = *blk++;
+        ++ctx->mac_initialised;
+        --len;
+    }
+
+    /* Initialise the IV if needed */
+    if (ctx->mac_initialised == 4) {
+        chacha20_iv(&ctx->b_cipher, ctx->mac_iv);
+        ++ctx->mac_initialised;  /* Don't do it again */
+
+        /* Do first rotation */
+        chacha20_round(&ctx->b_cipher);
+
+        /* Set the poly key */
+        poly1305_key(&ctx->mac, make_ptrlen(ctx->b_cipher.current, 32));
+
+        /* Set the first round as used */
+        ctx->b_cipher.currentIndex = 64;
+    }
+
+    /* Update the MAC with anything left */
+    if (len) {
+        poly1305_feed(&ctx->mac, blk, len);
+    }
+}
+
+static void poly_genresult(ssh2_mac *mac, unsigned char *blk)
+{
+    struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if);
+    poly1305_finalise(&ctx->mac, blk);
+}
+
+static const char *poly_text_name(ssh2_mac *mac)
+{
+    return "Poly1305";
+}
+
+const ssh2_macalg ssh2_poly1305 = {
+    // WINSCP
+    /*.new =*/ poly_ssh2_new,
+    /*.free =*/ poly_ssh2_free,
+    /*.setkey =*/ poly_setkey,
+    /*.start =*/ poly_start,
+    /*.genresult =*/ poly_genresult,
+    /*.text_name =*/ poly_text_name,
+    /*.name =*/ "",
+    /*.etm_name =*/ "", /* Not selectable individually, just part of
+                     * ChaCha20-Poly1305 */
+    /*.len =*/ 16,
+    /*.keylen =*/ 0,
+    NULL, // WINSCP
+};
+
+static ssh_cipher *ccp_new(const ssh_cipheralg *alg)
+{
+    struct ccp_context *ctx = snew(struct ccp_context);
+    BinarySink_INIT(ctx, poly_BinarySink_write);
+    poly1305_init(&ctx->mac);
+    ctx->ciph.vt = alg;
+    return &ctx->ciph;
+}
+
+static void ccp_free(ssh_cipher *cipher)
+{
+    struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+    smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher));
+    smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher));
+    smemclr(&ctx->mac, sizeof(ctx->mac));
+    sfree(ctx);
+}
+
+static void ccp_iv(ssh_cipher *cipher, const void *iv)
+{
+    /* struct ccp_context *ctx =
+           container_of(cipher, struct ccp_context, ciph); */
+    /* IV is set based on the sequence number */
+}
+
+static void ccp_key(ssh_cipher *cipher, const void *vkey)
+{
+    const unsigned char *key = (const unsigned char *)vkey;
+    struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+    /* Initialise the a_cipher (for decrypting lengths) with the first 256 bits */
+    chacha20_key(&ctx->a_cipher, key + 32);
+    /* Initialise the b_cipher (for content and MAC) with the second 256 bits */
+    chacha20_key(&ctx->b_cipher, key);
+}
+
+static void ccp_encrypt(ssh_cipher *cipher, void *blk, int len)
+{
+    struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+    chacha20_encrypt(&ctx->b_cipher, blk, len);
+}
+
+static void ccp_decrypt(ssh_cipher *cipher, void *blk, int len)
+{
+    struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+    chacha20_decrypt(&ctx->b_cipher, blk, len);
+}
+
+static void ccp_length_op(struct ccp_context *ctx, void *blk, int len,
+                          unsigned long seq)
+{
+    unsigned char iv[8];
+    /*
+     * According to RFC 4253 (section 6.4), the packet sequence number wraps
+     * at 2^32, so its 32 high-order bits will always be zero.
+     */
+    PUT_32BIT_LSB_FIRST(iv, 0);
+    PUT_32BIT_LSB_FIRST(iv + 4, seq);
+    chacha20_iv(&ctx->a_cipher, iv);
+    chacha20_iv(&ctx->b_cipher, iv);
+    /* Reset content block count to 1, as the first is the key for Poly1305 */
+    ++ctx->b_cipher.state[12];
+    smemclr(iv, sizeof(iv));
+}
+
+static void ccp_encrypt_length(ssh_cipher *cipher, void *blk, int len,
+                               unsigned long seq)
+{
+    struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+    ccp_length_op(ctx, blk, len, seq);
+    chacha20_encrypt(&ctx->a_cipher, blk, len);
+}
+
+static void ccp_decrypt_length(ssh_cipher *cipher, void *blk, int len,
+                               unsigned long seq)
+{
+    struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+    ccp_length_op(ctx, blk, len, seq);
+    chacha20_decrypt(&ctx->a_cipher, blk, len);
+}
+
+const ssh_cipheralg ssh2_chacha20_poly1305 = {
+    // WINSCP
+    /*.new =*/ ccp_new,
+    /*.free =*/ ccp_free,
+    /*.setiv =*/ ccp_iv,
+    /*.setkey =*/ ccp_key,
+    /*.encrypt =*/ ccp_encrypt,
+    /*.decrypt =*/ ccp_decrypt,
+    /*.encrypt_length =*/ ccp_encrypt_length,
+    /*.decrypt_length =*/ ccp_decrypt_length,
+    /*.ssh2_id =*/ "[email protected]",
+    /*.blksize =*/ 1,
+    /*.real_keybits =*/ 512,
+    /*.padded_keybytes =*/ 64,
+    /*.flags =*/ SSH_CIPHER_SEPARATE_LENGTH,
+    /*.text_name =*/ "ChaCha20",
+    /*.required_mac =*/ &ssh2_poly1305,
+    NULL, // WINSCP
+};
+
+static const ssh_cipheralg *const ccp_list[] = {
+    &ssh2_chacha20_poly1305
+};
+
+const ssh2_ciphers ssh2_ccp = { lenof(ccp_list), ccp_list };

+ 1092 - 0
source/putty/crypto/des.c

@@ -0,0 +1,1092 @@
+/*
+ * Implementation of DES.
+ */
+
+/*
+ * Background
+ * ----------
+ *
+ * The basic structure of DES is a Feistel network: the 64-bit cipher
+ * block is divided into two 32-bit halves L and R, and in each round,
+ * a mixing function is applied to one of them, the result is XORed
+ * into the other, and then the halves are swapped so that the other
+ * one will be the input to the mixing function next time. (This
+ * structure guarantees reversibility no matter whether the mixing
+ * function itself is bijective.)
+ *
+ * The mixing function for DES goes like this:
+ *  + Extract eight contiguous 6-bit strings from the 32-bit word.
+ *    They start at positions 4 bits apart, so each string overlaps
+ *    the next one by one bit. At least one has to wrap cyclically
+ *    round the end of the word.
+ *  + XOR each of those strings with 6 bits of data from the key
+ *    schedule (which consists of 8 x 6-bit strings per round).
+ *  + Use the resulting 6-bit numbers as the indices into eight
+ *    different lookup tables ('S-boxes'), each of which delivers a
+ *    4-bit output.
+ *  + Concatenate those eight 4-bit values into a 32-bit word.
+ *  + Finally, apply a fixed permutation P to that word.
+ *
+ * DES adds one more wrinkle on top of this structure, which is to
+ * conjugate it by a bitwise permutation of the cipher block. That is,
+ * before starting the main cipher rounds, the input bits are permuted
+ * according to a 64-bit permutation called IP, and after the rounds
+ * are finished, the output bits are permuted back again by applying
+ * the inverse of IP.
+ *
+ * This gives a lot of leeway to redefine the components of the cipher
+ * without actually changing the input and output. You could permute
+ * the bits in the output of any or all of the S-boxes, or reorder the
+ * S-boxes among themselves, and adjust the following permutation P to
+ * compensate. And you could adjust IP by post-composing a rotation of
+ * each 32-bit half, and adjust the starting offsets of the 6-bit
+ * S-box indices to compensate.
+ *
+ * test/desref.py demonstrates this by providing two equivalent forms
+ * of the cipher, called DES and SGTDES, which give the same output.
+ * DES is the form described in the original spec: if you make it
+ * print diagnostic output during the cipher and check it against the
+ * original, you should recognise the S-box outputs as matching the
+ * ones you expect. But SGTDES, which I egotistically name after
+ * myself, is much closer to the form implemented here: I've changed
+ * the permutation P to suit my implementation strategy and
+ * compensated by permuting the S-boxes, and also I've added a
+ * rotation right by 1 bit to IP so that only one S-box index has to
+ * wrap round the word and also so that the indices are nicely aligned
+ * for the constant-time selection system I'm using.
+ */
+
+#include <stdio.h>
+
+#include "ssh.h"
+#include "mpint_i.h"               /* we reuse the BignumInt system */
+
+/* If you compile with -DDES_DIAGNOSTICS, intermediate results will be
+ * sent to debug() (so you also need to compile with -DDEBUG).
+ * Otherwise this ifdef will condition away all the debug() calls. */
+#ifndef DES_DIAGNOSTICS
+#undef debug
+#define debug(...) ((void)0)
+#endif
+
+/*
+ * General utility functions.
+ */
+static inline uint32_t rol(uint32_t x, unsigned c)
+{
+    return (x << (31 & c)) | (x >> (31 & -(int/*WINSCP*/)c));
+}
+static inline uint32_t ror(uint32_t x, unsigned c)
+{
+    return rol(x, -(int/*WINSCP*/)c);
+}
+
+/*
+ * The hard part of doing DES in constant time is the S-box lookup.
+ *
+ * My strategy is to iterate over the whole lookup table! That's slow,
+ * but I don't see any way to avoid _something_ along those lines: in
+ * every round, every entry in every S-box is potentially needed, and
+ * if you can't change your memory access pattern based on the input
+ * data, it follows that you have to read a quantity of information
+ * equal to the size of all the S-boxes. (Unless they were to turn out
+ * to be significantly compressible, but I for one couldn't show them
+ * to be.)
+ *
+ * In more detail, I construct a sort of counter-based 'selection
+ * gadget', which is 15 bits wide and starts off with the top bit
+ * zero, the next eight bits all 1, and the bottom six set to the
+ * input S-box index:
+ *
+ *     011111111xxxxxx
+ *
+ * Now if you add 1 in the lowest bit position, then either it carries
+ * into the top section (resetting it to 100000000), or it doesn't do
+ * that yet. If you do that 64 times, then it will _guarantee_ to have
+ * ticked over into 100000000. In between those increments, the eight
+ * bits that started off as 11111111 will have stayed that way for
+ * some number of iterations and then become 00000000, and exactly how
+ * many iterations depends on the input index.
+ *
+ * The purpose of the 0 bit at the top is to absorb the carry when the
+ * switch happens, which means you can pack more than one gadget into
+ * the same machine word and have them all work in parallel without
+ * each one intefering with the next.
+ *
+ * The next step is to use each of those 8-bit segments as a bit mask:
+ * each one is ANDed with a lookup table entry, and all the results
+ * are XORed together. So you end up with the bitwise XOR of some
+ * initial segment of the table entries. And the stored S-box tables
+ * are transformed in such a way that the real S-box values are given
+ * not by the individual entries, but by the cumulative XORs
+ * constructed in this way.
+ *
+ * A refinement is that I increment each gadget by 2 rather than 1
+ * each time, so I only iterate 32 times instead of 64. That's why
+ * there are 8 selection bits instead of 4: each gadget selects enough
+ * bits to reconstruct _two_ S-box entries, for a pair of indices
+ * (2n,2n+1), and then finally I use the low bit of the index to do a
+ * parallel selection between each of those pairs.
+ *
+ * The selection gadget is not quite 16 bits wide. So you can fit four
+ * of them across a 64-bit word at 16-bit intervals, which is also
+ * convenient because the place the S-box indices are coming from also
+ * has pairs of them separated by 16-bit distances, so it's easy to
+ * copy them into the gadgets in the first place.
+ */
+
+/*
+ * The S-box data. Each pair of nonzero columns here describes one of
+ * the S-boxes, corresponding to the SGTDES tables in test/desref.py,
+ * under the following transformation.
+ *
+ * Take S-box #3 as an example. Its values in successive rows of this
+ * table are eb,e8,54,3d, ... So the cumulative XORs of initial
+ * sequences of those values are eb,(eb^e8),(eb^e8^54), ... which
+ * comes to eb,03,57,... Of _those_ values, the top nibble (e,0,5,...)
+ * gives the even-numbered entries in the S-box, in _reverse_ order
+ * (because a lower input index selects the XOR of a longer
+ * subsequence). The odd-numbered entries are given by XORing the two
+ * digits together: (e^b),(0^3),(5^7),... = 5,3,2,... And indeed, if
+ * you check SGTDES.sboxes[3] you find it ends ... 52 03 e5.
+ */
+#define SBOX_ITERATION(X)                       \
+    /*  66  22  44  00      77  33  55  11 */   \
+    X(0xf600970083008500, 0x0e00eb007b002e00)   \
+    X(0xda00e4009000e000, 0xad00e800a700b400)   \
+    X(0x1a009d003f003600, 0xf60054004300cd00)   \
+    X(0xaf00c500e900a900, 0x63003d00f2005900)   \
+    X(0xf300750079001400, 0x80005000a2008900)   \
+    X(0xa100d400d6007b00, 0xd3009000d300e100)   \
+    X(0x450087002600ac00, 0xae003c0031009c00)   \
+    X(0xd000b100b6003600, 0x3e006f0092005900)   \
+    X(0x4d008a0026001000, 0x89007a00b8004a00)   \
+    X(0xca00f5003f00ac00, 0x6f00f0003c009400)   \
+    X(0x92008d0090001000, 0x8c00c600ce004a00)   \
+    X(0xe2005900e9006d00, 0x790078007800fa00)   \
+    X(0x1300b10090008d00, 0xa300170027001800)   \
+    X(0xc70058005f006a00, 0x9c00c100e0006300)   \
+    X(0x9b002000f000f000, 0xf70057001600f900)   \
+    X(0xeb00b0009000af00, 0xa9006300b0005800)   \
+    X(0xa2001d00cf000000, 0x3800b00066000000)   \
+    X(0xf100da007900d000, 0xbc00790094007900)   \
+    X(0x570015001900ad00, 0x6f00ef005100cb00)   \
+    X(0xc3006100e9006d00, 0xc000b700f800f200)   \
+    X(0x1d005800b600d000, 0x67004d00cd002c00)   \
+    X(0xf400b800d600e000, 0x5e00a900b000e700)   \
+    X(0x5400d1003f009c00, 0xc90069002c005300)   \
+    X(0xe200e50060005900, 0x6a00b800c500f200)   \
+    X(0xdf0047007900d500, 0x7000ec004c00ea00)   \
+    X(0x7100d10060009c00, 0x3f00b10095005e00)   \
+    X(0x82008200f0002000, 0x87001d00cd008000)   \
+    X(0xd0007000af00c000, 0xe200be006100f200)   \
+    X(0x8000930060001000, 0x36006e0081001200)   \
+    X(0x6500a300d600ac00, 0xcf003d007d00c000)   \
+    X(0x9000700060009800, 0x62008100ad009200)   \
+    X(0xe000e4003f00f400, 0x5a00ed009000f200)   \
+    /* end of list */
+
+/*
+ * The S-box mapping function. Expects two 32-bit input words: si6420
+ * contains the table indices for S-boxes 0,2,4,6 with their low bits
+ * starting at position 2 (for S-box 0) and going up in steps of 8.
+ * si7531 has indices 1,3,5,7 in the same bit positions.
+ */
+static inline uint32_t des_S(uint32_t si6420, uint32_t si7531)
+{
+    debug("sindices: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+          0x3F & (si6420 >>  2), 0x3F & (si7531 >>  2),
+          0x3F & (si6420 >> 10), 0x3F & (si7531 >> 10),
+          0x3F & (si6420 >> 18), 0x3F & (si7531 >> 18),
+          0x3F & (si6420 >> 26), 0x3F & (si7531 >> 26));
+
+#ifdef SIXTY_FOUR_BIT
+    /*
+     * On 64-bit machines, we store the table in exactly the form
+     * shown above, and make two 64-bit words containing four
+     * selection gadgets each.
+     */
+
+    /* Set up the gadgets. The 'cNNNN' variables will be gradually
+     * incremented, and the bits in positions FF00FF00FF00FF00 will
+     * act as selectors for the words in the table.
+     *
+     * A side effect of moving the input indices further apart is that
+     * they change order, because it's easier to keep a pair that were
+     * originally 16 bits apart still 16 bits apart, which now makes
+     * them adjacent instead of separated by one. So the fact that
+     * si6420 turns into c6240 (with the 2,4 reversed) is not a typo!
+     * This will all be undone when we rebuild the output word later.
+     */
+    uint64_t c6240 = ((si6420 | ((uint64_t)si6420 << 24))
+                      & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00;
+    uint64_t c7351 = ((si7531 | ((uint64_t)si7531 << 24))
+                      & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00;
+    debug("S in:  c6240=%016"PRIx64" c7351=%016"PRIx64"\n", c6240, c7351);
+
+    /* Iterate over the table. The 'sNNNN' variables accumulate the
+     * XOR of all the table entries not masked out. */
+    static const struct tbl { uint64_t t6240, t7351; } tbl[32] = {
+#define TABLE64(a, b) { a, b },
+        SBOX_ITERATION(TABLE64)
+#undef TABLE64
+    };
+    uint64_t s6240 = 0, s7351 = 0;
+    for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) {
+        s6240 ^= c6240 & t->t6240; c6240 += 0x0008000800080008;
+        s7351 ^= c7351 & t->t7351; c7351 += 0x0008000800080008;
+    }
+    debug("S out: s6240=%016"PRIx64" s7351=%016"PRIx64"\n", s6240, s7351);
+
+    /* Final selection between each even/odd pair: mask off the low
+     * bits of all the input indices (which haven't changed throughout
+     * the iteration), and multiply by a bit mask that will turn each
+     * set bit into a mask covering the upper nibble of the selected
+     * pair. Then use those masks to control which set of lower
+     * nibbles is XORed into the upper nibbles. */
+    s6240 ^= (s6240 << 4) & ((0xf000/0x004) * (c6240 & 0x0004000400040004));
+    s7351 ^= (s7351 << 4) & ((0xf000/0x004) * (c7351 & 0x0004000400040004));
+
+    /* Now the eight final S-box outputs are in the upper nibble of
+     * each selection position. Mask away the rest of the clutter. */
+    s6240 &= 0xf000f000f000f000;
+    s7351 &= 0xf000f000f000f000;
+    debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n",
+          (unsigned)(0xF & (s6240 >> 12)),
+          (unsigned)(0xF & (s7351 >> 12)),
+          (unsigned)(0xF & (s6240 >> 44)),
+          (unsigned)(0xF & (s7351 >> 44)),
+          (unsigned)(0xF & (s6240 >> 28)),
+          (unsigned)(0xF & (s7351 >> 28)),
+          (unsigned)(0xF & (s6240 >> 60)),
+          (unsigned)(0xF & (s7351 >> 60)));
+
+    /* Combine them all into a single 32-bit output word, which will
+     * come out in the order 76543210. */
+    uint64_t combined = (s6240 >> 12) | (s7351 >> 8);
+    return combined | (combined >> 24);
+
+#else /* SIXTY_FOUR_BIT */
+    /*
+     * For 32-bit platforms, we do the same thing but in four 32-bit
+     * words instead of two 64-bit ones, so the CPU doesn't have to
+     * waste time propagating carries or shifted bits between the two
+     * halves of a uint64 that weren't needed anyway.
+     */
+
+    /* Set up the gadgets */
+    { // WINSCP
+    uint32_t c40 = ((si6420     ) & 0x00FC00FC) | 0xFF00FF00;
+    uint32_t c62 = ((si6420 >> 8) & 0x00FC00FC) | 0xFF00FF00;
+    uint32_t c51 = ((si7531     ) & 0x00FC00FC) | 0xFF00FF00;
+    uint32_t c73 = ((si7531 >> 8) & 0x00FC00FC) | 0xFF00FF00;
+    debug("S in:  c40=%08"PRIx32" c62=%08"PRIx32
+          " c51=%08"PRIx32" c73=%08"PRIx32"\n", c40, c62, c51, c73);
+
+    /* Iterate over the table */
+    { // WINSCP
+    static const struct tbl { uint32_t t40, t62, t51, t73; } tbl[32] = {
+#define TABLE32(a, b) { ((uint32_t)a##LL), (a##LL>>32), ((uint32_t)b##LL), (b##LL>>32) }, // WINSCP
+        SBOX_ITERATION(TABLE32)
+#undef TABLE32
+    };
+    uint32_t s40 = 0, s62 = 0, s51 = 0, s73 = 0;
+    const struct tbl *t, *limit; // WINSCP
+    for (t = tbl, limit = tbl + 32; t < limit; t++) {
+        s40 ^= c40 & t->t40; c40 += 0x00080008;
+        s62 ^= c62 & t->t62; c62 += 0x00080008;
+        s51 ^= c51 & t->t51; c51 += 0x00080008;
+        s73 ^= c73 & t->t73; c73 += 0x00080008;
+    }
+    debug("S out: s40=%08"PRIx32" s62=%08"PRIx32
+           " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73);
+
+    /* Final selection within each pair */
+    s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004));
+    s62 ^= (s62 << 4) & ((0xf000/0x004) * (c62 & 0x00040004));
+    s51 ^= (s51 << 4) & ((0xf000/0x004) * (c51 & 0x00040004));
+    s73 ^= (s73 << 4) & ((0xf000/0x004) * (c73 & 0x00040004));
+
+    /* Clean up the clutter */
+    s40 &= 0xf000f000;
+    s62 &= 0xf000f000;
+    s51 &= 0xf000f000;
+    s73 &= 0xf000f000;
+    debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n",
+          (unsigned)(0xF & (s40 >> 12)),
+          (unsigned)(0xF & (s51 >> 12)),
+          (unsigned)(0xF & (s62 >> 12)),
+          (unsigned)(0xF & (s73 >> 12)),
+          (unsigned)(0xF & (s40 >> 28)),
+          (unsigned)(0xF & (s51 >> 28)),
+          (unsigned)(0xF & (s62 >> 28)),
+          (unsigned)(0xF & (s73 >> 28)));
+
+    /* Recombine and return */
+    return (s40 >> 12) | (s62 >> 4) | (s51 >> 8) | (s73);
+    } // WINSCP
+    } // WINSCP
+
+#endif /* SIXTY_FOUR_BIT */
+
+}
+
+/*
+ * Now for the permutation P. The basic strategy here is to use a
+ * Benes network: in each stage, the bit at position i is allowed to
+ * either stay where it is or swap with i ^ D, where D is a power of 2
+ * that varies with each phase. (So when D=1, pairs of the form
+ * {2n,2n+1} can swap; when D=2, the pairs are {4n+j,4n+j+2} for
+ * j={0,1}, and so on.)
+ *
+ * You can recursively construct a Benes network for an arbitrary
+ * permutation, in which the values of D iterate across all the powers
+ * of 2 less than the permutation size and then go back again. For
+ * example, the typical presentation for 32 bits would have D iterate
+ * over 16,8,4,2,1,2,4,8,16, and there's an easy algorithm that can
+ * express any permutation in that form by deciding which pairs of
+ * bits to swap in the outer pair of stages and then recursing to do
+ * all the stages in between.
+ *
+ * Actually implementing the swaps is easy when they're all between
+ * bits at the same separation: make the value x ^ (x >> D), mask out
+ * just the bits in the low position of a pair that needs to swap, and
+ * then use the resulting value y to make x ^ y ^ (y << D) which is
+ * the swapped version.
+ *
+ * In this particular case, I processed the bit indices in the other
+ * order (going 1,2,4,8,16,8,4,2,1), which makes no significant
+ * difference to the construction algorithm (it's just a relabelling),
+ * but it now means that the first two steps only permute entries
+ * within the output of each S-box - and therefore we can leave them
+ * completely out, in favour of just defining the S-boxes so that
+ * those permutation steps are already applied. Furthermore, by
+ * exhaustive search over the rest of the possible bit-orders for each
+ * S-box, I was able to find a version of P which could be represented
+ * in such a way that two further phases had all their control bits
+ * zero and could be skipped. So the number of swap stages is reduced
+ * to 5 from the 9 that might have been needed.
+ */
+
+static inline uint32_t des_benes_step(uint32_t v, unsigned D, uint32_t mask)
+{
+    uint32_t diff = (v ^ (v >> D)) & mask;
+    return v ^ diff ^ (diff << D);
+}
+
+static inline uint32_t des_P(uint32_t v_orig)
+{
+    uint32_t v = v_orig;
+
+    /* initial stages with distance 1,2 are part of the S-box data table */
+    v = des_benes_step(v,  4, 0x07030702);
+    v = des_benes_step(v,  8, 0x004E009E);
+    v = des_benes_step(v, 16, 0x0000D9D3);
+/*  v = des_benes_step(v,  8, 0x00000000);  no-op, so we can skip it */
+    v = des_benes_step(v,  4, 0x05040004);
+/*  v = des_benes_step(v,  2, 0x00000000);  no-op, so we can skip it */
+    v = des_benes_step(v,  1, 0x04045015);
+
+    debug("P(%08"PRIx32") = %08"PRIx32"\n", v_orig, v);
+
+    return v;
+}
+
+/*
+ * Putting the S and P functions together, and adding in the round key
+ * as well, gives us the full mixing function f.
+ */
+
+static inline uint32_t des_f(uint32_t R, uint32_t K7531, uint32_t K6420)
+{
+    uint32_t s7531 = R ^ K7531, s6420 = rol(R, 4) ^ K6420;
+    return des_P(des_S(s6420, s7531));
+}
+
+/*
+ * The key schedule, and the function to set it up.
+ */
+
+typedef struct des_keysched des_keysched;
+struct des_keysched {
+    uint32_t k7531[16], k6420[16];
+};
+
+/*
+ * Simplistic function to select an arbitrary sequence of bits from
+ * one value and glue them together into another value. bitnums[]
+ * gives the sequence of bit indices of the input, from the highest
+ * output bit downwards. An index of -1 means that output bit is left
+ * at zero.
+ *
+ * This function is only used during key setup, so it doesn't need to
+ * be highly optimised.
+ */
+static inline uint64_t bitsel(
+    uint64_t input, const int8_t *bitnums, size_t size)
+{
+    uint64_t ret = 0;
+    while (size-- > 0) {
+        int bitpos = *bitnums++;
+        ret <<= 1;
+        if (bitpos >= 0)
+            ret |= 1 & (input >> bitpos);
+    }
+    return ret;
+}
+
+static void des_key_setup(uint64_t key, des_keysched *sched)
+{
+    static const int8_t PC1[] = {
+         7, 15, 23, 31, 39, 47, 55, 63,  6, 14, 22, 30, 38, 46,
+        54, 62,  5, 13, 21, 29, 37, 45, 53, 61,  4, 12, 20, 28,
+        -1, -1, -1, -1,
+         1,  9, 17, 25, 33, 41, 49, 57,  2, 10, 18, 26, 34, 42,
+        50, 58,  3, 11, 19, 27, 35, 43, 51, 59, 36, 44, 52, 60,
+    };
+    static const int8_t PC2_7531[] = {
+        46, 43, 49, 36, 59, 55, -1, -1, /* index into S-box 7 */
+        37, 41, 48, 56, 34, 52, -1, -1, /* index into S-box 5 */
+        15,  4, 25, 19,  9,  1, -1, -1, /* index into S-box 3 */
+        12,  7, 17,  0, 22,  3, -1, -1, /* index into S-box 1 */
+    };
+    static const int8_t PC2_6420[] = {
+        57, 32, 45, 54, 39, 50, -1, -1, /* index into S-box 6 */
+        44, 53, 33, 40, 47, 58, -1, -1, /* index into S-box 4 */
+        26, 16,  5, 11, 23,  8, -1, -1, /* index into S-box 2 */
+        10, 14,  6, 20, 27, 24, -1, -1, /* index into S-box 0 */
+    };
+    static const int leftshifts[] = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1};
+
+    /* Select 56 bits from the 64-bit input key integer (the low bit
+     * of each input byte is unused), into a word consisting of two
+     * 28-bit integers starting at bits 0 and 32. */
+    uint64_t CD = bitsel(key, PC1, lenof(PC1));
+
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < 16; i++) {
+        /* Rotate each 28-bit half of CD left by 1 or 2 bits (varying
+         * between rounds) */
+        CD <<= leftshifts[i];
+        CD = (CD & 0x0FFFFFFF0FFFFFFFLL) | ((CD & 0xF0000000F0000000LL) >> 28); // WINSCP
+
+        /* Select key bits from the rotated word to use during the
+         * actual cipher */
+        sched->k7531[i] = bitsel(CD, PC2_7531, lenof(PC2_7531));
+        sched->k6420[i] = bitsel(CD, PC2_6420, lenof(PC2_6420));
+    }
+    } // WINSCP
+}
+
+/*
+ * Helper routines for dealing with 64-bit blocks in the form of an L
+ * and R word.
+ */
+
+typedef struct LR LR;
+struct LR { uint32_t L, R; };
+
+static inline LR des_load_lr(const void *vp)
+{
+    const uint8_t *p = (const uint8_t *)vp;
+    LR out;
+    out.L = GET_32BIT_MSB_FIRST(p);
+    out.R = GET_32BIT_MSB_FIRST(p+4);
+    return out;
+}
+
+static inline void des_store_lr(void *vp, LR lr)
+{
+    uint8_t *p = (uint8_t *)vp;
+    PUT_32BIT_MSB_FIRST(p, lr.L);
+    PUT_32BIT_MSB_FIRST(p+4, lr.R);
+}
+
+static inline LR des_xor_lr(LR a, LR b)
+{
+    a.L ^= b.L;
+    a.R ^= b.R;
+    return a;
+}
+
+static inline LR des_swap_lr(LR in)
+{
+    LR out;
+    out.L = in.R;
+    out.R = in.L;
+    return out;
+}
+
+/*
+ * The initial and final permutations of official DES are in a
+ * restricted form, in which the 'before' and 'after' positions of a
+ * given data bit are derived from each other by permuting the bits of
+ * the _index_ and flipping some of them. This allows the permutation
+ * to be performed effectively by a method that looks rather like
+ * _half_ of a general Benes network, because the restricted form
+ * means only half of it is actually needed.
+ *
+ * _Our_ initial and final permutations include a rotation by 1 bit,
+ * but it's still easier to just suffix that to the standard IP/FP
+ * than to regenerate everything using a more general method.
+ *
+ * Because we're permuting 64 bits in this case, between two 32-bit
+ * words, there's a separate helper function for this code that
+ * doesn't look quite like des_benes_step() above.
+ */
+
+static inline void des_bitswap_IP_FP(uint32_t *L, uint32_t *R,
+                                     unsigned D, uint32_t mask)
+{
+    uint32_t diff = mask & ((*R >> D) ^ *L);
+    *R ^= diff << D;
+    *L ^= diff;
+}
+
+static inline LR des_IP(LR lr)
+{
+    des_bitswap_IP_FP(&lr.R, &lr.L,  4, 0x0F0F0F0F);
+    des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF);
+    des_bitswap_IP_FP(&lr.L, &lr.R,  2, 0x33333333);
+    des_bitswap_IP_FP(&lr.L, &lr.R,  8, 0x00FF00FF);
+    des_bitswap_IP_FP(&lr.R, &lr.L,  1, 0x55555555);
+
+    lr.L = ror(lr.L, 1);
+    lr.R = ror(lr.R, 1);
+
+    return lr;
+}
+
+static inline LR des_FP(LR lr)
+{
+    lr.L = rol(lr.L, 1);
+    lr.R = rol(lr.R, 1);
+
+    des_bitswap_IP_FP(&lr.R, &lr.L,  1, 0x55555555);
+    des_bitswap_IP_FP(&lr.L, &lr.R,  8, 0x00FF00FF);
+    des_bitswap_IP_FP(&lr.L, &lr.R,  2, 0x33333333);
+    des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF);
+    des_bitswap_IP_FP(&lr.R, &lr.L,  4, 0x0F0F0F0F);
+
+    return lr;
+}
+
+/*
+ * The main cipher functions, which are identical except that they use
+ * the key schedule in opposite orders.
+ *
+ * We provide a version without the initial and final permutations,
+ * for use in triple-DES mode (no sense undoing and redoing it in
+ * between the phases).
+ */
+
+static inline LR des_round(LR in, const des_keysched *sched, size_t round)
+{
+    LR out;
+    out.L = in.R;
+    out.R = in.L ^ des_f(in.R, sched->k7531[round], sched->k6420[round]);
+    return out;
+}
+
+static inline LR des_inner_cipher(LR lr, const des_keysched *sched,
+                                  size_t start, size_t step)
+{
+    lr = des_round(lr, sched, start+0x0*step);
+    lr = des_round(lr, sched, start+0x1*step);
+    lr = des_round(lr, sched, start+0x2*step);
+    lr = des_round(lr, sched, start+0x3*step);
+    lr = des_round(lr, sched, start+0x4*step);
+    lr = des_round(lr, sched, start+0x5*step);
+    lr = des_round(lr, sched, start+0x6*step);
+    lr = des_round(lr, sched, start+0x7*step);
+    lr = des_round(lr, sched, start+0x8*step);
+    lr = des_round(lr, sched, start+0x9*step);
+    lr = des_round(lr, sched, start+0xa*step);
+    lr = des_round(lr, sched, start+0xb*step);
+    lr = des_round(lr, sched, start+0xc*step);
+    lr = des_round(lr, sched, start+0xd*step);
+    lr = des_round(lr, sched, start+0xe*step);
+    lr = des_round(lr, sched, start+0xf*step);
+    return des_swap_lr(lr);
+}
+
+static inline LR des_full_cipher(LR lr, const des_keysched *sched,
+                                 size_t start, size_t step)
+{
+    lr = des_IP(lr);
+    lr = des_inner_cipher(lr, sched, start, step);
+    lr = des_FP(lr);
+    return lr;
+}
+
+/*
+ * Parameter pairs for the start,step arguments to the cipher routines
+ * above, causing them to use the same key schedule in opposite orders.
+ */
+#define ENCIPHER 0, 1                  /* for encryption */
+#define DECIPHER 15, -1                /* for decryption */
+
+/* ----------------------------------------------------------------------
+ * Single-DES
+ */
+
+struct des_cbc_ctx {
+    des_keysched sched;
+    LR iv;
+    ssh_cipher ciph;
+};
+
+static ssh_cipher *des_cbc_new(const ssh_cipheralg *alg)
+{
+    struct des_cbc_ctx *ctx = snew(struct des_cbc_ctx);
+    ctx->ciph.vt = alg;
+    return &ctx->ciph;
+}
+
+static void des_cbc_free(ssh_cipher *ciph)
+{
+    struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+}
+
+static void des_cbc_setkey(ssh_cipher *ciph, const void *vkey)
+{
+    struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+    const uint8_t *key = (const uint8_t *)vkey;
+    des_key_setup(GET_64BIT_MSB_FIRST(key), &ctx->sched);
+}
+
+static void des_cbc_setiv(ssh_cipher *ciph, const void *iv)
+{
+    struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+    ctx->iv = des_load_lr(iv);
+}
+
+static void des_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+    struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+    uint8_t *data = (uint8_t *)vdata;
+    for (; len > 0; len -= 8, data += 8) {
+        LR plaintext = des_load_lr(data);
+        LR cipher_in = des_xor_lr(plaintext, ctx->iv);
+        LR ciphertext = des_full_cipher(cipher_in, &ctx->sched, ENCIPHER);
+        des_store_lr(data, ciphertext);
+        ctx->iv = ciphertext;
+    }
+}
+
+static void des_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+    struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+    uint8_t *data = (uint8_t *)vdata;
+    for (; len > 0; len -= 8, data += 8) {
+        LR ciphertext = des_load_lr(data);
+        LR cipher_out = des_full_cipher(ciphertext, &ctx->sched, DECIPHER);
+        LR plaintext = des_xor_lr(cipher_out, ctx->iv);
+        des_store_lr(data, plaintext);
+        ctx->iv = ciphertext;
+    }
+}
+
+const ssh_cipheralg ssh_des = {
+    // WINSCP
+    /*.new =*/ des_cbc_new,
+    /*.free =*/ des_cbc_free,
+    /*.setiv =*/ des_cbc_setiv,
+    /*.setkey =*/ des_cbc_setkey,
+    /*.encrypt =*/ des_cbc_encrypt,
+    /*.decrypt =*/ des_cbc_decrypt,
+    NULL, NULL, // WINSCP
+    /*.ssh2_id =*/ "des-cbc",
+    /*.blksize =*/ 8,
+    /*.real_keybits =*/ 56,
+    /*.padded_keybytes =*/ 8,
+    /*.flags =*/ SSH_CIPHER_IS_CBC,
+    /*.text_name =*/ "single-DES CBC",
+    NULL, NULL, // WINSCP
+};
+
+const ssh_cipheralg ssh_des_sshcom_ssh2 = {
+    /* Same as ssh_des_cbc, but with a different SSH-2 ID */
+    // WINSCP
+    /*.new =*/ des_cbc_new,
+    /*.free =*/ des_cbc_free,
+    /*.setiv =*/ des_cbc_setiv,
+    /*.setkey =*/ des_cbc_setkey,
+    /*.encrypt =*/ des_cbc_encrypt,
+    /*.decrypt =*/ des_cbc_decrypt,
+    NULL, NULL, // WINSCP
+    /*.ssh2_id =*/ "[email protected]",
+    /*.blksize =*/ 8,
+    /*.real_keybits =*/ 56,
+    /*.padded_keybytes =*/ 8,
+    /*.flags =*/ SSH_CIPHER_IS_CBC,
+    /*.text_name =*/ "single-DES CBC",
+    NULL, NULL, // WINSCP
+};
+
+static const ssh_cipheralg *const des_list[] = {
+    &ssh_des,
+    &ssh_des_sshcom_ssh2
+};
+
+const ssh2_ciphers ssh2_des = { lenof(des_list), des_list };
+
+/* ----------------------------------------------------------------------
+ * Triple-DES CBC, SSH-2 style. The CBC mode treats the three
+ * invocations of DES as a single unified cipher, and surrounds it
+ * with just one layer of CBC, so only one IV is needed.
+ */
+
+struct des3_cbc1_ctx {
+    des_keysched sched[3];
+    LR iv;
+    ssh_cipher ciph;
+};
+
+static ssh_cipher *des3_cbc1_new(const ssh_cipheralg *alg)
+{
+    struct des3_cbc1_ctx *ctx = snew(struct des3_cbc1_ctx);
+    ctx->ciph.vt = alg;
+    return &ctx->ciph;
+}
+
+static void des3_cbc1_free(ssh_cipher *ciph)
+{
+    struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+}
+
+static void des3_cbc1_setkey(ssh_cipher *ciph, const void *vkey)
+{
+    struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+    const uint8_t *key = (const uint8_t *)vkey;
+    size_t i; // WINSCP
+    for (i = 0; i < 3; i++)
+        des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]);
+}
+
+static void des3_cbc1_setiv(ssh_cipher *ciph, const void *iv)
+{
+    struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+    ctx->iv = des_load_lr(iv);
+}
+
+static void des3_cbc1_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+    struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+    uint8_t *data = (uint8_t *)vdata;
+    for (; len > 0; len -= 8, data += 8) {
+        LR plaintext = des_load_lr(data);
+        LR cipher_in = des_xor_lr(plaintext, ctx->iv);
+
+        /* Run three copies of the cipher, without undoing and redoing
+         * IP/FP in between. */
+        LR lr = des_IP(cipher_in);
+        lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER);
+        lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER);
+        lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER);
+        { // WINSCP
+        LR ciphertext = des_FP(lr);
+
+        des_store_lr(data, ciphertext);
+        ctx->iv = ciphertext;
+        } // WINSCP
+    }
+}
+
+static void des3_cbc1_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+    struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+    uint8_t *data = (uint8_t *)vdata;
+    for (; len > 0; len -= 8, data += 8) {
+        LR ciphertext = des_load_lr(data);
+
+        /* Similarly to encryption, but with the order reversed. */
+        LR lr = des_IP(ciphertext);
+        lr = des_inner_cipher(lr, &ctx->sched[2], DECIPHER);
+        lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER);
+        lr = des_inner_cipher(lr, &ctx->sched[0], DECIPHER);
+        { // WINSCP
+        LR cipher_out = des_FP(lr);
+
+        LR plaintext = des_xor_lr(cipher_out, ctx->iv);
+        des_store_lr(data, plaintext);
+        ctx->iv = ciphertext;
+        } // WINSCP
+    }
+}
+
+const ssh_cipheralg ssh_3des_ssh2 = {
+    // WINSCP
+    /*.new =*/ des3_cbc1_new,
+    /*.free =*/ des3_cbc1_free,
+    /*.setiv =*/ des3_cbc1_setiv,
+    /*.setkey =*/ des3_cbc1_setkey,
+    /*.encrypt =*/ des3_cbc1_cbc_encrypt,
+    /*.decrypt =*/ des3_cbc1_cbc_decrypt,
+    NULL, NULL, // WINSCP
+    /*.ssh2_id =*/ "3des-cbc",
+    /*.blksize =*/ 8,
+    /*.real_keybits =*/ 168,
+    /*.padded_keybytes =*/ 24,
+    /*.flags =*/ SSH_CIPHER_IS_CBC,
+    /*.text_name =*/ "triple-DES CBC",
+    NULL, NULL, // WINSCP
+};
+
+/* ----------------------------------------------------------------------
+ * Triple-DES in SDCTR mode. Again, the three DES instances are
+ * treated as one big cipher, with a single counter encrypted through
+ * all three.
+ */
+
+#define SDCTR_WORDS (8 / BIGNUM_INT_BYTES)
+
+struct des3_sdctr_ctx {
+    des_keysched sched[3];
+    BignumInt counter[SDCTR_WORDS];
+    ssh_cipher ciph;
+};
+
+static ssh_cipher *des3_sdctr_new(const ssh_cipheralg *alg)
+{
+    struct des3_sdctr_ctx *ctx = snew(struct des3_sdctr_ctx);
+    ctx->ciph.vt = alg;
+    return &ctx->ciph;
+}
+
+static void des3_sdctr_free(ssh_cipher *ciph)
+{
+    struct des3_sdctr_ctx *ctx = container_of(
+        ciph, struct des3_sdctr_ctx, ciph);
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+}
+
+static void des3_sdctr_setkey(ssh_cipher *ciph, const void *vkey)
+{
+    struct des3_sdctr_ctx *ctx = container_of(
+        ciph, struct des3_sdctr_ctx, ciph);
+    const uint8_t *key = (const uint8_t *)vkey;
+    size_t i; // WINSCP
+    for (i = 0; i < 3; i++)
+        des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]);
+}
+
+static void des3_sdctr_setiv(ssh_cipher *ciph, const void *viv)
+{
+    struct des3_sdctr_ctx *ctx = container_of(
+        ciph, struct des3_sdctr_ctx, ciph);
+    const uint8_t *iv = (const uint8_t *)viv;
+
+    /* Import the initial counter value into the internal representation */
+    unsigned i = 0; // WINSCP
+    for (i = 0; i < SDCTR_WORDS; i++)
+        ctx->counter[i] = GET_BIGNUMINT_MSB_FIRST(
+            iv + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES);
+}
+
+static void des3_sdctr_encrypt_decrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+    struct des3_sdctr_ctx *ctx = container_of(
+        ciph, struct des3_sdctr_ctx, ciph);
+    uint8_t *data = (uint8_t *)vdata;
+    uint8_t iv_buf[8];
+    for (; len > 0; len -= 8, data += 8) {
+        /* Format the counter value into the buffer. */
+        unsigned i; // WINSCP
+        for (i = 0; i < SDCTR_WORDS; i++)
+            PUT_BIGNUMINT_MSB_FIRST(
+                iv_buf + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES,
+                ctx->counter[i]);
+
+        /* Increment the counter. */
+        { // WINSCP
+        BignumCarry carry = 1;
+        for (i = 0; i < SDCTR_WORDS; i++) // WINSCP
+            BignumADC(ctx->counter[i], carry, ctx->counter[i], 0, carry);
+
+        /* Triple-encrypt the counter value from the IV. */
+        { // WINSCP
+        LR lr = des_IP(des_load_lr(iv_buf));
+        lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER);
+        lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER);
+        lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER);
+        { // WINSCP
+        LR keystream = des_FP(lr);
+
+        LR input = des_load_lr(data);
+        LR output = des_xor_lr(input, keystream);
+        des_store_lr(data, output);
+        } // WINSCP
+        } // WINSCP
+        } // WINSCP
+    }
+    smemclr(iv_buf, sizeof(iv_buf));
+}
+
+const ssh_cipheralg ssh_3des_ssh2_ctr = {
+    // WINSCP
+    /*.new =*/ des3_sdctr_new,
+    /*.free =*/ des3_sdctr_free,
+    /*.setiv =*/ des3_sdctr_setiv,
+    /*.setkey =*/ des3_sdctr_setkey,
+    /*.encrypt =*/ des3_sdctr_encrypt_decrypt,
+    /*.decrypt =*/ des3_sdctr_encrypt_decrypt,
+    NULL, NULL, // WINSCP
+    /*.ssh2_id =*/ "3des-ctr",
+    /*.blksize =*/ 8,
+    /*.real_keybits =*/ 168,
+    /*.padded_keybytes =*/ 24,
+    /*.flags =*/ 0,
+    /*.text_name =*/ "triple-DES SDCTR",
+    NULL, NULL, // WINSCP
+};
+
+static const ssh_cipheralg *const des3_list[] = {
+    &ssh_3des_ssh2_ctr,
+    &ssh_3des_ssh2
+};
+
+const ssh2_ciphers ssh2_3des = { lenof(des3_list), des3_list };
+
+/* ----------------------------------------------------------------------
+ * Triple-DES, SSH-1 style. SSH-1 replicated the whole CBC structure
+ * three times, so there have to be three separate IVs, one in each
+ * layer.
+ */
+
+struct des3_cbc3_ctx {
+    des_keysched sched[3];
+    LR iv[3];
+    ssh_cipher ciph;
+};
+
+static ssh_cipher *des3_cbc3_new(const ssh_cipheralg *alg)
+{
+    struct des3_cbc3_ctx *ctx = snew(struct des3_cbc3_ctx);
+    ctx->ciph.vt = alg;
+    return &ctx->ciph;
+}
+
+static void des3_cbc3_free(ssh_cipher *ciph)
+{
+    struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+}
+
+static void des3_cbc3_setkey(ssh_cipher *ciph, const void *vkey)
+{
+    struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+    const uint8_t *key = (const uint8_t *)vkey;
+    size_t i; // WINSCP
+    for (i = 0; i < 3; i++)
+        des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]);
+}
+
+static void des3_cbc3_setiv(ssh_cipher *ciph, const void *viv)
+{
+    struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+
+    /*
+     * In principle, we ought to provide an interface for the user to
+     * input 24 instead of 8 bytes of IV. But that would make this an
+     * ugly exception to the otherwise universal rule that IV size =
+     * cipher block size, and there's really no need to violate that
+     * rule given that this is a historical one-off oddity and SSH-1
+     * always initialises all three IVs to zero anyway. So we fudge it
+     * by just setting all the IVs to the same value.
+     */
+
+    LR iv = des_load_lr(viv);
+
+    /* But we store the IVs in permuted form, so that we can handle
+     * all three CBC layers without having to do IP/FP in between. */
+    iv = des_IP(iv);
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < 3; i++)
+        ctx->iv[i] = iv;
+    } // WINSCP
+}
+
+static void des3_cbc3_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+    struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+    uint8_t *data = (uint8_t *)vdata;
+    for (; len > 0; len -= 8, data += 8) {
+        /* Load and IP the input. */
+        LR plaintext = des_IP(des_load_lr(data));
+        LR lr = plaintext;
+
+        /* Do three passes of CBC, with the middle one inverted. */
+
+        lr = des_xor_lr(lr, ctx->iv[0]);
+        lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER);
+        ctx->iv[0] = lr;
+
+        { // WINSCP
+        LR ciphertext = lr;
+        lr = des_inner_cipher(ciphertext, &ctx->sched[1], DECIPHER);
+        lr = des_xor_lr(lr, ctx->iv[1]);
+        ctx->iv[1] = ciphertext;
+
+        lr = des_xor_lr(lr, ctx->iv[2]);
+        lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER);
+        ctx->iv[2] = lr;
+
+        des_store_lr(data, des_FP(lr));
+        } // WINSCP
+    }
+}
+
+static void des3_cbc3_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+    struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+    uint8_t *data = (uint8_t *)vdata;
+    for (; len > 0; len -= 8, data += 8) {
+        /* Load and IP the input */
+        LR lr = des_IP(des_load_lr(data));
+        LR ciphertext;
+
+        /* Do three passes of CBC, with the middle one inverted. */
+        ciphertext = lr;
+        lr = des_inner_cipher(ciphertext, &ctx->sched[2], DECIPHER);
+        lr = des_xor_lr(lr, ctx->iv[2]);
+        ctx->iv[2] = ciphertext;
+
+        lr = des_xor_lr(lr, ctx->iv[1]);
+        lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER);
+        ctx->iv[1] = lr;
+
+        ciphertext = lr;
+        lr = des_inner_cipher(ciphertext, &ctx->sched[0], DECIPHER);
+        lr = des_xor_lr(lr, ctx->iv[0]);
+        ctx->iv[0] = ciphertext;
+
+        des_store_lr(data, des_FP(lr));
+    }
+}
+
+const ssh_cipheralg ssh_3des_ssh1 = {
+    // WINSCP
+    /*.new =*/ des3_cbc3_new,
+    /*.free =*/ des3_cbc3_free,
+    /*.setiv =*/ des3_cbc3_setiv,
+    /*.setkey =*/ des3_cbc3_setkey,
+    /*.encrypt =*/ des3_cbc3_cbc_encrypt,
+    /*.decrypt =*/ des3_cbc3_cbc_decrypt,
+    NULL, NULL, // WINSCP
+    NULL, // WINSCP
+    /*.blksize =*/ 8,
+    /*.real_keybits =*/ 168,
+    /*.padded_keybytes =*/ 24,
+    /*.flags =*/ SSH_CIPHER_IS_CBC,
+    /*.text_name =*/ "triple-DES inner-CBC",
+    NULL, NULL, // WINSCP
+};

+ 263 - 0
source/putty/crypto/diffie-hellman.c

@@ -0,0 +1,263 @@
+/*
+ * Diffie-Hellman implementation for PuTTY.
+ */
+
+#include <assert.h>
+
+#include "ssh.h"
+#include "misc.h"
+#include "mpint.h"
+
+struct dh_ctx {
+    mp_int *x, *e, *p, *q, *g;
+};
+
+struct dh_extra {
+    bool gex;
+    void (*construct)(dh_ctx *ctx);
+};
+
+static void dh_group1_construct(dh_ctx *ctx)
+{
+    /* Command to recompute, from the expression in RFC 2412 section E.2:
+spigot -B16 '2^1024 - 2^960 - 1 + 2^64 * ( floor(2^894 pi) + 129093 )'
+     */
+    ctx->p = MP_LITERAL_WINSCP_STR("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF");
+    ctx->g = mp_from_integer(2);
+}
+
+static void dh_group14_construct(dh_ctx *ctx)
+{
+    /* Command to recompute, from the expression in RFC 3526 section 3:
+spigot -B16 '2^2048 - 2^1984 - 1 + 2^64 * ( floor(2^1918 pi) + 124476 )'
+     */
+    ctx->p = MP_LITERAL_WINSCP_STR("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF");
+    ctx->g = mp_from_integer(2);
+}
+
+static const struct dh_extra extra_group1 = {
+    false, dh_group1_construct,
+};
+
+const ssh_kex ssh_diffiehellman_group1_sha1 = {
+    "diffie-hellman-group1-sha1", "group1",
+    KEXTYPE_DH, &ssh_sha1, &extra_group1,
+};
+
+static const ssh_kex *const group1_list[] = {
+    &ssh_diffiehellman_group1_sha1
+};
+
+const ssh_kexes ssh_diffiehellman_group1 = { lenof(group1_list), group1_list };
+
+static const struct dh_extra extra_group14 = {
+    false, dh_group14_construct,
+};
+
+const ssh_kex ssh_diffiehellman_group14_sha256 = {
+    "diffie-hellman-group14-sha256", "group14",
+    KEXTYPE_DH, &ssh_sha256, &extra_group14,
+};
+
+const ssh_kex ssh_diffiehellman_group14_sha1 = {
+    "diffie-hellman-group14-sha1", "group14",
+    KEXTYPE_DH, &ssh_sha1, &extra_group14,
+};
+
+static const ssh_kex *const group14_list[] = {
+    &ssh_diffiehellman_group14_sha256,
+    &ssh_diffiehellman_group14_sha1
+};
+
+const ssh_kexes ssh_diffiehellman_group14 = {
+    lenof(group14_list), group14_list
+};
+
+static const struct dh_extra extra_gex = { true };
+
+static const ssh_kex ssh_diffiehellman_gex_sha256 = {
+    "diffie-hellman-group-exchange-sha256", NULL,
+    KEXTYPE_DH, &ssh_sha256, &extra_gex,
+};
+
+static const ssh_kex ssh_diffiehellman_gex_sha1 = {
+    "diffie-hellman-group-exchange-sha1", NULL,
+    KEXTYPE_DH, &ssh_sha1, &extra_gex,
+};
+
+static const ssh_kex *const gex_list[] = {
+    &ssh_diffiehellman_gex_sha256,
+    &ssh_diffiehellman_gex_sha1
+};
+
+const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list };
+
+/*
+ * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5
+ * as the mechanism.
+ *
+ * This suffix is the base64-encoded MD5 hash of the byte sequence
+ * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER
+ * encoding of the object ID 1.2.840.113554.1.2.2 which designates
+ * Kerberos v5.
+ *
+ * (The same encoded OID, minus the two-byte DER header, is defined in
+ * ssh/pgssapi.c as GSS_MECH_KRB5.)
+ */
+#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g=="
+
+static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = {
+    "gss-gex-sha1-" GSS_KRB5_OID_HASH, NULL,
+    KEXTYPE_GSS, &ssh_sha1, &extra_gex,
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = {
+    "gss-group14-sha1-" GSS_KRB5_OID_HASH, "group14",
+    KEXTYPE_GSS, &ssh_sha1, &extra_group14,
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = {
+    "gss-group1-sha1-" GSS_KRB5_OID_HASH, "group1",
+    KEXTYPE_GSS, &ssh_sha1, &extra_group1,
+};
+
+static const ssh_kex *const gssk5_sha1_kex_list[] = {
+    &ssh_gssk5_diffiehellman_gex_sha1,
+    &ssh_gssk5_diffiehellman_group14_sha1,
+    &ssh_gssk5_diffiehellman_group1_sha1
+};
+
+const ssh_kexes ssh_gssk5_sha1_kex = {
+    lenof(gssk5_sha1_kex_list), gssk5_sha1_kex_list
+};
+
+/*
+ * Common DH initialisation.
+ */
+static void dh_init(dh_ctx *ctx)
+{
+    ctx->q = mp_rshift_fixed(ctx->p, 1);
+    ctx->x = ctx->e = NULL;
+}
+
+bool dh_is_gex(const ssh_kex *kex)
+{
+    const struct dh_extra *extra = (const struct dh_extra *)kex->extra;
+    return extra->gex;
+}
+
+/*
+ * Initialise DH for a standard group.
+ */
+dh_ctx *dh_setup_group(const ssh_kex *kex)
+{
+    const struct dh_extra *extra = (const struct dh_extra *)kex->extra;
+    pinitassert(!extra->gex);
+    dh_ctx *ctx = snew(dh_ctx);
+    extra->construct(ctx);
+    dh_init(ctx);
+    return ctx;
+}
+
+/*
+ * Initialise DH for a server-supplied group.
+ */
+dh_ctx *dh_setup_gex(mp_int *pval, mp_int *gval)
+{
+    dh_ctx *ctx = snew(dh_ctx);
+    ctx->p = mp_copy(pval);
+    ctx->g = mp_copy(gval);
+    dh_init(ctx);
+    return ctx;
+}
+
+/*
+ * Return size of DH modulus p.
+ */
+int dh_modulus_bit_size(const dh_ctx *ctx)
+{
+    return mp_get_nbits(ctx->p);
+}
+
+/*
+ * Clean up and free a context.
+ */
+void dh_cleanup(dh_ctx *ctx)
+{
+    if (ctx->x)
+        mp_free(ctx->x);
+    if (ctx->e)
+        mp_free(ctx->e);
+    if (ctx->p)
+        mp_free(ctx->p);
+    if (ctx->g)
+        mp_free(ctx->g);
+    if (ctx->q)
+        mp_free(ctx->q);
+    sfree(ctx);
+}
+
+/*
+ * DH stage 1: invent a number x between 1 and q, and compute e =
+ * g^x mod p. Return e.
+ */
+mp_int *dh_create_e(dh_ctx *ctx)
+{
+    /*
+     * Lower limit is just 2.
+     */
+    mp_int *lo = mp_from_integer(2);
+
+    /*
+     * Upper limit.
+     */
+    mp_int *hi = mp_copy(ctx->q);
+    mp_sub_integer_into(hi, hi, 1);
+
+    /*
+     * Make a random number in that range.
+     */
+    ctx->x = mp_random_in_range(lo, hi);
+    mp_free(lo);
+    mp_free(hi);
+
+    /*
+     * Now compute e = g^x mod p.
+     */
+    ctx->e = mp_modpow(ctx->g, ctx->x, ctx->p);
+
+    return ctx->e;
+}
+
+/*
+ * DH stage 2-epsilon: given a number f, validate it to ensure it's in
+ * range. (RFC 4253 section 8: "Values of 'e' or 'f' that are not in
+ * the range [1, p-1] MUST NOT be sent or accepted by either side."
+ * Also, we rule out 1 and p-1 too, since that's easy to do and since
+ * they lead to obviously weak keys that even a passive eavesdropper
+ * can figure out.)
+ */
+const char *dh_validate_f(dh_ctx *ctx, mp_int *f)
+{
+    if (!mp_hs_integer(f, 2)) {
+        return "f value received is too small";
+    } else {
+        mp_int *pm1 = mp_copy(ctx->p);
+        mp_sub_integer_into(pm1, pm1, 1);
+        { // WINSCP
+        unsigned cmp = mp_cmp_hs(f, pm1);
+        mp_free(pm1);
+        if (cmp)
+            return "f value received is too large";
+        } // WINSCP
+    }
+    return NULL;
+}
+
+/*
+ * DH stage 2: given a number f, compute K = f^x mod p.
+ */
+mp_int *dh_find_K(dh_ctx *ctx, mp_int *f)
+{
+    return mp_modpow(f, ctx->x, ctx->p);
+}

+ 525 - 0
source/putty/crypto/dsa.c

@@ -0,0 +1,525 @@
+/*
+ * Digital Signature Algorithm implementation for PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "mpint.h"
+#include "misc.h"
+
+static void dsa_freekey(ssh_key *key);    /* forward reference */
+
+static ssh_key *dsa_new_pub(const ssh_keyalg *self, ptrlen data)
+{
+    BinarySource src[1];
+    struct dsa_key *dsa;
+
+    BinarySource_BARE_INIT_PL(src, data);
+    if (!ptrlen_eq_string(get_string(src), "ssh-dss"))
+        return NULL;
+
+    dsa = snew(struct dsa_key);
+    dsa->sshk.vt = &ssh_dsa;
+    dsa->p = get_mp_ssh2(src);
+    dsa->q = get_mp_ssh2(src);
+    dsa->g = get_mp_ssh2(src);
+    dsa->y = get_mp_ssh2(src);
+    dsa->x = NULL;
+
+    if (get_err(src) ||
+        mp_eq_integer(dsa->p, 0) || mp_eq_integer(dsa->q, 0)) {
+        /* Invalid key. */
+        dsa_freekey(&dsa->sshk);
+        return NULL;
+    }
+
+    return &dsa->sshk;
+}
+
+static void dsa_freekey(ssh_key *key)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+    if (dsa->p)
+        mp_free(dsa->p);
+    if (dsa->q)
+        mp_free(dsa->q);
+    if (dsa->g)
+        mp_free(dsa->g);
+    if (dsa->y)
+        mp_free(dsa->y);
+    if (dsa->x)
+        mp_free(dsa->x);
+    sfree(dsa);
+}
+
+static void append_hex_to_strbuf(strbuf *sb, mp_int *x)
+{
+    if (sb->len > 0)
+        put_byte(sb, ',');
+    put_data(sb, "0x", 2);
+    { // WINSCP
+    char *hex = mp_get_hex(x);
+    size_t hexlen = strlen(hex);
+    put_data(sb, hex, hexlen);
+    smemclr(hex, hexlen);
+    sfree(hex);
+    } // WINSCP
+}
+
+static char *dsa_cache_str(ssh_key *key)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+    strbuf *sb = strbuf_new();
+
+    if (!dsa->p) {
+        strbuf_free(sb);
+        return NULL;
+    }
+
+    append_hex_to_strbuf(sb, dsa->p);
+    append_hex_to_strbuf(sb, dsa->q);
+    append_hex_to_strbuf(sb, dsa->g);
+    append_hex_to_strbuf(sb, dsa->y);
+
+    return strbuf_to_str(sb);
+}
+
+static key_components *dsa_components(ssh_key *key)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+    key_components *kc = key_components_new();
+
+    key_components_add_text(kc, "key_type", "DSA");
+    assert(dsa->p);
+    key_components_add_mp(kc, "p", dsa->p);
+    key_components_add_mp(kc, "q", dsa->q);
+    key_components_add_mp(kc, "g", dsa->g);
+    key_components_add_mp(kc, "public_y", dsa->y);
+    if (dsa->x)
+        key_components_add_mp(kc, "private_x", dsa->x);
+
+    return kc;
+}
+
+static char *dsa_invalid(ssh_key *key, unsigned flags)
+{
+    /* No validity criterion will stop us from using a DSA key at all */
+    return NULL;
+}
+
+static bool dsa_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+    BinarySource src[1];
+    unsigned char hash[20];
+    bool toret;
+
+    if (!dsa->p)
+        return false;
+
+    BinarySource_BARE_INIT_PL(src, sig);
+
+    /*
+     * Commercial SSH (2.0.13) and OpenSSH disagree over the format
+     * of a DSA signature. OpenSSH is in line with RFC 4253:
+     * it uses a string "ssh-dss", followed by a 40-byte string
+     * containing two 160-bit integers end-to-end. Commercial SSH
+     * can't be bothered with the header bit, and considers a DSA
+     * signature blob to be _just_ the 40-byte string containing
+     * the two 160-bit integers. We tell them apart by measuring
+     * the length: length 40 means the commercial-SSH bug, anything
+     * else is assumed to be RFC-compliant.
+     */
+    if (sig.len != 40) {      /* bug not present; read admin fields */
+        ptrlen type = get_string(src);
+        sig = get_string(src);
+
+        if (get_err(src) || !ptrlen_eq_string(type, "ssh-dss") ||
+            sig.len != 40)
+            return false;
+    }
+
+    /* Now we're sitting on a 40-byte string for sure. */
+    { // WINSCP
+    mp_int *r = mp_from_bytes_be(make_ptrlen(sig.ptr, 20));
+    mp_int *s = mp_from_bytes_be(make_ptrlen((const char *)sig.ptr + 20, 20));
+    if (!r || !s) {
+        if (r)
+            mp_free(r);
+        if (s)
+            mp_free(s);
+        return false;
+    }
+
+    /* Basic sanity checks: 0 < r,s < q */
+    { // WINSCP
+    unsigned invalid = 0;
+    invalid |= mp_eq_integer(r, 0);
+    invalid |= mp_eq_integer(s, 0);
+    invalid |= mp_cmp_hs(r, dsa->q);
+    invalid |= mp_cmp_hs(s, dsa->q);
+    if (invalid) {
+        mp_free(r);
+        mp_free(s);
+        return false;
+    }
+
+    /*
+     * Step 1. w <- s^-1 mod q.
+     */
+    { // WINSCP
+    mp_int *w = mp_invert(s, dsa->q);
+    if (!w) {
+        mp_free(r);
+        mp_free(s);
+        return false;
+    }
+
+    /*
+     * Step 2. u1 <- SHA(message) * w mod q.
+     */
+    hash_simple(&ssh_sha1, data, hash);
+    { // WINSCP
+    mp_int *sha = mp_from_bytes_be(make_ptrlen(hash, 20));
+    mp_int *u1 = mp_modmul(sha, w, dsa->q);
+
+    /*
+     * Step 3. u2 <- r * w mod q.
+     */
+    mp_int *u2 = mp_modmul(r, w, dsa->q);
+
+    /*
+     * Step 4. v <- (g^u1 * y^u2 mod p) mod q.
+     */
+    mp_int *gu1p = mp_modpow(dsa->g, u1, dsa->p);
+    mp_int *yu2p = mp_modpow(dsa->y, u2, dsa->p);
+    mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dsa->p);
+    mp_int *v = mp_mod(gu1yu2p, dsa->q);
+
+    /*
+     * Step 5. v should now be equal to r.
+     */
+
+    toret = mp_cmp_eq(v, r);
+
+    mp_free(w);
+    mp_free(sha);
+    mp_free(u1);
+    mp_free(u2);
+    mp_free(gu1p);
+    mp_free(yu2p);
+    mp_free(gu1yu2p);
+    mp_free(v);
+    mp_free(r);
+    mp_free(s);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+
+    return toret;
+}
+
+static void dsa_public_blob(ssh_key *key, BinarySink *bs)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+
+    put_stringz(bs, "ssh-dss");
+    put_mp_ssh2(bs, dsa->p);
+    put_mp_ssh2(bs, dsa->q);
+    put_mp_ssh2(bs, dsa->g);
+    put_mp_ssh2(bs, dsa->y);
+}
+
+static void dsa_private_blob(ssh_key *key, BinarySink *bs)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+
+    put_mp_ssh2(bs, dsa->x);
+}
+
+static ssh_key *dsa_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv)
+{
+    BinarySource src[1];
+    ssh_key *sshk;
+    struct dsa_key *dsa;
+    ptrlen hash;
+    unsigned char digest[20];
+    mp_int *ytest;
+
+    sshk = dsa_new_pub(self, pub);
+    if (!sshk)
+        return NULL;
+
+    dsa = container_of(sshk, struct dsa_key, sshk);
+    BinarySource_BARE_INIT_PL(src, priv);
+    dsa->x = get_mp_ssh2(src);
+    if (get_err(src)) {
+        dsa_freekey(&dsa->sshk);
+        return NULL;
+    }
+
+    /*
+     * Check the obsolete hash in the old DSA key format.
+     */
+    hash = get_string(src);
+    if (hash.len == 20) {
+        ssh_hash *h = ssh_hash_new(&ssh_sha1);
+        put_mp_ssh2(h, dsa->p);
+        put_mp_ssh2(h, dsa->q);
+        put_mp_ssh2(h, dsa->g);
+        ssh_hash_final(h, digest);
+        if (!smemeq(hash.ptr, digest, 20)) {
+            dsa_freekey(&dsa->sshk);
+            return NULL;
+        }
+    }
+
+    /*
+     * Now ensure g^x mod p really is y.
+     */
+    ytest = mp_modpow(dsa->g, dsa->x, dsa->p);
+    if (!mp_cmp_eq(ytest, dsa->y)) {
+        mp_free(ytest);
+        dsa_freekey(&dsa->sshk);
+        return NULL;
+    }
+    mp_free(ytest);
+
+    return &dsa->sshk;
+}
+
+static ssh_key *dsa_new_priv_openssh(const ssh_keyalg *self,
+                                     BinarySource *src)
+{
+    struct dsa_key *dsa;
+
+    dsa = snew(struct dsa_key);
+    dsa->sshk.vt = &ssh_dsa;
+
+    dsa->p = get_mp_ssh2(src);
+    dsa->q = get_mp_ssh2(src);
+    dsa->g = get_mp_ssh2(src);
+    dsa->y = get_mp_ssh2(src);
+    dsa->x = get_mp_ssh2(src);
+
+    if (get_err(src) ||
+        mp_eq_integer(dsa->q, 0) || mp_eq_integer(dsa->p, 0)) {
+        /* Invalid key. */
+        dsa_freekey(&dsa->sshk);
+        return NULL;
+    }
+
+    return &dsa->sshk;
+}
+
+static void dsa_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+
+    put_mp_ssh2(bs, dsa->p);
+    put_mp_ssh2(bs, dsa->q);
+    put_mp_ssh2(bs, dsa->g);
+    put_mp_ssh2(bs, dsa->y);
+    put_mp_ssh2(bs, dsa->x);
+}
+
+static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
+{
+    ssh_key *sshk;
+    struct dsa_key *dsa;
+    int ret;
+
+    sshk = dsa_new_pub(self, pub);
+    if (!sshk)
+        return -1;
+
+    dsa = container_of(sshk, struct dsa_key, sshk);
+    ret = mp_get_nbits(dsa->p);
+    dsa_freekey(&dsa->sshk);
+
+    return ret;
+}
+
+mp_int *dsa_gen_k(const char *id_string, mp_int *modulus,
+                     mp_int *private_key,
+                     unsigned char *digest, int digest_len)
+{
+    /*
+     * The basic DSA signing algorithm is:
+     *
+     *  - invent a random k between 1 and q-1 (exclusive).
+     *  - Compute r = (g^k mod p) mod q.
+     *  - Compute s = k^-1 * (hash + x*r) mod q.
+     *
+     * This has the dangerous properties that:
+     *
+     *  - if an attacker in possession of the public key _and_ the
+     *    signature (for example, the host you just authenticated
+     *    to) can guess your k, he can reverse the computation of s
+     *    and work out x = r^-1 * (s*k - hash) mod q. That is, he
+     *    can deduce the private half of your key, and masquerade
+     *    as you for as long as the key is still valid.
+     *
+     *  - since r is a function purely of k and the public key, if
+     *    the attacker only has a _range of possibilities_ for k
+     *    it's easy for him to work through them all and check each
+     *    one against r; he'll never be unsure of whether he's got
+     *    the right one.
+     *
+     *  - if you ever sign two different hashes with the same k, it
+     *    will be immediately obvious because the two signatures
+     *    will have the same r, and moreover an attacker in
+     *    possession of both signatures (and the public key of
+     *    course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q,
+     *    and from there deduce x as before.
+     *
+     *  - the Bleichenbacher attack on DSA makes use of methods of
+     *    generating k which are significantly non-uniformly
+     *    distributed; in particular, generating a 160-bit random
+     *    number and reducing it mod q is right out.
+     *
+     * For this reason we must be pretty careful about how we
+     * generate our k. Since this code runs on Windows, with no
+     * particularly good system entropy sources, we can't trust our
+     * RNG itself to produce properly unpredictable data. Hence, we
+     * use a totally different scheme instead.
+     *
+     * What we do is to take a SHA-512 (_big_) hash of the private
+     * key x, and then feed this into another SHA-512 hash that
+     * also includes the message hash being signed. That is:
+     *
+     *   proto_k = SHA512 ( SHA512(x) || SHA160(message) )
+     *
+     * This number is 512 bits long, so reducing it mod q won't be
+     * noticeably non-uniform. So
+     *
+     *   k = proto_k mod q
+     *
+     * This has the interesting property that it's _deterministic_:
+     * signing the same hash twice with the same key yields the
+     * same signature.
+     *
+     * Despite this determinism, it's still not predictable to an
+     * attacker, because in order to repeat the SHA-512
+     * construction that created it, the attacker would have to
+     * know the private key value x - and by assumption he doesn't,
+     * because if he knew that he wouldn't be attacking k!
+     *
+     * (This trick doesn't, _per se_, protect against reuse of k.
+     * Reuse of k is left to chance; all it does is prevent
+     * _excessively high_ chances of reuse of k due to entropy
+     * problems.)
+     *
+     * Thanks to Colin Plumb for the general idea of using x to
+     * ensure k is hard to guess, and to the Cambridge University
+     * Computer Security Group for helping to argue out all the
+     * fine details.
+     */
+    ssh_hash *h;
+    unsigned char digest512[64];
+
+    /*
+     * Hash some identifying text plus x.
+     */
+    h = ssh_hash_new(&ssh_sha512);
+    put_asciz(h, id_string);
+    put_mp_ssh2(h, private_key);
+    ssh_hash_digest(h, digest512);
+
+    /*
+     * Now hash that digest plus the message hash.
+     */
+    ssh_hash_reset(h);
+    put_data(h, digest512, sizeof(digest512));
+    put_data(h, digest, digest_len);
+    ssh_hash_final(h, digest512);
+
+    /*
+     * Now convert the result into a bignum, and coerce it to the
+     * range [2,q), which we do by reducing it mod q-2 and adding 2.
+     */
+    { // WINSCP
+    mp_int *modminus2 = mp_copy(modulus);
+    mp_sub_integer_into(modminus2, modminus2, 2);
+    { // WINSCP
+    mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64));
+    mp_int *k = mp_mod(proto_k, modminus2);
+    mp_free(proto_k);
+    mp_free(modminus2);
+    mp_add_integer_into(k, k, 2);
+
+    smemclr(digest512, sizeof(digest512));
+
+    return k;
+    } // WINSCP
+    } // WINSCP
+}
+
+static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
+{
+    struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+    unsigned char digest[20];
+    int i;
+
+    hash_simple(&ssh_sha1, data, digest);
+
+    { // WINSCP
+    mp_int *k = dsa_gen_k("DSA deterministic k generator", dsa->q, dsa->x,
+                          digest, sizeof(digest));
+    mp_int *kinv = mp_invert(k, dsa->q);       /* k^-1 mod q */
+
+    /*
+     * Now we have k, so just go ahead and compute the signature.
+     */
+    mp_int *gkp = mp_modpow(dsa->g, k, dsa->p); /* g^k mod p */
+    mp_int *r = mp_mod(gkp, dsa->q);        /* r = (g^k mod p) mod q */
+    mp_free(gkp);
+
+    { // WINSCP
+    mp_int *hash = mp_from_bytes_be(make_ptrlen(digest, 20));
+    mp_int *xr = mp_mul(dsa->x, r);
+    mp_int *hxr = mp_add(xr, hash);         /* hash + x*r */
+    { // WINSCP
+    mp_int *s = mp_modmul(kinv, hxr, dsa->q); /* s = k^-1 * (hash+x*r) mod q */
+    mp_free(hxr);
+    mp_free(xr);
+    mp_free(kinv);
+    mp_free(k);
+    mp_free(hash);
+
+    put_stringz(bs, "ssh-dss");
+    put_uint32(bs, 40);
+    for (i = 0; i < 20; i++)
+        put_byte(bs, mp_get_byte(r, 19 - i));
+    for (i = 0; i < 20; i++)
+        put_byte(bs, mp_get_byte(s, 19 - i));
+    mp_free(r);
+    mp_free(s);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+const ssh_keyalg ssh_dsa = {
+    // WINSCP
+    /*.new_pub =*/ dsa_new_pub,
+    /*.new_priv =*/ dsa_new_priv,
+    /*.new_priv_openssh =*/ dsa_new_priv_openssh,
+    /*.freekey =*/ dsa_freekey,
+    /*.invalid =*/ dsa_invalid,
+    /*.sign =*/ dsa_sign,
+    /*.verify =*/ dsa_verify,
+    /*.public_blob =*/ dsa_public_blob,
+    /*.private_blob =*/ dsa_private_blob,
+    /*.openssh_blob =*/ dsa_openssh_blob,
+    /*.cache_str =*/ dsa_cache_str,
+    /*.components =*/ dsa_components,
+    /*.pubkey_bits =*/ dsa_pubkey_bits,
+    /*.ssh_id =*/ "ssh-dss",
+    /*.cache_id =*/ "dss",
+    NULL, NULL,
+};

+ 1210 - 0
source/putty/crypto/ecc-arithmetic.c

@@ -0,0 +1,1210 @@
+/*
+ * Basic arithmetic for elliptic curves, implementing ecc.h.
+ */
+
+#include <assert.h>
+
+#include "ssh.h"
+#include "mpint.h"
+#include "ecc.h"
+
+/* ----------------------------------------------------------------------
+ * Weierstrass curves.
+ */
+
+struct WeierstrassPoint {
+    /*
+     * Internally, we represent a point using 'Jacobian coordinates',
+     * which are three values X,Y,Z whose relation to the affine
+     * coordinates x,y is that x = X/Z^2 and y = Y/Z^3.
+     *
+     * This allows us to do most of our calculations without having to
+     * take an inverse mod p: every time the obvious affine formulae
+     * would need you to divide by something, you instead multiply it
+     * into the 'denominator' coordinate Z. You only have to actually
+     * take the inverse of Z when you need to get the affine
+     * coordinates back out, which means you do it once after your
+     * entire computation instead of at every intermediate step.
+     *
+     * The point at infinity is represented by setting all three
+     * coordinates to zero.
+     *
+     * These values are also stored in the Montgomery-multiplication
+     * transformed representation.
+     */
+    mp_int *X, *Y, *Z;
+
+    WeierstrassCurve *wc;
+};
+
+struct WeierstrassCurve {
+    /* Prime modulus of the finite field. */
+    mp_int *p;
+
+    /* Persistent Montgomery context for doing arithmetic mod p. */
+    MontyContext *mc;
+
+    /* Modsqrt context for point decompression. NULL if this curve was
+     * constructed without providing nonsquare_mod_p. */
+    ModsqrtContext *sc;
+
+    /* Parameters of the curve, in Montgomery-multiplication
+     * transformed form. */
+    mp_int *a, *b;
+};
+
+WeierstrassCurve *ecc_weierstrass_curve(
+    mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p)
+{
+    WeierstrassCurve *wc = snew(WeierstrassCurve);
+    wc->p = mp_copy(p);
+    wc->mc = monty_new(p);
+    wc->a = monty_import(wc->mc, a);
+    wc->b = monty_import(wc->mc, b);
+
+    if (nonsquare_mod_p)
+        wc->sc = modsqrt_new(p, nonsquare_mod_p);
+    else
+        wc->sc = NULL;
+
+    return wc;
+}
+
+void ecc_weierstrass_curve_free(WeierstrassCurve *wc)
+{
+    mp_free(wc->p);
+    mp_free(wc->a);
+    mp_free(wc->b);
+    monty_free(wc->mc);
+    if (wc->sc)
+        modsqrt_free(wc->sc);
+    sfree(wc);
+}
+
+static WeierstrassPoint *ecc_weierstrass_point_new_empty(WeierstrassCurve *wc)
+{
+    WeierstrassPoint *wp = snew(WeierstrassPoint);
+    wp->wc = wc;
+    wp->X = wp->Y = wp->Z = NULL;
+    return wp;
+}
+
+static WeierstrassPoint *ecc_weierstrass_point_new_imported(
+    WeierstrassCurve *wc, mp_int *monty_x, mp_int *monty_y)
+{
+    WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc);
+    wp->X = monty_x;
+    wp->Y = monty_y;
+    wp->Z = mp_copy(monty_identity(wc->mc));
+    return wp;
+}
+
+WeierstrassPoint *ecc_weierstrass_point_new(
+    WeierstrassCurve *wc, mp_int *x, mp_int *y)
+{
+    return ecc_weierstrass_point_new_imported(
+        wc, monty_import(wc->mc, x), monty_import(wc->mc, y));
+}
+
+WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *wc)
+{
+    WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc);
+    size_t bits = mp_max_bits(wc->p);
+    wp->X = mp_new(bits);
+    wp->Y = mp_new(bits);
+    wp->Z = mp_new(bits);
+    return wp;
+}
+
+void ecc_weierstrass_point_copy_into(
+    WeierstrassPoint *dest, WeierstrassPoint *src)
+{
+    mp_copy_into(dest->X, src->X);
+    mp_copy_into(dest->Y, src->Y);
+    mp_copy_into(dest->Z, src->Z);
+}
+
+WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *orig)
+{
+    WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(orig->wc);
+    wp->X = mp_copy(orig->X);
+    wp->Y = mp_copy(orig->Y);
+    wp->Z = mp_copy(orig->Z);
+    return wp;
+}
+
+void ecc_weierstrass_point_free(WeierstrassPoint *wp)
+{
+    mp_free(wp->X);
+    mp_free(wp->Y);
+    mp_free(wp->Z);
+    smemclr(wp, sizeof(*wp));
+    sfree(wp);
+}
+
+WeierstrassPoint *ecc_weierstrass_point_new_from_x(
+    WeierstrassCurve *wc, mp_int *xorig, unsigned desired_y_parity)
+{
+    pinitassert(wc->sc);
+
+    /*
+     * The curve equation is y^2 = x^3 + ax + b, which is already
+     * conveniently in a form where we can compute the RHS and take
+     * the square root of it to get y.
+     */
+    unsigned success;
+
+    mp_int *x = monty_import(wc->mc, xorig);
+
+    /*
+     * Compute the RHS of the curve equation. We don't need to take
+     * account of z here, because we're constructing the point from
+     * scratch. So it really is just x^3 + ax + b.
+     */
+    mp_int *x2 = monty_mul(wc->mc, x, x);
+    mp_int *x2_plus_a = monty_add(wc->mc, x2, wc->a);
+    mp_int *x3_plus_ax = monty_mul(wc->mc, x2_plus_a, x);
+    mp_int *rhs = monty_add(wc->mc, x3_plus_ax, wc->b);
+    mp_free(x2);
+    mp_free(x2_plus_a);
+    mp_free(x3_plus_ax);
+
+    { // WINSCP
+    mp_int *y = monty_modsqrt(wc->sc, rhs, &success);
+    mp_free(rhs);
+
+    if (!success) {
+        /* Failure! x^3+ax+b worked out to be a number that has no
+         * square root mod p. In this situation there's no point in
+         * trying to be time-constant, since the protocol sequence is
+         * going to diverge anyway when we complain to whoever gave us
+         * this bogus value. */
+        mp_free(x);
+        mp_free(y);
+        return NULL;
+    }
+
+    /*
+     * Choose whichever of y and p-y has the specified parity (of its
+     * lowest positive residue mod p).
+     */
+    { // WINSCP
+    mp_int *tmp = monty_export(wc->mc, y);
+    unsigned flip = (mp_get_bit(tmp, 0) ^ desired_y_parity) & 1;
+    mp_sub_into(tmp, wc->p, y);
+    mp_select_into(y, y, tmp, flip);
+    mp_free(tmp);
+    } // WINSCP
+
+    return ecc_weierstrass_point_new_imported(wc, x, y);
+    } // WINSCP
+}
+
+static void ecc_weierstrass_cond_overwrite(
+    WeierstrassPoint *dest, WeierstrassPoint *src, unsigned overwrite)
+{
+    mp_select_into(dest->X, dest->X, src->X, overwrite);
+    mp_select_into(dest->Y, dest->Y, src->Y, overwrite);
+    mp_select_into(dest->Z, dest->Z, src->Z, overwrite);
+}
+
+static void ecc_weierstrass_cond_swap(
+    WeierstrassPoint *P, WeierstrassPoint *Q, unsigned swap)
+{
+    mp_cond_swap(P->X, Q->X, swap);
+    mp_cond_swap(P->Y, Q->Y, swap);
+    mp_cond_swap(P->Z, Q->Z, swap);
+}
+
+/*
+ * Shared code between all three of the basic arithmetic functions:
+ * once we've determined the slope of the line that we're intersecting
+ * the curve with, this takes care of finding the coordinates of the
+ * third intersection point (given the two input x-coordinates and one
+ * of the y-coords) and negating it to generate the output.
+ */
+static inline void ecc_weierstrass_epilogue(
+    mp_int *Px, mp_int *Qx, mp_int *Py, mp_int *common_Z,
+    mp_int *lambda_n, mp_int *lambda_d, WeierstrassPoint *out)
+{
+    WeierstrassCurve *wc = out->wc;
+
+    /* Powers of the numerator and denominator of the slope lambda */
+    mp_int *lambda_n2 = monty_mul(wc->mc, lambda_n, lambda_n);
+    mp_int *lambda_d2 = monty_mul(wc->mc, lambda_d, lambda_d);
+    mp_int *lambda_d3 = monty_mul(wc->mc, lambda_d, lambda_d2);
+
+    /* Make the output x-coordinate */
+    mp_int *xsum = monty_add(wc->mc, Px, Qx);
+    mp_int *lambda_d2_xsum = monty_mul(wc->mc, lambda_d2, xsum);
+    out->X = monty_sub(wc->mc, lambda_n2, lambda_d2_xsum);
+
+    /* Make the output y-coordinate */
+    { // WINSCP
+    mp_int *lambda_d2_Px = monty_mul(wc->mc, lambda_d2, Px);
+    mp_int *xdiff = monty_sub(wc->mc, lambda_d2_Px, out->X);
+    mp_int *lambda_n_xdiff = monty_mul(wc->mc, lambda_n, xdiff);
+    mp_int *lambda_d3_Py = monty_mul(wc->mc, lambda_d3, Py);
+    out->Y = monty_sub(wc->mc, lambda_n_xdiff, lambda_d3_Py);
+
+    /* Make the output z-coordinate */
+    out->Z = monty_mul(wc->mc, common_Z, lambda_d);
+
+    mp_free(lambda_n2);
+    mp_free(lambda_d2);
+    mp_free(lambda_d3);
+    mp_free(xsum);
+    mp_free(xdiff);
+    mp_free(lambda_d2_xsum);
+    mp_free(lambda_n_xdiff);
+    mp_free(lambda_d2_Px);
+    mp_free(lambda_d3_Py);
+    } // WINSCP
+}
+
+/*
+ * Shared code between add and add_general: put the two input points
+ * over a common denominator, and determine the slope lambda of the
+ * line through both of them. If the points have the same
+ * x-coordinate, then the slope will be returned with a zero
+ * denominator.
+ */
+static inline void ecc_weierstrass_add_prologue(
+    WeierstrassPoint *P, WeierstrassPoint *Q,
+    mp_int **Px, mp_int **Py, mp_int **Qx, mp_int **denom,
+    mp_int **lambda_n, mp_int **lambda_d)
+{
+    WeierstrassCurve *wc = P->wc;
+
+    /* Powers of the points' denominators */
+    mp_int *Pz2 = monty_mul(wc->mc, P->Z, P->Z);
+    mp_int *Pz3 = monty_mul(wc->mc, Pz2, P->Z);
+    mp_int *Qz2 = monty_mul(wc->mc, Q->Z, Q->Z);
+    mp_int *Qz3 = monty_mul(wc->mc, Qz2, Q->Z);
+
+    /* Points' x,y coordinates scaled by the other one's denominator
+     * (raised to the appropriate power) */
+    *Px = monty_mul(wc->mc, P->X, Qz2);
+    *Py = monty_mul(wc->mc, P->Y, Qz3);
+    *Qx = monty_mul(wc->mc, Q->X, Pz2);
+    { // WINSCP
+    mp_int *Qy = monty_mul(wc->mc, Q->Y, Pz3);
+
+    /* Common denominator */
+    *denom = monty_mul(wc->mc, P->Z, Q->Z);
+
+    /* Slope of the line through the two points, if P != Q */
+    *lambda_n = monty_sub(wc->mc, Qy, *Py);
+    *lambda_d = monty_sub(wc->mc, *Qx, *Px);
+
+    mp_free(Pz2);
+    mp_free(Pz3);
+    mp_free(Qz2);
+    mp_free(Qz3);
+    mp_free(Qy);
+    } // WINSCP
+}
+
+WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *P, WeierstrassPoint *Q)
+{
+    WeierstrassCurve *wc = P->wc;
+    assert(Q->wc == wc);
+
+    { // WINSCP
+    WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc);
+
+    mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d;
+    ecc_weierstrass_add_prologue(
+        P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d);
+
+    /* Never expect to have received two mutually inverse inputs, or
+     * two identical ones (which would make this a doubling). In other
+     * words, the two input x-coordinates (after putting over a common
+     * denominator) should never have been equal. */
+    assert(!mp_eq_integer(lambda_n, 0));
+
+    /* Now go to the common epilogue code. */
+    ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S);
+
+    mp_free(Px);
+    mp_free(Py);
+    mp_free(Qx);
+    mp_free(denom);
+    mp_free(lambda_n);
+    mp_free(lambda_d);
+
+    return S;
+    } // WINSCP
+}
+
+/*
+ * Code to determine the slope of the line you need to intersect with
+ * the curve in the case where you're adding a point to itself. In
+ * this situation you can't just say "the line through both input
+ * points" because that's under-determined; instead, you have to take
+ * the _tangent_ to the curve at the given point, by differentiating
+ * the curve equation y^2=x^3+ax+b to get 2y dy/dx = 3x^2+a.
+ */
+static inline void ecc_weierstrass_tangent_slope(
+    WeierstrassPoint *P, mp_int **lambda_n, mp_int **lambda_d)
+{
+    WeierstrassCurve *wc = P->wc;
+
+    mp_int *X2 = monty_mul(wc->mc, P->X, P->X);
+    mp_int *twoX2 = monty_add(wc->mc, X2, X2);
+    mp_int *threeX2 = monty_add(wc->mc, twoX2, X2);
+    mp_int *Z2 = monty_mul(wc->mc, P->Z, P->Z);
+    mp_int *Z4 = monty_mul(wc->mc, Z2, Z2);
+    mp_int *aZ4 = monty_mul(wc->mc, wc->a, Z4);
+
+    *lambda_n = monty_add(wc->mc, threeX2, aZ4);
+    *lambda_d = monty_add(wc->mc, P->Y, P->Y);
+
+    mp_free(X2);
+    mp_free(twoX2);
+    mp_free(threeX2);
+    mp_free(Z2);
+    mp_free(Z4);
+    mp_free(aZ4);
+}
+
+WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *P)
+{
+    WeierstrassCurve *wc = P->wc;
+    WeierstrassPoint *D = ecc_weierstrass_point_new_empty(wc);
+
+    mp_int *lambda_n, *lambda_d;
+    ecc_weierstrass_tangent_slope(P, &lambda_n, &lambda_d);
+    ecc_weierstrass_epilogue(P->X, P->X, P->Y, P->Z, lambda_n, lambda_d, D);
+    mp_free(lambda_n);
+    mp_free(lambda_d);
+
+    return D;
+}
+
+static inline void ecc_weierstrass_select_into(
+    WeierstrassPoint *dest, WeierstrassPoint *P, WeierstrassPoint *Q,
+    unsigned choose_Q)
+{
+    mp_select_into(dest->X, P->X, Q->X, choose_Q);
+    mp_select_into(dest->Y, P->Y, Q->Y, choose_Q);
+    mp_select_into(dest->Z, P->Z, Q->Z, choose_Q);
+}
+
+WeierstrassPoint *ecc_weierstrass_add_general(
+    WeierstrassPoint *P, WeierstrassPoint *Q)
+{
+    WeierstrassCurve *wc = P->wc;
+    assert(Q->wc == wc);
+
+    { // WINSCP
+    WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc);
+
+    /* Parameters for the epilogue, and slope of the line if P != Q */
+    mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d;
+    ecc_weierstrass_add_prologue(
+        P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d);
+
+    /* Slope if P == Q */
+    { // WINSCP
+    mp_int *lambda_n_tangent, *lambda_d_tangent;
+    ecc_weierstrass_tangent_slope(P, &lambda_n_tangent, &lambda_d_tangent);
+
+    /* Select between those slopes depending on whether P == Q */
+    { // WINSCP
+    unsigned same_x_coord = mp_eq_integer(lambda_d, 0);
+    unsigned same_y_coord = mp_eq_integer(lambda_n, 0);
+    unsigned equality = same_x_coord & same_y_coord;
+    mp_select_into(lambda_n, lambda_n, lambda_n_tangent, equality);
+    mp_select_into(lambda_d, lambda_d, lambda_d_tangent, equality);
+
+    /* Now go to the common code between addition and doubling */
+    ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S);
+
+    /* Check for the input identity cases, and overwrite the output if
+     * necessary. */
+    ecc_weierstrass_select_into(S, S, Q, mp_eq_integer(P->Z, 0));
+    ecc_weierstrass_select_into(S, S, P, mp_eq_integer(Q->Z, 0));
+
+    /*
+     * In the case where P == -Q and so the output is the identity,
+     * we'll have calculated lambda_d = 0 and so the output will have
+     * z==0 already. Detect that and use it to normalise the other two
+     * coordinates to zero.
+     */
+    { // WINSCP
+    unsigned output_id = mp_eq_integer(S->Z, 0);
+    mp_cond_clear(S->X, output_id);
+    mp_cond_clear(S->Y, output_id);
+
+    mp_free(Px);
+    mp_free(Py);
+    mp_free(Qx);
+    mp_free(denom);
+    mp_free(lambda_n);
+    mp_free(lambda_d);
+    mp_free(lambda_n_tangent);
+    mp_free(lambda_d_tangent);
+
+    return S;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *B, mp_int *n)
+{
+    WeierstrassPoint *two_B = ecc_weierstrass_double(B);
+    WeierstrassPoint *k_B = ecc_weierstrass_point_copy(B);
+    WeierstrassPoint *kplus1_B = ecc_weierstrass_point_copy(two_B);
+
+    /*
+     * This multiply routine more or less follows the shape of the
+     * 'Montgomery ladder' technique that you have to use under the
+     * extra constraint on addition in Montgomery curves, because it
+     * was fresh in my mind and easier to just do it the same way. See
+     * the comment in ecc_montgomery_multiply.
+     */
+
+    unsigned not_started_yet = 1;
+    size_t bitindex; // WINSCP
+    for (bitindex = mp_max_bits(n); bitindex-- > 0 ;) {
+        unsigned nbit = mp_get_bit(n, bitindex);
+
+        WeierstrassPoint *sum = ecc_weierstrass_add(k_B, kplus1_B);
+        ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit);
+        { // WINSCP
+        WeierstrassPoint *other = ecc_weierstrass_double(k_B);
+        ecc_weierstrass_point_free(k_B);
+        ecc_weierstrass_point_free(kplus1_B);
+        k_B = other;
+        kplus1_B = sum;
+        ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit);
+
+        ecc_weierstrass_cond_overwrite(k_B, B, not_started_yet);
+        ecc_weierstrass_cond_overwrite(kplus1_B, two_B, not_started_yet);
+        not_started_yet &= ~nbit;
+        } // WINSCP
+    }
+
+    ecc_weierstrass_point_free(two_B);
+    ecc_weierstrass_point_free(kplus1_B);
+    return k_B;
+}
+
+unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp)
+{
+    return mp_eq_integer(wp->Z, 0);
+}
+
+/*
+ * Normalise a point by scaling its Jacobian coordinates so that Z=1.
+ * This doesn't change what point is represented by the triple, but it
+ * means the affine x,y can now be easily recovered from X and Y.
+ */
+static void ecc_weierstrass_normalise(WeierstrassPoint *wp)
+{
+    WeierstrassCurve *wc = wp->wc;
+    mp_int *zinv = monty_invert(wc->mc, wp->Z);
+    mp_int *zinv2 = monty_mul(wc->mc, zinv, zinv);
+    mp_int *zinv3 = monty_mul(wc->mc, zinv2, zinv);
+    monty_mul_into(wc->mc, wp->X, wp->X, zinv2);
+    monty_mul_into(wc->mc, wp->Y, wp->Y, zinv3);
+    monty_mul_into(wc->mc, wp->Z, wp->Z, zinv);
+    mp_free(zinv);
+    mp_free(zinv2);
+    mp_free(zinv3);
+}
+
+void ecc_weierstrass_get_affine(
+    WeierstrassPoint *wp, mp_int **x, mp_int **y)
+{
+    WeierstrassCurve *wc = wp->wc;
+
+    ecc_weierstrass_normalise(wp);
+
+    if (x)
+        *x = monty_export(wc->mc, wp->X);
+    if (y)
+        *y = monty_export(wc->mc, wp->Y);
+}
+
+unsigned ecc_weierstrass_point_valid(WeierstrassPoint *P)
+{
+    WeierstrassCurve *wc = P->wc;
+
+    /*
+     * The projective version of the curve equation is
+     * Y^2 = X^3 + a X Z^4 + b Z^6
+     */
+    mp_int *lhs = monty_mul(P->wc->mc, P->Y, P->Y);
+    mp_int *x2 = monty_mul(wc->mc, P->X, P->X);
+    mp_int *x3 = monty_mul(wc->mc, x2, P->X);
+    mp_int *z2 = monty_mul(wc->mc, P->Z, P->Z);
+    mp_int *z4 = monty_mul(wc->mc, z2, z2);
+    mp_int *az4 = monty_mul(wc->mc, wc->a, z4);
+    mp_int *axz4 = monty_mul(wc->mc, az4, P->X);
+    mp_int *x3_plus_axz4 = monty_add(wc->mc, x3, axz4);
+    mp_int *z6 = monty_mul(wc->mc, z2, z4);
+    mp_int *bz6 = monty_mul(wc->mc, wc->b, z6);
+    mp_int *rhs = monty_add(wc->mc, x3_plus_axz4, bz6);
+
+    unsigned valid = mp_cmp_eq(lhs, rhs);
+
+    mp_free(lhs);
+    mp_free(x2);
+    mp_free(x3);
+    mp_free(z2);
+    mp_free(z4);
+    mp_free(az4);
+    mp_free(axz4);
+    mp_free(x3_plus_axz4);
+    mp_free(z6);
+    mp_free(bz6);
+    mp_free(rhs);
+
+    return valid;
+}
+
+/* ----------------------------------------------------------------------
+ * Montgomery curves.
+ */
+
+struct MontgomeryPoint {
+    /* XZ coordinates. These represent the affine x coordinate by the
+     * relationship x = X/Z. */
+    mp_int *X, *Z;
+
+    MontgomeryCurve *mc;
+};
+
+struct MontgomeryCurve {
+    /* Prime modulus of the finite field. */
+    mp_int *p;
+
+    /* Montgomery context for arithmetic mod p. */
+    MontyContext *mc;
+
+    /* Parameters of the curve, in Montgomery-multiplication
+     * transformed form. */
+    mp_int *a, *b;
+
+    /* (a+2)/4, also in Montgomery-multiplication form. */
+    mp_int *aplus2over4;
+};
+
+MontgomeryCurve *ecc_montgomery_curve(
+    mp_int *p, mp_int *a, mp_int *b)
+{
+    MontgomeryCurve *mc = snew(MontgomeryCurve);
+    mc->p = mp_copy(p);
+    mc->mc = monty_new(p);
+    mc->a = monty_import(mc->mc, a);
+    mc->b = monty_import(mc->mc, b);
+
+    { // WINSCP
+    mp_int *four = mp_from_integer(4);
+    mp_int *fourinverse = mp_invert(four, mc->p);
+    mp_int *aplus2 = mp_copy(a);
+    mp_add_integer_into(aplus2, aplus2, 2);
+    { // WINSCP
+    mp_int *aplus2over4 = mp_modmul(aplus2, fourinverse, mc->p);
+    mc->aplus2over4 = monty_import(mc->mc, aplus2over4);
+    mp_free(four);
+    mp_free(fourinverse);
+    mp_free(aplus2);
+    mp_free(aplus2over4);
+    } // WINSCP
+    } // WINSCP
+
+    return mc;
+}
+
+void ecc_montgomery_curve_free(MontgomeryCurve *mc)
+{
+    mp_free(mc->p);
+    mp_free(mc->a);
+    mp_free(mc->b);
+    mp_free(mc->aplus2over4);
+    monty_free(mc->mc);
+    sfree(mc);
+}
+
+static MontgomeryPoint *ecc_montgomery_point_new_empty(MontgomeryCurve *mc)
+{
+    MontgomeryPoint *mp = snew(MontgomeryPoint);
+    mp->mc = mc;
+    mp->X = mp->Z = NULL;
+    return mp;
+}
+
+MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x)
+{
+    MontgomeryPoint *mp = ecc_montgomery_point_new_empty(mc);
+    mp->X = monty_import(mc->mc, x);
+    mp->Z = mp_copy(monty_identity(mc->mc));
+    return mp;
+}
+
+void ecc_montgomery_point_copy_into(
+    MontgomeryPoint *dest, MontgomeryPoint *src)
+{
+    mp_copy_into(dest->X, src->X);
+    mp_copy_into(dest->Z, src->Z);
+}
+
+MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig)
+{
+    MontgomeryPoint *mp = ecc_montgomery_point_new_empty(orig->mc);
+    mp->X = mp_copy(orig->X);
+    mp->Z = mp_copy(orig->Z);
+    return mp;
+}
+
+void ecc_montgomery_point_free(MontgomeryPoint *mp)
+{
+    mp_free(mp->X);
+    mp_free(mp->Z);
+    smemclr(mp, sizeof(*mp));
+    sfree(mp);
+}
+
+static void ecc_montgomery_cond_overwrite(
+    MontgomeryPoint *dest, MontgomeryPoint *src, unsigned overwrite)
+{
+    mp_select_into(dest->X, dest->X, src->X, overwrite);
+    mp_select_into(dest->Z, dest->Z, src->Z, overwrite);
+}
+
+static void ecc_montgomery_cond_swap(
+    MontgomeryPoint *P, MontgomeryPoint *Q, unsigned swap)
+{
+    mp_cond_swap(P->X, Q->X, swap);
+    mp_cond_swap(P->Z, Q->Z, swap);
+}
+
+MontgomeryPoint *ecc_montgomery_diff_add(
+    MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ)
+{
+    MontgomeryCurve *mc = P->mc;
+    assert(Q->mc == mc);
+    assert(PminusQ->mc == mc);
+
+    /*
+     * Differential addition is achieved using the following formula
+     * that relates the affine x-coordinates of P, Q, P+Q and P-Q:
+     *
+     * x(P+Q) x(P-Q) (x(Q)-x(P))^2 = (x(P)x(Q) - 1)^2
+     *
+     * As with the Weierstrass coordinates, the code below transforms
+     * that affine relation into a projective one to avoid having to
+     * do a division during the main arithmetic.
+     */
+
+    { // WINSCP
+    MontgomeryPoint *S = ecc_montgomery_point_new_empty(mc);
+
+    mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z);
+    mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z);
+    mp_int *Qx_m_Qz = monty_sub(mc->mc, Q->X, Q->Z);
+    mp_int *Qx_p_Qz = monty_add(mc->mc, Q->X, Q->Z);
+    mp_int *PmQp = monty_mul(mc->mc, Px_m_Pz, Qx_p_Qz);
+    mp_int *PpQm = monty_mul(mc->mc, Px_p_Pz, Qx_m_Qz);
+    mp_int *Xpre = monty_add(mc->mc, PmQp, PpQm);
+    mp_int *Zpre = monty_sub(mc->mc, PmQp, PpQm);
+    mp_int *Xpre2 = monty_mul(mc->mc, Xpre, Xpre);
+    mp_int *Zpre2 = monty_mul(mc->mc, Zpre, Zpre);
+    S->X = monty_mul(mc->mc, Xpre2, PminusQ->Z);
+    S->Z = monty_mul(mc->mc, Zpre2, PminusQ->X);
+
+    mp_free(Px_m_Pz);
+    mp_free(Px_p_Pz);
+    mp_free(Qx_m_Qz);
+    mp_free(Qx_p_Qz);
+    mp_free(PmQp);
+    mp_free(PpQm);
+    mp_free(Xpre);
+    mp_free(Zpre);
+    mp_free(Xpre2);
+    mp_free(Zpre2);
+
+    return S;
+    } // WINSCP
+}
+
+MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P)
+{
+    MontgomeryCurve *mc = P->mc;
+    MontgomeryPoint *D = ecc_montgomery_point_new_empty(mc);
+
+    /*
+     * To double a point in affine coordinates, in principle you can
+     * use the same technique as for Weierstrass: differentiate the
+     * curve equation to get the tangent line at the input point, use
+     * that to get an expression for y which you substitute back into
+     * the curve equation, and subtract the known two roots (in this
+     * case both the same) from the x^2 coefficient of the resulting
+     * cubic.
+     *
+     * In this case, we don't have an input y-coordinate, so you have
+     * to do a bit of extra transformation to find a formula that can
+     * work without it. The tangent formula is (3x^2 + 2ax + 1)/(2y),
+     * and when that appears in the final formula it will be squared -
+     * so we can substitute the y^2 in the denominator for the RHS of
+     * the curve equation. Put together, that gives
+     *
+     *   x_out = (x+1)^2 (x-1)^2 / 4(x^3+ax^2+x)
+     *
+     * and, as usual, the code below transforms that into projective
+     * form to avoid the division.
+     */
+
+    mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z);
+    mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z);
+    mp_int *Px_m_Pz_2 = monty_mul(mc->mc, Px_m_Pz, Px_m_Pz);
+    mp_int *Px_p_Pz_2 = monty_mul(mc->mc, Px_p_Pz, Px_p_Pz);
+    D->X = monty_mul(mc->mc, Px_m_Pz_2, Px_p_Pz_2);
+    { // WINSCP
+    mp_int *XZ = monty_mul(mc->mc, P->X, P->Z);
+    mp_int *twoXZ = monty_add(mc->mc, XZ, XZ);
+    mp_int *fourXZ = monty_add(mc->mc, twoXZ, twoXZ);
+    mp_int *fourXZ_scaled = monty_mul(mc->mc, fourXZ, mc->aplus2over4);
+    mp_int *Zpre = monty_add(mc->mc, Px_m_Pz_2, fourXZ_scaled);
+    D->Z = monty_mul(mc->mc, fourXZ, Zpre);
+
+    mp_free(Px_m_Pz);
+    mp_free(Px_p_Pz);
+    mp_free(Px_m_Pz_2);
+    mp_free(Px_p_Pz_2);
+    mp_free(XZ);
+    mp_free(twoXZ);
+    mp_free(fourXZ);
+    mp_free(fourXZ_scaled);
+    mp_free(Zpre);
+    } // WINSCP
+
+    return D;
+}
+
+static void ecc_montgomery_normalise(MontgomeryPoint *mp)
+{
+    MontgomeryCurve *mc = mp->mc;
+    mp_int *zinv = monty_invert(mc->mc, mp->Z);
+    monty_mul_into(mc->mc, mp->X, mp->X, zinv);
+    monty_mul_into(mc->mc, mp->Z, mp->Z, zinv);
+    mp_free(zinv);
+}
+
+MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *B, mp_int *n)
+{
+    /*
+     * 'Montgomery ladder' technique, to compute an arbitrary integer
+     * multiple of B under the constraint that you can only add two
+     * unequal points if you also know their difference.
+     *
+     * The setup is that you maintain two curve points one of which is
+     * always the other one plus B. Call them kB and (k+1)B, where k
+     * is some integer that evolves as we go along. We begin by
+     * doubling the input B, to initialise those points to B and 2B,
+     * so that k=1.
+     *
+     * At each stage, we add kB and (k+1)B together - which we can do
+     * under the differential-addition constraint because we know
+     * their difference is always just B - to give us (2k+1)B. Then we
+     * double one of kB or (k+1)B, and depending on which one we
+     * choose, we end up with (2k)B or (2k+2)B. Either way, that
+     * differs by B from the other value we've just computed. So in
+     * each iteration, we do one diff-add and one doubling, plus a
+     * couple of conditional swaps to choose which value we double and
+     * which way round we put the output points, and the effect is to
+     * replace k with either 2k or 2k+1, which we choose based on the
+     * appropriate bit of the desired exponent.
+     *
+     * This routine doesn't assume we know the exact location of the
+     * topmost set bit of the exponent. So to maintain constant time
+     * it does an iteration for every _potential_ bit, starting from
+     * the top downwards; after each iteration in which we haven't
+     * seen a set exponent bit yet, we just overwrite the two points
+     * with B and 2B again,
+     */
+
+    MontgomeryPoint *two_B = ecc_montgomery_double(B);
+    MontgomeryPoint *k_B = ecc_montgomery_point_copy(B);
+    MontgomeryPoint *kplus1_B = ecc_montgomery_point_copy(two_B);
+
+    unsigned not_started_yet = 1;
+    size_t bitindex; // WINSCP
+    for (bitindex = mp_max_bits(n); bitindex-- > 0 ;) {
+        unsigned nbit = mp_get_bit(n, bitindex);
+
+        MontgomeryPoint *sum = ecc_montgomery_diff_add(k_B, kplus1_B, B);
+        ecc_montgomery_cond_swap(k_B, kplus1_B, nbit);
+        { // WINSCP
+        MontgomeryPoint *other = ecc_montgomery_double(k_B);
+        ecc_montgomery_point_free(k_B);
+        ecc_montgomery_point_free(kplus1_B);
+        k_B = other;
+        kplus1_B = sum;
+        ecc_montgomery_cond_swap(k_B, kplus1_B, nbit);
+
+        ecc_montgomery_cond_overwrite(k_B, B, not_started_yet);
+        ecc_montgomery_cond_overwrite(kplus1_B, two_B, not_started_yet);
+        not_started_yet &= ~nbit;
+        } // WINSCP
+    }
+
+    ecc_montgomery_point_free(two_B);
+    ecc_montgomery_point_free(kplus1_B);
+    return k_B;
+}
+
+void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x)
+{
+    MontgomeryCurve *mc = mp->mc;
+
+    ecc_montgomery_normalise(mp);
+
+    if (x)
+        *x = monty_export(mc->mc, mp->X);
+}
+
+unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp)
+{
+    return mp_eq_integer(mp->Z, 0);
+}
+
+/* ----------------------------------------------------------------------
+ * Twisted Edwards curves.
+ */
+
+struct EdwardsPoint {
+    /*
+     * We represent an Edwards curve point in 'extended coordinates'.
+     * There's more than one coordinate system going by that name,
+     * unfortunately. These ones have the semantics that X,Y,Z are
+     * ordinary projective coordinates (so x=X/Z and y=Y/Z), but also,
+     * we store the extra value T = xyZ = XY/Z.
+     */
+    mp_int *X, *Y, *Z, *T;
+
+    EdwardsCurve *ec;
+};
+
+struct EdwardsCurve {
+    /* Prime modulus of the finite field. */
+    mp_int *p;
+
+    /* Montgomery context for arithmetic mod p. */
+    MontyContext *mc;
+
+    /* Modsqrt context for point decompression. */
+    ModsqrtContext *sc;
+
+    /* Parameters of the curve, in Montgomery-multiplication
+     * transformed form. */
+    mp_int *d, *a;
+};
+
+EdwardsCurve *ecc_edwards_curve(mp_int *p, mp_int *d, mp_int *a,
+                                mp_int *nonsquare_mod_p)
+{
+    EdwardsCurve *ec = snew(EdwardsCurve);
+    ec->p = mp_copy(p);
+    ec->mc = monty_new(p);
+    ec->d = monty_import(ec->mc, d);
+    ec->a = monty_import(ec->mc, a);
+
+    if (nonsquare_mod_p)
+        ec->sc = modsqrt_new(p, nonsquare_mod_p);
+    else
+        ec->sc = NULL;
+
+    return ec;
+}
+
+void ecc_edwards_curve_free(EdwardsCurve *ec)
+{
+    mp_free(ec->p);
+    mp_free(ec->d);
+    mp_free(ec->a);
+    monty_free(ec->mc);
+    if (ec->sc)
+        modsqrt_free(ec->sc);
+    sfree(ec);
+}
+
+static EdwardsPoint *ecc_edwards_point_new_empty(EdwardsCurve *ec)
+{
+    EdwardsPoint *ep = snew(EdwardsPoint);
+    ep->ec = ec;
+    ep->X = ep->Y = ep->Z = ep->T = NULL;
+    return ep;
+}
+
+static EdwardsPoint *ecc_edwards_point_new_imported(
+    EdwardsCurve *ec, mp_int *monty_x, mp_int *monty_y)
+{
+    EdwardsPoint *ep = ecc_edwards_point_new_empty(ec);
+    ep->X = monty_x;
+    ep->Y = monty_y;
+    ep->T = monty_mul(ec->mc, ep->X, ep->Y);
+    ep->Z = mp_copy(monty_identity(ec->mc));
+    return ep;
+}
+
+EdwardsPoint *ecc_edwards_point_new(
+    EdwardsCurve *ec, mp_int *x, mp_int *y)
+{
+    return ecc_edwards_point_new_imported(
+        ec, monty_import(ec->mc, x), monty_import(ec->mc, y));
+}
+
+void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src)
+{
+    mp_copy_into(dest->X, src->X);
+    mp_copy_into(dest->Y, src->Y);
+    mp_copy_into(dest->Z, src->Z);
+    mp_copy_into(dest->T, src->T);
+}
+
+EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig)
+{
+    EdwardsPoint *ep = ecc_edwards_point_new_empty(orig->ec);
+    ep->X = mp_copy(orig->X);
+    ep->Y = mp_copy(orig->Y);
+    ep->Z = mp_copy(orig->Z);
+    ep->T = mp_copy(orig->T);
+    return ep;
+}
+
+void ecc_edwards_point_free(EdwardsPoint *ep)
+{
+    mp_free(ep->X);
+    mp_free(ep->Y);
+    mp_free(ep->Z);
+    mp_free(ep->T);
+    smemclr(ep, sizeof(*ep));
+    sfree(ep);
+}
+
+EdwardsPoint *ecc_edwards_point_new_from_y(
+    EdwardsCurve *ec, mp_int *yorig, unsigned desired_x_parity)
+{
+    pinitassert(ec->sc);
+
+    /*
+     * The curve equation is ax^2 + y^2 = 1 + dx^2y^2, which
+     * rearranges to x^2(dy^2-a) = y^2-1. So we compute
+     * (y^2-1)/(dy^2-a) and take its square root.
+     */
+    unsigned success;
+
+    mp_int *y = monty_import(ec->mc, yorig);
+    mp_int *y2 = monty_mul(ec->mc, y, y);
+    mp_int *dy2 = monty_mul(ec->mc, ec->d, y2);
+    mp_int *dy2ma = monty_sub(ec->mc, dy2, ec->a);
+    mp_int *y2m1 = monty_sub(ec->mc, y2, monty_identity(ec->mc));
+    mp_int *recip_denominator = monty_invert(ec->mc, dy2ma);
+    mp_int *radicand = monty_mul(ec->mc, y2m1, recip_denominator);
+    mp_int *x = monty_modsqrt(ec->sc, radicand, &success);
+    mp_free(y2);
+    mp_free(dy2);
+    mp_free(dy2ma);
+    mp_free(y2m1);
+    mp_free(recip_denominator);
+    mp_free(radicand);
+
+    if (!success) {
+        /* Failure! x^2 worked out to be a number that has no square
+         * root mod p. In this situation there's no point in trying to
+         * be time-constant, since the protocol sequence is going to
+         * diverge anyway when we complain to whoever gave us this
+         * bogus value. */
+        mp_free(x);
+        mp_free(y);
+        return NULL;
+    }
+
+    /*
+     * Choose whichever of x and p-x has the specified parity (of its
+     * lowest positive residue mod p).
+     */
+    { // WINSCP
+    mp_int *tmp = monty_export(ec->mc, x);
+    unsigned flip = (mp_get_bit(tmp, 0) ^ desired_x_parity) & 1;
+    mp_sub_into(tmp, ec->p, x);
+    mp_select_into(x, x, tmp, flip);
+    mp_free(tmp);
+    } // WINSCP
+
+    return ecc_edwards_point_new_imported(ec, x, y);
+}
+
+static void ecc_edwards_cond_overwrite(
+    EdwardsPoint *dest, EdwardsPoint *src, unsigned overwrite)
+{
+    mp_select_into(dest->X, dest->X, src->X, overwrite);
+    mp_select_into(dest->Y, dest->Y, src->Y, overwrite);
+    mp_select_into(dest->Z, dest->Z, src->Z, overwrite);
+    mp_select_into(dest->T, dest->T, src->T, overwrite);
+}
+
+static void ecc_edwards_cond_swap(
+    EdwardsPoint *P, EdwardsPoint *Q, unsigned swap)
+{
+    mp_cond_swap(P->X, Q->X, swap);
+    mp_cond_swap(P->Y, Q->Y, swap);
+    mp_cond_swap(P->Z, Q->Z, swap);
+    mp_cond_swap(P->T, Q->T, swap);
+}
+
+EdwardsPoint *ecc_edwards_add(EdwardsPoint *P, EdwardsPoint *Q)
+{
+    EdwardsCurve *ec = P->ec;
+    assert(Q->ec == ec);
+
+    { // WINSCP
+    EdwardsPoint *S = ecc_edwards_point_new_empty(ec);
+
+    /*
+     * The affine rule for Edwards addition of (x1,y1) and (x2,y2) is
+     *
+     *   x_out = (x1 y2 +   y1 x2) / (1 + d x1 x2 y1 y2)
+     *   y_out = (y1 y2 - a x1 x2) / (1 - d x1 x2 y1 y2)
+     *
+     * The formulae below are listed as 'add-2008-hwcd' in
+     * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html
+     *
+     * and if you undo the careful optimisation to find out what
+     * they're actually computing, it comes out to
+     *
+     *   X_out = (X1 Y2 +   Y1 X2) (Z1 Z2 - d T1 T2)
+     *   Y_out = (Y1 Y2 - a X1 X2) (Z1 Z2 + d T1 T2)
+     *   Z_out = (Z1 Z2 - d T1 T2) (Z1 Z2 + d T1 T2)
+     *   T_out = (X1 Y2 +   Y1 X2) (Y1 Y2 - a X1 X2)
+     */
+    mp_int *PxQx = monty_mul(ec->mc, P->X, Q->X);
+    mp_int *PyQy = monty_mul(ec->mc, P->Y, Q->Y);
+    mp_int *PtQt = monty_mul(ec->mc, P->T, Q->T);
+    mp_int *PzQz = monty_mul(ec->mc, P->Z, Q->Z);
+    mp_int *Psum = monty_add(ec->mc, P->X, P->Y);
+    mp_int *Qsum = monty_add(ec->mc, Q->X, Q->Y);
+    mp_int *aPxQx = monty_mul(ec->mc, ec->a, PxQx);
+    mp_int *dPtQt = monty_mul(ec->mc, ec->d, PtQt);
+    mp_int *sumprod = monty_mul(ec->mc, Psum, Qsum);
+    mp_int *xx_p_yy = monty_add(ec->mc, PxQx, PyQy);
+    mp_int *E = monty_sub(ec->mc, sumprod, xx_p_yy);
+    mp_int *F = monty_sub(ec->mc, PzQz, dPtQt);
+    mp_int *G = monty_add(ec->mc, PzQz, dPtQt);
+    mp_int *H = monty_sub(ec->mc, PyQy, aPxQx);
+    S->X = monty_mul(ec->mc, E, F);
+    S->Z = monty_mul(ec->mc, F, G);
+    S->Y = monty_mul(ec->mc, G, H);
+    S->T = monty_mul(ec->mc, H, E);
+
+    mp_free(PxQx);
+    mp_free(PyQy);
+    mp_free(PtQt);
+    mp_free(PzQz);
+    mp_free(Psum);
+    mp_free(Qsum);
+    mp_free(aPxQx);
+    mp_free(dPtQt);
+    mp_free(sumprod);
+    mp_free(xx_p_yy);
+    mp_free(E);
+    mp_free(F);
+    mp_free(G);
+    mp_free(H);
+
+    return S;
+    } // WINSCP
+}
+
+static void ecc_edwards_normalise(EdwardsPoint *ep)
+{
+    EdwardsCurve *ec = ep->ec;
+    mp_int *zinv = monty_invert(ec->mc, ep->Z);
+    monty_mul_into(ec->mc, ep->X, ep->X, zinv);
+    monty_mul_into(ec->mc, ep->Y, ep->Y, zinv);
+    monty_mul_into(ec->mc, ep->Z, ep->Z, zinv);
+    mp_free(zinv);
+    monty_mul_into(ec->mc, ep->T, ep->X, ep->Y);
+}
+
+EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *B, mp_int *n)
+{
+    EdwardsPoint *two_B = ecc_edwards_add(B, B);
+    EdwardsPoint *k_B = ecc_edwards_point_copy(B);
+    EdwardsPoint *kplus1_B = ecc_edwards_point_copy(two_B);
+
+    /*
+     * Another copy of the same exponentiation routine following the
+     * pattern of the Montgomery ladder, because it works as well as
+     * any other technique and this way I didn't have to debug two of
+     * them.
+     */
+
+    unsigned not_started_yet = 1;
+    size_t bitindex; // WINSCP
+    for (bitindex = mp_max_bits(n); bitindex-- > 0 ;) {
+        unsigned nbit = mp_get_bit(n, bitindex);
+
+        EdwardsPoint *sum = ecc_edwards_add(k_B, kplus1_B);
+        ecc_edwards_cond_swap(k_B, kplus1_B, nbit);
+        { // WINSCP
+        EdwardsPoint *other = ecc_edwards_add(k_B, k_B);
+        ecc_edwards_point_free(k_B);
+        ecc_edwards_point_free(kplus1_B);
+        k_B = other;
+        kplus1_B = sum;
+        ecc_edwards_cond_swap(k_B, kplus1_B, nbit);
+
+        ecc_edwards_cond_overwrite(k_B, B, not_started_yet);
+        ecc_edwards_cond_overwrite(kplus1_B, two_B, not_started_yet);
+        not_started_yet &= ~nbit;
+        } // WINSCP
+    }
+
+    ecc_edwards_point_free(two_B);
+    ecc_edwards_point_free(kplus1_B);
+    return k_B;
+}
+
+/*
+ * Helper routine to determine whether two values each given as a pair
+ * of projective coordinates represent the same affine value.
+ */
+static inline unsigned projective_eq(
+    MontyContext *mc, mp_int *An, mp_int *Ad,
+    mp_int *Bn, mp_int *Bd)
+{
+    mp_int *AnBd = monty_mul(mc, An, Bd);
+    mp_int *BnAd = monty_mul(mc, Bn, Ad);
+    unsigned toret = mp_cmp_eq(AnBd, BnAd);
+    mp_free(AnBd);
+    mp_free(BnAd);
+    return toret;
+}
+
+unsigned ecc_edwards_eq(EdwardsPoint *P, EdwardsPoint *Q)
+{
+    EdwardsCurve *ec = P->ec;
+    assert(Q->ec == ec);
+
+    return (projective_eq(ec->mc, P->X, P->Z, Q->X, Q->Z) &
+            projective_eq(ec->mc, P->Y, P->Z, Q->Y, Q->Z));
+}
+
+void ecc_edwards_get_affine(EdwardsPoint *ep, mp_int **x, mp_int **y)
+{
+    EdwardsCurve *ec = ep->ec;
+
+    ecc_edwards_normalise(ep);
+
+    if (x)
+        *x = monty_export(ec->mc, ep->X);
+    if (y)
+        *y = monty_export(ec->mc, ep->Y);
+}

+ 1941 - 0
source/putty/crypto/ecc-ssh.c

@@ -0,0 +1,1941 @@
+/*
+ * Elliptic-curve signing and key exchange for PuTTY's SSH layer.
+ */
+
+/*
+ * References:
+ *
+ * Elliptic curves in SSH are specified in RFC 5656:
+ *   http://tools.ietf.org/html/rfc5656
+ *
+ * That specification delegates details of public key formatting and a
+ * lot of underlying mechanism to SEC 1:
+ *   http://www.secg.org/sec1-v2.pdf
+ *
+ * Montgomery maths from:
+ * Handbook of elliptic and hyperelliptic curve cryptography, Chapter 13
+ *   http://cs.ucsb.edu/~koc/ccs130h/2013/EllipticHyperelliptic-CohenFrey.pdf
+ *
+ * Curve25519 spec from libssh (with reference to other things in the
+ * libssh code):
+ *   https://git.libssh.org/users/aris/libssh.git/tree/doc/[email protected]
+ *
+ * Edwards DSA:
+ *   http://ed25519.cr.yp.to/ed25519-20110926.pdf
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "mpint.h"
+#include "ecc.h"
+
+#ifdef MPEXT
+int ec_curve_cleanup = 0;
+
+static void finalize_common(struct ec_curve * curve)
+{
+    mp_free(curve->p);
+}
+
+static void finalize_wcurve(struct ec_curve *curve)
+{
+    ecc_weierstrass_curve_free(curve->w.wc);
+    ecc_weierstrass_point_free(curve->w.G);
+    mp_free(curve->w.G_order);
+    finalize_common(curve);
+}
+
+static void finalize_mcurve(struct ec_curve *curve)
+{
+    ecc_montgomery_curve_free(curve->m.mc);
+    ecc_montgomery_point_free(curve->m.G);
+    finalize_common(curve);
+}
+
+static void finalize_ecurve(struct ec_curve *curve)
+{
+    ecc_edwards_curve_free(curve->e.ec);
+    ecc_edwards_point_free(curve->e.G);
+    mp_free(curve->e.G_order);
+    finalize_common(curve);
+}
+#endif
+
+/* ----------------------------------------------------------------------
+ * Elliptic curve definitions
+ */
+
+static void initialise_common(
+    struct ec_curve *curve, EllipticCurveType type, mp_int *p,
+    unsigned extrabits)
+{
+    curve->type = type;
+    curve->p = mp_copy(p);
+    curve->fieldBits = mp_get_nbits(p);
+    curve->fieldBytes = (curve->fieldBits + extrabits + 7) / 8;
+}
+
+static void initialise_wcurve(
+    struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b,
+    mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order)
+{
+    initialise_common(curve, EC_WEIERSTRASS, p, 0);
+
+    curve->w.wc = ecc_weierstrass_curve(p, a, b, nonsquare);
+
+    curve->w.G = ecc_weierstrass_point_new(curve->w.wc, G_x, G_y);
+    curve->w.G_order = mp_copy(G_order);
+}
+
+static void initialise_mcurve(
+    struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b,
+    mp_int *G_x, unsigned log2_cofactor)
+{
+    initialise_common(curve, EC_MONTGOMERY, p, 0);
+
+    curve->m.mc = ecc_montgomery_curve(p, a, b);
+    curve->m.log2_cofactor = log2_cofactor;
+
+    curve->m.G = ecc_montgomery_point_new(curve->m.mc, G_x);
+}
+
+static void initialise_ecurve(
+    struct ec_curve *curve, mp_int *p, mp_int *d, mp_int *a,
+    mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order,
+    unsigned log2_cofactor)
+{
+    /* Ensure curve->fieldBytes is long enough to store an extra bit
+     * for a compressed point */
+    initialise_common(curve, EC_EDWARDS, p, 1);
+
+    curve->e.ec = ecc_edwards_curve(p, d, a, nonsquare);
+    curve->e.log2_cofactor = log2_cofactor;
+
+    curve->e.G = ecc_edwards_point_new(curve->e.ec, G_x, G_y);
+    curve->e.G_order = mp_copy(G_order);
+}
+
+static struct ec_curve *ec_p256(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    #ifdef MPEXT
+    if (ec_curve_cleanup)
+    {
+        if (initialised) finalize_wcurve(&curve);
+        initialised = 0;
+        return NULL;
+    }
+    #endif
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff);
+        mp_int *a = MP_LITERAL(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc);
+        mp_int *b = MP_LITERAL(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b);
+        mp_int *G_x = MP_LITERAL(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296);
+        mp_int *G_y = MP_LITERAL(0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5);
+        mp_int *G_order = MP_LITERAL(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551);
+        mp_int *nonsquare_mod_p = mp_from_integer(3);
+        initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order);
+        mp_free(p);
+        mp_free(a);
+        mp_free(b);
+        mp_free(G_x);
+        mp_free(G_y);
+        mp_free(G_order);
+        mp_free(nonsquare_mod_p);
+
+        curve.textname = curve.name = "nistp256";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_p384(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    #ifdef MPEXT
+    if (ec_curve_cleanup)
+    {
+        if (initialised) finalize_wcurve(&curve);
+        initialised = 0;
+        return NULL;
+    }
+    #endif
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff);
+        mp_int *a = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc);
+        mp_int *b = MP_LITERAL(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef);
+        mp_int *G_x = MP_LITERAL(0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7);
+        mp_int *G_y = MP_LITERAL(0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f);
+        mp_int *G_order = MP_LITERAL(0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973);
+        mp_int *nonsquare_mod_p = mp_from_integer(19);
+        initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order);
+        mp_free(p);
+        mp_free(a);
+        mp_free(b);
+        mp_free(G_x);
+        mp_free(G_y);
+        mp_free(G_order);
+        mp_free(nonsquare_mod_p);
+
+        curve.textname = curve.name = "nistp384";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_p521(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    #ifdef MPEXT
+    if (ec_curve_cleanup)
+    {
+        if (initialised) finalize_wcurve(&curve);
+        initialised = 0;
+        return NULL;
+    }
+    #endif
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
+        mp_int *a = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc);
+        mp_int *b = MP_LITERAL(0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00);
+        mp_int *G_x = MP_LITERAL(0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66);
+        mp_int *G_y = MP_LITERAL(0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650);
+        mp_int *G_order = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409);
+        mp_int *nonsquare_mod_p = mp_from_integer(3);
+        initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order);
+        mp_free(p);
+        mp_free(a);
+        mp_free(b);
+        mp_free(G_x);
+        mp_free(G_y);
+        mp_free(G_order);
+        mp_free(nonsquare_mod_p);
+
+        curve.textname = curve.name = "nistp521";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_curve25519(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    #ifdef MPEXT
+    if (ec_curve_cleanup)
+    {
+        if (initialised) finalize_mcurve(&curve);
+        initialised = 0;
+        return NULL;
+    }
+    #endif
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed);
+        mp_int *a = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000076d06);
+        mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000001);
+        mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000009);
+        initialise_mcurve(&curve, p, a, b, G_x, 3);
+        mp_free(p);
+        mp_free(a);
+        mp_free(b);
+        mp_free(G_x);
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+        curve.textname = "Curve25519";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_curve448(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
+        mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6);
+        mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001);
+        mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005);
+        initialise_mcurve(&curve, p, a, b, G_x, 2);
+        mp_free(p);
+        mp_free(a);
+        mp_free(b);
+        mp_free(G_x);
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+        curve.textname = "Curve448";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_ed25519(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    #ifdef MPEXT
+    if (ec_curve_cleanup)
+    {
+        if (initialised) finalize_ecurve(&curve);
+        initialised = 0;
+        return NULL;
+    }
+    #endif
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed);
+        mp_int *d = MP_LITERAL(0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3);
+        mp_int *a = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec); /* == p-1 */
+        mp_int *G_x = MP_LITERAL(0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a);
+        mp_int *G_y = MP_LITERAL(0x6666666666666666666666666666666666666666666666666666666666666658);
+        mp_int *G_order = MP_LITERAL(0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed);
+        mp_int *nonsquare_mod_p = mp_from_integer(2);
+        initialise_ecurve(&curve, p, d, a, nonsquare_mod_p,
+                          G_x, G_y, G_order, 3);
+        mp_free(p);
+        mp_free(d);
+        mp_free(a);
+        mp_free(G_x);
+        mp_free(G_y);
+        mp_free(G_order);
+        mp_free(nonsquare_mod_p);
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+
+        curve.textname = "Ed25519";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_ed448(void)
+{
+    static struct ec_curve curve = { 0 };
+    static bool initialised = false;
+
+    if (!initialised)
+    {
+        mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
+        mp_int *d = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756); /* = p - 39081 */
+        mp_int *a = MP_LITERAL(0x1);
+        mp_int *G_x = MP_LITERAL(0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e);
+        mp_int *G_y = MP_LITERAL(0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14);
+        mp_int *G_order = MP_LITERAL(0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3);
+        mp_int *nonsquare_mod_p = mp_from_integer(7);
+        initialise_ecurve(&curve, p, d, a, nonsquare_mod_p,
+                          G_x, G_y, G_order, 2);
+        mp_free(p);
+        mp_free(d);
+        mp_free(a);
+        mp_free(G_x);
+        mp_free(G_y);
+        mp_free(G_order);
+        mp_free(nonsquare_mod_p);
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+
+        curve.textname = "Ed448";
+
+        /* Now initialised, no need to do it again */
+        initialised = true;
+    }
+
+    return &curve;
+}
+
+/* ----------------------------------------------------------------------
+ * Public point from private
+ */
+
+struct ecsign_extra {
+    struct ec_curve *(*curve)(void);
+    const ssh_hashalg *hash;
+
+    /* These fields are used by the OpenSSH PEM format importer/exporter */
+    const unsigned char *oid;
+    int oidlen;
+
+    /* Some EdDSA instances prefix a string to all hash preimages, to
+     * disambiguate which signature variant they're being used with */
+    ptrlen hash_prefix;
+};
+
+WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)alg->extra;
+    struct ec_curve *curve = extra->curve();
+    pinitassert(curve->type == EC_WEIERSTRASS);
+
+    mp_int *priv_reduced = mp_mod(private_key, curve->p);
+    WeierstrassPoint *toret = ecc_weierstrass_multiply(
+        curve->w.G, priv_reduced);
+    mp_free(priv_reduced);
+    return toret;
+}
+
+static mp_int *eddsa_exponent_from_hash(
+    ptrlen hash, const struct ec_curve *curve)
+{
+    /*
+     * Make an integer out of the hash data, little-endian.
+     */
+    pinitassert(hash.len >= curve->fieldBytes);
+    mp_int *e = mp_from_bytes_le(make_ptrlen(hash.ptr, curve->fieldBytes));
+
+    /*
+     * Set the highest bit that fits in the modulus, and clear any
+     * above that.
+     */
+    mp_set_bit(e, curve->fieldBits - 1, 1);
+    mp_reduce_mod_2to(e, curve->fieldBits);
+
+    /*
+     * Clear a curve-specific number of low bits.
+     */
+    { // WINSCP
+    unsigned bit; // WINSCP 
+    for (bit = 0; bit < curve->e.log2_cofactor; bit++)
+        mp_set_bit(e, bit, 0);
+    } // WINSCP
+
+    return e;
+}
+
+EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)alg->extra;
+    struct ec_curve *curve = extra->curve();
+    pinitassert(curve->type == EC_EDWARDS);
+
+    ssh_hash *h = ssh_hash_new(extra->hash);
+    size_t i; // WINSCP
+    for (i = 0; i < curve->fieldBytes; ++i)
+        put_byte(h, mp_get_byte(private_key, i));
+
+    { // WINSCP
+    unsigned char hash[MAX_HASH_LEN];
+    ssh_hash_final(h, hash);
+
+    { // WINSCP
+    mp_int *exponent = eddsa_exponent_from_hash(
+        make_ptrlen(hash, extra->hash->hlen), curve);
+
+    EdwardsPoint *toret = ecc_edwards_multiply(curve->e.G, exponent);
+    mp_free(exponent);
+
+    return toret;
+    } // WINSCP
+    } // WINSCP
+}
+
+/* ----------------------------------------------------------------------
+ * Marshalling and unmarshalling functions
+ */
+
+static mp_int *BinarySource_get_mp_le(BinarySource *src)
+{
+    return mp_from_bytes_le(get_string(src));
+}
+#define get_mp_le(src) BinarySource_get_mp_le(BinarySource_UPCAST(src))
+
+static void BinarySink_put_mp_le_fixedlen(BinarySink *bs, mp_int *x,
+                                          size_t bytes)
+{
+    put_uint32(bs, bytes);
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < bytes; ++i)
+        put_byte(bs, mp_get_byte(x, i));
+    } // WINSCP
+}
+#define put_mp_le_fixedlen(bs, x, bytes)                        \
+    BinarySink_put_mp_le_fixedlen(BinarySink_UPCAST(bs), x, bytes)
+
+static WeierstrassPoint *ecdsa_decode(
+    ptrlen encoded, const struct ec_curve *curve)
+{
+    pinitassert(curve->type == EC_WEIERSTRASS);
+    BinarySource src[1];
+
+    BinarySource_BARE_INIT_PL(src, encoded);
+    { // WINSCP
+    unsigned char format_type = get_byte(src);
+
+    WeierstrassPoint *P;
+
+    size_t len = get_avail(src);
+    mp_int *x;
+    mp_int *y;
+
+    switch (format_type) {
+      case 0:
+        /* The identity. */
+        P = ecc_weierstrass_point_new_identity(curve->w.wc);
+        break;
+      case 2:
+      case 3:
+        /* A compressed point, in which the x-coordinate is stored in
+         * full, and y is deduced from that and a single bit
+         * indicating its parity (stored in the format type byte). */
+        x = mp_from_bytes_be(get_data(src, len));
+        P = ecc_weierstrass_point_new_from_x(curve->w.wc, x, format_type & 1);
+        mp_free(x);
+        if (!P)            /* this can fail if the input is invalid */
+            return NULL;
+        break;
+      case 4:
+        /* An uncompressed point: the x,y coordinates are stored in
+         * full. We expect the rest of the string to have even length,
+         * and be divided half and half between the two values. */
+        if (len % 2 != 0)
+            return NULL;
+        len /= 2;
+        x = mp_from_bytes_be(get_data(src, len));
+        y = mp_from_bytes_be(get_data(src, len));
+        P = ecc_weierstrass_point_new(curve->w.wc, x, y);
+        mp_free(x);
+        mp_free(y);
+        break;
+      default:
+        /* An unrecognised type byte. */
+        return NULL;
+    }
+
+    /* Verify the point is on the curve */
+    if (!ecc_weierstrass_point_valid(P)) {
+        ecc_weierstrass_point_free(P);
+        return NULL;
+    }
+
+    return P;
+    } // WINSCP
+}
+
+static WeierstrassPoint *BinarySource_get_wpoint(
+    BinarySource *src, const struct ec_curve *curve)
+{
+    ptrlen str = get_string(src);
+    if (get_err(src))
+        return NULL;
+    return ecdsa_decode(str, curve);
+}
+#define get_wpoint(src, curve) \
+    BinarySource_get_wpoint(BinarySource_UPCAST(src), curve)
+
+static void BinarySink_put_wpoint(
+    BinarySink *bs, WeierstrassPoint *point, const struct ec_curve *curve,
+    bool bare)
+{
+    strbuf *sb;
+    BinarySink *bs_inner;
+
+    if (!bare) {
+        /*
+         * Encapsulate the raw data inside an outermost string layer.
+         */
+        sb = strbuf_new();
+        bs_inner = BinarySink_UPCAST(sb);
+    } else {
+        /*
+         * Just write the data directly to the output.
+         */
+        bs_inner = bs;
+    }
+
+    if (ecc_weierstrass_is_identity(point)) {
+        put_byte(bs_inner, 0);
+    } else {
+        mp_int *x, *y;
+        ecc_weierstrass_get_affine(point, &x, &y);
+
+        /*
+         * For ECDSA, we only ever output uncompressed points.
+         */
+        put_byte(bs_inner, 0x04);
+        { // WINSCP
+        size_t i; // WINSCP
+        for (i = curve->fieldBytes; i--;)
+            put_byte(bs_inner, mp_get_byte(x, i));
+        for (i = curve->fieldBytes; i--;)
+            put_byte(bs_inner, mp_get_byte(y, i));
+        } // WINSCP
+
+        mp_free(x);
+        mp_free(y);
+    }
+
+    if (!bare)
+        put_stringsb(bs, sb);
+}
+#define put_wpoint(bs, point, curve, bare)                              \
+    BinarySink_put_wpoint(BinarySink_UPCAST(bs), point, curve, bare)
+
+static EdwardsPoint *eddsa_decode(ptrlen encoded, const struct ec_curve *curve)
+{
+    assert(curve->type == EC_EDWARDS);
+
+    { // WINSCP
+    mp_int *y = mp_from_bytes_le(encoded);
+
+    /* The topmost bit of the encoding isn't part of y, so it stores
+     * the bottom bit of x. Extract it, and zero that bit in y. */
+    unsigned desired_x_parity = mp_get_bit(y, curve->fieldBytes * 8 - 1);
+    mp_set_bit(y, curve->fieldBytes * 8 - 1, 0);
+
+    /* What's left should now be within the range of the curve's modulus */
+    if (mp_cmp_hs(y, curve->p)) {
+        mp_free(y);
+        return NULL;
+    }
+
+    { // WINSCP
+    { // WINSCP
+    EdwardsPoint *P = ecc_edwards_point_new_from_y(
+        curve->e.ec, y, desired_x_parity);
+    mp_free(y);
+
+    /* A point constructed in this way will always satisfy the curve
+     * equation, unless ecc-arithmetic.c wasn't able to construct one
+     * at all, in which case P is now NULL. Either way, return it. */
+    return P;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+static EdwardsPoint *BinarySource_get_epoint(
+    BinarySource *src, const struct ec_curve *curve)
+{
+    ptrlen str = get_string(src);
+    if (get_err(src))
+        return NULL;
+    return eddsa_decode(str, curve);
+}
+#define get_epoint(src, curve) \
+    BinarySource_get_epoint(BinarySource_UPCAST(src), curve)
+
+static void BinarySink_put_epoint(
+    BinarySink *bs, EdwardsPoint *point, const struct ec_curve *curve,
+    bool bare)
+{
+    mp_int *x, *y;
+    ecc_edwards_get_affine(point, &x, &y);
+
+    assert(curve->fieldBytes >= 2);
+
+    /*
+     * EdDSA requires point compression. We store a single integer,
+     * with bytes in little-endian order, which mostly contains y but
+     * in which the topmost bit is the low bit of x.
+     */
+    if (!bare)
+        put_uint32(bs, curve->fieldBytes);   /* string length field */
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < curve->fieldBytes - 1; i++)
+        put_byte(bs, mp_get_byte(y, i));
+    } // WINSCP
+    put_byte(bs, (mp_get_byte(y, curve->fieldBytes - 1) & 0x7F) |
+             (mp_get_bit(x, 0) << 7));
+
+    mp_free(x);
+    mp_free(y);
+}
+#define put_epoint(bs, point, curve, bare)                      \
+    BinarySink_put_epoint(BinarySink_UPCAST(bs), point, curve, bare)
+
+/* ----------------------------------------------------------------------
+ * Exposed ECDSA interface
+ */
+
+static void ecdsa_freekey(ssh_key *key)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+
+    if (ek->publicKey)
+        ecc_weierstrass_point_free(ek->publicKey);
+    if (ek->privateKey)
+        mp_free(ek->privateKey);
+    sfree(ek);
+}
+
+static void eddsa_freekey(ssh_key *key)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+
+    if (ek->publicKey)
+        ecc_edwards_point_free(ek->publicKey);
+    if (ek->privateKey)
+        mp_free(ek->privateKey);
+    sfree(ek);
+}
+
+static char *ec_signkey_invalid(ssh_key *key, unsigned flags)
+{
+    /* All validity criteria for both ECDSA and EdDSA were checked
+     * when we loaded the key in the first place */
+    return NULL;
+}
+
+static ssh_key *ecdsa_new_pub(const ssh_keyalg *alg, ptrlen data)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)alg->extra;
+    struct ec_curve *curve = extra->curve();
+    pinitassert(curve->type == EC_WEIERSTRASS);
+
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, data);
+    get_string(src);
+
+    /* Curve name is duplicated for Weierstrass form */
+    if (!ptrlen_eq_string(get_string(src), curve->name))
+        return NULL;
+
+    { // WINSCP
+    struct ecdsa_key *ek = snew(struct ecdsa_key);
+    ek->sshk.vt = alg;
+    ek->curve = curve;
+    ek->privateKey = NULL;
+
+    ek->publicKey = get_wpoint(src, curve);
+    if (!ek->publicKey) {
+        ecdsa_freekey(&ek->sshk);
+        return NULL;
+    }
+
+    return &ek->sshk;
+    } // WINSCP
+}
+
+static ssh_key *eddsa_new_pub(const ssh_keyalg *alg, ptrlen data)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)alg->extra;
+    struct ec_curve *curve = extra->curve();
+    pinitassert(curve->type == EC_EDWARDS);
+
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, data);
+    get_string(src);
+
+    { // WINSCP
+    struct eddsa_key *ek = snew(struct eddsa_key);
+    ek->sshk.vt = alg;
+    ek->curve = curve;
+    ek->privateKey = NULL;
+
+    ek->publicKey = get_epoint(src, curve);
+    if (!ek->publicKey) {
+        eddsa_freekey(&ek->sshk);
+        return NULL;
+    }
+
+    return &ek->sshk;
+    } // WINSCP
+}
+
+static char *ecc_cache_str_shared(
+    const char *curve_name, mp_int *x, mp_int *y)
+{
+    strbuf *sb = strbuf_new();
+
+    if (curve_name)
+        put_fmt(sb, "%s,", curve_name);
+
+    { // WINSCP
+    char *hx = mp_get_hex(x);
+    char *hy = mp_get_hex(y);
+    put_fmt(sb, "0x%s,0x%s", hx, hy);
+    sfree(hx);
+    sfree(hy);
+    } // WINSCP
+
+    return strbuf_to_str(sb);
+}
+
+static char *ecdsa_cache_str(ssh_key *key)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+    mp_int *x, *y;
+
+    ecc_weierstrass_get_affine(ek->publicKey, &x, &y);
+    { // WINSCP
+    char *toret = ecc_cache_str_shared(ek->curve->name, x, y);
+    mp_free(x);
+    mp_free(y);
+    return toret;
+    } // WINSCP
+}
+
+static key_components *ecdsa_components(ssh_key *key)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+    key_components *kc = key_components_new();
+
+    key_components_add_text(kc, "key_type", "ECDSA");
+    key_components_add_text(kc, "curve_name", ek->curve->textname);
+
+    { // WINSCP
+    mp_int *x, *y;
+    ecc_weierstrass_get_affine(ek->publicKey, &x, &y);
+    key_components_add_mp(kc, "public_affine_x", x);
+    key_components_add_mp(kc, "public_affine_y", y);
+    mp_free(x);
+    mp_free(y);
+
+    if (ek->privateKey)
+        key_components_add_mp(kc, "private_exponent", ek->privateKey);
+
+    return kc;
+    } // WINSCP
+}
+
+static char *eddsa_cache_str(ssh_key *key)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+    mp_int *x, *y;
+
+    ecc_edwards_get_affine(ek->publicKey, &x, &y);
+    { // WINSCP
+    char *toret = ecc_cache_str_shared(ek->curve->name, x, y);
+    mp_free(x);
+    mp_free(y);
+    return toret;
+    } // WINSCP
+}
+
+static key_components *eddsa_components(ssh_key *key)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+    key_components *kc = key_components_new();
+
+    key_components_add_text(kc, "key_type", "EdDSA");
+    key_components_add_text(kc, "curve_name", ek->curve->textname);
+
+    { // WINSCP
+    mp_int *x, *y;
+    ecc_edwards_get_affine(ek->publicKey, &x, &y);
+    key_components_add_mp(kc, "public_affine_x", x);
+    key_components_add_mp(kc, "public_affine_y", y);
+    mp_free(x);
+    mp_free(y);
+
+    if (ek->privateKey)
+        key_components_add_mp(kc, "private_exponent", ek->privateKey);
+
+    return kc;
+    } // WINSCP
+}
+
+static void ecdsa_public_blob(ssh_key *key, BinarySink *bs)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+
+    put_stringz(bs, ek->sshk.vt->ssh_id);
+    put_stringz(bs, ek->curve->name);
+    put_wpoint(bs, ek->publicKey, ek->curve, false);
+}
+
+static void eddsa_public_blob(ssh_key *key, BinarySink *bs)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+
+    put_stringz(bs, ek->sshk.vt->ssh_id);
+    put_epoint(bs, ek->publicKey, ek->curve, false);
+}
+
+static void ecdsa_private_blob(ssh_key *key, BinarySink *bs)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+
+    /* ECDSA uses ordinary SSH-2 mpint format to store the private key */
+    assert(ek->privateKey);
+    put_mp_ssh2(bs, ek->privateKey);
+}
+
+static void eddsa_private_blob(ssh_key *key, BinarySink *bs)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+
+    /* EdDSA stores the private key integer little-endian and unsigned */
+    assert(ek->privateKey);
+    put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes);
+}
+
+static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv)
+{
+    ssh_key *sshk = ecdsa_new_pub(alg, pub);
+    if (!sshk)
+        return NULL;
+    { // WINSCP
+    struct ecdsa_key *ek = container_of(sshk, struct ecdsa_key, sshk);
+
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, priv);
+    ek->privateKey = get_mp_ssh2(src);
+
+    return &ek->sshk;
+    } // WINSCP
+}
+
+static ssh_key *eddsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv)
+{
+    ssh_key *sshk = eddsa_new_pub(alg, pub);
+    if (!sshk)
+        return NULL;
+    { // WINSCP
+    struct eddsa_key *ek = container_of(sshk, struct eddsa_key, sshk);
+
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, priv);
+    ek->privateKey = get_mp_le(src);
+
+    return &ek->sshk;
+    } // WINSCP
+}
+
+static ssh_key *eddsa_new_priv_openssh(
+    const ssh_keyalg *alg, BinarySource *src)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)alg->extra;
+    struct ec_curve *curve = extra->curve();
+    assert(curve->type == EC_EDWARDS);
+
+    { // WINSCP
+    ptrlen pubkey_pl = get_string(src);
+    ptrlen privkey_extended_pl = get_string(src);
+    if (get_err(src) || pubkey_pl.len != curve->fieldBytes)
+        return NULL;
+
+    /*
+     * The OpenSSH format for ed25519 private keys also for some
+     * reason encodes an extra copy of the public key in the second
+     * half of the secret-key string. Check that that's present and
+     * correct as well, otherwise the key we think we've imported
+     * won't behave identically to the way OpenSSH would have treated
+     * it.
+     *
+     * We assume that Ed448 will work the same way, as and when
+     * OpenSSH implements it, which at the time of writing this they
+     * had not.
+     */
+    { // WINSCP
+    BinarySource subsrc[1];
+    BinarySource_BARE_INIT_PL(subsrc, privkey_extended_pl);
+    { // WINSCP
+    ptrlen privkey_pl = get_data(subsrc, curve->fieldBytes);
+    ptrlen pubkey_copy_pl = get_data(subsrc, curve->fieldBytes);
+    if (get_err(subsrc) || get_avail(subsrc))
+        return NULL;
+    if (!ptrlen_eq_ptrlen(pubkey_pl, pubkey_copy_pl))
+        return NULL;
+
+    { // WINSCP
+    struct eddsa_key *ek = snew(struct eddsa_key);
+    ek->sshk.vt = alg;
+    ek->curve = curve;
+    ek->privateKey = NULL;
+
+    ek->publicKey = eddsa_decode(pubkey_pl, curve);
+    if (!ek->publicKey) {
+        eddsa_freekey(&ek->sshk);
+        return NULL;
+    }
+
+    ek->privateKey = mp_from_bytes_le(privkey_pl);
+
+    return &ek->sshk;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+static void eddsa_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+    assert(ek->curve->type == EC_EDWARDS);
+
+    /* Encode the public and private points as strings */
+    { // WINSCP
+    strbuf *pub_sb = strbuf_new();
+    put_epoint(pub_sb, ek->publicKey, ek->curve, false);
+    { // WINSCP
+    ptrlen pub = make_ptrlen(pub_sb->s + 4, pub_sb->len - 4);
+
+    strbuf *priv_sb = strbuf_new_nm();
+    put_mp_le_fixedlen(priv_sb, ek->privateKey, ek->curve->fieldBytes);
+    { // WINSCP
+    ptrlen priv = make_ptrlen(priv_sb->s + 4, priv_sb->len - 4);
+
+    put_stringpl(bs, pub);
+
+    /* Encode the private key as the concatenation of the
+     * little-endian key integer and the public key again */
+    put_uint32(bs, priv.len + pub.len);
+    put_datapl(bs, priv);
+    put_datapl(bs, pub);
+
+    strbuf_free(pub_sb);
+    strbuf_free(priv_sb);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+static ssh_key *ecdsa_new_priv_openssh(
+    const ssh_keyalg *alg, BinarySource *src)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)alg->extra;
+    struct ec_curve *curve = extra->curve();
+    assert(curve->type == EC_WEIERSTRASS);
+
+    get_string(src);
+
+    { // WINSCP
+    struct ecdsa_key *ek = snew(struct ecdsa_key);
+    ek->sshk.vt = alg;
+    ek->curve = curve;
+    ek->privateKey = NULL;
+
+    ek->publicKey = get_wpoint(src, curve);
+    if (!ek->publicKey) {
+        ecdsa_freekey(&ek->sshk);
+        return NULL;
+    }
+
+    ek->privateKey = get_mp_ssh2(src);
+
+    return &ek->sshk;
+    } // WINSCP
+}
+
+static void ecdsa_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+    put_stringz(bs, ek->curve->name);
+    put_wpoint(bs, ek->publicKey, ek->curve, false);
+    put_mp_ssh2(bs, ek->privateKey);
+}
+
+static int ec_shared_pubkey_bits(const ssh_keyalg *alg, ptrlen blob)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)alg->extra;
+    struct ec_curve *curve = extra->curve();
+    return curve->fieldBits;
+}
+
+static mp_int *ecdsa_signing_exponent_from_data(
+    const struct ec_curve *curve, const struct ecsign_extra *extra,
+    ptrlen data)
+{
+    /* Hash the data being signed. */
+    unsigned char hash[MAX_HASH_LEN];
+    ssh_hash *h = ssh_hash_new(extra->hash);
+    put_datapl(h, data);
+    ssh_hash_final(h, hash);
+
+    /*
+     * Take the leftmost b bits of the hash of the signed data (where
+     * b is the number of bits in order(G)), interpreted big-endian.
+     */
+    { // WINSCP
+    mp_int *z = mp_from_bytes_be(make_ptrlen(hash, extra->hash->hlen));
+    size_t zbits = mp_get_nbits(z);
+    size_t nbits = mp_get_nbits(curve->w.G_order);
+    size_t shift = zbits - nbits;
+    /* Bound the shift count below at 0, using bit twiddling to avoid
+     * a conditional branch */
+    shift &= ~-(int)(shift >> (CHAR_BIT * sizeof(size_t) - 1)); // WINSCP
+    { // WINSCP
+    mp_int *toret = mp_rshift_safe(z, shift);
+    mp_free(z);
+
+    return toret;
+    } // WINSCP
+    } // WINSCP
+}
+
+static bool ecdsa_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)ek->sshk.vt->extra;
+
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, sig);
+
+    /* Check the signature starts with the algorithm name */
+    if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id))
+        return false;
+
+    /* Everything else is nested inside a sub-string. Descend into that. */
+    { // WINSCP
+    ptrlen sigstr = get_string(src);
+    if (get_err(src))
+        return false;
+    BinarySource_BARE_INIT_PL(src, sigstr);
+
+    /* Extract the signature integers r,s */
+    { // WINSCP
+    mp_int *r = get_mp_ssh2(src);
+    mp_int *s = get_mp_ssh2(src);
+    if (get_err(src)) {
+        mp_free(r);
+        mp_free(s);
+        return false;
+    }
+
+    /* Basic sanity checks: 0 < r,s < order(G) */
+    { // WINSCP
+    unsigned invalid = 0;
+    invalid |= mp_eq_integer(r, 0);
+    invalid |= mp_eq_integer(s, 0);
+    invalid |= mp_cmp_hs(r, ek->curve->w.G_order);
+    invalid |= mp_cmp_hs(s, ek->curve->w.G_order);
+
+    /* Get the hash of the signed data, converted to an integer */
+    { // WINSCP
+    mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data);
+
+    /* Verify the signature integers against the hash */
+    mp_int *w = mp_invert(s, ek->curve->w.G_order);
+    mp_int *u1 = mp_modmul(z, w, ek->curve->w.G_order);
+    mp_free(z);
+    { // WINSCP
+    mp_int *u2 = mp_modmul(r, w, ek->curve->w.G_order);
+    mp_free(w);
+    { // WINSCP
+    WeierstrassPoint *u1G = ecc_weierstrass_multiply(ek->curve->w.G, u1);
+    mp_free(u1);
+    { // WINSCP
+    WeierstrassPoint *u2P = ecc_weierstrass_multiply(ek->publicKey, u2);
+    mp_free(u2);
+    { // WINSCP
+    WeierstrassPoint *sum = ecc_weierstrass_add_general(u1G, u2P);
+    ecc_weierstrass_point_free(u1G);
+    ecc_weierstrass_point_free(u2P);
+
+    { // WINSCP
+    mp_int *x;
+    ecc_weierstrass_get_affine(sum, &x, NULL);
+    ecc_weierstrass_point_free(sum);
+
+    mp_divmod_into(x, ek->curve->w.G_order, NULL, x);
+    invalid |= (1 ^ mp_cmp_eq(r, x));
+    mp_free(x);
+
+    mp_free(r);
+    mp_free(s);
+
+    return !invalid;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+static mp_int *eddsa_signing_exponent_from_data(
+    struct eddsa_key *ek, const struct ecsign_extra *extra,
+    ptrlen r_encoded, ptrlen data)
+{
+    /* Hash (r || public key || message) */
+    unsigned char hash[MAX_HASH_LEN];
+    ssh_hash *h = ssh_hash_new(extra->hash);
+    put_datapl(h, extra->hash_prefix);
+    put_datapl(h, r_encoded);
+    put_epoint(h, ek->publicKey, ek->curve, true); /* omit string header */
+    put_datapl(h, data);
+    ssh_hash_final(h, hash);
+
+    /* Convert to an integer */
+    { // WINSCP
+    mp_int *toret = mp_from_bytes_le(make_ptrlen(hash, extra->hash->hlen));
+
+    smemclr(hash, extra->hash->hlen);
+    return toret;
+    } // WINSCP
+}
+
+static bool eddsa_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)ek->sshk.vt->extra;
+
+    BinarySource src[1];
+    BinarySource_BARE_INIT_PL(src, sig);
+
+    /* Check the signature starts with the algorithm name */
+    if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id))
+        return false;
+
+    /* Now expect a single string which is the concatenation of an
+     * encoded curve point r and an integer s. */
+    { // WINSCP
+    ptrlen sigstr = get_string(src);
+    if (get_err(src))
+        return false;
+    BinarySource_BARE_INIT_PL(src, sigstr);
+    { // WINSCP
+    ptrlen rstr = get_data(src, ek->curve->fieldBytes);
+    ptrlen sstr = get_data(src, ek->curve->fieldBytes);
+    if (get_err(src) || get_avail(src))
+        return false;
+
+    { // WINSCP
+    EdwardsPoint *r = eddsa_decode(rstr, ek->curve);
+    if (!r)
+        return false;
+    { // WINSCP
+    mp_int *s = mp_from_bytes_le(sstr);
+
+    mp_int *H = eddsa_signing_exponent_from_data(ek, extra, rstr, data);
+
+    /* Verify that s*G == r + H*publicKey */
+    EdwardsPoint *lhs = ecc_edwards_multiply(ek->curve->e.G, s);
+    mp_free(s);
+    { // WINSCP
+    EdwardsPoint *hpk = ecc_edwards_multiply(ek->publicKey, H);
+    mp_free(H);
+    { // WINSCP
+    EdwardsPoint *rhs = ecc_edwards_add(r, hpk);
+    ecc_edwards_point_free(hpk);
+    { // WINSCP
+    unsigned valid = ecc_edwards_eq(lhs, rhs);
+    ecc_edwards_point_free(lhs);
+    ecc_edwards_point_free(rhs);
+    ecc_edwards_point_free(r);
+
+    return valid;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+static void ecdsa_sign(ssh_key *key, ptrlen data,
+                       unsigned flags, BinarySink *bs)
+{
+    struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)ek->sshk.vt->extra;
+    assert(ek->privateKey);
+
+    { // WINSCP
+    mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data);
+
+    /* Generate k between 1 and curve->n, using the same deterministic
+     * k generation system we use for conventional DSA. */
+    mp_int *k;
+    {
+        unsigned char digest[20];
+        hash_simple(&ssh_sha1, data, digest);
+        k = dsa_gen_k(
+            "ECDSA deterministic k generator", ek->curve->w.G_order,
+            ek->privateKey, digest, sizeof(digest));
+    }
+
+    { // WINSCP
+    WeierstrassPoint *kG = ecc_weierstrass_multiply(ek->curve->w.G, k);
+    mp_int *x;
+    ecc_weierstrass_get_affine(kG, &x, NULL);
+    ecc_weierstrass_point_free(kG);
+
+    /* r = kG.x mod order(G) */
+    { // WINSCP
+    mp_int *r = mp_mod(x, ek->curve->w.G_order);
+    mp_free(x);
+
+    /* s = (z + r * priv)/k mod n */
+    { // WINSCP
+    mp_int *rPriv = mp_modmul(r, ek->privateKey, ek->curve->w.G_order);
+    mp_int *numerator = mp_modadd(z, rPriv, ek->curve->w.G_order);
+    mp_free(z);
+    mp_free(rPriv);
+    { // WINSCP
+    mp_int *kInv = mp_invert(k, ek->curve->w.G_order);
+    mp_free(k);
+    { // WINSCP
+    mp_int *s = mp_modmul(numerator, kInv, ek->curve->w.G_order);
+    mp_free(numerator);
+    mp_free(kInv);
+
+    /* Format the output */
+    put_stringz(bs, ek->sshk.vt->ssh_id);
+
+    { // WINSCP
+    strbuf *substr = strbuf_new();
+    put_mp_ssh2(substr, r);
+    put_mp_ssh2(substr, s);
+    put_stringsb(bs, substr);
+    } // WINSCP
+
+    mp_free(r);
+    mp_free(s);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+static void eddsa_sign(ssh_key *key, ptrlen data,
+                       unsigned flags, BinarySink *bs)
+{
+    struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)ek->sshk.vt->extra;
+    assert(ek->privateKey);
+
+    /*
+     * EdDSA prescribes a specific method of generating the random
+     * nonce integer for the signature. (A verifier can't tell
+     * whether you followed that method, but it's important to
+     * follow it anyway, because test vectors will want a specific
+     * signature for a given message, and because this preserves
+     * determinism of signatures even if the same signature were
+     * made twice by different software.)
+     */
+
+    /*
+     * First, we hash the private key integer (bare, little-endian)
+     * into a hash generating 2*fieldBytes of output.
+     */
+    { // WINSCP
+    unsigned char hash[MAX_HASH_LEN];
+    ssh_hash *h = ssh_hash_new(extra->hash);
+    size_t i; // WINSCP
+    for (i = 0; i < ek->curve->fieldBytes; ++i)
+        put_byte(h, mp_get_byte(ek->privateKey, i));
+    ssh_hash_final(h, hash);
+
+    /*
+     * The first half of the output hash is converted into an
+     * integer a, by the standard EdDSA transformation.
+     */
+    { // WINSCP
+    mp_int *a = eddsa_exponent_from_hash(
+        make_ptrlen(hash, ek->curve->fieldBytes), ek->curve);
+
+    /*
+     * The second half of the hash of the private key is hashed again
+     * with the message to be signed, and used as an exponent to
+     * generate the signature point r.
+     */
+    h = ssh_hash_new(extra->hash);
+    put_datapl(h, extra->hash_prefix);
+    put_data(h, hash + ek->curve->fieldBytes,
+             extra->hash->hlen - ek->curve->fieldBytes);
+    put_datapl(h, data);
+    ssh_hash_final(h, hash);
+    { // WINSCP
+    mp_int *log_r_unreduced = mp_from_bytes_le(
+        make_ptrlen(hash, extra->hash->hlen));
+    mp_int *log_r = mp_mod(log_r_unreduced, ek->curve->e.G_order);
+    mp_free(log_r_unreduced);
+    { // WINSCP
+    EdwardsPoint *r = ecc_edwards_multiply(ek->curve->e.G, log_r);
+
+    /*
+     * Encode r now, because we'll need its encoding for the next
+     * hashing step as well as to write into the actual signature.
+     */
+    strbuf *r_enc = strbuf_new();
+    put_epoint(r_enc, r, ek->curve, true); /* omit string header */
+    ecc_edwards_point_free(r);
+
+    /*
+     * Compute the hash of (r || public key || message) just as
+     * eddsa_verify does.
+     */
+    { // WINSCP
+    mp_int *H = eddsa_signing_exponent_from_data(
+        ek, extra, ptrlen_from_strbuf(r_enc), data);
+
+    /* And then s = (log(r) + H*a) mod order(G). */
+    mp_int *Ha = mp_modmul(H, a, ek->curve->e.G_order);
+    mp_int *s = mp_modadd(log_r, Ha, ek->curve->e.G_order);
+    mp_free(H);
+    mp_free(a);
+    mp_free(Ha);
+    mp_free(log_r);
+
+    /* Format the output */
+    put_stringz(bs, ek->sshk.vt->ssh_id);
+    put_uint32(bs, r_enc->len + ek->curve->fieldBytes);
+    put_data(bs, r_enc->u, r_enc->len);
+    strbuf_free(r_enc);
+    { // WINSCP
+    size_t i;
+    for (i = 0; i < ek->curve->fieldBytes; ++i)
+        put_byte(bs, mp_get_byte(s, i));
+    mp_free(s);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+static const struct ecsign_extra sign_extra_ed25519 = {
+    ec_ed25519, &ssh_sha512,
+    NULL, 0, PTRLEN_DECL_LITERAL(""),
+};
+const ssh_keyalg ssh_ecdsa_ed25519 = {
+    // WINSCP
+    /*.new_pub =*/ eddsa_new_pub,
+    /*.new_priv =*/ eddsa_new_priv,
+    /*.new_priv_openssh =*/ eddsa_new_priv_openssh,
+    /*.freekey =*/ eddsa_freekey,
+    /*.invalid =*/ ec_signkey_invalid,
+    /*.sign =*/ eddsa_sign,
+    /*.verify =*/ eddsa_verify,
+    /*.public_blob =*/ eddsa_public_blob,
+    /*.private_blob =*/ eddsa_private_blob,
+    /*.openssh_blob =*/ eddsa_openssh_blob,
+    /*.cache_str =*/ eddsa_cache_str,
+    /*.components =*/ eddsa_components,
+    /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    /*.ssh_id =*/ "ssh-ed25519",
+    /*.cache_id =*/ "ssh-ed25519",
+    /*.extra =*/ &sign_extra_ed25519,
+    0, // WINSCP
+};
+
+static const struct ecsign_extra sign_extra_ed448 = {
+    ec_ed448, &ssh_shake256_114bytes,
+    NULL, 0, PTRLEN_DECL_LITERAL("SigEd448\0\0"),
+};
+const ssh_keyalg ssh_ecdsa_ed448 = {
+    // WINSCP
+    /*.new_pub =*/ eddsa_new_pub,
+    /*.new_priv =*/ eddsa_new_priv,
+    /*.new_priv_openssh =*/ eddsa_new_priv_openssh,
+    /*.freekey =*/ eddsa_freekey,
+    /*.invalid =*/ ec_signkey_invalid,
+    /*.sign =*/ eddsa_sign,
+    /*.verify =*/ eddsa_verify,
+    /*.public_blob =*/ eddsa_public_blob,
+    /*.private_blob =*/ eddsa_private_blob,
+    /*.openssh_blob =*/ eddsa_openssh_blob,
+    /*.cache_str =*/ eddsa_cache_str,
+    /*.components =*/ eddsa_components,
+    /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    /*.ssh_id =*/ "ssh-ed448",
+    /*.cache_id =*/ "ssh-ed448",
+    /*.extra =*/ &sign_extra_ed448,
+    0, // WINSCP
+};
+
+/* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */
+static const unsigned char nistp256_oid[] = {
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
+};
+static const struct ecsign_extra sign_extra_nistp256 = {
+    ec_p256, &ssh_sha256,
+    nistp256_oid, lenof(nistp256_oid),
+};
+const ssh_keyalg ssh_ecdsa_nistp256 = {
+    // WINSCP
+    /*.new_pub =*/ ecdsa_new_pub,
+    /*.new_priv =*/ ecdsa_new_priv,
+    /*.new_priv_openssh =*/ ecdsa_new_priv_openssh,
+    /*.freekey =*/ ecdsa_freekey,
+    /*.invalid =*/ ec_signkey_invalid,
+    /*.sign =*/ ecdsa_sign,
+    /*.verify =*/ ecdsa_verify,
+    /*.public_blob =*/ ecdsa_public_blob,
+    /*.private_blob =*/ ecdsa_private_blob,
+    /*.openssh_blob =*/ ecdsa_openssh_blob,
+    /*.cache_str =*/ ecdsa_cache_str,
+    /*.components =*/ ecdsa_components,
+    /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    /*.ssh_id =*/ "ecdsa-sha2-nistp256",
+    /*.cache_id =*/ "ecdsa-sha2-nistp256",
+    /*.extra =*/ &sign_extra_nistp256,
+    0, // WINSCP
+};
+
+/* OID: 1.3.132.0.34 (secp384r1) */
+static const unsigned char nistp384_oid[] = {
+    0x2b, 0x81, 0x04, 0x00, 0x22
+};
+static const struct ecsign_extra sign_extra_nistp384 = {
+    ec_p384, &ssh_sha384,
+    nistp384_oid, lenof(nistp384_oid),
+};
+const ssh_keyalg ssh_ecdsa_nistp384 = {
+    // WINSCP
+    /*.new_pub =*/ ecdsa_new_pub,
+    /*.new_priv =*/ ecdsa_new_priv,
+    /*.new_priv_openssh =*/ ecdsa_new_priv_openssh,
+    /*.freekey =*/ ecdsa_freekey,
+    /*.invalid =*/ ec_signkey_invalid,
+    /*.sign =*/ ecdsa_sign,
+    /*.verify =*/ ecdsa_verify,
+    /*.public_blob =*/ ecdsa_public_blob,
+    /*.private_blob =*/ ecdsa_private_blob,
+    /*.openssh_blob =*/ ecdsa_openssh_blob,
+    /*.cache_str =*/ ecdsa_cache_str,
+    /*.components =*/ ecdsa_components,
+    /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    /*.ssh_id =*/ "ecdsa-sha2-nistp384",
+    /*.cache_id =*/ "ecdsa-sha2-nistp384",
+    /*.extra =*/ &sign_extra_nistp384,
+    0, // WINSCP
+};
+
+/* OID: 1.3.132.0.35 (secp521r1) */
+static const unsigned char nistp521_oid[] = {
+    0x2b, 0x81, 0x04, 0x00, 0x23
+};
+static const struct ecsign_extra sign_extra_nistp521 = {
+    ec_p521, &ssh_sha512,
+    nistp521_oid, lenof(nistp521_oid),
+};
+const ssh_keyalg ssh_ecdsa_nistp521 = {
+    // WINSCP
+    /*.new_pub =*/ ecdsa_new_pub,
+    /*.new_priv =*/ ecdsa_new_priv,
+    /*.new_priv_openssh =*/ ecdsa_new_priv_openssh,
+    /*.freekey =*/ ecdsa_freekey,
+    /*.invalid =*/ ec_signkey_invalid,
+    /*.sign =*/ ecdsa_sign,
+    /*.verify =*/ ecdsa_verify,
+    /*.public_blob =*/ ecdsa_public_blob,
+    /*.private_blob =*/ ecdsa_private_blob,
+    /*.openssh_blob =*/ ecdsa_openssh_blob,
+    /*.cache_str =*/ ecdsa_cache_str,
+    /*.components =*/ ecdsa_components,
+    /*.pubkey_bits =*/ ec_shared_pubkey_bits,
+    /*.ssh_id =*/ "ecdsa-sha2-nistp521",
+    /*.cache_id =*/ "ecdsa-sha2-nistp521",
+    /*.extra =*/ &sign_extra_nistp521,
+    0, // WINSCP
+};
+
+/* ----------------------------------------------------------------------
+ * Exposed ECDH interface
+ */
+
+struct eckex_extra {
+    struct ec_curve *(*curve)(void);
+    void (*setup)(ecdh_key *dh);
+    void (*cleanup)(ecdh_key *dh);
+    void (*getpublic)(ecdh_key *dh, BinarySink *bs);
+    mp_int *(*getkey)(ecdh_key *dh, ptrlen remoteKey);
+};
+
+struct ecdh_key {
+    const struct eckex_extra *extra;
+    const struct ec_curve *curve;
+    mp_int *private;
+    union {
+        WeierstrassPoint *w_public;
+        MontgomeryPoint *m_public;
+    };
+};
+
+const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex)
+{
+    const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+    struct ec_curve *curve = extra->curve();
+    return curve->textname;
+}
+
+static void ssh_ecdhkex_w_setup(ecdh_key *dh)
+{
+    mp_int *one = mp_from_integer(1);
+    dh->private = mp_random_in_range(one, dh->curve->w.G_order);
+    mp_free(one);
+
+    dh->w_public = ecc_weierstrass_multiply(dh->curve->w.G, dh->private);
+}
+
+static void ssh_ecdhkex_m_setup(ecdh_key *dh)
+{
+    strbuf *bytes = strbuf_new_nm();
+    random_read(strbuf_append(bytes, dh->curve->fieldBytes),
+                dh->curve->fieldBytes);
+
+    dh->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes));
+
+    /* Ensure the private key has the highest valid bit set, and no
+     * bits _above_ the highest valid one */
+    mp_reduce_mod_2to(dh->private, dh->curve->fieldBits);
+    mp_set_bit(dh->private, dh->curve->fieldBits - 1, 1);
+
+    /* Clear a curve-specific number of low bits */
+    { // WINSCP
+    unsigned bit;
+    for (bit = 0; bit < dh->curve->m.log2_cofactor; bit++)
+        mp_set_bit(dh->private, bit, 0);
+    } // WINSCP
+
+    strbuf_free(bytes);
+
+    dh->m_public = ecc_montgomery_multiply(dh->curve->m.G, dh->private);
+}
+
+ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex)
+{
+    const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+    const struct ec_curve *curve = extra->curve();
+
+    ecdh_key *dh = snew(ecdh_key);
+    dh->extra = extra;
+    dh->curve = curve;
+    dh->extra->setup(dh);
+    return dh;
+}
+
+static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs)
+{
+    put_wpoint(bs, dh->w_public, dh->curve, true);
+}
+
+static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs)
+{
+    mp_int *x;
+    size_t i; // WINSCP
+    ecc_montgomery_get_affine(dh->m_public, &x);
+    for (i = 0; i < dh->curve->fieldBytes; ++i)
+        put_byte(bs, mp_get_byte(x, i));
+    mp_free(x);
+}
+
+void ssh_ecdhkex_getpublic(ecdh_key *dh, BinarySink *bs)
+{
+    dh->extra->getpublic(dh, bs);
+}
+
+static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey)
+{
+    WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dh->curve);
+    if (!remote_p)
+        return NULL;
+
+    if (ecc_weierstrass_is_identity(remote_p)) {
+        /* Not a sensible Diffie-Hellman input value */
+        ecc_weierstrass_point_free(remote_p);
+        return NULL;
+    }
+
+    { // WINSCP
+    WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private);
+
+    mp_int *x;
+    ecc_weierstrass_get_affine(p, &x, NULL);
+
+    ecc_weierstrass_point_free(remote_p);
+    ecc_weierstrass_point_free(p);
+
+    return x;
+    } // WINSCP
+}
+
+static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey)
+{
+    mp_int *remote_x = mp_from_bytes_le(remoteKey);
+
+    /* Per RFC 7748 section 5, discard any set bits of the other
+     * side's public value beyond the minimum number of bits required
+     * to represent all valid values. However, an overlarge value that
+     * still fits into the remaining number of bits is accepted, and
+     * will be reduced mod p. */
+    mp_reduce_mod_2to(remote_x, dh->curve->fieldBits);
+
+    { // WINSCP
+    MontgomeryPoint *remote_p = ecc_montgomery_point_new(
+        dh->curve->m.mc, remote_x);
+    mp_free(remote_x);
+
+    { // WINSCP
+    MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dh->private);
+
+    if (ecc_montgomery_is_identity(p)) {
+        ecc_montgomery_point_free(remote_p);
+        ecc_montgomery_point_free(p);
+        return NULL;
+    }
+
+    { // WINSCP
+    mp_int *x;
+    ecc_montgomery_get_affine(p, &x);
+
+    ecc_montgomery_point_free(remote_p);
+    ecc_montgomery_point_free(p);
+
+    /*
+     * Endianness-swap. The Curve25519 algorithm definition assumes
+     * you were doing your computation in arrays of 32 little-endian
+     * bytes, and now specifies that you take your final one of those
+     * and convert it into a bignum in _network_ byte order, i.e.
+     * big-endian.
+     *
+     * In particular, the spec says, you convert the _whole_ 32 bytes
+     * into a bignum. That is, on the rare occasions that x has come
+     * out with the most significant 8 bits zero, we have to imagine
+     * that being represented by a 32-byte string with the last byte
+     * being zero, so that has to be converted into an SSH-2 bignum
+     * with the _low_ byte zero, i.e. a multiple of 256.
+     */
+    { // WINSCP
+    strbuf *sb = strbuf_new();
+    size_t i;
+    for (i = 0; i < dh->curve->fieldBytes; ++i)
+        put_byte(sb, mp_get_byte(x, i));
+    mp_free(x);
+    x = mp_from_bytes_be(ptrlen_from_strbuf(sb));
+    strbuf_free(sb);
+
+    return x;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+mp_int *ssh_ecdhkex_getkey(ecdh_key *dh, ptrlen remoteKey)
+{
+    return dh->extra->getkey(dh, remoteKey);
+}
+
+static void ssh_ecdhkex_w_cleanup(ecdh_key *dh)
+{
+    ecc_weierstrass_point_free(dh->w_public);
+}
+
+static void ssh_ecdhkex_m_cleanup(ecdh_key *dh)
+{
+    ecc_montgomery_point_free(dh->m_public);
+}
+
+void ssh_ecdhkex_freekey(ecdh_key *dh)
+{
+    mp_free(dh->private);
+    dh->extra->cleanup(dh);
+    sfree(dh);
+}
+
+static const struct eckex_extra kex_extra_curve25519 = {
+    ec_curve25519,
+    ssh_ecdhkex_m_setup,
+    ssh_ecdhkex_m_cleanup,
+    ssh_ecdhkex_m_getpublic,
+    ssh_ecdhkex_m_getkey,
+};
+const ssh_kex ssh_ec_kex_curve25519 = {
+    "curve25519-sha256", NULL, KEXTYPE_ECDH,
+    &ssh_sha256, &kex_extra_curve25519,
+};
+/* Pre-RFC alias */
+const ssh_kex ssh_ec_kex_curve25519_libssh = {
+    "[email protected]", NULL, KEXTYPE_ECDH,
+    &ssh_sha256, &kex_extra_curve25519,
+};
+
+static const struct eckex_extra kex_extra_curve448 = {
+    ec_curve448,
+    ssh_ecdhkex_m_setup,
+    ssh_ecdhkex_m_cleanup,
+    ssh_ecdhkex_m_getpublic,
+    ssh_ecdhkex_m_getkey,
+};
+const ssh_kex ssh_ec_kex_curve448 = {
+    "curve448-sha512", NULL, KEXTYPE_ECDH,
+    &ssh_sha512, &kex_extra_curve448,
+};
+
+static const struct eckex_extra kex_extra_nistp256 = {
+    ec_p256,
+    ssh_ecdhkex_w_setup,
+    ssh_ecdhkex_w_cleanup,
+    ssh_ecdhkex_w_getpublic,
+    ssh_ecdhkex_w_getkey,
+};
+const ssh_kex ssh_ec_kex_nistp256 = {
+    "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH,
+    &ssh_sha256, &kex_extra_nistp256,
+};
+
+static const struct eckex_extra kex_extra_nistp384 = {
+    ec_p384,
+    ssh_ecdhkex_w_setup,
+    ssh_ecdhkex_w_cleanup,
+    ssh_ecdhkex_w_getpublic,
+    ssh_ecdhkex_w_getkey,
+};
+const ssh_kex ssh_ec_kex_nistp384 = {
+    "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH,
+    &ssh_sha384, &kex_extra_nistp384,
+};
+
+static const struct eckex_extra kex_extra_nistp521 = {
+    ec_p521,
+    ssh_ecdhkex_w_setup,
+    ssh_ecdhkex_w_cleanup,
+    ssh_ecdhkex_w_getpublic,
+    ssh_ecdhkex_w_getkey,
+};
+const ssh_kex ssh_ec_kex_nistp521 = {
+    "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH,
+    &ssh_sha512, &kex_extra_nistp521,
+};
+
+static const ssh_kex *const ec_kex_list[] = {
+    &ssh_ec_kex_curve448,
+    &ssh_ec_kex_curve25519,
+    &ssh_ec_kex_curve25519_libssh,
+    &ssh_ec_kex_nistp256,
+    &ssh_ec_kex_nistp384,
+    &ssh_ec_kex_nistp521,
+};
+
+const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list };
+
+/* ----------------------------------------------------------------------
+ * Helper functions for finding key algorithms and returning auxiliary
+ * data.
+ */
+
+const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
+                                        const struct ec_curve **curve)
+{
+    static const ssh_keyalg *algs_with_oid[] = {
+        &ssh_ecdsa_nistp256,
+        &ssh_ecdsa_nistp384,
+        &ssh_ecdsa_nistp521,
+    };
+    int i;
+
+    for (i = 0; i < lenof(algs_with_oid); i++) {
+        const ssh_keyalg *alg = algs_with_oid[i];
+        const struct ecsign_extra *extra =
+            (const struct ecsign_extra *)alg->extra;
+        if (len == extra->oidlen && !memcmp(oid, extra->oid, len)) {
+            *curve = extra->curve();
+            return alg;
+        }
+    }
+    return NULL;
+}
+
+const unsigned char *ec_alg_oid(const ssh_keyalg *alg,
+                                int *oidlen)
+{
+    const struct ecsign_extra *extra = (const struct ecsign_extra *)alg->extra;
+    *oidlen = extra->oidlen;
+    return extra->oid;
+}
+
+const int ec_nist_curve_lengths[] = { 256, 384, 521 };
+const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths);
+
+const int ec_ed_curve_lengths[] = { 255, 448 };
+const int n_ec_ed_curve_lengths = lenof(ec_ed_curve_lengths);
+
+bool ec_nist_alg_and_curve_by_bits(
+    int bits, const struct ec_curve **curve, const ssh_keyalg **alg)
+{
+    switch (bits) {
+      case 256: *alg = &ssh_ecdsa_nistp256; break;
+      case 384: *alg = &ssh_ecdsa_nistp384; break;
+      case 521: *alg = &ssh_ecdsa_nistp521; break;
+      default: return false;
+    }
+    *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();
+    return true;
+}
+
+bool ec_ed_alg_and_curve_by_bits(
+    int bits, const struct ec_curve **curve, const ssh_keyalg **alg)
+{
+    switch (bits) {
+      case 255: case 256: *alg = &ssh_ecdsa_ed25519; break;
+      case 448: *alg = &ssh_ecdsa_ed448; break;
+      default: return false;
+    }
+    *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();
+    return true;
+}
+
+#ifdef MPEXT
+
+void ec_cleanup(void)
+{
+  ec_curve_cleanup = 1;
+  ec_p256();
+  ec_p384();
+  ec_p521();
+  ec_curve25519();
+  ec_ed25519();
+  // in case we want to restart (unlikely)
+  ec_curve_cleanup = 0;
+}
+
+#endif

+ 243 - 0
source/putty/crypto/ecc.h

@@ -0,0 +1,243 @@
+#ifndef PUTTY_ECC_H
+#define PUTTY_ECC_H
+
+/*
+ * Arithmetic functions for the various kinds of elliptic curves used
+ * by PuTTY's public-key cryptography.
+ *
+ * All of these elliptic curves are over the finite field whose order
+ * is a large prime p. (Elliptic curves over a field of order 2^n are
+ * also known, but PuTTY currently has no need of them.)
+ */
+
+/* ----------------------------------------------------------------------
+ * Weierstrass curves (or rather, 'short form' Weierstrass curves).
+ *
+ * A curve in this form is defined by two parameters a,b, and the
+ * non-identity points on the curve are represented by (x,y) (the
+ * 'affine coordinates') such that y^2 = x^3 + ax + b.
+ *
+ * The identity element of the curve's group is an additional 'point
+ * at infinity', which is considered to be the third point on the
+ * intersection of the curve with any vertical line. Hence, the
+ * inverse of the point (x,y) is (x,-y).
+ */
+
+/*
+ * Create and destroy Weierstrass curve data structures. The mandatory
+ * parameters to the constructor are the prime modulus p, and the
+ * curve parameters a,b.
+ *
+ * 'nonsquare_mod_p' is an optional extra parameter, only needed by
+ * ecc_edwards_point_new_from_y which has to take a modular square
+ * root. You can pass it as NULL if you don't need that function.
+ */
+WeierstrassCurve *ecc_weierstrass_curve(
+    mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p);
+void ecc_weierstrass_curve_free(WeierstrassCurve *);
+
+/*
+ * Create points on a Weierstrass curve, given the curve.
+ *
+ * point_new_identity returns the special identity point.
+ * point_new(x,y) returns the non-identity point with the given affine
+ * coordinates.
+ *
+ * point_new_from_x constructs a non-identity point given only the
+ * x-coordinate, by using the curve equation to work out what y has to
+ * be. Of course the equation only tells you y^2, so it only
+ * determines y up to sign; the parameter desired_y_parity controls
+ * which of the two values of y you get, by saying whether you'd like
+ * its minimal non-negative residue mod p to be even or odd. (Of
+ * course, since p itself is odd, exactly one of y and p-y is odd.)
+ * This function has to take a modular square root, so it will only
+ * work if you passed in a non-square mod p when constructing the
+ * curve.
+ */
+WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *curve);
+WeierstrassPoint *ecc_weierstrass_point_new(
+    WeierstrassCurve *curve, mp_int *x, mp_int *y);
+WeierstrassPoint *ecc_weierstrass_point_new_from_x(
+    WeierstrassCurve *curve, mp_int *x, unsigned desired_y_parity);
+
+/* Memory management: copy and free points. */
+void ecc_weierstrass_point_copy_into(
+    WeierstrassPoint *dest, WeierstrassPoint *src);
+WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *orig);
+void ecc_weierstrass_point_free(WeierstrassPoint *point);
+
+/* Check whether a point is actually on the curve. */
+unsigned ecc_weierstrass_point_valid(WeierstrassPoint *);
+
+/*
+ * Add two points and return their sum. This function is fully
+ * general: it should do the right thing if the two inputs are the
+ * same, or if either (or both) of the input points is the identity,
+ * or if the two input points are inverses so the output is the
+ * identity. However, it pays for that generality by being slower than
+ * the special-purpose functions below..
+ */
+WeierstrassPoint *ecc_weierstrass_add_general(
+    WeierstrassPoint *, WeierstrassPoint *);
+
+/*
+ * Fast but less general arithmetic functions: add two points on the
+ * condition that they are not equal and neither is the identity, and
+ * add a point to itself.
+ */
+WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *, WeierstrassPoint *);
+WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *);
+
+/*
+ * Compute an integer multiple of a point. Not guaranteed to work
+ * unless the integer argument is less than the order of the point in
+ * the group (because it won't cope if an identity element shows up in
+ * any intermediate product).
+ */
+WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *, mp_int *);
+
+/*
+ * Query functions to get the value of a point back out. is_identity
+ * tells you whether the point is the identity; if it isn't, then
+ * get_affine will retrieve one or both of its affine coordinates.
+ * (You can pass NULL as either output pointer, if you don't need that
+ * coordinate as output.)
+ */
+unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp);
+void ecc_weierstrass_get_affine(WeierstrassPoint *wp, mp_int **x, mp_int **y);
+
+/* ----------------------------------------------------------------------
+ * Montgomery curves.
+ *
+ * A curve in this form is defined by two parameters a,b, and the
+ * curve equation is by^2 = x^3 + ax^2 + x.
+ *
+ * As with Weierstrass curves, there's an additional point at infinity
+ * that is the identity element, and the inverse of (x,y) is (x,-y).
+ *
+ * However, we don't actually work with full (x,y) pairs. We just
+ * store the x-coordinate (so what we're really representing is not a
+ * specific point on the curve but a two-point set {P,-P}). This means
+ * you can't quite do point addition, because if you're given {P,-P}
+ * and {Q,-Q} as input, you can work out a pair of x-coordinates that
+ * are those of P-Q and P+Q, but you don't know which is which.
+ *
+ * Instead, the basic operation is 'differential addition', in which
+ * you are given three parameters P, Q and P-Q and you return P+Q. (As
+ * well as disambiguating which of the possible answers you want, that
+ * extra input also enables a fast formulae for computing it. This
+ * fast formula is more or less why Montgomery curves are useful in
+ * the first place.)
+ *
+ * Doubling a point is still possible to do unambiguously, so you can
+ * still compute an integer multiple of P if you start by making 2P
+ * and then doing a series of differential additions.
+ */
+
+/*
+ * Create and destroy Montgomery curve data structures.
+ */
+MontgomeryCurve *ecc_montgomery_curve(mp_int *p, mp_int *a, mp_int *b);
+void ecc_montgomery_curve_free(MontgomeryCurve *);
+
+/*
+ * Create, copy and free points on the curve. We don't need to
+ * explicitly represent the identity for this application.
+ */
+MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x);
+void ecc_montgomery_point_copy_into(
+    MontgomeryPoint *dest, MontgomeryPoint *src);
+MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig);
+void ecc_montgomery_point_free(MontgomeryPoint *mp);
+
+/*
+ * Basic arithmetic routines: differential addition and point-
+ * doubling. Each of these assumes that no special cases come up - no
+ * input or output point should be the identity, and in diff_add, P
+ * and Q shouldn't be the same.
+ */
+MontgomeryPoint *ecc_montgomery_diff_add(
+    MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ);
+MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P);
+
+/*
+ * Compute an integer multiple of a point.
+ */
+MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *, mp_int *);
+
+/*
+ * Return the affine x-coordinate of a point.
+ */
+void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x);
+
+/*
+ * Test whether a point is the curve identity.
+ */
+unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp);
+
+/* ----------------------------------------------------------------------
+ * Twisted Edwards curves.
+ *
+ * A curve in this form is defined by two parameters d,a, and the
+ * curve equation is a x^2 + y^2 = 1 + d x^2 y^2.
+ *
+ * Apparently if you ask a proper algebraic geometer they'll tell you
+ * that this is technically not an actual elliptic curve. Certainly it
+ * doesn't work quite the same way as the other kinds: in this form,
+ * there is no need for a point at infinity, because the identity
+ * element is represented by the affine coordinates (0,1). And you
+ * invert a point by negating its x rather than y coordinate: the
+ * inverse of (x,y) is (-x,y).
+ *
+ * The usefulness of this representation is that the addition formula
+ * is 'strongly unified', meaning that the same formula works for any
+ * input and output points, without needing special cases for the
+ * identity or for doubling.
+ */
+
+/*
+ * Create and destroy Edwards curve data structures.
+ *
+ * Similarly to ecc_weierstrass_curve, you don't have to provide
+ * nonsquare_mod_p if you don't need ecc_edwards_point_new_from_y.
+ */
+EdwardsCurve *ecc_edwards_curve(
+    mp_int *p, mp_int *d, mp_int *a, mp_int *nonsquare_mod_p);
+void ecc_edwards_curve_free(EdwardsCurve *);
+
+/*
+ * Create points.
+ *
+ * There's no need to have a separate function to create the identity
+ * point, because you can just pass x=0 and y=1 to the usual function.
+ *
+ * Similarly to the Weierstrass curve, ecc_edwards_point_new_from_y
+ * creates a point given only its y-coordinate and the desired parity
+ * of its x-coordinate, and you can only call it if you provided the
+ * optional nonsquare_mod_p argument when creating the curve.
+ */
+EdwardsPoint *ecc_edwards_point_new(
+    EdwardsCurve *curve, mp_int *x, mp_int *y);
+EdwardsPoint *ecc_edwards_point_new_from_y(
+    EdwardsCurve *curve, mp_int *y, unsigned desired_x_parity);
+
+/* Copy and free points. */
+void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src);
+EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig);
+void ecc_edwards_point_free(EdwardsPoint *point);
+
+/*
+ * Arithmetic: add two points, and calculate an integer multiple of a
+ * point.
+ */
+EdwardsPoint *ecc_edwards_add(EdwardsPoint *, EdwardsPoint *);
+EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *, mp_int *);
+
+/*
+ * Query functions: compare two points for equality, and return the
+ * affine coordinates of a point.
+ */
+unsigned ecc_edwards_eq(EdwardsPoint *, EdwardsPoint *);
+void ecc_edwards_get_affine(EdwardsPoint *wp, mp_int **x, mp_int **y);
+
+#endif /* PUTTY_ECC_H */

+ 13 - 0
source/putty/crypto/hash_simple.c

@@ -0,0 +1,13 @@
+/*
+ * Convenience function to hash a single piece of data, wrapping up
+ * the faff of making and freeing an ssh_hash.
+ */
+
+#include "ssh.h"
+
+void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output)
+{
+    ssh_hash *hash = ssh_hash_new(alg);
+    put_datapl(hash, data);
+    ssh_hash_final(hash, output);
+}

+ 272 - 0
source/putty/crypto/hmac.c

@@ -0,0 +1,272 @@
+/*
+ * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that
+ * can wrap any underlying hash function.
+ */
+
+#include "ssh.h"
+
+struct hmac {
+    const ssh_hashalg *hashalg;
+    ssh_hash *h_outer, *h_inner, *h_live;
+    uint8_t *digest;
+    strbuf *text_name;
+    ssh2_mac mac;
+};
+
+struct hmac_extra {
+    const ssh_hashalg *hashalg_base;
+    const char *suffix, *annotation;
+};
+
+static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
+{
+    struct hmac *ctx = snew(struct hmac);
+    const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra;
+
+    ctx->h_outer = ssh_hash_new(extra->hashalg_base);
+    /* In case that hashalg was a selector vtable, we'll now switch to
+     * using whatever real one it selected, for all future purposes. */
+    ctx->hashalg = ssh_hash_alg(ctx->h_outer);
+    ctx->h_inner = ssh_hash_new(ctx->hashalg);
+    ctx->h_live = ssh_hash_new(ctx->hashalg);
+
+    /*
+     * HMAC is not well defined as a wrapper on an absolutely general
+     * hash function; it expects that the function it's wrapping will
+     * consume data in fixed-size blocks, and it's partially defined
+     * in terms of that block size. So we insist that the hash we're
+     * given must have defined a meaningful block size.
+     */
+    assert(ctx->hashalg->blocklen);
+
+    ctx->digest = snewn(ctx->hashalg->hlen, uint8_t);
+
+    ctx->text_name = strbuf_new();
+    put_fmt(ctx->text_name, "HMAC-%s%s",
+            ctx->hashalg->text_basename, extra->suffix);
+    if (extra->annotation || ctx->hashalg->annotation) {
+        put_fmt(ctx->text_name, " (");
+        { // WINSCP
+        const char *sep = "";
+        if (extra->annotation) {
+            put_fmt(ctx->text_name, "%s%s", sep, extra->annotation);
+            sep = ", ";
+        }
+        if (ctx->hashalg->annotation) {
+            put_fmt(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation);
+            sep = ", ";
+        }
+        put_fmt(ctx->text_name, ")");
+        } // WINSCP
+    }
+
+    ctx->mac.vt = alg;
+    BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live);
+
+    return &ctx->mac;
+}
+
+static void hmac_free(ssh2_mac *mac)
+{
+    struct hmac *ctx = container_of(mac, struct hmac, mac);
+
+    ssh_hash_free(ctx->h_outer);
+    ssh_hash_free(ctx->h_inner);
+    ssh_hash_free(ctx->h_live);
+    smemclr(ctx->digest, ctx->hashalg->hlen);
+    sfree(ctx->digest);
+    strbuf_free(ctx->text_name);
+
+    smemclr(ctx, sizeof(*ctx));
+    sfree(ctx);
+}
+
+#define PAD_OUTER 0x5C
+#define PAD_INNER 0x36
+
+static void hmac_key(ssh2_mac *mac, ptrlen key)
+{
+    struct hmac *ctx = container_of(mac, struct hmac, mac);
+
+    const uint8_t *kp;
+    size_t klen;
+    strbuf *sb = NULL;
+
+    if (key.len > ctx->hashalg->blocklen) {
+        /*
+         * RFC 2104 section 2: if the key exceeds the block length of
+         * the underlying hash, then we start by hashing the key, and
+         * use that hash as the 'true' key for the HMAC construction.
+         */
+        sb = strbuf_new_nm();
+        strbuf_append(sb, ctx->hashalg->hlen);
+        hash_simple(ctx->hashalg, key, sb->u);
+        { // WINSCP
+        kp = sb->u;
+        klen = sb->len;
+        } // WINSCP
+    } else {
+        /*
+         * A short enough key is used as is.
+         */
+        kp = (const uint8_t *)key.ptr;
+        klen = key.len;
+    }
+
+    ssh_hash_reset(ctx->h_outer);
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < klen; i++)
+        put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]);
+    for (i = klen; i < ctx->hashalg->blocklen; i++)
+        put_byte(ctx->h_outer, PAD_OUTER);
+
+    ssh_hash_reset(ctx->h_inner);
+    for (i = 0; i < klen; i++)
+        put_byte(ctx->h_inner, PAD_INNER ^ kp[i]);
+    for (i = klen; i < ctx->hashalg->blocklen; i++)
+        put_byte(ctx->h_inner, PAD_INNER);
+
+    if (sb)
+        strbuf_free(sb);
+    } // WINSCP
+}
+
+static void hmac_start(ssh2_mac *mac)
+{
+    struct hmac *ctx = container_of(mac, struct hmac, mac);
+    ssh_hash_copyfrom(ctx->h_live, ctx->h_inner);
+}
+
+static void hmac_genresult(ssh2_mac *mac, unsigned char *output)
+{
+    struct hmac *ctx = container_of(mac, struct hmac, mac);
+    ssh_hash *htmp;
+
+    /* Leave h_live and h_outer in place, so that the SSH-2 BPP can
+     * continue regenerating test results from different-length
+     * prefixes of the packet */
+    ssh_hash_digest_nondestructive(ctx->h_live, ctx->digest);
+
+    htmp = ssh_hash_copy(ctx->h_outer);
+    put_data(htmp, ctx->digest, ctx->hashalg->hlen);
+    ssh_hash_final(htmp, ctx->digest);
+
+    /*
+     * Some instances of HMAC truncate the output hash, so instead of
+     * writing it directly to 'output' we wrote it to our own
+     * full-length buffer, and now we copy the required amount.
+     */
+    memcpy(output, ctx->digest, mac->vt->len);
+    smemclr(ctx->digest, ctx->hashalg->hlen);
+}
+
+static const char *hmac_text_name(ssh2_mac *mac)
+{
+    struct hmac *ctx = container_of(mac, struct hmac, mac);
+    return ctx->text_name->s;
+}
+
+static const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" };
+const ssh2_macalg ssh_hmac_sha256 = {
+    // WINSCP
+    /*.new =*/ hmac_new,
+    /*.free =*/ hmac_free,
+    /*.setkey =*/ hmac_key,
+    /*.start =*/ hmac_start,
+    /*.genresult =*/ hmac_genresult,
+    /*.text_name =*/ hmac_text_name,
+    /*.name =*/ "hmac-sha2-256",
+    /*.etm_name =*/ "[email protected]",
+    /*.len =*/ 32,
+    /*.keylen =*/ 32,
+    /*.extra =*/ &ssh_hmac_sha256_extra,
+};
+
+static const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" };
+const ssh2_macalg ssh_hmac_md5 = {
+    // WINSCP
+    /*.new =*/ hmac_new,
+    /*.free =*/ hmac_free,
+    /*.setkey =*/ hmac_key,
+    /*.start =*/ hmac_start,
+    /*.genresult =*/ hmac_genresult,
+    /*.text_name =*/ hmac_text_name,
+    /*.name =*/ "hmac-md5",
+    /*.etm_name =*/ "[email protected]",
+    /*.len =*/ 16,
+    /*.keylen =*/ 16,
+    /*.extra =*/ &ssh_hmac_md5_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" };
+
+const ssh2_macalg ssh_hmac_sha1 = {
+    // WINSCP
+    /*.new =*/ hmac_new,
+    /*.free =*/ hmac_free,
+    /*.setkey =*/ hmac_key,
+    /*.start =*/ hmac_start,
+    /*.genresult =*/ hmac_genresult,
+    /*.text_name =*/ hmac_text_name,
+    /*.name =*/ "hmac-sha1",
+    /*.etm_name =*/ "[email protected]",
+    /*.len =*/ 20,
+    /*.keylen =*/ 20,
+    /*.extra =*/ &ssh_hmac_sha1_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" };
+
+const ssh2_macalg ssh_hmac_sha1_96 = {
+    // WINSCP
+    /*.new =*/ hmac_new,
+    /*.free =*/ hmac_free,
+    /*.setkey =*/ hmac_key,
+    /*.start =*/ hmac_start,
+    /*.genresult =*/ hmac_genresult,
+    /*.text_name =*/ hmac_text_name,
+    /*.name =*/ "hmac-sha1-96",
+    /*.etm_name =*/ "[email protected]",
+    /*.len =*/ 12,
+    /*.keylen =*/ 20,
+    /*.extra =*/ &ssh_hmac_sha1_96_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_buggy_extra = {
+    &ssh_sha1, "", "bug-compatible"
+};
+
+const ssh2_macalg ssh_hmac_sha1_buggy = {
+    // WINSCP
+    /*.new =*/ hmac_new,
+    /*.free =*/ hmac_free,
+    /*.setkey =*/ hmac_key,
+    /*.start =*/ hmac_start,
+    /*.genresult =*/ hmac_genresult,
+    /*.text_name =*/ hmac_text_name,
+    /*.name =*/ "hmac-sha1",
+    NULL, // WINSCP
+    /*.len =*/ 20,
+    /*.keylen =*/ 16,
+    /*.extra =*/ &ssh_hmac_sha1_buggy_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = {
+    &ssh_sha1, "-96", "bug-compatible"
+};
+
+const ssh2_macalg ssh_hmac_sha1_96_buggy = {
+    // WINSCP
+    /*.new =*/ hmac_new,
+    /*.free =*/ hmac_free,
+    /*.setkey =*/ hmac_key,
+    /*.start =*/ hmac_start,
+    /*.genresult =*/ hmac_genresult,
+    /*.text_name =*/ hmac_text_name,
+    /*.name =*/ "hmac-sha1-96",
+    NULL, // WINSCP
+    /*.len =*/ 12,
+    /*.keylen =*/ 16,
+    /*.extra =*/ &ssh_hmac_sha1_96_buggy_extra,
+};

+ 43 - 0
source/putty/crypto/mac.c

@@ -0,0 +1,43 @@
+/*
+ * Centralised parts of the SSH-2 MAC API, which don't need to vary
+ * with the MAC implementation.
+ */
+
+#include <assert.h>
+
+#include "ssh.h"
+
+bool ssh2_mac_verresult(ssh2_mac *mac, const void *candidate)
+{
+    unsigned char correct[64]; /* at least as big as all known MACs */
+    bool toret;
+
+    assert(mac->vt->len <= sizeof(correct));
+    ssh2_mac_genresult(mac, correct);
+    toret = smemeq(correct, candidate, mac->vt->len);
+
+    smemclr(correct, sizeof(correct));
+
+    return toret;
+}
+
+static void ssh2_mac_prepare(ssh2_mac *mac, const void *blk, int len,
+                             unsigned long seq)
+{
+    ssh2_mac_start(mac);
+    put_uint32(mac, seq);
+    put_data(mac, blk, len);
+}
+
+void ssh2_mac_generate(ssh2_mac *mac, void *blk, int len, unsigned long seq)
+{
+    ssh2_mac_prepare(mac, blk, len, seq);
+    ssh2_mac_genresult(mac, (unsigned char *)blk + len);
+}
+
+bool ssh2_mac_verify(
+    ssh2_mac *mac, const void *blk, int len, unsigned long seq)
+{
+    ssh2_mac_prepare(mac, blk, len, seq);
+    return ssh2_mac_verresult(mac, (const unsigned char *)blk + len);
+}

+ 16 - 0
source/putty/crypto/mac_simple.c

@@ -0,0 +1,16 @@
+/*
+ * Convenience function to MAC a single piece of data, wrapping up
+ * the faff of making and freeing an ssh_mac.
+ */
+
+#include "ssh.h"
+
+void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output)
+{
+    ssh2_mac *mac = ssh2_mac_new(alg, NULL);
+    ssh2_mac_setkey(mac, key);
+    ssh2_mac_start(mac);
+    put_datapl(mac, data);
+    ssh2_mac_genresult(mac, output);
+    ssh2_mac_free(mac);
+}

+ 256 - 0
source/putty/crypto/md5.c

@@ -0,0 +1,256 @@
+/*
+ * MD5 implementation for PuTTY. Written directly from the spec by
+ * Simon Tatham.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+
+static const uint32_t md5_initial_state[] = {
+    0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476,
+};
+
+static const struct md5_round_constant {
+    uint32_t addition, rotation, msg_index;
+} md5_round_constants[] = {
+    { 0xd76aa478,  7,  0 }, { 0xe8c7b756, 12,  1 },
+    { 0x242070db, 17,  2 }, { 0xc1bdceee, 22,  3 },
+    { 0xf57c0faf,  7,  4 }, { 0x4787c62a, 12,  5 },
+    { 0xa8304613, 17,  6 }, { 0xfd469501, 22,  7 },
+    { 0x698098d8,  7,  8 }, { 0x8b44f7af, 12,  9 },
+    { 0xffff5bb1, 17, 10 }, { 0x895cd7be, 22, 11 },
+    { 0x6b901122,  7, 12 }, { 0xfd987193, 12, 13 },
+    { 0xa679438e, 17, 14 }, { 0x49b40821, 22, 15 },
+    { 0xf61e2562,  5,  1 }, { 0xc040b340,  9,  6 },
+    { 0x265e5a51, 14, 11 }, { 0xe9b6c7aa, 20,  0 },
+    { 0xd62f105d,  5,  5 }, { 0x02441453,  9, 10 },
+    { 0xd8a1e681, 14, 15 }, { 0xe7d3fbc8, 20,  4 },
+    { 0x21e1cde6,  5,  9 }, { 0xc33707d6,  9, 14 },
+    { 0xf4d50d87, 14,  3 }, { 0x455a14ed, 20,  8 },
+    { 0xa9e3e905,  5, 13 }, { 0xfcefa3f8,  9,  2 },
+    { 0x676f02d9, 14,  7 }, { 0x8d2a4c8a, 20, 12 },
+    { 0xfffa3942,  4,  5 }, { 0x8771f681, 11,  8 },
+    { 0x6d9d6122, 16, 11 }, { 0xfde5380c, 23, 14 },
+    { 0xa4beea44,  4,  1 }, { 0x4bdecfa9, 11,  4 },
+    { 0xf6bb4b60, 16,  7 }, { 0xbebfbc70, 23, 10 },
+    { 0x289b7ec6,  4, 13 }, { 0xeaa127fa, 11,  0 },
+    { 0xd4ef3085, 16,  3 }, { 0x04881d05, 23,  6 },
+    { 0xd9d4d039,  4,  9 }, { 0xe6db99e5, 11, 12 },
+    { 0x1fa27cf8, 16, 15 }, { 0xc4ac5665, 23,  2 },
+    { 0xf4292244,  6,  0 }, { 0x432aff97, 10,  7 },
+    { 0xab9423a7, 15, 14 }, { 0xfc93a039, 21,  5 },
+    { 0x655b59c3,  6, 12 }, { 0x8f0ccc92, 10,  3 },
+    { 0xffeff47d, 15, 10 }, { 0x85845dd1, 21,  1 },
+    { 0x6fa87e4f,  6,  8 }, { 0xfe2ce6e0, 10, 15 },
+    { 0xa3014314, 15,  6 }, { 0x4e0811a1, 21, 13 },
+    { 0xf7537e82,  6,  4 }, { 0xbd3af235, 10, 11 },
+    { 0x2ad7d2bb, 15,  2 }, { 0xeb86d391, 21,  9 },
+};
+
+typedef struct md5_block md5_block;
+struct md5_block {
+    uint8_t block[64];
+    size_t used;
+    uint64_t len;
+};
+
+static inline void md5_block_setup(md5_block *blk)
+{
+    blk->used = 0;
+    blk->len = 0;
+}
+
+static inline bool md5_block_write(
+    md5_block *blk, const void **vdata, size_t *len)
+{
+    size_t blkleft = sizeof(blk->block) - blk->used;
+    size_t chunk = *len < blkleft ? *len : blkleft;
+
+    const uint8_t *p = *vdata;
+    memcpy(blk->block + blk->used, p, chunk);
+    *vdata = p + chunk;
+    *len -= chunk;
+    blk->used += chunk;
+    blk->len += chunk;
+
+    if (blk->used == sizeof(blk->block)) {
+        blk->used = 0;
+        return true;
+    }
+
+    return false;
+}
+
+static inline void md5_block_pad(md5_block *blk, BinarySink *bs)
+{
+    uint64_t final_len = blk->len << 3;
+    size_t pad = 63 & (55 - blk->used);
+
+    put_byte(bs, 0x80);
+    put_padding(bs, pad, 0);
+
+    { // WINSCP
+    unsigned char buf[8];
+    PUT_64BIT_LSB_FIRST(buf, final_len);
+    put_data(bs, buf, 8);
+    smemclr(buf, 8);
+    } // WINSCP
+
+    assert(blk->used == 0 && "Should have exactly hit a block boundary");
+}
+
+static inline uint32_t rol(uint32_t x, unsigned y)
+{
+#pragma option push -w-ngu // WINSCP
+    return (x << (31 & y)) | (x >> (31 & -y));
+#pragma option pop // WINSCP
+}
+
+static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0)
+{
+    return if0 ^ (ctrl & (if1 ^ if0));
+}
+
+/* Parameter functions for the four MD5 round types */
+static inline uint32_t F(uint32_t x, uint32_t y, uint32_t z)
+{ return Ch(x, y, z); }
+static inline uint32_t G(uint32_t x, uint32_t y, uint32_t z)
+{ return Ch(z, x, y); }
+static inline uint32_t H(uint32_t x, uint32_t y, uint32_t z)
+{ return x ^ y ^ z; }
+static inline uint32_t I(uint32_t x, uint32_t y, uint32_t z)
+{ return y ^ (x | ~z); }
+
+static inline void md5_round(
+    unsigned round_index, const uint32_t *message,
+    uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d,
+    uint32_t (*f)(uint32_t, uint32_t, uint32_t))
+{
+    struct md5_round_constant rc = md5_round_constants[round_index];
+
+    *a = *b + rol(*a + f(*b, *c, *d) + message[rc.msg_index] + rc.addition,
+                  rc.rotation);
+}
+
+static void md5_do_block(uint32_t *core, const uint8_t *block)
+{
+    uint32_t message_words[16];
+    size_t i; // WINSCP
+    for (i = 0; i < 16; i++)
+        message_words[i] = GET_32BIT_LSB_FIRST(block + 4*i);
+
+    { // WINSCP
+    uint32_t a = core[0], b = core[1], c = core[2], d = core[3];
+
+    size_t t = 0;
+    size_t u; // WINSCP
+    for (u = 0; u < 4; u++) {
+        md5_round(t++, message_words, &a, &b, &c, &d, F);
+        md5_round(t++, message_words, &d, &a, &b, &c, F);
+        md5_round(t++, message_words, &c, &d, &a, &b, F);
+        md5_round(t++, message_words, &b, &c, &d, &a, F);
+    }
+    for (u = 0; u < 4; u++) {
+        md5_round(t++, message_words, &a, &b, &c, &d, G);
+        md5_round(t++, message_words, &d, &a, &b, &c, G);
+        md5_round(t++, message_words, &c, &d, &a, &b, G);
+        md5_round(t++, message_words, &b, &c, &d, &a, G);
+    }
+    for (u = 0; u < 4; u++) {
+        md5_round(t++, message_words, &a, &b, &c, &d, H);
+        md5_round(t++, message_words, &d, &a, &b, &c, H);
+        md5_round(t++, message_words, &c, &d, &a, &b, H);
+        md5_round(t++, message_words, &b, &c, &d, &a, H);
+    }
+    for (u = 0; u < 4; u++) {
+        md5_round(t++, message_words, &a, &b, &c, &d, I);
+        md5_round(t++, message_words, &d, &a, &b, &c, I);
+        md5_round(t++, message_words, &c, &d, &a, &b, I);
+        md5_round(t++, message_words, &b, &c, &d, &a, I);
+    }
+
+    core[0] += a;
+    core[1] += b;
+    core[2] += c;
+    core[3] += d;
+    } // WINSCP
+
+    smemclr(message_words, sizeof(message_words));
+}
+
+typedef struct md5 {
+    uint32_t core[4];
+    md5_block blk;
+    BinarySink_IMPLEMENTATION;
+    ssh_hash hash;
+} md5;
+
+static void md5_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *md5_new(const ssh_hashalg *alg)
+{
+    md5 *s = snew(md5);
+
+    s->hash.vt = alg;
+    BinarySink_INIT(s, md5_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+}
+
+static void md5_reset(ssh_hash *hash)
+{
+    md5 *s = container_of(hash, md5, hash);
+
+    memcpy(s->core, md5_initial_state, sizeof(s->core));
+    md5_block_setup(&s->blk);
+}
+
+static void md5_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    md5 *copy = container_of(hcopy, md5, hash);
+    md5 *orig = container_of(horig, md5, hash);
+
+    memcpy(copy, orig, sizeof(*copy));
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void md5_free(ssh_hash *hash)
+{
+    md5 *s = container_of(hash, md5, hash);
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void md5_write(BinarySink *bs, const void *vp, size_t len)
+{
+    md5 *s = BinarySink_DOWNCAST(bs, md5);
+
+    while (len > 0)
+        if (md5_block_write(&s->blk, &vp, &len))
+            md5_do_block(s->core, s->blk.block);
+}
+
+static void md5_digest(ssh_hash *hash, uint8_t *digest)
+{
+    md5 *s = container_of(hash, md5, hash);
+
+    size_t i; // WINSCP
+    md5_block_pad(&s->blk, BinarySink_UPCAST(s));
+    for (i = 0; i < 4; i++)
+        PUT_32BIT_LSB_FIRST(digest + 4*i, s->core[i]);
+}
+
+const ssh_hashalg ssh_md5 = {
+    // WINSCP
+    /*.new =*/ md5_new,
+    /*.reset =*/ md5_reset,
+    /*.copyfrom =*/ md5_copyfrom,
+    /*.digest =*/ md5_digest,
+    /*.free =*/ md5_free,
+    /*.hlen =*/ 16,
+    /*.blocklen =*/ 64,
+    HASHALG_NAMES_BARE("MD5"),
+    NULL, // WINSCP
+};

+ 3019 - 0
source/putty/crypto/mpint.c

@@ -0,0 +1,3019 @@
+/*
+ * Multiprecision integer arithmetic, implementing mpint.h.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "defs.h"
+#include "misc.h"
+#include "puttymem.h"
+
+#include "mpint.h"
+#include "mpint_i.h"
+
+#pragma warn -ngu // WINSCP
+
+#define SIZE_T_BITS (CHAR_BIT * sizeof(size_t))
+
+/*
+ * Inline helpers to take min and max of size_t values, used
+ * throughout this code.
+ */
+static inline size_t size_t_min(size_t a, size_t b)
+{
+    return a < b ? a : b;
+}
+static inline size_t size_t_max(size_t a, size_t b)
+{
+    return a > b ? a : b;
+}
+
+/*
+ * Helper to fetch a word of data from x with array overflow checking.
+ * If x is too short to have that word, 0 is returned.
+ */
+static inline BignumInt mp_word(mp_int *x, size_t i)
+{
+    return i < x->nw ? x->w[i] : 0;
+}
+
+/*
+ * Shift an ordinary C integer by BIGNUM_INT_BITS, in a way that
+ * avoids writing a shift operator whose RHS is greater or equal to
+ * the size of the type, because that's undefined behaviour in C.
+ *
+ * In fact we must avoid even writing it in a definitely-untaken
+ * branch of an if, because compilers will sometimes warn about
+ * that. So you can't just write 'shift too big ? 0 : n >> shift',
+ * because even if 'shift too big' is a constant-expression
+ * evaluating to false, you can still get complaints about the
+ * else clause of the ?:.
+ *
+ * So we have to re-check _inside_ that clause, so that the shift
+ * count is reset to something nonsensical but safe in the case
+ * where the clause wasn't going to be taken anyway.
+ */
+static uintmax_t shift_right_by_one_word(uintmax_t n)
+{
+    bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n);
+    return shift_too_big ? 0 :
+        n >> (shift_too_big ? 0 : BIGNUM_INT_BITS);
+}
+static uintmax_t shift_left_by_one_word(uintmax_t n)
+{
+    bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n);
+    return shift_too_big ? 0 :
+        n << (shift_too_big ? 0 : BIGNUM_INT_BITS);
+}
+
+mp_int *mp_make_sized(size_t nw)
+{
+    mp_int *x = snew_plus(mp_int, nw * sizeof(BignumInt));
+    assert(nw);                   /* we outlaw the zero-word mp_int */
+    x->nw = nw;
+    x->w = snew_plus_get_aux(x);
+    mp_clear(x);
+    return x;
+}
+
+mp_int *mp_new(size_t maxbits)
+{
+    size_t words = (maxbits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+    return mp_make_sized(words);
+}
+
+mp_int *mp_from_integer(uintmax_t n)
+{
+    mp_int *x = mp_make_sized(
+        (sizeof(n) + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES);
+    size_t i; // WINSCP
+    for (i = 0; i < x->nw; i++)
+        x->w[i] = n >> (i * BIGNUM_INT_BITS);
+    return x;
+}
+
+size_t mp_max_bytes(mp_int *x)
+{
+    return x->nw * BIGNUM_INT_BYTES;
+}
+
+size_t mp_max_bits(mp_int *x)
+{
+    return x->nw * BIGNUM_INT_BITS;
+}
+
+void mp_free(mp_int *x)
+{
+    mp_clear(x);
+    smemclr(x, sizeof(*x));
+    sfree(x);
+}
+
+void mp_dump(FILE *fp, const char *prefix, mp_int *x, const char *suffix)
+{
+    size_t i; // WINSCP
+    fprintf(fp, "%s0x", prefix);
+    for (i = mp_max_bytes(x); i-- > 0 ;)
+        fprintf(fp, "%02X", mp_get_byte(x, i));
+    fputs(suffix, fp);
+}
+
+void mp_copy_into(mp_int *dest, mp_int *src)
+{
+    size_t copy_nw = size_t_min(dest->nw, src->nw);
+    memmove(dest->w, src->w, copy_nw * sizeof(BignumInt));
+    smemclr(dest->w + copy_nw, (dest->nw - copy_nw) * sizeof(BignumInt));
+}
+
+void mp_copy_integer_into(mp_int *r, uintmax_t n)
+{
+    size_t i; // WINSCP
+    for (i = 0; i < r->nw; i++) {
+        r->w[i] = n;
+        n = shift_right_by_one_word(n);
+    }
+}
+
+/*
+ * Conditional selection is done by negating 'which', to give a mask
+ * word which is all 1s if which==1 and all 0s if which==0. Then you
+ * can select between two inputs a,b without data-dependent control
+ * flow by XORing them to get their difference; ANDing with the mask
+ * word to replace that difference with 0 if which==0; and XORing that
+ * into a, which will either turn it into b or leave it alone.
+ *
+ * This trick will be used throughout this code and taken as read the
+ * rest of the time (or else I'd be here all week typing comments),
+ * but I felt I ought to explain it in words _once_.
+ */
+void mp_select_into(mp_int *dest, mp_int *src0, mp_int *src1,
+                    unsigned which)
+{
+    BignumInt mask = -(BignumInt)(1 & which);
+    size_t i; // WINSCP
+    for (i = 0; i < dest->nw; i++) {
+        BignumInt srcword0 = mp_word(src0, i), srcword1 = mp_word(src1, i);
+        dest->w[i] = srcword0 ^ ((srcword1 ^ srcword0) & mask);
+    }
+}
+
+void mp_cond_swap(mp_int *x0, mp_int *x1, unsigned swap)
+{
+    pinitassert(x0->nw == x1->nw);
+    volatile BignumInt mask = -(BignumInt)(1 & swap);
+    size_t i; // WINSCP
+    for (i = 0; i < x0->nw; i++) {
+        BignumInt diff = (x0->w[i] ^ x1->w[i]) & mask;
+        x0->w[i] ^= diff;
+        x1->w[i] ^= diff;
+    }
+}
+
+void mp_clear(mp_int *x)
+{
+    smemclr(x->w, x->nw * sizeof(BignumInt));
+}
+
+void mp_cond_clear(mp_int *x, unsigned clear)
+{
+    BignumInt mask = ~-(BignumInt)(1 & clear);
+    size_t i; // WINSCP
+    for (i = 0; i < x->nw; i++)
+        x->w[i] &= mask;
+}
+
+/*
+ * Common code between mp_from_bytes_{le,be} which reads bytes in an
+ * arbitrary arithmetic progression.
+ */
+static mp_int *mp_from_bytes_int(ptrlen bytes, size_t m, size_t c)
+{
+    size_t nw = (bytes.len + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES;
+    nw = size_t_max(nw, 1);
+    { // WINSCP
+    mp_int *n = mp_make_sized(nw);
+    size_t i; // WINSCP
+    for (i = 0; i < bytes.len; i++)
+        n->w[i / BIGNUM_INT_BYTES] |=
+            (BignumInt)(((const unsigned char *)bytes.ptr)[m*i+c]) <<
+            (8 * (i % BIGNUM_INT_BYTES));
+    return n;
+    } // WINSCP
+}
+
+mp_int *mp_from_bytes_le(ptrlen bytes)
+{
+    return mp_from_bytes_int(bytes, 1, 0);
+}
+
+mp_int *mp_from_bytes_be(ptrlen bytes)
+{
+    return mp_from_bytes_int(bytes, -1, bytes.len - 1);
+}
+
+static mp_int *mp_from_words(size_t nw, const BignumInt *w)
+{
+    mp_int *x = mp_make_sized(nw);
+    memcpy(x->w, w, x->nw * sizeof(BignumInt));
+    return x;
+}
+
+/*
+ * Decimal-to-binary conversion: just go through the input string
+ * adding on the decimal value of each digit, and then multiplying the
+ * number so far by 10.
+ */
+mp_int *mp_from_decimal_pl(ptrlen decimal)
+{
+    /* 196/59 is an upper bound (and also a continued-fraction
+     * convergent) for log2(10), so this conservatively estimates the
+     * number of bits that will be needed to store any number that can
+     * be written in this many decimal digits. */
+    pinitassert(decimal.len < (~(size_t)0) / 196);
+    size_t bits = 196 * decimal.len / 59;
+
+    /* Now round that up to words. */
+    size_t words = bits / BIGNUM_INT_BITS + 1;
+
+    mp_int *x = mp_make_sized(words);
+    size_t i; // WINSCP
+    for (i = 0; i < decimal.len; i++) {
+        mp_add_integer_into(x, x, ((const char *)decimal.ptr)[i] - '0');
+
+        if (i+1 == decimal.len)
+            break;
+
+        mp_mul_integer_into(x, x, 10);
+    }
+    return x;
+}
+
+mp_int *mp_from_decimal(const char *decimal)
+{
+    return mp_from_decimal_pl(ptrlen_from_asciz(decimal));
+}
+
+/*
+ * Hex-to-binary conversion: _algorithmically_ simpler than decimal
+ * (none of those multiplications by 10), but there's some fiddly
+ * bit-twiddling needed to process each hex digit without diverging
+ * control flow depending on whether it's a letter or a number.
+ */
+mp_int *mp_from_hex_pl(ptrlen hex)
+{
+    pinitassert(hex.len <= (~(size_t)0) / 4);
+    size_t bits = hex.len * 4;
+    size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+    words = size_t_max(words, 1);
+    { // WINSCP
+    mp_int *x = mp_make_sized(words);
+    size_t nibble; // WINSCP
+    for (nibble = 0; nibble < hex.len; nibble++) {
+        BignumInt digit = ((const char *)hex.ptr)[hex.len-1 - nibble];
+
+        BignumInt lmask = ~-((BignumInt)((digit-'a')|('f'-digit))
+                             >> (BIGNUM_INT_BITS-1));
+        BignumInt umask = ~-((BignumInt)((digit-'A')|('F'-digit))
+                             >> (BIGNUM_INT_BITS-1));
+
+        BignumInt digitval = digit - '0';
+        digitval ^= (digitval ^ (digit - 'a' + 10)) & lmask;
+        digitval ^= (digitval ^ (digit - 'A' + 10)) & umask;
+        digitval &= 0xF; /* at least be _slightly_ nice about weird input */
+
+        { // WINSCP
+        size_t word_idx = nibble / (BIGNUM_INT_BYTES*2);
+        size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2);
+        x->w[word_idx] |= digitval << (nibble_within_word * 4);
+        } // WINSCP
+    }
+    return x;
+    } // WINSCP
+}
+
+mp_int *mp_from_hex(const char *hex)
+{
+    return mp_from_hex_pl(ptrlen_from_asciz(hex));
+}
+
+mp_int *mp_copy(mp_int *x)
+{
+    return mp_from_words(x->nw, x->w);
+}
+
+uint8_t mp_get_byte(mp_int *x, size_t byte)
+{
+    return 0xFF & (mp_word(x, byte / BIGNUM_INT_BYTES) >>
+                   (8 * (byte % BIGNUM_INT_BYTES)));
+}
+
+unsigned mp_get_bit(mp_int *x, size_t bit)
+{
+    return 1 & (mp_word(x, bit / BIGNUM_INT_BITS) >>
+                (bit % BIGNUM_INT_BITS));
+}
+
+uintmax_t mp_get_integer(mp_int *x)
+{
+    uintmax_t toret = 0;
+    size_t i; // WINSCP 
+    for (i = x->nw; i-- > 0 ;)
+        toret = shift_left_by_one_word(toret) | x->w[i];
+    return toret;
+}
+
+void mp_set_bit(mp_int *x, size_t bit, unsigned val)
+{
+    size_t word = bit / BIGNUM_INT_BITS;
+    pinitassert(word < x->nw);
+
+    unsigned shift = (bit % BIGNUM_INT_BITS);
+
+    x->w[word] &= ~((BignumInt)1 << shift);
+    x->w[word] |= (BignumInt)(val & 1) << shift;
+}
+
+/*
+ * Helper function used here and there to normalise any nonzero input
+ * value to 1.
+ */
+static inline unsigned normalise_to_1(BignumInt n)
+{
+    n = (n >> 1) | (n & 1);            /* ensure top bit is clear */
+    n = (BignumInt)(-n) >> (BIGNUM_INT_BITS - 1); /* normalise to 0 or 1 */
+    return n;
+}
+static inline unsigned normalise_to_1_u64(uint64_t n)
+{
+    n = (n >> 1) | (n & 1);            /* ensure top bit is clear */
+    n = (-n) >> 63;                    /* normalise to 0 or 1 */
+    return n;
+}
+
+/*
+ * Find the highest nonzero word in a number. Returns the index of the
+ * word in x->w, and also a pair of output uint64_t in which that word
+ * appears in the high one shifted left by 'shift_wanted' bits, the
+ * words immediately below it occupy the space to the right, and the
+ * words below _that_ fill up the low one.
+ *
+ * If there is no nonzero word at all, the passed-by-reference output
+ * variables retain their original values.
+ */
+static inline void mp_find_highest_nonzero_word_pair(
+    mp_int *x, size_t shift_wanted, size_t *index,
+    uint64_t *hi, uint64_t *lo)
+{
+    uint64_t curr_hi = 0, curr_lo = 0;
+
+    size_t curr_index; // WINSCP
+    for (curr_index = 0; curr_index < x->nw; curr_index++) {
+        BignumInt curr_word = x->w[curr_index];
+        unsigned indicator = normalise_to_1(curr_word);
+
+        curr_lo = (BIGNUM_INT_BITS < 64 ? (curr_lo >> BIGNUM_INT_BITS) : 0) |
+            (curr_hi << (64 - BIGNUM_INT_BITS));
+        curr_hi = (BIGNUM_INT_BITS < 64 ? (curr_hi >> BIGNUM_INT_BITS) : 0) |
+            ((uint64_t)curr_word << shift_wanted);
+
+        if (hi)    *hi    ^= (curr_hi    ^ *hi   ) & -(uint64_t)indicator;
+        if (lo)    *lo    ^= (curr_lo    ^ *lo   ) & -(uint64_t)indicator;
+        if (index) *index ^= (curr_index ^ *index) & -(size_t)  indicator;
+    }
+}
+
+size_t mp_get_nbits(mp_int *x)
+{
+    /* Sentinel values in case there are no bits set at all: we
+     * imagine that there's a word at position -1 (i.e. the topmost
+     * fraction word) which is all 1s, because that way, we handle a
+     * zero input by considering its highest set bit to be the top one
+     * of that word, i.e. just below the units digit, i.e. at bit
+     * index -1, i.e. so we'll return 0 on output. */
+    size_t hiword_index = -(size_t)1;
+    uint64_t hiword64 = ~(BignumInt)0;
+
+    /*
+     * Find the highest nonzero word and its index.
+     */
+    mp_find_highest_nonzero_word_pair(x, 0, &hiword_index, &hiword64, NULL);
+    { // WINSCP
+    BignumInt hiword = hiword64; /* in case BignumInt is a narrower type */
+
+    /*
+     * Find the index of the highest set bit within hiword.
+     */
+    BignumInt hibit_index = 0;
+    size_t i; // WINSCP
+    for (i = (1 << (BIGNUM_INT_BITS_BITS-1)); i != 0; i >>= 1) {
+        BignumInt shifted_word = hiword >> i;
+        BignumInt indicator =
+            (BignumInt)(-shifted_word) >> (BIGNUM_INT_BITS-1);
+        hiword ^= (shifted_word ^ hiword ) & -indicator;
+        hibit_index += i & -(size_t)indicator;
+    }
+
+    /*
+     * Put together the result.
+     */
+    return (hiword_index << BIGNUM_INT_BITS_BITS) + hibit_index + 1;
+    } // WINSCP
+}
+
+/*
+ * Shared code between the hex and decimal output functions to get rid
+ * of leading zeroes on the output string. The idea is that we wrote
+ * out a fixed number of digits and a trailing \0 byte into 'buf', and
+ * now we want to shift it all left so that the first nonzero digit
+ * moves to buf[0] (or, if there are no nonzero digits at all, we move
+ * up by 'maxtrim', so that we return 0 as "0" instead of "").
+ */
+static void trim_leading_zeroes(char *buf, size_t bufsize, size_t maxtrim)
+{
+    size_t trim = maxtrim;
+
+    /*
+     * Look for the first character not equal to '0', to find the
+     * shift count.
+     */
+    if (trim > 0) {
+        size_t pos; // WINSCP
+        for (pos = trim; pos-- > 0 ;) {
+            uint8_t diff = buf[pos] ^ '0';
+            size_t mask = -((((size_t)diff) - 1) >> (SIZE_T_BITS - 1));
+            trim ^= (trim ^ pos) & ~mask;
+        }
+    }
+
+    /*
+     * Now do the shift, in log n passes each of which does a
+     * conditional shift by 2^i bytes if bit i is set in the shift
+     * count.
+     */
+    { // WINSCP
+    uint8_t *ubuf = (uint8_t *)buf;
+    size_t logd; // WINSCP
+    for (logd = 0; bufsize >> logd; logd++) {
+        uint8_t mask = -(uint8_t)((trim >> logd) & 1);
+        size_t d = (size_t)1 << logd;
+        size_t i; // WINSCP
+        for (i = 0; i+d < bufsize; i++) {
+            uint8_t diff = mask & (ubuf[i] ^ ubuf[i+d]);
+            ubuf[i] ^= diff;
+            ubuf[i+d] ^= diff;
+        }
+    }
+    } // WINSCP
+}
+
+/*
+ * Binary to decimal conversion. Our strategy here is to extract each
+ * decimal digit by finding the input number's residue mod 10, then
+ * subtract that off to give an exact multiple of 10, which then means
+ * you can safely divide by 10 by means of shifting right one bit and
+ * then multiplying by the inverse of 5 mod 2^n.
+ */
+char *mp_get_decimal(mp_int *x_orig)
+{
+    mp_int *x = mp_copy(x_orig), *y = mp_make_sized(x->nw);
+
+    /*
+     * The inverse of 5 mod 2^lots is 0xccccccccccccccccccccd, for an
+     * appropriate number of 'c's. Manually construct an integer the
+     * right size.
+     */
+    mp_int *inv5 = mp_make_sized(x->nw);
+    pinitassert(BIGNUM_INT_BITS % 8 == 0);
+    size_t i; // WINSCP
+    for (i = 0; i < inv5->nw; i++)
+        inv5->w[i] = BIGNUM_INT_MASK / 5 * 4;
+    inv5->w[0]++;
+
+    /*
+     * 146/485 is an upper bound (and also a continued-fraction
+     * convergent) of log10(2), so this is a conservative estimate of
+     * the number of decimal digits needed to store a value that fits
+     * in this many binary bits.
+     */
+    assert(x->nw < (~(size_t)1) / (146 * BIGNUM_INT_BITS));
+    { // WINSCP
+    size_t bufsize = size_t_max(x->nw * (146 * BIGNUM_INT_BITS) / 485, 1) + 2;
+    char *outbuf = snewn(bufsize, char);
+    outbuf[bufsize - 1] = '\0';
+
+    /*
+     * Loop over the number generating digits from the least
+     * significant upwards, so that we write to outbuf in reverse
+     * order.
+     */
+    { // WINSCP
+    size_t pos; // WINSCP
+    for (pos = bufsize - 1; pos-- > 0 ;) {
+        /*
+         * Find the current residue mod 10. We do this by first
+         * summing the bytes of the number, with all but the lowest
+         * one multiplied by 6 (because 256^i == 6 mod 10 for all
+         * i>0). That gives us a single word congruent mod 10 to the
+         * input number, and then we reduce it further by manual
+         * multiplication and shifting, just in case the compiler
+         * target implements the C division operator in a way that has
+         * input-dependent timing.
+         */
+        uint32_t low_digit = 0, maxval = 0, mult = 1;
+        size_t i; // WINSCP
+        for (i = 0; i < x->nw; i++) {
+            unsigned j; // WINSCP
+            for (j = 0; j < BIGNUM_INT_BYTES; j++) {
+                low_digit += mult * (0xFF & (x->w[i] >> (8*j)));
+                maxval += mult * 0xFF;
+                mult = 6;
+            }
+            /*
+             * For _really_ big numbers, prevent overflow of t by
+             * periodically folding the top half of the accumulator
+             * into the bottom half, using the same rule 'multiply by
+             * 6 when shifting down by one or more whole bytes'.
+             */
+            if (maxval > UINT32_MAX - (6 * 0xFF * BIGNUM_INT_BYTES)) {
+                low_digit = (low_digit & 0xFFFF) + 6 * (low_digit >> 16);
+                maxval = (maxval & 0xFFFF) + 6 * (maxval >> 16);
+            }
+        }
+
+        /*
+         * Final reduction of low_digit. We multiply by 2^32 / 10
+         * (that's the constant 0x19999999) to get a 64-bit value
+         * whose top 32 bits are the approximate quotient
+         * low_digit/10; then we subtract off 10 times that; and
+         * finally we do one last trial subtraction of 10 by adding 6
+         * (which sets bit 4 if the number was just over 10) and then
+         * testing bit 4.
+         */
+        low_digit -= 10 * ((0x19999999ULL * low_digit) >> 32);
+        low_digit -= 10 * ((low_digit + 6) >> 4);
+
+        assert(low_digit < 10);        /* make sure we did reduce fully */
+        outbuf[pos] = '0' + low_digit;
+
+        /*
+         * Now subtract off that digit, divide by 2 (using a right
+         * shift) and by 5 (using the modular inverse), to get the
+         * next output digit into the units position.
+         */
+        mp_sub_integer_into(x, x, low_digit);
+        mp_rshift_fixed_into(y, x, 1);
+        mp_mul_into(x, y, inv5);
+    }
+
+    mp_free(x);
+    mp_free(y);
+    mp_free(inv5);
+
+    trim_leading_zeroes(outbuf, bufsize, bufsize - 2);
+    return outbuf;
+    } // WINSCP
+    } // WINSCP
+}
+
+/*
+ * Binary to hex conversion. Reasonably simple (only a spot of bit
+ * twiddling to choose whether to output a digit or a letter for each
+ * nibble).
+ */
+static char *mp_get_hex_internal(mp_int *x, uint8_t letter_offset)
+{
+    size_t nibbles = x->nw * BIGNUM_INT_BYTES * 2;
+    size_t bufsize = nibbles + 1;
+    char *outbuf = snewn(bufsize, char);
+    size_t nibble; // WINSCP
+    outbuf[nibbles] = '\0';
+
+    for (nibble = 0; nibble < nibbles; nibble++) {
+        size_t word_idx = nibble / (BIGNUM_INT_BYTES*2);
+        size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2);
+        uint8_t digitval = 0xF & (x->w[word_idx] >> (nibble_within_word * 4));
+
+        uint8_t mask = -((digitval + 6) >> 4);
+        char digit = digitval + '0' + (letter_offset & mask);
+        outbuf[nibbles-1 - nibble] = digit;
+    }
+
+    trim_leading_zeroes(outbuf, bufsize, nibbles - 1);
+    return outbuf;
+}
+
+char *mp_get_hex(mp_int *x)
+{
+    return mp_get_hex_internal(x, 'a' - ('0'+10));
+}
+
+char *mp_get_hex_uppercase(mp_int *x)
+{
+    return mp_get_hex_internal(x, 'A' - ('0'+10));
+}
+
+/*
+ * Routines for reading and writing the SSH-1 and SSH-2 wire formats
+ * for multiprecision integers, declared in marshal.h.
+ *
+ * These can't avoid having control flow dependent on the true bit
+ * size of the number, because the wire format requires the number of
+ * output bytes to depend on that.
+ */
+void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x)
+{
+    size_t bits = mp_get_nbits(x);
+    size_t bytes = (bits + 7) / 8;
+
+    size_t i; // WINSCP
+    assert(bits < 0x10000);
+    put_uint16(bs, bits);
+    for (i = bytes; i-- > 0 ;)
+        put_byte(bs, mp_get_byte(x, i));
+}
+
+void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x)
+{
+    size_t bytes = (mp_get_nbits(x) + 8) / 8;
+
+    size_t i; // WINSCP
+    put_uint32(bs, bytes);
+    for (i = bytes; i-- > 0 ;)
+        put_byte(bs, mp_get_byte(x, i));
+}
+
+mp_int *BinarySource_get_mp_ssh1(BinarySource *src)
+{
+    unsigned bitc = get_uint16(src);
+    ptrlen bytes = get_data(src, (bitc + 7) / 8);
+    if (get_err(src)) {
+        return mp_from_integer(0);
+    } else {
+        mp_int *toret = mp_from_bytes_be(bytes);
+        /* SSH-1.5 spec says that it's OK for the prefix uint16 to be
+         * _greater_ than the actual number of bits */
+        if (mp_get_nbits(toret) > bitc) {
+            src->err = BSE_INVALID;
+            mp_free(toret);
+            toret = mp_from_integer(0);
+        }
+        return toret;
+    }
+}
+
+mp_int *BinarySource_get_mp_ssh2(BinarySource *src)
+{
+    ptrlen bytes = get_string(src);
+    if (get_err(src)) {
+        return mp_from_integer(0);
+    } else {
+        const unsigned char *p = bytes.ptr;
+        if ((bytes.len > 0 &&
+             ((p[0] & 0x80) ||
+              (p[0] == 0 && (bytes.len <= 1 || !(p[1] & 0x80)))))) {
+            src->err = BSE_INVALID;
+            return mp_from_integer(0);
+        }
+        return mp_from_bytes_be(bytes);
+    }
+}
+
+/*
+ * Make an mp_int structure whose words array aliases a subinterval of
+ * some other mp_int. This makes it easy to read or write just the low
+ * or high words of a number, e.g. to add a number starting from a
+ * high bit position, or to reduce mod 2^{n*BIGNUM_INT_BITS}.
+ *
+ * The convention throughout this code is that when we store an mp_int
+ * directly by value, we always expect it to be an alias of some kind,
+ * so its words array won't ever need freeing. Whereas an 'mp_int *'
+ * has an owner, who knows whether it needs freeing or whether it was
+ * created by address-taking an alias.
+ */
+static mp_int mp_make_alias(mp_int *in, size_t offset, size_t len)
+{
+    /*
+     * Bounds-check the offset and length so that we always return
+     * something valid, even if it's not necessarily the length the
+     * caller asked for.
+     */
+    if (offset > in->nw)
+        offset = in->nw;
+    if (len > in->nw - offset)
+        len = in->nw - offset;
+
+    { // WINSCP
+    mp_int toret;
+    toret.nw = len;
+    toret.w = in->w + offset;
+    return toret;
+    } // WINSCP
+}
+
+/*
+ * A special case of mp_make_alias: in some cases we preallocate a
+ * large mp_int to use as scratch space (to avoid pointless
+ * malloc/free churn in recursive or iterative work).
+ *
+ * mp_alloc_from_scratch creates an alias of size 'len' to part of
+ * 'pool', and adjusts 'pool' itself so that further allocations won't
+ * overwrite that space.
+ *
+ * There's no free function to go with this. Typically you just copy
+ * the pool mp_int by value, allocate from the copy, and when you're
+ * done with those allocations, throw the copy away and go back to the
+ * original value of pool. (A mark/release system.)
+ */
+static mp_int mp_alloc_from_scratch(mp_int *pool, size_t len)
+{
+    pinitassert(len <= pool->nw);
+    mp_int toret = mp_make_alias(pool, 0, len);
+    *pool = mp_make_alias(pool, len, pool->nw);
+    return toret;
+}
+
+/*
+ * Internal component common to lots of assorted add/subtract code.
+ * Reads words from a,b; writes into w_out (which might be NULL if the
+ * output isn't even needed). Takes an input carry flag in 'carry',
+ * and returns the output carry. Each word read from b is ANDed with
+ * b_and and then XORed with b_xor.
+ *
+ * So you can implement addition by setting b_and to all 1s and b_xor
+ * to 0; you can subtract by making b_xor all 1s too (effectively
+ * bit-flipping b) and also passing 1 as the input carry (to turn
+ * one's complement into two's complement). And you can do conditional
+ * add/subtract by choosing b_and to be all 1s or all 0s based on a
+ * condition, because the value of b will be totally ignored if b_and
+ * == 0.
+ */
+static BignumCarry mp_add_masked_into(
+    BignumInt *w_out, size_t rw, mp_int *a, mp_int *b,
+    BignumInt b_and, BignumInt b_xor, BignumCarry carry)
+{
+    size_t i; // WINSCP
+    for (i = 0; i < rw; i++) {
+        BignumInt aword = mp_word(a, i), bword = mp_word(b, i), out;
+        bword = (bword & b_and) ^ b_xor;
+        BignumADC(out, carry, aword, bword, carry);
+        if (w_out)
+            w_out[i] = out;
+    }
+    return carry;
+}
+
+/*
+ * Like the public mp_add_into except that it returns the output carry.
+ */
+static inline BignumCarry mp_add_into_internal(mp_int *r, mp_int *a, mp_int *b)
+{
+    return mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, 0, 0);
+}
+
+void mp_add_into(mp_int *r, mp_int *a, mp_int *b)
+{
+    mp_add_into_internal(r, a, b);
+}
+
+void mp_sub_into(mp_int *r, mp_int *a, mp_int *b)
+{
+    mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1);
+}
+
+void mp_and_into(mp_int *r, mp_int *a, mp_int *b)
+{
+    size_t i; // WINSCP
+    for (i = 0; i < r->nw; i++) {
+        BignumInt aword = mp_word(a, i), bword = mp_word(b, i);
+        r->w[i] = aword & bword;
+    }
+}
+
+void mp_or_into(mp_int *r, mp_int *a, mp_int *b)
+{
+    size_t i; // WINSCP
+    for (i = 0; i < r->nw; i++) {
+        BignumInt aword = mp_word(a, i), bword = mp_word(b, i);
+        r->w[i] = aword | bword;
+    }
+}
+
+void mp_xor_into(mp_int *r, mp_int *a, mp_int *b)
+{
+    size_t i; // WINSCP
+    for (i = 0; i < r->nw; i++) {
+        BignumInt aword = mp_word(a, i), bword = mp_word(b, i);
+        r->w[i] = aword ^ bword;
+    }
+}
+
+void mp_bic_into(mp_int *r, mp_int *a, mp_int *b)
+{
+    size_t i; // WINSCP
+    for (i = 0; i < r->nw; i++) {
+        BignumInt aword = mp_word(a, i), bword = mp_word(b, i);
+        r->w[i] = aword & ~bword;
+    }
+}
+
+static void mp_cond_negate(mp_int *r, mp_int *x, unsigned yes)
+{
+    BignumCarry carry = yes;
+    BignumInt flip = -(BignumInt)yes;
+    size_t i; // WINSCP
+    for (i = 0; i < r->nw; i++) {
+        BignumInt xword = mp_word(x, i);
+        xword ^= flip;
+        BignumADC(r->w[i], carry, 0, xword, carry);
+    }
+}
+
+/*
+ * Similar to mp_add_masked_into, but takes a C integer instead of an
+ * mp_int as the masked operand.
+ */
+static BignumCarry mp_add_masked_integer_into(
+    BignumInt *w_out, size_t rw, mp_int *a, uintmax_t b,
+    BignumInt b_and, BignumInt b_xor, BignumCarry carry)
+{
+    size_t i; // WINSCP
+    for (i = 0; i < rw; i++) {
+        BignumInt aword = mp_word(a, i);
+        BignumInt bword = b;
+        b = shift_right_by_one_word(b);
+        { // WINSCP
+        BignumInt out;
+        bword = (bword ^ b_xor) & b_and;
+        BignumADC(out, carry, aword, bword, carry);
+        if (w_out)
+            w_out[i] = out;
+        } // WINSCP
+    }
+    return carry;
+}
+
+void mp_add_integer_into(mp_int *r, mp_int *a, uintmax_t n)
+{
+    mp_add_masked_integer_into(r->w, r->nw, a, n, ~(BignumInt)0, 0, 0);
+}
+
+void mp_sub_integer_into(mp_int *r, mp_int *a, uintmax_t n)
+{
+    mp_add_masked_integer_into(r->w, r->nw, a, n,
+                               ~(BignumInt)0, ~(BignumInt)0, 1);
+}
+
+/*
+ * Sets r to a + n << (word_index * BIGNUM_INT_BITS), treating
+ * word_index as secret data.
+ */
+static void mp_add_integer_into_shifted_by_words(
+    mp_int *r, mp_int *a, uintmax_t n, size_t word_index)
+{
+    unsigned indicator = 0;
+    BignumCarry carry = 0;
+    size_t i; // WINSCP
+
+    for (i = 0; i < r->nw; i++) {
+        /* indicator becomes 1 when we reach the index that the least
+         * significant bits of n want to be placed at, and it stays 1
+         * thereafter. */
+        indicator |= 1 ^ normalise_to_1(i ^ word_index);
+
+        /* If indicator is 1, we add the low bits of n into r, and
+         * shift n down. If it's 0, we add zero bits into r, and
+         * leave n alone. */
+        { // WINSCP
+        BignumInt bword = n & -(BignumInt)indicator;
+        uintmax_t new_n = shift_right_by_one_word(n);
+        n ^= (n ^ new_n) & -(uintmax_t)indicator;
+
+        { // WINSCP
+        BignumInt aword = mp_word(a, i);
+        BignumInt out;
+        BignumADC(out, carry, aword, bword, carry);
+        r->w[i] = out;
+        } // WINSCP
+        } // WINSCP
+    }
+}
+
+void mp_mul_integer_into(mp_int *r, mp_int *a, uint16_t n)
+{
+    BignumInt carry = 0, mult = n;
+    size_t i; // WINSCP
+    for (i = 0; i < r->nw; i++) {
+        BignumInt aword = mp_word(a, i);
+        BignumMULADD(carry, r->w[i], aword, mult, carry);
+    }
+    assert(!carry);
+}
+
+void mp_cond_add_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes)
+{
+    BignumInt mask = -(BignumInt)(yes & 1);
+    mp_add_masked_into(r->w, r->nw, a, b, mask, 0, 0);
+}
+
+void mp_cond_sub_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes)
+{
+    BignumInt mask = -(BignumInt)(yes & 1);
+    mp_add_masked_into(r->w, r->nw, a, b, mask, mask, 1 & mask);
+}
+
+/*
+ * Ordered comparison between unsigned numbers is done by subtracting
+ * one from the other and looking at the output carry.
+ */
+unsigned mp_cmp_hs(mp_int *a, mp_int *b)
+{
+    size_t rw = size_t_max(a->nw, b->nw);
+    return mp_add_masked_into(NULL, rw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1);
+}
+
+unsigned mp_hs_integer(mp_int *x, uintmax_t n)
+{
+    BignumInt carry = 1;
+    size_t nwords = sizeof(n)/BIGNUM_INT_BYTES;
+    size_t i, e; // WINSCP
+    for (i = 0, e = size_t_max(x->nw, nwords); i < e; i++) {
+        BignumInt nword = n;
+        n = shift_right_by_one_word(n);
+        { // WINSCP
+        BignumInt dummy_out;
+        BignumADC(dummy_out, carry, mp_word(x, i), ~nword, carry);
+        (void)dummy_out;
+        } // WINSCP
+    }
+    return carry;
+}
+
+/*
+ * Equality comparison is done by bitwise XOR of the input numbers,
+ * ORing together all the output words, and normalising the result
+ * using our careful normalise_to_1 helper function.
+ */
+unsigned mp_cmp_eq(mp_int *a, mp_int *b)
+{
+    BignumInt diff = 0;
+    size_t i, limit; // WINSCP
+    for (i = 0, limit = size_t_max(a->nw, b->nw); i < limit; i++)
+        diff |= mp_word(a, i) ^ mp_word(b, i);
+    return 1 ^ normalise_to_1(diff);   /* return 1 if diff _is_ zero */
+}
+
+unsigned mp_eq_integer(mp_int *x, uintmax_t n)
+{
+    BignumInt diff = 0;
+    size_t nwords = sizeof(n)/BIGNUM_INT_BYTES;
+    size_t i, e; // WINSCP
+    for (i = 0, e = size_t_max(x->nw, nwords); i < e; i++) {
+        BignumInt nword = n;
+        n = shift_right_by_one_word(n);
+        diff |= mp_word(x, i) ^ nword;
+    }
+    return 1 ^ normalise_to_1(diff);   /* return 1 if diff _is_ zero */
+}
+
+static void mp_neg_into(mp_int *r, mp_int *a)
+{
+    mp_int zero;
+    zero.nw = 0;
+    mp_sub_into(r, &zero, a);
+}
+
+mp_int *mp_add(mp_int *x, mp_int *y)
+{
+    mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw) + 1);
+    mp_add_into(r, x, y);
+    return r;
+}
+
+mp_int *mp_sub(mp_int *x, mp_int *y)
+{
+    mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw));
+    mp_sub_into(r, x, y);
+    return r;
+}
+
+/*
+ * Internal routine: multiply and accumulate in the trivial O(N^2)
+ * way. Sets r <- r + a*b.
+ */
+static void mp_mul_add_simple(mp_int *r, mp_int *a, mp_int *b)
+{
+    BignumInt *aend = a->w + a->nw, *bend = b->w + b->nw, *rend = r->w + r->nw;
+
+    BignumInt *ap, *rp; // WINSCP
+    for (ap = a->w, rp = r->w;
+         ap < aend && rp < rend; ap++, rp++) {
+
+        BignumInt adata = *ap, carry = 0, *rq = rp;
+
+        { // WINSCP
+        BignumInt *bp; // WINSCP
+        for (bp = b->w; bp < bend && rq < rend; bp++, rq++) {
+            BignumInt bdata = bp < bend ? *bp : 0;
+            BignumMULADD2(carry, *rq, adata, bdata, *rq, carry);
+        }
+        } // WINSCP
+
+        for (; rq < rend; rq++)
+            BignumADC(*rq, carry, carry, *rq, 0);
+    }
+}
+
+#ifndef KARATSUBA_THRESHOLD      /* allow redefinition via -D for testing */
+#define KARATSUBA_THRESHOLD 24
+#endif
+
+static inline size_t mp_mul_scratchspace_unary(size_t n)
+{
+    /*
+     * Simplistic and overcautious bound on the amount of scratch
+     * space that the recursive multiply function will need.
+     *
+     * The rationale is: on the main Karatsuba branch of
+     * mp_mul_internal, which is the most space-intensive one, we
+     * allocate space for (a0+a1) and (b0+b1) (each just over half the
+     * input length n) and their product (the sum of those sizes, i.e.
+     * just over n itself). Then in order to actually compute the
+     * product, we do a recursive multiplication of size just over n.
+     *
+     * If all those 'just over' weren't there, and everything was
+     * _exactly_ half the length, you'd get the amount of space for a
+     * size-n multiply defined by the recurrence M(n) = 2n + M(n/2),
+     * which is satisfied by M(n) = 4n. But instead it's (2n plus a
+     * word or two) and M(n/2 plus a word or two). On the assumption
+     * that there's still some constant k such that M(n) <= kn, this
+     * gives us kn = 2n + w + k(n/2 + w), where w is a small constant
+     * (one or two words). That simplifies to kn/2 = 2n + (k+1)w, and
+     * since we don't even _start_ needing scratch space until n is at
+     * least 50, we can bound 2n + (k+1)w above by 3n, giving k=6.
+     *
+     * So I claim that 6n words of scratch space will suffice, and I
+     * check that by assertion at every stage of the recursion.
+     */
+    return n * 6;
+}
+
+static size_t mp_mul_scratchspace(size_t rw, size_t aw, size_t bw)
+{
+    size_t inlen = size_t_min(rw, size_t_max(aw, bw));
+    return mp_mul_scratchspace_unary(inlen);
+}
+
+static void mp_mul_internal(mp_int *r, mp_int *a, mp_int *b, mp_int scratch)
+{
+    size_t inlen = size_t_min(r->nw, size_t_max(a->nw, b->nw));
+    assert(scratch.nw >= mp_mul_scratchspace_unary(inlen));
+
+    mp_clear(r);
+
+    if (inlen < KARATSUBA_THRESHOLD || a->nw == 0 || b->nw == 0) {
+        /*
+         * The input numbers are too small to bother optimising. Go
+         * straight to the simple primitive approach.
+         */
+        mp_mul_add_simple(r, a, b);
+        return;
+    }
+
+    /*
+     * Karatsuba divide-and-conquer algorithm. We cut each input in
+     * half, so that it's expressed as two big 'digits' in a giant
+     * base D:
+     *
+     *   a = a_1 D + a_0
+     *   b = b_1 D + b_0
+     *
+     * Then the product is of course
+     *
+     *   ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0
+     *
+     * and we compute the three coefficients by recursively calling
+     * ourself to do half-length multiplications.
+     *
+     * The clever bit that makes this worth doing is that we only need
+     * _one_ half-length multiplication for the central coefficient
+     * rather than the two that it obviouly looks like, because we can
+     * use a single multiplication to compute
+     *
+     *   (a_1 + a_0) (b_1 + b_0) = a_1 b_1 + a_1 b_0 + a_0 b_1 + a_0 b_0
+     *
+     * and then we subtract the other two coefficients (a_1 b_1 and
+     * a_0 b_0) which we were computing anyway.
+     *
+     * Hence we get to multiply two numbers of length N in about three
+     * times as much work as it takes to multiply numbers of length
+     * N/2, which is obviously better than the four times as much work
+     * it would take if we just did a long conventional multiply.
+     */
+
+    { // WINSCP
+    /* Break up the input as botlen + toplen, with botlen >= toplen.
+     * The 'base' D is equal to 2^{botlen * BIGNUM_INT_BITS}. */
+    size_t toplen = inlen / 2;
+    size_t botlen = inlen - toplen;
+
+    /* Alias bignums that address the two halves of a,b, and useful
+     * pieces of r. */
+    mp_int a0 = mp_make_alias(a, 0, botlen);
+    mp_int b0 = mp_make_alias(b, 0, botlen);
+    mp_int a1 = mp_make_alias(a, botlen, toplen);
+    mp_int b1 = mp_make_alias(b, botlen, toplen);
+    mp_int r0 = mp_make_alias(r, 0, botlen*2);
+    mp_int r1 = mp_make_alias(r, botlen, r->nw);
+    mp_int r2 = mp_make_alias(r, botlen*2, r->nw);
+
+    /* Recurse to compute a0*b0 and a1*b1, in their correct positions
+     * in the output bignum. They can't overlap. */
+    mp_mul_internal(&r0, &a0, &b0, scratch);
+    mp_mul_internal(&r2, &a1, &b1, scratch);
+
+    if (r->nw < inlen*2) {
+        /*
+         * The output buffer isn't large enough to require the whole
+         * product, so some of a1*b1 won't have been stored. In that
+         * case we won't try to do the full Karatsuba optimisation;
+         * we'll just recurse again to compute a0*b1 and a1*b0 - or at
+         * least as much of them as the output buffer size requires -
+         * and add each one in.
+         */
+        mp_int s = mp_alloc_from_scratch(
+            &scratch, size_t_min(botlen+toplen, r1.nw));
+
+        mp_mul_internal(&s, &a0, &b1, scratch);
+        mp_add_into(&r1, &r1, &s);
+        mp_mul_internal(&s, &a1, &b0, scratch);
+        mp_add_into(&r1, &r1, &s);
+        return;
+    }
+
+    { // WINSCP
+    /* a0+a1 and b0+b1 */
+    mp_int asum = mp_alloc_from_scratch(&scratch, botlen+1);
+    mp_int bsum = mp_alloc_from_scratch(&scratch, botlen+1);
+    mp_add_into(&asum, &a0, &a1);
+    mp_add_into(&bsum, &b0, &b1);
+
+    { // WINSCP
+    /* Their product */
+    mp_int product = mp_alloc_from_scratch(&scratch, botlen*2+1);
+    mp_mul_internal(&product, &asum, &bsum, scratch);
+
+    /* Subtract off the outer terms we already have */
+    mp_sub_into(&product, &product, &r0);
+    mp_sub_into(&product, &product, &r2);
+
+    /* And add it in with the right offset. */
+    mp_add_into(&r1, &r1, &product);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+void mp_mul_into(mp_int *r, mp_int *a, mp_int *b)
+{
+    mp_int *scratch = mp_make_sized(mp_mul_scratchspace(r->nw, a->nw, b->nw));
+    mp_mul_internal(r, a, b, *scratch);
+    mp_free(scratch);
+}
+
+mp_int *mp_mul(mp_int *x, mp_int *y)
+{
+    mp_int *r = mp_make_sized(x->nw + y->nw);
+    mp_mul_into(r, x, y);
+    return r;
+}
+
+void mp_lshift_fixed_into(mp_int *r, mp_int *a, size_t bits)
+{
+    size_t words = bits / BIGNUM_INT_BITS;
+    size_t bitoff = bits % BIGNUM_INT_BITS;
+
+    size_t i; // WINSCP
+    for (i = r->nw; i-- > 0 ;) {
+        if (i < words) {
+            r->w[i] = 0;
+        } else {
+            r->w[i] = mp_word(a, i - words);
+            if (bitoff != 0) {
+                r->w[i] <<= bitoff;
+                if (i > words)
+                    r->w[i] |= mp_word(a, i - words - 1) >>
+                        (BIGNUM_INT_BITS - bitoff);
+            }
+        }
+    }
+}
+
+void mp_rshift_fixed_into(mp_int *r, mp_int *a, size_t bits)
+{
+    size_t words = bits / BIGNUM_INT_BITS;
+    size_t bitoff = bits % BIGNUM_INT_BITS;
+
+    size_t i; // WINSCP
+    for (i = 0; i < r->nw; i++) {
+        r->w[i] = mp_word(a, i + words);
+        if (bitoff != 0) {
+            r->w[i] >>= bitoff;
+            r->w[i] |= mp_word(a, i + words + 1) << (BIGNUM_INT_BITS - bitoff);
+        }
+    }
+}
+
+mp_int *mp_lshift_fixed(mp_int *x, size_t bits)
+{
+    size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+    mp_int *r = mp_make_sized(x->nw + words);
+    mp_lshift_fixed_into(r, x, bits);
+    return r;
+}
+
+mp_int *mp_rshift_fixed(mp_int *x, size_t bits)
+{
+    size_t words = bits / BIGNUM_INT_BITS;
+    size_t nw = x->nw - size_t_min(x->nw, words);
+    mp_int *r = mp_make_sized(size_t_max(nw, 1));
+    mp_rshift_fixed_into(r, x, bits);
+    return r;
+}
+
+/*
+ * Safe right shift is done using the same technique as
+ * trim_leading_zeroes above: you make an n-word left shift by
+ * composing an appropriate subset of power-of-2-sized shifts, so it
+ * takes log_2(n) loop iterations each of which does a different shift
+ * by a power of 2 words, using the usual bit twiddling to make the
+ * whole shift conditional on the appropriate bit of n.
+ */
+static void mp_rshift_safe_in_place(mp_int *r, size_t bits)
+{
+    size_t wordshift = bits / BIGNUM_INT_BITS;
+    size_t bitshift = bits % BIGNUM_INT_BITS;
+
+    unsigned bit; // WINSCP
+    unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1);
+    mp_cond_clear(r, clear);
+
+    for (bit = 0; r->nw >> bit; bit++) {
+        size_t word_offset = (size_t)1 << bit;
+        BignumInt mask = -(BignumInt)((wordshift >> bit) & 1);
+        size_t i; // WINSCP
+        for (i = 0; i < r->nw; i++) {
+            BignumInt w = mp_word(r, i + word_offset);
+            r->w[i] ^= (r->w[i] ^ w) & mask;
+        }
+    }
+
+    /*
+     * That's done the shifting by words; now we do the shifting by
+     * bits.
+     */
+    for (bit = 0; bit < BIGNUM_INT_BITS_BITS; bit++) { // WINSCP
+        unsigned shift = 1 << bit, upshift = BIGNUM_INT_BITS - shift;
+        BignumInt mask = -(BignumInt)((bitshift >> bit) & 1);
+        size_t i; // WINSCP
+        for (i = 0; i < r->nw; i++) {
+            BignumInt w = ((r->w[i] >> shift) | (mp_word(r, i+1) << upshift));
+            r->w[i] ^= (r->w[i] ^ w) & mask;
+        }
+    }
+}
+
+mp_int *mp_rshift_safe(mp_int *x, size_t bits)
+{
+    mp_int *r = mp_copy(x);
+    mp_rshift_safe_in_place(r, bits);
+    return r;
+}
+
+void mp_rshift_safe_into(mp_int *r, mp_int *x, size_t bits)
+{
+    mp_copy_into(r, x);
+    mp_rshift_safe_in_place(r, bits);
+}
+
+static void mp_lshift_safe_in_place(mp_int *r, size_t bits)
+{
+    size_t wordshift = bits / BIGNUM_INT_BITS;
+    size_t bitshift = bits % BIGNUM_INT_BITS;
+
+    /*
+     * Same strategy as mp_rshift_safe_in_place, but of course the
+     * other way up.
+     */
+
+    unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1);
+    mp_cond_clear(r, clear);
+
+    { // WINSCP
+    unsigned bit; // WINSCP
+    for (bit = 0; r->nw >> bit; bit++) {
+        size_t word_offset = (size_t)1 << bit;
+        BignumInt mask = -(BignumInt)((wordshift >> bit) & 1);
+        size_t i; // WINSCP
+        for (i = r->nw; i-- > 0 ;) {
+            BignumInt w = mp_word(r, i - word_offset);
+            r->w[i] ^= (r->w[i] ^ w) & mask;
+        }
+    }
+
+    { // WINSCP
+    size_t downshift = BIGNUM_INT_BITS - bitshift;
+    size_t no_shift = (downshift >> BIGNUM_INT_BITS_BITS);
+    downshift &= ~-(size_t)no_shift;
+    { // WINSCP
+    BignumInt downshifted_mask = ~-(BignumInt)no_shift;
+
+    size_t i; // WINSCP
+    for (i = r->nw; i-- > 0 ;) {
+        r->w[i] = (r->w[i] << bitshift) |
+            ((mp_word(r, i-1) >> downshift) & downshifted_mask);
+    }
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+void mp_lshift_safe_into(mp_int *r, mp_int *x, size_t bits)
+{
+    mp_copy_into(r, x);
+    mp_lshift_safe_in_place(r, bits);
+}
+
+void mp_reduce_mod_2to(mp_int *x, size_t p)
+{
+    size_t word = p / BIGNUM_INT_BITS;
+    size_t mask = ((size_t)1 << (p % BIGNUM_INT_BITS)) - 1;
+    for (; word < x->nw; word++) {
+        x->w[word] &= mask;
+        mask = 0;
+    }
+}
+
+/*
+ * Inverse mod 2^n is computed by an iterative technique which doubles
+ * the number of bits at each step.
+ */
+mp_int *mp_invert_mod_2to(mp_int *x, size_t p)
+{
+    /* Input checks: x must be coprime to the modulus, i.e. odd, and p
+     * can't be zero */
+    assert(x->nw > 0);
+    assert(x->w[0] & 1);
+    assert(p > 0);
+
+    { // WINSCP
+    size_t rw = (p + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+    rw = size_t_max(rw, 1);
+    { // WINSCP
+    mp_int *r = mp_make_sized(rw);
+
+    size_t mul_scratchsize = mp_mul_scratchspace(2*rw, rw, rw);
+    mp_int *scratch_orig = mp_make_sized(6 * rw + mul_scratchsize);
+    mp_int scratch_per_iter = *scratch_orig;
+    mp_int mul_scratch = mp_alloc_from_scratch(
+        &scratch_per_iter, mul_scratchsize);
+    size_t b; // WINSCP
+
+    r->w[0] = 1;
+
+    for (b = 1; b < p; b <<= 1) {
+        /*
+         * In each step of this iteration, we have the inverse of x
+         * mod 2^b, and we want the inverse of x mod 2^{2b}.
+         *
+         * Write B = 2^b for convenience, so we want x^{-1} mod B^2.
+         * Let x = x_0 + B x_1 + k B^2, with 0 <= x_0,x_1 < B.
+         *
+         * We want to find r_0 and r_1 such that
+         *    (r_1 B + r_0) (x_1 B + x_0) == 1 (mod B^2)
+         *
+         * To begin with, we know r_0 must be the inverse mod B of
+         * x_0, i.e. of x, i.e. it is the inverse we computed in the
+         * previous iteration. So now all we need is r_1.
+         *
+         * Multiplying out, neglecting multiples of B^2, and writing
+         * x_0 r_0 = K B + 1, we have
+         *
+         *    r_1 x_0 B + r_0 x_1 B + K B == 0                    (mod B^2)
+         * =>                   r_1 x_0 B == - r_0 x_1 B - K B    (mod B^2)
+         * =>                     r_1 x_0 == - r_0 x_1 - K        (mod B)
+         * =>                         r_1 == r_0 (- r_0 x_1 - K)  (mod B)
+         *
+         * (the last step because we multiply through by the inverse
+         * of x_0, which we already know is r_0).
+         */
+
+        mp_int scratch_this_iter = scratch_per_iter;
+        size_t Bw = (b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+        size_t B2w = (2*b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+
+        /* Start by finding K: multiply x_0 by r_0, and shift down. */
+        mp_int x0 = mp_alloc_from_scratch(&scratch_this_iter, Bw);
+        mp_copy_into(&x0, x);
+        mp_reduce_mod_2to(&x0, b);
+        { // WINSCP
+        mp_int r0 = mp_make_alias(r, 0, Bw);
+        mp_int Kshift = mp_alloc_from_scratch(&scratch_this_iter, B2w);
+        mp_mul_internal(&Kshift, &x0, &r0, mul_scratch);
+        { // WINSCP
+        mp_int K = mp_alloc_from_scratch(&scratch_this_iter, Bw);
+        mp_rshift_fixed_into(&K, &Kshift, b);
+
+        /* Now compute the product r_0 x_1, reusing the space of Kshift. */
+        { // WINSCP
+        mp_int x1 = mp_alloc_from_scratch(&scratch_this_iter, Bw);
+        mp_rshift_fixed_into(&x1, x, b);
+        mp_reduce_mod_2to(&x1, b);
+        { // WINSCP
+        mp_int r0x1 = mp_make_alias(&Kshift, 0, Bw);
+        mp_mul_internal(&r0x1, &r0, &x1, mul_scratch);
+
+        /* Add K to that. */
+        mp_add_into(&r0x1, &r0x1, &K);
+
+        /* Negate it. */
+        mp_neg_into(&r0x1, &r0x1);
+
+        /* Multiply by r_0. */
+        { // WINSCP
+        mp_int r1 = mp_alloc_from_scratch(&scratch_this_iter, Bw);
+        mp_mul_internal(&r1, &r0, &r0x1, mul_scratch);
+        mp_reduce_mod_2to(&r1, b);
+
+        /* That's our r_1, so add it on to r_0 to get the full inverse
+         * output from this iteration. */
+        mp_lshift_fixed_into(&K, &r1, (b % BIGNUM_INT_BITS));
+        { // WINSCP
+        size_t Bpos = b / BIGNUM_INT_BITS;
+        mp_int r1_position = mp_make_alias(r, Bpos, B2w-Bpos);
+        mp_add_into(&r1_position, &r1_position, &K);
+        } // WINSCP
+        } // WINSCP
+        } // WINSCP
+        } // WINSCP
+        } // WINSCP
+        } // WINSCP
+    }
+
+    /* Finally, reduce mod the precise desired number of bits. */
+    mp_reduce_mod_2to(r, p);
+
+    mp_free(scratch_orig);
+    return r;
+    } // WINSCP
+    } // WINSCP
+}
+
+static size_t monty_scratch_size(MontyContext *mc)
+{
+    return 3*mc->rw + mc->pw + mp_mul_scratchspace(mc->pw, mc->rw, mc->rw);
+}
+
+MontyContext *monty_new(mp_int *modulus)
+{
+    MontyContext *mc = snew(MontyContext);
+
+    mc->rw = modulus->nw;
+    mc->rbits = mc->rw * BIGNUM_INT_BITS;
+    mc->pw = mc->rw * 2 + 1;
+
+    mc->m = mp_copy(modulus);
+
+    mc->minus_minv_mod_r = mp_invert_mod_2to(mc->m, mc->rbits);
+    mp_neg_into(mc->minus_minv_mod_r, mc->minus_minv_mod_r);
+
+    { // WINSCP
+    size_t j; // WINSCP
+    mp_int *r = mp_make_sized(mc->rw + 1);
+    r->w[mc->rw] = 1;
+    mc->powers_of_r_mod_m[0] = mp_mod(r, mc->m);
+    mp_free(r);
+
+    for (j = 1; j < lenof(mc->powers_of_r_mod_m); j++)
+        mc->powers_of_r_mod_m[j] = mp_modmul(
+            mc->powers_of_r_mod_m[0], mc->powers_of_r_mod_m[j-1], mc->m);
+
+    mc->scratch = mp_make_sized(monty_scratch_size(mc));
+
+    return mc;
+    } // WINSCP
+}
+
+void monty_free(MontyContext *mc)
+{
+    size_t j; // WINSCP
+    mp_free(mc->m);
+    for (j = 0; j < 3; j++)
+        mp_free(mc->powers_of_r_mod_m[j]);
+    mp_free(mc->minus_minv_mod_r);
+    mp_free(mc->scratch);
+    smemclr(mc, sizeof(*mc));
+    sfree(mc);
+}
+
+/*
+ * The main Montgomery reduction step.
+ */
+static mp_int monty_reduce_internal(MontyContext *mc, mp_int *x, mp_int scratch)
+{
+    /*
+     * The trick with Montgomery reduction is that on the one hand we
+     * want to reduce the size of the input by a factor of about r,
+     * and on the other hand, the two numbers we just multiplied were
+     * both stored with an extra factor of r multiplied in. So we
+     * computed ar*br = ab r^2, but we want to return abr, so we need
+     * to divide by r - and if we can do that by _actually dividing_
+     * by r then this also reduces the size of the number.
+     *
+     * But we can only do that if the number we're dividing by r is a
+     * multiple of r. So first we must add an adjustment to it which
+     * clears its bottom 'rbits' bits. That adjustment must be a
+     * multiple of m in order to leave the residue mod n unchanged, so
+     * the question is, what multiple of m can we add to x to make it
+     * congruent to 0 mod r? And the answer is, x * (-m)^{-1} mod r.
+     */
+
+    /* x mod r */
+    mp_int x_lo = mp_make_alias(x, 0, mc->rbits);
+
+    /* x * (-m)^{-1}, i.e. the number we want to multiply by m */
+    mp_int k = mp_alloc_from_scratch(&scratch, mc->rw);
+    mp_mul_internal(&k, &x_lo, mc->minus_minv_mod_r, scratch);
+
+    /* m times that, i.e. the number we want to add to x */
+    { // WINSCP
+    mp_int mk = mp_alloc_from_scratch(&scratch, mc->pw);
+    mp_mul_internal(&mk, mc->m, &k, scratch);
+
+    /* Add it to x */
+    mp_add_into(&mk, x, &mk);
+
+    /* Reduce mod r, by simply making an alias to the upper words of x */
+    { // WINSCP
+    mp_int toret = mp_make_alias(&mk, mc->rw, mk.nw - mc->rw);
+
+    /*
+     * We'll generally be doing this after a multiplication of two
+     * fully reduced values. So our input could be anything up to m^2,
+     * and then we added up to rm to it. Hence, the maximum value is
+     * rm+m^2, and after dividing by r, that becomes r + m(m/r) < 2r.
+     * So a single trial-subtraction will finish reducing to the
+     * interval [0,m).
+     */
+    mp_cond_sub_into(&toret, &toret, mc->m, mp_cmp_hs(&toret, mc->m));
+    return toret;
+    } // WINSCP
+    } // WINSCP
+}
+
+void monty_mul_into(MontyContext *mc, mp_int *r, mp_int *x, mp_int *y)
+{
+    assert(x->nw <= mc->rw);
+    assert(y->nw <= mc->rw);
+
+    { // WINSCP
+    mp_int scratch = *mc->scratch;
+    mp_int tmp = mp_alloc_from_scratch(&scratch, 2*mc->rw);
+    mp_mul_into(&tmp, x, y);
+    { // WINSCP
+    mp_int reduced = monty_reduce_internal(mc, &tmp, scratch);
+    mp_copy_into(r, &reduced);
+    mp_clear(mc->scratch);
+    } // WINSCP
+    } // WINSCP
+}
+
+mp_int *monty_mul(MontyContext *mc, mp_int *x, mp_int *y)
+{
+    mp_int *toret = mp_make_sized(mc->rw);
+    monty_mul_into(mc, toret, x, y);
+    return toret;
+}
+
+mp_int *monty_modulus(MontyContext *mc)
+{
+    return mc->m;
+}
+
+mp_int *monty_identity(MontyContext *mc)
+{
+    return mc->powers_of_r_mod_m[0];
+}
+
+mp_int *monty_invert(MontyContext *mc, mp_int *x)
+{
+    /* Given xr, we want to return x^{-1}r = (xr)^{-1} r^2 =
+     * monty_reduce((xr)^{-1} r^3) */
+    mp_int *tmp = mp_invert(x, mc->m);
+    mp_int *toret = monty_mul(mc, tmp, mc->powers_of_r_mod_m[2]);
+    mp_free(tmp);
+    return toret;
+}
+
+/*
+ * Importing a number into Montgomery representation involves
+ * multiplying it by r and reducing mod m. We use the general-purpose
+ * mp_modmul for this, in case the input number is out of range.
+ */
+mp_int *monty_import(MontyContext *mc, mp_int *x)
+{
+    return mp_modmul(x, mc->powers_of_r_mod_m[0], mc->m);
+}
+
+void monty_import_into(MontyContext *mc, mp_int *r, mp_int *x)
+{
+    mp_int *imported = monty_import(mc, x);
+    mp_copy_into(r, imported);
+    mp_free(imported);
+}
+
+/*
+ * Exporting a number means multiplying it by r^{-1}, which is exactly
+ * what monty_reduce does anyway, so we just do that.
+ */
+void monty_export_into(MontyContext *mc, mp_int *r, mp_int *x)
+{
+    pinitassert(x->nw <= 2*mc->rw);
+    mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch);
+    mp_copy_into(r, &reduced);
+    mp_clear(mc->scratch);
+}
+
+mp_int *monty_export(MontyContext *mc, mp_int *x)
+{
+    mp_int *toret = mp_make_sized(mc->rw);
+    monty_export_into(mc, toret, x);
+    return toret;
+}
+
+#define MODPOW_LOG2_WINDOW_SIZE 5
+#define MODPOW_WINDOW_SIZE (1 << MODPOW_LOG2_WINDOW_SIZE)
+mp_int *monty_pow(MontyContext *mc, mp_int *base, mp_int *exponent)
+{
+    /*
+     * Modular exponentiation is done from the top down, using a
+     * fixed-window technique.
+     *
+     * We have a table storing every power of the base from base^0 up
+     * to base^{w-1}, where w is a small power of 2, say 2^k. (k is
+     * defined above as MODPOW_LOG2_WINDOW_SIZE, and w = 2^k is
+     * defined as MODPOW_WINDOW_SIZE.)
+     *
+     * We break the exponent up into k-bit chunks, from the bottom up,
+     * that is
+     *
+     *   exponent = c_0 + 2^k c_1 + 2^{2k} c_2 + ... + 2^{nk} c_n
+     *
+     * and we compute base^exponent by computing in turn
+     *
+     *   base^{c_n}
+     *   base^{2^k c_n + c_{n-1}}
+     *   base^{2^{2k} c_n + 2^k c_{n-1} + c_{n-2}}
+     *   ...
+     *
+     * where each line is obtained by raising the previous line to the
+     * power 2^k (i.e. squaring it k times) and then multiplying in
+     * a value base^{c_i}, which we can look up in our table.
+     *
+     * Side-channel considerations: the exponent is secret, so
+     * actually doing a single table lookup by using a chunk of
+     * exponent bits as an array index would be an obvious leak of
+     * secret information into the cache. So instead, in each
+     * iteration, we read _all_ the table entries, and do a sequence
+     * of mp_select operations to leave just the one we wanted in the
+     * variable that will go into the multiplication. In other
+     * contexts (like software AES) that technique is so prohibitively
+     * slow that it makes you choose a strategy that doesn't use table
+     * lookups at all (we do bitslicing in preference); but here, this
+     * iteration through 2^k table elements is replacing k-1 bignum
+     * _multiplications_ that you'd have to use instead if you did
+     * simple square-and-multiply, and that makes it still a win.
+     */
+
+    /* Table that holds base^0, ..., base^{w-1} */
+    mp_int *table[MODPOW_WINDOW_SIZE];
+    table[0] = mp_copy(monty_identity(mc));
+    { // WINSCP
+    size_t i;
+    for (i = 1; i < MODPOW_WINDOW_SIZE; i++)
+        table[i] = monty_mul(mc, table[i-1], base);
+
+    /* out accumulates the output value */
+    { // WINSCP
+    mp_int *out = mp_make_sized(mc->rw);
+    mp_copy_into(out, monty_identity(mc));
+
+    /* table_entry will hold each value we get out of the table */
+    { // WINSCP
+    mp_int *table_entry = mp_make_sized(mc->rw);
+
+    /* Bit index of the chunk of bits we're working on. Start with the
+     * highest multiple of k strictly less than the size of our
+     * bignum, i.e. the highest-index chunk of bits that might
+     * conceivably contain any nonzero bit. */
+    { // WINSCP
+    size_t i = (exponent->nw * BIGNUM_INT_BITS) - 1;
+    i -= i % MODPOW_LOG2_WINDOW_SIZE;
+
+    { // WINSCP
+    bool first_iteration = true;
+
+    while (true) {
+        /* Construct the table index */
+        unsigned table_index = 0;
+        { // WINSCP
+        size_t j;
+        for (j = 0; j < MODPOW_LOG2_WINDOW_SIZE; j++)
+            table_index |= mp_get_bit(exponent, i+j) << j;
+
+        /* Iterate through the table to do a side-channel-safe lookup,
+         * ending up with table_entry = table[table_index] */
+        mp_copy_into(table_entry, table[0]);
+        { // WINSCP
+        size_t j;
+        for (j = 1; j < MODPOW_WINDOW_SIZE; j++) {
+            unsigned not_this_one =
+                ((table_index ^ j) + MODPOW_WINDOW_SIZE - 1)
+                >> MODPOW_LOG2_WINDOW_SIZE;
+            mp_select_into(table_entry, table[j], table_entry, not_this_one);
+        }
+
+        if (!first_iteration) {
+            /* Multiply into the output */
+            monty_mul_into(mc, out, out, table_entry);
+        } else {
+            /* On the first iteration, we can save one multiplication
+             * by just copying */
+            mp_copy_into(out, table_entry);
+            first_iteration = false;
+        }
+
+        /* If that was the bottommost chunk of bits, we're done */
+        if (i == 0)
+            break;
+
+        /* Otherwise, square k times and go round again. */
+        { // WINSCP
+        size_t j;
+        for (j = 0; j < MODPOW_LOG2_WINDOW_SIZE; j++)
+            monty_mul_into(mc, out, out, out);
+
+        i-= MODPOW_LOG2_WINDOW_SIZE;
+        } // WINSCP
+        } // WINSCP
+        } // WINSCP
+    }
+
+    { // WINSCP
+    size_t i;
+    for (i = 0; i < MODPOW_WINDOW_SIZE; i++)
+        mp_free(table[i]);
+    mp_free(table_entry);
+    mp_clear(mc->scratch);
+    return out;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus)
+{
+    assert(modulus->nw > 0);
+    assert(modulus->w[0] & 1);
+
+    { // WINSCP
+    MontyContext *mc = monty_new(modulus);
+    mp_int *m_base = monty_import(mc, base);
+    mp_int *m_out = monty_pow(mc, m_base, exponent);
+    mp_int *out = monty_export(mc, m_out);
+    mp_free(m_base);
+    mp_free(m_out);
+    monty_free(mc);
+    return out;
+    } // WINSCP
+}
+
+/*
+ * Given two input integers a,b which are not both even, computes d =
+ * gcd(a,b) and also two integers A,B such that A*a - B*b = d. A,B
+ * will be the minimal non-negative pair satisfying that criterion,
+ * which is equivalent to saying that 0 <= A < b/d and 0 <= B < a/d.
+ *
+ * This algorithm is an adapted form of Stein's algorithm, which
+ * computes gcd(a,b) using only addition and bit shifts (i.e. without
+ * needing general division), using the following rules:
+ *
+ *  - if both of a,b are even, divide off a common factor of 2
+ *  - if one of a,b (WLOG a) is even, then gcd(a,b) = gcd(a/2,b), so
+ *    just divide a by 2
+ *  - if both of a,b are odd, then WLOG a>b, and gcd(a,b) =
+ *    gcd(b,(a-b)/2).
+ *
+ * Sometimes this function is used for modular inversion, in which
+ * case we already know we expect the two inputs to be coprime, so to
+ * save time the 'both even' initial case is assumed not to arise (or
+ * to have been handled already by the caller). So this function just
+ * performs a sequence of reductions in the following form:
+ *
+ *  - if a,b are both odd, sort them so that a > b, and replace a with
+ *    b-a; otherwise sort them so that a is the even one
+ *  - either way, now a is even and b is odd, so divide a by 2.
+ *
+ * The big change to Stein's algorithm is that we need the Bezout
+ * coefficients as output, not just the gcd. So we need to know how to
+ * generate those in each case, based on the coefficients from the
+ * reduced pair of numbers:
+ *
+ *  - If a is even, and u,v are such that u*(a/2) + v*b = d:
+ *     + if u is also even, then this is just (u/2)*a + v*b = d
+ *     + otherwise, (u+b)*(a/2) + (v-a/2)*b is also equal to d, and
+ *       since u and b are both odd, (u+b)/2 is an integer, so we have
+ *       ((u+b)/2)*a + (v-a/2)*b = d.
+ *
+ *  - If a,b are both odd, and u,v are such that u*b + v*(a-b) = d,
+ *    then v*a + (u-v)*b = d.
+ *
+ * In the case where we passed from (a,b) to (b,(a-b)/2), we regard it
+ * as having first subtracted b from a and then halved a, so both of
+ * these transformations must be done in sequence.
+ *
+ * The code below transforms this from a recursive to an iterative
+ * algorithm. We first reduce a,b to 0,1, recording at each stage
+ * whether we did the initial subtraction, and whether we had to swap
+ * the two values; then we iterate backwards over that record of what
+ * we did, applying the above rules for building up the Bezout
+ * coefficients as we go. Of course, all the case analysis is done by
+ * the usual bit-twiddling conditionalisation to avoid data-dependent
+ * control flow.
+ *
+ * Also, since these mp_ints are generally treated as unsigned, we
+ * store the coefficients by absolute value, with the semantics that
+ * they always have opposite sign, and in the unwinding loop we keep a
+ * bit indicating whether Aa-Bb is currently expected to be +d or -d,
+ * so that we can do one final conditional adjustment if it's -d.
+ *
+ * Once the reduction rules have managed to reduce the input numbers
+ * to (0,d), then they are stable (the next reduction will always
+ * divide the even one by 2, which maps 0 to 0). So it doesn't matter
+ * if we do more steps of the algorithm than necessary; hence, for
+ * constant time, we just need to find the maximum number we could
+ * _possibly_ require, and do that many.
+ *
+ * If a,b < 2^n, at most 2n iterations are required. Proof: consider
+ * the quantity Q = log_2(a) + log_2(b). Every step halves one of the
+ * numbers (and may also reduce one of them further by doing a
+ * subtraction beforehand, but in the worst case, not by much or not
+ * at all). So Q reduces by at least 1 per iteration, and it starts
+ * off with a value at most 2n.
+ *
+ * The worst case inputs (I think) are where x=2^{n-1} and y=2^n-1
+ * (i.e. x is a power of 2 and y is all 1s). In that situation, the
+ * first n-1 steps repeatedly halve x until it's 1, and then there are
+ * n further steps each of which subtracts 1 from y and halves it.
+ */
+static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out,
+                           mp_int *gcd_out, mp_int *a_in, mp_int *b_in)
+{
+    size_t nw = size_t_max(1, size_t_max(a_in->nw, b_in->nw));
+
+    /* Make mutable copies of the input numbers */
+    mp_int *a = mp_make_sized(nw), *b = mp_make_sized(nw);
+    mp_copy_into(a, a_in);
+    mp_copy_into(b, b_in);
+
+    /* Space to build up the output coefficients, with an extra word
+     * so that intermediate values can overflow off the top and still
+     * right-shift back down to the correct value */
+    { // WINSCP
+    mp_int *ac = mp_make_sized(nw + 1), *bc = mp_make_sized(nw + 1);
+
+    /* And a general-purpose temp register */
+    mp_int *tmp = mp_make_sized(nw);
+
+    /* Space to record the sequence of reduction steps to unwind. We
+     * make it a BignumInt for no particular reason except that (a)
+     * mp_make_sized conveniently zeroes the allocation and mp_free
+     * wipes it, and (b) this way I can use mp_dump() if I have to
+     * debug this code. */
+    size_t steps = 2 * nw * BIGNUM_INT_BITS;
+    mp_int *record = mp_make_sized(
+        (steps*2 + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS);
+    size_t step; // WINSCP
+
+    for (step = 0; step < steps; step++) {
+        /*
+         * If a and b are both odd, we want to sort them so that a is
+         * larger. But if one is even, we want to sort them so that a
+         * is the even one.
+         */
+        unsigned swap_if_both_odd = mp_cmp_hs(b, a);
+        unsigned swap_if_one_even = a->w[0] & 1;
+        unsigned both_odd = a->w[0] & b->w[0] & 1;
+        unsigned swap = swap_if_one_even ^ (
+            (swap_if_both_odd ^ swap_if_one_even) & both_odd);
+
+        mp_cond_swap(a, b, swap);
+
+        /*
+         * If a,b are both odd, then a is the larger number, so
+         * subtract the smaller one from it.
+         */
+        mp_cond_sub_into(a, a, b, both_odd);
+
+        /*
+         * Now a is even, so divide it by two.
+         */
+        mp_rshift_fixed_into(a, a, 1);
+
+        /*
+         * Record the two 1-bit values both_odd and swap.
+         */
+        mp_set_bit(record, step*2, both_odd);
+        mp_set_bit(record, step*2+1, swap);
+    }
+
+    /*
+     * Now we expect to have reduced the two numbers to 0 and d,
+     * although we don't know which way round. (But we avoid checking
+     * this by assertion; sometimes we'll need to do this computation
+     * without giving away that we already know the inputs were bogus.
+     * So we'd prefer to just press on and return nonsense.)
+     */
+
+    if (gcd_out) {
+        /*
+         * At this point we can return the actual gcd. Since one of
+         * a,b is it and the other is zero, the easiest way to get it
+         * is to add them together.
+         */
+        mp_add_into(gcd_out, a, b);
+    }
+
+    /*
+     * If the caller _only_ wanted the gcd, and neither Bezout
+     * coefficient is even required, we can skip the entire unwind
+     * stage.
+     */
+    if (a_coeff_out || b_coeff_out) {
+
+        /*
+         * The Bezout coefficients of a,b at this point are simply 0
+         * for whichever of a,b is zero, and 1 for whichever is
+         * nonzero. The nonzero number equals gcd(a,b), which by
+         * assumption is odd, so we can do this by just taking the low
+         * bit of each one.
+         */
+        ac->w[0] = mp_get_bit(a, 0);
+        bc->w[0] = mp_get_bit(b, 0);
+
+        /*
+         * Overwrite a,b themselves with those same numbers. This has
+         * the effect of dividing both of them by d, which will
+         * arrange that during the unwind stage we generate the
+         * minimal coefficients instead of a larger pair.
+         */
+        mp_copy_into(a, ac);
+        mp_copy_into(b, bc);
+
+        /*
+         * We'll maintain the invariant as we unwind that ac * a - bc
+         * * b is either +d or -d (or rather, +1/-1 after scaling by
+         * d), and we'll remember which. (We _could_ keep it at +d the
+         * whole time, but it would cost more work every time round
+         * the loop, so it's cheaper to fix that up once at the end.)
+         *
+         * Initially, the result is +d if a was the nonzero value after
+         * reduction, and -d if b was.
+         */
+        { // WINSCP
+        unsigned minus_d = b->w[0];
+
+        size_t step; // WINSCP
+        for (step = steps; step-- > 0 ;) {
+            /*
+             * Recover the data from the step we're unwinding.
+             */
+            unsigned both_odd = mp_get_bit(record, step*2);
+            unsigned swap = mp_get_bit(record, step*2+1);
+
+            /*
+             * Unwind the division: if our coefficient of a is odd, we
+             * adjust the coefficients by +b and +a respectively.
+             */
+            unsigned adjust = ac->w[0] & 1;
+            mp_cond_add_into(ac, ac, b, adjust);
+            mp_cond_add_into(bc, bc, a, adjust);
+
+            /*
+             * Now ac is definitely even, so we divide it by two.
+             */
+            mp_rshift_fixed_into(ac, ac, 1);
+
+            /*
+             * Now unwind the subtraction, if there was one, by adding
+             * ac to bc.
+             */
+            mp_cond_add_into(bc, bc, ac, both_odd);
+
+            /*
+             * Undo the transformation of the input numbers, by
+             * multiplying a by 2 and then adding b to a (the latter
+             * only if both_odd).
+             */
+            mp_lshift_fixed_into(a, a, 1);
+            mp_cond_add_into(a, a, b, both_odd);
+
+            /*
+             * Finally, undo the swap. If we do swap, this also
+             * reverses the sign of the current result ac*a+bc*b.
+             */
+            mp_cond_swap(a, b, swap);
+            mp_cond_swap(ac, bc, swap);
+            minus_d ^= swap;
+        }
+
+        /*
+         * Now we expect to have recovered the input a,b (or rather,
+         * the versions of them divided by d). But we might find that
+         * our current result is -d instead of +d, that is, we have
+         * A',B' such that A'a - B'b = -d.
+         *
+         * In that situation, we set A = b-A' and B = a-B', giving us
+         * Aa-Bb = ab - A'a - ab + B'b = +1.
+         */
+        mp_sub_into(tmp, b, ac);
+        mp_select_into(ac, ac, tmp, minus_d);
+        mp_sub_into(tmp, a, bc);
+        mp_select_into(bc, bc, tmp, minus_d);
+
+        /*
+         * Now we really are done. Return the outputs.
+         */
+        if (a_coeff_out)
+            mp_copy_into(a_coeff_out, ac);
+        if (b_coeff_out)
+            mp_copy_into(b_coeff_out, bc);
+
+        } // WINSCP
+    }
+
+    mp_free(a);
+    mp_free(b);
+    mp_free(ac);
+    mp_free(bc);
+    mp_free(tmp);
+    mp_free(record);
+    } // WINSCP
+}
+
+mp_int *mp_invert(mp_int *x, mp_int *m)
+{
+    mp_int *result = mp_make_sized(m->nw);
+    mp_bezout_into(result, NULL, NULL, x, m);
+    return result;
+}
+
+void mp_gcd_into(mp_int *a, mp_int *b, mp_int *gcd, mp_int *A, mp_int *B)
+{
+    /*
+     * Identify shared factors of 2. To do this we OR the two numbers
+     * to get something whose lowest set bit is in the right place,
+     * remove all higher bits by ANDing it with its own negation, and
+     * use mp_get_nbits to find the location of the single remaining
+     * set bit.
+     */
+    mp_int *tmp = mp_make_sized(size_t_max(a->nw, b->nw));
+    size_t i; // WINSCP
+    for (i = 0; i < tmp->nw; i++)
+        tmp->w[i] = mp_word(a, i) | mp_word(b, i);
+    { // WINSCP
+    BignumCarry carry = 1;
+    size_t i;
+    for (i = 0; i < tmp->nw; i++) {
+        BignumInt negw;
+        BignumADC(negw, carry, 0, ~tmp->w[i], carry);
+        tmp->w[i] &= negw;
+    }
+    { // WINSCP
+    size_t shift = mp_get_nbits(tmp) - 1;
+    mp_free(tmp);
+
+    /*
+     * Make copies of a,b with those shared factors of 2 divided off,
+     * so that at least one is odd (which is the precondition for
+     * mp_bezout_into). Compute the gcd of those.
+     */
+    { // WINSCP
+    mp_int *as = mp_rshift_safe(a, shift);
+    mp_int *bs = mp_rshift_safe(b, shift);
+    mp_bezout_into(A, B, gcd, as, bs);
+    mp_free(as);
+    mp_free(bs);
+
+    /*
+     * And finally shift the gcd back up (unless the caller didn't
+     * even ask for it), to put the shared factors of 2 back in.
+     */
+    if (gcd)
+        mp_lshift_safe_in_place(gcd, shift);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+mp_int *mp_gcd(mp_int *a, mp_int *b)
+{
+    mp_int *gcd = mp_make_sized(size_t_min(a->nw, b->nw));
+    mp_gcd_into(a, b, gcd, NULL, NULL);
+    return gcd;
+}
+
+unsigned mp_coprime(mp_int *a, mp_int *b)
+{
+    mp_int *gcd = mp_gcd(a, b);
+    unsigned toret = mp_eq_integer(gcd, 1);
+    mp_free(gcd);
+    return toret;
+}
+
+static uint32_t recip_approx_32(uint32_t x)
+{
+    /*
+     * Given an input x in [2^31,2^32), i.e. a uint32_t with its high
+     * bit set, this function returns an approximation to 2^63/x,
+     * computed using only multiplications and bit shifts just in case
+     * the C divide operator has non-constant time (either because the
+     * underlying machine instruction does, or because the operator
+     * expands to a library function on a CPU without hardware
+     * division).
+     *
+     * The coefficients are derived from those of the degree-9
+     * polynomial which is the minimax-optimal approximation to that
+     * function on the given interval (generated using the Remez
+     * algorithm), converted into integer arithmetic with shifts used
+     * to maximise the number of significant bits at every state. (A
+     * sort of 'static floating point' - the exponent is statically
+     * known at every point in the code, so it never needs to be
+     * stored at run time or to influence runtime decisions.)
+     *
+     * Exhaustive iteration over the whole input space shows the
+     * largest possible error to be 1686.54. (The input value
+     * attaining that bound is 4226800006 == 0xfbefd986, whose true
+     * reciprocal is 2182116973.540... == 0x8210766d.8a6..., whereas
+     * this function returns 2182115287 == 0x82106fd7.)
+     */
+    uint64_t r = 0x92db03d6ULL;
+    r = 0xf63e71eaULL - ((r*x) >> 34);
+    r = 0xb63721e8ULL - ((r*x) >> 34);
+    r = 0x9c2da00eULL - ((r*x) >> 33);
+    r = 0xaada0bb8ULL - ((r*x) >> 32);
+    r = 0xf75cd403ULL - ((r*x) >> 31);
+    r = 0xecf97a41ULL - ((r*x) >> 31);
+    r = 0x90d876cdULL - ((r*x) >> 31);
+    r = 0x6682799a0ULL - ((r*x) >> 26);
+    return r;
+}
+
+void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out)
+{
+    pinitassert(!mp_eq_integer(d, 0));
+
+    /*
+     * We do division by using Newton-Raphson iteration to converge to
+     * the reciprocal of d (or rather, R/d for R a sufficiently large
+     * power of 2); then we multiply that reciprocal by n; and we
+     * finish up with conditional subtraction.
+     *
+     * But we have to do it in a fixed number of N-R iterations, so we
+     * need some error analysis to know how many we might need.
+     *
+     * The iteration is derived by defining f(r) = d - R/r.
+     * Differentiating gives f'(r) = R/r^2, and the Newton-Raphson
+     * formula applied to those functions gives
+     *
+     *      r_{i+1} = r_i - f(r_i) / f'(r_i)
+     *              = r_i - (d - R/r_i) r_i^2 / R
+     *              = r_i (2 R - d r_i) / R
+     *
+     * Now let e_i be the error in a given iteration, in the sense
+     * that
+     *
+     *        d r_i = R + e_i
+     *  i.e.  e_i/R = (r_i - r_true) / r_true
+     *
+     * so e_i is the _relative_ error in r_i.
+     *
+     * We must also introduce a rounding-error term, because the
+     * division by R always gives an integer. This might make the
+     * output off by up to 1 (in the negative direction, because
+     * right-shifting gives floor of the true quotient). So when we
+     * divide by R, we must imagine adding some f in [0,1). Then we
+     * have
+     *
+     *    d r_{i+1} = d r_i (2 R - d r_i) / R - d f
+     *              = (R + e_i) (R - e_i) / R - d f
+     *              = (R^2 - e_i^2) / R - d f
+     *              = R - (e_i^2 / R + d f)
+     * =>   e_{i+1} = - (e_i^2 / R + d f)
+     *
+     * The sum of two positive quantities is bounded above by twice
+     * their max, and max |f| = 1, so we can bound this as follows:
+     *
+     *               |e_{i+1}| <= 2 max (e_i^2/R, d)
+     *             |e_{i+1}/R| <= 2 max ((e_i/R)^2, d/R)
+     *        log2 |R/e_{i+1}| <= min (2 log2 |R/e_i|, log2 |R/d|) - 1
+     *
+     * which tells us that the number of 'good' bits - i.e.
+     * log2(R/e_i) - very nearly doubles at every iteration (apart
+     * from that subtraction of 1), until it gets to the same size as
+     * log2(R/d). In other words, the size of R in bits has to be the
+     * size of denominator we're putting in, _plus_ the amount of
+     * precision we want to get back out.
+     *
+     * So when we multiply n (the input numerator) by our final
+     * reciprocal approximation r, but actually r differs from R/d by
+     * up to 2, then it follows that
+     *
+     *   n/d - nr/R = n/d - [ n (R/d + e) ] / R
+     *              = n/d - [ (n/d) R + n e ] / R
+     *              = -ne/R
+     *      =>   0 <= n/d - nr/R < 2n/R
+     *
+     * so our computed quotient can differ from the true n/d by up to
+     * 2n/R. Hence, as long as we also choose R large enough that 2n/R
+     * is bounded above by a constant, we can guarantee a bounded
+     * number of final conditional-subtraction steps.
+     */
+
+    /*
+     * Get at least 32 of the most significant bits of the input
+     * number.
+     */
+    size_t hiword_index = 0;
+    uint64_t hibits = 0, lobits = 0;
+    mp_find_highest_nonzero_word_pair(d, 64 - BIGNUM_INT_BITS,
+                                      &hiword_index, &hibits, &lobits);
+
+    /*
+     * Make a shifted combination of those two words which puts the
+     * topmost bit of the number at bit 63.
+     */
+    { // WINSCP
+    size_t shift_up = 0;
+    size_t i; // WINSCP
+    for (i = BIGNUM_INT_BITS_BITS; i-- > 0;) {
+        size_t sl = (size_t)1 << i;       /* left shift count */
+        size_t sr = 64 - sl;     /* complementary right-shift count */
+
+        /* Should we shift up? */
+        unsigned indicator = 1 ^ normalise_to_1_u64(hibits >> sr);
+
+        /* If we do, what will we get? */
+        uint64_t new_hibits = (hibits << sl) | (lobits >> sr);
+        uint64_t new_lobits = lobits << sl;
+        size_t new_shift_up = shift_up + sl;
+
+        /* Conditionally swap those values in. */
+        hibits    ^= (hibits    ^ new_hibits   ) & -(uint64_t)indicator;
+        lobits    ^= (lobits    ^ new_lobits   ) & -(uint64_t)indicator;
+        shift_up  ^= (shift_up  ^ new_shift_up ) & -(size_t)  indicator;
+    }
+
+    /*
+     * So now we know the most significant 32 bits of d are at the top
+     * of hibits. Approximate the reciprocal of those bits.
+     */
+    lobits = (uint64_t)recip_approx_32(hibits >> 32) << 32;
+    hibits = 0;
+
+    /*
+     * And shift that up by as many bits as the input was shifted up
+     * just now, so that the product of this approximation and the
+     * actual input will be close to a fixed power of two regardless
+     * of where the MSB was.
+     *
+     * I do this in another log n individual passes, partly in case
+     * the CPU's register-controlled shift operation isn't
+     * time-constant, and also in case the compiler code-generates
+     * uint64_t shifts out of a variable number of smaller-word shift
+     * instructions, e.g. by splitting up into cases.
+     */
+    for (i = BIGNUM_INT_BITS_BITS; i-- > 0;) {
+        size_t sl = (size_t)1 << i;       /* left shift count */
+        size_t sr = 64 - sl;     /* complementary right-shift count */
+
+        /* Should we shift up? */
+        unsigned indicator = 1 & (shift_up >> i);
+
+        /* If we do, what will we get? */
+        uint64_t new_hibits = (hibits << sl) | (lobits >> sr);
+        uint64_t new_lobits = lobits << sl;
+
+        /* Conditionally swap those values in. */
+        hibits    ^= (hibits    ^ new_hibits   ) & -(uint64_t)indicator;
+        lobits    ^= (lobits    ^ new_lobits   ) & -(uint64_t)indicator;
+    }
+
+    /*
+     * The product of the 128-bit value now in hibits:lobits with the
+     * 128-bit value we originally retrieved in the same variables
+     * will be in the vicinity of 2^191. So we'll take log2(R) to be
+     * 191, plus a multiple of BIGNUM_INT_BITS large enough to allow R
+     * to hold the combined sizes of n and d.
+     */
+    { // WINSCP
+    size_t log2_R;
+    {
+        size_t max_log2_n = (n->nw + d->nw) * BIGNUM_INT_BITS;
+        log2_R = max_log2_n + 3;
+        log2_R -= size_t_min(191, log2_R);
+        log2_R = (log2_R + BIGNUM_INT_BITS - 1) & ~(BIGNUM_INT_BITS - 1);
+        log2_R += 191;
+    }
+
+    /* Number of words in a bignum capable of holding numbers the size
+     * of twice R. */
+    { // WINSCP
+    size_t rw = ((log2_R+2) + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+
+    /*
+     * Now construct our full-sized starting reciprocal approximation.
+     */
+    mp_int *r_approx = mp_make_sized(rw);
+    size_t output_bit_index;
+    {
+        /* Where in the input number did the input 128-bit value come from? */
+        size_t input_bit_index =
+            (hiword_index * BIGNUM_INT_BITS) - (128 - BIGNUM_INT_BITS);
+
+        /* So how far do we need to shift our 64-bit output, if the
+         * product of those two fixed-size values is 2^191 and we want
+         * to make it 2^log2_R instead? */
+        output_bit_index = log2_R - 191 - input_bit_index;
+
+        /* If we've done all that right, it should be a whole number
+         * of words. */
+        assert(output_bit_index % BIGNUM_INT_BITS == 0);
+        { // WINSCP
+        size_t output_word_index = output_bit_index / BIGNUM_INT_BITS;
+
+        mp_add_integer_into_shifted_by_words(
+            r_approx, r_approx, lobits, output_word_index);
+        mp_add_integer_into_shifted_by_words(
+            r_approx, r_approx, hibits,
+            output_word_index + 64 / BIGNUM_INT_BITS);
+        } // WINSCP
+    }
+
+    /*
+     * Make the constant 2*R, which we'll need in the iteration.
+     */
+    { // WINSCP
+    mp_int *two_R = mp_make_sized(rw);
+    BignumInt top_word = (BignumInt)1 << ((log2_R+1) % BIGNUM_INT_BITS);
+    mp_add_integer_into_shifted_by_words(
+        two_R, two_R, top_word, (log2_R+1) / BIGNUM_INT_BITS);
+
+    /*
+     * Scratch space.
+     */
+    { // WINSCP
+    mp_int *dr = mp_make_sized(rw + d->nw);
+    mp_int *diff = mp_make_sized(size_t_max(rw, dr->nw));
+    mp_int *product = mp_make_sized(rw + diff->nw);
+    size_t scratchsize = size_t_max(
+        mp_mul_scratchspace(dr->nw, r_approx->nw, d->nw),
+        mp_mul_scratchspace(product->nw, r_approx->nw, diff->nw));
+    mp_int *scratch = mp_make_sized(scratchsize);
+    mp_int product_shifted = mp_make_alias(
+        product, log2_R / BIGNUM_INT_BITS, product->nw);
+
+    /*
+     * Initial error estimate: the 32-bit output of recip_approx_32
+     * differs by less than 2048 (== 2^11) from the true top 32 bits
+     * of the reciprocal, so the relative error is at most 2^11
+     * divided by the 32-bit reciprocal, which at worst is 2^11/2^31 =
+     * 2^-20. So even in the worst case, we have 20 good bits of
+     * reciprocal to start with.
+     */
+    size_t good_bits = 31 - 11;
+    size_t good_bits_needed = BIGNUM_INT_BITS * n->nw + 4; /* add a few */
+
+    /*
+     * Now do Newton-Raphson iterations until we have reason to think
+     * they're not converging any more.
+     */
+    while (good_bits < good_bits_needed) {
+        /*
+         * Compute the next iterate.
+         */
+        mp_mul_internal(dr, r_approx, d, *scratch);
+        mp_sub_into(diff, two_R, dr);
+        mp_mul_internal(product, r_approx, diff, *scratch);
+        mp_rshift_fixed_into(r_approx, &product_shifted,
+                             log2_R % BIGNUM_INT_BITS);
+
+        /*
+         * Adjust the error estimate.
+         */
+        good_bits = good_bits * 2 - 1;
+    }
+
+    mp_free(dr);
+    mp_free(diff);
+    mp_free(product);
+    mp_free(scratch);
+
+    /*
+     * Now we've got our reciprocal, we can compute the quotient, by
+     * multiplying in n and then shifting down by log2_R bits.
+     */
+    { // WINSCP
+    mp_int *quotient_full = mp_mul(r_approx, n);
+    mp_int quotient_alias = mp_make_alias(
+        quotient_full, log2_R / BIGNUM_INT_BITS, quotient_full->nw);
+    mp_int *quotient = mp_make_sized(n->nw);
+    mp_rshift_fixed_into(quotient, &quotient_alias, log2_R % BIGNUM_INT_BITS);
+
+    /*
+     * Next, compute the remainder.
+     */
+    { // WINSCP
+    mp_int *remainder = mp_make_sized(d->nw);
+    mp_mul_into(remainder, quotient, d);
+    mp_sub_into(remainder, n, remainder);
+
+    /*
+     * Finally, two conditional subtractions to fix up any remaining
+     * rounding error. (I _think_ one should be enough, but this
+     * routine isn't time-critical enough to take chances.)
+     */
+    { // WINSCP
+    unsigned q_correction = 0;
+    unsigned iter; // WINSCP
+    for (iter = 0; iter < 2; iter++) {
+        unsigned need_correction = mp_cmp_hs(remainder, d);
+        mp_cond_sub_into(remainder, remainder, d, need_correction);
+        q_correction += need_correction;
+    }
+    mp_add_integer_into(quotient, quotient, q_correction);
+
+    /*
+     * Now we should have a perfect answer, i.e. 0 <= r < d.
+     */
+    assert(!mp_cmp_hs(remainder, d));
+
+    if (q_out)
+        mp_copy_into(q_out, quotient);
+    if (r_out)
+        mp_copy_into(r_out, remainder);
+
+    mp_free(r_approx);
+    mp_free(two_R);
+    mp_free(quotient_full);
+    mp_free(quotient);
+    mp_free(remainder);
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+mp_int *mp_div(mp_int *n, mp_int *d)
+{
+    mp_int *q = mp_make_sized(n->nw);
+    mp_divmod_into(n, d, q, NULL);
+    return q;
+}
+
+mp_int *mp_mod(mp_int *n, mp_int *d)
+{
+    mp_int *r = mp_make_sized(d->nw);
+    mp_divmod_into(n, d, NULL, r);
+    return r;
+}
+
+uint32_t mp_mod_known_integer(mp_int *x, uint32_t m)
+{
+    uint64_t reciprocal = ((uint64_t)1 << 48) / m;
+    uint64_t accumulator = 0;
+    { // WINSCP
+    size_t i;
+    for (i = mp_max_bytes(x); i-- > 0 ;) {
+        accumulator = 0x100 * accumulator + mp_get_byte(x, i);
+        /*
+         * Let A be the value in 'accumulator' at this point, and let
+         * R be the value it will have after we subtract quot*m below.
+         *
+         * Lemma 1: if A < 2^48, then R < 2m.
+         *
+         * Proof:
+         *
+         * By construction, we have 2^48/m - 1 < reciprocal <= 2^48/m.
+         * Multiplying that by the accumulator gives
+         *
+         *      A/m * 2^48 - A < unshifted_quot <= A/m * 2^48
+         * i.e. 0 <= (A/m * 2^48) - unshifted_quot < A
+         * i.e. 0 <= A/m - unshifted_quot/2^48 < A/2^48
+         *
+         * So when we shift this quotient right by 48 bits, i.e. take
+         * the floor of (unshifted_quot/2^48), the value we take the
+         * floor of is at most A/2^48 less than the true rational
+         * value A/m that we _wanted_ to take the floor of.
+         *
+         * Provided A < 2^48, this is less than 1. So the quotient
+         * 'quot' that we've just produced is either the true quotient
+         * floor(A/m), or one less than it. Hence, the output value R
+         * is less than 2m. []
+         *
+         * Lemma 2: if A < 2^16 m, then the multiplication of
+         * accumulator*reciprocal does not overflow.
+         *
+         * Proof: as above, we have reciprocal <= 2^48/m. Multiplying
+         * by A gives unshifted_quot <= 2^48 * A / m < 2^48 * 2^16 =
+         * 2^64. []
+         */
+        { // WINSCP
+        uint64_t unshifted_quot = accumulator * reciprocal;
+        uint64_t quot = unshifted_quot >> 48;
+        accumulator -= quot * m;
+        } // WINSCP
+    }
+
+    /*
+     * Theorem 1: accumulator < 2m at the end of every iteration of
+     * this loop.
+     *
+     * Proof: induction on the above loop.
+     *
+     * Base case: at the start of the first loop iteration, the
+     * accumulator is 0, which is certainly < 2m.
+     *
+     * Inductive step: in each loop iteration, we take a value at most
+     * 2m-1, multiply it by 2^8, and add another byte less than 2^8 to
+     * generate the input value A to the reduction process above. So
+     * we have A < 2m * 2^8 - 1. We know m < 2^32 (because it was
+     * passed in as a uint32_t), so A < 2^41, which is enough to allow
+     * us to apply Lemma 1, showing that the value of 'accumulator' at
+     * the end of the loop is still < 2m. []
+     *
+     * Corollary: we need at most one final subtraction of m to
+     * produce the canonical residue of x mod m, i.e. in the range
+     * [0,m).
+     *
+     * Theorem 2: no multiplication in the inner loop overflows.
+     *
+     * Proof: in Theorem 1 we established A < 2m * 2^8 - 1 in every
+     * iteration. That is less than m * 2^16, so Lemma 2 applies.
+     *
+     * The other multiplication, of quot * m, cannot overflow because
+     * quot is at most A/m, so quot*m <= A < 2^64. []
+     */
+
+    { // WINSCP
+    uint32_t result = accumulator;
+    uint32_t reduced = result - m;
+    uint32_t select = -(reduced >> 31);
+    result = reduced ^ ((result ^ reduced) & select);
+    assert(result < m);
+    return result;
+    } // WINSCP
+    } // WINSCP
+}
+
+mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder_out)
+{
+    /*
+     * Allocate scratch space.
+     */
+    mp_int **alloc, **powers, **newpowers, *scratch;
+    size_t nalloc = 2*(n+1)+1;
+    alloc = snewn(nalloc, mp_int *);
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < nalloc; i++)
+        alloc[i] = mp_make_sized(y->nw + 1);
+    powers = alloc;
+    newpowers = alloc + (n+1);
+    scratch = alloc[2*n+2];
+
+    /*
+     * We're computing the rounded-down nth root of y, i.e. the
+     * maximal x such that x^n <= y. We try to add 2^i to it for each
+     * possible value of i, starting from the largest one that might
+     * fit (i.e. such that 2^{n*i} fits in the size of y) downwards to
+     * i=0.
+     *
+     * We track all the smaller powers of x in the array 'powers'. In
+     * each iteration, if we update x, we update all of those values
+     * to match.
+     */
+    mp_copy_integer_into(powers[0], 1);
+    { // WINSCP
+    size_t s; // WINSCP
+    for (s = mp_max_bits(y) / n + 1; s-- > 0 ;) {
+        /*
+         * Let b = 2^s. We need to compute the powers (x+b)^i for each
+         * i, starting from our recorded values of x^i.
+         */
+        size_t i; // WINSCP
+        for (i = 0; i < n+1; i++) {
+            /*
+             * (x+b)^i = x^i
+             *         + (i choose 1) x^{i-1} b
+             *         + (i choose 2) x^{i-2} b^2
+             *         + ...
+             *         + b^i
+             */
+            uint16_t binom = 1;       /* coefficient of b^i */
+            mp_copy_into(newpowers[i], powers[i]);
+            { // WINSCP
+            size_t j; // WINSCP
+            for (j = 0; j < i; j++) {
+                /* newpowers[i] += binom * powers[j] * 2^{(i-j)*s} */
+                mp_mul_integer_into(scratch, powers[j], binom);
+                mp_lshift_fixed_into(scratch, scratch, (i-j) * s);
+                mp_add_into(newpowers[i], newpowers[i], scratch);
+
+                { // WINSCP
+                uint32_t binom_mul = binom;
+                binom_mul *= (i-j);
+                binom_mul /= (j+1);
+                assert(binom_mul < 0x10000);
+                binom = binom_mul;
+                } // WINSCP
+            }
+            } // WINSCP
+        }
+
+        /*
+         * Now, is the new value of x^n still <= y? If so, update.
+         */
+        { // WINSCP
+        unsigned newbit = mp_cmp_hs(y, newpowers[n]);
+        size_t i; // WINSCP
+        for (i = 0; i < n+1; i++)
+            mp_select_into(powers[i], powers[i], newpowers[i], newbit);
+        } // WINSCP
+    }
+
+    if (remainder_out)
+        mp_sub_into(remainder_out, y, powers[n]);
+
+    { // WINSCP
+    mp_int *root = mp_new(mp_max_bits(y) / n);
+    mp_copy_into(root, powers[1]);
+
+    { // WINSCP
+    size_t i;
+    for (i = 0; i < nalloc; i++)
+        mp_free(alloc[i]);
+    sfree(alloc);
+
+    return root;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+mp_int *mp_modmul(mp_int *x, mp_int *y, mp_int *modulus)
+{
+    mp_int *product = mp_mul(x, y);
+    mp_int *reduced = mp_mod(product, modulus);
+    mp_free(product);
+    return reduced;
+}
+
+mp_int *mp_modadd(mp_int *x, mp_int *y, mp_int *modulus)
+{
+    mp_int *sum = mp_add(x, y);
+    mp_int *reduced = mp_mod(sum, modulus);
+    mp_free(sum);
+    return reduced;
+}
+
+mp_int *mp_modsub(mp_int *x, mp_int *y, mp_int *modulus)
+{
+    mp_int *diff = mp_make_sized(size_t_max(x->nw, y->nw));
+    mp_sub_into(diff, x, y);
+    { // WINSCP
+    unsigned negate = mp_cmp_hs(y, x);
+    mp_cond_negate(diff, diff, negate);
+    { // WINSCP
+    mp_int *residue = mp_mod(diff, modulus);
+    mp_cond_negate(residue, residue, negate);
+    /* If we've just negated the residue, then it will be < 0 and need
+     * the modulus adding to it to make it positive - *except* if the
+     * residue was zero when we negated it. */
+    { // WINSCP
+    unsigned make_positive = negate & ~mp_eq_integer(residue, 0);
+    mp_cond_add_into(residue, residue, modulus, make_positive);
+    mp_free(diff);
+    return residue;
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+static mp_int *mp_modadd_in_range(mp_int *x, mp_int *y, mp_int *modulus)
+{
+    mp_int *sum = mp_make_sized(modulus->nw);
+    unsigned carry = mp_add_into_internal(sum, x, y);
+    mp_cond_sub_into(sum, sum, modulus, carry | mp_cmp_hs(sum, modulus));
+    return sum;
+}
+
+static mp_int *mp_modsub_in_range(mp_int *x, mp_int *y, mp_int *modulus)
+{
+    mp_int *diff = mp_make_sized(modulus->nw);
+    mp_sub_into(diff, x, y);
+    mp_cond_add_into(diff, diff, modulus, 1 ^ mp_cmp_hs(x, y));
+    return diff;
+}
+
+mp_int *monty_add(MontyContext *mc, mp_int *x, mp_int *y)
+{
+    return mp_modadd_in_range(x, y, mc->m);
+}
+
+mp_int *monty_sub(MontyContext *mc, mp_int *x, mp_int *y)
+{
+    return mp_modsub_in_range(x, y, mc->m);
+}
+
+void mp_min_into(mp_int *r, mp_int *x, mp_int *y)
+{
+    mp_select_into(r, x, y, mp_cmp_hs(x, y));
+}
+
+void mp_max_into(mp_int *r, mp_int *x, mp_int *y)
+{
+    mp_select_into(r, y, x, mp_cmp_hs(x, y));
+}
+
+mp_int *mp_min(mp_int *x, mp_int *y)
+{
+    mp_int *r = mp_make_sized(size_t_min(x->nw, y->nw));
+    mp_min_into(r, x, y);
+    return r;
+}
+
+mp_int *mp_max(mp_int *x, mp_int *y)
+{
+    mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw));
+    mp_max_into(r, x, y);
+    return r;
+}
+
+mp_int *mp_power_2(size_t power)
+{
+    mp_int *x = mp_new(power + 1);
+    mp_set_bit(x, power, 1);
+    return x;
+}
+
+struct ModsqrtContext {
+    mp_int *p;                      /* the prime */
+    MontyContext *mc;                  /* for doing arithmetic mod p */
+
+    /* Decompose p-1 as 2^e k, for positive integer e and odd k */
+    size_t e;
+    mp_int *k;
+    mp_int *km1o2;                  /* (k-1)/2 */
+
+    /* The user-provided value z which is not a quadratic residue mod
+     * p, and its kth power. Both in Montgomery form. */
+    mp_int *z, *zk;
+};
+
+ModsqrtContext *modsqrt_new(mp_int *p, mp_int *any_nonsquare_mod_p)
+{
+    ModsqrtContext *sc = snew(ModsqrtContext);
+    memset(sc, 0, sizeof(ModsqrtContext));
+
+    sc->p = mp_copy(p);
+    sc->mc = monty_new(sc->p);
+    sc->z = monty_import(sc->mc, any_nonsquare_mod_p);
+
+    /* Find the lowest set bit in p-1. Since this routine expects p to
+     * be non-secret (typically a well-known standard elliptic curve
+     * parameter), for once we don't need clever bit tricks. */
+    for (sc->e = 1; sc->e < BIGNUM_INT_BITS * p->nw; sc->e++)
+        if (mp_get_bit(p, sc->e))
+            break;
+
+    sc->k = mp_rshift_fixed(p, sc->e);
+    sc->km1o2 = mp_rshift_fixed(sc->k, 1);
+
+    /* Leave zk to be filled in lazily, since it's more expensive to
+     * compute. If this context turns out never to be needed, we can
+     * save the bulk of the setup time this way. */
+
+    return sc;
+}
+
+static void modsqrt_lazy_setup(ModsqrtContext *sc)
+{
+    if (!sc->zk)
+        sc->zk = monty_pow(sc->mc, sc->z, sc->k);
+}
+
+void modsqrt_free(ModsqrtContext *sc)
+{
+    monty_free(sc->mc);
+    mp_free(sc->p);
+    mp_free(sc->z);
+    mp_free(sc->k);
+    mp_free(sc->km1o2);
+
+    if (sc->zk)
+        mp_free(sc->zk);
+
+    sfree(sc);
+}
+
+mp_int *mp_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success)
+{
+    mp_int *mx = monty_import(sc->mc, x);
+    mp_int *mroot = monty_modsqrt(sc, mx, success);
+    mp_free(mx);
+    { // WINSCP
+    mp_int *root = monty_export(sc->mc, mroot);
+    mp_free(mroot);
+    return root;
+    } // WINSCP
+}
+
+/*
+ * Modular square root, using an algorithm more or less similar to
+ * Tonelli-Shanks but adapted for constant time.
+ *
+ * The basic idea is to write p-1 = k 2^e, where k is odd and e > 0.
+ * Then the multiplicative group mod p (call it G) has a sequence of
+ * e+1 nested subgroups G = G_0 > G_1 > G_2 > ... > G_e, where each
+ * G_i is exactly half the size of G_{i-1} and consists of all the
+ * squares of elements in G_{i-1}. So the innermost group G_e has
+ * order k, which is odd, and hence within that group you can take a
+ * square root by raising to the power (k+1)/2.
+ *
+ * Our strategy is to iterate over these groups one by one and make
+ * sure the number x we're trying to take the square root of is inside
+ * each one, by adjusting it if it isn't.
+ *
+ * Suppose g is a primitive root of p, i.e. a generator of G_0. (We
+ * don't actually need to know what g _is_; we just imagine it for the
+ * sake of understanding.) Then G_i consists of precisely the (2^i)th
+ * powers of g, and hence, you can tell if a number is in G_i if
+ * raising it to the power k 2^{e-i} gives 1. So the conceptual
+ * algorithm goes: for each i, test whether x is in G_i by that
+ * method. If it isn't, then the previous iteration ensured it's in
+ * G_{i-1}, so it will be an odd power of g^{2^{i-1}}, and hence
+ * multiplying by any other odd power of g^{2^{i-1}} will give x' in
+ * G_i. And we have one of those, because our non-square z is an odd
+ * power of g, so z^{2^{i-1}} is an odd power of g^{2^{i-1}}.
+ *
+ * (There's a special case in the very first iteration, where we don't
+ * have a G_{i-1}. If it turns out that x is not even in G_1, that
+ * means it's not a square, so we set *success to 0. We still run the
+ * rest of the algorithm anyway, for the sake of constant time, but we
+ * don't give a hoot what it returns.)
+ *
+ * When we get to the end and have x in G_e, then we can take its
+ * square root by raising to (k+1)/2. But of course that's not the
+ * square root of the original input - it's only the square root of
+ * the adjusted version we produced during the algorithm. To get the
+ * true output answer we also have to multiply by a power of z,
+ * namely, z to the power of _half_ whatever we've been multiplying in
+ * as we go along. (The power of z we multiplied in must have been
+ * even, because the case in which we would have multiplied in an odd
+ * power of z is the i=0 case, in which we instead set the failure
+ * flag.)
+ *
+ * The code below is an optimised version of that basic idea, in which
+ * we _start_ by computing x^k so as to be able to test membership in
+ * G_i by only a few squarings rather than a full from-scratch modpow
+ * every time; we also start by computing our candidate output value
+ * x^{(k+1)/2}. So when the above description says 'adjust x by z^i'
+ * for some i, we have to adjust our running values of x^k and
+ * x^{(k+1)/2} by z^{ik} and z^{ik/2} respectively (the latter is safe
+ * because, as above, i is always even). And it turns out that we
+ * don't actually have to store the adjusted version of x itself at
+ * all - we _only_ keep those two powers of it.
+ */
+mp_int *monty_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success)
+{
+    modsqrt_lazy_setup(sc);
+
+    { // WINSCP
+    mp_int *scratch_to_free = mp_make_sized(3 * sc->mc->rw);
+    mp_int scratch = *scratch_to_free;
+
+    /*
+     * Compute toret = x^{(k+1)/2}, our starting point for the output
+     * square root, and also xk = x^k which we'll use as we go along
+     * for knowing when to apply correction factors. We do this by
+     * first computing x^{(k-1)/2}, then multiplying it by x, then
+     * multiplying the two together.
+     */
+    mp_int *toret = monty_pow(sc->mc, x, sc->km1o2);
+    mp_int xk = mp_alloc_from_scratch(&scratch, sc->mc->rw);
+    mp_copy_into(&xk, toret);
+    monty_mul_into(sc->mc, toret, toret, x);
+    monty_mul_into(sc->mc, &xk, toret, &xk);
+
+    { // WINSCP
+    mp_int tmp = mp_alloc_from_scratch(&scratch, sc->mc->rw);
+
+    mp_int power_of_zk = mp_alloc_from_scratch(&scratch, sc->mc->rw);
+    size_t i; // WINSCP
+    mp_copy_into(&power_of_zk, sc->zk);
+
+    for (i = 0; i < sc->e; i++) {
+        size_t j; // WINSCP
+        mp_copy_into(&tmp, &xk);
+        for (j = i+1; j < sc->e; j++)
+            monty_mul_into(sc->mc, &tmp, &tmp, &tmp);
+        { // WINSCP
+        unsigned eq1 = mp_cmp_eq(&tmp, monty_identity(sc->mc));
+
+        if (i == 0) {
+            /* One special case: if x=0, then no power of x will ever
+             * equal 1, but we should still report success on the
+             * grounds that 0 does have a square root mod p. */
+            *success = eq1 | mp_eq_integer(x, 0);
+        } else {
+            monty_mul_into(sc->mc, &tmp, toret, &power_of_zk);
+            mp_select_into(toret, &tmp, toret, eq1);
+
+            monty_mul_into(sc->mc, &power_of_zk,
+                           &power_of_zk, &power_of_zk);
+
+            monty_mul_into(sc->mc, &tmp, &xk, &power_of_zk);
+            mp_select_into(&xk, &tmp, &xk, eq1);
+        }
+        } // WINSCP
+    }
+
+    mp_free(scratch_to_free);
+
+    return toret;
+    } // WINSCP
+    } // WINSCP
+}
+
+mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t random_read)
+{
+    size_t bytes = (bits + 7) / 8;
+    uint8_t *randbuf = snewn(bytes, uint8_t);
+    random_read(randbuf, bytes);
+    if (bytes)
+        randbuf[0] &= (2 << ((bits-1) & 7)) - 1;
+    { // WINSCP
+    mp_int *toret = mp_from_bytes_be(make_ptrlen(randbuf, bytes));
+    smemclr(randbuf, bytes);
+    sfree(randbuf);
+    return toret;
+    } // WINSCP
+}
+
+mp_int *mp_random_upto_fn(mp_int *limit, random_read_fn_t rf)
+{
+    /*
+     * It would be nice to generate our random numbers in such a way
+     * as to make every possible outcome literally equiprobable. But
+     * we can't do that in constant time, so we have to go for a very
+     * close approximation instead. I'm going to take the view that a
+     * factor of (1+2^-128) between the probabilities of two outcomes
+     * is acceptable on the grounds that you'd have to examine so many
+     * outputs to even detect it.
+     */
+    mp_int *unreduced = mp_random_bits_fn(mp_max_bits(limit) + 128, rf);
+    mp_int *reduced = mp_mod(unreduced, limit);
+    mp_free(unreduced);
+    return reduced;
+}
+
+mp_int *mp_random_in_range_fn(mp_int *lo, mp_int *hi, random_read_fn_t rf)
+{
+    mp_int *n_outcomes = mp_sub(hi, lo);
+    mp_int *addend = mp_random_upto_fn(n_outcomes, rf);
+    mp_int *result = mp_make_sized(hi->nw);
+    mp_add_into(result, addend, lo);
+    mp_free(addend);
+    mp_free(n_outcomes);
+    return result;
+}

+ 324 - 0
source/putty/crypto/mpint_i.h

@@ -0,0 +1,324 @@
+/*
+ * mpint_i.h: definitions used internally by the bignum code, and
+ * also a few other vaguely-bignum-like places.
+ */
+
+/* ----------------------------------------------------------------------
+ * The assorted conditional definitions of BignumInt and multiply
+ * macros used throughout the bignum code to treat numbers as arrays
+ * of the most conveniently sized word for the target machine.
+ * Exported so that other code (e.g. poly1305) can use it too.
+ *
+ * This code must export, in whatever ifdef branch it ends up in:
+ *
+ *  - two types: 'BignumInt' and 'BignumCarry'. BignumInt is an
+ *    unsigned integer type which will be used as the base word size
+ *    for all bignum operations. BignumCarry is an unsigned integer
+ *    type used to hold the carry flag taken as input and output by
+ *    the BignumADC macro (see below).
+ *
+ *  - five constant macros:
+ *     + BIGNUM_INT_BITS, the number of bits in BignumInt,
+ *     + BIGNUM_INT_BYTES, the number of bytes that works out to
+ *     + BIGNUM_TOP_BIT, the BignumInt value consisting of only the top bit
+ *     + BIGNUM_INT_MASK, the BignumInt value with all bits set
+ *     + BIGNUM_INT_BITS_BITS, log to the base 2 of BIGNUM_INT_BITS.
+ *
+ *  - four statement macros: BignumADC, BignumMUL, BignumMULADD,
+ *    BignumMULADD2. These do various kinds of multi-word arithmetic,
+ *    and all produce two output values.
+ *     * BignumADC(ret,retc,a,b,c) takes input BignumInt values a,b
+ *       and a BignumCarry c, and outputs a BignumInt ret = a+b+c and
+ *       a BignumCarry retc which is the carry off the top of that
+ *       addition.
+ *     * BignumMUL(rh,rl,a,b) returns the two halves of the
+ *       double-width product a*b.
+ *     * BignumMULADD(rh,rl,a,b,addend) returns the two halves of the
+ *       double-width value a*b + addend.
+ *     * BignumMULADD2(rh,rl,a,b,addend1,addend2) returns the two
+ *       halves of the double-width value a*b + addend1 + addend2.
+ *
+ * Every branch of the main ifdef below defines the type BignumInt and
+ * the value BIGNUM_INT_BITS_BITS. The other constant macros are
+ * filled in by common code further down.
+ *
+ * Most branches also define a macro DEFINE_BIGNUMDBLINT containing a
+ * typedef statement which declares a type _twice_ the length of a
+ * BignumInt. This causes the common code further down to produce a
+ * default implementation of the four statement macros in terms of
+ * that double-width type, and also to defined BignumCarry to be
+ * BignumInt.
+ *
+ * However, if a particular compile target does not have a type twice
+ * the length of the BignumInt you want to use but it does provide
+ * some alternative means of doing add-with-carry and double-word
+ * multiply, then the ifdef branch in question can just define
+ * BignumCarry and the four statement macros itself, and that's fine
+ * too.
+ */
+
+/* You can lower the BignumInt size by defining BIGNUM_OVERRIDE on the
+ * command line to be your chosen max value of BIGNUM_INT_BITS_BITS */
+#if defined BIGNUM_OVERRIDE
+#define BB_OK(b) ((b) <= BIGNUM_OVERRIDE)
+#else
+#define BB_OK(b) (1)
+#endif
+
+#if defined __SIZEOF_INT128__ && BB_OK(6)
+
+  /*
+   * 64-bit BignumInt using gcc/clang style 128-bit BignumDblInt.
+   *
+   * gcc and clang both provide a __uint128_t type on 64-bit targets
+   * (and, when they do, indicate its presence by the above macro),
+   * using the same 'two machine registers' kind of code generation
+   * that 32-bit targets use for 64-bit ints.
+   */
+
+  typedef unsigned long long BignumInt;
+  #define BIGNUM_INT_BITS_BITS 6
+  #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt
+
+#elif defined _MSC_VER && defined _M_AMD64 && BB_OK(6)
+
+  /*
+   * 64-bit BignumInt, using Visual Studio x86-64 compiler intrinsics.
+   *
+   * 64-bit Visual Studio doesn't provide very much in the way of help
+   * here: there's no int128 type, and also no inline assembler giving
+   * us direct access to the x86-64 MUL or ADC instructions. However,
+   * there are compiler intrinsics giving us that access, so we can
+   * use those - though it turns out we have to be a little careful,
+   * since they seem to generate wrong code if their pointer-typed
+   * output parameters alias their inputs. Hence all the internal temp
+   * variables inside the macros.
+   */
+
+  #include <intrin.h>
+  typedef unsigned char BignumCarry; /* the type _addcarry_u64 likes to use */
+  typedef unsigned __int64 BignumInt;
+  #define BIGNUM_INT_BITS_BITS 6
+  #define BignumADC(ret, retc, a, b, c) do                \
+      {                                                   \
+          BignumInt ADC_tmp;                              \
+          (retc) = _addcarry_u64(c, a, b, &ADC_tmp);      \
+          (ret) = ADC_tmp;                                \
+      } while (0)
+  #define BignumMUL(rh, rl, a, b) do              \
+      {                                           \
+          BignumInt MULADD_hi;                    \
+          (rl) = _umul128(a, b, &MULADD_hi);      \
+          (rh) = MULADD_hi;                       \
+      } while (0)
+  #define BignumMULADD(rh, rl, a, b, addend) do                           \
+      {                                                                   \
+          BignumInt MULADD_lo, MULADD_hi;                                 \
+          MULADD_lo = _umul128(a, b, &MULADD_hi);                         \
+          MULADD_hi += _addcarry_u64(0, MULADD_lo, (addend), &(rl));     \
+          (rh) = MULADD_hi;                                               \
+      } while (0)
+  #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do                \
+      {                                                                   \
+          BignumInt MULADD_lo1, MULADD_lo2, MULADD_hi;                    \
+          MULADD_lo1 = _umul128(a, b, &MULADD_hi);                        \
+          MULADD_hi += _addcarry_u64(0, MULADD_lo1, (addend1), &MULADD_lo2); \
+          MULADD_hi += _addcarry_u64(0, MULADD_lo2, (addend2), &(rl));    \
+          (rh) = MULADD_hi;                                               \
+      } while (0)
+
+#elif (defined __GNUC__ || defined _LLP64 || __STDC__ >= 199901L) && BB_OK(5)
+
+  /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */
+
+  typedef unsigned int BignumInt;
+  #define BIGNUM_INT_BITS_BITS 5
+  #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt
+
+#elif defined _MSC_VER && BB_OK(5) || defined(MPEXT)
+
+  /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */
+
+  typedef unsigned int BignumInt;
+  #define BIGNUM_INT_BITS_BITS 5
+  #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt
+
+#elif defined _LP64 && BB_OK(5)
+
+  /*
+   * 32-bit BignumInt, using unsigned long itself as BignumDblInt.
+   *
+   * Only for platforms where long is 64 bits, of course.
+   */
+
+  typedef unsigned int BignumInt;
+  #define BIGNUM_INT_BITS_BITS 5
+  #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt
+
+#elif BB_OK(4)
+
+  /*
+   * 16-bit BignumInt, using unsigned long as BignumDblInt.
+   *
+   * This is the final fallback for real emergencies: C89 guarantees
+   * unsigned short/long to be at least the required sizes, so this
+   * should work on any C implementation at all. But it'll be
+   * noticeably slow, so if you find yourself in this case you
+   * probably want to move heaven and earth to find an alternative!
+   */
+
+  typedef unsigned short BignumInt;
+  #define BIGNUM_INT_BITS_BITS 4
+  #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt
+
+#else
+
+  /* Should only get here if BB_OK(4) evaluated false, i.e. the
+   * command line defined BIGNUM_OVERRIDE to an absurdly small
+   * value. */
+  #error Must define BIGNUM_OVERRIDE to at least 4
+
+#endif
+
+#undef BB_OK
+
+/*
+ * Common code across all branches of that ifdef: define all the
+ * easy constant macros in terms of BIGNUM_INT_BITS_BITS.
+ */
+#define BIGNUM_INT_BITS (1 << BIGNUM_INT_BITS_BITS)
+#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8)
+#define BIGNUM_TOP_BIT (((BignumInt)1) << (BIGNUM_INT_BITS-1))
+#define BIGNUM_INT_MASK (BIGNUM_TOP_BIT | (BIGNUM_TOP_BIT-1))
+
+/*
+ * Just occasionally, we might need a GET_nnBIT_xSB_FIRST macro to
+ * operate on whatever BignumInt is.
+ */
+#if BIGNUM_INT_BITS_BITS == 4
+#define GET_BIGNUMINT_MSB_FIRST GET_16BIT_MSB_FIRST
+#define GET_BIGNUMINT_LSB_FIRST GET_16BIT_LSB_FIRST
+#define PUT_BIGNUMINT_MSB_FIRST PUT_16BIT_MSB_FIRST
+#define PUT_BIGNUMINT_LSB_FIRST PUT_16BIT_LSB_FIRST
+#elif BIGNUM_INT_BITS_BITS == 5
+#define GET_BIGNUMINT_MSB_FIRST GET_32BIT_MSB_FIRST
+#define GET_BIGNUMINT_LSB_FIRST GET_32BIT_LSB_FIRST
+#define PUT_BIGNUMINT_MSB_FIRST PUT_32BIT_MSB_FIRST
+#define PUT_BIGNUMINT_LSB_FIRST PUT_32BIT_LSB_FIRST
+#elif BIGNUM_INT_BITS_BITS == 6
+#define GET_BIGNUMINT_MSB_FIRST GET_64BIT_MSB_FIRST
+#define GET_BIGNUMINT_LSB_FIRST GET_64BIT_LSB_FIRST
+#define PUT_BIGNUMINT_MSB_FIRST PUT_64BIT_MSB_FIRST
+#define PUT_BIGNUMINT_LSB_FIRST PUT_64BIT_LSB_FIRST
+#else
+  #error Ran out of options for GET_BIGNUMINT_xSB_FIRST
+#endif
+
+/*
+ * Common code across _most_ branches of the ifdef: define a set of
+ * statement macros in terms of the BignumDblInt type provided. In
+ * this case, we also define BignumCarry to be the same thing as
+ * BignumInt, for simplicity.
+ */
+#ifdef DEFINE_BIGNUMDBLINT
+
+  typedef BignumInt BignumCarry;
+  #define BignumADC(ret, retc, a, b, c) do                        \
+      {                                                           \
+          DEFINE_BIGNUMDBLINT;                                    \
+          BignumDblInt ADC_temp = (BignumInt)(a);                 \
+          ADC_temp += (BignumInt)(b);                             \
+          ADC_temp += (c);                                        \
+          (ret) = (BignumInt)ADC_temp;                            \
+          (retc) = (BignumCarry)(ADC_temp >> BIGNUM_INT_BITS);    \
+      } while (0)
+
+  #define BignumMUL(rh, rl, a, b) do                              \
+      {                                                           \
+          DEFINE_BIGNUMDBLINT;                                    \
+          BignumDblInt MUL_temp = (BignumInt)(a);                 \
+          MUL_temp *= (BignumInt)(b);                             \
+          (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS);        \
+          (rl) = (BignumInt)(MUL_temp);                           \
+      } while (0)
+
+  #define BignumMULADD(rh, rl, a, b, addend) do                   \
+      {                                                           \
+          DEFINE_BIGNUMDBLINT;                                    \
+          BignumDblInt MUL_temp = (BignumInt)(a);                 \
+          MUL_temp *= (BignumInt)(b);                             \
+          MUL_temp += (BignumInt)(addend);                        \
+          (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS);        \
+          (rl) = (BignumInt)(MUL_temp);                           \
+      } while (0)
+
+  #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do        \
+      {                                                           \
+          DEFINE_BIGNUMDBLINT;                                    \
+          BignumDblInt MUL_temp = (BignumInt)(a);                 \
+          MUL_temp *= (BignumInt)(b);                             \
+          MUL_temp += (BignumInt)(addend1);                       \
+          MUL_temp += (BignumInt)(addend2);                       \
+          (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS);        \
+          (rl) = (BignumInt)(MUL_temp);                           \
+      } while (0)
+
+#endif /* DEFINE_BIGNUMDBLINT */
+
+/* ----------------------------------------------------------------------
+ * Data structures used inside mpint.c.
+ */
+
+struct mp_int {
+    size_t nw;
+    BignumInt *w;
+};
+
+struct MontyContext {
+    /*
+     * The actual modulus.
+     */
+    mp_int *m;
+
+    /*
+     * Montgomery multiplication works by selecting a value r > m,
+     * coprime to m, which is really easy to divide by. In binary
+     * arithmetic, that means making it a power of 2; in fact we make
+     * it a whole number of BignumInt.
+     *
+     * We don't store r directly as an mp_int (there's no need). But
+     * its value is 2^rbits; we also store rw = rbits/BIGNUM_INT_BITS
+     * (the corresponding word offset within an mp_int).
+     *
+     * pw is the number of words needed to store an mp_int you're
+     * doing reduction on: it has to be big enough to hold the sum of
+     * an input value up to m^2 plus an extra addend up to m*r.
+     */
+    size_t rbits, rw, pw;
+
+    /*
+     * The key step in Montgomery reduction requires the inverse of -m
+     * mod r.
+     */
+    mp_int *minus_minv_mod_r;
+
+    /*
+     * r^1, r^2 and r^3 mod m, which are used for various purposes.
+     *
+     * (Annoyingly, this is one of the rare cases where it would have
+     * been nicer to have a Pascal-style 1-indexed array. I couldn't
+     * _quite_ bring myself to put a gratuitous zero element in here.
+     * So you just have to live with getting r^k by taking the [k-1]th
+     * element of this array.)
+     */
+    mp_int *powers_of_r_mod_m[3];
+
+    /*
+     * Persistent scratch space from which monty_* functions can
+     * allocate storage for intermediate values.
+     */
+    mp_int *scratch;
+};
+
+/* Functions shared between mpint.c and mpunsafe.c */
+mp_int *mp_make_sized(size_t nw);

+ 307 - 0
source/putty/crypto/prng.c

@@ -0,0 +1,307 @@
+/*
+ * PuTTY's cryptographic pseudorandom number generator.
+ *
+ * This module just defines the PRNG object type and its methods. The
+ * usual global instance of it is managed by sshrand.c.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+#include "mpint_i.h"
+
+#ifdef PRNG_DIAGNOSTICS
+#define prngdebug debug
+#else
+#define prngdebug(...) ((void)0)
+#endif
+
+/*
+ * This random number generator is based on the 'Fortuna' design by
+ * Niels Ferguson and Bruce Schneier. The biggest difference is that I
+ * use SHA-256 in place of a block cipher: the generator side of the
+ * system works by computing HASH(key || counter) instead of
+ * ENCRYPT(counter, key).
+ *
+ * Rationale: the Fortuna description itself suggests that using
+ * SHA-256 would be nice but people wouldn't accept it because it's
+ * too slow - but PuTTY isn't a heavy enough user of random numbers to
+ * make that a serious worry. In fact even with SHA-256 this generator
+ * is faster than the one we previously used. Also the Fortuna
+ * description worries about periodic rekeying to avoid the barely
+ * detectable pattern of never repeating a cipher block - but with
+ * SHA-256, even that shouldn't be a worry, because the output
+ * 'blocks' are twice the size, and also SHA-256 has no guarantee of
+ * bijectivity, so it surely _could_ be possible to generate the same
+ * block from two counter values. Thirdly, Fortuna has to have a hash
+ * function anyway, for reseeding and entropy collection, so reusing
+ * the same one means it only depends on one underlying primitive and
+ * can be easily reinstantiated with a larger hash function if you
+ * decide you'd like to do that on a particular occasion.
+ */
+
+#define NCOLLECTORS 32
+#define RESEED_DATA_SIZE 64
+
+typedef struct prng_impl prng_impl;
+struct prng_impl {
+    prng Prng;
+
+    const ssh_hashalg *hashalg;
+
+    /*
+     * Generation side:
+     *
+     * 'generator' is a hash object with the current key preloaded
+     * into it. The counter-mode generation is achieved by copying
+     * that hash object, appending the counter value to the copy, and
+     * calling ssh_hash_final.
+     */
+    ssh_hash *generator;
+    BignumInt counter[128 / BIGNUM_INT_BITS];
+
+    /*
+     * When re-seeding the generator, you call prng_seed_begin(),
+     * which sets up a hash object in 'keymaker'. You write your new
+     * seed data into it (which you can do by calling put_data on the
+     * PRNG object itself) and then call prng_seed_finish(), which
+     * finalises this hash and uses the output to set up the new
+     * generator.
+     *
+     * The keymaker hash preimage includes the previous key, so if you
+     * just want to change keys for the sake of not keeping the same
+     * one for too long, you don't have to put any extra seed data in
+     * at all.
+     */
+    ssh_hash *keymaker;
+
+    /*
+     * Collection side:
+     *
+     * There are NCOLLECTORS hash objects collecting entropy. Each
+     * separately numbered entropy source puts its output into those
+     * hash objects in the order 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,...,
+     * that is to say, each entropy source has a separate counter
+     * which is incremented every time that source generates an event,
+     * and the event data is added to the collector corresponding to
+     * the index of the lowest set bit in the current counter value.
+     *
+     * Whenever collector #0 has at least RESEED_DATA_SIZE bytes (and
+     * it's not at least 100ms since the last reseed), the PRNG is
+     * reseeded, with seed data on reseed #n taken from the first j
+     * collectors, where j is one more than the number of factors of 2
+     * in n. That is, collector #0 is used in every reseed; #1 in
+     * every other one, #2 in every fourth, etc.
+     *
+     * 'until_reseed' counts the amount of data that still needs to be
+     * added to collector #0 before a reseed will be triggered.
+     */
+    uint32_t source_counters[NOISE_MAX_SOURCES];
+    ssh_hash *collectors[NCOLLECTORS];
+    size_t until_reseed;
+    uint32_t reseeds;
+    uint64_t last_reseed_time;
+};
+
+static void prng_seed_BinarySink_write(
+    BinarySink *bs, const void *data, size_t len);
+
+prng *prng_new(const ssh_hashalg *hashalg)
+{
+    prng_impl *pi = snew(prng_impl);
+
+    memset(pi, 0, sizeof(prng_impl));
+    pi->hashalg = hashalg;
+    pi->keymaker = NULL;
+    pi->generator = NULL;
+    memset(pi->counter, 0, sizeof(pi->counter));
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < NCOLLECTORS; i++)
+        pi->collectors[i] = ssh_hash_new(pi->hashalg);
+    pi->until_reseed = 0;
+    BinarySink_INIT(&pi->Prng, prng_seed_BinarySink_write);
+
+    pi->Prng.savesize = pi->hashalg->hlen * 4;
+
+    return &pi->Prng;
+    } // WINSCP
+}
+
+void prng_free(prng *pr)
+{
+    prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+    smemclr(pi->counter, sizeof(pi->counter));
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < NCOLLECTORS; i++)
+        ssh_hash_free(pi->collectors[i]);
+    if (pi->generator)
+        ssh_hash_free(pi->generator);
+    if (pi->keymaker)
+        ssh_hash_free(pi->keymaker);
+    smemclr(pi, sizeof(*pi));
+    sfree(pi);
+    } // WINSCP
+}
+
+void prng_seed_begin(prng *pr)
+{
+    prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+    assert(!pi->keymaker);
+
+    prngdebug("prng: reseed begin\n");
+
+    /*
+     * Make a hash instance that will generate the key for the new one.
+     */
+    if (pi->generator) {
+        pi->keymaker = pi->generator;
+        pi->generator = NULL;
+    } else {
+        pi->keymaker = ssh_hash_new(pi->hashalg);
+    }
+
+    put_byte(pi->keymaker, 'R');
+}
+
+static void prng_seed_BinarySink_write(
+    BinarySink *bs, const void *data, size_t len)
+{
+    prng *pr = BinarySink_DOWNCAST(bs, prng);
+    prng_impl *pi = container_of(pr, prng_impl, Prng);
+    assert(pi->keymaker);
+    prngdebug("prng: got %"SIZEu" bytes of seed\n", len);
+    put_data(pi->keymaker, data, len);
+}
+
+void prng_seed_finish(prng *pr)
+{
+    prng_impl *pi = container_of(pr, prng_impl, Prng);
+    unsigned char buf[MAX_HASH_LEN];
+
+    assert(pi->keymaker);
+
+    prngdebug("prng: reseed finish\n");
+
+    /*
+     * Actually generate the key.
+     */
+    ssh_hash_final(pi->keymaker, buf);
+    pi->keymaker = NULL;
+
+    /*
+     * Load that key into a fresh hash instance, which will become the
+     * new generator.
+     */
+    assert(!pi->generator);
+    pi->generator = ssh_hash_new(pi->hashalg);
+    put_data(pi->generator, buf, pi->hashalg->hlen);
+
+    pi->until_reseed = RESEED_DATA_SIZE;
+    pi->last_reseed_time = prng_reseed_time_ms();
+
+    smemclr(buf, sizeof(buf));
+}
+
+static inline void prng_generate(prng_impl *pi, void *outbuf)
+{
+    ssh_hash *h = ssh_hash_copy(pi->generator);
+
+    prngdebug("prng_generate\n");
+    put_byte(h, 'G');
+    { // WINSCP
+    unsigned i; // WINSCP
+    for (i = 0; i < 128; i += 8)
+        put_byte(h, pi->counter[i/BIGNUM_INT_BITS] >> (i%BIGNUM_INT_BITS));
+    { // WINSCP
+    BignumCarry c = 1;
+    for (i = 0; i < lenof(pi->counter); i++)
+        BignumADC(pi->counter[i], c, pi->counter[i], 0, c);
+    ssh_hash_final(h, outbuf);
+    } // WINSCP
+    } // WINSCP
+}
+
+void prng_read(prng *pr, void *vout, size_t size)
+{
+    prng_impl *pi = container_of(pr, prng_impl, Prng);
+    unsigned char buf[MAX_HASH_LEN];
+
+    assert(!pi->keymaker);
+
+    prngdebug("prng_read %"SIZEu"\n", size);
+
+    { // WINSCP
+    uint8_t *out = (uint8_t *)vout;
+    while (size > 0) {
+        prng_generate(pi, buf);
+        { // WINSCP
+        size_t to_use = size > pi->hashalg->hlen ? pi->hashalg->hlen : size;
+        memcpy(out, buf, to_use);
+        out += to_use;
+        size -= to_use;
+        } // WINSCP
+    }
+
+    smemclr(buf, sizeof(buf));
+
+    prng_seed_begin(&pi->Prng);
+    prng_seed_finish(&pi->Prng);
+    } // WINSCP
+}
+
+void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data)
+{
+    prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+    pinitassert(source_id < NOISE_MAX_SOURCES);
+    uint32_t counter = ++pi->source_counters[source_id];
+
+    size_t index = 0;
+    while (index+1 < NCOLLECTORS && !(counter & 1)) {
+        counter >>= 1;
+        index++;
+    }
+
+    prngdebug("prng_add_entropy source=%u size=%"SIZEu" -> collector %zi\n",
+              source_id, data.len, index);
+
+    put_datapl(pi->collectors[index], data);
+
+    if (index == 0)
+        pi->until_reseed = (pi->until_reseed < data.len ? 0 :
+                            pi->until_reseed - data.len);
+
+    if (pi->until_reseed == 0 &&
+        prng_reseed_time_ms() - pi->last_reseed_time >= 100) {
+        prng_seed_begin(&pi->Prng);
+
+        { // WINSCP
+        unsigned char buf[MAX_HASH_LEN];
+        uint32_t reseed_index = ++pi->reseeds;
+        prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index);
+        { // WINSCP
+        size_t i; // WINSCP
+        for (i = 0; i < NCOLLECTORS; i++) {
+            prngdebug("emptying collector %"SIZEu"\n", i);
+            ssh_hash_digest(pi->collectors[i], buf);
+            put_data(&pi->Prng, buf, pi->hashalg->hlen);
+            ssh_hash_reset(pi->collectors[i]);
+            if (reseed_index & 1)
+                break;
+            reseed_index >>= 1;
+        }
+        smemclr(buf, sizeof(buf));
+        prng_seed_finish(&pi->Prng);
+        } // WINSCP
+        } // WINSCP
+    }
+}
+
+size_t prng_seed_bits(prng *pr)
+{
+    prng_impl *pi = container_of(pr, prng_impl, Prng);
+    return pi->hashalg->hlen * 8;
+}

+ 32 - 0
source/putty/crypto/pubkey-pem.c

@@ -0,0 +1,32 @@
+/*
+ * Convenience functions to encrypt and decrypt OpenSSH PEM format for
+ * SSH-2 private key files. This uses triple-DES in SSH-2 style (one
+ * CBC layer), with three distinct keys, and an IV also generated from
+ * the passphrase.
+ */
+
+#include "ssh.h"
+
+static ssh_cipher *des3_pubkey_ossh_cipher(const void *vkey, const void *viv)
+{
+    ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh2);
+    ssh_cipher_setkey(c, vkey);
+    ssh_cipher_setiv(c, viv);
+    return c;
+}
+
+void des3_decrypt_pubkey_ossh(const void *vkey, const void *viv,
+                              void *vblk, int len)
+{
+    ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv);
+    ssh_cipher_decrypt(c, vblk, len);
+    ssh_cipher_free(c);
+}
+
+void des3_encrypt_pubkey_ossh(const void *vkey, const void *viv,
+                              void *vblk, int len)
+{
+    ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv);
+    ssh_cipher_encrypt(c, vblk, len);
+    ssh_cipher_free(c);
+}

+ 29 - 0
source/putty/crypto/pubkey-ppk.c

@@ -0,0 +1,29 @@
+/*
+ * Convenience functions to encrypt and decrypt PuTTY's own .PPK
+ * format for SSH-2 private key files, which uses 256-bit AES in CBC
+ * mode.
+ */
+
+#include "ssh.h"
+
+static ssh_cipher *aes256_pubkey_cipher(const void *key, const void *iv)
+{
+    ssh_cipher *cipher = ssh_cipher_new(&ssh_aes256_cbc);
+    ssh_cipher_setkey(cipher, key);
+    ssh_cipher_setiv(cipher, iv);
+    return cipher;
+}
+
+void aes256_encrypt_pubkey(const void *key, const void *iv, void *blk, int len)
+{
+    ssh_cipher *c = aes256_pubkey_cipher(key, iv);
+    ssh_cipher_encrypt(c, blk, len);
+    ssh_cipher_free(c);
+}
+
+void aes256_decrypt_pubkey(const void *key, const void *iv, void *blk, int len)
+{
+    ssh_cipher *c = aes256_pubkey_cipher(key, iv);
+    ssh_cipher_decrypt(c, blk, len);
+    ssh_cipher_free(c);
+}

+ 1147 - 0
source/putty/crypto/rsa.c

@@ -0,0 +1,1147 @@
+/*
+ * RSA implementation for PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "mpint.h"
+#include "misc.h"
+
+void BinarySource_get_rsa_ssh1_pub(
+    BinarySource *src, RSAKey *rsa, RsaSsh1Order order)
+{
+    unsigned bits;
+    mp_int *e, *m;
+
+    bits = get_uint32(src);
+    if (order == RSA_SSH1_EXPONENT_FIRST) {
+        e = get_mp_ssh1(src);
+        m = get_mp_ssh1(src);
+    } else {
+        m = get_mp_ssh1(src);
+        e = get_mp_ssh1(src);
+    }
+
+    if (rsa) {
+        rsa->bits = bits;
+        rsa->exponent = e;
+        rsa->modulus = m;
+        rsa->bytes = (mp_get_nbits(m) + 7) / 8;
+    } else {
+        mp_free(e);
+        mp_free(m);
+    }
+}
+
+void BinarySource_get_rsa_ssh1_priv(
+    BinarySource *src, RSAKey *rsa)
+{
+    rsa->private_exponent = get_mp_ssh1(src);
+}
+
+key_components *rsa_components(RSAKey *rsa)
+{
+    key_components *kc = key_components_new();
+    key_components_add_text(kc, "key_type", "RSA");
+    key_components_add_mp(kc, "public_modulus", rsa->modulus);
+    key_components_add_mp(kc, "public_exponent", rsa->exponent);
+    if (rsa->private_exponent) {
+        key_components_add_mp(kc, "private_exponent", rsa->private_exponent);
+        key_components_add_mp(kc, "private_p", rsa->p);
+        key_components_add_mp(kc, "private_q", rsa->q);
+        key_components_add_mp(kc, "private_inverse_q_mod_p", rsa->iqmp);
+    }
+    return kc;
+}
+
+RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src)
+{
+    RSAKey *rsa = snew(RSAKey);
+    memset(rsa, 0, sizeof(RSAKey));
+
+    get_rsa_ssh1_pub(src, rsa, RSA_SSH1_MODULUS_FIRST);
+    get_rsa_ssh1_priv(src, rsa);
+
+    /* SSH-1 names p and q the other way round, i.e. we have the
+     * inverse of p mod q and not of q mod p. We swap the names,
+     * because our internal RSA wants iqmp. */
+    rsa->iqmp = get_mp_ssh1(src);
+    rsa->q = get_mp_ssh1(src);
+    rsa->p = get_mp_ssh1(src);
+
+    return rsa;
+}
+
+bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key)
+{
+    mp_int *b1, *b2;
+    int i;
+    unsigned char *p;
+
+    if (key->bytes < length + 4)
+        return false;                  /* RSA key too short! */
+
+    memmove(data + key->bytes - length, data, length);
+    data[0] = 0;
+    data[1] = 2;
+
+    { // WINSCP
+    size_t npad = key->bytes - length - 3;
+    /*
+     * Generate a sequence of nonzero padding bytes. We do this in a
+     * reasonably uniform way and without having to loop round
+     * retrying the random number generation, by first generating an
+     * integer in [0,2^n) for an appropriately large n; then we
+     * repeatedly multiply by 255 to give an integer in [0,255*2^n),
+     * extract the top 8 bits to give an integer in [0,255), and mask
+     * those bits off before multiplying up again for the next digit.
+     * This gives us a sequence of numbers in [0,255), and of course
+     * adding 1 to each of them gives numbers in [1,256) as we wanted.
+     *
+     * (You could imagine this being a sort of fixed-point operation:
+     * given a uniformly random binary _fraction_, multiplying it by k
+     * and subtracting off the integer part will yield you a sequence
+     * of integers each in [0,k). I'm just doing that scaled up by a
+     * power of 2 to avoid the fractions.)
+     */
+    size_t random_bits = (npad + 16) * 8;
+    mp_int *randval = mp_new(random_bits + 8);
+    mp_int *tmp = mp_random_bits(random_bits);
+    mp_copy_into(randval, tmp);
+    mp_free(tmp);
+    for (i = 2; i < key->bytes - length - 1; i++) {
+        mp_mul_integer_into(randval, randval, 255);
+        { // WINSCP
+        uint8_t byte = mp_get_byte(randval, random_bits / 8);
+        assert(byte != 255);
+        data[i] = byte + 1;
+        mp_reduce_mod_2to(randval, random_bits);
+        } // WINSCP
+    }
+    mp_free(randval);
+    data[key->bytes - length - 1] = 0;
+
+    b1 = mp_from_bytes_be(make_ptrlen(data, key->bytes));
+
+    b2 = mp_modpow(b1, key->exponent, key->modulus);
+
+    p = data;
+    for (i = key->bytes; i--;) {
+        *p++ = mp_get_byte(b2, i);
+    }
+
+    mp_free(b1);
+    mp_free(b2);
+
+    return true;
+    } // WINSCP
+}
+
+/*
+ * Compute (base ^ exp) % mod, provided mod == p * q, with p,q
+ * distinct primes, and iqmp is the multiplicative inverse of q mod p.
+ * Uses Chinese Remainder Theorem to speed computation up over the
+ * obvious implementation of a single big modpow.
+ */
+static mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod,
+                          mp_int *p, mp_int *q, mp_int *iqmp)
+{
+    mp_int *pm1, *qm1, *pexp, *qexp, *presult, *qresult;
+    mp_int *diff, *multiplier, *ret0, *ret;
+
+    /*
+     * Reduce the exponent mod phi(p) and phi(q), to save time when
+     * exponentiating mod p and mod q respectively. Of course, since p
+     * and q are prime, phi(p) == p-1 and similarly for q.
+     */
+    pm1 = mp_copy(p);
+    mp_sub_integer_into(pm1, pm1, 1);
+    qm1 = mp_copy(q);
+    mp_sub_integer_into(qm1, qm1, 1);
+    pexp = mp_mod(exp, pm1);
+    qexp = mp_mod(exp, qm1);
+
+    /*
+     * Do the two modpows.
+     */
+    { // WINSCP
+    mp_int *base_mod_p = mp_mod(base, p);
+    presult = mp_modpow(base_mod_p, pexp, p);
+    mp_free(base_mod_p);
+    } // WINSCP
+    { // WINSCP
+    mp_int *base_mod_q = mp_mod(base, q);
+    qresult = mp_modpow(base_mod_q, qexp, q);
+    mp_free(base_mod_q);
+    } // WINSCP
+
+    /*
+     * Recombine the results. We want a value which is congruent to
+     * qresult mod q, and to presult mod p.
+     *
+     * We know that iqmp * q is congruent to 1 * mod p (by definition
+     * of iqmp) and to 0 mod q (obviously). So we start with qresult
+     * (which is congruent to qresult mod both primes), and add on
+     * (presult-qresult) * (iqmp * q) which adjusts it to be congruent
+     * to presult mod p without affecting its value mod q.
+     *
+     * (If presult-qresult < 0, we add p to it to keep it positive.)
+     */
+    { // WINSCP
+    unsigned presult_too_small = mp_cmp_hs(qresult, presult);
+    mp_cond_add_into(presult, presult, p, presult_too_small);
+    } // WINSCP
+
+    diff = mp_sub(presult, qresult);
+    multiplier = mp_mul(iqmp, q);
+    ret0 = mp_mul(multiplier, diff);
+    mp_add_into(ret0, ret0, qresult);
+
+    /*
+     * Finally, reduce the result mod n.
+     */
+    ret = mp_mod(ret0, mod);
+
+    /*
+     * Free all the intermediate results before returning.
+     */
+    mp_free(pm1);
+    mp_free(qm1);
+    mp_free(pexp);
+    mp_free(qexp);
+    mp_free(presult);
+    mp_free(qresult);
+    mp_free(diff);
+    mp_free(multiplier);
+    mp_free(ret0);
+
+    return ret;
+}
+
+/*
+ * Wrapper on crt_modpow that looks up all the right values from an
+ * RSAKey.
+ */
+static mp_int *rsa_privkey_op(mp_int *input, RSAKey *key)
+{
+    return crt_modpow(input, key->private_exponent,
+                      key->modulus, key->p, key->q, key->iqmp);
+}
+
+mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key)
+{
+    return rsa_privkey_op(input, key);
+}
+
+bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key,
+                            strbuf *outbuf)
+{
+    strbuf *data = strbuf_new_nm();
+    bool success = false;
+    BinarySource src[1];
+
+    {
+        mp_int *b = rsa_ssh1_decrypt(input, key);
+        size_t i; // WINSCP
+        for (i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) {
+            put_byte(data, mp_get_byte(b, i));
+        }
+        mp_free(b);
+    }
+
+    BinarySource_BARE_INIT(src, data->u, data->len);
+
+    /* Check PKCS#1 formatting prefix */
+    if (get_byte(src) != 0) goto out;
+    if (get_byte(src) != 2) goto out;
+    while (1) {
+        unsigned char byte = get_byte(src);
+        if (get_err(src)) goto out;
+        if (byte == 0)
+            break;
+    }
+
+    /* Everything else is the payload */
+    success = true;
+    put_data(outbuf, get_ptr(src), get_avail(src));
+
+  out:
+    strbuf_free(data);
+    return success;
+}
+
+static void append_hex_to_strbuf(strbuf *sb, mp_int *x)
+{
+    if (sb->len > 0)
+        put_byte(sb, ',');
+    put_data(sb, "0x", 2);
+    { // WINSCP
+    char *hex = mp_get_hex(x);
+    size_t hexlen = strlen(hex);
+    put_data(sb, hex, hexlen);
+    smemclr(hex, hexlen);
+    sfree(hex);
+    } // WINSCP
+}
+
+char *rsastr_fmt(RSAKey *key)
+{
+    strbuf *sb = strbuf_new();
+
+    append_hex_to_strbuf(sb, key->exponent);
+    append_hex_to_strbuf(sb, key->modulus);
+
+    return strbuf_to_str(sb);
+}
+
+/*
+ * Generate a fingerprint string for the key. Compatible with the
+ * OpenSSH fingerprint code.
+ */
+char *rsa_ssh1_fingerprint(RSAKey *key)
+{
+    unsigned char digest[16];
+    strbuf *out;
+    int i;
+
+    /*
+     * The hash preimage for SSH-1 key fingerprinting consists of the
+     * modulus and exponent _without_ any preceding length field -
+     * just the minimum number of bytes to represent each integer,
+     * stored big-endian, concatenated with no marker at the division
+     * between them.
+     */
+
+    ssh_hash *hash = ssh_hash_new(&ssh_md5);
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;)
+        put_byte(hash, mp_get_byte(key->modulus, i));
+    for (i = (mp_get_nbits(key->exponent) + 7) / 8; i-- > 0 ;)
+        put_byte(hash, mp_get_byte(key->exponent, i));
+    } // WINSCP
+    ssh_hash_final(hash, digest);
+
+    out = strbuf_new();
+    put_fmt(out, "%"SIZEu" ", mp_get_nbits(key->modulus));
+    for (i = 0; i < 16; i++)
+        put_fmt(out, "%s%02x", i ? ":" : "", digest[i]);
+    if (key->comment)
+        put_fmt(out, " %s", key->comment);
+    return strbuf_to_str(out);
+}
+
+/*
+ * Wrap the output of rsa_ssh1_fingerprint up into the same kind of
+ * structure that comes from ssh2_all_fingerprints.
+ */
+char **rsa_ssh1_fake_all_fingerprints(RSAKey *key)
+{
+    char **ret = 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;
+}
+
+/*
+ * Verify that the public data in an RSA key matches the private
+ * data. We also check the private data itself: we ensure that p >
+ * q and that iqmp really is the inverse of q mod p.
+ */
+bool rsa_verify(RSAKey *key)
+{
+    mp_int *n, *ed, *pm1, *qm1;
+    unsigned ok = 1;
+
+    /* Preliminary checks: p,q can't be 0 or 1. (Of course no other
+     * very small value is any good either, but these are the values
+     * we _must_ check for to avoid assertion failures further down
+     * this function.) */
+    if (!(mp_hs_integer(key->p, 2) & mp_hs_integer(key->q, 2)))
+        return false;
+
+    /* n must equal pq. */
+    n = mp_mul(key->p, key->q);
+    ok &= mp_cmp_eq(n, key->modulus);
+    mp_free(n);
+
+    /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */
+    pm1 = mp_copy(key->p);
+    mp_sub_integer_into(pm1, pm1, 1);
+    ed = mp_modmul(key->exponent, key->private_exponent, pm1);
+    mp_free(pm1);
+    ok &= mp_eq_integer(ed, 1);
+    mp_free(ed);
+
+    qm1 = mp_copy(key->q);
+    mp_sub_integer_into(qm1, qm1, 1);
+    ed = mp_modmul(key->exponent, key->private_exponent, qm1);
+    mp_free(qm1);
+    ok &= mp_eq_integer(ed, 1);
+    mp_free(ed);
+
+    /*
+     * Ensure p > q.
+     *
+     * I have seen key blobs in the wild which were generated with
+     * p < q, so instead of rejecting the key in this case we
+     * should instead flip them round into the canonical order of
+     * p > q. This also involves regenerating iqmp.
+     */
+    { // WINSCP
+    mp_int *p_new = mp_max(key->p, key->q);
+    mp_int *q_new = mp_min(key->p, key->q);
+    mp_free(key->p);
+    mp_free(key->q);
+    mp_free(key->iqmp);
+    key->p = p_new;
+    key->q = q_new;
+    key->iqmp = mp_invert(key->q, key->p);
+
+    return ok;
+    } // WINSCP
+}
+
+void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key,
+                          RsaSsh1Order order)
+{
+    put_uint32(bs, mp_get_nbits(key->modulus));
+    if (order == RSA_SSH1_EXPONENT_FIRST) {
+        put_mp_ssh1(bs, key->exponent);
+        put_mp_ssh1(bs, key->modulus);
+    } else {
+        put_mp_ssh1(bs, key->modulus);
+        put_mp_ssh1(bs, key->exponent);
+    }
+}
+
+void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key)
+{
+    rsa_ssh1_public_blob(bs, key, RSA_SSH1_MODULUS_FIRST);
+    put_mp_ssh1(bs, key->private_exponent);
+    put_mp_ssh1(bs, key->iqmp);
+    put_mp_ssh1(bs, key->q);
+    put_mp_ssh1(bs, key->p);
+}
+
+/* Given an SSH-1 public key blob, determine its length. */
+int rsa_ssh1_public_blob_len(ptrlen data)
+{
+    BinarySource src[1];
+
+    BinarySource_BARE_INIT_PL(src, data);
+
+    /* Expect a length word, then exponent and modulus. (It doesn't
+     * even matter which order.) */
+    get_uint32(src);
+    mp_free(get_mp_ssh1(src));
+    mp_free(get_mp_ssh1(src));
+
+    if (get_err(src))
+        return -1;
+
+    /* Return the number of bytes consumed. */
+    return src->pos;
+}
+
+void freersapriv(RSAKey *key)
+{
+    if (key->private_exponent) {
+        mp_free(key->private_exponent);
+        key->private_exponent = NULL;
+    }
+    if (key->p) {
+        mp_free(key->p);
+        key->p = NULL;
+    }
+    if (key->q) {
+        mp_free(key->q);
+        key->q = NULL;
+    }
+    if (key->iqmp) {
+        mp_free(key->iqmp);
+        key->iqmp = NULL;
+    }
+}
+
+void freersakey(RSAKey *key)
+{
+    freersapriv(key);
+    if (key->modulus) {
+        mp_free(key->modulus);
+        key->modulus = NULL;
+    }
+    if (key->exponent) {
+        mp_free(key->exponent);
+        key->exponent = NULL;
+    }
+    if (key->comment) {
+        sfree(key->comment);
+        key->comment = NULL;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Implementation of the ssh-rsa signing key type family.
+ */
+
+struct ssh2_rsa_extra {
+    unsigned signflags;
+};
+
+static void rsa2_freekey(ssh_key *key);   /* forward reference */
+
+static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data)
+{
+    BinarySource src[1];
+    RSAKey *rsa;
+
+    BinarySource_BARE_INIT_PL(src, data);
+    if (!ptrlen_eq_string(get_string(src), "ssh-rsa"))
+        return NULL;
+
+    rsa = snew(RSAKey);
+    rsa->sshk.vt = self;
+    rsa->exponent = get_mp_ssh2(src);
+    rsa->modulus = get_mp_ssh2(src);
+    rsa->private_exponent = NULL;
+    rsa->p = rsa->q = rsa->iqmp = NULL;
+    rsa->comment = NULL;
+
+    if (get_err(src)) {
+        rsa2_freekey(&rsa->sshk);
+        return NULL;
+    }
+
+    return &rsa->sshk;
+}
+
+static void rsa2_freekey(ssh_key *key)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+    freersakey(rsa);
+    sfree(rsa);
+}
+
+static char *rsa2_cache_str(ssh_key *key)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+    return rsastr_fmt(rsa);
+}
+
+static key_components *rsa2_components(ssh_key *key)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+    return rsa_components(rsa);
+}
+
+static void rsa2_public_blob(ssh_key *key, BinarySink *bs)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+
+    put_stringz(bs, "ssh-rsa");
+    put_mp_ssh2(bs, rsa->exponent);
+    put_mp_ssh2(bs, rsa->modulus);
+}
+
+static void rsa2_private_blob(ssh_key *key, BinarySink *bs)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+
+    put_mp_ssh2(bs, rsa->private_exponent);
+    put_mp_ssh2(bs, rsa->p);
+    put_mp_ssh2(bs, rsa->q);
+    put_mp_ssh2(bs, rsa->iqmp);
+}
+
+static ssh_key *rsa2_new_priv(const ssh_keyalg *self,
+                               ptrlen pub, ptrlen priv)
+{
+    BinarySource src[1];
+    ssh_key *sshk;
+    RSAKey *rsa;
+
+    sshk = rsa2_new_pub(self, pub);
+    if (!sshk)
+        return NULL;
+
+    rsa = container_of(sshk, RSAKey, sshk);
+    BinarySource_BARE_INIT_PL(src, priv);
+    rsa->private_exponent = get_mp_ssh2(src);
+    rsa->p = get_mp_ssh2(src);
+    rsa->q = get_mp_ssh2(src);
+    rsa->iqmp = get_mp_ssh2(src);
+
+    if (get_err(src) || !rsa_verify(rsa)) {
+        rsa2_freekey(&rsa->sshk);
+        return NULL;
+    }
+
+    return &rsa->sshk;
+}
+
+static ssh_key *rsa2_new_priv_openssh(const ssh_keyalg *self,
+                                      BinarySource *src)
+{
+    RSAKey *rsa;
+
+    rsa = snew(RSAKey);
+    rsa->sshk.vt = &ssh_rsa;
+    rsa->comment = NULL;
+
+    rsa->modulus = get_mp_ssh2(src);
+    rsa->exponent = get_mp_ssh2(src);
+    rsa->private_exponent = get_mp_ssh2(src);
+    rsa->iqmp = get_mp_ssh2(src);
+    rsa->p = get_mp_ssh2(src);
+    rsa->q = get_mp_ssh2(src);
+
+    if (get_err(src) || !rsa_verify(rsa)) {
+        rsa2_freekey(&rsa->sshk);
+        return NULL;
+    }
+
+    return &rsa->sshk;
+}
+
+static void rsa2_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+
+    put_mp_ssh2(bs, rsa->modulus);
+    put_mp_ssh2(bs, rsa->exponent);
+    put_mp_ssh2(bs, rsa->private_exponent);
+    put_mp_ssh2(bs, rsa->iqmp);
+    put_mp_ssh2(bs, rsa->p);
+    put_mp_ssh2(bs, rsa->q);
+}
+
+static int rsa2_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
+{
+    ssh_key *sshk;
+    RSAKey *rsa;
+    int ret;
+
+    sshk = rsa2_new_pub(self, pub);
+    if (!sshk)
+        return -1;
+
+    rsa = container_of(sshk, RSAKey, sshk);
+    ret = mp_get_nbits(rsa->modulus);
+    rsa2_freekey(&rsa->sshk);
+
+    return ret;
+}
+
+static inline const ssh_hashalg *rsa2_hash_alg_for_flags(
+    unsigned flags, const char **protocol_id_out)
+{
+    const ssh_hashalg *halg;
+    const char *protocol_id;
+
+    if (flags & SSH_AGENT_RSA_SHA2_256) {
+        halg = &ssh_sha256;
+        protocol_id = "rsa-sha2-256";
+    } else if (flags & SSH_AGENT_RSA_SHA2_512) {
+        halg = &ssh_sha512;
+        protocol_id = "rsa-sha2-512";
+    } else {
+        halg = &ssh_sha1;
+        protocol_id = "ssh-rsa";
+    }
+
+    if (protocol_id_out)
+        *protocol_id_out = protocol_id;
+
+    return halg;
+}
+
+static inline ptrlen rsa_pkcs1_prefix_for_hash(const ssh_hashalg *halg)
+{
+    if (halg == &ssh_sha1) {
+        /*
+         * This is the magic ASN.1/DER prefix that goes in the decoded
+         * signature, between the string of FFs and the actual SHA-1
+         * hash value. The meaning of it is:
+         *
+         * 00 -- this marks the end of the FFs; not part of the ASN.1
+         * bit itself
+         *
+         * 30 21 -- a constructed SEQUENCE of length 0x21
+         *    30 09 -- a constructed sub-SEQUENCE of length 9
+         *       06 05 -- an object identifier, length 5
+         *          2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 }
+         *                            (the 1,3 comes from 0x2B = 43 = 40*1+3)
+         *       05 00 -- NULL
+         *    04 14 -- a primitive OCTET STRING of length 0x14
+         *       [0x14 bytes of hash data follows]
+         *
+         * The object id in the middle there is listed as `id-sha1' in
+         * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn
+         * (the ASN module for PKCS #1) and its expanded form is as
+         * follows:
+         *
+         * id-sha1                OBJECT IDENTIFIER ::= {
+         *    iso(1) identified-organization(3) oiw(14) secsig(3)
+         *    algorithms(2) 26 }
+         */
+        static const unsigned char sha1_asn1_prefix[] = {
+            0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B,
+            0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14,
+        };
+        return PTRLEN_FROM_CONST_BYTES(sha1_asn1_prefix);
+    }
+
+    if (halg == &ssh_sha256) {
+        /*
+         * A similar piece of ASN.1 used for signatures using SHA-256,
+         * in the same format but differing only in various length
+         * fields and OID.
+         */
+        static const unsigned char sha256_asn1_prefix[] = {
+            0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60,
+            0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
+            0x05, 0x00, 0x04, 0x20,
+        };
+        return PTRLEN_FROM_CONST_BYTES(sha256_asn1_prefix);
+    }
+
+    if (halg == &ssh_sha512) {
+        /*
+         * And one more for SHA-512.
+         */
+        static const unsigned char sha512_asn1_prefix[] = {
+            0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60,
+            0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
+            0x05, 0x00, 0x04, 0x40,
+        };
+        return PTRLEN_FROM_CONST_BYTES(sha512_asn1_prefix);
+    }
+
+    unreachable("bad hash algorithm for RSA PKCS#1");
+}
+
+static inline size_t rsa_pkcs1_length_of_fixed_parts(const ssh_hashalg *halg)
+{
+    ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg);
+    return halg->hlen + asn1_prefix.len + 2;
+}
+
+static unsigned char *rsa_pkcs1_signature_string(
+    size_t nbytes, const ssh_hashalg *halg, ptrlen data)
+{
+    size_t fixed_parts = rsa_pkcs1_length_of_fixed_parts(halg);
+    pinitassert(nbytes >= fixed_parts);
+    size_t padding = nbytes - fixed_parts;
+
+    ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg);
+
+    unsigned char *bytes = snewn(nbytes, unsigned char);
+
+    bytes[0] = 0;
+    bytes[1] = 1;
+
+    memset(bytes + 2, 0xFF, padding);
+
+    memcpy(bytes + 2 + padding, asn1_prefix.ptr, asn1_prefix.len);
+
+    { // WINSCP
+    ssh_hash *h = ssh_hash_new(halg);
+    put_datapl(h, data);
+    ssh_hash_final(h, bytes + 2 + padding + asn1_prefix.len);
+    } // WINSCP
+
+    return bytes;
+}
+
+static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+    BinarySource src[1];
+    ptrlen type, in_pl;
+    mp_int *in, *out;
+
+    const struct ssh2_rsa_extra *extra =
+        (const struct ssh2_rsa_extra *)key->vt->extra;
+
+    const ssh_hashalg *halg = rsa2_hash_alg_for_flags(extra->signflags, NULL);
+
+    /* Start by making sure the key is even long enough to encode a
+     * signature. If not, everything fails to verify. */
+    size_t nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8;
+    if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg))
+        return false;
+
+    BinarySource_BARE_INIT_PL(src, sig);
+    type = get_string(src);
+    /*
+     * RFC 4253 section 6.6: the signature integer in an ssh-rsa
+     * signature is 'without lengths or padding'. That is, we _don't_
+     * expect the usual leading zero byte if the topmost bit of the
+     * first byte is set. (However, because of the possibility of
+     * BUG_SSH2_RSA_PADDING at the other end, we tolerate it if it's
+     * there.) So we can't use get_mp_ssh2, which enforces that
+     * leading-byte scheme; instead we use get_string and
+     * mp_from_bytes_be, which will tolerate anything.
+     */
+    in_pl = get_string(src);
+    if (get_err(src) || !ptrlen_eq_string(type, key->vt->ssh_id))
+        return false;
+
+    in = mp_from_bytes_be(in_pl);
+    out = mp_modpow(in, rsa->exponent, rsa->modulus);
+    mp_free(in);
+
+
+    { // WINSCP
+    unsigned diff = 0;
+
+    unsigned char *bytes = rsa_pkcs1_signature_string(nbytes, halg, data);
+    size_t i; // WINSCP
+    for (i = 0; i < nbytes; i++)
+        diff |= bytes[nbytes-1 - i] ^ mp_get_byte(out, i);
+    smemclr(bytes, nbytes);
+    sfree(bytes);
+    mp_free(out);
+
+    return diff == 0;
+    } // WINSCP
+}
+
+static void rsa2_sign(ssh_key *key, ptrlen data,
+                      unsigned flags, BinarySink *bs)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+    unsigned char *bytes;
+    size_t nbytes;
+    mp_int *in, *out;
+    const ssh_hashalg *halg;
+    const char *sign_alg_name;
+
+    const struct ssh2_rsa_extra *extra =
+        (const struct ssh2_rsa_extra *)key->vt->extra;
+    flags |= extra->signflags;
+
+    halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name);
+
+    nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8;
+
+    bytes = rsa_pkcs1_signature_string(nbytes, halg, data);
+    in = mp_from_bytes_be(make_ptrlen(bytes, nbytes));
+    smemclr(bytes, nbytes);
+    sfree(bytes);
+
+    out = rsa_privkey_op(in, rsa);
+    mp_free(in);
+
+    put_stringz(bs, sign_alg_name);
+    nbytes = (mp_get_nbits(out) + 7) / 8;
+    put_uint32(bs, nbytes);
+    { // WINSCP
+    size_t i; // WINSCP
+    for (i = 0; i < nbytes; i++)
+        put_byte(bs, mp_get_byte(out, nbytes - 1 - i));
+    } // WINSCP
+
+    mp_free(out);
+}
+
+static char *rsa2_invalid(ssh_key *key, unsigned flags)
+{
+    RSAKey *rsa = container_of(key, RSAKey, sshk);
+    size_t bits = mp_get_nbits(rsa->modulus), nbytes = (bits + 7) / 8;
+    const char *sign_alg_name;
+    const ssh_hashalg *halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name);
+    if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) {
+        return dupprintf(
+            "%"SIZEu"-bit RSA key is too short to generate %s signatures",
+            bits, sign_alg_name);
+    }
+
+    return NULL;
+}
+
+static const struct ssh2_rsa_extra
+    rsa_extra = { 0 },
+    rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 },
+    rsa_sha512_extra = { SSH_AGENT_RSA_SHA2_512 };
+
+// WINSCP
+#define COMMON_KEYALG_FIELDS                    \
+    /*.new_pub =*/ rsa2_new_pub,                    \
+    /*.new_priv =*/ rsa2_new_priv,                  \
+    /*.new_priv_openssh =*/ rsa2_new_priv_openssh,  \
+    /*.freekey =*/ rsa2_freekey,                    \
+    /*.invalid =*/ rsa2_invalid,                    \
+    /*.sign =*/ rsa2_sign,                          \
+    /*.verify =*/ rsa2_verify,                      \
+    /*.public_blob =*/ rsa2_public_blob,            \
+    /*.private_blob =*/ rsa2_private_blob,          \
+    /*.openssh_blob =*/ rsa2_openssh_blob,          \
+    /*.cache_str =*/ rsa2_cache_str,                \
+    /*.components =*/ rsa2_components,              \
+    /*.pubkey_bits =*/ rsa2_pubkey_bits
+#define COMMON_KEYALG_FIELDS2 \
+    /*.cache_id =*/ "rsa2"
+
+const ssh_keyalg ssh_rsa = {
+    // WINSCP
+    COMMON_KEYALG_FIELDS,
+    /*.ssh_id =*/ "ssh-rsa",
+    COMMON_KEYALG_FIELDS2,
+    /*.extra =*/ &rsa_extra,
+    /*.supported_flags =*/ SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512,
+};
+
+const ssh_keyalg ssh_rsa_sha256 = {
+    // WINSCP
+    COMMON_KEYALG_FIELDS,
+    /*.ssh_id =*/ "rsa-sha2-256",
+    COMMON_KEYALG_FIELDS2,
+    /*.extra =*/ &rsa_sha256_extra,
+    /*.supported_flags =*/ 0,
+};
+
+const ssh_keyalg ssh_rsa_sha512 = {
+    // WINSCP
+    COMMON_KEYALG_FIELDS,
+    /*.ssh_id =*/ "rsa-sha2-512",
+    COMMON_KEYALG_FIELDS2,
+    /*.extra =*/ &rsa_sha512_extra,
+    /*.supported_flags =*/ 0,
+};
+
+RSAKey *ssh_rsakex_newkey(ptrlen data)
+{
+    ssh_key *sshk = rsa2_new_pub(&ssh_rsa, data);
+    if (!sshk)
+        return NULL;
+    return container_of(sshk, RSAKey, sshk);
+}
+
+void ssh_rsakex_freekey(RSAKey *key)
+{
+    rsa2_freekey(&key->sshk);
+}
+
+int ssh_rsakex_klen(RSAKey *rsa)
+{
+    return mp_get_nbits(rsa->modulus);
+}
+
+static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen,
+                      void *vdata, int datalen)
+{
+    unsigned char *data = (unsigned char *)vdata;
+    unsigned count = 0;
+
+    ssh_hash *s = ssh_hash_new(h);
+
+    while (datalen > 0) {
+        int i, max = (datalen > h->hlen ? h->hlen : datalen);
+        unsigned char hash[MAX_HASH_LEN];
+
+        ssh_hash_reset(s);
+        assert(h->hlen <= MAX_HASH_LEN);
+        put_data(s, seed, seedlen);
+        put_uint32(s, count);
+        ssh_hash_digest(s, hash);
+        count++;
+
+        for (i = 0; i < max; i++)
+            data[i] ^= hash[i];
+
+        data += max;
+        datalen -= max;
+    }
+
+    ssh_hash_free(s);
+}
+
+strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in)
+{
+    mp_int *b1, *b2;
+    int k, i;
+    char *p;
+    const int HLEN = h->hlen;
+
+    /*
+     * Here we encrypt using RSAES-OAEP. Essentially this means:
+     *
+     *  - we have a SHA-based `mask generation function' which
+     *    creates a pseudo-random stream of mask data
+     *    deterministically from an input chunk of data.
+     *
+     *  - we have a random chunk of data called a seed.
+     *
+     *  - we use the seed to generate a mask which we XOR with our
+     *    plaintext.
+     *
+     *  - then we use _the masked plaintext_ to generate a mask
+     *    which we XOR with the seed.
+     *
+     *  - then we concatenate the masked seed and the masked
+     *    plaintext, and RSA-encrypt that lot.
+     *
+     * The result is that the data input to the encryption function
+     * is random-looking and (hopefully) contains no exploitable
+     * structure such as PKCS1-v1_5 does.
+     *
+     * For a precise specification, see RFC 3447, section 7.1.1.
+     * Some of the variable names below are derived from that, so
+     * it'd probably help to read it anyway.
+     */
+
+    /* k denotes the length in octets of the RSA modulus. */
+    k = (7 + mp_get_nbits(rsa->modulus)) / 8;
+
+    /* The length of the input data must be at most k - 2hLen - 2. */
+    assert(in.len > 0 && in.len <= k - 2*HLEN - 2);
+
+    /* The length of the output data wants to be precisely k. */
+    { // WINSCP
+    strbuf *toret = strbuf_new_nm();
+    int outlen = k;
+    unsigned char *out = strbuf_append(toret, outlen);
+
+    /*
+     * Now perform EME-OAEP encoding. First set up all the unmasked
+     * output data.
+     */
+    /* Leading byte zero. */
+    out[0] = 0;
+    /* At position 1, the seed: HLEN bytes of random data. */
+    random_read(out + 1, HLEN);
+    /* At position 1+HLEN, the data block DB, consisting of: */
+    /* The hash of the label (we only support an empty label here) */
+    hash_simple(h, PTRLEN_LITERAL(""), out + HLEN + 1);
+    /* A bunch of zero octets */
+    memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1));
+    /* A single 1 octet, followed by the input message data. */
+    out[outlen - in.len - 1] = 1;
+    memcpy(out + outlen - in.len, in.ptr, in.len);
+
+    /*
+     * Now use the seed data to mask the block DB.
+     */
+    oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1);
+
+    /*
+     * And now use the masked DB to mask the seed itself.
+     */
+    oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN);
+
+    /*
+     * Now `out' contains precisely the data we want to
+     * RSA-encrypt.
+     */
+    b1 = mp_from_bytes_be(make_ptrlen(out, outlen));
+    b2 = mp_modpow(b1, rsa->exponent, rsa->modulus);
+    p = (char *)out;
+    for (i = outlen; i--;) {
+        *p++ = mp_get_byte(b2, i);
+    }
+    mp_free(b1);
+    mp_free(b2);
+
+    /*
+     * And we're done.
+     */
+    return toret;
+    } // WINSCP
+}
+
+mp_int *ssh_rsakex_decrypt(
+    RSAKey *rsa, const ssh_hashalg *h, ptrlen ciphertext)
+{
+    mp_int *b1, *b2;
+    int outlen, i;
+    unsigned char *out;
+    unsigned char labelhash[64];
+    BinarySource src[1];
+    const int HLEN = h->hlen;
+
+    /*
+     * Decryption side of the RSA key exchange operation.
+     */
+
+    /* The length of the encrypted data should be exactly the length
+     * in octets of the RSA modulus.. */
+    outlen = (7 + mp_get_nbits(rsa->modulus)) / 8;
+    if (ciphertext.len != outlen)
+        return NULL;
+
+    /* Do the RSA decryption, and extract the result into a byte array. */
+    b1 = mp_from_bytes_be(ciphertext);
+    b2 = rsa_privkey_op(b1, rsa);
+    out = snewn(outlen, unsigned char);
+    for (i = 0; i < outlen; i++)
+        out[i] = mp_get_byte(b2, outlen-1-i);
+    mp_free(b1);
+    mp_free(b2);
+
+    /* Do the OAEP masking operations, in the reverse order from encryption */
+    oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN);
+    oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1);
+
+    /* Check the leading byte is zero. */
+    if (out[0] != 0) {
+        sfree(out);
+        return NULL;
+    }
+    /* Check the label hash at position 1+HLEN */
+    assert(HLEN <= lenof(labelhash));
+    hash_simple(h, PTRLEN_LITERAL(""), labelhash);
+    if (memcmp(out + HLEN + 1, labelhash, HLEN)) {
+        sfree(out);
+        return NULL;
+    }
+    /* Expect zero bytes followed by a 1 byte */
+    for (i = 1 + 2 * HLEN; i < outlen; i++) {
+        if (out[i] == 1) {
+            i++;  /* skip over the 1 byte */
+            break;
+        } else if (out[i] != 0) {
+            sfree(out);
+            return NULL;
+        }
+    }
+    /* And what's left is the input message data, which should be
+     * encoded as an ordinary SSH-2 mpint. */
+    BinarySource_BARE_INIT(src, out + i, outlen - i);
+    b1 = get_mp_ssh2(src);
+    sfree(out);
+    if (get_err(src) || get_avail(src) != 0) {
+        mp_free(b1);
+        return NULL;
+    }
+
+    /* Success! */
+    return b1;
+}
+
+static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 };
+static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 };
+
+static const ssh_kex ssh_rsa_kex_sha1 = {
+    "rsa1024-sha1", NULL, KEXTYPE_RSA,
+    &ssh_sha1, &ssh_rsa_kex_extra_sha1,
+};
+
+static const ssh_kex ssh_rsa_kex_sha256 = {
+    "rsa2048-sha256", NULL, KEXTYPE_RSA,
+    &ssh_sha256, &ssh_rsa_kex_extra_sha256,
+};
+
+static const ssh_kex *const rsa_kex_list[] = {
+    &ssh_rsa_kex_sha256,
+    &ssh_rsa_kex_sha1
+};
+
+const ssh_kexes ssh_rsa_kex = { lenof(rsa_kex_list), rsa_kex_list };

+ 10 - 0
source/putty/crypto/sha1-common.c

@@ -0,0 +1,10 @@
+/*
+ * Common variable definitions across all the SHA-1 implementations.
+ */
+
+#include "ssh.h"
+#include "sha1.h"
+
+const uint32_t sha1_initial_state[5] = {
+    0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0,
+};

+ 53 - 0
source/putty/crypto/sha1-select.c

@@ -0,0 +1,53 @@
+/*
+ * Top-level vtables to select a SHA-1 implementation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sha1.h"
+
+static ssh_hash *sha1_select(const ssh_hashalg *alg)
+{
+    static const ssh_hashalg *const real_algs[] = {
+#if HAVE_SHA_NI
+        &ssh_sha1_ni,
+#endif
+#if HAVE_NEON_CRYPTO
+        &ssh_sha1_neon,
+#endif
+        &ssh_sha1_sw,
+        NULL,
+    };
+
+    { // WINSCP
+    size_t i;
+    for (i = 0; real_algs[i]; i++) {
+        const ssh_hashalg *alg = real_algs[i];
+        const struct sha1_extra *alg_extra =
+            (const struct sha1_extra *)alg->extra;
+        if (check_availability(alg_extra))
+            return ssh_hash_new(alg);
+    }
+
+    /* We should never reach the NULL at the end of the list, because
+     * the last non-NULL entry should be software-only SHA-1, which
+     * is always available. */
+    unreachable("sha1_select ran off the end of its list");
+    } // WINSCP
+}
+
+const ssh_hashalg ssh_sha1 = {
+    // WINSCP
+    /*.new =*/ sha1_select,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    /*.hlen =*/ 20,
+    /*.blocklen =*/ 64,
+    HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"),
+    NULL,
+};

+ 176 - 0
source/putty/crypto/sha1-sw.c

@@ -0,0 +1,176 @@
+/*
+ * Software implementation of SHA-1.
+ */
+
+#include "ssh.h"
+#include "sha1.h"
+
+static bool sha1_sw_available(void)
+{
+    /* Software SHA-1 is always available */
+    return true;
+}
+
+static inline uint32_t rol(uint32_t x, unsigned y)
+{
+    return (x << (31 & y)) | (x >> (31 & (uint32_t)(-(int32_t)y))); // WINSCP
+}
+
+static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0)
+{
+    return if0 ^ (ctrl & (if1 ^ if0));
+}
+
+static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z)
+{
+    return (x & y) | (z & (x | y));
+}
+
+static inline uint32_t Par(uint32_t x, uint32_t y, uint32_t z)
+{
+    return (x ^ y ^ z);
+}
+
+static inline void sha1_sw_round(
+    unsigned round_index, const uint32_t *schedule,
+    uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e,
+    uint32_t f, uint32_t constant)
+{
+    *e = rol(*a, 5) + f + *e + schedule[round_index] + constant;
+    *b = rol(*b, 30);
+}
+
+static void sha1_sw_block(uint32_t *core, const uint8_t *block)
+{
+    uint32_t w[SHA1_ROUNDS];
+    uint32_t a,b,c,d,e;
+
+    { // WINSCP
+    size_t t;
+    for (t = 0; t < 16; t++)
+        w[t] = GET_32BIT_MSB_FIRST(block + 4*t);
+
+    { // WINSCP
+    size_t t;
+    for (t = 16; t < SHA1_ROUNDS; t++)
+        w[t] = rol(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1);
+
+    a = core[0]; b = core[1]; c = core[2]; d = core[3];
+    e = core[4];
+
+    { // WINSCP
+    size_t t = 0;
+    size_t u;
+    for (u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) {
+        sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Ch(b,c,d), SHA1_STAGE0_CONSTANT);
+        sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Ch(a,b,c), SHA1_STAGE0_CONSTANT);
+        sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Ch(e,a,b), SHA1_STAGE0_CONSTANT);
+        sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Ch(d,e,a), SHA1_STAGE0_CONSTANT);
+        sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Ch(c,d,e), SHA1_STAGE0_CONSTANT);
+    }
+    { // WINSCP
+    size_t u;
+    for (u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) {
+        sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE1_CONSTANT);
+        sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE1_CONSTANT);
+        sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE1_CONSTANT);
+        sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE1_CONSTANT);
+        sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE1_CONSTANT);
+    }
+    { // WINSCP
+    size_t u;
+    for (u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) {
+        sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Maj(b,c,d), SHA1_STAGE2_CONSTANT);
+        sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Maj(a,b,c), SHA1_STAGE2_CONSTANT);
+        sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Maj(e,a,b), SHA1_STAGE2_CONSTANT);
+        sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Maj(d,e,a), SHA1_STAGE2_CONSTANT);
+        sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Maj(c,d,e), SHA1_STAGE2_CONSTANT);
+    }
+    { // WINSCP
+    size_t u;
+    for (u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) {
+        sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE3_CONSTANT);
+        sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE3_CONSTANT);
+        sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE3_CONSTANT);
+        sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE3_CONSTANT);
+        sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE3_CONSTANT);
+    }
+
+    core[0] += a; core[1] += b; core[2] += c; core[3] += d; core[4] += e;
+
+    smemclr(w, sizeof(w));
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}
+
+typedef struct sha1_sw {
+    uint32_t core[5];
+    sha1_block blk;
+    BinarySink_IMPLEMENTATION;
+    ssh_hash hash;
+} sha1_sw;
+
+static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha1_sw_new(const ssh_hashalg *alg)
+{
+    sha1_sw *s = snew(sha1_sw);
+
+    s->hash.vt = alg;
+    BinarySink_INIT(s, sha1_sw_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+}
+
+static void sha1_sw_reset(ssh_hash *hash)
+{
+    sha1_sw *s = container_of(hash, sha1_sw, hash);
+
+    memcpy(s->core, sha1_initial_state, sizeof(s->core));
+    sha1_block_setup(&s->blk);
+}
+
+static void sha1_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    sha1_sw *copy = container_of(hcopy, sha1_sw, hash);
+    sha1_sw *orig = container_of(horig, sha1_sw, hash);
+
+    memcpy(copy, orig, sizeof(*copy));
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha1_sw_free(ssh_hash *hash)
+{
+    sha1_sw *s = container_of(hash, sha1_sw, hash);
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len)
+{
+    sha1_sw *s = BinarySink_DOWNCAST(bs, sha1_sw);
+
+    while (len > 0)
+        if (sha1_block_write(&s->blk, &vp, &len))
+            sha1_sw_block(s->core, s->blk.block);
+}
+
+static void sha1_sw_digest(ssh_hash *hash, uint8_t *digest)
+{
+    sha1_sw *s = container_of(hash, sha1_sw, hash);
+
+    sha1_block_pad(&s->blk, BinarySink_UPCAST(s));
+    { // WINSCP
+    size_t i;
+    for (i = 0; i < 5; i++)
+        PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]);
+    } // WINSCP
+}
+
+SHA1_VTABLE(sw, "unaccelerated");

+ 115 - 0
source/putty/crypto/sha1.h

@@ -0,0 +1,115 @@
+/*
+ * Definitions likely to be helpful to multiple SHA-1 implementations.
+ */
+
+/*
+ * The 'extra' structure used by SHA-1 implementations is used to
+ * include information about how to check if a given implementation is
+ * available at run time, and whether we've already checked.
+ */
+struct sha1_extra_mutable;
+struct sha1_extra {
+    /* Function to check availability. Might be expensive, so we don't
+     * want to call it more than once. */
+    bool (*check_available)(void);
+
+    /* Point to a writable substructure. */
+    struct sha1_extra_mutable *mut;
+};
+struct sha1_extra_mutable {
+    bool checked_availability;
+    bool is_available;
+};
+static inline bool check_availability(const struct sha1_extra *extra)
+{
+    if (!extra->mut->checked_availability) {
+        extra->mut->is_available = extra->check_available();
+        extra->mut->checked_availability = true;
+    }
+
+    return extra->mut->is_available;
+}
+
+/*
+ * Macro to define a SHA-1 vtable together with its 'extra'
+ * structure.
+ */
+// WINSCP Restore use of impl_display, once we have HW-accelerated variant
+#define SHA1_VTABLE(impl_c, impl_display)                               \
+    static struct sha1_extra_mutable sha1_ ## impl_c ## _extra_mut;     \
+    static const struct sha1_extra sha1_ ## impl_c ## _extra = {        \
+        /* WINSCP */ \
+        /*.check_available =*/ sha1_ ## impl_c ## _available,               \
+        /*.mut =*/ &sha1_ ## impl_c ## _extra_mut,                          \
+    };                                                                  \
+    const ssh_hashalg ssh_sha1_ ## impl_c = {                           \
+        /* WINSCP */ \
+        /*.new =*/ sha1_ ## impl_c ## _new,                                 \
+        /*.reset =*/ sha1_ ## impl_c ## _reset,                             \
+        /*.copyfrom =*/ sha1_ ## impl_c ## _copyfrom,                       \
+        /*.digest =*/ sha1_ ## impl_c ## _digest,                           \
+        /*.free =*/ sha1_ ## impl_c ## _free,                               \
+        /*.hlen =*/ 20,                                                     \
+        /*.blocklen =*/ 64,                                                 \
+        HASHALG_NAMES_BARE("SHA-1"),                 \
+        /*.extra =*/ &sha1_ ## impl_c ## _extra,                            \
+    }
+
+extern const uint32_t sha1_initial_state[5];
+
+#define SHA1_ROUNDS_PER_STAGE 20
+#define SHA1_STAGE0_CONSTANT 0x5a827999
+#define SHA1_STAGE1_CONSTANT 0x6ed9eba1
+#define SHA1_STAGE2_CONSTANT 0x8f1bbcdc
+#define SHA1_STAGE3_CONSTANT 0xca62c1d6
+#define SHA1_ROUNDS (4 * SHA1_ROUNDS_PER_STAGE)
+
+typedef struct sha1_block sha1_block;
+struct sha1_block {
+    uint8_t block[64];
+    size_t used;
+    uint64_t len;
+};
+
+static inline void sha1_block_setup(sha1_block *blk)
+{
+    blk->used = 0;
+    blk->len = 0;
+}
+
+static inline bool sha1_block_write(
+    sha1_block *blk, const void **vdata, size_t *len)
+{
+    size_t blkleft = sizeof(blk->block) - blk->used;
+    size_t chunk = *len < blkleft ? *len : blkleft;
+
+    const uint8_t *p = *vdata;
+    memcpy(blk->block + blk->used, p, chunk);
+    *vdata = p + chunk;
+    *len -= chunk;
+    blk->used += chunk;
+    blk->len += chunk;
+
+    if (blk->used == sizeof(blk->block)) {
+        blk->used = 0;
+        return true;
+    }
+
+    return false;
+}
+
+static inline void sha1_block_pad(sha1_block *blk, BinarySink *bs)
+{
+    uint64_t final_len = blk->len << 3;
+    size_t pad = 1 + (63 & (55 - blk->used));
+
+    put_byte(bs, 0x80);
+    { // WINSCP
+    size_t i;
+    for (i = 1; i < pad; i++)
+        put_byte(bs, 0);
+    put_uint64(bs, final_len);
+
+    assert(blk->used == 0 && "Should have exactly hit a block boundary");
+    } // WINSCP
+}

+ 30 - 0
source/putty/crypto/sha256-common.c

@@ -0,0 +1,30 @@
+/*
+ * Common variable definitions across all the SHA-256 implementations.
+ */
+
+#include "ssh.h"
+#include "sha256.h"
+
+const uint32_t sha256_initial_state[8] = {
+    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
+};
+
+const uint32_t sha256_round_constants[64] = {
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+};

+ 53 - 0
source/putty/crypto/sha256-select.c

@@ -0,0 +1,53 @@
+/*
+ * Top-level vtables to select a SHA-256 implementation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sha256.h"
+
+static ssh_hash *sha256_select(const ssh_hashalg *alg)
+{
+    static const ssh_hashalg *const real_algs[] = {
+#if HAVE_SHA_NI
+        &ssh_sha256_ni,
+#endif
+#if HAVE_NEON_CRYPTO
+        &ssh_sha256_neon,
+#endif
+        &ssh_sha256_sw,
+        NULL,
+    };
+
+    { // WINSCP
+    size_t i;
+    for (i = 0; real_algs[i]; i++) {
+        const ssh_hashalg *alg = real_algs[i];
+        const struct sha256_extra *alg_extra =
+            (const struct sha256_extra *)alg->extra;
+        if (check_availability(alg_extra))
+            return ssh_hash_new(alg);
+    }
+
+    /* We should never reach the NULL at the end of the list, because
+     * the last non-NULL entry should be software-only SHA-256, which
+     * is always available. */
+    unreachable("sha256_select ran off the end of its list");
+    } // WINSCP
+}
+
+const ssh_hashalg ssh_sha256 = {
+    // WINSCP
+    /*.new =*/ sha256_select,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    /*.hlen =*/ 32,
+    /*.blocklen =*/ 64,
+    HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"),
+    NULL,
+};

+ 170 - 0
source/putty/crypto/sha256-sw.c

@@ -0,0 +1,170 @@
+/*
+ * Software implementation of SHA-256.
+ */
+
+#include "ssh.h"
+#include "sha256.h"
+
+#ifndef WINSCP_VS
+
+static bool sha256_sw_available(void)
+{
+    /* Software SHA-256 is always available */
+    return true;
+}
+
+#endif // WINSCP_VS
+
+#ifdef WINSCP_VS
+
+static inline uint32_t ror(uint32_t x, unsigned y)
+{
+    return (x << (31 & /*WINSCP*/(uint32_t)(-(int32_t)y))) | (x >> (31 & y));
+}
+
+static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0)
+{
+    return if0 ^ (ctrl & (if1 ^ if0));
+}
+
+static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z)
+{
+    return (x & y) | (z & (x | y));
+}
+
+static inline uint32_t Sigma_0(uint32_t x)
+{
+    return ror(x,2) ^ ror(x,13) ^ ror(x,22);
+}
+
+static inline uint32_t Sigma_1(uint32_t x)
+{
+    return ror(x,6) ^ ror(x,11) ^ ror(x,25);
+}
+
+static inline uint32_t sigma_0(uint32_t x)
+{
+    return ror(x,7) ^ ror(x,18) ^ (x >> 3);
+}
+
+static inline uint32_t sigma_1(uint32_t x)
+{
+    return ror(x,17) ^ ror(x,19) ^ (x >> 10);
+}
+
+static inline void sha256_sw_round(
+    unsigned round_index, const uint32_t *schedule,
+    uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d,
+    uint32_t *e, uint32_t *f, uint32_t *g, uint32_t *h)
+{
+    uint32_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) +
+        sha256_round_constants[round_index] + schedule[round_index];
+
+    uint32_t t2 = Sigma_0(*a) + Maj(*a,*b,*c);
+
+    *d += t1;
+    *h = t1 + t2;
+}
+
+/*WINSCP static*/ void sha256_sw_block(uint32_t *core, const uint8_t *block)
+{
+    uint32_t w[SHA256_ROUNDS];
+    uint32_t a,b,c,d,e,f,g,h;
+
+    for (size_t t = 0; t < 16; t++)
+        w[t] = GET_32BIT_MSB_FIRST(block + 4*t);
+
+    for (size_t t = 16; t < SHA256_ROUNDS; t++)
+        w[t] = sigma_1(w[t-2]) + w[t-7] + sigma_0(w[t-15]) + w[t-16];
+
+    a = core[0]; b = core[1]; c = core[2]; d = core[3];
+    e = core[4]; f = core[5]; g = core[6]; h = core[7];
+
+    for (size_t t = 0; t < SHA256_ROUNDS; t += 8) {
+        sha256_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h);
+        sha256_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g);
+        sha256_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f);
+        sha256_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e);
+        sha256_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d);
+        sha256_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c);
+        sha256_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b);
+        sha256_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a);
+    }
+
+    core[0] += a; core[1] += b; core[2] += c; core[3] += d;
+    core[4] += e; core[5] += f; core[6] += g; core[7] += h;
+
+    smemclr(w, sizeof(w));
+}
+
+#else // WINSCP_VS
+
+typedef struct sha256_sw {
+    uint32_t core[8];
+    sha256_block blk;
+    BinarySink_IMPLEMENTATION;
+    ssh_hash hash;
+} sha256_sw;
+
+static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha256_sw_new(const ssh_hashalg *alg)
+{
+    sha256_sw *s = snew(sha256_sw);
+
+    s->hash.vt = alg;
+    BinarySink_INIT(s, sha256_sw_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+}
+
+static void sha256_sw_reset(ssh_hash *hash)
+{
+    sha256_sw *s = container_of(hash, sha256_sw, hash);
+
+    memcpy(s->core, sha256_initial_state, sizeof(s->core));
+    sha256_block_setup(&s->blk);
+}
+
+static void sha256_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    sha256_sw *copy = container_of(hcopy, sha256_sw, hash);
+    sha256_sw *orig = container_of(horig, sha256_sw, hash);
+
+    memcpy(copy, orig, sizeof(*copy));
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha256_sw_free(ssh_hash *hash)
+{
+    sha256_sw *s = container_of(hash, sha256_sw, hash);
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len)
+{
+    sha256_sw *s = BinarySink_DOWNCAST(bs, sha256_sw);
+
+    while (len > 0)
+        if (sha256_block_write(&s->blk, &vp, &len))
+            sha256_sw_block(s->core, s->blk.block);
+}
+
+static void sha256_sw_digest(ssh_hash *hash, uint8_t *digest)
+{
+    sha256_sw *s = container_of(hash, sha256_sw, hash);
+
+    sha256_block_pad(&s->blk, BinarySink_UPCAST(s));
+    { // WINSCP
+    size_t i;
+    for (i = 0; i < 8; i++)
+        PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]);
+    } // WINSCP
+}
+
+SHA256_VTABLE(sw, "unaccelerated");
+
+#endif // WINSCP_VS

+ 119 - 0
source/putty/crypto/sha256.h

@@ -0,0 +1,119 @@
+/*
+ * Definitions likely to be helpful to multiple SHA-256 implementations.
+ */
+
+/*
+ * The 'extra' structure used by SHA-256 implementations is used to
+ * include information about how to check if a given implementation is
+ * available at run time, and whether we've already checked.
+ */
+struct sha256_extra_mutable;
+struct sha256_extra {
+    /* Function to check availability. Might be expensive, so we don't
+     * want to call it more than once. */
+    bool (*check_available)(void);
+
+    /* Point to a writable substructure. */
+    struct sha256_extra_mutable *mut;
+};
+struct sha256_extra_mutable {
+    bool checked_availability;
+    bool is_available;
+};
+static inline bool check_availability(const struct sha256_extra *extra)
+{
+    if (!extra->mut->checked_availability) {
+        extra->mut->is_available = extra->check_available();
+        extra->mut->checked_availability = true;
+    }
+
+    return extra->mut->is_available;
+}
+
+/*
+ * Macro to define a SHA-256 vtable together with its 'extra'
+ * structure.
+ */
+// WINSCP Restore use of impl_display, once we have HW-accelerated variant
+#define SHA256_VTABLE(impl_c, impl_display)                             \
+    static struct sha256_extra_mutable sha256_ ## impl_c ## _extra_mut; \
+    static const struct sha256_extra sha256_ ## impl_c ## _extra = {    \
+        /* WINSCP */ \
+        /*.check_available =*/ sha256_ ## impl_c ## _available,             \
+        /*.mut =*/ &sha256_ ## impl_c ## _extra_mut,                        \
+    };                                                                  \
+    const ssh_hashalg ssh_sha256_ ## impl_c = {                         \
+        /* WINSCP */ \
+        /*.new =*/ sha256_ ## impl_c ## _new,                               \
+        /*.reset =*/ sha256_ ## impl_c ## _reset,                           \
+        /*.copyfrom =*/ sha256_ ## impl_c ## _copyfrom,                     \
+        /*.digest =*/ sha256_ ## impl_c ## _digest,                         \
+        /*.free =*/ sha256_ ## impl_c ## _free,                             \
+        /*.hlen =*/ 32,                                                     \
+        /*.blocklen =*/ 64,                                                 \
+        HASHALG_NAMES_BARE("SHA-256"),               \
+        /*.extra =*/ &sha256_ ## impl_c ## _extra,                          \
+    }
+
+extern const uint32_t sha256_initial_state[8];
+extern const uint32_t sha256_round_constants[64];
+
+#define SHA256_ROUNDS 64
+
+typedef struct sha256_block sha256_block;
+struct sha256_block {
+    uint8_t block[64];
+    size_t used;
+    uint64_t len;
+};
+
+static inline void sha256_block_setup(sha256_block *blk)
+{
+    blk->used = 0;
+    blk->len = 0;
+}
+
+#ifdef WINSCP_VS
+
+/*WINSCP static inline*/ bool sha256_block_write(
+    sha256_block *blk, const void **vdata, size_t *len)
+{
+    size_t blkleft = sizeof(blk->block) - blk->used;
+    size_t chunk = *len < blkleft ? *len : blkleft;
+
+    const uint8_t *p = *vdata;
+    memcpy(blk->block + blk->used, p, chunk);
+    *vdata = p + chunk;
+    *len -= chunk;
+    blk->used += chunk;
+    blk->len += chunk;
+
+    if (blk->used == sizeof(blk->block)) {
+        blk->used = 0;
+        return true;
+    }
+
+    return false;
+}
+
+/*WINSCP static inline*/ void sha256_block_pad(sha256_block *blk, BinarySink *bs)
+{
+    uint64_t final_len = blk->len << 3;
+    size_t pad = 1 + (63 & (55 - blk->used));
+
+    put_byte(bs, 0x80);
+    for (size_t i = 1; i < pad; i++)
+        put_byte(bs, 0);
+    put_uint64(bs, final_len);
+
+    assert(blk->used == 0 && "Should have exactly hit a block boundary");
+}
+
+#else
+
+bool sha256_block_write(
+    sha256_block *blk, const void **vdata, size_t *len);
+void sha256_sw_block(uint32_t *core, const uint8_t *block);
+void sha256_block_pad(sha256_block *blk, BinarySink *bs);
+
+#endif

+ 357 - 0
source/putty/crypto/sha3.c

@@ -0,0 +1,357 @@
+/*
+ * SHA-3, as defined in FIPS PUB 202.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "ssh.h"
+
+static inline uint64_t rol(uint64_t x, unsigned shift)
+{
+    unsigned L = (+shift) & 63;
+#pragma option push -w-ngu // WINSCP
+    unsigned R = (-shift) & 63;
+#pragma option pop // WINSCP
+    return (x << L) | (x >> R);
+}
+
+/*
+ * General Keccak is defined such that its state is a 5x5 array of
+ * words which can be any power-of-2 size from 1 up to 64. SHA-3 fixes
+ * on 64, and so do we.
+ *
+ * The number of rounds is defined as 12 + 2k if the word size is 2^k.
+ * Here we have 64-bit words only, so k=6, so 24 rounds always.
+ */
+typedef uint64_t keccak_core_state[5][5];
+#define NROUNDS 24               /* would differ for other word sizes */
+static const uint64_t round_constants[NROUNDS];
+static const unsigned rotation_counts[5][5];
+
+/*
+ * Core Keccak transform: just squodge the state around internally,
+ * without adding or extracting any data from it.
+ */
+static void keccak_transform(keccak_core_state A)
+{
+    union {
+        uint64_t C[5];
+        uint64_t B[5][5];
+    } u;
+
+    unsigned round; // WINSCP
+    for (round = 0; round < NROUNDS; round++) {
+        /* theta step */
+        unsigned x; // WINSCP
+        for (x = 0; x < 5; x++)
+            u.C[x] = A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4];
+        for (x = 0; x < 5; x++) { // WINSCP
+            uint64_t D = rol(u.C[(x+1) % 5], 1) ^ u.C[(x+4) % 5];
+            { // WINSCP
+            unsigned y; // WINSCP
+            for (y = 0; y < 5; y++)
+                A[x][y] ^= D;
+            } // WINSCP
+        }
+
+        /* rho and pi steps */
+        for (x = 0; x < 5; x++) // WINSCP
+        { // WINSCP
+            unsigned y; // WINSCP
+            for (y = 0; y < 5; y++)
+                u.B[y][(2*x+3*y) % 5] = rol(A[x][y], rotation_counts[x][y]);
+        } // WINSCP
+
+        /* chi step */
+        for (x = 0; x < 5; x++) // WINSCP
+        { // WINSCP
+            unsigned y; // WINSCP
+            for (y = 0; y < 5; y++)
+                A[x][y] = u.B[x][y] ^ (u.B[(x+2)%5][y] & ~u.B[(x+1)%5][y]);
+        } // WINSCP
+
+        /* iota step */
+        A[0][0] ^= round_constants[round];
+    }
+
+    smemclr(&u, sizeof(u));
+}
+
+typedef struct {
+    keccak_core_state A;
+    unsigned char bytes[25*8];
+    unsigned char first_pad_byte;
+    size_t bytes_got, bytes_wanted, hash_bytes;
+} keccak_state;
+
+/*
+ * Keccak accumulation function: given a piece of message, add it to
+ * the hash.
+ */
+static void keccak_accumulate(keccak_state *s, const void *vdata, size_t len)
+{
+    const unsigned char *data = (const unsigned char *)vdata;
+
+    while (len >= s->bytes_wanted - s->bytes_got) {
+        size_t b = s->bytes_wanted - s->bytes_got;
+        memcpy(s->bytes + s->bytes_got, data, b);
+        len -= b;
+        data += b;
+
+        { // WINSCP
+        size_t n = 0;
+        unsigned y; // WINSCP
+        for (y = 0; y < 5; y++) {
+            unsigned x; // WINSCP
+            for (x = 0; x < 5; x++) {
+                if (n >= s->bytes_wanted)
+                    break;
+
+                s->A[x][y] ^= GET_64BIT_LSB_FIRST(s->bytes + n);
+                n += 8;
+            }
+        }
+        keccak_transform(s->A);
+
+        s->bytes_got = 0;
+        } // WINSCP
+    }
+
+    memcpy(s->bytes + s->bytes_got, data, len);
+    s->bytes_got += len;
+}
+
+/*
+ * Keccak output function.
+ */
+static void keccak_output(keccak_state *s, void *voutput)
+{
+    unsigned char *output = (unsigned char *)voutput;
+
+    /*
+     * Add message padding.
+     */
+    {
+        unsigned char padding[25*8];
+        size_t len = s->bytes_wanted - s->bytes_got;
+        if (len == 0)
+            len = s->bytes_wanted;
+        memset(padding, 0, len);
+        padding[0] |= s->first_pad_byte;
+        padding[len-1] |= 0x80;
+        keccak_accumulate(s, padding, len);
+    }
+
+    { // WINSCP
+    size_t n = 0;
+    unsigned y; // WINSCP
+    for (y = 0; y < 5; y++) {
+        unsigned x; // WINSCP
+        for (x = 0; x < 5; x++) {
+            size_t to_copy = s->hash_bytes - n;
+            if (to_copy == 0)
+                break;
+            if (to_copy > 8)
+                to_copy = 8;
+            { // WINSCP
+            unsigned char outbytes[8];
+            PUT_64BIT_LSB_FIRST(outbytes, s->A[x][y]);
+            memcpy(output + n, outbytes, to_copy);
+            n += to_copy;
+            } // WINSCP
+        }
+    }
+    } // WINSCP
+}
+
+static void keccak_init(keccak_state *s, unsigned hashbits, unsigned ratebits,
+                        unsigned char first_pad_byte)
+{
+    int x, y;
+
+    assert(hashbits % 8 == 0);
+    assert(ratebits % 8 == 0);
+
+    s->hash_bytes = hashbits / 8;
+    s->bytes_wanted = (25 * 64 - ratebits) / 8;
+    s->bytes_got = 0;
+    s->first_pad_byte = first_pad_byte;
+
+    assert(s->bytes_wanted % 8 == 0);
+
+    for (y = 0; y < 5; y++)
+        for (x = 0; x < 5; x++)
+            s->A[x][y] = 0;
+}
+
+static void keccak_sha3_init(keccak_state *s, int hashbits)
+{
+    keccak_init(s, hashbits, hashbits * 2, 0x06);
+}
+
+static void keccak_shake_init(keccak_state *s, int parambits, int hashbits)
+{
+    keccak_init(s, hashbits, parambits * 2, 0x1f);
+}
+
+/*
+ * Keccak round constants, generated via the LFSR specified in the
+ * Keccak reference by the following piece of Python:
+
+import textwrap
+from functools import reduce
+
+rbytes = [1]
+while len(rbytes) < 7*24:
+    k = rbytes[-1] * 2
+    rbytes.append(k ^ (0x171 * (k >> 8)))
+
+rbits = [byte & 1 for byte in rbytes]
+
+rwords = [sum(rbits[i+j] << ((1 << j) - 1) for j in range(7))
+          for i in range(0, len(rbits), 7)]
+
+print(textwrap.indent("\n".join(textwrap.wrap(", ".join(
+    map("0x{:016x}".format, rwords)))), " "*4))
+
+*/
+
+static const uint64_t round_constants[24] = {
+    // WINSCP (ULL)
+    0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL,
+    0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL,
+    0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL,
+    0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000aULL,
+    0x000000008000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL,
+    0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL,
+    0x000000000000800aULL, 0x800000008000000aULL, 0x8000000080008081ULL,
+    0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL
+};
+
+/*
+ * Keccak per-element rotation counts, generated from the matrix
+ * formula in the Keccak reference by the following piece of Python:
+
+coords = [1, 0]
+while len(coords) < 26:
+    coords.append((2*coords[-2] + 3*coords[-1]) % 5)
+
+matrix = { (coords[i], coords[i+1]) : i for i in range(24) }
+matrix[0,0] = -1
+
+f = lambda t: (t+1) * (t+2) // 2 % 64
+
+for y in range(5):
+    print("    {{{}}},".format(", ".join("{:2d}".format(f(matrix[y,x]))
+                                         for x in range(5))))
+
+*/
+static const unsigned rotation_counts[5][5] = {
+    { 0, 36,  3, 41, 18},
+    { 1, 44, 10, 45,  2},
+    {62,  6, 43, 15, 61},
+    {28, 55, 25, 21, 56},
+    {27, 20, 39,  8, 14},
+};
+
+/*
+ * The PuTTY ssh_hashalg abstraction.
+ */
+struct keccak_hash {
+    keccak_state state;
+    ssh_hash hash;
+    BinarySink_IMPLEMENTATION;
+};
+
+static void keccak_BinarySink_write(BinarySink *bs, const void *p, size_t len)
+{
+    struct keccak_hash *kh = BinarySink_DOWNCAST(bs, struct keccak_hash);
+    keccak_accumulate(&kh->state, p, len);
+}
+
+static ssh_hash *keccak_new(const ssh_hashalg *alg)
+{
+    struct keccak_hash *kh = snew(struct keccak_hash);
+    kh->hash.vt = alg;
+    BinarySink_INIT(kh, keccak_BinarySink_write);
+    BinarySink_DELEGATE_INIT(&kh->hash, kh);
+    return ssh_hash_reset(&kh->hash);
+}
+
+static void keccak_free(ssh_hash *hash)
+{
+    struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash);
+    smemclr(kh, sizeof(*kh));
+    sfree(kh);
+}
+
+static void keccak_copyfrom(ssh_hash *hnew, ssh_hash *hold)
+{
+    struct keccak_hash *khold = container_of(hold, struct keccak_hash, hash);
+    struct keccak_hash *khnew = container_of(hnew, struct keccak_hash, hash);
+    khnew->state = khold->state;
+}
+
+static void keccak_digest(ssh_hash *hash, unsigned char *output)
+{
+    struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash);
+    keccak_output(&kh->state, output);
+}
+
+static void sha3_reset(ssh_hash *hash)
+{
+    struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash);
+    keccak_sha3_init(&kh->state, hash->vt->hlen * 8);
+}
+
+#define DEFINE_SHA3(bits)                       \
+    const ssh_hashalg ssh_sha3_##bits = {       \
+        /* WINSCP */ \
+        /*.new =*/ keccak_new,                      \
+        /*.reset =*/ sha3_reset,                    \
+        /*.copyfrom =*/ keccak_copyfrom,            \
+        /*.digest =*/ keccak_digest,                \
+        /*.free =*/ keccak_free,                    \
+        /*.hlen =*/ bits/8,                         \
+        /*.blocklen =*/ 200 - 2*(bits/8),           \
+        HASHALG_NAMES_BARE("SHA3-" #bits),      \
+        NULL, /* WINSCP */ \
+    }
+
+DEFINE_SHA3(224);
+DEFINE_SHA3(256);
+DEFINE_SHA3(384);
+DEFINE_SHA3(512);
+
+static void shake256_reset(ssh_hash *hash)
+{
+    struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash);
+    keccak_shake_init(&kh->state, 256, hash->vt->hlen * 8);
+}
+
+/*
+ * There is some confusion over the output length parameter for the
+ * SHAKE functions. By my reading, FIPS PUB 202 defines SHAKE256(M,d)
+ * to generate d _bits_ of output. But RFC 8032 (defining Ed448) talks
+ * about "SHAKE256(x,114)" in a context where it definitely means
+ * generating 114 _bytes_ of output.
+ *
+ * Our internal ID therefore suffixes the output length with "bytes",
+ * to be clear which we're talking about
+ */
+
+#define DEFINE_SHAKE(param, hashbytes)                          \
+    const ssh_hashalg ssh_shake##param##_##hashbytes##bytes = { \
+        /* WINSCP */ \
+        /*.new =*/ keccak_new,                                      \
+        /*.reset =*/ shake##param##_reset,                          \
+        /*.copyfrom =*/ keccak_copyfrom,                            \
+        /*.digest =*/ keccak_digest,                                \
+        /*.free =*/ keccak_free,                                    \
+        /*.hlen =*/ hashbytes,                                      \
+        /*.blocklen =*/ 0,                                          \
+        HASHALG_NAMES_BARE("SHAKE" #param),                     \
+        NULL, /*NULL*/ \
+    }
+
+DEFINE_SHAKE(256, 114);

+ 71 - 0
source/putty/crypto/sha512-common.c

@@ -0,0 +1,71 @@
+/*
+ * Common variable definitions across all the SHA-512 implementations.
+ */
+
+#include "ssh.h"
+#include "sha512.h"
+
+const uint64_t sha512_initial_state[8] = {
+    0x6a09e667f3bcc908ULL,
+    0xbb67ae8584caa73bULL,
+    0x3c6ef372fe94f82bULL,
+    0xa54ff53a5f1d36f1ULL,
+    0x510e527fade682d1ULL,
+    0x9b05688c2b3e6c1fULL,
+    0x1f83d9abfb41bd6bULL,
+    0x5be0cd19137e2179ULL,
+};
+
+const uint64_t sha384_initial_state[8] = {
+    0xcbbb9d5dc1059ed8ULL,
+    0x629a292a367cd507ULL,
+    0x9159015a3070dd17ULL,
+    0x152fecd8f70e5939ULL,
+    0x67332667ffc00b31ULL,
+    0x8eb44a8768581511ULL,
+    0xdb0c2e0d64f98fa7ULL,
+    0x47b5481dbefa4fa4ULL,
+};
+
+const uint64_t sha512_round_constants[80] = {
+    0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,
+    0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+    0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+    0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+    0xd807aa98a3030242ULL, 0x12835b0145706fbeULL,
+    0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+    0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL,
+    0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+    0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+    0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+    0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL,
+    0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+    0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL,
+    0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+    0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+    0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+    0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL,
+    0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+    0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL,
+    0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+    0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+    0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+    0xd192e819d6ef5218ULL, 0xd69906245565a910ULL,
+    0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+    0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL,
+    0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+    0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+    0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+    0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL,
+    0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+    0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL,
+    0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+    0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+    0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+    0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL,
+    0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+    0x28db77f523047d84ULL, 0x32caab7b40c72493ULL,
+    0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+    0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+    0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL,
+};

+ 66 - 0
source/putty/crypto/sha512-select.c

@@ -0,0 +1,66 @@
+/*
+ * Top-level vtables to select a SHA-512 implementation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sha512.h"
+
+static const ssh_hashalg *const real_sha512_algs[] = {
+#if HAVE_NEON_SHA512
+    &ssh_sha512_neon,
+#endif
+    &ssh_sha512_sw,
+    NULL,
+};
+
+static const ssh_hashalg *const real_sha384_algs[] = {
+#if HAVE_NEON_SHA512
+    &ssh_sha384_neon,
+#endif
+    &ssh_sha384_sw,
+    NULL,
+};
+
+static ssh_hash *sha512_select(const ssh_hashalg *alg)
+{
+    const ssh_hashalg *const *real_algs =
+        (const ssh_hashalg *const *)alg->extra;
+
+    { // WINSCP
+    size_t i;
+    for (i = 0; real_algs[i]; i++) {
+        const ssh_hashalg *alg = real_algs[i];
+        const struct sha512_extra *alg_extra =
+            (const struct sha512_extra *)alg->extra;
+        if (check_availability(alg_extra))
+            return ssh_hash_new(alg);
+    }
+
+    /* We should never reach the NULL at the end of the list, because
+     * the last non-NULL entry should be software-only SHA-512, which
+     * is always available. */
+    unreachable("sha512_select ran off the end of its list");
+    } // WINSCP
+}
+
+const ssh_hashalg ssh_sha512 = {
+    /*.new =*/ sha512_select,
+    NULL, NULL, NULL, NULL, // WINSCP
+    /*.hlen =*/ 64,
+    /*.blocklen =*/ 128,
+    HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"),
+    /*.extra =*/ real_sha512_algs,
+};
+
+const ssh_hashalg ssh_sha384 = {
+    /*.new =*/ sha512_select,
+    NULL, NULL, NULL, NULL, // WINSCP
+    /*.hlen =*/ 48,
+    /*.blocklen =*/ 128,
+    HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"),
+    /*.extra =*/ real_sha384_algs,
+};

+ 173 - 0
source/putty/crypto/sha512-sw.c

@@ -0,0 +1,173 @@
+/*
+ * Software implementation of SHA-512.
+ */
+
+#include "ssh.h"
+#include "sha512.h"
+
+static bool sha512_sw_available(void)
+{
+    /* Software SHA-512 is always available */
+    return true;
+}
+
+static inline uint64_t ror(uint64_t x, unsigned y)
+{
+#pragma option push -w-ngu // WINSCP
+    return (x << (63 & -y)) | (x >> (63 & y));
+#pragma option pop // WINSCP
+}
+
+static inline uint64_t Ch(uint64_t ctrl, uint64_t if1, uint64_t if0)
+{
+    return if0 ^ (ctrl & (if1 ^ if0));
+}
+
+static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z)
+{
+    return (x & y) | (z & (x | y));
+}
+
+static inline uint64_t Sigma_0(uint64_t x)
+{
+    return ror(x,28) ^ ror(x,34) ^ ror(x,39);
+}
+
+static inline uint64_t Sigma_1(uint64_t x)
+{
+    return ror(x,14) ^ ror(x,18) ^ ror(x,41);
+}
+
+static inline uint64_t sigma_0(uint64_t x)
+{
+    return ror(x,1) ^ ror(x,8) ^ (x >> 7);
+}
+
+static inline uint64_t sigma_1(uint64_t x)
+{
+    return ror(x,19) ^ ror(x,61) ^ (x >> 6);
+}
+
+static inline void sha512_sw_round(
+    unsigned round_index, const uint64_t *schedule,
+    uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d,
+    uint64_t *e, uint64_t *f, uint64_t *g, uint64_t *h)
+{
+    uint64_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) +
+        sha512_round_constants[round_index] + schedule[round_index];
+
+    uint64_t t2 = Sigma_0(*a) + Maj(*a,*b,*c);
+
+    *d += t1;
+    *h = t1 + t2;
+}
+
+static void sha512_sw_block(uint64_t *core, const uint8_t *block)
+{
+    uint64_t w[SHA512_ROUNDS];
+    uint64_t a,b,c,d,e,f,g,h;
+
+    int t;
+
+    for (t = 0; t < 16; t++)
+        w[t] = GET_64BIT_MSB_FIRST(block + 8*t);
+
+    for (t = 16; t < SHA512_ROUNDS; t++)
+        w[t] = w[t-16] + w[t-7] + sigma_0(w[t-15]) + sigma_1(w[t-2]);
+
+    a = core[0]; b = core[1]; c = core[2]; d = core[3];
+    e = core[4]; f = core[5]; g = core[6]; h = core[7];
+
+    for (t = 0; t < SHA512_ROUNDS; t+=8) {
+        sha512_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h);
+        sha512_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g);
+        sha512_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f);
+        sha512_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e);
+        sha512_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d);
+        sha512_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c);
+        sha512_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b);
+        sha512_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a);
+    }
+
+    core[0] += a; core[1] += b; core[2] += c; core[3] += d;
+    core[4] += e; core[5] += f; core[6] += g; core[7] += h;
+
+    smemclr(w, sizeof(w));
+}
+
+typedef struct sha512_sw {
+    uint64_t core[8];
+    sha512_block blk;
+    BinarySink_IMPLEMENTATION;
+    ssh_hash hash;
+} sha512_sw;
+
+static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha512_sw_new(const ssh_hashalg *alg)
+{
+    sha512_sw *s = snew(sha512_sw);
+
+    s->hash.vt = alg;
+    BinarySink_INIT(s, sha512_sw_write);
+    BinarySink_DELEGATE_INIT(&s->hash, s);
+    return &s->hash;
+}
+
+static void sha512_sw_reset(ssh_hash *hash)
+{
+    sha512_sw *s = container_of(hash, sha512_sw, hash);
+    const struct sha512_extra *extra =
+        (const struct sha512_extra *)hash->vt->extra;
+
+    memcpy(s->core, extra->initial_state, sizeof(s->core));
+    sha512_block_setup(&s->blk);
+}
+
+static void sha512_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+    sha512_sw *copy = container_of(hcopy, sha512_sw, hash);
+    sha512_sw *orig = container_of(horig, sha512_sw, hash);
+
+    memcpy(copy, orig, sizeof(*copy));
+    BinarySink_COPIED(copy);
+    BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha512_sw_free(ssh_hash *hash)
+{
+    sha512_sw *s = container_of(hash, sha512_sw, hash);
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len)
+{
+    sha512_sw *s = BinarySink_DOWNCAST(bs, sha512_sw);
+
+    while (len > 0)
+        if (sha512_block_write(&s->blk, &vp, &len))
+            sha512_sw_block(s->core, s->blk.block);
+}
+
+static void sha512_sw_digest(ssh_hash *hash, uint8_t *digest)
+{
+    sha512_sw *s = container_of(hash, sha512_sw, hash);
+
+    sha512_block_pad(&s->blk, BinarySink_UPCAST(s));
+    { // WINSCP
+    size_t i;
+    for (i = 0; i < hash->vt->hlen / 8; i++)
+        PUT_64BIT_MSB_FIRST(digest + 8*i, s->core[i]);
+    } // WINSCP
+}
+
+/*
+ * This implementation doesn't need separate digest methods for
+ * SHA-384 and SHA-512, because the above implementation reads the
+ * hash length out of the vtable.
+ */
+#define sha384_sw_digest sha512_sw_digest
+
+SHA512_VTABLES(sw, "unaccelerated");

+ 137 - 0
source/putty/crypto/sha512.h

@@ -0,0 +1,137 @@
+/*
+ * Definitions likely to be helpful to multiple SHA-512 implementations.
+ */
+
+/*
+ * The 'extra' structure used by SHA-512 implementations is used to
+ * include information about how to check if a given implementation is
+ * available at run time, and whether we've already checked.
+ */
+struct sha512_extra_mutable;
+struct sha512_extra {
+    /* Pointer to the initial state (distinguishes SHA-384 from -512) */
+    const uint64_t *initial_state;
+
+    /* Function to check availability. Might be expensive, so we don't
+     * want to call it more than once. */
+    bool (*check_available)(void);
+
+    /* Point to a writable substructure. */
+    struct sha512_extra_mutable *mut;
+};
+struct sha512_extra_mutable {
+    bool checked_availability;
+    bool is_available;
+};
+static inline bool check_availability(const struct sha512_extra *extra)
+{
+    if (!extra->mut->checked_availability) {
+        extra->mut->is_available = extra->check_available();
+        extra->mut->checked_availability = true;
+    }
+
+    return extra->mut->is_available;
+}
+
+/*
+ * Macro to define a pair of SHA-{384,512} vtables together with their
+ * 'extra' structure.
+ */
+#define SHA512_VTABLES(impl_c, impl_display)                            \
+    static struct sha512_extra_mutable sha512_ ## impl_c ## _extra_mut; \
+    static const struct sha512_extra sha384_ ## impl_c ## _extra = {    \
+        /* WINSCP */ \
+        /*.initial_state =*/ sha384_initial_state,                          \
+        /*.check_available =*/ sha512_ ## impl_c ## _available,             \
+        /*.mut =*/ &sha512_ ## impl_c ## _extra_mut,                        \
+    };                                                                  \
+    static const struct sha512_extra sha512_ ## impl_c ## _extra = {    \
+        /* WINSCP */ \
+        /*.initial_state =*/ sha512_initial_state,                          \
+        /*.check_available =*/ sha512_ ## impl_c ## _available,             \
+        /*.mut =*/ &sha512_ ## impl_c ## _extra_mut,                        \
+    };                                                                  \
+    const ssh_hashalg ssh_sha384_ ## impl_c = {                         \
+        /* WINSCP */ \
+        /*.new =*/ sha512_ ## impl_c ## _new,                               \
+        /*.reset =*/ sha512_ ## impl_c ## _reset,                           \
+        /*.copyfrom =*/ sha512_ ## impl_c ## _copyfrom,                     \
+        /*.digest =*/ sha384_ ## impl_c ## _digest,                         \
+        /*.free =*/ sha512_ ## impl_c ## _free,                             \
+        /*.hlen =*/ 48,                                                     \
+        /*.blocklen =*/ 128,                                                \
+        HASHALG_NAMES_ANNOTATED("SHA-384", impl_display),               \
+        /*.extra =*/ &sha384_ ## impl_c ## _extra,                          \
+    };                                                                  \
+    const ssh_hashalg ssh_sha512_ ## impl_c = {                         \
+        /* WINSCP */ \
+        /*.new =*/ sha512_ ## impl_c ## _new,                               \
+        /*.reset =*/ sha512_ ## impl_c ## _reset,                           \
+        /*.copyfrom =*/ sha512_ ## impl_c ## _copyfrom,                     \
+        /*.digest =*/ sha512_ ## impl_c ## _digest,                         \
+        /*.free =*/ sha512_ ## impl_c ## _free,                             \
+        /*.hlen =*/ 64,                                                     \
+        /*.blocklen =*/ 128,                                                \
+        HASHALG_NAMES_ANNOTATED("SHA-512", impl_display),               \
+        /*.extra =*/ &sha512_ ## impl_c ## _extra,                          \
+    }
+
+extern const uint64_t sha512_initial_state[8];
+extern const uint64_t sha384_initial_state[8];
+extern const uint64_t sha512_round_constants[80];
+
+#define SHA512_ROUNDS 80
+
+typedef struct sha512_block sha512_block;
+struct sha512_block {
+    uint8_t block[128];
+    size_t used;
+    uint64_t lenhi, lenlo;
+};
+
+static inline void sha512_block_setup(sha512_block *blk)
+{
+    blk->used = 0;
+    blk->lenhi = blk->lenlo = 0;
+}
+
+static inline bool sha512_block_write(
+    sha512_block *blk, const void **vdata, size_t *len)
+{
+    size_t blkleft = sizeof(blk->block) - blk->used;
+    size_t chunk = *len < blkleft ? *len : blkleft;
+
+    const uint8_t *p = *vdata;
+    memcpy(blk->block + blk->used, p, chunk);
+    *vdata = p + chunk;
+    *len -= chunk;
+    blk->used += chunk;
+
+    { // WINSCP
+    size_t chunkbits = chunk << 3;
+
+    blk->lenlo += chunkbits;
+    blk->lenhi += (blk->lenlo < chunkbits);
+
+    if (blk->used == sizeof(blk->block)) {
+        blk->used = 0;
+        return true;
+    }
+
+    return false;
+    } // WINSCP
+}
+
+static inline void sha512_block_pad(sha512_block *blk, BinarySink *bs)
+{
+    uint64_t final_lenhi = blk->lenhi;
+    uint64_t final_lenlo = blk->lenlo;
+    size_t pad = 127 & (111 - blk->used);
+
+    put_byte(bs, 0x80);
+    put_padding(bs, pad, 0);
+    put_uint64(bs, final_lenhi);
+    put_uint64(bs, final_lenlo);
+
+    assert(blk->used == 0 && "Should have exactly hit a block boundary");
+}

+ 55 - 0
source/putty/crypto/xdmauth.c

@@ -0,0 +1,55 @@
+/*
+ * Convenience functions to encrypt and decrypt the cookies used in
+ * XDM-AUTHORIZATION-1. 
+ */
+
+#include "ssh.h"
+
+static ssh_cipher *des_xdmauth_cipher(const void *vkeydata)
+{
+    /*
+     * XDM-AUTHORIZATION-1 uses single-DES, but packs the key into 7
+     * bytes, so here we have to repack it manually into the canonical
+     * form where it occupies 8 bytes each with the low bit unused.
+     */
+    const unsigned char *keydata = (const unsigned char *)vkeydata;
+    unsigned char key[8];
+    int i, nbits, j;
+    unsigned int bits;
+
+    bits = 0;
+    nbits = 0;
+    j = 0;
+    for (i = 0; i < 8; i++) {
+        if (nbits < 7) {
+            bits = (bits << 8) | keydata[j];
+            nbits += 8;
+            j++;
+        }
+        key[i] = (bits >> (nbits - 7)) << 1;
+        bits &= ~(0x7F << (nbits - 7));
+        nbits -= 7;
+    }
+
+    { // WINSCP
+    ssh_cipher *c = ssh_cipher_new(&ssh_des);
+    ssh_cipher_setkey(c, key);
+    smemclr(key, sizeof(key));
+    ssh_cipher_setiv(c, key);
+    return c;
+    } // WINSCP
+}
+
+void des_encrypt_xdmauth(const void *keydata, void *blk, int len)
+{
+    ssh_cipher *c = des_xdmauth_cipher(keydata);
+    ssh_cipher_encrypt(c, blk, len);
+    ssh_cipher_free(c);
+}
+
+void des_decrypt_xdmauth(const void *keydata, void *blk, int len)
+{
+    ssh_cipher *c = des_xdmauth_cipher(keydata);
+    ssh_cipher_decrypt(c, blk, len);
+    ssh_cipher_free(c);
+}

+ 7 - 1
source/putty/defs.h

@@ -11,6 +11,7 @@
 #ifndef PUTTY_DEFS_H
 #define PUTTY_DEFS_H
 
+#ifndef WINSCP
 #ifdef NDEBUG
 /*
  * PuTTY is a security project, so assertions are important - if an
@@ -21,6 +22,7 @@
  */
 #error Do not compile this code base with NDEBUG defined!
 #endif
+#endif
 
 #if HAVE_CMAKE_H
 #include "cmake.h"
@@ -31,6 +33,10 @@
 #include <stdio.h>                     /* for __MINGW_PRINTF_FORMAT */
 #include <stdbool.h>
 
+#ifdef WINSCP
+#define HAVE_AES_NI 1
+#endif
+
 #if (!defined WINSCP) && defined _MSC_VER && _MSC_VER < 1800
 /* Work around lack of inttypes.h and strtoumax in older MSVC */
 #define PRIx32 "x"
@@ -41,7 +47,6 @@
 #define SCNu64 "I64u"
 #define SIZEx "Ix"
 #define SIZEu "Iu"
-uintmax_t strtoumax(const char *nptr, char **endptr, int base);
 #else
 #ifndef WINSCP
 // Not needed by the code WinSCP uses
@@ -55,6 +60,7 @@ uintmax_t strtoumax(const char *nptr, char **endptr, int base);
 #define SIZEx "zx"
 #define SIZEu "zu"
 #endif
+uintmax_t strtoumax(const char *nptr, char **endptr, int base);
 
 #if defined __GNUC__ || defined __clang__
 /*

+ 197 - 0
source/putty/proxy/cproxy.c

@@ -0,0 +1,197 @@
+/*
+ * Routines to do cryptographic interaction with proxies in PuTTY.
+ * This is in a separate module from proxy.c, so that it can be
+ * conveniently removed in PuTTYtel by replacing this module with
+ * the stub version nocproxy.c.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "putty.h"
+#include "ssh.h" /* For MD5 support */
+#include "network.h"
+#include "proxy.h"
+#include "marshal.h"
+
+const bool socks5_chap_available = true;
+const bool http_digest_available = true;
+
+strbuf *chap_response(ptrlen challenge, ptrlen password)
+{
+    strbuf *sb = strbuf_new_nm();
+    const ssh2_macalg *alg = &ssh_hmac_md5;
+    mac_simple(alg, password, challenge, strbuf_append(sb, alg->len));
+    return sb;
+}
+
+void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, size_t len)
+{
+    const unsigned char *p = (const unsigned char *)vptr;
+    const char *hexdigits = "0123456789abcdef";
+    while (len-- > 0) {
+        unsigned c = *p++;
+        put_byte(bs, hexdigits[0xF & (c >> 4)]);
+        put_byte(bs, hexdigits[0xF & (c     )]);
+    }
+}
+
+#define put_hex_data(bs, p, len) \
+    BinarySink_put_hex_data(BinarySink_UPCAST(bs), p, len)
+
+const char *const httphashnames[] = {
+    #define DECL_ARRAY(id, str, alg, bits, accepted) str,
+    HTTP_DIGEST_HASHES(DECL_ARRAY)
+    #undef DECL_ARRAY
+};
+
+const bool httphashaccepted[] = {
+    #define DECL_ARRAY(id, str, alg, bits, accepted) accepted,
+    HTTP_DIGEST_HASHES(DECL_ARRAY)
+    #undef DECL_ARRAY
+};
+
+static const ssh_hashalg *const httphashalgs[] = {
+    #define DECL_ARRAY(id, str, alg, bits, accepted) alg,
+    HTTP_DIGEST_HASHES(DECL_ARRAY)
+    #undef DECL_ARRAY
+};
+static const size_t httphashlengths[] = {
+    #define DECL_ARRAY(id, str, alg, bits, accepted) bits/8,
+    HTTP_DIGEST_HASHES(DECL_ARRAY)
+    #undef DECL_ARRAY
+};
+
+void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password,
+                          ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop,
+                          ptrlen nonce, ptrlen opaque, uint32_t nonce_count,
+                          HttpDigestHash hash, bool hash_username)
+{
+    unsigned char a1hash[MAX_HASH_LEN];
+    unsigned char a2hash[MAX_HASH_LEN];
+    unsigned char rsphash[MAX_HASH_LEN];
+    const ssh_hashalg *alg = httphashalgs[hash];
+    size_t hashlen = httphashlengths[hash];
+
+    unsigned char ncbuf[4];
+    PUT_32BIT_MSB_FIRST(ncbuf, nonce_count);
+
+    { // WINSCP
+    unsigned char client_nonce_raw[33];
+    random_read(client_nonce_raw, lenof(client_nonce_raw));
+    { // WINSCP
+    char client_nonce_base64[lenof(client_nonce_raw) / 3 * 4];
+    { // WINSCP
+    unsigned i;
+    for (i = 0; i < lenof(client_nonce_raw)/3; i++)
+        base64_encode_atom(client_nonce_raw + 3*i, 3,
+                           client_nonce_base64 + 4*i);
+
+    /*
+     * RFC 7616 section 3.4.2: the hash "A1" is a hash of
+     * username:realm:password (in the absence of hash names like
+     * "MD5-sess" which as far as I know don't sensibly apply to
+     * proxies and HTTP CONNECT).
+     */
+    { // WINSCP
+    ssh_hash *h = ssh_hash_new(alg);
+    put_datapl(h, username);
+    put_byte(h, ':');
+    put_datapl(h, realm);
+    put_byte(h, ':');
+    put_datapl(h, password);
+    ssh_hash_digest_nondestructive(h, a1hash);
+
+    /*
+     * RFC 7616 section 3.4.3: the hash "A2" is a hash of method:uri
+     * (in the absence of more interesting quality-of-protection
+     * schemes than plain "auth" - e.g. "auth-int" hashes the entire
+     * document as well - which again I don't think make sense in the
+     * context of proxies and CONNECT).
+     */
+    ssh_hash_reset(h);
+    put_datapl(h, method);
+    put_byte(h, ':');
+    put_datapl(h, uri);
+    ssh_hash_digest_nondestructive(h, a2hash);
+
+    /*
+     * RFC 7616 section 3.4.1: the overall output hash in the
+     * "response" parameter of the authorization header is a hash of
+     * A1:nonce:nonce-count:client-nonce:qop:A2, where A1 and A2 are
+     * the hashes computed above.
+     */
+    ssh_hash_reset(h);
+    put_hex_data(h, a1hash, hashlen);
+    put_byte(h, ':');
+    put_datapl(h, nonce);
+    put_byte(h, ':');
+    put_hex_data(h, ncbuf, 4);
+    put_byte(h, ':');
+    put_data(h, client_nonce_base64, lenof(client_nonce_base64));
+    put_byte(h, ':');
+    put_datapl(h, qop);
+    put_byte(h, ':');
+    put_hex_data(h, a2hash, hashlen);
+    ssh_hash_final(h, rsphash);
+
+    /*
+     * Now construct the output header (everything after the initial
+     * "Proxy-Authorization: Digest ") and write it to the provided
+     * BinarySink.
+     */
+    put_datalit(bs, "username=\"");
+    if (hash_username) {
+        /*
+         * RFC 7616 section 3.4.4: if we're hashing the username, we
+         * actually hash username:realm (like a truncated version of
+         * A1 above).
+         */
+        ssh_hash *h = ssh_hash_new(alg);
+        put_datapl(h, username);
+        put_byte(h, ':');
+        put_datapl(h, realm);
+        ssh_hash_final(h, a1hash);
+        put_hex_data(bs, a1hash, hashlen);
+    } else {
+        put_datapl(bs, username);
+    }
+    put_datalit(bs, "\", realm=\"");
+    put_datapl(bs, realm);
+    put_datalit(bs, "\", uri=\"");
+    put_datapl(bs, uri);
+    put_datalit(bs, "\", algorithm=");
+    put_dataz(bs, httphashnames[hash]);
+    put_datalit(bs, ", nonce=\"");
+    put_datapl(bs, nonce);
+    put_datalit(bs, "\", nc=");
+    put_hex_data(bs, ncbuf, 4);
+    put_datalit(bs, ", cnonce=\"");
+    put_data(bs, client_nonce_base64, lenof(client_nonce_base64));
+    put_datalit(bs, "\", qop=");
+    put_datapl(bs, qop);
+    put_datalit(bs, ", response=\"");
+    put_hex_data(bs, rsphash, hashlen);
+    put_datalit(bs, "\"");
+
+    if (opaque.ptr) {
+        put_datalit(bs, ", opaque=\"");
+        put_datapl(bs, opaque);
+        put_datalit(bs, "\"");
+    }
+
+    if (hash_username) {
+        put_datalit(bs, ", userhash=true");
+    }
+
+    smemclr(a1hash, lenof(a1hash));
+    smemclr(a2hash, lenof(a2hash));
+    smemclr(rsphash, lenof(rsphash));
+    smemclr(client_nonce_raw, lenof(client_nonce_raw));
+    smemclr(client_nonce_base64, lenof(client_nonce_base64));
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+    } // WINSCP
+}

+ 99 - 0
source/putty/proxy/cproxy.h

@@ -0,0 +1,99 @@
+/*
+ * Header for the interaction between proxy.c and cproxy.c. Separated
+ * from proxy.h proper so that testcrypt can include it conveniently.
+ */
+
+extern const bool socks5_chap_available;
+strbuf *chap_response(ptrlen challenge, ptrlen password);
+extern const bool http_digest_available;
+
+/*
+ * List macro for the various hash functions defined for HTTP Digest.
+ *
+ * Of these, MD5 is the original one; SHA-256 is unambiguous; but
+ * SHA-512-256 seems to be controversial.
+ *
+ * RFC 7616 doesn't provide a normative reference, or any text
+ * explaining what they mean by it. They apparently expect you to
+ * already know. The problem with that is that there are two plausible
+ * things they _might_ have meant:
+ *
+ *  1. Ordinary SHA-512, truncated to 256 bits by discarding the
+ *     second half of the hash output, per FIPS 180-4 section 7 (which
+ *     says that in general it's OK to truncate hash functions like
+ *     that if you need to). FIPS 180-4 assigns no particular specific
+ *     spelling to this kind of truncated hash.
+ *
+ *  2. The same except that the initial state of the SHA-512 algorithm
+ *     is reset to a different 512-bit vector to ensure that it's a
+ *     distinguishable hash function in its own right, per FIPS 180-4
+ *     section 6.7 (which in turn refers to section 5.3.6.2 for the
+ *     actual initial values). FIPS 180-4 spells this "SHA-512/256".
+ *
+ * The text of RFC 7616 is totally silent as to which of these they
+ * meant. Their spelling is inconsistent: the protocol identifier is
+ * "SHA-512-256", but in some places in the RFC they say
+ * "SHA-512/256", matching FIPS's spelling for the hash in option 2
+ * above. On the other hand, the example authentication exchange in
+ * section 3.9.2 of the RFC contains hashes that are consistent with
+ * option 1 above (a truncation of plain SHA-512).
+ *
+ * Erratum 4897, https://www.rfc-editor.org/errata/eid4897, points out
+ * this ambiguity, and suggests correcting the example exchange to be
+ * consistent with option 2. However, as of 2021-11-27, that erratum
+ * is shown on the RFC Editor website in state "Reported", with no
+ * response (positive _or_ negative) from the RFC authors or anyone
+ * else. (And it was reported in 2016, so it's not as if they haven't
+ * had time.)
+ *
+ * So, which hash should we implement? Perhaps there's a consensus
+ * among existing implementations in the wild?
+ *
+ * I rigged up an HTTP server to present a SHA-512-256 Digest auth
+ * request, and tried various HTTP clients against it. The only HTTP
+ * client I found that accepts 'algorithm="SHA-512-256"' and sends
+ * back an auth attempt quoting the same hash is curl - and curl,
+ * bizarrely, seems to treat "SHA-512-256" as _neither_ of the above
+ * options, but as simply an alias for SHA-256!
+ *
+ * Therefore, I think the only safe answer is to refuse to support
+ * that hash at all: it's too confusing.
+ *
+ * However, I keep it in the list of hashes here, so that we can check
+ * the test case from RFC 7616, because that test case is also the
+ * only test of username hashing. So we reject it in proxy/http.c, but
+ * accept it in the internal function http_digest_response(), and
+ * treat it as option 1 (truncated SHA-512).
+ *
+ * Therefore, the parameters to each invocation of X in the following
+ * list macro are:
+ *
+ *  - internal enum id for the hash
+ *  - protocol identifier string
+ *  - algorithm to use for computing it (as a const ssh_hashalg *)
+ *  - length to truncate the output to
+ *  - whether we accept it in http.c or not.
+ *
+ * Finally, the ordering of the accepted hashes is our preference
+ * order among them if the server offers a choice.
+ */
+#define HTTP_DIGEST_HASHES(X)                                           \
+    X(HTTP_DIGEST_MD5, "MD5", &ssh_md5, 128, true)                      \
+    X(HTTP_DIGEST_SHA256, "SHA-256", &ssh_sha256, 256, true)            \
+    X(HTTP_DIGEST_SHA512_256, "SHA-512-256", &ssh_sha512, 256, false)   \
+    /* end of list */
+
+typedef enum HttpDigestHash {
+    #define DECL_ENUM(id, str, alg, bits, accepted) id,
+    HTTP_DIGEST_HASHES(DECL_ENUM)
+    #undef DECL_ENUM
+    N_HTTP_DIGEST_HASHES
+} HttpDigestHash;
+
+extern const char *const httphashnames[];
+extern const bool httphashaccepted[];
+
+void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password,
+                          ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop,
+                          ptrlen nonce, ptrlen opaque, uint32_t nonce_count,
+                          HttpDigestHash hash, bool hash_username);

+ 715 - 0
source/putty/proxy/http_p.c

@@ -0,0 +1,715 @@
+/*
+ * HTTP CONNECT proxy negotiation.
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "sshcr.h"
+
+static bool read_line(bufchain *input, strbuf *output, bool is_header)
+{
+    char c;
+
+    while (bufchain_try_fetch(input, &c, 1)) {
+        if (is_header && output->len > 0 &&
+            output->s[output->len - 1] == '\n') {
+            /*
+             * A newline terminates the header, provided we're sure it
+             * is _not_ followed by a space or a tab.
+             */
+            if (c != ' ' && c != '\t')
+                goto done;  /* we have a complete header line */
+        } else {
+            put_byte(output, c);
+            bufchain_consume(input, 1);
+
+            if (!is_header && output->len > 0 &&
+                output->s[output->len - 1] == '\n') {
+                /* If we're looking for just a line, not an HTTP
+                 * header, then any newline terminates it. */
+                goto done;
+            }
+        }
+    }
+
+    return false;
+
+  done:
+    strbuf_chomp(output, '\n');
+    strbuf_chomp(output, '\r');
+    return true;
+}
+
+/* Types of HTTP authentication, in preference order. */
+typedef enum HttpAuthType {
+    AUTH_ERROR, /* if an HttpAuthDetails was never satisfactorily filled in */
+    AUTH_NONE,  /* if no auth header is seen, assume no auth required */
+    AUTH_BASIC, /* username + password sent in clear (only keyless base64) */
+    AUTH_DIGEST, /* cryptographic hash, most preferred if available */
+} HttpAuthType;
+
+typedef struct HttpAuthDetails {
+    HttpAuthType auth_type;
+    bool digest_nonce_was_stale;
+    HttpDigestHash digest_hash;
+    strbuf *realm, *nonce, *opaque, *error;
+    bool got_opaque;
+    bool hash_username;
+} HttpAuthDetails;
+
+typedef struct HttpProxyNegotiator {
+    int crLine;
+    strbuf *response, *header, *token;
+    int http_status_pos;
+    size_t header_pos;
+    strbuf *username, *password;
+    int http_status;
+    bool connection_close;
+    HttpAuthDetails *next_auth;
+    bool try_auth_from_conf;
+    strbuf *uri;
+    uint32_t nonce_count;
+    prompts_t *prompts;
+    int username_prompt_index, password_prompt_index;
+    size_t content_length;
+    ProxyNegotiator pn;
+} HttpProxyNegotiator;
+
+static inline HttpAuthDetails *auth_error(HttpAuthDetails *d,
+                                          const char *fmt, ...)
+{
+    d->auth_type = AUTH_ERROR;
+    put_fmt(d->error, "Unable to parse auth header from HTTP proxy");
+    if (fmt) {
+        va_list ap;
+        va_start(ap, fmt);
+        put_datalit(d->error, ": ");
+        put_fmtv(d->error, fmt, ap);
+        va_end(ap);
+    }
+    return d;
+}
+
+static HttpAuthDetails *http_auth_details_new(void)
+{
+    HttpAuthDetails *d = snew(HttpAuthDetails);
+    memset(d, 0, sizeof(*d));
+    d->realm = strbuf_new();
+    d->nonce = strbuf_new();
+    d->opaque = strbuf_new();
+    d->error = strbuf_new();
+    return d;
+}
+
+static void http_auth_details_free(HttpAuthDetails *d)
+{
+    strbuf_free(d->realm);
+    strbuf_free(d->nonce);
+    strbuf_free(d->opaque);
+    strbuf_free(d->error);
+    sfree(d);
+}
+
+static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt)
+{
+    HttpProxyNegotiator *s = snew(HttpProxyNegotiator);
+    memset(s, 0, sizeof(*s));
+    s->pn.vt = vt;
+    s->response = strbuf_new();
+    s->header = strbuf_new();
+    s->token = strbuf_new();
+    s->username = strbuf_new();
+    s->password = strbuf_new_nm();
+    s->uri = strbuf_new();
+    s->nonce_count = 0;
+    /*
+     * Always start with a CONNECT request containing no auth. If the
+     * proxy rejects that, it will tell us what kind of auth it would
+     * prefer.
+     */
+    s->next_auth = http_auth_details_new();
+    s->next_auth->auth_type = AUTH_NONE;
+    return &s->pn;
+}
+
+static void proxy_http_free(ProxyNegotiator *pn)
+{
+    HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn);
+    strbuf_free(s->response);
+    strbuf_free(s->header);
+    strbuf_free(s->token);
+    strbuf_free(s->username);
+    strbuf_free(s->password);
+    strbuf_free(s->uri);
+    http_auth_details_free(s->next_auth);
+    if (s->prompts)
+        free_prompts(s->prompts);
+    sfree(s);
+}
+
+#define HTTP_HEADER_LIST(X) \
+    X(HDR_CONNECTION, "Connection") \
+    X(HDR_CONTENT_LENGTH, "Content-Length") \
+    X(HDR_PROXY_AUTHENTICATE, "Proxy-Authenticate") \
+    /* end of list */
+
+typedef enum HttpHeader {
+    #define ENUM_DEF(id, string) id,
+    HTTP_HEADER_LIST(ENUM_DEF)
+    #undef ENUM_DEF
+    HDR_UNKNOWN
+} HttpHeader;
+
+static inline bool is_whitespace(char c)
+{
+    return (c == ' ' || c == '\t' || c == '\n');
+}
+
+static inline bool is_separator(char c)
+{
+    return (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
+            c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
+            c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
+            c == '{' || c == '}');
+}
+
+#define HTTP_SEPARATORS
+
+static bool get_end_of_header(HttpProxyNegotiator *s)
+{
+    size_t pos = s->header_pos;
+
+    while (pos < s->header->len && is_whitespace(s->header->s[pos]))
+        pos++;
+
+    if (pos == s->header->len) {
+        s->header_pos = pos;
+        return true;
+    }
+
+    return false;
+}
+
+static bool get_token(HttpProxyNegotiator *s)
+{
+    size_t pos = s->header_pos;
+
+    while (pos < s->header->len && is_whitespace(s->header->s[pos]))
+        pos++;
+
+    if (pos == s->header->len)
+        return false;                  /* end of string */
+
+    if (is_separator(s->header->s[pos]))
+        return false;
+
+    strbuf_clear(s->token);
+    while (pos < s->header->len &&
+           !is_whitespace(s->header->s[pos]) &&
+           !is_separator(s->header->s[pos]))
+        put_byte(s->token, s->header->s[pos++]);
+
+    s->header_pos = pos;
+    return true;
+}
+
+static bool get_separator(HttpProxyNegotiator *s, char sep)
+{
+    size_t pos = s->header_pos;
+
+    while (pos < s->header->len && is_whitespace(s->header->s[pos]))
+        pos++;
+
+    if (pos == s->header->len)
+        return false;                  /* end of string */
+
+    if (s->header->s[pos] != sep)
+        return false;
+
+    s->header_pos = ++pos;
+    return true;
+}
+
+static bool get_quoted_string(HttpProxyNegotiator *s)
+{
+    size_t pos = s->header_pos;
+
+    while (pos < s->header->len && is_whitespace(s->header->s[pos]))
+        pos++;
+
+    if (pos == s->header->len)
+        return false;                  /* end of string */
+
+    if (s->header->s[pos] != '"')
+        return false;
+    pos++;
+
+    strbuf_clear(s->token);
+    while (pos < s->header->len && s->header->s[pos] != '"') {
+        if (s->header->s[pos] == '\\') {
+            /* Backslash makes the next char literal, even if it's " or \ */
+            pos++;
+            if (pos == s->header->len)
+                return false;          /* unexpected end of string */
+        }
+        put_byte(s->token, s->header->s[pos++]);
+    }
+
+    if (pos == s->header->len)
+        return false;                  /* no closing quote */
+    pos++;
+
+    s->header_pos = pos;
+    return true;
+}
+
+static HttpAuthDetails *parse_http_auth_header(HttpProxyNegotiator *s)
+{
+    HttpAuthDetails *d = http_auth_details_new();
+
+    /* Default hash for HTTP Digest is MD5, if none specified explicitly */
+    d->digest_hash = HTTP_DIGEST_MD5;
+
+    if (!get_token(s))
+        return auth_error(d, "parse error");
+
+    if (!stricmp(s->token->s, "Basic")) {
+        /* For Basic authentication, we don't need anything else. The
+         * realm string is not required for the protocol. */
+        d->auth_type = AUTH_BASIC;
+        return d;
+    }
+
+    if (!stricmp(s->token->s, "Digest")) {
+        /* Parse all the additional parts of the Digest header. */
+        if (!http_digest_available)
+            return auth_error(d, "Digest authentication not supported");
+
+        /* Parse the rest of the Digest header */
+        while (true) {
+            if (!get_token(s))
+                return auth_error(d, "parse error in Digest header");
+
+            if (!stricmp(s->token->s, "realm")) {
+                if (!get_separator(s, '=') ||
+                    !get_quoted_string(s))
+                    return auth_error(d, "parse error in Digest realm field");
+                put_datapl(d->realm, ptrlen_from_strbuf(s->token));
+            } else if (!stricmp(s->token->s, "nonce")) {
+                if (!get_separator(s, '=') ||
+                    !get_quoted_string(s))
+                    return auth_error(d, "parse error in Digest nonce field");
+                put_datapl(d->nonce, ptrlen_from_strbuf(s->token));
+            } else if (!stricmp(s->token->s, "opaque")) {
+                if (!get_separator(s, '=') ||
+                    !get_quoted_string(s))
+                    return auth_error(d, "parse error in Digest opaque field");
+                put_datapl(d->opaque,
+                           ptrlen_from_strbuf(s->token));
+                d->got_opaque = true;
+            } else if (!stricmp(s->token->s, "stale")) {
+                if (!get_separator(s, '=') ||
+                    !get_token(s))
+                    return auth_error(d, "parse error in Digest stale field");
+                d->digest_nonce_was_stale = !stricmp(
+                    s->token->s, "true");
+            } else if (!stricmp(s->token->s, "userhash")) {
+                if (!get_separator(s, '=') ||
+                    !get_token(s))
+                    return auth_error(d, "parse error in Digest userhash "
+                                      "field");
+                d->hash_username = !stricmp(s->token->s, "true");
+            } else if (!stricmp(s->token->s, "algorithm")) {
+                if (!get_separator(s, '=') ||
+                    !get_token(s))
+                    return auth_error(d, "parse error in Digest algorithm "
+                                      "field");
+                { // WINSCP
+                bool found = false;
+                size_t i;
+
+                for (i = 0; i < N_HTTP_DIGEST_HASHES; i++) {
+                    if (!stricmp(s->token->s, httphashnames[i])) {
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found) {
+                    /* We don't even recognise the name */
+                    return auth_error(d, "Digest hash algorithm '%s' not "
+                                      "recognised", s->token->s);
+                }
+
+                if (!httphashaccepted[i]) {
+                    /* We do recognise the name but we
+                     * don't like it (see comment in cproxy.h) */
+                    return auth_error(d, "Digest hash algorithm '%s' not "
+                                      "supported", s->token->s);
+                }
+
+                d->digest_hash = i;
+                } // WINSCP
+            } else if (!stricmp(s->token->s, "qop")) {
+                if (!get_separator(s, '=') ||
+                    !get_quoted_string(s))
+                    return auth_error(d, "parse error in Digest qop field");
+                if (stricmp(s->token->s, "auth"))
+                    return auth_error(d, "quality-of-protection type '%s' not "
+                                      "supported", s->token->s);
+            } else {
+                /* Ignore any other auth-param */
+                if (!get_separator(s, '=') ||
+                    (!get_quoted_string(s) && !get_token(s)))
+                    return auth_error(d, "parse error in Digest header");
+            }
+
+            if (get_end_of_header(s))
+                break;
+            if (!get_separator(s, ','))
+                return auth_error(d, "parse error in Digest header");
+        }
+        d->auth_type = AUTH_DIGEST;
+        return d;
+    }
+
+    return auth_error(d, "authentication type '%s' not supported",
+                      s->token->s);
+}
+
+static void proxy_http_process_queue(ProxyNegotiator *pn)
+{
+    HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn);
+
+    crBegin(s->crLine);
+
+    /*
+     * Initialise our username and password strbufs from the Conf.
+     */
+    put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username));
+    put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password));
+    if (s->username->len || s->password->len)
+        s->try_auth_from_conf = true;
+
+    /*
+     * Set up the host:port string we're trying to connect to, also
+     * used as the URI string in HTTP Digest auth.
+     */
+    {
+        char dest[512];
+        sk_getaddr(pn->ps->remote_addr, dest, lenof(dest));
+        put_fmt(s->uri, "%s:%d", dest, pn->ps->remote_port);
+    }
+
+    while (true) {
+        /*
+         * Standard prefix for the HTTP CONNECT request.
+         */
+        put_fmt(pn->output,
+                "CONNECT %s HTTP/1.1\r\n"
+                "Host: %s\r\n", s->uri->s, s->uri->s);
+
+        /*
+         * Add an auth header, if we're planning to this time round.
+         */
+        if (s->next_auth->auth_type == AUTH_BASIC) {
+            put_datalit(pn->output, "Proxy-Authorization: Basic ");
+
+            { // WINSCP
+            strbuf *base64_input = strbuf_new_nm();
+            put_datapl(base64_input, ptrlen_from_strbuf(s->username));
+            put_byte(base64_input, ':');
+            put_datapl(base64_input, ptrlen_from_strbuf(s->password));
+
+            { // WINSCP
+            char base64_output[4];
+            size_t i, e; // WINSCP
+            for (i = 0, e = base64_input->len; i < e; i += 3) {
+                base64_encode_atom(base64_input->u + i,
+                                   e-i > 3 ? 3 : e-i, base64_output);
+                put_data(pn->output, base64_output, 4);
+            }
+            strbuf_free(base64_input);
+            smemclr(base64_output, sizeof(base64_output));
+            put_datalit(pn->output, "\r\n");
+            } // WINSCP
+            } // WINSCP
+        } else if (s->next_auth->auth_type == AUTH_DIGEST) {
+            put_datalit(pn->output, "Proxy-Authorization: Digest ");
+
+            /* If we have a fresh nonce, reset the
+             * nonce count. Otherwise, keep incrementing it. */
+            if (!ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->token),
+                                  ptrlen_from_strbuf(s->next_auth->nonce)))
+                s->nonce_count = 0;
+
+            http_digest_response(BinarySink_UPCAST(pn->output),
+                                 ptrlen_from_strbuf(s->username),
+                                 ptrlen_from_strbuf(s->password),
+                                 ptrlen_from_strbuf(s->next_auth->realm),
+                                 PTRLEN_LITERAL("CONNECT"),
+                                 ptrlen_from_strbuf(s->uri),
+                                 PTRLEN_LITERAL("auth"),
+                                 ptrlen_from_strbuf(s->next_auth->nonce),
+                                 (s->next_auth->got_opaque ?
+                                  ptrlen_from_strbuf(s->next_auth->opaque) :
+                                  make_ptrlen(NULL, 0)),
+                                 ++s->nonce_count, s->next_auth->digest_hash,
+                                 s->next_auth->hash_username);
+            put_datalit(pn->output, "\r\n");
+        }
+
+        /*
+         * Blank line to terminate the HTTP request.
+         */
+        put_datalit(pn->output, "\r\n");
+        crReturnV;
+
+        s->content_length = 0;
+        s->connection_close = false;
+
+        /*
+         * Read and parse the HTTP status line, and check if it's a 2xx
+         * for success.
+         */
+        strbuf_clear(s->response);
+        crMaybeWaitUntilV(read_line(pn->input, s->response, false));
+        {
+            int maj_ver, min_ver, n_scanned;
+            n_scanned = sscanf(
+                s->response->s, "HTTP/%d.%d %n%d",
+                &maj_ver, &min_ver, &s->http_status_pos, &s->http_status);
+
+            if (n_scanned < 3) {
+                pn->error = dupstr("HTTP response was absent or malformed");
+                crStopV;
+            }
+
+            if (maj_ver < 1 && (maj_ver == 1 && min_ver < 1)) {
+                /* Before HTTP/1.1, connections close by default */
+                s->connection_close = true;
+            }
+        }
+
+        if (s->http_status == 407) {
+            /*
+             * If this is going to be an auth request, we expect to
+             * see at least one Proxy-Authorization header offering us
+             * auth options. Start by preloading s->next_auth with a
+             * fallback error message, which will be used if nothing
+             * better is available.
+             */
+            http_auth_details_free(s->next_auth);
+            s->next_auth = http_auth_details_new();
+            auth_error(s->next_auth, "no Proxy-Authorization header seen in "
+                       "HTTP 407 Proxy Authentication Required response");
+        }
+
+        /*
+         * Read the HTTP response header section.
+         */
+        do {
+            strbuf_clear(s->header);
+            crMaybeWaitUntilV(read_line(pn->input, s->header, true));
+            s->header_pos = 0;
+
+            if (!get_token(s)) {
+                /* Possibly we ought to panic if we see an HTTP header
+                 * we can't make any sense of at all? But whatever,
+                 * ignore it and hope the next one makes more sense */
+                continue;
+            }
+
+            /* Parse the header name */
+            { // WINSCP
+            HttpHeader hdr = HDR_UNKNOWN;
+            {
+                #define CHECK_HEADER(id, string) \
+                    if (!stricmp(s->token->s, string)) hdr = id;
+                HTTP_HEADER_LIST(CHECK_HEADER);
+                #undef CHECK_HEADER
+            }
+
+            if (!get_separator(s, ':'))
+                continue;
+
+            if (hdr == HDR_CONTENT_LENGTH) {
+                if (!get_token(s))
+                    continue;
+                s->content_length = strtoumax(s->token->s, NULL, 10);
+            } else if (hdr == HDR_CONNECTION) {
+                if (!get_token(s))
+                    continue;
+                if (!stricmp(s->token->s, "close"))
+                    s->connection_close = true;
+                else if (!stricmp(s->token->s, "keep-alive"))
+                    s->connection_close = false;
+            } else if (hdr == HDR_PROXY_AUTHENTICATE) {
+                HttpAuthDetails *auth = parse_http_auth_header(s);
+
+                /*
+                 * See if we prefer this set of auth details to the
+                 * previous one we had (either from a previous auth
+                 * header, or the fallback when no auth header is
+                 * provided at all).
+                 */
+                bool change;
+
+                if (auth->auth_type != s->next_auth->auth_type) {
+                    /* Use the preference order implied by the enum */
+                    change = auth->auth_type > s->next_auth->auth_type;
+                } else if (auth->auth_type == AUTH_DIGEST &&
+                           auth->digest_hash != s->next_auth->digest_hash) {
+                    /* Choose based on the hash functions */
+                    change = auth->digest_hash > s->next_auth->digest_hash;
+                } else {
+                    /*
+                     * If in doubt, go with the later one of the
+                     * headers.
+                     *
+                     * The main reason for this is so that an error in
+                     * interpreting an auth header will supersede the
+                     * default error we preload saying 'no header
+                     * found', because that would be a particularly
+                     * bad error to report if there _was_ one.
+                     *
+                     * But we're in a tie-breaking situation by now,
+                     * so there's no other reason to choose - we might
+                     * as well apply the same policy everywhere else
+                     * too.
+                     */
+                    change = true;
+                }
+
+                if (change) {
+                    http_auth_details_free(s->next_auth);
+                    s->next_auth = auth;
+                } else {
+                    http_auth_details_free(auth);
+                }
+            }
+            } // WINSCP
+        } while (s->header->len > 0);
+
+        /* Read and ignore the entire response document */
+        crMaybeWaitUntilV(bufchain_try_consume(
+                              pn->input, s->content_length));
+
+        if (200 <= s->http_status && s->http_status < 300) {
+            /* Any 2xx HTTP response means we're done */
+            goto authenticated;
+        } else if (s->http_status == 407) {
+            /* 407 is Proxy Authentication Required, which we may be
+             * able to do something about. */
+            if (s->connection_close) {
+                pn->error = dupprintf("HTTP proxy closed connection after "
+                                      "asking for authentication");
+                crStopV;
+            }
+
+            /* If the best we can do is report some kind of error from
+             * a Proxy-Auth header (or an error saying there wasn't
+             * one at all), and no successful parsing of an auth
+             * header superseded that, then just throw that error and
+             * die. */
+            if (s->next_auth->auth_type == AUTH_ERROR) {
+                pn->error = dupstr(s->next_auth->error->s);
+                crStopV;
+            }
+
+            /* If we have auth details from the Conf and haven't tried
+             * them yet, that's our first step. */
+            if (s->try_auth_from_conf) {
+                s->try_auth_from_conf = false;
+                continue;
+            }
+
+            /* If the server sent us stale="true" in a Digest auth
+             * header, that means we _don't_ need to request a new
+             * password yet; just try again with the existing details
+             * and the fresh nonce it sent us. */
+            if (s->next_auth->digest_nonce_was_stale)
+                continue;
+
+            /* Either we never had a password in the first place, or
+             * the one we already presented was rejected. We can only
+             * proceed from here if we have a way to ask the user
+             * questions. */
+            if (!pn->itr) {
+                pn->error = dupprintf("HTTP proxy requested authentication "
+                                      "which we do not have");
+                crStopV;
+            }
+
+            /*
+             * Send some prompts to the user. We'll assume the
+             * password is always required (since it's just been
+             * rejected, even if we did send one before), and we'll
+             * prompt for the username only if we don't have one from
+             * the Conf.
+             */
+            s->prompts = proxy_new_prompts(pn->ps);
+            s->prompts->to_server = true;
+            s->prompts->from_server = false;
+            s->prompts->name = dupstr("HTTP proxy authentication");
+            if (!s->username->len) {
+                s->username_prompt_index = s->prompts->n_prompts;
+                add_prompt(s->prompts, dupstr("Proxy username: "), true);
+            } else {
+                s->username_prompt_index = -1;
+            }
+
+            s->password_prompt_index = s->prompts->n_prompts;
+            add_prompt(s->prompts, dupstr("Proxy password: "), false);
+
+            while (true) {
+                SeatPromptResult spr = seat_get_userpass_input(
+                    interactor_announce(pn->itr), s->prompts);
+                if (spr.kind == SPRK_OK) {
+                    break;
+                } else if (spr_is_abort(spr)) {
+                    proxy_spr_abort(pn, spr);
+                    crStopV;
+                }
+                crReturnV;
+            }
+
+            if (s->username_prompt_index != -1) {
+                strbuf_clear(s->username);
+                put_dataz(s->username,
+                          prompt_get_result_ref(
+                              s->prompts->prompts[s->username_prompt_index]));
+            }
+
+            strbuf_clear(s->password);
+            put_dataz(s->password,
+                      prompt_get_result_ref(
+                          s->prompts->prompts[s->password_prompt_index]));
+
+            free_prompts(s->prompts);
+            s->prompts = NULL;
+        } else {
+            /* Any other HTTP response is treated as permanent failure */
+            pn->error = dupprintf("HTTP response %s",
+                                  s->response->s + s->http_status_pos);
+            crStopV;
+        }
+    }
+
+  authenticated:
+    /*
+     * Success! Hand over to the main connection.
+     */
+    pn->done = true;
+
+    crFinishV;
+}
+
+const struct ProxyNegotiatorVT http_proxy_negotiator_vt = {
+    // WINSCP
+    /*.new =*/ proxy_http_new,
+    /*.process_queue =*/ proxy_http_process_queue,
+    /*.free =*/ proxy_http_free,
+    /*.type =*/ "HTTP",
+};

+ 129 - 0
source/putty/proxy/interactor.c

@@ -0,0 +1,129 @@
+/*
+ * Centralised functions for the Interactor trait.
+ */
+
+#include "putty.h"
+
+Seat *interactor_borrow_seat(Interactor *itr)
+{
+    Seat *clientseat = interactor_get_seat(itr);
+    if (!clientseat)
+        return NULL;
+
+    /* If the client has already had its Seat borrowed, then look
+     * through the existing TempSeat to find the underlying one. */
+    if (is_tempseat(clientseat))
+        return tempseat_get_real(clientseat);
+
+    /* Otherwise, make a new TempSeat and give that to the client. */
+    { // WINSCP
+    Seat *tempseat = tempseat_new(clientseat);
+    interactor_set_seat(itr, tempseat);
+    return clientseat;
+    } // WINSCP
+}
+
+static Interactor *interactor_toplevel(Interactor *itr, unsigned *level_out)
+{
+    /*
+     * Find the Interactor at the top of the chain, so that all the
+     * Interactors in a stack can share that one's last-to-talk field.
+     * Also, count how far we had to go to get to it, to put in the
+     * message.
+     */
+    Interactor *itr_top = itr;
+    unsigned level = 0;
+    while (itr_top->parent) {
+        itr_top = itr_top->parent;
+        level++;
+    }
+
+    if (level_out)
+        *level_out = level;
+    return itr_top;
+}
+
+void interactor_return_seat(Interactor *itr)
+{
+    Seat *tempseat = interactor_get_seat(itr);
+    if (!is_tempseat(tempseat))
+        return;                        /* no-op */
+
+    tempseat_flush(tempseat);
+    { // WINSCP
+    Seat *realseat = tempseat_get_real(tempseat);
+    interactor_set_seat(itr, realseat);
+    tempseat_free(tempseat);
+
+    /*
+     * If we have a parent Interactor, and anyone has ever called
+     * interactor_announce, then all Interactors from now on will
+     * announce themselves even if they have nothing to say.
+     */
+    { // WINSCP
+    Interactor *itr_top = interactor_toplevel(itr, NULL);
+    if (itr_top->last_to_talk)
+        interactor_announce(itr);
+
+    /*
+     * We're about to hand this seat back to the parent Interactor to
+     * do its own thing with. It will typically expect to start in the
+     * same state as if the seat had never been borrowed, i.e. in the
+     * starting trust state.
+     */
+    seat_set_trust_status(realseat, true);
+    } // WINSCP
+    } // WINSCP
+}
+
+InteractionReadySeat interactor_announce(Interactor *itr)
+{
+    Seat *seat = interactor_get_seat(itr);
+    assert(!is_tempseat(seat) &&
+           "Shouldn't call announce when someone else is using our seat");
+
+    { // WINSCP
+    InteractionReadySeat iseat;
+    iseat.seat = seat;
+
+    { // WINSCP
+    unsigned level;
+    Interactor *itr_top = interactor_toplevel(itr, &level);
+
+    /*
+     * Generally, we should announce ourself if the previous
+     * Interactor that said anything was not us. That includes if
+     * there was no previous Interactor to talk (i.e. if we're the
+     * first to say anything) - *except* that the primary Interactor
+     * doesn't need to announce itself, if no proxy has intervened
+     * before it.
+     */
+    bool need_announcement = (itr_top->last_to_talk != itr);
+    if (!itr->parent && !itr_top->last_to_talk)
+        need_announcement = false;
+
+    if (need_announcement) {
+        const char *prefix = "";
+        if (itr_top->last_to_talk != NULL)
+            seat_antispoof_msg(iseat, ""); /* leave a separating blank line */
+
+        { // WINSCP
+        char *desc = interactor_description(itr);
+        char *adjective = (level == 0 ? dupstr("primary") :
+                           level == 1 ? dupstr("proxy") :
+                           dupprintf("proxy^%u", level));
+        char *msg = dupprintf("%sMaking %s %s", prefix, adjective, desc);
+        sfree(adjective);
+        sfree(desc);
+
+        seat_antispoof_msg(iseat, msg);
+        sfree(msg);
+
+        itr_top->last_to_talk = itr;
+        } // WINSCP
+    }
+
+    return iseat;
+    } // WINSCP
+    } // WINSCP
+}

+ 281 - 0
source/putty/proxy/local.c

@@ -0,0 +1,281 @@
+/*
+ * Implement LocalProxyOpener, a centralised system for setting up the
+ * command string to be run by platform-specific local-subprocess
+ * proxy types.
+ *
+ * The platform-specific local proxy code is expected to use this
+ * system by calling local_proxy_opener() from
+ * platform_new_connection(); then using the resulting
+ * DeferredSocketOpener to make a deferred version of whatever local
+ * socket type is used for talking to subcommands (Unix FdSocket,
+ * Windows HandleSocket); then passing the 'Socket *' back to us via
+ * local_proxy_opener_set_socket().
+ *
+ * The LocalProxyOpener object implemented by this code will set
+ * itself up as an Interactor if possible, so that it can prompt for
+ * the proxy username and/or password if they're referred to in the
+ * command string but not given in the config (exactly as the Telnet
+ * proxy does). Once it knows the exact command it wants to run -
+ * whether that was done immediately or after user interaction - it
+ * calls back to platform_setup_local_proxy() with the full command,
+ * which is expected to actually start the subprocess and fill in the
+ * missing details in the deferred socket, freeing the
+ * LocalProxyOpener as a side effect.
+ */
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "sshcr.h"
+#include "proxy/proxy.h"
+
+typedef struct LocalProxyOpener {
+    int crLine;
+
+    Socket *socket;
+    char *formatted_cmd;
+    Plug *plug;
+    SockAddr *addr;
+    int port;
+    Conf *conf;
+
+    Interactor *clientitr;
+    LogPolicy *clientlp;
+    Seat *clientseat;
+    prompts_t *prompts;
+    int username_prompt_index, password_prompt_index;
+
+    Interactor interactor;
+    DeferredSocketOpener opener;
+} LocalProxyOpener;
+
+static void local_proxy_opener_free(DeferredSocketOpener *opener)
+{
+    LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener);
+    burnstr(lp->formatted_cmd);
+    if (lp->prompts)
+        free_prompts(lp->prompts);
+    sk_addr_free(lp->addr);
+    conf_free(lp->conf);
+    sfree(lp);
+}
+
+static const DeferredSocketOpenerVtable LocalProxyOpener_openervt = {
+    /*WINSCP .free =*/ local_proxy_opener_free,
+};
+
+static char *local_proxy_opener_description(Interactor *itr)
+{
+    return dupstr("connection via local command");
+}
+
+static LogPolicy *local_proxy_opener_logpolicy(Interactor *itr)
+{
+    LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor);
+    return lp->clientlp;
+}
+
+static Seat *local_proxy_opener_get_seat(Interactor *itr)
+{
+    LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor);
+    return lp->clientseat;
+}
+
+static void local_proxy_opener_set_seat(Interactor *itr, Seat *seat)
+{
+    LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor);
+    lp->clientseat = seat;
+}
+
+static const InteractorVtable LocalProxyOpener_interactorvt = {
+    // WINSCP
+    /*.description =*/ local_proxy_opener_description,
+    /*.logpolicy =*/ local_proxy_opener_logpolicy,
+    /*.get_seat =*/ local_proxy_opener_get_seat,
+    /*.set_seat =*/ local_proxy_opener_set_seat,
+};
+
+static void local_proxy_opener_cleanup_interactor(LocalProxyOpener *lp)
+{
+    if (lp->clientseat) {
+        interactor_return_seat(lp->clientitr);
+        lp->clientitr = NULL;
+        lp->clientseat = NULL;
+    }
+}
+
+static void local_proxy_opener_coroutine(void *vctx)
+{
+    LocalProxyOpener *lp = (LocalProxyOpener *)vctx;
+
+    crBegin(lp->crLine);
+
+    /*
+     * Make an initial attempt to figure out the command we want, and
+     * see if it tried to include a username or password that we don't
+     * have.
+     */
+    {
+        unsigned flags;
+        lp->formatted_cmd = format_telnet_command(
+            lp->addr, lp->port, lp->conf, &flags);
+
+        if (lp->clientseat && (flags & (TELNET_CMD_MISSING_USERNAME |
+                                        TELNET_CMD_MISSING_PASSWORD))) {
+            burnstr(lp->formatted_cmd);
+            lp->formatted_cmd = NULL;
+
+            /*
+             * We're missing at least one of the two parts, and we
+             * have an Interactor we can use to prompt for them, so
+             * try it.
+             */
+            lp->prompts = new_prompts();
+            lp->prompts->callback = local_proxy_opener_coroutine;
+            lp->prompts->callback_ctx = lp;
+            lp->prompts->to_server = true;
+            lp->prompts->from_server = false;
+            lp->prompts->name = dupstr("Local proxy authentication");
+            if (flags & TELNET_CMD_MISSING_USERNAME) {
+                lp->username_prompt_index = lp->prompts->n_prompts;
+                add_prompt(lp->prompts, dupstr("Proxy username: "), true);
+            } else {
+                lp->username_prompt_index = -1;
+            }
+            if (flags & TELNET_CMD_MISSING_PASSWORD) {
+                lp->password_prompt_index = lp->prompts->n_prompts;
+                add_prompt(lp->prompts, dupstr("Proxy password: "), false);
+            } else {
+                lp->password_prompt_index = -1;
+            }
+
+            while (true) {
+                SeatPromptResult spr = seat_get_userpass_input(
+                    interactor_announce(&lp->interactor), lp->prompts);
+                if (spr.kind == SPRK_OK) {
+                    break;
+                } else if (spr.kind == SPRK_USER_ABORT) {
+                    local_proxy_opener_cleanup_interactor(lp);
+                    plug_closing_user_abort(lp->plug);
+                    /* That will have freed us, so we must just return
+                     * without calling any crStop */
+                    return;
+                } else if (spr.kind == SPRK_SW_ABORT) {
+                    local_proxy_opener_cleanup_interactor(lp);
+                    { // WINSCP
+                    char *err = spr_get_error_message(spr);
+                    plug_closing_error(lp->plug, err);
+                    sfree(err);
+                    return; /* without crStop, as above */
+                    } // WINSCP
+                }
+                crReturnV;
+            }
+
+            if (lp->username_prompt_index != -1) {
+                conf_set_str(
+                    lp->conf, CONF_proxy_username,
+                    prompt_get_result_ref(
+                        lp->prompts->prompts[lp->username_prompt_index]));
+            }
+
+            if (lp->password_prompt_index != -1) {
+                conf_set_str(
+                    lp->conf, CONF_proxy_password,
+                    prompt_get_result_ref(
+                        lp->prompts->prompts[lp->password_prompt_index]));
+            }
+
+            free_prompts(lp->prompts);
+            lp->prompts = NULL;
+        }
+
+        /*
+         * Now format the command a second time, with the results of
+         * those prompts written into lp->conf.
+         */
+        lp->formatted_cmd = format_telnet_command(
+            lp->addr, lp->port, lp->conf, NULL);
+    }
+
+    /*
+     * Log the command, with some changes. Firstly, we regenerate it
+     * with the password masked; secondly, we escape control
+     * characters so that the log message is printable.
+     */
+    conf_set_str(lp->conf, CONF_proxy_password, "*password*");
+    {
+        char *censored_cmd = format_telnet_command(
+            lp->addr, lp->port, lp->conf, NULL);
+
+        strbuf *logmsg = strbuf_new();
+        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);
+        strbuf_free(logmsg);
+        sfree(censored_cmd);
+    }
+
+    /*
+     * Now we're ready to actually do the platform-specific socket
+     * setup.
+     */
+    { // WINSCP
+    char *cmd = lp->formatted_cmd;
+    lp->formatted_cmd = NULL;
+
+    local_proxy_opener_cleanup_interactor(lp);
+
+    { // WINSCP
+    char *error_msg = platform_setup_local_proxy(lp->socket, cmd);
+    burnstr(cmd);
+
+    if (error_msg) {
+        plug_closing_error(lp->plug, error_msg);
+        sfree(error_msg);
+    } else {
+        /* If error_msg was NULL, there was no error in setup,
+         * which means that platform_setup_local_proxy will have
+         * called back to free us. So return without calling any
+         * crStop. */
+        return;
+    }
+
+    } // WINSCP
+    } // WINSCP
+    crFinishV;
+}
+
+DeferredSocketOpener *local_proxy_opener(
+    SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr)
+{
+    LocalProxyOpener *lp = snew(LocalProxyOpener);
+    memset(lp, 0, sizeof(*lp));
+    lp->plug = plug;
+    lp->opener.vt = &LocalProxyOpener_openervt;
+    lp->interactor.vt = &LocalProxyOpener_interactorvt;
+    lp->addr = sk_addr_dup(addr);
+    lp->port = port;
+    lp->conf = conf_copy(conf);
+
+    if (itr) {
+        lp->clientitr = itr;
+        interactor_set_child(lp->clientitr, &lp->interactor);
+        lp->clientlp = interactor_logpolicy(lp->clientitr);
+        lp->clientseat = interactor_borrow_seat(lp->clientitr);
+    }
+
+    return &lp->opener;
+}
+
+void local_proxy_opener_set_socket(DeferredSocketOpener *opener,
+                                   Socket *socket)
+{
+    assert(opener->vt == &LocalProxyOpener_openervt);
+    { // WINSCP
+    LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener);
+    lp->socket = socket;
+    queue_toplevel_callback(get_callback_set(lp->plug), local_proxy_opener_coroutine, lp);
+    } // WINSCP
+}

+ 16 - 0
source/putty/proxy/nosshproxy.c

@@ -0,0 +1,16 @@
+/*
+ * nosshproxy.c: stub implementation of sshproxy_new_connection().
+ */
+
+#include "putty.h"
+#include "network.h"
+
+const bool ssh_proxy_supported = false;
+
+Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname,
+                                int port, bool privport,
+                                bool oobinline, bool nodelay, bool keepalive,
+                                Plug *plug, Conf *conf, Interactor *itr)
+{
+    return NULL;
+}

+ 652 - 0
source/putty/proxy/proxy.c

@@ -0,0 +1,652 @@
+/*
+ * Network proxy abstraction in PuTTY
+ *
+ * A proxy layer, if necessary, wedges itself between the network
+ * code and the higher level backend.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+#define do_proxy_dns(conf) \
+    (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \
+         (conf_get_int(conf, CONF_proxy_dns) == AUTO && \
+              conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4))
+
+static void proxy_negotiator_cleanup(ProxySocket *ps)
+{
+    if (ps->pn) {
+        proxy_negotiator_free(ps->pn);
+        ps->pn = NULL;
+    }
+    if (ps->clientseat) {
+        interactor_return_seat(ps->clientitr);
+        ps->clientitr = NULL;
+        ps->clientseat = NULL;
+    }
+}
+
+/*
+ * Call this when proxy negotiation is complete, so that this
+ * socket can begin working normally.
+ */
+void proxy_activate(ProxySocket *ps)
+{
+    size_t output_before, output_after;
+
+    proxy_negotiator_cleanup(ps);
+
+    plug_log(ps->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0);
+
+    /* we want to ignore new receive events until we have sent
+     * all of our buffered receive data.
+     */
+    sk_set_frozen(ps->sub_socket, true);
+
+    /* how many bytes of output have we buffered? */
+    output_before = bufchain_size(&ps->pending_oob_output_data) +
+        bufchain_size(&ps->pending_output_data);
+    /* and keep track of how many bytes do not get sent. */
+    output_after = 0;
+
+    /* send buffered OOB writes */
+    while (bufchain_size(&ps->pending_oob_output_data) > 0) {
+        ptrlen data = bufchain_prefix(&ps->pending_oob_output_data);
+        output_after += sk_write_oob(ps->sub_socket, data.ptr, data.len);
+        bufchain_consume(&ps->pending_oob_output_data, data.len);
+    }
+
+    /* send buffered normal writes */
+    while (bufchain_size(&ps->pending_output_data) > 0) {
+        ptrlen data = bufchain_prefix(&ps->pending_output_data);
+        output_after += sk_write(ps->sub_socket, data.ptr, data.len);
+        bufchain_consume(&ps->pending_output_data, data.len);
+    }
+
+    /* if we managed to send any data, let the higher levels know. */
+    if (output_after < output_before)
+        plug_sent(ps->plug, output_after);
+
+    /* if we have a pending EOF to send, send it */
+    if (ps->pending_eof) sk_write_eof(ps->sub_socket);
+
+    /* if the backend wanted the socket unfrozen, try to unfreeze.
+     * our set_frozen handler will flush buffered receive data before
+     * unfreezing the actual underlying socket.
+     */
+    if (!ps->freeze)
+        sk_set_frozen(&ps->sock, false);
+}
+
+/* basic proxy socket functions */
+
+static Plug *sk_proxy_plug (Socket *s, Plug *p)
+{
+    ProxySocket *ps = container_of(s, ProxySocket, sock);
+    Plug *ret = ps->plug;
+    if (p)
+        ps->plug = p;
+    return ret;
+}
+
+static void sk_proxy_close (Socket *s)
+{
+    ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+    sk_close(ps->sub_socket);
+    sk_addr_free(ps->remote_addr);
+    proxy_negotiator_cleanup(ps);
+    bufchain_clear(&ps->output_from_negotiator);
+    sfree(ps);
+}
+
+static size_t sk_proxy_write (Socket *s, const void *data, size_t len)
+{
+    ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+    if (ps->pn) {
+        bufchain_add(&ps->pending_output_data, data, len);
+        return bufchain_size(&ps->pending_output_data);
+    }
+    return sk_write(ps->sub_socket, data, len);
+}
+
+static size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len)
+{
+    ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+    if (ps->pn) {
+        bufchain_clear(&ps->pending_output_data);
+        bufchain_clear(&ps->pending_oob_output_data);
+        bufchain_add(&ps->pending_oob_output_data, data, len);
+        return len;
+    }
+    return sk_write_oob(ps->sub_socket, data, len);
+}
+
+static void sk_proxy_write_eof (Socket *s)
+{
+    ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+    if (ps->pn) {
+        ps->pending_eof = true;
+        return;
+    }
+    sk_write_eof(ps->sub_socket);
+}
+
+static void sk_proxy_set_frozen (Socket *s, bool is_frozen)
+{
+    ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+    if (ps->pn) {
+        ps->freeze = is_frozen;
+        return;
+    }
+
+    /* handle any remaining buffered recv data first */
+    if (bufchain_size(&ps->pending_input_data) > 0) {
+        ps->freeze = is_frozen;
+
+        /* loop while we still have buffered data, and while we are
+         * unfrozen. the plug_receive call in the loop could result
+         * in a call back into this function refreezing the socket,
+         * so we have to check each time.
+         */
+        while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) {
+            char databuf[512];
+            ptrlen data = bufchain_prefix(&ps->pending_input_data);
+            if (data.len > lenof(databuf))
+                data.len = lenof(databuf);
+            memcpy(databuf, data.ptr, data.len);
+            bufchain_consume(&ps->pending_input_data, data.len);
+            plug_receive(ps->plug, 0, databuf, data.len);
+        }
+
+        /* if we're still frozen, we'll have to wait for another
+         * call from the backend to finish unbuffering the data.
+         */
+        if (ps->freeze) return;
+    }
+
+    sk_set_frozen(ps->sub_socket, is_frozen);
+}
+
+static const char * sk_proxy_socket_error (Socket *s)
+{
+    ProxySocket *ps = container_of(s, ProxySocket, sock);
+    if (ps->error != NULL || ps->sub_socket == NULL) {
+        return ps->error;
+    }
+    return sk_socket_error(ps->sub_socket);
+}
+
+/* basic proxy plug functions */
+
+static void plug_proxy_log(Plug *plug, 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);
+}
+
+static void plug_proxy_closing(Plug *p, PlugCloseType type,
+                               const char *error_msg)
+{
+    ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
+
+    proxy_negotiator_cleanup(ps);
+    plug_closing(ps->plug, type, error_msg);
+}
+
+static void proxy_negotiate(ProxySocket *ps)
+{
+    assert(ps->pn);
+    proxy_negotiator_process_queue(ps->pn);
+
+    if (ps->pn->error) {
+        char *err = dupprintf("Proxy error: %s", ps->pn->error);
+        sfree(ps->pn->error);
+        proxy_negotiator_cleanup(ps);
+        plug_closing_error(ps->plug, err);
+        sfree(err);
+        return;
+    } else if (ps->pn->aborted) {
+        proxy_negotiator_cleanup(ps);
+        plug_closing_user_abort(ps->plug);
+        return;
+    }
+
+    while (bufchain_size(&ps->output_from_negotiator)) {
+        ptrlen data = bufchain_prefix(&ps->output_from_negotiator);
+        sk_write(ps->sub_socket, data.ptr, data.len);
+        bufchain_consume(&ps->output_from_negotiator, data.len);
+    }
+    if (ps->pn->done)
+        proxy_activate(ps);
+}
+
+static void plug_proxy_receive(
+    Plug *p, int urgent, const char *data, size_t len)
+{
+    ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
+
+    if (ps->pn) {
+        /* we will lose the urgentness of this data, but since most,
+         * if not all, of this data will be consumed by the negotiation
+         * process, hopefully it won't affect the protocol above us
+         */
+        bufchain_add(&ps->pending_input_data, data, len);
+        proxy_negotiate(ps);
+    } else {
+        plug_receive(ps->plug, urgent, data, len);
+    }
+}
+
+static void plug_proxy_sent (Plug *p, size_t bufsize)
+{
+    ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
+
+    if (ps->pn)
+        return;
+    plug_sent(ps->plug, bufsize);
+}
+
+static int plug_proxy_accepting(Plug *p,
+                                accept_fn_t constructor, accept_ctx_t ctx)
+{
+    unreachable("ProxySockets never create listening Sockets");
+}
+
+/*
+ * This function can accept a NULL pointer as `addr', in which case
+ * it will only check the host name.
+ */
+static bool proxy_for_destination(SockAddr *addr, const char *hostname,
+                                  int port, Conf *conf)
+{
+    int s = 0, e = 0;
+    char hostip[64];
+    int hostip_len, hostname_len;
+    const char *exclude_list;
+
+    /*
+     * Special local connections such as Unix-domain sockets
+     * unconditionally cannot be proxied, even in proxy-localhost
+     * mode. There just isn't any way to ask any known proxy type for
+     * them.
+     */
+    if (addr && sk_address_is_special_local(addr))
+        return false;                  /* do not proxy */
+
+    /*
+     * Check the host name and IP against the hard-coded
+     * representations of `localhost'.
+     */
+    if (!conf_get_bool(conf, CONF_even_proxy_localhost) &&
+        (sk_hostname_is_local(hostname) ||
+         (addr && sk_address_is_local(addr))))
+        return false;                  /* do not proxy */
+
+    /* we want a string representation of the IP address for comparisons */
+    if (addr) {
+        sk_getaddr(addr, hostip, 64);
+        hostip_len = strlen(hostip);
+    } else
+        hostip_len = 0;                /* placate gcc; shouldn't be required */
+
+    hostname_len = strlen(hostname);
+
+    exclude_list = conf_get_str(conf, CONF_proxy_exclude_list);
+
+    /* now parse the exclude list, and see if either our IP
+     * or hostname matches anything in it.
+     */
+
+    while (exclude_list[s]) {
+        while (exclude_list[s] &&
+               (isspace((unsigned char)exclude_list[s]) ||
+                exclude_list[s] == ',')) s++;
+
+        if (!exclude_list[s]) break;
+
+        e = s;
+
+        while (exclude_list[e] &&
+               (isalnum((unsigned char)exclude_list[e]) ||
+                exclude_list[e] == '-' ||
+                exclude_list[e] == '.' ||
+                exclude_list[e] == '*')) e++;
+
+        if (exclude_list[s] == '*') {
+            /* wildcard at beginning of entry */
+
+            if ((addr && strnicmp(hostip + hostip_len - (e - s - 1),
+                                  exclude_list + s + 1, e - s - 1) == 0) ||
+                strnicmp(hostname + hostname_len - (e - s - 1),
+                         exclude_list + s + 1, e - s - 1) == 0) {
+                /* IP/hostname range excluded. do not use proxy. */
+                return false;
+            }
+        } else if (exclude_list[e-1] == '*') {
+            /* wildcard at end of entry */
+
+            if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) ||
+                strnicmp(hostname, exclude_list + s, e - s - 1) == 0) {
+                /* IP/hostname range excluded. do not use proxy. */
+                return false;
+            }
+        } else {
+            /* no wildcard at either end, so let's try an absolute
+             * match (ie. a specific IP)
+             */
+
+            if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0)
+                return false; /* IP/hostname excluded. do not use proxy. */
+            if (strnicmp(hostname, exclude_list + s, e - s) == 0)
+                return false; /* IP/hostname excluded. do not use proxy. */
+        }
+
+        s = e;
+
+        /* Make sure we really have reached the next comma or end-of-string */
+        while (exclude_list[s] &&
+               !isspace((unsigned char)exclude_list[s]) &&
+               exclude_list[s] != ',') s++;
+    }
+
+    /* no matches in the exclude list, so use the proxy */
+    return true;
+}
+
+static char *dns_log_msg(const char *host, int addressfamily,
+                         const char *reason)
+{
+    return dupprintf("Looking up host \"%s\"%s for %s", host,
+                     (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+                      addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
+                      ""), reason);
+}
+
+SockAddr *name_lookup(const char *host, int port, char **canonicalname,
+                     Conf *conf, int addressfamily, LogContext *logctx,
+                     const char *reason)
+{
+    if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
+        do_proxy_dns(conf) &&
+        proxy_for_destination(NULL, host, port, conf)) {
+
+        if (logctx)
+            logeventf(logctx, "Leaving host lookup to proxy of \"%s\""
+                      " (for %s)", host, reason);
+
+        *canonicalname = dupstr(host);
+        return sk_nonamelookup(host);
+    } else {
+        if (logctx)
+            logevent_and_free(
+                logctx, dns_log_msg(host, addressfamily, reason));
+
+        return sk_namelookup(host, canonicalname, addressfamily);
+    }
+}
+
+static const SocketVtable ProxySocket_sockvt = {
+    // WINSCP
+    /*.plug =*/ sk_proxy_plug,
+    /*.close =*/ sk_proxy_close,
+    /*.write =*/ sk_proxy_write,
+    /*.write_oob =*/ sk_proxy_write_oob,
+    /*.write_eof =*/ sk_proxy_write_eof,
+    /*.set_frozen =*/ sk_proxy_set_frozen,
+    /*.socket_error =*/ sk_proxy_socket_error,
+    /*.peer_info =*/ NULL,
+};
+
+static const PlugVtable ProxySocket_plugvt = {
+    // WINSCP
+    /*.log =*/ plug_proxy_log,
+    /*.closing =*/ plug_proxy_closing,
+    /*.receive =*/ plug_proxy_receive,
+    /*.sent =*/ plug_proxy_sent,
+    /*.accepting =*/ plug_proxy_accepting
+};
+
+static char *proxy_description(Interactor *itr)
+{
+    ProxySocket *ps = container_of(itr, ProxySocket, interactor);
+    assert(ps->pn);
+    return dupprintf("%s connection to %s port %d", ps->pn->vt->type,
+                     conf_get_str(ps->conf, CONF_proxy_host),
+                     conf_get_int(ps->conf, CONF_proxy_port));
+}
+
+static LogPolicy *proxy_logpolicy(Interactor *itr)
+{
+    ProxySocket *ps = container_of(itr, ProxySocket, interactor);
+    return ps->clientlp;
+}
+
+static Seat *proxy_get_seat(Interactor *itr)
+{
+    ProxySocket *ps = container_of(itr, ProxySocket, interactor);
+    return ps->clientseat;
+}
+
+static void proxy_set_seat(Interactor *itr, Seat *seat)
+{
+    ProxySocket *ps = container_of(itr, ProxySocket, interactor);
+    ps->clientseat = seat;
+}
+
+static const InteractorVtable ProxySocket_interactorvt = {
+    // WINSCP
+    /*.description =*/ proxy_description,
+    /*.logpolicy =*/ proxy_logpolicy,
+    /*.get_seat =*/ proxy_get_seat,
+    /*.set_seat =*/ proxy_set_seat,
+};
+
+static void proxy_prompts_callback(void *ctx)
+{
+    proxy_negotiate((ProxySocket *)ctx);
+}
+
+prompts_t *proxy_new_prompts(ProxySocket *ps)
+{
+    prompts_t *prs = new_prompts();
+    prs->callback = proxy_prompts_callback;
+    prs->callback_ctx = ps;
+    return prs;
+}
+
+void proxy_spr_abort(ProxyNegotiator *pn, SeatPromptResult spr)
+{
+    if (spr.kind == SPRK_SW_ABORT) {
+        pn->error = spr_get_error_message(spr);
+    } else {
+        assert(spr.kind == SPRK_USER_ABORT);
+        pn->aborted = true;
+    }
+}
+
+Socket *new_connection(SockAddr *addr, const char *hostname,
+                       int port, bool privport,
+                       bool oobinline, bool nodelay, bool keepalive,
+                       Plug *plug, Conf *conf, Interactor *itr)
+{
+    int type = conf_get_int(conf, CONF_proxy_type);
+
+    if (type != PROXY_NONE &&
+        proxy_for_destination(addr, hostname, port, conf))
+    {
+        ProxySocket *ps;
+        SockAddr *proxy_addr;
+        char *proxy_canonical_name;
+        Socket *sret;
+
+        if (type == PROXY_SSH &&
+            (sret = sshproxy_new_connection(addr, hostname, port, privport,
+                                            oobinline, nodelay, keepalive,
+                                            plug, conf, itr)) != NULL)
+            return sret;
+
+        if ((sret = platform_new_connection(addr, hostname, port, privport,
+                                            oobinline, nodelay, keepalive,
+                                            plug, conf, itr)) != NULL)
+            return sret;
+
+        ps = snew(ProxySocket);
+        ps->sock.vt = &ProxySocket_sockvt;
+        ps->plugimpl.vt = &ProxySocket_plugvt;
+        ps->interactor.vt = &ProxySocket_interactorvt;
+        ps->conf = conf_copy(conf);
+        ps->plug = plug;
+        ps->remote_addr = addr;       /* will need to be freed on close */
+        ps->remote_port = port;
+
+        ps->error = NULL;
+        ps->pending_eof = false;
+        ps->freeze = false;
+
+        bufchain_init(&ps->pending_input_data);
+        bufchain_init(&ps->pending_output_data);
+        bufchain_init(&ps->pending_oob_output_data);
+        bufchain_init(&ps->output_from_negotiator);
+
+        ps->sub_socket = NULL;
+
+        /*
+         * If we've been given an Interactor by the caller, set ourselves
+         * up to work with it.
+         */
+        if (itr) {
+            ps->clientitr = itr;
+            interactor_set_child(ps->clientitr, &ps->interactor);
+            ps->clientlp = interactor_logpolicy(ps->clientitr);
+            ps->clientseat = interactor_borrow_seat(ps->clientitr);
+        }
+
+        { // WINSCP
+        const ProxyNegotiatorVT *vt;
+        switch (type) {
+          case PROXY_HTTP:
+            vt = &http_proxy_negotiator_vt;
+            break;
+          case PROXY_SOCKS4:
+            vt = &socks4_proxy_negotiator_vt;
+            break;
+          case PROXY_SOCKS5:
+            vt = &socks5_proxy_negotiator_vt;
+            break;
+          case PROXY_TELNET:
+            vt = &telnet_proxy_negotiator_vt;
+            break;
+          default:
+            ps->error = "Proxy error: Unknown proxy method";
+            return &ps->sock;
+        }
+        ps->pn = proxy_negotiator_new(vt);
+        ps->pn->ps = ps;
+        ps->pn->done = false;
+        ps->pn->error = NULL;
+        ps->pn->aborted = false;
+        ps->pn->input = &ps->pending_input_data;
+        /* Provide an Interactor to the negotiator if and only if we
+         * are usefully able to ask interactive questions of the user */
+        ps->pn->itr = ps->clientseat ? &ps->interactor : NULL;
+        bufchain_sink_init(ps->pn->output, &ps->output_from_negotiator);
+
+        {
+            char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect"
+                                      " to %s:%d", vt->type,
+                                      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);
+            sfree(logmsg);
+        }
+
+        {
+            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);
+            sfree(logmsg);
+        }
+
+        /* look-up proxy */
+        proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host),
+                                   &proxy_canonical_name,
+                                   conf_get_int(conf, CONF_addressfamily));
+        if (sk_addr_error(proxy_addr) != NULL) {
+            ps->error = "Proxy error: Unable to resolve proxy host name";
+            sk_addr_free(proxy_addr);
+            return &ps->sock;
+        }
+        sfree(proxy_canonical_name);
+
+        {
+            char addrbuf[256], *logmsg;
+            sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf));
+            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);
+            sfree(logmsg);
+        }
+
+        /* create the actual socket we will be using,
+         * connected to our proxy server and port.
+         */
+        ps->sub_socket = sk_new(proxy_addr,
+                                conf_get_int(conf, CONF_proxy_port),
+                                privport, oobinline,
+                                nodelay, keepalive, &ps->plugimpl,
+                                #ifdef WINSCP
+                                conf_get_int(conf, CONF_connect_timeout), conf_get_int(conf, CONF_sndbuf),
+                                conf_get_str(conf, CONF_srcaddr)
+                                #endif
+                                );
+        if (sk_socket_error(ps->sub_socket) != NULL)
+            return &ps->sock;
+
+        /* start the proxy negotiation process... */
+        sk_set_frozen(ps->sub_socket, false);
+        proxy_negotiate(ps);
+
+        return &ps->sock;
+        } // WINSCP
+    }
+
+    /* no proxy, so just return the direct socket */
+    return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug,
+        #ifdef WINSCP
+        conf_get_int(conf, CONF_connect_timeout), conf_get_int(conf, CONF_sndbuf),
+        conf_get_str(conf, CONF_srcaddr)
+        #endif
+    );
+}
+
+Socket *new_listener(const char *srcaddr, int port, Plug *plug,
+                     bool local_host_only, Conf *conf, int addressfamily)
+{
+    /* TODO: SOCKS (and potentially others) support inbound
+     * TODO: connections via the proxy. support them.
+     */
+
+    return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily);
+}
+
+#ifdef WINSCP
+ProxySocket * get_proxy_plug_socket(Plug * p)
+{
+    return container_of(p, ProxySocket, plugimpl);
+}
+#endif

+ 123 - 0
source/putty/proxy/proxy.h

@@ -0,0 +1,123 @@
+/*
+ * Network proxy abstraction in PuTTY
+ *
+ * A proxy layer, if necessary, wedges itself between the
+ * network code and the higher level backend.
+ *
+ * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5
+ */
+
+#ifndef PUTTY_PROXY_H
+#define PUTTY_PROXY_H
+
+typedef struct ProxySocket ProxySocket;
+typedef struct ProxyNegotiator ProxyNegotiator;
+typedef struct ProxyNegotiatorVT ProxyNegotiatorVT;
+
+struct ProxySocket {
+    const char *error;
+
+    Socket *sub_socket;
+    Plug *plug;
+    SockAddr *remote_addr;
+    int remote_port;
+
+    bufchain pending_output_data;
+    bufchain pending_oob_output_data;
+    bufchain pending_input_data;
+    bool pending_eof;
+
+    bool freeze; /* should we freeze the underlying socket when
+                  * we are done with the proxy negotiation? this
+                  * simply caches the value of sk_set_frozen calls.
+                  */
+
+    ProxyNegotiator *pn; /* non-NULL if still negotiating */
+    bufchain output_from_negotiator;
+
+    /* configuration, used to look up proxy settings */
+    Conf *conf;
+
+    /* for interaction with the Seat */
+    Interactor *clientitr;
+    LogPolicy *clientlp;
+    Seat *clientseat;
+
+    Socket sock;
+    Plug plugimpl;
+    Interactor interactor;
+};
+
+struct ProxyNegotiator {
+    const ProxyNegotiatorVT *vt;
+
+    /* Standard fields for any ProxyNegotiator. new() and free() don't
+     * have to set these up; that's done centrally, to save duplication. */
+    ProxySocket *ps;
+    bufchain *input;
+    bufchain_sink output[1];
+    Interactor *itr; /* NULL if we are not able to interact with the user */
+
+    /* Set to report success during proxy negotiation.  */
+    bool done;
+
+    /* Set to report an error during proxy negotiation. The main
+     * ProxySocket will free it, and will then guarantee never to call
+     * process_queue again. */
+    char *error;
+
+    /* Set to report user abort during proxy negotiation.  */
+    bool aborted;
+};
+
+#ifndef __cplusplus
+struct ProxyNegotiatorVT {
+    ProxyNegotiator *(*new)(const ProxyNegotiatorVT *);
+    void (*process_queue)(ProxyNegotiator *);
+    void (*free)(ProxyNegotiator *);
+    const char *type;
+};
+
+static inline ProxyNegotiator *proxy_negotiator_new(
+    const ProxyNegotiatorVT *vt)
+{ return vt->new(vt); }
+static inline void proxy_negotiator_process_queue(ProxyNegotiator *pn)
+{ pn->vt->process_queue(pn); }
+static inline void proxy_negotiator_free(ProxyNegotiator *pn)
+{ pn->vt->free(pn); }
+
+extern const ProxyNegotiatorVT http_proxy_negotiator_vt;
+extern const ProxyNegotiatorVT socks4_proxy_negotiator_vt;
+extern const ProxyNegotiatorVT socks5_proxy_negotiator_vt;
+extern const ProxyNegotiatorVT telnet_proxy_negotiator_vt;
+#endif
+
+/*
+ * Centralised functions to allow ProxyNegotiators to get hold of a
+ * prompts_t, and to deal with SeatPromptResults coming back.
+ */
+prompts_t *proxy_new_prompts(ProxySocket *ps);
+void proxy_spr_abort(ProxyNegotiator *pn, SeatPromptResult spr);
+
+/*
+ * This may be reused by local-command proxies on individual
+ * platforms.
+ */
+#define TELNET_CMD_MISSING_USERNAME 0x0001
+#define TELNET_CMD_MISSING_PASSWORD 0x0002
+char *format_telnet_command(SockAddr *addr, int port, Conf *conf,
+                            unsigned *flags_out);
+
+DeferredSocketOpener *local_proxy_opener(
+    SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr);
+void local_proxy_opener_set_socket(DeferredSocketOpener *opener,
+                                   Socket *socket);
+char *platform_setup_local_proxy(Socket *socket, const char *cmd);
+
+#include "cproxy.h"
+
+#ifdef WINSCP
+ProxySocket * get_proxy_plug_socket(Plug * p);
+#endif
+
+#endif

+ 72 - 0
source/putty/proxy/socks.h

@@ -0,0 +1,72 @@
+/*
+ * Constants used in the SOCKS protocols.
+ */
+
+/* Command codes common to both versions */
+#define SOCKS_CMD_CONNECT 1
+#define SOCKS_CMD_BIND 2
+
+/* SOCKS 4 definitions */
+
+#define SOCKS4_REQUEST_VERSION 4
+#define SOCKS4_REPLY_VERSION 0
+
+#define SOCKS4_RESP_SUCCESS         90
+#define SOCKS4_RESP_FAILURE         91
+#define SOCKS4_RESP_WANT_IDENTD     92
+#define SOCKS4_RESP_IDENTD_MISMATCH 93
+
+/*
+ * Special nonsense IP address range, used as a signal to indicate
+ * that an ASCIZ hostname follows the user id field.
+ *
+ * Strictly speaking, the use of this extension indicates that we're
+ * speaking SOCKS 4A rather than vanilla SOCKS 4, although we don't
+ * bother to draw the distinction.
+ */
+#define SOCKS4A_NAME_FOLLOWS_BASE  0x00000001 /* inclusive */
+#define SOCKS4A_NAME_FOLLOWS_LIMIT 0x00000100 /* exclusive */
+
+/* SOCKS 5 definitions */
+
+#define SOCKS5_REQUEST_VERSION 5
+#define SOCKS5_REPLY_VERSION 5
+
+/* Extra command codes extending the SOCKS_CMD_* list above */
+#define SOCKS5_CMD_UDP_ASSOCIATE 3
+
+#define SOCKS5_AUTH_NONE 0
+#define SOCKS5_AUTH_GSSAPI 1
+#define SOCKS5_AUTH_PASSWORD 2
+#define SOCKS5_AUTH_CHAP 3
+#define SOCKS5_AUTH_REJECTED 0xFF /* used in reply to indicate 'no
+                                   * acceptable method offered' */
+
+#define SOCKS5_AUTH_PASSWORD_VERSION 1
+
+#define SOCKS5_AUTH_CHAP_VERSION 1
+
+#define SOCKS5_AUTH_CHAP_ATTR_STATUS     0x00
+#define SOCKS5_AUTH_CHAP_ATTR_INFO       0x01
+#define SOCKS5_AUTH_CHAP_ATTR_USERNAME   0x02
+#define SOCKS5_AUTH_CHAP_ATTR_CHALLENGE  0x03
+#define SOCKS5_AUTH_CHAP_ATTR_RESPONSE   0x04
+#define SOCKS5_AUTH_CHAP_ATTR_CHARSET    0x05
+#define SOCKS5_AUTH_CHAP_ATTR_IDENTIFIER 0x10
+#define SOCKS5_AUTH_CHAP_ATTR_ALGLIST    0x11
+
+#define SOCKS5_AUTH_CHAP_ALG_HMACMD5   0x85
+
+#define SOCKS5_ADDR_IPV4 1
+#define SOCKS5_ADDR_IPV6 4
+#define SOCKS5_ADDR_HOSTNAME 3
+
+#define SOCKS5_RESP_SUCCESS 0
+#define SOCKS5_RESP_FAILURE 1
+#define SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET 2
+#define SOCKS5_RESP_NETWORK_UNREACHABLE 3
+#define SOCKS5_RESP_HOST_UNREACHABLE 4
+#define SOCKS5_RESP_CONNECTION_REFUSED 5
+#define SOCKS5_RESP_TTL_EXPIRED 6
+#define SOCKS5_RESP_COMMAND_NOT_SUPPORTED 7
+#define SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED 8

+ 137 - 0
source/putty/proxy/socks4.c

@@ -0,0 +1,137 @@
+/*
+ * SOCKS 4 proxy negotiation.
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "socks.h"
+#include "sshcr.h"
+
+typedef struct Socks4ProxyNegotiator {
+    int crLine;
+    ProxyNegotiator pn;
+} Socks4ProxyNegotiator;
+
+static ProxyNegotiator *proxy_socks4_new(const ProxyNegotiatorVT *vt)
+{
+    Socks4ProxyNegotiator *s = snew(Socks4ProxyNegotiator);
+    s->pn.vt = vt;
+    s->crLine = 0;
+    return &s->pn;
+}
+
+static void proxy_socks4_free(ProxyNegotiator *pn)
+{
+    Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn);
+    sfree(s);
+}
+
+static void proxy_socks4_process_queue(ProxyNegotiator *pn)
+{
+    Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn);
+
+    crBegin(s->crLine);
+
+    {
+        char hostname[512];
+        bool write_hostname = false;
+
+        /*
+         * SOCKS 4 request packet:
+         *
+         *   byte     version
+         *   byte     command
+         *   uint16   destination port number
+         *   uint32   destination IPv4 address (or something in the
+         *            SOCKS4A_NAME_FOLLOWS range)
+         *   asciz    username
+         *   asciz    destination hostname (if we sent SOCKS4A_NAME_FOLLOWS_*)
+         */
+
+        put_byte(pn->output, SOCKS4_REQUEST_VERSION);
+        put_byte(pn->output, SOCKS_CMD_CONNECT);
+        put_uint16(pn->output, pn->ps->remote_port);
+
+        switch (sk_addrtype(pn->ps->remote_addr)) {
+          case ADDRTYPE_IPV4: {
+            char addr[4];
+            sk_addrcopy(pn->ps->remote_addr, addr);
+            put_data(pn->output, addr, 4);
+            break;
+          }
+          case ADDRTYPE_NAME:
+            put_uint32(pn->output, SOCKS4A_NAME_FOLLOWS_BASE);
+            sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname));
+            write_hostname = true;
+            break;
+          case ADDRTYPE_IPV6:
+            pn->error = dupstr("SOCKS version 4 does not support IPv6");
+            crStopV;
+        }
+
+        put_asciz(pn->output, conf_get_str(pn->ps->conf, CONF_proxy_username));
+
+        if (write_hostname)
+            put_asciz(pn->output, hostname);
+    }
+
+    crReturnV;
+
+    {
+        unsigned char data[8];
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 8));
+
+        /*
+         * SOCKS 4 response packet:
+         *
+         *   byte     version
+         *   byte     status
+         *   uint16   port number
+         *   uint32   IPv4 address
+         *
+         * We don't need to worry about the port and destination address.
+         */
+
+        if (data[0] != SOCKS4_REPLY_VERSION) {
+            pn->error = dupprintf("SOCKS proxy response contained reply "
+                                  "version number %d (expected 0)",
+                                  (int)data[0]);
+            crStopV;
+        }
+
+        switch (data[1]) {
+          case SOCKS4_RESP_SUCCESS:
+            pn->done = true;
+            break;
+
+          case SOCKS4_RESP_FAILURE:
+            pn->error = dupstr("SOCKS server reported failure to connect");
+            break;
+
+          case SOCKS4_RESP_WANT_IDENTD:
+            pn->error = dupstr("SOCKS server wanted IDENTD on client");
+            break;
+
+          case SOCKS4_RESP_IDENTD_MISMATCH:
+            pn->error = dupstr("Username and IDENTD on client don't agree");
+            break;
+
+          default:
+            pn->error = dupprintf("SOCKS server sent unrecognised error "
+                                  "code %d", (int)data[1]);
+            break;
+        }
+        crStopV;
+    }
+
+    crFinishV;
+}
+
+const struct ProxyNegotiatorVT socks4_proxy_negotiator_vt = {
+    // WINSCP
+    /*.new =*/ proxy_socks4_new,
+    /*.process_queue =*/ proxy_socks4_process_queue,
+    /*.free =*/ proxy_socks4_free,
+    /*.type =*/ "SOCKS 4",
+};

+ 508 - 0
source/putty/proxy/socks5.c

@@ -0,0 +1,508 @@
+/*
+ * SOCKS 5 proxy negotiation.
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "socks.h"
+#include "sshcr.h"
+
+static inline const char *socks5_auth_name(unsigned char m)
+{
+    switch (m) {
+      case SOCKS5_AUTH_NONE: return "none";
+      case SOCKS5_AUTH_GSSAPI: return "GSSAPI";
+      case SOCKS5_AUTH_PASSWORD: return "password";
+      case SOCKS5_AUTH_CHAP: return "CHAP";
+      default: return "unknown";
+    }
+}
+
+static inline const char *socks5_response_text(unsigned char m)
+{
+    switch (m) {
+      case SOCKS5_RESP_SUCCESS: return "success";
+      case SOCKS5_RESP_FAILURE: return "unspecified failure";
+      case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET:
+        return "connection not allowed by ruleset";
+      case SOCKS5_RESP_NETWORK_UNREACHABLE: return "network unreachable";
+      case SOCKS5_RESP_HOST_UNREACHABLE: return "host unreachable";
+      case SOCKS5_RESP_CONNECTION_REFUSED: return "connection refused";
+      case SOCKS5_RESP_TTL_EXPIRED: return "TTL expired";
+      case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: return "command not supported";
+      case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED:
+        return "address type not supported";
+      default: return "unknown";
+    }
+}
+
+typedef struct Socks5ProxyNegotiator {
+    int crLine;
+    strbuf *auth_methods_offered;
+    unsigned char auth_method;
+    unsigned n_chap_attrs;
+    unsigned chap_attr, chap_attr_len;
+    unsigned char chap_buf[256];
+    strbuf *username, *password;
+    prompts_t *prompts;
+    int username_prompt_index, password_prompt_index;
+    int response_addr_length;
+    ProxyNegotiator pn;
+} Socks5ProxyNegotiator;
+
+static ProxyNegotiator *proxy_socks5_new(const ProxyNegotiatorVT *vt)
+{
+    Socks5ProxyNegotiator *s = snew(Socks5ProxyNegotiator);
+    memset(s, 0, sizeof(*s));
+    s->pn.vt = vt;
+    s->auth_methods_offered = strbuf_new();
+    s->username = strbuf_new();
+    s->password = strbuf_new_nm();
+    return &s->pn;
+}
+
+static void proxy_socks5_free(ProxyNegotiator *pn)
+{
+    Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn);
+    strbuf_free(s->auth_methods_offered);
+    strbuf_free(s->username);
+    strbuf_free(s->password);
+    if (s->prompts)
+        free_prompts(s->prompts);
+    smemclr(s, sizeof(s));
+    sfree(s);
+}
+
+static void proxy_socks5_process_queue(ProxyNegotiator *pn)
+{
+    Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn);
+
+    crBegin(s->crLine);
+
+    /*
+     * SOCKS 5 initial client packet:
+     *
+     *   byte      version
+     *   byte      number of available auth methods
+     *   byte[]    that many bytes indicating auth types
+     */
+
+    put_byte(pn->output, SOCKS5_REQUEST_VERSION);
+
+    strbuf_clear(s->auth_methods_offered);
+
+    /*
+     * We have two basic kinds of authentication to offer: none at
+     * all, and password-based systems (whether the password is sent
+     * in cleartext or proved via CHAP).
+     *
+     * We always offer 'none' as an option. We offer 'password' if we
+     * either have a username and password already from the Conf, or
+     * we have a Seat available to ask for them interactively. If
+     * neither, we don't offer those options in the first place.
+     */
+    put_byte(s->auth_methods_offered, SOCKS5_AUTH_NONE);
+
+    put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username));
+    put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password));
+    if (pn->itr || (s->username->len && s->password->len)) {
+        if (socks5_chap_available)
+            put_byte(s->auth_methods_offered, SOCKS5_AUTH_CHAP);
+
+        put_byte(s->auth_methods_offered, SOCKS5_AUTH_PASSWORD);
+    }
+
+    put_byte(pn->output, s->auth_methods_offered->len);
+    put_datapl(pn->output, ptrlen_from_strbuf(s->auth_methods_offered));
+
+    crReturnV;
+
+    /*
+     * SOCKS 5 initial server packet:
+     *
+     *   byte      version
+     *   byte      selected auth method, or SOCKS5_AUTH_REJECTED
+     */
+    {
+        unsigned char data[2];
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2));
+
+        if (data[0] != SOCKS5_REPLY_VERSION) {
+            pn->error = dupprintf("SOCKS proxy returned unexpected "
+                                  "reply version %d (expected %d)",
+                                  (int)data[0], SOCKS5_REPLY_VERSION);
+            crStopV;
+        }
+
+        if (data[1] == SOCKS5_AUTH_REJECTED) {
+            pn->error = dupstr("SOCKS server rejected every authentication "
+                               "method we offered");
+            crStopV;
+        }
+
+        {
+            bool found = false;
+            size_t i; // WINSCP
+            for (i = 0; i < s->auth_methods_offered->len; i++)
+                if (s->auth_methods_offered->u[i] == data[1]) {
+                    found = true;
+                    break;
+                }
+
+            if (!found) {
+                pn->error = dupprintf("SOCKS server asked for auth method %d "
+                                      "(%s), which we did not offer",
+                                      (int)data[1], socks5_auth_name(data[1]));
+                crStopV;
+            }
+        }
+
+        s->auth_method = data[1];
+    }
+
+    /*
+     * The 'none' auth option requires no further negotiation. If that
+     * was the one we selected, go straight to making the connection.
+     */
+    if (s->auth_method == SOCKS5_AUTH_NONE)
+        goto authenticated;
+
+    /*
+     * Otherwise, we're going to need a username and password, so this
+     * is the moment to stop and ask for one if we don't already have
+     * them.
+     */
+    if (pn->itr && (!s->username->len || !s->password->len)) {
+        s->prompts = proxy_new_prompts(pn->ps);
+        s->prompts->to_server = true;
+        s->prompts->from_server = false;
+        s->prompts->name = dupstr("SOCKS proxy authentication");
+        if (!s->username->len) {
+            s->username_prompt_index = s->prompts->n_prompts;
+            add_prompt(s->prompts, dupstr("Proxy username: "), true);
+        } else {
+            s->username_prompt_index = -1;
+        }
+        if (!s->password->len) {
+            s->password_prompt_index = s->prompts->n_prompts;
+            add_prompt(s->prompts, dupstr("Proxy password: "), false);
+        } else {
+            s->password_prompt_index = -1;
+        }
+
+        while (true) {
+            SeatPromptResult spr = seat_get_userpass_input(
+                interactor_announce(pn->itr), s->prompts);
+            if (spr.kind == SPRK_OK) {
+                break;
+            } else if (spr_is_abort(spr)) {
+                proxy_spr_abort(pn, spr);
+                crStopV;
+            }
+            crReturnV;
+        }
+
+        if (s->username_prompt_index != -1) {
+            strbuf_clear(s->username);
+            put_dataz(s->username,
+                      prompt_get_result_ref(
+                          s->prompts->prompts[s->username_prompt_index]));
+        }
+
+        if (s->password_prompt_index != -1) {
+            strbuf_clear(s->password);
+            put_dataz(s->password,
+                      prompt_get_result_ref(
+                          s->prompts->prompts[s->password_prompt_index]));
+        }
+
+        free_prompts(s->prompts);
+        s->prompts = NULL;
+    }
+
+    /*
+     * Now process the different auth methods that will use that
+     * username and password. Note we can't do this using the natural
+     * idiom of a switch statement, because there are crReturns inside
+     * some cases.
+     */
+    if (s->auth_method == SOCKS5_AUTH_PASSWORD) {
+        /*
+         * SOCKS 5 password auth packet:
+         *
+         *   byte      version
+         *   pstring   username
+         *   pstring   password
+         */
+        put_byte(pn->output, SOCKS5_AUTH_PASSWORD_VERSION);
+        if (!put_pstring(pn->output, s->username->s)) {
+            pn->error = dupstr("SOCKS 5 authentication cannot support "
+                               "usernames longer than 255 chars");
+            crStopV;
+        }
+        if (!put_pstring(pn->output, s->password->s)) {
+            pn->error = dupstr("SOCKS 5 authentication cannot support "
+                               "passwords longer than 255 chars");
+            crStopV;
+        }
+
+        /*
+         * SOCKS 5 password reply packet:
+         *
+         *   byte      version
+         *   byte      0 for success, >0 for failure
+         */
+        { // WINSCP
+        unsigned char data[2];
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2));
+
+        if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) {
+            pn->error = dupprintf(
+                "SOCKS 5 password reply had version number %d (expected "
+                "%d)", (int)data[0], SOCKS5_AUTH_PASSWORD_VERSION);
+            crStopV;
+        }
+
+        if (data[1] != 0) {
+            pn->error = dupstr("SOCKS 5 server rejected our password");
+            crStopV;
+        }
+        } // WINSCP
+    } else if (s->auth_method == SOCKS5_AUTH_CHAP) {
+        assert(socks5_chap_available);
+
+        /*
+         * All CHAP packets, in both directions, have the same
+         * overall format:
+         *
+         *   byte      version
+         *   byte      number of attributes
+         *
+         * and then for each attribute:
+         *
+         *   byte      attribute type
+         *   byte      length
+         *   byte[]    that many bytes of payload
+         *
+         * In the initial outgoing packet we send two attributes:
+         * the list of supported algorithm names, and the
+         * username.
+         *
+         * (It's possible that we ought to delay sending the
+         * username until the second packet, in case the proxy
+         * sent back an attribute indicating which character set
+         * it would like us to use.)
+         */
+        put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION);
+        put_byte(pn->output, 2);   /* number of attributes */
+
+        put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_ALGLIST);
+        put_byte(pn->output, 1);   /* string length */
+        put_byte(pn->output, SOCKS5_AUTH_CHAP_ALG_HMACMD5);
+
+        /* Second attribute: username */
+        {
+            put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_USERNAME);
+            if (!put_pstring(pn->output, s->username->s)) {
+                pn->error = dupstr(
+                    "SOCKS 5 CHAP authentication cannot support "
+                    "usernames longer than 255 chars");
+                crStopV;
+            }
+        }
+
+        while (true) {
+            /*
+             * Process a CHAP response packet, which has the same
+             * overall format as the outgoing packet shown above.
+             */
+            unsigned char data[2];
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                  pn->input, data, 2));
+            if (data[0] != SOCKS5_AUTH_CHAP_VERSION) {
+                pn->error = dupprintf(
+                    "SOCKS 5 CHAP reply had version number %d (expected "
+                    "%d)", (int)data[0], SOCKS5_AUTH_CHAP_VERSION);
+                crStopV;
+            }
+
+            s->n_chap_attrs = data[1];
+            if (s->n_chap_attrs == 0) {
+                /*
+                 * If we receive a CHAP packet containing no
+                 * attributes, then we have nothing we didn't have
+                 * before, and can't make further progress.
+                 */
+                pn->error = dupprintf(
+                    "SOCKS 5 CHAP reply sent no attributes");
+                crStopV;
+            }
+            while (s->n_chap_attrs-- > 0) {
+                unsigned char data[2];
+                crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                      pn->input, data, 2));
+                s->chap_attr = data[0];
+                s->chap_attr_len = data[1];
+                crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                      pn->input, s->chap_buf, s->chap_attr_len));
+
+                if (s->chap_attr == SOCKS5_AUTH_CHAP_ATTR_STATUS) {
+                    if (s->chap_attr_len == 1 && s->chap_buf[0] == 0) {
+                        /* Status 0 means success: we are authenticated! */
+                        goto authenticated;
+                    } else {
+                        pn->error = dupstr(
+                            "SOCKS 5 CHAP authentication failed");
+                        crStopV;
+                    }
+                } else if (s->chap_attr==SOCKS5_AUTH_CHAP_ATTR_CHALLENGE) {
+                    /* The CHAP challenge string. Send the response */
+                    strbuf *response = chap_response(
+                        make_ptrlen(s->chap_buf, s->chap_attr_len),
+                        ptrlen_from_strbuf(s->password));
+
+                    put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION);
+                    put_byte(pn->output, 1); /* number of attributes */
+                    put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_RESPONSE);
+                    put_byte(pn->output, response->len);
+                    put_datapl(pn->output, ptrlen_from_strbuf(response));
+
+                    strbuf_free(response);
+                } else {
+                    /* ignore all other attributes */
+                }
+            }
+        }
+    } else {
+        unreachable("bad auth method in SOCKS 5 negotiation");
+    }
+
+  authenticated:
+
+    /*
+     * SOCKS 5 connection command:
+     *
+     *   byte      version
+     *   byte      command
+     *   byte      reserved (send as zero)
+     *   byte      address type
+     *   byte[]    address, with variable size (see below)
+     *   uint16    port
+     */
+    put_byte(pn->output, SOCKS5_REPLY_VERSION);
+    put_byte(pn->output, SOCKS_CMD_CONNECT);
+    put_byte(pn->output, 0);   /* reserved byte */
+
+    switch (sk_addrtype(pn->ps->remote_addr)) {
+      case ADDRTYPE_IPV4: {
+        /* IPv4: address is 4 raw bytes */
+        put_byte(pn->output, SOCKS5_ADDR_IPV4);
+        { // WINSCP
+        char buf[4];
+        sk_addrcopy(pn->ps->remote_addr, buf);
+        put_data(pn->output, buf, sizeof(buf));
+        break;
+        } // WINSCP
+      }
+      case ADDRTYPE_IPV6: {
+        /* IPv6: address is 16 raw bytes */
+        put_byte(pn->output, SOCKS5_ADDR_IPV6);
+        { // WINSCP
+        char buf[16];
+        sk_addrcopy(pn->ps->remote_addr, buf);
+        put_data(pn->output, buf, sizeof(buf));
+        break;
+        } // WINSCP
+      }
+      case ADDRTYPE_NAME: {
+        /* Hostname: address is a pstring (Pascal-style string,
+         * unterminated but with a one-byte prefix length) */
+        put_byte(pn->output, SOCKS5_ADDR_HOSTNAME);
+        { // WINSCP
+        char hostname[512];
+        sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname));
+        if (!put_pstring(pn->output, hostname)) {
+            pn->error = dupstr(
+                "SOCKS 5 cannot support host names longer than 255 chars");
+            crStopV;
+        }
+        } // WINSCP
+        break;
+      }
+      default:
+        unreachable("Unexpected addrtype in SOCKS 5 proxy");
+    }
+
+    put_uint16(pn->output, pn->ps->remote_port);
+    crReturnV;
+
+    /*
+     * SOCKS 5 connection response:
+     *
+     *   byte      version
+     *   byte      status
+     *   byte      reserved
+     *   byte      address type
+     *   byte[]    address bound to (same formats as in connection request)
+     *   uint16    port
+     *
+     * We read the first four bytes and then decide what to do next.
+     */
+    {
+        unsigned char data[4];
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 4));
+
+        if (data[0] != SOCKS5_REPLY_VERSION) {
+            pn->error = dupprintf("SOCKS proxy returned unexpected "
+                                  "reply version %d (expected %d)",
+                                  (int)data[0], SOCKS5_REPLY_VERSION);
+            crStopV;
+        }
+
+        if (data[1] != SOCKS5_RESP_SUCCESS) {
+            pn->error = dupprintf("SOCKS proxy failed to connect, error %d "
+                                  "(%s)", (int)data[1],
+                                  socks5_response_text(data[1]));
+            crStopV;
+        }
+
+        /*
+         * Process each address type to find out the size of the rest
+         * of the packet. Note we can't do this using the natural
+         * idiom of a switch statement, because there are crReturns
+         * inside some cases.
+         */
+        if (data[3] == SOCKS5_ADDR_IPV4) {
+            s->response_addr_length = 4;
+        } else if (data[3] == SOCKS5_ADDR_IPV6) {
+            s->response_addr_length = 16;
+        } else if (data[3] == SOCKS5_ADDR_HOSTNAME) {
+            /* Read the hostname length byte to find out how much to read */
+            unsigned char len; 
+            crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, &len, 1));
+            s->response_addr_length = len;
+            break;
+        } else {
+            pn->error = dupprintf("SOCKS proxy response included unknown "
+                                  "address type %d", (int)data[3]);
+            crStopV;
+        }
+
+        /* Read and ignore the address and port fields */
+        crMaybeWaitUntilV(bufchain_try_consume(
+                              pn->input, s->response_addr_length + 2));
+    }
+
+    /* And we're done! */
+    pn->done = true;
+    crFinishV;
+}
+
+const struct ProxyNegotiatorVT socks5_proxy_negotiator_vt = {
+    // WINSCP
+    /*.new =*/ proxy_socks5_new,
+    /*.process_queue =*/ proxy_socks5_process_queue,
+    /*.free =*/ proxy_socks5_free,
+    /*.type =*/ "SOCKS 5",
+};

+ 369 - 0
source/putty/proxy/telnet.c

@@ -0,0 +1,369 @@
+/*
+ * "Telnet" proxy negotiation.
+ *
+ * (This is for ad-hoc proxies where you connect to the proxy's
+ * telnet port and send a command such as `connect host port'. The
+ * command is configurable, since this proxy type is typically not
+ * standardised or at all well-defined.)
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "sshcr.h"
+
+char *format_telnet_command(SockAddr *addr, int port, Conf *conf,
+                            unsigned *flags_out)
+{
+    char *fmt = conf_get_str(conf, CONF_proxy_telnet_command);
+    int so = 0, eo = 0;
+    strbuf *buf = strbuf_new();
+    unsigned flags = 0;
+
+    /* we need to escape \\, \%, \r, \n, \t, \x??, \0???,
+     * %%, %host, %port, %user, and %pass
+     */
+
+    while (fmt[eo] != 0) {
+
+        /* scan forward until we hit end-of-line,
+         * or an escape character (\ or %) */
+        while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\')
+            eo++;
+
+        /* if we hit eol, break out of our escaping loop */
+        if (fmt[eo] == 0) break;
+
+        /* if there was any unescaped text before the escape
+         * character, send that now */
+        if (eo != so)
+            put_data(buf, fmt + so, eo - so);
+
+        so = eo++;
+
+        /* if the escape character was the last character of
+         * the line, we'll just stop and send it. */
+        if (fmt[eo] == 0) break;
+
+        if (fmt[so] == '\\') {
+
+            /* we recognize \\, \%, \r, \n, \t, \x??.
+             * anything else, we just send unescaped (including the \).
+             */
+
+            switch (fmt[eo]) {
+
+              case '\\':
+                put_byte(buf, '\\');
+                eo++;
+                break;
+
+              case '%':
+                put_byte(buf, '%');
+                eo++;
+                break;
+
+              case 'r':
+                put_byte(buf, '\r');
+                eo++;
+                break;
+
+              case 'n':
+                put_byte(buf, '\n');
+                eo++;
+                break;
+
+              case 't':
+                put_byte(buf, '\t');
+                eo++;
+                break;
+
+              case 'x':
+              case 'X': {
+                /* escaped hexadecimal value (ie. \xff) */
+                unsigned char v = 0;
+                int i = 0;
+
+                for (;;) {
+                  eo++;
+                  if (fmt[eo] >= '0' && fmt[eo] <= '9')
+                      v += fmt[eo] - '0';
+                  else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
+                      v += fmt[eo] - 'a' + 10;
+                  else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
+                      v += fmt[eo] - 'A' + 10;
+                  else {
+                    /* non hex character, so we abort and just
+                     * send the whole thing unescaped (including \x)
+                     */
+                    put_byte(buf, '\\');
+                    eo = so + 1;
+                    break;
+                  }
+
+                  /* we only extract two hex characters */
+                  if (i == 1) {
+                    put_byte(buf, v);
+                    eo++;
+                    break;
+                  }
+
+                  i++;
+                  v <<= 4;
+                }
+                break;
+              }
+
+              default:
+                put_data(buf, fmt + so, 2);
+                eo++;
+                break;
+            }
+        } else {
+
+            /* % escape. we recognize %%, %host, %port, %user, %pass.
+             * %proxyhost, %proxyport. Anything else we just send
+             * unescaped (including the %).
+             */
+
+            if (fmt[eo] == '%') {
+                put_byte(buf, '%');
+                eo++;
+            }
+            else if (strnicmp(fmt + eo, "host", 4) == 0) {
+                char dest[512];
+                sk_getaddr(addr, dest, lenof(dest));
+                put_data(buf, dest, strlen(dest));
+                eo += 4;
+            }
+            else if (strnicmp(fmt + eo, "port", 4) == 0) {
+                put_fmt(buf, "%d", port);
+                eo += 4;
+            }
+            else if (strnicmp(fmt + eo, "user", 4) == 0) {
+                const char *username = conf_get_str(conf, CONF_proxy_username);
+                put_data(buf, username, strlen(username));
+                eo += 4;
+                if (!*username)
+                    flags |= TELNET_CMD_MISSING_USERNAME;
+            }
+            else if (strnicmp(fmt + eo, "pass", 4) == 0) {
+                const char *password = conf_get_str(conf, CONF_proxy_password);
+                put_data(buf, password, strlen(password));
+                eo += 4;
+                if (!*password)
+                    flags |= TELNET_CMD_MISSING_PASSWORD;
+            }
+            else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) {
+                const char *host = conf_get_str(conf, CONF_proxy_host);
+                put_data(buf, host, strlen(host));
+                eo += 9;
+            }
+            else if (strnicmp(fmt + eo, "proxyport", 9) == 0) {
+                int port = conf_get_int(conf, CONF_proxy_port);
+                put_fmt(buf, "%d", port);
+                eo += 9;
+            }
+            else {
+                /* we don't escape this, so send the % now, and
+                 * don't advance eo, so that we'll consider the
+                 * text immediately following the % as unescaped.
+                 */
+                put_byte(buf, '%');
+            }
+        }
+
+        /* resume scanning for additional escapes after this one. */
+        so = eo;
+    }
+
+    /* if there is any unescaped text at the end of the line, send it */
+    if (eo != so) {
+        put_data(buf, fmt + so, eo - so);
+    }
+
+    if (flags_out)
+        *flags_out = flags;
+    return strbuf_to_str(buf);
+}
+
+typedef struct TelnetProxyNegotiator {
+    int crLine;
+    Conf *conf;
+    char *formatted_cmd;
+    prompts_t *prompts;
+    int username_prompt_index, password_prompt_index;
+    ProxyNegotiator pn;
+} TelnetProxyNegotiator;
+
+static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt)
+{
+    TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator);
+    memset(s, 0, sizeof(*s));
+    s->pn.vt = vt;
+    return &s->pn;
+}
+
+static void proxy_telnet_free(ProxyNegotiator *pn)
+{
+    TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
+    if (s->conf)
+        conf_free(s->conf);
+    if (s->prompts)
+        free_prompts(s->prompts);
+    burnstr(s->formatted_cmd);
+    delete_callbacks_for_context(get_seat_callback_set(pn->ps->clientseat), s);
+    sfree(s);
+}
+
+static void proxy_telnet_process_queue_callback(void *vctx)
+{
+    TelnetProxyNegotiator *s = (TelnetProxyNegotiator *)vctx;
+    proxy_negotiator_process_queue(&s->pn);
+}
+
+static void proxy_telnet_process_queue(ProxyNegotiator *pn)
+{
+    TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
+
+    crBegin(s->crLine);
+
+    s->conf = conf_copy(pn->ps->conf);
+
+    /*
+     * Make an initial attempt to figure out the command we want, and
+     * see if it tried to include a username or password that we don't
+     * have.
+     */
+    {
+        unsigned flags;
+        s->formatted_cmd = format_telnet_command(
+            pn->ps->remote_addr, pn->ps->remote_port, s->conf, &flags);
+
+        if (pn->itr && (flags & (TELNET_CMD_MISSING_USERNAME |
+                                 TELNET_CMD_MISSING_PASSWORD))) {
+            burnstr(s->formatted_cmd);
+            s->formatted_cmd = NULL;
+
+            /*
+             * We're missing at least one of the two parts, and we
+             * have an Interactor we can use to prompt for them, so
+             * try it.
+             */
+            s->prompts = proxy_new_prompts(pn->ps);
+            s->prompts->to_server = true;
+            s->prompts->from_server = false;
+            s->prompts->name = dupstr("Telnet proxy authentication");
+            if (flags & TELNET_CMD_MISSING_USERNAME) {
+                s->username_prompt_index = s->prompts->n_prompts;
+                add_prompt(s->prompts, dupstr("Proxy username: "), true);
+            } else {
+                s->username_prompt_index = -1;
+            }
+            if (flags & TELNET_CMD_MISSING_PASSWORD) {
+                s->password_prompt_index = s->prompts->n_prompts;
+                add_prompt(s->prompts, dupstr("Proxy password: "), false);
+            } else {
+                s->password_prompt_index = -1;
+            }
+
+            /*
+             * This prompt is presented extremely early in PuTTY's
+             * setup. (Very promptly, you might say.)
+             *
+             * In particular, we can get here through a chain of
+             * synchronous calls from backend_init, which means (in
+             * GUI PuTTY) that the terminal we'll be sending this
+             * prompt to may not have its Ldisc set up yet (due to
+             * cyclic dependencies among all the things that have to
+             * be initialised).
+             *
+             * So we'll start by having ourself called back via a
+             * toplevel callback, to make sure we don't call
+             * seat_get_userpass_input until we've returned from
+             * backend_init and the frontend has finished getting
+             * everything ready.
+             */
+            queue_toplevel_callback(get_seat_callback_set(pn->ps->clientseat), proxy_telnet_process_queue_callback, s);
+            crReturnV;
+
+            while (true) {
+                SeatPromptResult spr = seat_get_userpass_input(
+                    interactor_announce(pn->itr), s->prompts);
+                if (spr.kind == SPRK_OK) {
+                    break;
+                } else if (spr_is_abort(spr)) {
+                    proxy_spr_abort(pn, spr);
+                    crStopV;
+                }
+                crReturnV;
+            }
+
+            if (s->username_prompt_index != -1) {
+                conf_set_str(
+                    s->conf, CONF_proxy_username,
+                    prompt_get_result_ref(
+                        s->prompts->prompts[s->username_prompt_index]));
+            }
+
+            if (s->password_prompt_index != -1) {
+                conf_set_str(
+                    s->conf, CONF_proxy_password,
+                    prompt_get_result_ref(
+                        s->prompts->prompts[s->password_prompt_index]));
+            }
+
+            free_prompts(s->prompts);
+            s->prompts = NULL;
+        }
+
+        /*
+         * Now format the command a second time, with the results of
+         * those prompts written into s->conf.
+         */
+        s->formatted_cmd = format_telnet_command(
+            pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL);
+    }
+
+    /*
+     * Log the command, with some changes. Firstly, we regenerate it
+     * with the password masked; secondly, we escape control
+     * characters so that the log message is printable.
+     */
+    conf_set_str(s->conf, CONF_proxy_password, "*password*");
+    {
+        char *censored_cmd = format_telnet_command(
+            pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL);
+
+        strbuf *logmsg = strbuf_new();
+        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);
+        strbuf_free(logmsg);
+        sfree(censored_cmd);
+    }
+
+    /*
+     * Actually send the command.
+     */
+    put_dataz(pn->output, s->formatted_cmd);
+
+    /*
+     * Unconditionally report success. We don't hang around waiting
+     * for error messages from the proxy, because this proxy type is
+     * so ad-hoc that we wouldn't know how to even recognise an error
+     * message if we saw one, let alone what to do about it.
+     */
+    pn->done = true;
+
+    crFinishV;
+}
+
+const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = {
+    // WINSCP
+    /*.new =*/ proxy_telnet_new,
+    /*.process_queue =*/ proxy_telnet_process_queue,
+    /*.free =*/ proxy_telnet_free,
+    /*.type =*/ "Telnet",
+};

+ 8 - 3
source/putty/putty.h

@@ -2496,7 +2496,6 @@ void pgp_fingerprints(void);
  */
 bool have_ssh_host_key(Seat *seat, const char *host, int port, const char *keytype);
 
-void display_banner(Seat *seat, const char* banner, int size); // WINSCP
 /*
  * Exports from console frontends (console.c in platform subdirs)
  * that aren't equivalents to things in windlg.c et al.
@@ -2772,11 +2771,18 @@ unsigned long timing_last_clock(void);
 typedef struct callback callback;
 struct IdempotentCallback;
 typedef struct PacketQueueNode PacketQueueNode;
+typedef struct handle_list_node handle_list_node;
+struct handle_list_node {
+    handle_list_node *next, *prev;
+};
 struct callback_set {
     struct callback *cbcurr, *cbhead, *cbtail;
     IdempotentCallback * ic_pktin_free;
     PacketQueueNode * pktin_freeq_head;
-    tree234 * handles_by_evtomain;
+    handle_list_node ready_head[1];
+    CRITICAL_SECTION ready_critsec[1];
+    HANDLE ready_event;
+    tree234 *handlewaits_tree_real;
 };
 #define CALLBACK_SET_ONLY struct callback_set * callback_set_v
 #define CALLBACK_SET CALLBACK_SET_ONLY,
@@ -2794,7 +2800,6 @@ void delete_callbacks_for_context(CALLBACK_SET void *ctx);
 LogPolicy *log_get_logpolicy(LogContext *ctx); // WINSCP
 Seat * get_log_seat(LogContext * lp); // WINSCP
 struct callback_set * get_log_callback_set(LogContext * lp); // WINSCP
-tree234 *new_handles_by_evtomain(); // WINSCP
 
 /*
  * Another facility in callback.c deals with 'idempotent' callbacks,

+ 2 - 4
source/putty/puttyexp.h

@@ -26,9 +26,7 @@ int have_any_ssh2_hostkey(Seat * seat, const char * host, int port);
 
 // from wingss.c
 
-#ifndef SSH2_GSS_OIDTYPE
-#include "sshgss.h"
-#endif
+#include "ssh\gss.h"
 
 void wingss_cleanup(void);
 
@@ -39,7 +37,7 @@ Seat * get_pfwd_seat(Plug * plug);
 
 // for winstore.c
 
-#include "winstuff.h"
+#include <windows.h>
 
 long reg_open_winscp_key(HKEY Key, const char * SubKey, HKEY * Result);
 long reg_create_winscp_key(HKEY Key, const char * SubKey, HKEY * Result);

+ 252 - 0
source/putty/ssh/agentf.c

@@ -0,0 +1,252 @@
+/*
+ * SSH agent forwarding.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "pageant.h"
+#include "channel.h"
+
+typedef struct agentf {
+    Plug *plug; // WINSCP
+    SshChannel *c;
+    bufchain inbuffer;
+    agent_pending_query *pending;
+    bool input_wanted;
+    bool rcvd_eof;
+
+    Channel chan;
+} agentf;
+
+static void agentf_got_response(agentf *af, void *reply, int replylen)
+{
+    af->pending = NULL;
+
+    if (!reply) {
+        /* The real agent didn't send any kind of reply at all for
+         * some reason, so fake an SSH_AGENT_FAILURE. */
+        reply = "\0\0\0\1\5";
+        replylen = 5;
+    }
+
+    sshfwd_write(af->c, reply, replylen);
+}
+
+static void agentf_callback(void *vctx, void *reply, int replylen);
+
+static void agentf_try_forward(agentf *af)
+{
+    size_t datalen, length;
+    strbuf *message;
+    unsigned char msglen[4];
+    void *reply;
+    int replylen;
+
+    /*
+     * Don't try to parallelise agent requests. Wait for each one to
+     * return before attempting the next.
+     */
+    if (af->pending)
+        return;
+
+    /*
+     * If the outgoing side of the channel connection is currently
+     * throttled, don't submit any new forwarded requests to the real
+     * agent. This causes the input side of the agent forwarding not
+     * to be emptied, exerting the required back-pressure on the
+     * remote client, and encouraging it to read our responses before
+     * sending too many more requests.
+     */
+    if (!af->input_wanted)
+        return;
+
+    while (1) {
+        /*
+         * Try to extract a complete message from the input buffer.
+         */
+        datalen = bufchain_size(&af->inbuffer);
+        if (datalen < 4)
+            break;         /* not even a length field available yet */
+
+        bufchain_fetch(&af->inbuffer, msglen, 4);
+        length = GET_32BIT_MSB_FIRST(msglen);
+
+        if (length > AGENT_MAX_MSGLEN-4) {
+            /*
+             * If the remote has sent a message that's just _too_
+             * long, we should reject it in advance of seeing the rest
+             * of the incoming message, and also close the connection
+             * for good measure (which avoids us having to faff about
+             * with carefully ignoring just the right number of bytes
+             * from the overlong message).
+             */
+            agentf_got_response(af, NULL, 0);
+            sshfwd_write_eof(af->c);
+            return;
+        }
+
+        if (length > datalen - 4)
+            break;          /* a whole message is not yet available */
+
+        bufchain_consume(&af->inbuffer, 4);
+
+        message = strbuf_new_for_agent_query();
+        bufchain_fetch_consume(
+            &af->inbuffer, strbuf_append(message, length), length);
+        af->pending = agent_query(
+            message, &reply, &replylen, agentf_callback, af, get_callback_set(af->plug)); // WINSCP
+        strbuf_free(message);
+
+        if (af->pending)
+            return;   /* agent_query promised to reply in due course */
+
+        /*
+         * If the agent gave us an answer immediately, pass it
+         * straight on and go round this loop again.
+         */
+        agentf_got_response(af, reply, replylen);
+        sfree(reply);
+    }
+
+    /*
+     * If we get here (i.e. we left the above while loop via 'break'
+     * rather than 'return'), that means we've determined that the
+     * input buffer for the agent forwarding connection doesn't
+     * contain a complete request.
+     *
+     * So if there's potentially more data to come, we can return now,
+     * and wait for the remote client to send it. But if the remote
+     * has sent EOF, it would be a mistake to do that, because we'd be
+     * waiting a long time. So this is the moment to check for EOF,
+     * and respond appropriately.
+     */
+    if (af->rcvd_eof)
+        sshfwd_write_eof(af->c);
+}
+
+static void agentf_callback(void *vctx, void *reply, int replylen)
+{
+    agentf *af = (agentf *)vctx;
+
+    agentf_got_response(af, reply, replylen);
+    sfree(reply);
+
+    /*
+     * Now try to extract and send further messages from the channel's
+     * input-side buffer.
+     */
+    agentf_try_forward(af);
+}
+
+static void agentf_free(Channel *chan);
+static size_t agentf_send(Channel *chan, bool is_stderr, const void *, size_t);
+static void agentf_send_eof(Channel *chan);
+static char *agentf_log_close_msg(Channel *chan);
+static void agentf_set_input_wanted(Channel *chan, bool wanted);
+
+static const ChannelVtable agentf_channelvt = {
+    // WINSCP
+    /*.free =*/ agentf_free,
+    /*.open_confirmation =*/ chan_remotely_opened_confirmation,
+    /*.open_failed =*/ chan_remotely_opened_failure,
+    /*.send =*/ agentf_send,
+    /*.send_eof =*/ agentf_send_eof,
+    /*.set_input_wanted =*/ agentf_set_input_wanted,
+    /*.log_close_msg =*/ agentf_log_close_msg,
+    /*.want_close =*/ chan_default_want_close,
+    /*.rcvd_exit_status =*/ chan_no_exit_status,
+    /*.rcvd_exit_signal =*/ chan_no_exit_signal,
+    /*.rcvd_exit_signal_numeric =*/ chan_no_exit_signal_numeric,
+    /*.run_shell =*/ chan_no_run_shell,
+    /*.run_command =*/ chan_no_run_command,
+    /*.run_subsystem =*/ chan_no_run_subsystem,
+    /*.enable_x11_forwarding =*/ chan_no_enable_x11_forwarding,
+    /*.enable_agent_forwarding =*/ chan_no_enable_agent_forwarding,
+    /*.allocate_pty =*/ chan_no_allocate_pty,
+    /*.set_env =*/ chan_no_set_env,
+    /*.send_break =*/ chan_no_send_break,
+    /*.send_signal =*/ chan_no_send_signal,
+    /*.change_window_size =*/ chan_no_change_window_size,
+    /*.request_response =*/ chan_no_request_response,
+};
+
+Channel *agentf_new(SshChannel *c, Plug *plug) // WINSCP
+{
+    agentf *af = snew(agentf);
+    af->plug = plug; // WINSCP
+    af->c = c;
+    af->chan.vt = &agentf_channelvt;
+    af->chan.initial_fixed_window_size = 0;
+    af->rcvd_eof = false;
+    bufchain_init(&af->inbuffer);
+    af->pending = NULL;
+    af->input_wanted = true;
+    return &af->chan;
+}
+
+static void agentf_free(Channel *chan)
+{
+    pinitassert(chan->vt == &agentf_channelvt);
+    agentf *af = container_of(chan, agentf, chan);
+
+    if (af->pending)
+        agent_cancel_query(af->pending);
+    bufchain_clear(&af->inbuffer);
+    sfree(af);
+}
+
+static size_t agentf_send(Channel *chan, bool is_stderr,
+                          const void *data, size_t length)
+{
+    pinitassert(chan->vt == &agentf_channelvt);
+    agentf *af = container_of(chan, agentf, chan);
+    bufchain_add(&af->inbuffer, data, length);
+    agentf_try_forward(af);
+
+    /*
+     * We exert back-pressure on an agent forwarding client if and
+     * only if we're waiting for the response to an asynchronous agent
+     * request. This prevents the client running out of window while
+     * receiving the _first_ message, but means that if any message
+     * takes time to process, the client will be discouraged from
+     * sending an endless stream of further ones after it.
+     */
+    return (af->pending ? bufchain_size(&af->inbuffer) : 0);
+}
+
+static void agentf_send_eof(Channel *chan)
+{
+    pinitassert(chan->vt == &agentf_channelvt);
+    agentf *af = container_of(chan, agentf, chan);
+
+    af->rcvd_eof = true;
+
+    /* Call try_forward, which will respond to the EOF now if
+     * appropriate, or wait until the queue of outstanding requests is
+     * dealt with if not. */
+    agentf_try_forward(af);
+}
+
+static char *agentf_log_close_msg(Channel *chan)
+{
+    return dupstr("Agent-forwarding connection closed");
+}
+
+static void agentf_set_input_wanted(Channel *chan, bool wanted)
+{
+    pinitassert(chan->vt == &agentf_channelvt);
+    agentf *af = container_of(chan, agentf, chan);
+
+    af->input_wanted = wanted;
+
+    /* Agent forwarding channels are buffer-managed by not asking the
+     * agent questions if the SSH channel isn't accepting input. So if
+     * it's started again, we should ask a question if we have one
+     * pending.. */
+    if (wanted)
+        agentf_try_forward(af);
+}

+ 207 - 0
source/putty/ssh/bpp-bare.c

@@ -0,0 +1,207 @@
+/*
+ * Trivial binary packet protocol for the 'bare' ssh-connection
+ * protocol used in PuTTY's SSH-2 connection sharing system.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "sshcr.h"
+
+struct ssh2_bare_bpp_state {
+    int crState;
+    long packetlen, maxlen;
+    unsigned char *data;
+    unsigned long incoming_sequence, outgoing_sequence;
+    PktIn *pktin;
+
+    BinaryPacketProtocol bpp;
+};
+
+static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp);
+static PktOut *ssh2_bare_bpp_new_pktout(int type);
+
+static const BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = {
+    // WINSCP
+    /*.free =*/ ssh2_bare_bpp_free,
+    /*.handle_input =*/ ssh2_bare_bpp_handle_input,
+    /*.handle_output =*/ ssh2_bare_bpp_handle_output,
+    /*.new_pktout =*/ ssh2_bare_bpp_new_pktout,
+    /*.queue_disconnect =*/ ssh2_bpp_queue_disconnect, /* in common.c */
+
+    /* packet size limit, per protocol spec in sharing.c comment */
+    /*.packet_size_limit =*/ 0x4000,
+};
+
+BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx)
+{
+    struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state);
+    memset(s, 0, sizeof(*s));
+    s->bpp.vt = &ssh2_bare_bpp_vtable;
+    s->bpp.logctx = logctx;
+    ssh_bpp_common_setup(&s->bpp);
+    return &s->bpp;
+}
+
+static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bare_bpp_state *s =
+        container_of(bpp, struct ssh2_bare_bpp_state, bpp);
+    sfree(s->pktin);
+    sfree(s);
+}
+
+#define BPP_READ(ptr, len) do                                           \
+    {                                                                   \
+        bool success;                                                   \
+        crMaybeWaitUntilV((success = bufchain_try_fetch_consume(        \
+                               s->bpp.in_raw, ptr, len)) ||             \
+                          s->bpp.input_eof);                            \
+        if (!success)                                                   \
+            goto eof;                                                   \
+        ssh_check_frozen(s->bpp.ssh);                                   \
+    } while (0)
+
+static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bare_bpp_state *s =
+        container_of(bpp, struct ssh2_bare_bpp_state, bpp);
+
+    crBegin(s->crState);
+
+    while (1) {
+        /* Read the length field. */
+        {
+            unsigned char lenbuf[4];
+            BPP_READ(lenbuf, 4);
+            s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf));
+        }
+
+        if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
+            ssh_sw_abort(s->bpp.ssh, "Invalid packet length received");
+            crStopV;
+        }
+
+        /*
+         * Allocate the packet to return, now we know its length.
+         */
+        s->pktin = snew_plus(PktIn, s->packetlen);
+        s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+        s->pktin->qnode.on_free_queue = false;
+        s->maxlen = 0;
+        s->data = snew_plus_get_aux(s->pktin);
+
+        s->pktin->sequence = s->incoming_sequence++;
+
+        /*
+         * Read the remainder of the packet.
+         */
+        BPP_READ(s->data, s->packetlen);
+
+        /*
+         * The data we just read is precisely the initial type byte
+         * followed by the packet payload.
+         */
+        s->pktin->type = s->data[0];
+        s->data++;
+        s->packetlen--;
+        BinarySource_INIT(s->pktin, s->data, s->packetlen);
+
+        if (s->pktin->type == SSH2_MSG_EXT_INFO) {
+            /*
+             * Mild layer violation: EXT_INFO is not permitted in the
+             * bare ssh-connection protocol. Faulting it here means
+             * that ssh2_common_filter_queue doesn't receive it in the
+             * first place unless it's legal to have sent it.
+             */
+            ssh_proto_error(s->bpp.ssh, "Remote side sent SSH2_MSG_EXT_INFO "
+                            "in bare connection protocol");
+            return;
+        }
+
+        /*
+         * Log incoming packet, possibly omitting sensitive fields.
+         */
+        if (s->bpp.logctx) {
+            logblank_t blanks[MAX_BLANKS];
+            int nblanks = ssh2_censor_packet(
+                s->bpp.pls, s->pktin->type, false,
+                make_ptrlen(s->data, s->packetlen), blanks);
+            log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
+                       ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+                                     s->pktin->type),
+                       get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks,
+                       &s->pktin->sequence, 0, NULL);
+        }
+
+        if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
+            sfree(s->pktin);
+            s->pktin = NULL;
+            continue;
+        }
+
+        s->pktin->qnode.formal_size = get_avail(s->pktin);
+        pq_push(&s->bpp.in_pq, s->pktin);
+        s->pktin = NULL;
+    }
+
+  eof:
+    if (!s->bpp.expect_close) {
+        ssh_remote_error(s->bpp.ssh,
+                         "Remote side unexpectedly closed network connection");
+    } else {
+        ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
+    }
+    return;  /* avoid touching s now it's been freed */
+
+    crFinishV;
+}
+
+static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type)
+{
+    PktOut *pkt = ssh_new_packet();
+    pkt->length = 4; /* space for packet length */
+    pkt->type = pkt_type;
+    put_byte(pkt, pkt_type);
+    return pkt;
+}
+
+static void ssh2_bare_bpp_format_packet(struct ssh2_bare_bpp_state *s,
+                                        PktOut *pkt)
+{
+    if (s->bpp.logctx) {
+        ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5);
+        logblank_t blanks[MAX_BLANKS];
+        int nblanks = ssh2_censor_packet(
+            s->bpp.pls, pkt->type, true, pktdata, blanks);
+        log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
+                   ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+                                 pkt->type),
+                   pktdata.ptr, pktdata.len, nblanks, blanks,
+                   &s->outgoing_sequence,
+                   pkt->downstream_id, pkt->additional_log_text);
+    }
+
+    s->outgoing_sequence++;        /* only for diagnostics, really */
+
+    PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4);
+    bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+}
+
+static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bare_bpp_state *s =
+        container_of(bpp, struct ssh2_bare_bpp_state, bpp);
+    PktOut *pkt;
+
+    while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+        ssh2_bare_bpp_format_packet(s, pkt);
+        ssh_free_pktout(pkt);
+    }
+
+    ssh_sendbuffer_changed(bpp->ssh);
+}

+ 186 - 0
source/putty/ssh/bpp.h

@@ -0,0 +1,186 @@
+/*
+ * Abstraction of the binary packet protocols used in SSH.
+ */
+
+#ifndef PUTTY_SSHBPP_H
+#define PUTTY_SSHBPP_H
+
+typedef struct BinaryPacketProtocolVtable BinaryPacketProtocolVtable;
+
+struct BinaryPacketProtocolVtable {
+    void (*free)(BinaryPacketProtocol *);
+    void (*handle_input)(BinaryPacketProtocol *);
+    void (*handle_output)(BinaryPacketProtocol *);
+    PktOut *(*new_pktout)(int type);
+    void (*queue_disconnect)(BinaryPacketProtocol *,
+                             const char *msg, int category);
+    uint32_t packet_size_limit;
+};
+
+struct BinaryPacketProtocol {
+    const struct BinaryPacketProtocolVtable *vt;
+    bufchain *in_raw, *out_raw;
+    bool input_eof;   /* set this if in_raw will never be added to again */
+    PktInQueue in_pq;
+    PktOutQueue out_pq;
+    PacketLogSettings *pls;
+    LogContext *logctx;
+    Ssh *ssh;
+
+    /* ic_in_raw is filled in by the BPP (probably by calling
+     * ssh_bpp_common_setup). The BPP's owner triggers it when data is
+     * added to in_raw, and also when the BPP is newly created. */
+    IdempotentCallback ic_in_raw;
+
+    /* ic_out_pq is entirely internal to the BPP itself; it's used as
+     * the callback on out_pq. */
+    IdempotentCallback ic_out_pq;
+
+    /* Information that all packet layers sharing this BPP will
+     * potentially be interested in. */
+    int remote_bugs;
+    bool ext_info_rsa_sha256_ok, ext_info_rsa_sha512_ok;
+
+    /* Set this if remote connection closure should not generate an
+     * error message (either because it's not to be treated as an
+     * error at all, or because some other error message has already
+     * been emitted). */
+    bool expect_close;
+};
+
+static inline void ssh_bpp_handle_input(BinaryPacketProtocol *bpp)
+{ bpp->vt->handle_input(bpp); }
+static inline void ssh_bpp_handle_output(BinaryPacketProtocol *bpp)
+{ bpp->vt->handle_output(bpp); }
+static inline PktOut *ssh_bpp_new_pktout(BinaryPacketProtocol *bpp, int type)
+{ return bpp->vt->new_pktout(type); }
+static inline void ssh_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+                                            const char *msg, int category)
+{ bpp->vt->queue_disconnect(bpp, msg, category); }
+
+/* ssh_bpp_free is more than just a macro wrapper on the vtable; it
+ * does centralised parts of the freeing too. */
+void ssh_bpp_free(BinaryPacketProtocol *bpp);
+
+BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx);
+void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
+                         const ssh_cipheralg *cipher,
+                         const void *session_key);
+/* This is only called from outside the BPP in server mode; in client
+ * mode the BPP detects compression start time automatically by
+ * snooping message types */
+void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp);
+
+/* Helper routine which does common BPP initialisation, e.g. setting
+ * up in_pq and out_pq, and initialising input_consumer. */
+void ssh_bpp_common_setup(BinaryPacketProtocol *);
+
+/* Common helper functions between the SSH-2 full and bare BPPs */
+void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+                               const char *msg, int category);
+bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin);
+
+/* Convenience macro for BPPs to send formatted strings to the Event
+ * Log. Assumes a function parameter called 'bpp' is in scope. */
+#define bpp_logevent(...) ( \
+    logevent_and_free((bpp)->logctx, dupprintf(__VA_ARGS__)))
+
+/*
+ * Structure that tracks how much data is sent and received, for
+ * purposes of triggering an SSH-2 rekey when either one gets over a
+ * configured limit. In each direction, the flag 'running' indicates
+ * that we haven't hit the limit yet, and 'remaining' tracks how much
+ * longer until we do. The function dts_consume() subtracts a given
+ * amount from the counter in a particular direction, and sets
+ * 'expired' if the limit has been hit.
+ *
+ * The limit is sticky: once 'running' has flipped to false,
+ * 'remaining' is no longer decremented, so it shouldn't dangerously
+ * wrap round.
+ */
+struct DataTransferStatsDirection {
+    bool running, expired;
+    unsigned long remaining;
+};
+struct DataTransferStats {
+    struct DataTransferStatsDirection in, out;
+};
+static inline void dts_consume(struct DataTransferStatsDirection *s,
+                               unsigned long size_consumed)
+{
+    if (s->running) {
+        if (s->remaining <= size_consumed) {
+            s->running = false;
+            s->expired = true;
+        } else {
+            s->remaining -= size_consumed;
+        }
+    }
+}
+static inline void dts_reset(struct DataTransferStatsDirection *s,
+                             unsigned long starting_size)
+{
+    s->expired = false;
+    s->remaining = starting_size;
+    /*
+     * The semantics of setting CONF_ssh_rekey_data to zero are to
+     * disable data-volume based rekeying completely. So if the
+     * starting size is actually zero, we don't set 'running' to true
+     * in the first place, which means we won't ever set the expired
+     * flag.
+     */
+    s->running = (starting_size != 0);
+}
+
+BinaryPacketProtocol *ssh2_bpp_new(
+    LogContext *logctx, struct DataTransferStats *stats, bool is_server);
+void ssh2_bpp_new_outgoing_crypto(
+    BinaryPacketProtocol *bpp,
+    const ssh_cipheralg *cipher, const void *ckey, const void *iv,
+    const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
+    const ssh_compression_alg *compression, bool delayed_compression);
+void ssh2_bpp_new_incoming_crypto(
+    BinaryPacketProtocol *bpp,
+    const ssh_cipheralg *cipher, const void *ckey, const void *iv,
+    const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
+    const ssh_compression_alg *compression, bool delayed_compression);
+
+/*
+ * A query method specific to the interface between ssh2transport and
+ * ssh2bpp. If true, it indicates that we're potentially in the
+ * race-condition-prone part of delayed compression setup and so
+ * asynchronous outgoing transport-layer packets are currently not
+ * being sent, which means in particular that it would be a bad idea
+ * to start a rekey because then we'd stop responding to anything
+ * _other_ than transport-layer packets and deadlock the protocol.
+ */
+bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp);
+
+BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx);
+
+/*
+ * The initial code to handle the SSH version exchange is also
+ * structured as an implementation of BinaryPacketProtocol, because
+ * that makes it easy to switch from that to the next BPP once it
+ * tells us which one we're using.
+ */
+struct ssh_version_receiver {
+    void (*got_ssh_version)(struct ssh_version_receiver *rcv,
+                            int major_version);
+};
+BinaryPacketProtocol *ssh_verstring_new(
+    Conf *conf, LogContext *logctx, bool bare_connection_mode,
+    const char *protoversion, struct ssh_version_receiver *rcv,
+    bool server_mode, const char *impl_name);
+const char *ssh_verstring_get_remote(BinaryPacketProtocol *);
+const char *ssh_verstring_get_local(BinaryPacketProtocol *);
+int ssh_verstring_get_bugs(BinaryPacketProtocol *);
+
+#ifdef MPEXT
+const ssh_cipher * ssh2_bpp_get_cscipher(BinaryPacketProtocol *bpp);
+const ssh_cipher * ssh2_bpp_get_sccipher(BinaryPacketProtocol *bpp);
+const struct ssh_compressor * ssh2_bpp_get_cscomp(BinaryPacketProtocol *bpp);
+const struct ssh_decompressor * ssh2_bpp_get_sccomp(BinaryPacketProtocol *bpp);
+#endif
+
+#endif /* PUTTY_SSHBPP_H */

+ 1011 - 0
source/putty/ssh/bpp2.c

@@ -0,0 +1,1011 @@
+/*
+ * Binary packet protocol for SSH-2.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "sshcr.h"
+
+struct ssh2_bpp_direction {
+    unsigned long sequence;
+    ssh_cipher *cipher;
+    ssh2_mac *mac;
+    bool etm_mode;
+    const ssh_compression_alg *pending_compression;
+};
+
+struct ssh2_bpp_state {
+    int crState;
+    long len, pad, payload, packetlen, maclen, length, maxlen;
+    unsigned char *buf;
+    size_t bufsize;
+    unsigned char *data;
+    unsigned cipherblk;
+    PktIn *pktin;
+    struct DataTransferStats *stats;
+    bool cbc_ignore_workaround;
+
+    struct ssh2_bpp_direction in, out;
+    /* comp and decomp logically belong in the per-direction
+     * substructure, except that they have different types */
+    ssh_decompressor *in_decomp;
+    ssh_compressor *out_comp;
+
+    bool is_server;
+    bool pending_newkeys;
+    bool pending_compression, seen_userauth_success;
+    bool enforce_next_packet_is_userauth_success;
+    unsigned nnewkeys;
+    int prev_type;
+
+    BinaryPacketProtocol bpp;
+};
+
+static void ssh2_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp);
+static PktOut *ssh2_bpp_new_pktout(int type);
+
+static const BinaryPacketProtocolVtable ssh2_bpp_vtable = {
+    // WINSCP
+    /*.free =*/ ssh2_bpp_free,
+    /*.handle_input =*/ ssh2_bpp_handle_input,
+    /*.handle_output =*/ ssh2_bpp_handle_output,
+    /*.new_pktout =*/ ssh2_bpp_new_pktout,
+    /*.queue_disconnect =*/ ssh2_bpp_queue_disconnect, /* in common.c */
+    /*.packet_size_limit =*/ 0xFFFFFFFF, /* no special limit for this bpp */
+};
+
+BinaryPacketProtocol *ssh2_bpp_new(
+    LogContext *logctx, struct DataTransferStats *stats, bool is_server)
+{
+    struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state);
+    memset(s, 0, sizeof(*s));
+    s->bpp.vt = &ssh2_bpp_vtable;
+    s->bpp.logctx = logctx;
+    s->stats = stats;
+    s->is_server = is_server;
+    ssh_bpp_common_setup(&s->bpp);
+    return &s->bpp;
+}
+
+static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s)
+{
+    /*
+     * We must free the MAC before the cipher, because sometimes the
+     * MAC is not actually separately allocated but just a different
+     * facet of the same object as the cipher, in which case
+     * ssh2_mac_free does nothing and ssh_cipher_free does the actual
+     * freeing. So if we freed the cipher first and then tried to
+     * dereference the MAC's vtable pointer to find out how to free
+     * that too, we'd be accessing freed memory.
+     */
+    if (s->out.mac)
+        ssh2_mac_free(s->out.mac);
+    if (s->out.cipher)
+        ssh_cipher_free(s->out.cipher);
+    if (s->out_comp)
+        ssh_compressor_free(s->out_comp);
+}
+
+static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s)
+{
+    /* As above, take care to free in.mac before in.cipher */
+    if (s->in.mac)
+        ssh2_mac_free(s->in.mac);
+    if (s->in.cipher)
+        ssh_cipher_free(s->in.cipher);
+    if (s->in_decomp)
+        ssh_decompressor_free(s->in_decomp);
+}
+
+static void ssh2_bpp_free(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+    sfree(s->buf);
+    ssh2_bpp_free_outgoing_crypto(s);
+    ssh2_bpp_free_incoming_crypto(s);
+    sfree(s->pktin);
+    sfree(s);
+}
+
+void ssh2_bpp_new_outgoing_crypto(
+    BinaryPacketProtocol *bpp,
+    const ssh_cipheralg *cipher, const void *ckey, const void *iv,
+    const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
+    const ssh_compression_alg *compression, bool delayed_compression)
+{
+    struct ssh2_bpp_state *s;
+    assert(bpp->vt == &ssh2_bpp_vtable);
+    s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+    ssh2_bpp_free_outgoing_crypto(s);
+
+    if (cipher) {
+        s->out.cipher = ssh_cipher_new(cipher);
+        ssh_cipher_setkey(s->out.cipher, ckey);
+        ssh_cipher_setiv(s->out.cipher, iv);
+
+        s->cbc_ignore_workaround = (
+            (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) &&
+            !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE));
+
+        bpp_logevent("Initialised %s [%s] outbound encryption",
+                     ssh_cipher_alg(s->out.cipher)->text_name, ssh_cipher_alg(s->out.cipher)->ssh2_id);
+    } else {
+        s->out.cipher = NULL;
+        s->cbc_ignore_workaround = false;
+    }
+    s->out.etm_mode = etm_mode;
+    if (mac) {
+        s->out.mac = ssh2_mac_new(mac, s->out.cipher);
+        ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen));
+
+        bpp_logevent("Initialised %s outbound MAC algorithm%s%s",
+                     ssh2_mac_text_name(s->out.mac),
+                     etm_mode ? " (in ETM mode)" : "",
+                     (s->out.cipher &&
+                      ssh_cipher_alg(s->out.cipher)->required_mac ?
+                      " (required by cipher)" : ""));
+    } else {
+        s->out.mac = NULL;
+    }
+
+    if (delayed_compression && !s->seen_userauth_success) {
+        s->out.pending_compression = compression;
+        s->out_comp = NULL;
+
+        bpp_logevent("Will enable %s compression after user authentication",
+                     s->out.pending_compression->text_name);
+    } else {
+        s->out.pending_compression = NULL;
+
+        /* 'compression' is always non-NULL, because no compression is
+         * indicated by ssh_comp_none. But this setup call may return a
+         * null out_comp. */
+        s->out_comp = ssh_compressor_new(compression);
+
+        if (s->out_comp)
+            bpp_logevent("Initialised %s compression",
+                         ssh_compressor_alg(s->out_comp)->text_name);
+    }
+}
+
+void ssh2_bpp_new_incoming_crypto(
+    BinaryPacketProtocol *bpp,
+    const ssh_cipheralg *cipher, const void *ckey, const void *iv,
+    const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
+    const ssh_compression_alg *compression, bool delayed_compression)
+{
+    struct ssh2_bpp_state *s;
+    assert(bpp->vt == &ssh2_bpp_vtable);
+    s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+    ssh2_bpp_free_incoming_crypto(s);
+
+    if (cipher) {
+        s->in.cipher = ssh_cipher_new(cipher);
+        ssh_cipher_setkey(s->in.cipher, ckey);
+        ssh_cipher_setiv(s->in.cipher, iv);
+
+        bpp_logevent("Initialised %s [%s] inbound encryption",
+                     ssh_cipher_alg(s->in.cipher)->text_name, ssh_cipher_alg(s->in.cipher)->ssh2_id);
+    } else {
+        s->in.cipher = NULL;
+    }
+    s->in.etm_mode = etm_mode;
+    if (mac) {
+        s->in.mac = ssh2_mac_new(mac, s->in.cipher);
+        ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen));
+
+        bpp_logevent("Initialised %s inbound MAC algorithm%s%s",
+                     ssh2_mac_text_name(s->in.mac),
+                     etm_mode ? " (in ETM mode)" : "",
+                     (s->in.cipher &&
+                      ssh_cipher_alg(s->in.cipher)->required_mac ?
+                      " (required by cipher)" : ""));
+    } else {
+        s->in.mac = NULL;
+    }
+
+    if (delayed_compression && !s->seen_userauth_success) {
+        s->in.pending_compression = compression;
+        s->in_decomp = NULL;
+
+        bpp_logevent("Will enable %s decompression after user authentication",
+                     s->in.pending_compression->text_name);
+    } else {
+        s->in.pending_compression = NULL;
+
+        /* 'compression' is always non-NULL, because no compression is
+         * indicated by ssh_comp_none. But this setup call may return a
+         * null in_decomp. */
+        s->in_decomp = ssh_decompressor_new(compression);
+
+        if (s->in_decomp)
+            bpp_logevent("Initialised %s decompression",
+                         ssh_decompressor_alg(s->in_decomp)->text_name);
+    }
+
+    /* Clear the pending_newkeys flag, so that handle_input below will
+     * start consuming the input data again. */
+    s->pending_newkeys = false;
+
+    /* And schedule a run of handle_input, in case there's already
+     * input data in the queue. */
+    queue_idempotent_callback(&s->bpp.ic_in_raw);
+}
+
+bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bpp_state *s;
+    assert(bpp->vt == &ssh2_bpp_vtable);
+    s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+    return s->pending_compression;
+}
+
+static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s)
+{
+    BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
+
+    if (s->in.pending_compression) {
+        s->in_decomp = ssh_decompressor_new(s->in.pending_compression);
+        bpp_logevent("Initialised delayed %s decompression",
+                     ssh_decompressor_alg(s->in_decomp)->text_name);
+        s->in.pending_compression = NULL;
+    }
+    if (s->out.pending_compression) {
+        s->out_comp = ssh_compressor_new(s->out.pending_compression);
+        bpp_logevent("Initialised delayed %s compression",
+                     ssh_compressor_alg(s->out_comp)->text_name);
+        s->out.pending_compression = NULL;
+    }
+}
+
+#define BPP_READ(ptr, len) do                                           \
+    {                                                                   \
+        bool success;                                                   \
+        crMaybeWaitUntilV((success = bufchain_try_fetch_consume(        \
+                               s->bpp.in_raw, ptr, len)) ||             \
+                          s->bpp.input_eof);                            \
+        if (!success)                                                   \
+            goto eof;                                                   \
+        ssh_check_frozen(s->bpp.ssh);                                   \
+    } while (0)
+
+#define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20)
+
+static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+    crBegin(s->crState);
+
+    while (1) {
+        s->maxlen = 0;
+        s->length = 0;
+        if (s->in.cipher)
+            s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize;
+        else
+            s->cipherblk = 8;
+        if (s->cipherblk < 8)
+            s->cipherblk = 8;
+        s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0;
+
+        if (s->in.cipher &&
+            (ssh_cipher_alg(s->in.cipher)->flags & SSH_CIPHER_IS_CBC) &&
+            s->in.mac && !s->in.etm_mode) {
+            /*
+             * When dealing with a CBC-mode cipher, we want to avoid the
+             * possibility of an attacker's tweaking the ciphertext stream
+             * so as to cause us to feed the same block to the block
+             * cipher more than once and thus leak information
+             * (VU#958563).  The way we do this is not to take any
+             * decisions on the basis of anything we've decrypted until
+             * we've verified it with a MAC.  That includes the packet
+             * length, so we just read data and check the MAC repeatedly,
+             * and when the MAC passes, see if the length we've got is
+             * plausible.
+             *
+             * This defence is unnecessary in OpenSSH ETM mode, because
+             * the whole point of ETM mode is that the attacker can't
+             * tweak the ciphertext stream at all without the MAC
+             * detecting it before we decrypt anything.
+             */
+
+            /*
+             * Make sure we have buffer space for a maximum-size packet.
+             */
+            unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen;
+            if (s->bufsize < buflimit) {
+                s->bufsize = buflimit;
+                s->buf = sresize(s->buf, s->bufsize, unsigned char);
+            }
+
+            /* Read an amount corresponding to the MAC. */
+            BPP_READ(s->buf, s->maclen);
+
+            s->packetlen = 0;
+            ssh2_mac_start(s->in.mac);
+            put_uint32(s->in.mac, s->in.sequence);
+
+            for (;;) { /* Once around this loop per cipher block. */
+                /* Read another cipher-block's worth, and tack it on to
+                 * the end. */
+                BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk);
+                /* Decrypt one more block (a little further back in
+                 * the stream). */
+                ssh_cipher_decrypt(s->in.cipher,
+                                   s->buf + s->packetlen, s->cipherblk);
+
+                /* Feed that block to the MAC. */
+                put_data(s->in.mac,
+                         s->buf + s->packetlen, s->cipherblk);
+                s->packetlen += s->cipherblk;
+
+                /* See if that gives us a valid packet. */
+                if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) &&
+                    ((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) ==
+                     s->packetlen-4))
+                    break;
+                if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
+                    ssh_sw_abort(s->bpp.ssh,
+                                 "No valid incoming packet found");
+                    crStopV;
+                }
+            }
+            s->maxlen = s->packetlen + s->maclen;
+
+            /*
+             * Now transfer the data into an output packet.
+             */
+            s->pktin = snew_plus(PktIn, s->maxlen);
+            s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+            s->pktin->type = 0;
+            s->pktin->qnode.on_free_queue = false;
+            s->data = snew_plus_get_aux(s->pktin);
+            memcpy(s->data, s->buf, s->maxlen);
+        } else if (s->in.mac && s->in.etm_mode) {
+            if (s->bufsize < 4) {
+                s->bufsize = 4;
+                s->buf = sresize(s->buf, s->bufsize, unsigned char);
+            }
+
+            /*
+             * OpenSSH encrypt-then-MAC mode: the packet length is
+             * unencrypted, unless the cipher supports length encryption.
+             */
+            BPP_READ(s->buf, 4);
+
+            /* Cipher supports length decryption, so do it */
+            if (s->in.cipher && (ssh_cipher_alg(s->in.cipher)->flags &
+                                 SSH_CIPHER_SEPARATE_LENGTH)) {
+                /* Keep the packet the same though, so the MAC passes */
+                unsigned char len[4];
+                memcpy(len, s->buf, 4);
+                ssh_cipher_decrypt_length(
+                    s->in.cipher, len, 4, s->in.sequence);
+                s->len = toint(GET_32BIT_MSB_FIRST(len));
+            } else {
+                s->len = toint(GET_32BIT_MSB_FIRST(s->buf));
+            }
+
+            /*
+             * _Completely_ silly lengths should be stomped on before they
+             * do us any more damage.
+             */
+            if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
+                s->len % s->cipherblk != 0) {
+                ssh_sw_abort(s->bpp.ssh,
+                             "Incoming packet length field was garbled");
+                crStopV;
+            }
+
+            /*
+             * So now we can work out the total packet length.
+             */
+            s->packetlen = s->len + 4;
+
+            /*
+             * Allocate the packet to return, now we know its length.
+             */
+            s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen);
+            s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+            s->pktin->type = 0;
+            s->pktin->qnode.on_free_queue = false;
+            s->data = snew_plus_get_aux(s->pktin);
+            memcpy(s->data, s->buf, 4);
+
+            /*
+             * Read the remainder of the packet.
+             */
+            BPP_READ(s->data + 4, s->packetlen + s->maclen - 4);
+
+            /*
+             * Check the MAC.
+             */
+            if (s->in.mac && !ssh2_mac_verify(
+                    s->in.mac, s->data, s->len + 4, s->in.sequence)) {
+                ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
+                crStopV;
+            }
+
+            /* Decrypt everything between the length field and the MAC. */
+            if (s->in.cipher)
+                ssh_cipher_decrypt(
+                    s->in.cipher, s->data + 4, s->packetlen - 4);
+        } else {
+            if (s->bufsize < s->cipherblk) {
+                s->bufsize = s->cipherblk;
+                s->buf = sresize(s->buf, s->bufsize, unsigned char);
+            }
+
+            /*
+             * Acquire and decrypt the first block of the packet. This will
+             * contain the length and padding details.
+             */
+            BPP_READ(s->buf, s->cipherblk);
+
+            if (s->in.cipher)
+                ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk);
+
+            /*
+             * Now get the length figure.
+             */
+            s->len = toint(GET_32BIT_MSB_FIRST(s->buf));
+
+            /*
+             * _Completely_ silly lengths should be stomped on before they
+             * do us any more damage.
+             */
+            if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
+                (s->len + 4) % s->cipherblk != 0) {
+                ssh_sw_abort(s->bpp.ssh,
+                             "Incoming packet was garbled on decryption");
+                crStopV;
+            }
+
+            /*
+             * So now we can work out the total packet length.
+             */
+            s->packetlen = s->len + 4;
+
+            /*
+             * Allocate the packet to return, now we know its length.
+             */
+            s->maxlen = s->packetlen + s->maclen;
+            s->pktin = snew_plus(PktIn, s->maxlen);
+            s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+            s->pktin->type = 0;
+            s->pktin->qnode.on_free_queue = false;
+            s->data = snew_plus_get_aux(s->pktin);
+            memcpy(s->data, s->buf, s->cipherblk);
+
+            /*
+             * Read and decrypt the remainder of the packet.
+             */
+            BPP_READ(s->data + s->cipherblk,
+                     s->packetlen + s->maclen - s->cipherblk);
+
+            /* Decrypt everything _except_ the MAC. */
+            if (s->in.cipher)
+                ssh_cipher_decrypt(
+                    s->in.cipher,
+                    s->data + s->cipherblk, s->packetlen - s->cipherblk);
+
+            /*
+             * Check the MAC.
+             */
+            if (s->in.mac && !ssh2_mac_verify(
+                    s->in.mac, s->data, s->len + 4, s->in.sequence)) {
+                ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
+                crStopV;
+            }
+        }
+        /* Get and sanity-check the amount of random padding. */
+        s->pad = s->data[4];
+        if (s->pad < 4 || s->len - s->pad < 1) {
+            ssh_sw_abort(s->bpp.ssh,
+                         "Invalid padding length on received packet");
+            crStopV;
+        }
+        /*
+         * This enables us to deduce the payload length.
+         */
+        s->payload = s->len - s->pad - 1;
+
+        s->length = s->payload + 5;
+
+        dts_consume(&s->stats->in, s->packetlen);
+
+        s->pktin->sequence = s->in.sequence++;
+
+        s->length = s->packetlen - s->pad;
+        assert(s->length >= 0);
+
+        /*
+         * Decompress packet payload.
+         */
+        {
+            unsigned char *newpayload;
+            int newlen;
+            if (s->in_decomp && ssh_decompressor_decompress(
+                    s->in_decomp, s->data + 5, s->length - 5,
+                    &newpayload, &newlen)) {
+                if (s->maxlen < newlen + 5) {
+                    PktIn *old_pktin = s->pktin;
+
+                    s->maxlen = newlen + 5;
+                    s->pktin = snew_plus(PktIn, s->maxlen);
+                    *s->pktin = *old_pktin; /* structure copy */
+                    s->data = snew_plus_get_aux(s->pktin);
+
+                    smemclr(old_pktin, s->packetlen + s->maclen);
+                    sfree(old_pktin);
+                }
+                s->length = 5 + newlen;
+                memcpy(s->data + 5, newpayload, newlen);
+                sfree(newpayload);
+            }
+        }
+
+        /*
+         * Now we can identify the semantic content of the packet,
+         * and also the initial type byte.
+         */
+        if (s->length <= 5) { /* == 5 we hope, but robustness */
+            /*
+             * RFC 4253 doesn't explicitly say that completely empty
+             * packets with no type byte are forbidden. We handle them
+             * here by giving them a type code larger than 0xFF, which
+             * will be picked up at the next layer and trigger
+             * SSH_MSG_UNIMPLEMENTED.
+             */
+            s->pktin->type = SSH_MSG_NO_TYPE_CODE;
+            s->data += 5;
+            s->length = 0;
+        } else {
+            s->pktin->type = s->data[5];
+            s->data += 6;
+            s->length -= 6;
+        }
+        BinarySource_INIT(s->pktin, s->data, s->length);
+
+        if (s->bpp.logctx) {
+            logblank_t blanks[MAX_BLANKS];
+            int nblanks = ssh2_censor_packet(
+                s->bpp.pls, s->pktin->type, false,
+                make_ptrlen(s->data, s->length), blanks);
+            log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
+                       ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+                                     s->pktin->type),
+                       s->data, s->length, nblanks, blanks,
+                       &s->pktin->sequence, 0, NULL);
+        }
+
+        if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
+            sfree(s->pktin);
+            s->pktin = NULL;
+            continue;
+        }
+
+        s->pktin->qnode.formal_size = get_avail(s->pktin);
+        pq_push(&s->bpp.in_pq, s->pktin);
+
+        {
+            int type = s->pktin->type;
+            int prev_type = s->prev_type;
+            s->prev_type = type;
+            s->pktin = NULL;
+
+            if (s->enforce_next_packet_is_userauth_success) {
+                /* See EXT_INFO handler below */
+                if (type != SSH2_MSG_USERAUTH_SUCCESS) {
+                    ssh_proto_error(s->bpp.ssh,
+                                    "Remote side sent SSH2_MSG_EXT_INFO "
+                                    "not either preceded by NEWKEYS or "
+                                    "followed by USERAUTH_SUCCESS");
+                    return;
+                }
+                s->enforce_next_packet_is_userauth_success = false;
+            }
+
+            if (type == SSH2_MSG_NEWKEYS) {
+                if (s->nnewkeys < 2)
+                    s->nnewkeys++;
+                /*
+                 * Mild layer violation: in this situation we must
+                 * suspend processing of the input byte stream until
+                 * the transport layer has initialised the new keys by
+                 * calling ssh2_bpp_new_incoming_crypto above.
+                 */
+                s->pending_newkeys = true;
+                crWaitUntilV(!s->pending_newkeys);
+                continue;
+            }
+
+            if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) {
+                /*
+                 * Another one: if we were configured with OpenSSH's
+                 * deferred compression which is triggered on receipt
+                 * of USERAUTH_SUCCESS, then this is the moment to
+                 * turn on compression.
+                 */
+                ssh2_bpp_enable_pending_compression(s);
+
+                /*
+                 * Whether or not we were doing delayed compression in
+                 * _this_ set of crypto parameters, we should set a
+                 * flag indicating that we're now authenticated, so
+                 * that a delayed compression method enabled in any
+                 * future rekey will be treated as un-delayed.
+                 */
+                s->seen_userauth_success = true;
+            }
+
+            if (type == SSH2_MSG_EXT_INFO) {
+                /*
+                 * And another: enforce that an incoming EXT_INFO is
+                 * either the message immediately after the initial
+                 * NEWKEYS, or (if we're the client) the one
+                 * immediately before USERAUTH_SUCCESS.
+                 */
+                if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) {
+                    /* OK - this is right after the first NEWKEYS. */
+                } else if (s->is_server) {
+                    /* We're the server, so they're the client.
+                     * Clients may not send EXT_INFO at _any_ other
+                     * time. */
+                    ssh_proto_error(s->bpp.ssh,
+                                    "Remote side sent SSH2_MSG_EXT_INFO "
+                                    "that was not immediately after the "
+                                    "initial NEWKEYS");
+                    return;
+                } else if (s->nnewkeys > 0 && s->seen_userauth_success) {
+                    /* We're the client, so they're the server. In
+                     * that case they may also send EXT_INFO
+                     * immediately before USERAUTH_SUCCESS. Error out
+                     * immediately if this can't _possibly_ be that
+                     * moment (because we haven't even seen NEWKEYS
+                     * yet, or because we've already seen
+                     * USERAUTH_SUCCESS). */
+                    ssh_proto_error(s->bpp.ssh,
+                                    "Remote side sent SSH2_MSG_EXT_INFO "
+                                    "after USERAUTH_SUCCESS");
+                    return;
+                } else {
+                    /* This _could_ be OK, provided the next packet is
+                     * USERAUTH_SUCCESS. Set a flag to remember to
+                     * fault it if not. */
+                    s->enforce_next_packet_is_userauth_success = true;
+                }
+            }
+
+            if (s->pending_compression && userauth_range(type)) {
+                /*
+                 * Receiving any userauth message at all indicates
+                 * that we're not about to turn on delayed compression
+                 * - either because we just _have_ done, or because
+                 * this message is a USERAUTH_FAILURE or some kind of
+                 * intermediate 'please send more data' continuation
+                 * message. Either way, we turn off the outgoing
+                 * packet blockage for now, and release any queued
+                 * output packets, so that we can make another attempt
+                 * to authenticate. The next userauth packet we send
+                 * will re-block the output direction.
+                 */
+                s->pending_compression = false;
+                queue_idempotent_callback(&s->bpp.ic_out_pq);
+            }
+        }
+    }
+
+  eof:
+    /*
+     * We've seen EOF. But we might have pushed stuff on the outgoing
+     * packet queue first, and that stuff _might_ include a DISCONNECT
+     * message, in which case we'd like to use that as the diagnostic.
+     * So first wait for the queue to have been processed.
+     */
+    crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq));
+    if (!s->bpp.expect_close) {
+        ssh_remote_error(s->bpp.ssh,
+                         "Remote side unexpectedly closed network connection");
+    } else {
+        ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
+    }
+    return;  /* avoid touching s now it's been freed */
+
+    crFinishV;
+}
+
+static PktOut *ssh2_bpp_new_pktout(int pkt_type)
+{
+    PktOut *pkt = ssh_new_packet();
+    pkt->length = 5; /* space for packet length + padding length */
+    pkt->minlen = 0;
+    pkt->type = pkt_type;
+    put_byte(pkt, pkt_type);
+    pkt->prefix = pkt->length;
+    return pkt;
+}
+
+static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt)
+{
+    int origlen, cipherblk, maclen, padding, unencrypted_prefix, i;
+
+    if (s->bpp.logctx) {
+        ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix,
+                                     pkt->length - pkt->prefix);
+        logblank_t blanks[MAX_BLANKS];
+        int nblanks = ssh2_censor_packet(
+            s->bpp.pls, pkt->type, true, pktdata, blanks);
+        log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
+                   ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+                                 pkt->type),
+                   pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence,
+                   pkt->downstream_id, pkt->additional_log_text);
+    }
+
+    cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8;
+    cipherblk = cipherblk < 8 ? 8 : cipherblk;  /* or 8 if blksize < 8 */
+
+    if (s->out_comp) {
+        unsigned char *newpayload;
+        int minlen, newlen;
+
+        /*
+         * Compress packet payload.
+         */
+        minlen = pkt->minlen;
+        if (minlen) {
+            /*
+             * Work out how much compressed data we need (at least) to
+             * make the overall packet length come to pkt->minlen.
+             */
+            if (s->out.mac)
+                minlen -= ssh2_mac_alg(s->out.mac)->len;
+            minlen -= 8;              /* length field + min padding */
+        }
+
+        ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5,
+                                &newpayload, &newlen, minlen);
+        pkt->length = 5;
+        put_data(pkt, newpayload, newlen);
+        sfree(newpayload);
+    }
+
+    /*
+     * Add padding. At least four bytes, and must also bring total
+     * length (minus MAC) up to a multiple of the block size.
+     * If pkt->forcepad is set, make sure the packet is at least that size
+     * after padding.
+     */
+    padding = 4;
+    unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0;
+    padding +=
+        (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
+        % cipherblk;
+    assert(padding <= 255);
+    maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0;
+    origlen = pkt->length;
+    for (i = 0; i < padding; i++)
+        put_byte(pkt, 0);              /* make space for random padding */
+    random_read(pkt->data + origlen, padding);
+    pkt->data[4] = padding;
+    PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4);
+
+    /* Encrypt length if the scheme requires it */
+    if (s->out.cipher &&
+        (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
+        ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4,
+                                  s->out.sequence);
+    }
+
+    put_padding(pkt, maclen, 0);
+
+    if (s->out.mac && s->out.etm_mode) {
+        /*
+         * OpenSSH-defined encrypt-then-MAC protocol.
+         */
+        if (s->out.cipher)
+            ssh_cipher_encrypt(s->out.cipher,
+                               pkt->data + 4, origlen + padding - 4);
+        ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
+                          s->out.sequence);
+    } else {
+        /*
+         * SSH-2 standard protocol.
+         */
+        if (s->out.mac)
+            ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
+                              s->out.sequence);
+        if (s->out.cipher)
+            ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding);
+    }
+
+    s->out.sequence++;       /* whether or not we MACed */
+
+    dts_consume(&s->stats->out, origlen + padding);
+}
+
+static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt)
+{
+    if (pkt->minlen > 0 && !s->out_comp) {
+        /*
+         * If we've been told to pad the packet out to a given minimum
+         * length, but we're not compressing (and hence can't get the
+         * compression to do the padding by pointlessly opening and
+         * closing zlib blocks), then our other strategy is to precede
+         * this message with an SSH_MSG_IGNORE that makes it up to the
+         * right length.
+         *
+         * A third option in principle, and the most obviously
+         * sensible, would be to set the explicit padding field in the
+         * packet to more than its minimum value. Sadly, that turns
+         * out to break some servers (our institutional memory thinks
+         * Cisco in particular) and so we abandoned that idea shortly
+         * after trying it.
+         */
+
+        /*
+         * Calculate the length we expect the real packet to have.
+         */
+        int block, length;
+        PktOut *ignore_pkt;
+
+        block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0;
+        if (block < 8)
+            block = 8;
+        length = pkt->length;
+        length += 4;       /* minimum 4 byte padding */
+        length += block-1;
+        length -= (length % block);
+        if (s->out.mac)
+            length += ssh2_mac_alg(s->out.mac)->len;
+
+        if (length < pkt->minlen) {
+            /*
+             * We need an ignore message. Calculate its length.
+             */
+            length = pkt->minlen - length;
+
+            /*
+             * And work backwards from that to the length of the
+             * contained string.
+             */
+            if (s->out.mac)
+                length -= ssh2_mac_alg(s->out.mac)->len;
+            length -= 8;               /* length field + min padding */
+            length -= 5;               /* type code + string length prefix */
+
+            if (length < 0)
+                length = 0;
+
+            ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE);
+            put_uint32(ignore_pkt, length);
+            { // WINSCP
+            size_t origlen = ignore_pkt->length;
+            size_t i; // WINSCP
+            for (i = 0; i < length; i++)
+                put_byte(ignore_pkt, 0);  /* make space for random padding */
+            random_read(ignore_pkt->data + origlen, length);
+            ssh2_bpp_format_packet_inner(s, ignore_pkt);
+            bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length);
+            ssh_free_pktout(ignore_pkt);
+            } // WINSCP
+        }
+    }
+
+    ssh2_bpp_format_packet_inner(s, pkt);
+    bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+}
+
+static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+    PktOut *pkt;
+    int n_userauth;
+
+    /*
+     * Count the userauth packets in the queue.
+     */
+    n_userauth = 0;
+    for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL;
+         pkt = pq_next(&s->bpp.out_pq, pkt))
+        if (userauth_range(pkt->type))
+            n_userauth++;
+
+    if (s->pending_compression && !n_userauth) {
+        /*
+         * We're currently blocked from sending any outgoing packets
+         * until the other end tells us whether we're going to have to
+         * enable compression or not.
+         *
+         * If our end has pushed a userauth packet on the queue, that
+         * must mean it knows that a USERAUTH_SUCCESS is not
+         * immediately forthcoming, so we unblock ourselves and send
+         * up to and including that packet. But in this if statement,
+         * there aren't any, so we're still blocked.
+         */
+        return;
+    }
+
+    if (s->cbc_ignore_workaround) {
+        /*
+         * When using a CBC-mode cipher in SSH-2, it's necessary to
+         * ensure that an attacker can't provide data to be encrypted
+         * using an IV that they know. We ensure this by inserting an
+         * SSH_MSG_IGNORE if the last cipher block of the previous
+         * packet has already been sent to the network (which we
+         * approximate conservatively by checking if it's vanished
+         * from out_raw).
+         */
+        if (bufchain_size(s->bpp.out_raw) <
+            (ssh_cipher_alg(s->out.cipher)->blksize +
+             ssh2_mac_alg(s->out.mac)->len)) {
+            /*
+             * There's less data in out_raw than the MAC size plus the
+             * cipher block size, which means at least one byte of
+             * that cipher block must already have left. Add an
+             * IGNORE.
+             */
+            pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE);
+            put_stringz(pkt, "");
+            ssh2_bpp_format_packet(s, pkt);
+        }
+    }
+
+    while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+        int type = pkt->type;
+
+        if (userauth_range(type))
+            n_userauth--;
+
+        ssh2_bpp_format_packet(s, pkt);
+        ssh_free_pktout(pkt);
+
+        if (n_userauth == 0 && s->out.pending_compression && !s->is_server) {
+            /*
+             * This is the last userauth packet in the queue, so
+             * unless our side decides to send another one in future,
+             * we have to assume will potentially provoke
+             * USERAUTH_SUCCESS. Block (non-userauth) outgoing packets
+             * until we see the reply.
+             */
+            s->pending_compression = true;
+            return;
+        } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) {
+            ssh2_bpp_enable_pending_compression(s);
+        }
+    }
+
+    ssh_sendbuffer_changed(bpp->ssh);
+}
+
+#ifdef MPEXT
+const ssh_cipher * ssh2_bpp_get_cscipher(BinaryPacketProtocol *bpp)
+{
+    return container_of(bpp, struct ssh2_bpp_state, bpp)->out.cipher;
+}
+
+const ssh_cipher * ssh2_bpp_get_sccipher(BinaryPacketProtocol *bpp)
+{
+    return container_of(bpp, struct ssh2_bpp_state, bpp)->in.cipher;
+}
+
+const struct ssh_compressor * ssh2_bpp_get_cscomp(BinaryPacketProtocol *bpp)
+{
+    return container_of(bpp, struct ssh2_bpp_state, bpp)->out_comp;
+}
+
+const struct ssh_decompressor * ssh2_bpp_get_sccomp(BinaryPacketProtocol *bpp)
+{
+    return container_of(bpp, struct ssh2_bpp_state, bpp)->in_decomp;
+}
+
+#endif

+ 107 - 0
source/putty/ssh/censor2.c

@@ -0,0 +1,107 @@
+/*
+ * Packet-censoring code for SSH-2, used to identify sensitive fields
+ * like passwords so that the logging system can avoid writing them
+ * into log files.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+int ssh2_censor_packet(
+    const PacketLogSettings *pls, int type, bool sender_is_client,
+    ptrlen pkt, logblank_t *blanks)
+{
+    int nblanks = 0;
+    ptrlen str;
+    BinarySource src[1];
+
+    BinarySource_BARE_INIT_PL(src, pkt);
+
+    if (pls->omit_data &&
+        (type == SSH2_MSG_CHANNEL_DATA ||
+         type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) {
+        /* "Session data" packets - omit the data string. */
+        get_uint32(src);              /* skip channel id */
+        if (type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
+            get_uint32(src);          /* skip extended data type */
+        str = get_string(src);
+        if (!get_err(src)) {
+            assert(nblanks < MAX_BLANKS);
+            blanks[nblanks].offset = src->pos - str.len;
+            blanks[nblanks].type = PKTLOG_OMIT;
+            blanks[nblanks].len = str.len;
+            nblanks++;
+        }
+    }
+
+    if (sender_is_client && pls->omit_passwords) {
+        if (type == SSH2_MSG_USERAUTH_REQUEST) {
+            /* If this is a password packet, blank the password(s). */
+            get_string(src);              /* username */
+            get_string(src);              /* service name */
+            str = get_string(src);        /* auth method */
+            if (ptrlen_eq_string(str, "password")) {
+                get_bool(src);
+                /* Blank the password field. */
+                str = get_string(src);
+                if (!get_err(src)) {
+                    assert(nblanks < MAX_BLANKS);
+                    blanks[nblanks].offset = src->pos - str.len;
+                    blanks[nblanks].type = PKTLOG_BLANK;
+                    blanks[nblanks].len = str.len;
+                    nblanks++;
+                    /* If there's another password field beyond it
+                     * (change of password), blank that too. */
+                    str = get_string(src);
+                    if (!get_err(src))
+                        blanks[nblanks-1].len =
+                            src->pos - blanks[nblanks].offset;
+                }
+            }
+        } else if (pls->actx == SSH2_PKTCTX_KBDINTER &&
+                   type == SSH2_MSG_USERAUTH_INFO_RESPONSE) {
+            /* If this is a keyboard-interactive response packet,
+             * blank the responses. */
+            get_uint32(src);
+            assert(nblanks < MAX_BLANKS);
+            blanks[nblanks].offset = src->pos;
+            blanks[nblanks].type = PKTLOG_BLANK;
+            do {
+                str = get_string(src);
+            } while (!get_err(src));
+            blanks[nblanks].len = src->pos - blanks[nblanks].offset;
+            nblanks++;
+        } else if (type == SSH2_MSG_CHANNEL_REQUEST) {
+            /*
+             * If this is an X forwarding request packet, blank the
+             * fake auth data.
+             *
+             * Note that while we blank the X authentication data
+             * here, we don't take any special action to blank the
+             * start of an X11 channel, so using MIT-MAGIC-COOKIE-1
+             * and actually opening an X connection without having
+             * session blanking enabled is likely to leak your cookie
+             * into the log.
+             */
+            get_uint32(src);
+            str = get_string(src);
+            if (ptrlen_eq_string(str, "x11-req")) {
+                get_bool(src);
+                get_bool(src);
+                get_string(src);
+                str = get_string(src);
+                if (!get_err(src)) {
+                    assert(nblanks < MAX_BLANKS);
+                    blanks[nblanks].offset = src->pos - str.len;
+                    blanks[nblanks].type = PKTLOG_BLANK;
+                    blanks[nblanks].len = str.len;
+                    nblanks++;
+                }
+            }
+        }
+    }
+
+    return nblanks;
+}

+ 316 - 0
source/putty/ssh/channel.h

@@ -0,0 +1,316 @@
+/*
+ * Abstraction of the various ways to handle the local end of an SSH
+ * connection-layer channel.
+ */
+
+#ifndef PUTTY_SSHCHAN_H
+#define PUTTY_SSHCHAN_H
+
+typedef struct ChannelVtable ChannelVtable;
+
+struct ChannelVtable {
+    void (*free)(Channel *);
+
+    /* Called for channel types that were created at the same time as
+     * we sent an outgoing CHANNEL_OPEN, when the confirmation comes
+     * back from the server indicating that the channel has been
+     * opened, or the failure message indicating that it hasn't,
+     * respectively. In the latter case, this must _not_ free the
+     * Channel structure - the client will call the free method
+     * separately. But it might do logging or other local cleanup. */
+    void (*open_confirmation)(Channel *);
+    void (*open_failed)(Channel *, const char *error_text);
+
+    size_t (*send)(Channel *, bool is_stderr, const void *buf, size_t len);
+    void (*send_eof)(Channel *);
+    void (*set_input_wanted)(Channel *, bool wanted);
+
+    char *(*log_close_msg)(Channel *);
+
+    bool (*want_close)(Channel *, bool sent_local_eof, bool rcvd_remote_eof);
+
+    /* A method for every channel request we know of. All of these
+     * return true for success or false for failure. */
+    bool (*rcvd_exit_status)(Channel *, int status);
+    bool (*rcvd_exit_signal)(
+        Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg);
+    bool (*rcvd_exit_signal_numeric)(
+        Channel *chan, int signum, bool core_dumped, ptrlen msg);
+    bool (*run_shell)(Channel *chan);
+    bool (*run_command)(Channel *chan, ptrlen command);
+    bool (*run_subsystem)(Channel *chan, ptrlen subsys);
+    bool (*enable_x11_forwarding)(
+        Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
+        unsigned screen_number);
+    bool (*enable_agent_forwarding)(Channel *chan);
+    bool (*allocate_pty)(
+        Channel *chan, ptrlen termtype, unsigned width, unsigned height,
+        unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
+    bool (*set_env)(Channel *chan, ptrlen var, ptrlen value);
+    bool (*send_break)(Channel *chan, unsigned length);
+    bool (*send_signal)(Channel *chan, ptrlen signame);
+    bool (*change_window_size)(
+        Channel *chan, unsigned width, unsigned height,
+        unsigned pixwidth, unsigned pixheight);
+
+    /* A method for signalling success/failure responses to channel
+     * requests initiated from the SshChannel vtable with want_reply
+     * true. */
+    void (*request_response)(Channel *, bool success);
+};
+
+struct Channel {
+    const struct ChannelVtable *vt;
+    unsigned initial_fixed_window_size;
+};
+
+static inline void chan_free(Channel *ch)
+{ ch->vt->free(ch); }
+static inline void chan_open_confirmation(Channel *ch)
+{ ch->vt->open_confirmation(ch); }
+static inline void chan_open_failed(Channel *ch, const char *err)
+{ ch->vt->open_failed(ch, err); }
+static inline size_t chan_send(
+    Channel *ch, bool err, const void *buf, size_t len)
+{ return ch->vt->send(ch, err, buf, len); }
+static inline void chan_send_eof(Channel *ch)
+{ ch->vt->send_eof(ch); }
+static inline void chan_set_input_wanted(Channel *ch, bool wanted)
+{ ch->vt->set_input_wanted(ch, wanted); }
+static inline char *chan_log_close_msg(Channel *ch)
+{ return ch->vt->log_close_msg(ch); }
+static inline bool chan_want_close(Channel *ch, bool leof, bool reof)
+{ return ch->vt->want_close(ch, leof, reof); }
+static inline bool chan_rcvd_exit_status(Channel *ch, int status)
+{ return ch->vt->rcvd_exit_status(ch, status); }
+static inline bool chan_rcvd_exit_signal(
+        Channel *ch, ptrlen sig, bool core, ptrlen msg)
+{ return ch->vt->rcvd_exit_signal(ch, sig, core, msg); }
+static inline bool chan_rcvd_exit_signal_numeric(
+        Channel *ch, int sig, bool core, ptrlen msg)
+{ return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); }
+static inline bool chan_run_shell(Channel *ch)
+{ return ch->vt->run_shell(ch); }
+static inline bool chan_run_command(Channel *ch, ptrlen cmd)
+{ return ch->vt->run_command(ch, cmd); }
+static inline bool chan_run_subsystem(Channel *ch, ptrlen subsys)
+{ return ch->vt->run_subsystem(ch, subsys); }
+static inline bool chan_enable_x11_forwarding(
+    Channel *ch, bool oneshot, ptrlen ap, ptrlen ad, unsigned scr)
+{ return ch->vt->enable_x11_forwarding(ch, oneshot, ap, ad, scr); }
+static inline bool chan_enable_agent_forwarding(Channel *ch)
+{ return ch->vt->enable_agent_forwarding(ch); }
+static inline bool chan_allocate_pty(
+    Channel *ch, ptrlen termtype, unsigned w, unsigned h,
+    unsigned pw, unsigned ph, struct ssh_ttymodes modes)
+{ return ch->vt->allocate_pty(ch, termtype, w, h, pw, ph, modes); }
+static inline bool chan_set_env(Channel *ch, ptrlen var, ptrlen value)
+{ return ch->vt->set_env(ch, var, value); }
+static inline bool chan_send_break(Channel *ch, unsigned length)
+{ return ch->vt->send_break(ch, length); }
+static inline bool chan_send_signal(Channel *ch, ptrlen signame)
+{ return ch->vt->send_signal(ch, signame); }
+static inline bool chan_change_window_size(
+    Channel *ch, unsigned w, unsigned h, unsigned pw, unsigned ph)
+{ return ch->vt->change_window_size(ch, w, h, pw, ph); }
+static inline void chan_request_response(Channel *ch, bool success)
+{ ch->vt->request_response(ch, success); }
+
+/*
+ * Reusable methods you can put in vtables to give default handling of
+ * some of those functions.
+ */
+
+/* open_confirmation / open_failed for any channel it doesn't apply to */
+void chan_remotely_opened_confirmation(Channel *chan);
+void chan_remotely_opened_failure(Channel *chan, const char *errtext);
+
+/* want_close for any channel that wants the default behaviour of not
+ * closing until both directions have had an EOF */
+bool chan_default_want_close(Channel *, bool, bool);
+
+/* default implementations that refuse all the channel requests */
+bool chan_no_exit_status(Channel *, int);
+bool chan_no_exit_signal(Channel *, ptrlen, bool, ptrlen);
+bool chan_no_exit_signal_numeric(Channel *, int, bool, ptrlen);
+bool chan_no_run_shell(Channel *chan);
+bool chan_no_run_command(Channel *chan, ptrlen command);
+bool chan_no_run_subsystem(Channel *chan, ptrlen subsys);
+bool chan_no_enable_x11_forwarding(
+    Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
+    unsigned screen_number);
+bool chan_no_enable_agent_forwarding(Channel *chan);
+bool chan_no_allocate_pty(
+    Channel *chan, ptrlen termtype, unsigned width, unsigned height,
+    unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
+bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value);
+bool chan_no_send_break(Channel *chan, unsigned length);
+bool chan_no_send_signal(Channel *chan, ptrlen signame);
+bool chan_no_change_window_size(
+    Channel *chan, unsigned width, unsigned height,
+    unsigned pixwidth, unsigned pixheight);
+
+/* default implementation that never expects to receive a response */
+void chan_no_request_response(Channel *, bool);
+
+/*
+ * Constructor for a trivial do-nothing implementation of
+ * ChannelVtable. Used for 'zombie' channels, i.e. channels whose
+ * proper local source of data has been shut down or otherwise stopped
+ * existing, but the SSH side is still there and needs some kind of a
+ * Channel implementation to talk to. In particular, the want_close
+ * method for this channel always returns 'yes, please close this
+ * channel asap', regardless of whether local and/or remote EOF have
+ * been sent - indeed, even if _neither_ has.
+ */
+Channel *zombiechan_new(void);
+
+/* ----------------------------------------------------------------------
+ * This structure is owned by an SSH connection layer, and identifies
+ * the connection layer's end of the channel, for the Channel
+ * implementation to talk back to.
+ */
+
+typedef struct SshChannelVtable SshChannelVtable;
+
+struct SshChannelVtable {
+    size_t (*write)(SshChannel *c, bool is_stderr, const void *, size_t);
+    void (*write_eof)(SshChannel *c);
+    void (*initiate_close)(SshChannel *c, const char *err);
+    void (*unthrottle)(SshChannel *c, size_t bufsize);
+    Conf *(*get_conf)(SshChannel *c);
+    void (*window_override_removed)(SshChannel *c);
+    void (*x11_sharing_handover)(SshChannel *c,
+                                 ssh_sharing_connstate *share_cs,
+                                 share_channel *share_chan,
+                                 const char *peer_addr, int peer_port,
+                                 int endian, int protomajor, int protominor,
+                                 const void *initial_data, int initial_len);
+
+    /*
+     * All the outgoing channel requests we support. Each one has a
+     * want_reply flag, which will cause a callback to
+     * chan_request_response when the result is available.
+     *
+     * The ones that return 'bool' use it to indicate that the SSH
+     * protocol in use doesn't support this request at all.
+     *
+     * (It's also intentional that not all of them have a want_reply
+     * flag: the ones that don't are because SSH-1 has no method for
+     * signalling success or failure of that request, or because we
+     * wouldn't do anything usefully different with the reply in any
+     * case.)
+     */
+    void (*send_exit_status)(SshChannel *c, int status);
+    void (*send_exit_signal)(
+        SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
+    void (*send_exit_signal_numeric)(
+        SshChannel *c, int signum, bool core_dumped, ptrlen msg);
+    void (*request_x11_forwarding)(
+        SshChannel *c, bool want_reply, const char *authproto,
+        const char *authdata, int screen_number, bool oneshot);
+    void (*request_agent_forwarding)(
+        SshChannel *c, bool want_reply);
+    void (*request_pty)(
+        SshChannel *c, bool want_reply, Conf *conf, int w, int h);
+    bool (*send_env_var)(
+        SshChannel *c, bool want_reply, const char *var, const char *value);
+    void (*start_shell)(
+        SshChannel *c, bool want_reply);
+    void (*start_command)(
+        SshChannel *c, bool want_reply, const char *command);
+    bool (*start_subsystem)(
+        SshChannel *c, bool want_reply, const char *subsystem);
+    bool (*send_serial_break)(
+        SshChannel *c, bool want_reply, int length); /* length=0 for default */
+    bool (*send_signal)(
+        SshChannel *c, bool want_reply, const char *signame);
+    void (*send_terminal_size_change)(
+        SshChannel *c, int w, int h);
+    void (*hint_channel_is_simple)(SshChannel *c);
+};
+
+struct SshChannel {
+    const struct SshChannelVtable *vt;
+    ConnectionLayer *cl;
+};
+
+static inline size_t sshfwd_write_ext(
+    SshChannel *c, bool is_stderr, const void *data, size_t len)
+{ return c->vt->write(c, is_stderr, data, len); }
+static inline size_t sshfwd_write(SshChannel *c, const void *data, size_t len)
+{ return sshfwd_write_ext(c, false, data, len); }
+static inline void sshfwd_write_eof(SshChannel *c)
+{ c->vt->write_eof(c); }
+static inline void sshfwd_initiate_close(SshChannel *c, const char *err)
+{ c->vt->initiate_close(c, err); }
+static inline void sshfwd_unthrottle(SshChannel *c, size_t bufsize)
+{ c->vt->unthrottle(c, bufsize); }
+static inline Conf *sshfwd_get_conf(SshChannel *c)
+{ return c->vt->get_conf(c); }
+static inline void sshfwd_window_override_removed(SshChannel *c)
+{ c->vt->window_override_removed(c); }
+static inline void sshfwd_x11_sharing_handover(
+    SshChannel *c, ssh_sharing_connstate *cs, share_channel *sch,
+    const char *addr, int port, int endian, int maj, int min,
+    const void *idata, int ilen)
+{ c->vt->x11_sharing_handover(c, cs, sch, addr, port, endian,
+                              maj, min, idata, ilen); }
+static inline void sshfwd_send_exit_status(SshChannel *c, int status)
+{ c->vt->send_exit_status(c, status); }
+static inline void sshfwd_send_exit_signal(
+    SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg)
+{ c->vt->send_exit_signal(c, signame, core_dumped, msg); }
+static inline void sshfwd_send_exit_signal_numeric(
+    SshChannel *c, int signum, bool core_dumped, ptrlen msg)
+{ c->vt->send_exit_signal_numeric(c, signum, core_dumped, msg); }
+static inline void sshfwd_request_x11_forwarding(
+    SshChannel *c, bool want_reply, const char *proto,
+    const char *data, int scr, bool once)
+{ c->vt->request_x11_forwarding(c, want_reply, proto, data, scr, once); }
+static inline void sshfwd_request_agent_forwarding(
+    SshChannel *c, bool want_reply)
+{ c->vt->request_agent_forwarding(c, want_reply); }
+static inline void sshfwd_request_pty(
+    SshChannel *c, bool want_reply, Conf *conf, int w, int h)
+{ c->vt->request_pty(c, want_reply, conf, w, h); }
+static inline bool sshfwd_send_env_var(
+    SshChannel *c, bool want_reply, const char *var, const char *value)
+{ return c->vt->send_env_var(c, want_reply, var, value); }
+static inline void sshfwd_start_shell(
+    SshChannel *c, bool want_reply)
+{ c->vt->start_shell(c, want_reply); }
+static inline void sshfwd_start_command(
+    SshChannel *c, bool want_reply, const char *command)
+{ c->vt->start_command(c, want_reply, command); }
+static inline bool sshfwd_start_subsystem(
+    SshChannel *c, bool want_reply, const char *subsystem)
+{ return c->vt->start_subsystem(c, want_reply, subsystem); }
+static inline bool sshfwd_send_serial_break(
+    SshChannel *c, bool want_reply, int length)
+{ return c->vt->send_serial_break(c, want_reply, length); }
+static inline bool sshfwd_send_signal(
+    SshChannel *c, bool want_reply, const char *signame)
+{ return c->vt->send_signal(c, want_reply, signame); }
+static inline void sshfwd_send_terminal_size_change(
+    SshChannel *c, int w, int h)
+{ c->vt->send_terminal_size_change(c, w, h); }
+static inline void sshfwd_hint_channel_is_simple(SshChannel *c)
+{ c->vt->hint_channel_is_simple(c); }
+
+/* ----------------------------------------------------------------------
+ * The 'main' or primary channel of the SSH connection is special,
+ * because it's the one that's connected directly to parts of the
+ * frontend such as the terminal and the specials menu. So it exposes
+ * a richer API.
+ */
+
+mainchan *mainchan_new(
+    PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
+    int term_width, int term_height, bool is_simple, SshChannel **sc_out);
+void mainchan_get_specials(
+    mainchan *mc, add_special_fn_t add_special, void *ctx);
+void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg);
+void mainchan_terminal_size(mainchan *mc, int width, int height);
+
+#endif /* PUTTY_SSHCHAN_H */

+ 1056 - 0
source/putty/ssh/common_p.c

@@ -0,0 +1,1056 @@
+/*
+ * Supporting routines used in common by all the various components of
+ * the SSH system.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "mpint.h"
+#include "ssh.h"
+#include "storage.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+
+/* ----------------------------------------------------------------------
+ * Implementation of PacketQueue.
+ */
+
+static void pq_ensure_unlinked(PacketQueueNode *node)
+{
+    if (node->on_free_queue) {
+        node->next->prev = node->prev;
+        node->prev->next = node->next;
+    } else {
+        assert(!node->next);
+        assert(!node->prev);
+    }
+}
+
+void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node)
+{
+    pq_ensure_unlinked(node);
+    node->next = &pqb->end;
+    node->prev = pqb->end.prev;
+    node->next->prev = node;
+    node->prev->next = node;
+    pqb->total_size += node->formal_size;
+
+    if (pqb->ic)
+        queue_idempotent_callback(pqb->ic);
+}
+
+void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node)
+{
+    pq_ensure_unlinked(node);
+    node->prev = &pqb->end;
+    node->next = pqb->end.next;
+    node->next->prev = node;
+    node->prev->next = node;
+    pqb->total_size += node->formal_size;
+
+    if (pqb->ic)
+        queue_idempotent_callback(pqb->ic);
+}
+
+#ifndef WINSCP
+static PacketQueueNode pktin_freeq_head = {
+    &pktin_freeq_head, &pktin_freeq_head, true
+};
+#endif
+
+/*WINSCP static*/ void pktin_free_queue_callback(void *vctx)
+{
+    struct callback_set * set = (struct callback_set *)vctx;
+    while (set->pktin_freeq_head->next != set->pktin_freeq_head) {
+        PacketQueueNode *node = set->pktin_freeq_head->next;
+        PktIn *pktin = container_of(node, PktIn, qnode);
+        set->pktin_freeq_head->next = node->next;
+        sfree(pktin);
+    }
+
+    set->pktin_freeq_head->prev = set->pktin_freeq_head;
+}
+
+#ifndef WINSCP
+static IdempotentCallback ic_pktin_free = {
+    pktin_free_queue_callback, NULL, false
+};
+#endif
+
+static inline void pq_unlink_common(PacketQueueBase *pqb,
+                                    PacketQueueNode *node)
+{
+    node->next->prev = node->prev;
+    node->prev->next = node->next;
+
+    /* Check total_size doesn't drift out of sync downwards, by
+     * ensuring it doesn't underflow when we do this subtraction */
+    assert(pqb->total_size >= node->formal_size);
+    pqb->total_size -= node->formal_size;
+
+    /* Check total_size doesn't drift out of sync upwards, by checking
+     * that it's returned to exactly zero whenever a queue is
+     * emptied */
+    assert(pqb->end.next != &pqb->end || pqb->total_size == 0);
+}
+
+static PktIn *pq_in_after(PacketQueueBase *pqb,
+                          PacketQueueNode *prev, bool pop)
+{
+    PacketQueueNode *node = prev->next;
+    if (node == &pqb->end)
+        return NULL;
+
+    if (pop) {
+        #ifdef WINSCP
+        struct callback_set * set = get_seat_callback_set(pqb->seat);
+        assert(set != NULL);
+        if (set->ic_pktin_free == NULL)
+        {
+            set->pktin_freeq_head = snew(PacketQueueNode);
+            set->pktin_freeq_head->next = set->pktin_freeq_head;
+            set->pktin_freeq_head->prev = set->pktin_freeq_head;
+            set->pktin_freeq_head->on_free_queue = TRUE;
+
+            set->ic_pktin_free = snew(IdempotentCallback);
+            set->ic_pktin_free->fn = pktin_free_queue_callback;
+            set->ic_pktin_free->ctx = set;
+            set->ic_pktin_free->queued = FALSE;
+            set->ic_pktin_free->set = set;
+        }
+        #endif
+
+        pq_unlink_common(pqb, node);
+
+        node->prev = set->pktin_freeq_head->prev; // WINSCP
+        node->next = set->pktin_freeq_head; // WINSCP
+        node->next->prev = node;
+        node->prev->next = node;
+        node->on_free_queue = true;
+
+        queue_idempotent_callback(set->ic_pktin_free); // WINSCP
+    }
+
+    return container_of(node, PktIn, qnode);
+}
+
+static PktOut *pq_out_after(PacketQueueBase *pqb,
+                            PacketQueueNode *prev, bool pop)
+{
+    PacketQueueNode *node = prev->next;
+    if (node == &pqb->end)
+        return NULL;
+
+    if (pop) {
+        pq_unlink_common(pqb, node);
+
+        node->prev = node->next = NULL;
+    }
+
+    return container_of(node, PktOut, qnode);
+}
+
+void pq_in_init(PktInQueue *pq, Seat * seat) // WINSCP
+{
+    pq->pqb.ic = NULL;
+    pq->pqb.seat = seat;
+    pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
+    pq->after = pq_in_after;
+    pq->pqb.total_size = 0;
+}
+
+void pq_out_init(PktOutQueue *pq, Seat * seat) // WINSCP
+{
+    pq->pqb.ic = NULL;
+    pq->pqb.seat = seat;
+    pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
+    pq->after = pq_out_after;
+    pq->pqb.total_size = 0;
+}
+
+void pq_in_clear(PktInQueue *pq)
+{
+    PktIn *pkt;
+    pq->pqb.ic = NULL;
+    while ((pkt = pq_pop(pq)) != NULL) {
+        /* No need to actually free these packets: pq_pop on a
+         * PktInQueue will automatically move them to the free
+         * queue. */
+    }
+}
+
+void pq_out_clear(PktOutQueue *pq)
+{
+    PktOut *pkt;
+    pq->pqb.ic = NULL;
+    while ((pkt = pq_pop(pq)) != NULL)
+        ssh_free_pktout(pkt);
+}
+
+/*
+ * Concatenate the contents of the two queues q1 and q2, and leave the
+ * result in qdest. qdest must be either empty, or one of the input
+ * queues.
+ */
+void pq_base_concatenate(PacketQueueBase *qdest,
+                         PacketQueueBase *q1, PacketQueueBase *q2)
+{
+    struct PacketQueueNode *head1, *tail1, *head2, *tail2;
+
+    size_t total_size = q1->total_size + q2->total_size;
+
+    /*
+     * Extract the contents from both input queues, and empty them.
+     */
+
+    head1 = (q1->end.next == &q1->end ? NULL : q1->end.next);
+    tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev);
+    head2 = (q2->end.next == &q2->end ? NULL : q2->end.next);
+    tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev);
+
+    q1->end.next = q1->end.prev = &q1->end;
+    q2->end.next = q2->end.prev = &q2->end;
+    q1->total_size = q2->total_size = 0;
+
+    /*
+     * Link the two lists together, handling the case where one or
+     * both is empty.
+     */
+
+    if (tail1)
+        tail1->next = head2;
+    else
+        head1 = head2;
+
+    if (head2)
+        head2->prev = tail1;
+    else
+        tail2 = tail1;
+
+    /*
+     * Check the destination queue is currently empty. (If it was one
+     * of the input queues, then it will be, because we emptied both
+     * of those just a moment ago.)
+     */
+
+    assert(qdest->end.next == &qdest->end);
+    assert(qdest->end.prev == &qdest->end);
+
+    /*
+     * If our concatenated list has anything in it, then put it in
+     * dest.
+     */
+
+    if (!head1) {
+        assert(!tail2);
+    } else {
+        assert(tail2);
+        qdest->end.next = head1;
+        qdest->end.prev = tail2;
+        head1->prev = &qdest->end;
+        tail2->next = &qdest->end;
+
+        if (qdest->ic)
+            queue_idempotent_callback(qdest->ic);
+    }
+
+    qdest->total_size = total_size;
+}
+
+/* ----------------------------------------------------------------------
+ * Low-level functions for the packet structures themselves.
+ */
+
+static void ssh_pkt_BinarySink_write(BinarySink *bs,
+                                     const void *data, size_t len);
+PktOut *ssh_new_packet(void)
+{
+    PktOut *pkt = snew(PktOut);
+
+    BinarySink_INIT(pkt, ssh_pkt_BinarySink_write);
+    pkt->data = NULL;
+    pkt->length = 0;
+    pkt->maxlen = 0;
+    pkt->downstream_id = 0;
+    pkt->additional_log_text = NULL;
+    pkt->qnode.next = pkt->qnode.prev = NULL;
+    pkt->qnode.on_free_queue = false;
+
+    return pkt;
+}
+
+static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len)
+{
+    sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len);
+    memcpy(pkt->data + pkt->length, data, len);
+    pkt->length += len;
+    pkt->qnode.formal_size = pkt->length;
+}
+
+static void ssh_pkt_BinarySink_write(BinarySink *bs,
+                                     const void *data, size_t len)
+{
+    PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut);
+    ssh_pkt_adddata(pkt, data, len);
+}
+
+void ssh_free_pktout(PktOut *pkt)
+{
+    sfree(pkt->data);
+    sfree(pkt);
+}
+
+/* ----------------------------------------------------------------------
+ * Implement zombiechan_new() and its trivial vtable.
+ */
+
+static void zombiechan_free(Channel *chan);
+static size_t zombiechan_send(
+    Channel *chan, bool is_stderr, const void *, size_t);
+static void zombiechan_set_input_wanted(Channel *chan, bool wanted);
+static void zombiechan_do_nothing(Channel *chan);
+static void zombiechan_open_failure(Channel *chan, const char *);
+static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof);
+static char *zombiechan_log_close_msg(Channel *chan) { return NULL; }
+
+static const ChannelVtable zombiechan_channelvt = {
+    /*.free =*/ zombiechan_free,
+    /*.open_confirmation =*/ zombiechan_do_nothing,
+    /*.open_failed =*/ zombiechan_open_failure,
+    /*.send =*/ zombiechan_send,
+    /*.send_eof =*/ zombiechan_do_nothing,
+    /*.set_input_wanted =*/ zombiechan_set_input_wanted,
+    /*.log_close_msg =*/ zombiechan_log_close_msg,
+    /*.want_close =*/ zombiechan_want_close,
+    /*.rcvd_exit_status =*/ chan_no_exit_status,
+    /*.rcvd_exit_signal =*/ chan_no_exit_signal,
+    /*.rcvd_exit_signal_numeric =*/ chan_no_exit_signal_numeric,
+    /*.run_shell =*/ chan_no_run_shell,
+    /*.run_command =*/ chan_no_run_command,
+    /*.run_subsystem =*/ chan_no_run_subsystem,
+    /*.enable_x11_forwarding =*/ chan_no_enable_x11_forwarding,
+    /*.enable_agent_forwarding =*/ chan_no_enable_agent_forwarding,
+    /*.allocate_pty =*/ chan_no_allocate_pty,
+    /*.set_env =*/ chan_no_set_env,
+    /*.send_break =*/ chan_no_send_break,
+    /*.send_signal =*/ chan_no_send_signal,
+    /*.change_window_size =*/ chan_no_change_window_size,
+    /*.request_response =*/ chan_no_request_response,
+};
+
+Channel *zombiechan_new(void)
+{
+    Channel *chan = snew(Channel);
+    chan->vt = &zombiechan_channelvt;
+    chan->initial_fixed_window_size = 0;
+    return chan;
+}
+
+static void zombiechan_free(Channel *chan)
+{
+    assert(chan->vt == &zombiechan_channelvt);
+    sfree(chan);
+}
+
+static void zombiechan_do_nothing(Channel *chan)
+{
+    assert(chan->vt == &zombiechan_channelvt);
+}
+
+static void zombiechan_open_failure(Channel *chan, const char *errtext)
+{
+    assert(chan->vt == &zombiechan_channelvt);
+}
+
+static size_t zombiechan_send(Channel *chan, bool is_stderr,
+                              const void *data, size_t length)
+{
+    assert(chan->vt == &zombiechan_channelvt);
+    return 0;
+}
+
+static void zombiechan_set_input_wanted(Channel *chan, bool enable)
+{
+    assert(chan->vt == &zombiechan_channelvt);
+}
+
+static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof)
+{
+    return true;
+}
+
+/* ----------------------------------------------------------------------
+ * Common routines for handling SSH tty modes.
+ */
+
+static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version)
+{
+    switch (our_opcode) {
+      case TTYMODE_ISPEED:
+        return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2;
+      case TTYMODE_OSPEED:
+        return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2;
+      default:
+        return our_opcode;
+    }
+}
+
+static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version)
+{
+    if (ssh_version == 1) {
+        switch (real_opcode) {
+          case TTYMODE_ISPEED_SSH1:
+            return TTYMODE_ISPEED;
+          case TTYMODE_OSPEED_SSH1:
+            return TTYMODE_OSPEED;
+          default:
+            return real_opcode;
+        }
+    } else {
+        switch (real_opcode) {
+          case TTYMODE_ISPEED_SSH2:
+            return TTYMODE_ISPEED;
+          case TTYMODE_OSPEED_SSH2:
+            return TTYMODE_OSPEED;
+          default:
+            return real_opcode;
+        }
+    }
+}
+
+struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf)
+{
+    struct ssh_ttymodes modes;
+    size_t i;
+
+    static const struct mode_name_type {
+        const char *mode;
+        int opcode;
+        enum { TYPE_CHAR, TYPE_BOOL } type;
+    } modes_names_types[] = {
+        #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR },
+        #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL },
+        #include "ttymode-list.h"
+        #undef TTYMODE_CHAR
+        #undef TTYMODE_FLAG
+    };
+
+    memset(&modes, 0, sizeof(modes));
+
+    for (i = 0; i < lenof(modes_names_types); i++) {
+        const struct mode_name_type *mode = &modes_names_types[i];
+        const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode);
+        char *to_free = NULL;
+
+        if (!sval)
+            sval = "N";                /* just in case */
+
+        /*
+         * sval[0] can be
+         *  - 'V', indicating that an explicit value follows it;
+         *  - 'A', indicating that we should pass the value through from
+         *    the local environment via get_ttymode; or
+         *  - 'N', indicating that we should explicitly not send this
+         *    mode.
+         */
+        if (sval[0] == 'A') {
+            sval = to_free = seat_get_ttymode(seat, mode->mode);
+        } else if (sval[0] == 'V') {
+            sval++;                    /* skip the 'V' */
+        } else {
+            /* else 'N', or something from the future we don't understand */
+            continue;
+        }
+
+        if (sval) {
+            /*
+             * Parse the string representation of the tty mode
+             * into the integer value it will take on the wire.
+             */
+            unsigned ival = 0;
+
+            switch (mode->type) {
+              case TYPE_CHAR:
+                if (*sval) {
+                    char *next = NULL;
+                    /* We know ctrlparse won't write to the string, so
+                     * casting away const is ugly but allowable. */
+                    ival = ctrlparse((char *)sval, &next);
+                    if (!next)
+                        ival = sval[0];
+                } else {
+                    ival = 255; /* special value meaning "don't set" */
+                }
+                break;
+              case TYPE_BOOL:
+                if (stricmp(sval, "yes") == 0 ||
+                    stricmp(sval, "on") == 0 ||
+                    stricmp(sval, "true") == 0 ||
+                    stricmp(sval, "+") == 0)
+                    ival = 1;      /* true */
+                else if (stricmp(sval, "no") == 0 ||
+                         stricmp(sval, "off") == 0 ||
+                         stricmp(sval, "false") == 0 ||
+                         stricmp(sval, "-") == 0)
+                    ival = 0;      /* false */
+                else
+                    ival = (atoi(sval) != 0);
+                break;
+              default:
+                unreachable("Bad mode->type");
+            }
+
+            modes.have_mode[mode->opcode] = true;
+            modes.mode_val[mode->opcode] = ival;
+        }
+
+        sfree(to_free);
+    }
+
+    {
+        unsigned ospeed, ispeed;
+
+        /* Unpick the terminal-speed config string. */
+        ospeed = ispeed = 38400;           /* last-resort defaults */
+        sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed);
+        /* Currently we unconditionally set these */
+        modes.have_mode[TTYMODE_ISPEED] = true;
+        modes.mode_val[TTYMODE_ISPEED] = ispeed;
+        modes.have_mode[TTYMODE_OSPEED] = true;
+        modes.mode_val[TTYMODE_OSPEED] = ospeed;
+    }
+
+    return modes;
+}
+
+struct ssh_ttymodes read_ttymodes_from_packet(
+    BinarySource *bs, int ssh_version)
+{
+    struct ssh_ttymodes modes;
+    memset(&modes, 0, sizeof(modes));
+
+    while (1) {
+        unsigned real_opcode, our_opcode;
+
+        real_opcode = get_byte(bs);
+        if (real_opcode == TTYMODE_END_OF_LIST)
+            break;
+        if (real_opcode >= 160) {
+            /*
+             * RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255
+             * are not yet defined, and cause parsing to stop (they
+             * should only be used after any other data)."
+             *
+             * My interpretation of this is that if one of these
+             * opcodes appears, it's not a parse _error_, but it is
+             * something that we don't know how to parse even well
+             * enough to step over it to find the next opcode, so we
+             * stop parsing now and assume that the rest of the string
+             * is composed entirely of things we don't understand and
+             * (as usual for unsupported terminal modes) silently
+             * ignore.
+             */
+            return modes;
+        }
+
+        our_opcode = our_ttymode_opcode(real_opcode, ssh_version);
+        assert(our_opcode < TTYMODE_LIMIT);
+        modes.have_mode[our_opcode] = true;
+
+        if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127)
+            modes.mode_val[our_opcode] = get_byte(bs);
+        else
+            modes.mode_val[our_opcode] = get_uint32(bs);
+    }
+
+    return modes;
+}
+
+void write_ttymodes_to_packet(BinarySink *bs, int ssh_version,
+                              struct ssh_ttymodes modes)
+{
+    unsigned i;
+
+    for (i = 0; i < TTYMODE_LIMIT; i++) {
+        if (modes.have_mode[i]) {
+            unsigned val = modes.mode_val[i];
+            unsigned opcode = real_ttymode_opcode(i, ssh_version);
+
+            put_byte(bs, opcode);
+            if (ssh_version == 1 && opcode >= 1 && opcode <= 127)
+                put_byte(bs, val);
+            else
+                put_uint32(bs, val);
+        }
+    }
+
+    put_byte(bs, TTYMODE_END_OF_LIST);
+}
+
+/* ----------------------------------------------------------------------
+ * Routine for allocating a new channel ID, given a means of finding
+ * the index field in a given channel structure.
+ */
+
+unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset)
+{
+    const unsigned CHANNEL_NUMBER_OFFSET = 256;
+    search234_state ss;
+
+    /*
+     * First-fit allocation of channel numbers: we always pick the
+     * lowest unused one.
+     *
+     * Every channel before that, and no channel after it, has an ID
+     * exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So
+     * we can use the search234 system to identify the length of that
+     * initial sequence, in a single log-time pass down the channels
+     * tree.
+     */
+    search234_start(&ss, channels);
+    while (ss.element) {
+        unsigned localid = *(unsigned *)((char *)ss.element + localid_offset);
+        if (localid == ss.index + CHANNEL_NUMBER_OFFSET)
+            search234_step(&ss, +1);
+        else
+            search234_step(&ss, -1);
+    }
+
+    /*
+     * Now ss.index gives exactly the number of channels in that
+     * initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must
+     * give precisely the lowest unused channel number.
+     */
+    return ss.index + CHANNEL_NUMBER_OFFSET;
+}
+
+/* ----------------------------------------------------------------------
+ * Functions for handling the comma-separated strings used to store
+ * lists of protocol identifiers in SSH-2.
+ */
+
+void add_to_commasep(strbuf *buf, const char *data)
+{
+    if (buf->len > 0)
+        put_byte(buf, ',');
+    put_data(buf, data, strlen(data));
+}
+
+bool get_commasep_word(ptrlen *list, ptrlen *word)
+{
+    const char *comma;
+
+    /*
+     * Discard empty list elements, should there be any, because we
+     * never want to return one as if it was a real string. (This
+     * introduces a mild tolerance of badly formatted data in lists we
+     * receive, but I think that's acceptable.)
+     */
+    while (list->len > 0 && *(const char *)list->ptr == ',') {
+        list->ptr = (const char *)list->ptr + 1;
+        list->len--;
+    }
+
+    if (!list->len)
+        return false;
+
+    comma = memchr(list->ptr, ',', list->len);
+    if (!comma) {
+        *word = *list;
+        list->len = 0;
+    } else {
+        size_t wordlen = comma - (const char *)list->ptr;
+        word->ptr = list->ptr;
+        word->len = wordlen;
+        list->ptr = (const char *)list->ptr + wordlen + 1;
+        list->len -= wordlen + 1;
+    }
+    return true;
+}
+
+/* ----------------------------------------------------------------------
+ * Functions for translating SSH packet type codes into their symbolic
+ * string names.
+ */
+
+#define TRANSLATE_UNIVERSAL(y, name, value)      \
+    if (type == value) return #name;
+#define TRANSLATE_KEX(y, name, value, ctx) \
+    if (type == value && pkt_kctx == ctx) return #name;
+#define TRANSLATE_AUTH(y, name, value, ctx) \
+    if (type == value && pkt_actx == ctx) return #name;
+
+const char *ssh1_pkt_type(int type)
+{
+    SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y);
+    return "unknown";
+}
+const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
+{
+    SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y);
+    return "unknown";
+}
+
+#undef TRANSLATE_UNIVERSAL
+#undef TRANSLATE_KEX
+#undef TRANSLATE_AUTH
+
+/* ----------------------------------------------------------------------
+ * Common helper function for clients and implementations of
+ * PacketProtocolLayer.
+ */
+
+void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new)
+{
+    new->bpp = old->bpp;
+    ssh_ppl_setup_queues(new, old->in_pq, old->out_pq);
+    new->selfptr = old->selfptr;
+    new->seat = old->seat;
+    new->ssh = old->ssh;
+
+    *new->selfptr = new;
+    ssh_ppl_free(old);
+
+    /* The new layer might need to be the first one that sends a
+     * packet, so trigger a call to its main coroutine immediately. If
+     * it doesn't need to go first, the worst that will do is return
+     * straight away. */
+    queue_idempotent_callback(&new->ic_process_queue);
+}
+
+void ssh_ppl_free(PacketProtocolLayer *ppl)
+{
+    delete_callbacks_for_context(get_seat_callback_set(ppl->seat), ppl); // WINSCP
+    ppl->vt->free(ppl);
+}
+
+static void ssh_ppl_ic_process_queue_callback(void *context)
+{
+    PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;
+    ssh_ppl_process_queue(ppl);
+}
+
+void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,
+                          PktInQueue *inq, PktOutQueue *outq)
+{
+    ppl->in_pq = inq;
+    ppl->out_pq = outq;
+    ppl->in_pq->pqb.ic = &ppl->ic_process_queue;
+    ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback;
+    ppl->ic_process_queue.ctx = ppl;
+    ppl->ic_process_queue.set = get_seat_callback_set(ppl->seat);
+
+    /* If there's already something on the input queue, it will want
+     * handling immediately. */
+    if (pq_peek(ppl->in_pq))
+        queue_idempotent_callback(&ppl->ic_process_queue);
+}
+
+void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text)
+{
+    /* Messages sent via this function are from the SSH layer, not
+     * from the server-side process, so they always have the stderr
+     * flag set. */
+    SeatOutputType stderrflag = (SeatOutputType)-1; // WINSCP
+    seat_output(ppl->seat, stderrflag, text, strlen(text)); // WINSCP
+    sfree(text);
+}
+
+size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl)
+{
+    return ppl->out_pq->pqb.total_size;
+}
+
+static void ssh_ppl_prompts_callback(void *ctx)
+{
+    ssh_ppl_process_queue((PacketProtocolLayer *)ctx);
+}
+
+prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl)
+{
+    prompts_t *p = new_prompts();
+    p->callback = ssh_ppl_prompts_callback;
+    p->callback_ctx = ppl;
+    return p;
+}
+
+/* ----------------------------------------------------------------------
+ * Common helper functions for clients and implementations of
+ * BinaryPacketProtocol.
+ */
+
+static void ssh_bpp_input_raw_data_callback(void *context)
+{
+    BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;
+    Ssh *ssh = bpp->ssh;               /* in case bpp is about to get freed */
+    ssh_bpp_handle_input(bpp);
+    /* If we've now cleared enough backlog on the input connection, we
+     * may need to unfreeze it. */
+    ssh_conn_processed_data(ssh);
+}
+
+static void ssh_bpp_output_packet_callback(void *context)
+{
+    BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;
+    ssh_bpp_handle_output(bpp);
+}
+
+void ssh_bpp_common_setup(BinaryPacketProtocol *bpp)
+{
+    pq_in_init(&bpp->in_pq, get_log_seat(bpp->logctx)); // WINSCP
+    pq_out_init(&bpp->out_pq, get_log_seat(bpp->logctx)); // WINSCP
+    bpp->input_eof = false;
+    bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback;
+    bpp->ic_in_raw.set = get_log_callback_set(bpp->logctx);
+    bpp->ic_in_raw.ctx = bpp;
+    bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback;
+    bpp->ic_out_pq.set = get_log_callback_set(bpp->logctx);
+    bpp->ic_out_pq.ctx = bpp;
+    bpp->out_pq.pqb.ic = &bpp->ic_out_pq;
+}
+
+void ssh_bpp_free(BinaryPacketProtocol *bpp)
+{
+    // WINSCP
+    delete_callbacks_for_context(get_log_callback_set(bpp->logctx), bpp);
+    bpp->vt->free(bpp);
+}
+
+void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+                               const char *msg, int category)
+{
+    PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT);
+    put_uint32(pkt, category);
+    put_stringz(pkt, msg);
+    put_stringz(pkt, "en");            /* language tag */
+    pq_push(&bpp->out_pq, pkt);
+}
+
+#define BITMAP_UNIVERSAL(y, name, value)                        \
+    | (value >= y && value < y+32                               \
+       ? 1UL << (value >= y && value < y+32 ? (value-y) : 0)    \
+       : 0)
+#define BITMAP_CONDITIONAL(y, name, value, ctx) \
+    BITMAP_UNIVERSAL(y, name, value)
+#define SSH2_BITMAP_WORD(y) \
+    (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \
+                          BITMAP_CONDITIONAL, (32*y)))
+
+bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
+{
+    #pragma warn -osh
+    static const unsigned valid_bitmap[] = {
+        SSH2_BITMAP_WORD(0),
+        SSH2_BITMAP_WORD(1),
+        SSH2_BITMAP_WORD(2),
+        SSH2_BITMAP_WORD(3),
+        SSH2_BITMAP_WORD(4),
+        SSH2_BITMAP_WORD(5),
+        SSH2_BITMAP_WORD(6),
+        SSH2_BITMAP_WORD(7),
+    };
+    #pragma warn +osh
+
+    if (pktin->type < 0x100 &&
+        !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) {
+        PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED);
+        put_uint32(pkt, pktin->sequence);
+        pq_push(&bpp->out_pq, pkt);
+        return true;
+    }
+
+    return false;
+}
+
+#undef BITMAP_UNIVERSAL
+#undef BITMAP_CONDITIONAL
+#undef SSH1_BITMAP_WORD
+
+/* ----------------------------------------------------------------------
+ * Centralised component of SSH host key verification.
+ *
+ * verify_ssh_host_key is called from both the SSH-1 and SSH-2
+ * transport layers, and does the initial work of checking whether the
+ * host key is already known. If so, it returns success on its own
+ * account; otherwise, it calls out to the Seat to give an interactive
+ * prompt (the nature of which varies depending on the Seat itself).
+ */
+
+SeatPromptResult verify_ssh_host_key(
+    InteractionReadySeat iseat, Conf *conf, const char *host, int port,
+    ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
+    char **fingerprints, void (*callback)(void *ctx, SeatPromptResult result),
+    void *ctx)
+{
+    /*
+     * First, check if the Conf includes a manual specification of the
+     * expected host key. If so, that completely supersedes everything
+     * else, including the normal host key cache _and_ including
+     * manual overrides: we return success or failure immediately,
+     * entirely based on whether the key matches the Conf.
+     */
+    if (conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) {
+        if (fingerprints) {
+            size_t i; // WINSCP
+            for (i = 0; i < SSH_N_FPTYPES; i++) {
+                /*
+                 * Each fingerprint string we've been given will have
+                 * things like 'ssh-rsa 2048' at the front of it. Strip
+                 * those off and narrow down to just the hash at the end
+                 * of the string.
+                 */
+                const char *fingerprint = fingerprints[i];
+                if (!fingerprint)
+                    continue;
+                    
+                { // WINSCP
+                const char *p = strrchr(fingerprint, ' ');
+                fingerprint = p ? p+1 : fingerprint;
+                if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,
+                                         fingerprint))
+                    return SPR_OK;
+                } // WINSCP
+            }
+        }
+
+        if (key) {
+            /*
+             * Construct the base64-encoded public key blob and see if
+             * that's listed.
+             */
+            strbuf *binblob;
+            char *base64blob;
+            int atoms, i;
+            binblob = strbuf_new();
+            ssh_key_public_blob(key, BinarySink_UPCAST(binblob));
+            atoms = (binblob->len + 2) / 3;
+            base64blob = snewn(atoms * 4 + 1, char);
+            for (i = 0; i < atoms; i++)
+                base64_encode_atom(binblob->u + 3*i,
+                                   binblob->len - 3*i, base64blob + 4*i);
+            base64blob[atoms * 4] = '\0';
+            strbuf_free(binblob);
+            if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,
+                                     base64blob)) {
+                sfree(base64blob);
+                return SPR_OK;
+            }
+            sfree(base64blob);
+        }
+
+        return SPR_SW_ABORT("Host key not in manually configured list");
+    }
+
+    /*
+     * Next, check the host key cache.
+     */
+    { // WINSCP
+    int storage_status = 1; // WINSCP check_stored_host_key(host, port, keytype, keystr);
+    if (storage_status == 0) /* matching key was found in the cache */
+        return SPR_OK;
+
+    /*
+     * The key is either missing from the cache, or does not match.
+     * Either way, fall back to an interactive prompt from the Seat.
+     */
+    { // WINSCP
+    bool mismatch = (storage_status != 1);
+    return seat_confirm_ssh_host_key(
+        iseat, host, port, keytype, keystr, keydisp, fingerprints, mismatch,
+        callback, ctx);
+    } // WINSCP
+    } // WINSCP
+}
+
+/* ----------------------------------------------------------------------
+ * Common functions shared between SSH-1 layers.
+ */
+
+bool ssh1_common_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+    /*
+     * Don't bother offering IGNORE if we've decided the remote
+     * won't cope with it, since we wouldn't bother sending it if
+     * asked anyway.
+     */
+    if (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+        add_special(ctx, "IGNORE message", SS_NOP, 0);
+        return true;
+    }
+
+    return false;
+}
+
+bool ssh1_common_filter_queue(PacketProtocolLayer *ppl)
+{
+    PktIn *pktin;
+    ptrlen msg;
+
+    while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
+        switch (pktin->type) {
+          case SSH1_MSG_DISCONNECT:
+            msg = get_string(pktin);
+            ssh_remote_error(ppl->ssh,
+                             "Remote side sent disconnect message:\n\"%.*s\"",
+                             PTRLEN_PRINTF(msg));
+            /* don't try to pop the queue, because we've been freed! */
+            return true;               /* indicate that we've been freed */
+
+          case SSH1_MSG_DEBUG:
+            msg = get_string(pktin);
+            ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg));
+            pq_pop(ppl->in_pq);
+            break;
+
+          case SSH1_MSG_IGNORE:
+            /* Do nothing, because we're ignoring it! Duhh. */
+            pq_pop(ppl->in_pq);
+            break;
+
+          default:
+            return false;
+        }
+    }
+
+    return false;
+}
+
+void ssh1_compute_session_id(
+    unsigned char *session_id, const unsigned char *cookie,
+    RSAKey *hostkey, RSAKey *servkey)
+{
+    ssh_hash *hash = ssh_hash_new(&ssh_md5);
+    size_t i; // WINSCP
+
+    for (i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;)
+        put_byte(hash, mp_get_byte(hostkey->modulus, i));
+    for (i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;)
+        put_byte(hash, mp_get_byte(servkey->modulus, i));
+    put_data(hash, cookie, 8);
+    ssh_hash_final(hash, session_id);
+}
+
+/* ----------------------------------------------------------------------
+ * Wrapper function to handle the abort-connection modes of a
+ * SeatPromptResult without a lot of verbiage at every call site.
+ *
+ * Can become ssh_sw_abort or ssh_user_close, depending on the kind of
+ * negative SeatPromptResult.
+ */
+void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context)
+{
+    if (spr.kind == SPRK_USER_ABORT) {
+        ssh_user_close(ssh, "User aborted at %s", context);
+    } else {
+        assert(spr.kind == SPRK_SW_ABORT);
+        { // WINSCP
+        char *err = spr_get_error_message(spr);
+        ssh_sw_abort(ssh, "%s", err);
+        sfree(err);
+        } // WINSCP
+    }
+}

+ 522 - 0
source/putty/ssh/connection2-client.c

@@ -0,0 +1,522 @@
+/*
+ * Client-specific parts of the SSH-2 connection layer.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "sshcr.h"
+#include "connection2.h"
+
+static ChanopenResult chan_open_x11(
+    struct ssh2_connection_state *s, SshChannel *sc,
+    ptrlen peeraddr, int peerport)
+{
+    #ifndef WINSCP
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    char *peeraddr_str;
+    Channel *ch;
+
+    ppl_logevent("Received X11 connect request from %.*s:%d",
+                 PTRLEN_PRINTF(peeraddr), peerport);
+
+    if (!s->X11_fwd_enabled && !s->connshare) {
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+            ("X11 forwarding is not enabled"));
+    }
+
+    peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL;
+    ch = x11_new_channel(
+        s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL);
+    sfree(peeraddr_str);
+    ppl_logevent("Opened X11 forward channel");
+    CHANOPEN_RETURN_SUCCESS(ch);
+    #else
+    assert(false);
+    CHANOPEN_RETURN_FAILURE(SSH2_OPEN_UNKNOWN_CHANNEL_TYPE, ("Unsupported"));
+    #endif
+}
+
+static ChanopenResult chan_open_forwarded_tcpip(
+    struct ssh2_connection_state *s, SshChannel *sc,
+    ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh_rportfwd pf, *realpf;
+    Channel *ch;
+    char *err;
+
+    ppl_logevent("Received remote port %.*s:%d open request from %.*s:%d",
+                 PTRLEN_PRINTF(fwdaddr), fwdport,
+                 PTRLEN_PRINTF(peeraddr), peerport);
+
+    pf.shost = mkstr(fwdaddr);
+    pf.sport = fwdport;
+    realpf = find234(s->rportfwds, &pf, NULL);
+    sfree(pf.shost);
+
+    if (realpf == NULL) {
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+            ("Remote port is not recognised"));
+    }
+
+    if (realpf->share_ctx) {
+        /*
+         * This port forwarding is on behalf of a connection-sharing
+         * downstream.
+         */
+        CHANOPEN_RETURN_DOWNSTREAM(realpf->share_ctx);
+    }
+
+    err = portfwdmgr_connect(
+        s->portfwdmgr, &ch, realpf->dhost, realpf->dport,
+        sc, realpf->addressfamily);
+    ppl_logevent("Attempting to forward remote port to %s:%d",
+                 realpf->dhost, realpf->dport);
+    if (err != NULL) {
+        ppl_logevent("Port open failed: %s", err);
+        sfree(err);
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_CONNECT_FAILED,
+            ("Port open failed"));
+    }
+
+    ppl_logevent("Forwarded port opened successfully");
+    CHANOPEN_RETURN_SUCCESS(ch);
+}
+
+static ChanopenResult chan_open_auth_agent(
+    struct ssh2_connection_state *s, SshChannel *sc)
+{
+    if (!ssh_agent_forwarding_permitted(&s->cl)) {
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+            ("Agent forwarding is not enabled"));
+    }
+
+    /*
+     * If possible, make a stream-oriented connection to the agent and
+     * set up an ordinary port-forwarding type channel over it.
+     */
+    { // WINSCP
+    Plug *plug;
+    Channel *ch = portfwd_raw_new(&s->cl, &plug, true);
+    Socket *skt = agent_connect(plug);
+
+    if (!sk_socket_error(skt)) {
+        portfwd_raw_setup(ch, skt, sc);
+        CHANOPEN_RETURN_SUCCESS(ch);
+    } else {
+        portfwd_raw_free(ch);
+        /*
+         * Otherwise, fall back to the old-fashioned system of parsing the
+         * forwarded data stream ourselves for message boundaries, and
+         * passing each individual message to the one-off agent_query().
+         */
+        CHANOPEN_RETURN_SUCCESS(agentf_new(sc, plug));
+    }
+    } // WINSCP
+}
+
+ChanopenResult ssh2_connection_parse_channel_open(
+    struct ssh2_connection_state *s, ptrlen type,
+    PktIn *pktin, SshChannel *sc)
+{
+    if (ptrlen_eq_string(type, "x11")) {
+        ptrlen peeraddr = get_string(pktin);
+        int peerport = get_uint32(pktin);
+
+        return chan_open_x11(s, sc, peeraddr, peerport);
+    } else if (ptrlen_eq_string(type, "forwarded-tcpip")) {
+        ptrlen fwdaddr = get_string(pktin);
+        int fwdport = toint(get_uint32(pktin));
+        ptrlen peeraddr = get_string(pktin);
+        int peerport = toint(get_uint32(pktin));
+
+        return chan_open_forwarded_tcpip(
+            s, sc, fwdaddr, fwdport, peeraddr, peerport);
+    } else if (ptrlen_eq_string(type, "[email protected]")) {
+        return chan_open_auth_agent(s, sc);
+    } else {
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_UNKNOWN_CHANNEL_TYPE,
+            ("Unsupported channel type requested"));
+    }
+}
+
+bool ssh2_connection_parse_global_request(
+    struct ssh2_connection_state *s, ptrlen type, PktIn *pktin)
+{
+    /*
+     * We don't know of any global requests that an SSH client needs
+     * to honour.
+     */
+    return false;
+}
+
+PktOut *ssh2_portfwd_chanopen(
+    struct ssh2_connection_state *s, struct ssh2_channel *c,
+    const char *hostname, int port,
+    const char *description, const SocketPeerInfo *peerinfo)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    PktOut *pktout;
+
+    /*
+     * In client mode, this function is called by portfwdmgr in
+     * response to PortListeners that were set up in
+     * portfwdmgr_config, which means that the hostname and port
+     * parameters will indicate the host we want to tell the server to
+     * connect _to_.
+     */
+
+    ppl_logevent("Opening connection to %s:%d for %s",
+                 hostname, port, description);
+
+    pktout = ssh2_chanopen_init(c, "direct-tcpip");
+    {
+        char *trimmed_host = host_strduptrim(hostname);
+        put_stringz(pktout, trimmed_host);
+        sfree(trimmed_host);
+    }
+    put_uint32(pktout, port);
+
+    /*
+     * We make up values for the originator data; partly it's too much
+     * hassle to keep track, and partly I'm not convinced the server
+     * should be told details like that about my local network
+     * configuration. The "originator IP address" is syntactically a
+     * numeric IP address, and some servers (e.g., Tectia) get upset
+     * if it doesn't match this syntax.
+     */
+    put_stringz(pktout, "0.0.0.0");
+    put_uint32(pktout, 0);
+
+    return pktout;
+}
+
+static int ssh2_rportfwd_cmp(void *av, void *bv)
+{
+    struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+    struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+    int i;
+    if ( (i = strcmp(a->shost, b->shost)) != 0)
+        return i < 0 ? -1 : +1;
+    if (a->sport > b->sport)
+        return +1;
+    if (a->sport < b->sport)
+        return -1;
+    return 0;
+}
+
+static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s,
+                                           PktIn *pktin, void *ctx)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
+
+    if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) {
+        ppl_logevent("Remote port forwarding from %s enabled",
+                     rpf->log_description);
+    } else {
+        ppl_logevent("Remote port forwarding from %s refused",
+                     rpf->log_description);
+
+        { // WINSCP
+        struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+        assert(realpf == rpf);
+        portfwdmgr_close(s->portfwdmgr, rpf->pfr);
+        free_rportfwd(rpf);
+        } // WINSCP
+    }
+}
+
+struct ssh_rportfwd *ssh2_rportfwd_alloc(
+    ConnectionLayer *cl,
+    const char *shost, int sport, const char *dhost, int dport,
+    int addressfamily, const char *log_description, PortFwdRecord *pfr,
+    ssh_sharing_connstate *share_ctx)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
+
+    if (!s->rportfwds)
+        s->rportfwds = newtree234(ssh2_rportfwd_cmp);
+
+    rpf->shost = dupstr(shost);
+    rpf->sport = sport;
+    rpf->dhost = dupstr(dhost);
+    rpf->dport = dport;
+    rpf->addressfamily = addressfamily;
+    rpf->log_description = dupstr(log_description);
+    rpf->pfr = pfr;
+    rpf->share_ctx = share_ctx;
+
+    if (add234(s->rportfwds, rpf) != rpf) {
+        free_rportfwd(rpf);
+        return NULL;
+    }
+
+    if (!rpf->share_ctx) {
+        PktOut *pktout = ssh_bpp_new_pktout(
+            s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
+        put_stringz(pktout, "tcpip-forward");
+        put_bool(pktout, true);       /* want reply */
+        put_stringz(pktout, rpf->shost);
+        put_uint32(pktout, rpf->sport);
+        pq_push(s->ppl.out_pq, pktout);
+
+        ssh2_queue_global_request_handler(
+            s, ssh2_rportfwd_globreq_response, rpf);
+    }
+
+    return rpf;
+}
+
+void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+
+    if (rpf->share_ctx) {
+        /*
+         * We don't manufacture a cancel-tcpip-forward message for
+         * remote port forwardings being removed on behalf of a
+         * downstream; we just pass through the one the downstream
+         * sent to us.
+         */
+    } else {
+        PktOut *pktout = ssh_bpp_new_pktout(
+            s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
+        put_stringz(pktout, "cancel-tcpip-forward");
+        put_bool(pktout, false);           /* _don't_ want reply */
+        put_stringz(pktout, rpf->shost);
+        put_uint32(pktout, rpf->sport);
+        pq_push(s->ppl.out_pq, pktout);
+    }
+
+    assert(s->rportfwds);
+    { // WINSCP
+    struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+    assert(realpf == rpf);
+    } // WINSCP
+    free_rportfwd(rpf);
+}
+
+SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh2_channel *c = snew(struct ssh2_channel);
+    PktOut *pktout;
+
+    c->connlayer = s;
+    ssh2_channel_init(c);
+    c->halfopen = true;
+    c->chan = chan;
+
+    ppl_logevent("Opening main session channel");
+
+    pktout = ssh2_chanopen_init(c, "session");
+    pq_push(s->ppl.out_pq, pktout);
+
+    return &c->sc;
+}
+
+SshChannel *ssh2_serverside_x11_open(
+    ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
+{
+    unreachable("Should never be called in the client");
+}
+
+SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
+{
+    unreachable("Should never be called in the client");
+}
+
+static void ssh2_channel_response(
+    struct ssh2_channel *c, PktIn *pkt, void *ctx)
+{
+    /* If pkt==NULL (because this handler has been called in response
+     * to CHANNEL_CLOSE arriving while the request was still
+     * outstanding), we treat that the same as CHANNEL_FAILURE. */
+    chan_request_response(c->chan,
+                          pkt && pkt->type == SSH2_MSG_CHANNEL_SUCCESS);
+}
+
+void ssh2channel_start_shell(SshChannel *sc, bool want_reply)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "shell", want_reply ? ssh2_channel_response : NULL, NULL);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_start_command(
+    SshChannel *sc, bool want_reply, const char *command)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "exec", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, command);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+bool ssh2channel_start_subsystem(
+    SshChannel *sc, bool want_reply, const char *subsystem)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, subsystem);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return true;
+}
+
+void ssh2channel_send_exit_status(SshChannel *sc, int status)
+{
+    unreachable("Should never be called in the client");
+}
+
+void ssh2channel_send_exit_signal(
+    SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+    unreachable("Should never be called in the client");
+}
+
+void ssh2channel_send_exit_signal_numeric(
+    SshChannel *sc, int signum, bool core_dumped, ptrlen msg)
+{
+    unreachable("Should never be called in the client");
+}
+
+void ssh2channel_request_x11_forwarding(
+    SshChannel *sc, bool want_reply, const char *authproto,
+    const char *authdata, int screen_number, bool oneshot)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_bool(pktout, oneshot);
+    put_stringz(pktout, authproto);
+    put_stringz(pktout, authdata);
+    put_uint32(pktout, screen_number);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "[email protected]",
+        want_reply ? ssh2_channel_response : NULL, NULL);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_request_pty(
+    SshChannel *sc, bool want_reply, Conf *conf, int w, int h)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+    strbuf *modebuf;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, conf_get_str(conf, CONF_termtype));
+    put_uint32(pktout, w);
+    put_uint32(pktout, h);
+    put_uint32(pktout, 0);             /* pixel width */
+    put_uint32(pktout, 0);             /* pixel height */
+    modebuf = strbuf_new();
+    write_ttymodes_to_packet(
+        BinarySink_UPCAST(modebuf), 2,
+        get_ttymodes_from_conf(s->ppl.seat, conf));
+    put_stringsb(pktout, modebuf);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+bool ssh2channel_send_env_var(
+    SshChannel *sc, bool want_reply, const char *var, const char *value)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "env", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, var);
+    put_stringz(pktout, value);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return true;
+}
+
+bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "break", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_uint32(pktout, length);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return true;
+}
+
+bool ssh2channel_send_signal(
+    SshChannel *sc, bool want_reply, const char *signame)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "signal", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, signame);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return true;
+}
+
+void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL);
+    put_uint32(pktout, w);
+    put_uint32(pktout, h);
+    put_uint32(pktout, 0);             /* pixel width */
+    put_uint32(pktout, 0);             /* pixel height */
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s)
+{
+    seat_set_trust_status(s->ppl.seat, false);
+    if (!seat_has_mixed_input_stream(s->ppl.seat))
+        return false;
+    if (seat_can_set_trust_status(s->ppl.seat))
+        return false;
+    if (ssh_is_bare(s->ppl.ssh))
+        return false;
+    return true;
+}

+ 1814 - 0
source/putty/ssh/connection2.c

@@ -0,0 +1,1814 @@
+/*
+ * Packet protocol layer for the SSH-2 connection protocol (RFC 4254).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "sshcr.h"
+#include "connection2.h"
+// WINSCP
+#define queue_toplevel_callback(FN, CTX) queue_toplevel_callback(get_log_callback_set(CTX->cl.logctx), FN, CTX)
+
+static void ssh2_connection_free(PacketProtocolLayer *);
+static void ssh2_connection_process_queue(PacketProtocolLayer *);
+static bool ssh2_connection_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl,
+                                        SessionSpecialCode code, int arg);
+static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+static unsigned int ssh2_connection_winscp_query(PacketProtocolLayer *ppl, int query);
+
+static const PacketProtocolLayerVtable ssh2_connection_vtable = {
+    // WINSCP
+    /*.free =*/ ssh2_connection_free,
+    /*.process_queue =*/ ssh2_connection_process_queue,
+    /*.get_specials =*/ ssh2_connection_get_specials,
+    /*.special_cmd =*/ ssh2_connection_special_cmd,
+    /*.reconfigure =*/ ssh2_connection_reconfigure,
+    /*.queued_data_size =*/ ssh_ppl_default_queued_data_size,
+    /*.name =*/ "ssh-connection",
+    ssh2_connection_winscp_query,
+};
+
+static SshChannel *ssh2_lportfwd_open(
+    ConnectionLayer *cl, const char *hostname, int port,
+    const char *description, const SocketPeerInfo *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(
+    ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs,
+    share_channel *share_chan);
+static void ssh2_remove_sharing_x11_display(ConnectionLayer *cl,
+                                            struct X11FakeAuth *auth);
+static void ssh2_send_packet_from_downstream(
+    ConnectionLayer *cl, unsigned id, int type,
+    const void *pkt, int pktlen, const char *additional_log_text);
+static unsigned ssh2_alloc_sharing_channel(
+    ConnectionLayer *cl, ssh_sharing_connstate *connstate);
+static void ssh2_delete_sharing_channel(
+    ConnectionLayer *cl, unsigned localid);
+static void ssh2_sharing_queue_global_request(
+    ConnectionLayer *cl, ssh_sharing_connstate *share_ctx);
+static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl);
+static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl);
+static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height);
+static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize);
+static size_t ssh2_stdin_backlog(ConnectionLayer *cl);
+static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled);
+static bool ssh2_ldisc_option(ConnectionLayer *cl, int option);
+static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value);
+static void ssh2_enable_x_fwd(ConnectionLayer *cl);
+static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted);
+static bool ssh2_get_wants_user_input(ConnectionLayer *cl);
+static void ssh2_got_user_input(ConnectionLayer *cl);
+
+static const ConnectionLayerVtable ssh2_connlayer_vtable = {
+    // WINSCP
+    /*.rportfwd_alloc =*/ ssh2_rportfwd_alloc,
+    /*.rportfwd_remove =*/ ssh2_rportfwd_remove,
+    /*.lportfwd_open =*/ ssh2_lportfwd_open,
+    /*.session_open =*/ ssh2_session_open,
+    /*.serverside_x11_open =*/ ssh2_serverside_x11_open,
+    /*.serverside_agent_open =*/ ssh2_serverside_agent_open,
+    /*.add_x11_display =*/ ssh2_add_x11_display,
+    /*.add_sharing_x11_display =*/ ssh2_add_sharing_x11_display,
+    /*.remove_sharing_x11_display =*/ ssh2_remove_sharing_x11_display,
+    /*.send_packet_from_downstream =*/ ssh2_send_packet_from_downstream,
+    /*.alloc_sharing_channel =*/ ssh2_alloc_sharing_channel,
+    /*.delete_sharing_channel =*/ ssh2_delete_sharing_channel,
+    /*.sharing_queue_global_request =*/ ssh2_sharing_queue_global_request,
+    /*.sharing_no_more_downstreams =*/ ssh2_sharing_no_more_downstreams,
+    /*.agent_forwarding_permitted =*/ ssh2_agent_forwarding_permitted,
+    /*.terminal_size =*/ ssh2_terminal_size,
+    /*.stdout_unthrottle =*/ ssh2_stdout_unthrottle,
+    /*.stdin_backlog =*/ ssh2_stdin_backlog,
+    /*.throttle_all_channels =*/ ssh2_throttle_all_channels,
+    /*.ldisc_option =*/ ssh2_ldisc_option,
+    /*.set_ldisc_option =*/ ssh2_set_ldisc_option,
+    /*.enable_x_fwd =*/ ssh2_enable_x_fwd,
+    /*.set_wants_user_input =*/ ssh2_set_wants_user_input,
+    /*.get_wants_user_input =*/ ssh2_get_wants_user_input,
+    /*.got_user_input =*/ ssh2_got_user_input,
+};
+
+static char *ssh2_channel_open_failure_error_text(PktIn *pktin)
+{
+    static const char *const reasons[] = {
+        NULL,
+        "Administratively prohibited",
+        "Connect failed",
+        "Unknown channel type",
+        "Resource shortage",
+    };
+    unsigned reason_code;
+    const char *reason_code_string;
+    char reason_code_buf[256];
+    ptrlen reason;
+
+    reason_code = get_uint32(pktin);
+    if (reason_code < lenof(reasons) && reasons[reason_code]) {
+        reason_code_string = reasons[reason_code];
+    } else {
+        reason_code_string = reason_code_buf;
+        sprintf(reason_code_buf, "unknown reason code %#x", reason_code);
+    }
+
+    reason = get_string(pktin);
+
+    return dupprintf("%s [%.*s]", reason_code_string, PTRLEN_PRINTF(reason));
+}
+
+static size_t ssh2channel_write(
+    SshChannel *c, bool is_stderr, const void *buf, size_t len);
+static void ssh2channel_write_eof(SshChannel *c);
+static void ssh2channel_initiate_close(SshChannel *c, const char *err);
+static void ssh2channel_unthrottle(SshChannel *c, size_t bufsize);
+static Conf *ssh2channel_get_conf(SshChannel *c);
+static void ssh2channel_window_override_removed(SshChannel *c);
+static void ssh2channel_x11_sharing_handover(
+    SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan,
+    const char *peer_addr, int peer_port, int endian,
+    int protomajor, int protominor, const void *initial_data, int initial_len);
+static void ssh2channel_hint_channel_is_simple(SshChannel *c);
+
+static const SshChannelVtable ssh2channel_vtable = {
+    // WINSCP
+    /*.write =*/ ssh2channel_write,
+    /*.write_eof =*/ ssh2channel_write_eof,
+    /*.initiate_close =*/ ssh2channel_initiate_close,
+    /*.unthrottle =*/ ssh2channel_unthrottle,
+    /*.get_conf =*/ ssh2channel_get_conf,
+    /*.window_override_removed =*/ ssh2channel_window_override_removed,
+    /*.x11_sharing_handover =*/ ssh2channel_x11_sharing_handover,
+    /*.send_exit_status =*/ ssh2channel_send_exit_status,
+    /*.send_exit_signal =*/ ssh2channel_send_exit_signal,
+    /*.send_exit_signal_numeric =*/ ssh2channel_send_exit_signal_numeric,
+    /*.request_x11_forwarding =*/ ssh2channel_request_x11_forwarding,
+    /*.request_agent_forwarding =*/ ssh2channel_request_agent_forwarding,
+    /*.request_pty =*/ ssh2channel_request_pty,
+    /*.send_env_var =*/ ssh2channel_send_env_var,
+    /*.start_shell =*/ ssh2channel_start_shell,
+    /*.start_command =*/ ssh2channel_start_command,
+    /*.start_subsystem =*/ ssh2channel_start_subsystem,
+    /*.send_serial_break =*/ ssh2channel_send_serial_break,
+    /*.send_signal =*/ ssh2channel_send_signal,
+    /*.send_terminal_size_change =*/ ssh2channel_send_terminal_size_change,
+    /*.hint_channel_is_simple =*/ ssh2channel_hint_channel_is_simple,
+};
+
+static void ssh2_channel_check_close(struct ssh2_channel *c);
+static void ssh2_channel_try_eof(struct ssh2_channel *c);
+static void ssh2_set_window(struct ssh2_channel *c, int newwin);
+static size_t ssh2_try_send(struct ssh2_channel *c);
+static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c);
+static void ssh2_channel_check_throttle(struct ssh2_channel *c);
+static void ssh2_channel_close_local(struct ssh2_channel *c,
+                                     const char *reason);
+static void ssh2_channel_destroy(struct ssh2_channel *c);
+
+static void ssh2_check_termination(struct ssh2_connection_state *s);
+
+struct outstanding_global_request {
+    gr_handler_fn_t handler;
+    void *ctx;
+    struct outstanding_global_request *next;
+};
+void ssh2_queue_global_request_handler(
+    struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx)
+{
+    struct outstanding_global_request *ogr =
+        snew(struct outstanding_global_request);
+    ogr->handler = handler;
+    ogr->ctx = ctx;
+    if (s->globreq_tail)
+        s->globreq_tail->next = ogr;
+    else
+        s->globreq_head = ogr;
+    s->globreq_tail = ogr;
+}
+
+static int ssh2_channelcmp(void *av, void *bv)
+{
+    const struct ssh2_channel *a = (const struct ssh2_channel *) av;
+    const struct ssh2_channel *b = (const struct ssh2_channel *) bv;
+    if (a->localid < b->localid)
+        return -1;
+    if (a->localid > b->localid)
+        return +1;
+    return 0;
+}
+
+static int ssh2_channelfind(void *av, void *bv)
+{
+    const unsigned *a = (const unsigned *) av;
+    const struct ssh2_channel *b = (const struct ssh2_channel *) bv;
+    if (*a < b->localid)
+        return -1;
+    if (*a > b->localid)
+        return +1;
+    return 0;
+}
+
+/*
+ * Each channel has a queue of outstanding CHANNEL_REQUESTS and their
+ * handlers.
+ */
+struct outstanding_channel_request {
+    cr_handler_fn_t handler;
+    void *ctx;
+    struct outstanding_channel_request *next;
+};
+
+static void ssh2_channel_free(struct ssh2_channel *c)
+{
+    bufchain_clear(&c->outbuffer);
+    bufchain_clear(&c->errbuffer);
+    while (c->chanreq_head) {
+        struct outstanding_channel_request *chanreq = c->chanreq_head;
+        c->chanreq_head = c->chanreq_head->next;
+        sfree(chanreq);
+    }
+    if (c->chan) {
+        struct ssh2_connection_state *s = c->connlayer;
+        if (s->mainchan_sc == &c->sc) {
+            s->mainchan = NULL;
+            s->mainchan_sc = NULL;
+        }
+        chan_free(c->chan);
+    }
+    sfree(c);
+}
+
+PacketProtocolLayer *ssh2_connection_new(
+    Ssh *ssh, ssh_sharing_state *connshare, bool is_simple,
+    Conf *conf, const char *peer_verstring, bufchain *user_input,
+    ConnectionLayer **cl_out)
+{
+    struct ssh2_connection_state *s = snew(struct ssh2_connection_state);
+    memset(s, 0, sizeof(*s));
+    s->ppl.vt = &ssh2_connection_vtable;
+
+    s->conf = conf_copy(conf);
+
+    s->ssh_is_simple = is_simple;
+
+    /*
+     * If the ssh_no_shell option is enabled, we disable the usual
+     * termination check, so that we persist even in the absence of
+     * any at all channels (because our purpose is probably to be a
+     * background port forwarder).
+     */
+    s->persistent = conf_get_bool(s->conf, CONF_ssh_no_shell);
+
+    s->connshare = connshare;
+    s->peer_verstring = dupstr(peer_verstring);
+
+    s->channels = newtree234(ssh2_channelcmp);
+
+    #ifndef WINSCP
+    s->x11authtree = newtree234(x11_authcmp);
+    #endif
+
+    s->user_input = user_input;
+
+    /* Need to get the log context for s->cl now, because we won't be
+     * helpfully notified when a copy is written into s->ppl by our
+     * owner. */
+    s->cl.vt = &ssh2_connlayer_vtable;
+    s->cl.logctx = ssh_get_logctx(ssh);
+
+    s->portfwdmgr = portfwdmgr_new(&s->cl);
+
+    *cl_out = &s->cl;
+    if (s->connshare)
+        ssh_connshare_provide_connlayer(s->connshare, &s->cl);
+
+    return &s->ppl;
+}
+
+static void ssh2_connection_free(PacketProtocolLayer *ppl)
+{
+    struct ssh2_connection_state *s =
+        container_of(ppl, struct ssh2_connection_state, ppl);
+    struct X11FakeAuth *auth;
+    struct ssh2_channel *c;
+    struct ssh_rportfwd *rpf;
+
+    sfree(s->peer_verstring);
+
+    conf_free(s->conf);
+
+    while ((c = delpos234(s->channels, 0)) != NULL)
+        ssh2_channel_free(c);
+    freetree234(s->channels);
+
+    #ifndef WINSCP
+    while ((auth = delpos234(s->x11authtree, 0)) != NULL) {
+        if (auth->disp)
+            x11_free_display(auth->disp);
+        x11_free_fake_auth(auth);
+    }
+    freetree234(s->x11authtree);
+    #endif
+
+    if (s->rportfwds) {
+        while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
+            free_rportfwd(rpf);
+        freetree234(s->rportfwds);
+    }
+    portfwdmgr_free(s->portfwdmgr);
+
+    if (s->antispoof_prompt)
+        free_prompts(s->antispoof_prompt);
+
+    delete_callbacks_for_context(get_log_callback_set(s->cl.logctx), s);
+
+    sfree(s);
+}
+
+static bool ssh2_connection_filter_queue(struct ssh2_connection_state *s)
+{
+    PktIn *pktin;
+    PktOut *pktout;
+    ptrlen type, data;
+    struct ssh2_channel *c;
+    struct outstanding_channel_request *ocr;
+    unsigned localid, remid, winsize, pktsize, ext_type;
+    bool want_reply, reply_success, expect_halfopen;
+    ChanopenResult chanopen_result;
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+    while (1) {
+        if (ssh2_common_filter_queue(&s->ppl))
+        {
+            return true;
+        }
+        if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+        {
+            return false;
+        }
+
+        switch (pktin->type) {
+          case SSH2_MSG_GLOBAL_REQUEST:
+            type = get_string(pktin);
+            want_reply = get_bool(pktin);
+
+            reply_success = ssh2_connection_parse_global_request(
+                s, type, pktin);
+
+            if (want_reply) {
+                int type = (reply_success ? SSH2_MSG_REQUEST_SUCCESS :
+                            SSH2_MSG_REQUEST_FAILURE);
+                pktout = ssh_bpp_new_pktout(s->ppl.bpp, type);
+                pq_push(s->ppl.out_pq, pktout);
+            }
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH2_MSG_REQUEST_SUCCESS:
+          case SSH2_MSG_REQUEST_FAILURE:
+            if (!s->globreq_head) {
+                ssh_proto_error(
+                    s->ppl.ssh,
+                    "Received %s with no outstanding global request",
+                    ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx,
+                                  pktin->type));
+                return true;
+            }
+
+            s->globreq_head->handler(s, pktin, s->globreq_head->ctx);
+            {
+                struct outstanding_global_request *tmp = s->globreq_head;
+                s->globreq_head = s->globreq_head->next;
+                sfree(tmp);
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH2_MSG_CHANNEL_OPEN:
+            type = get_string(pktin);
+            c = snew(struct ssh2_channel);
+            c->connlayer = s;
+            c->chan = NULL;
+
+            remid = get_uint32(pktin);
+            winsize = get_uint32(pktin);
+            pktsize = get_uint32(pktin);
+
+            chanopen_result = ssh2_connection_parse_channel_open(
+                s, type, pktin, &c->sc);
+
+            if (chanopen_result.outcome == CHANOPEN_RESULT_DOWNSTREAM) {
+                /*
+                 * This channel-open request needs to go to a
+                 * connection-sharing downstream, so abandon our own
+                 * channel-open procedure and just pass the message on
+                 * to sharing.c.
+                 */
+                share_got_pkt_from_server(
+                    chanopen_result.u.downstream.share_ctx, pktin->type,
+                    BinarySource_UPCAST(pktin)->data,
+                    BinarySource_UPCAST(pktin)->len);
+                sfree(c);
+                break;
+            }
+
+            c->remoteid = remid;
+            c->halfopen = false;
+            if (chanopen_result.outcome == CHANOPEN_RESULT_FAILURE) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE);
+                put_uint32(pktout, c->remoteid);
+                put_uint32(pktout, chanopen_result.u.failure.reason_code);
+                put_stringz(pktout, chanopen_result.u.failure.wire_message);
+                put_stringz(pktout, "en");      /* language tag */
+                pq_push(s->ppl.out_pq, pktout);
+                ppl_logevent("Rejected channel open: %s",
+                             chanopen_result.u.failure.wire_message);
+                sfree(chanopen_result.u.failure.wire_message);
+                sfree(c);
+            } else {
+                c->chan = chanopen_result.u.success.channel;
+                ssh2_channel_init(c);
+                c->remwindow = winsize;
+                c->remmaxpkt = pktsize;
+                if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit)
+                    c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit;
+                if (c->chan->initial_fixed_window_size) {
+                    c->locwindow = c->locmaxwin = c->remlocwin =
+                        c->chan->initial_fixed_window_size;
+                }
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+                put_uint32(pktout, c->remoteid);
+                put_uint32(pktout, c->localid);
+                put_uint32(pktout, c->locwindow);
+                put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
+                pq_push(s->ppl.out_pq, pktout);
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH2_MSG_CHANNEL_DATA:
+          case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+          case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+          case SSH2_MSG_CHANNEL_REQUEST:
+          case SSH2_MSG_CHANNEL_EOF:
+          case SSH2_MSG_CHANNEL_CLOSE:
+          case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+          case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+          case SSH2_MSG_CHANNEL_SUCCESS:
+          case SSH2_MSG_CHANNEL_FAILURE:
+            /*
+             * Common preliminary code for all the messages from the
+             * server that cite one of our channel ids: look up that
+             * channel id, check it exists, and if it's for a sharing
+             * downstream, pass it on.
+             */
+            localid = get_uint32(pktin);
+            c = find234(s->channels, &localid, ssh2_channelfind);
+
+            if (c && c->sharectx) {
+                share_got_pkt_from_server(c->sharectx, pktin->type,
+                                          BinarySource_UPCAST(pktin)->data,
+                                          BinarySource_UPCAST(pktin)->len);
+                pq_pop(s->ppl.in_pq);
+                break;
+            }
+
+            expect_halfopen = (
+                pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION ||
+                pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE);
+
+            if (!c || c->halfopen != expect_halfopen) {
+                ssh_proto_error(s->ppl.ssh,
+                                "Received %s for %s channel %u",
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type),
+                                (!c ? "nonexistent" :
+                                 c->halfopen ? "half-open" : "open"),
+                                localid);
+                return true;
+            }
+
+            switch (pktin->type) {
+              case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+                assert(c->halfopen);
+                c->remoteid = get_uint32(pktin);
+                c->halfopen = false;
+                c->remwindow = get_uint32(pktin);
+                c->remmaxpkt = get_uint32(pktin);
+                if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit)
+                    c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit;
+
+                chan_open_confirmation(c->chan);
+
+                /*
+                 * Now that the channel is fully open, it's possible
+                 * in principle to immediately close it. Check whether
+                 * it wants us to!
+                 *
+                 * This can occur if a local socket error occurred
+                 * between us sending out CHANNEL_OPEN and receiving
+                 * OPEN_CONFIRMATION. If that happens, all we can do
+                 * is immediately initiate close proceedings now that
+                 * we know the server's id to put in the close
+                 * message. We'll have handled that in this code by
+                 * having already turned c->chan into a zombie, so its
+                 * want_close method (which ssh2_channel_check_close
+                 * will consult) will already be returning true.
+                 */
+                ssh2_channel_check_close(c);
+
+                if (c->pending_eof)
+                    ssh2_channel_try_eof(c); /* in case we had a pending EOF */
+                break;
+
+              case SSH2_MSG_CHANNEL_OPEN_FAILURE: {
+                assert(c->halfopen);
+
+                { // WINSCP
+                char *err = ssh2_channel_open_failure_error_text(pktin);
+                chan_open_failed(c->chan, err);
+                sfree(err);
+                } // WINSCP
+
+                del234(s->channels, c);
+                ssh2_channel_free(c);
+
+                break;
+              }
+
+              case SSH2_MSG_CHANNEL_DATA:
+              case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+                ext_type = (pktin->type == SSH2_MSG_CHANNEL_DATA ? 0 :
+                            get_uint32(pktin));
+                data = get_string(pktin);
+                if (!get_err(pktin)) {
+                    int bufsize;
+                    c->locwindow -= data.len;
+                    c->remlocwin -= data.len;
+                    if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR)
+                        data.len = 0; /* ignore unknown extended data */
+                    bufsize = chan_send(
+                        c->chan, ext_type == SSH2_EXTENDED_DATA_STDERR,
+                        data.ptr, data.len);
+
+                    /*
+                     * The channel may have turned into a connection-
+                     * shared one as a result of that chan_send, e.g.
+                     * if the data we just provided completed the X11
+                     * auth phase and caused a callback to
+                     * x11_sharing_handover. If so, do nothing
+                     * further.
+                     */
+                    if (c->sharectx)
+                        break;
+
+                    /*
+                     * If it looks like the remote end hit the end of
+                     * its window, and we didn't want it to do that,
+                     * think about using a larger window.
+                     */
+                    if (c->remlocwin <= 0 &&
+                        c->throttle_state == UNTHROTTLED &&
+                        c->locmaxwin < 0x40000000)
+                        c->locmaxwin += OUR_V2_WINSIZE;
+
+                    /*
+                     * If we are not buffering too much data, enlarge
+                     * the window again at the remote side. If we are
+                     * buffering too much, we may still need to adjust
+                     * the window if the server's sent excess data.
+                     */
+                    if (bufsize < c->locmaxwin)
+                        ssh2_set_window(c, c->locmaxwin - bufsize);
+
+                    /*
+                     * If we're either buffering way too much data, or
+                     * if we're buffering anything at all and we're in
+                     * "simple" mode, throttle the whole channel.
+                     */
+                    if ((bufsize > c->locmaxwin ||
+                         (s->ssh_is_simple && bufsize>0)) &&
+                        !c->throttling_conn) {
+                        c->throttling_conn = true;
+                        ssh_throttle_conn(s->ppl.ssh, +1);
+                    }
+                }
+                break;
+
+              case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+                if (!(c->closes & CLOSES_SENT_EOF)) {
+                    c->remwindow += get_uint32(pktin);
+                    ssh2_try_send_and_unthrottle(c);
+                }
+                break;
+
+              case SSH2_MSG_CHANNEL_REQUEST:
+                type = get_string(pktin);
+                want_reply = get_bool(pktin);
+
+                reply_success = false;
+
+                if (c->closes & CLOSES_SENT_CLOSE) {
+                    /*
+                     * We don't reply to channel requests after we've
+                     * sent CHANNEL_CLOSE for the channel, because our
+                     * reply might cross in the network with the other
+                     * side's CHANNEL_CLOSE and arrive after they have
+                     * wound the channel up completely.
+                     */
+                    want_reply = false;
+                }
+
+                /*
+                 * Try every channel request name we recognise, no
+                 * matter what the channel, and see if the Channel
+                 * instance will accept it.
+                 */
+                if (ptrlen_eq_string(type, "exit-status")) {
+                    int exitcode = toint(get_uint32(pktin));
+                    reply_success = chan_rcvd_exit_status(c->chan, exitcode);
+                } else if (ptrlen_eq_string(type, "exit-signal")) {
+                    ptrlen signame;
+                    int signum;
+                    bool core = false;
+                    ptrlen errmsg;
+                    int format;
+
+                    /*
+                     * ICK: older versions of OpenSSH (e.g. 3.4p1)
+                     * provide an `int' for the signal, despite its
+                     * having been a `string' in the drafts of RFC
+                     * 4254 since at least 2001. (Fixed in session.c
+                     * 1.147.) Try to infer which we can safely parse
+                     * it as.
+                     */
+
+                    size_t startpos = BinarySource_UPCAST(pktin)->pos;
+
+                    for (format = 0; format < 2; format++) {
+                        BinarySource_UPCAST(pktin)->pos = startpos;
+                        BinarySource_UPCAST(pktin)->err = BSE_NO_ERROR;
+
+                        /* placate compiler warnings about unin */
+                        signame = make_ptrlen(NULL, 0);
+                        signum = 0;
+
+                        if (format == 0) /* standard string-based format */
+                            signame = get_string(pktin);
+                        else      /* nonstandard integer format */
+                            signum = toint(get_uint32(pktin));
+
+                        core = get_bool(pktin);
+                        errmsg = get_string(pktin); /* error message */
+                        get_string(pktin);     /* language tag */
+
+                        if (!get_err(pktin) && get_avail(pktin) == 0)
+                            break;             /* successful parse */
+                    }
+
+                    switch (format) {
+                      case 0:
+                        reply_success = chan_rcvd_exit_signal(
+                            c->chan, signame, core, errmsg);
+                        break;
+                      case 1:
+                        reply_success = chan_rcvd_exit_signal_numeric(
+                            c->chan, signum, core, errmsg);
+                        break;
+                      default:
+                        /* Couldn't parse this message in either format */
+                        reply_success = false;
+                        break;
+                    }
+                } else if (ptrlen_eq_string(type, "shell")) {
+                    reply_success = chan_run_shell(c->chan);
+                } else if (ptrlen_eq_string(type, "exec")) {
+                    ptrlen command = get_string(pktin);
+                    reply_success = chan_run_command(c->chan, command);
+                } else if (ptrlen_eq_string(type, "subsystem")) {
+                    ptrlen subsys = get_string(pktin);
+                    reply_success = chan_run_subsystem(c->chan, subsys);
+                } else if (ptrlen_eq_string(type, "x11-req")) {
+                    bool oneshot = get_bool(pktin);
+                    ptrlen authproto = get_string(pktin);
+                    ptrlen authdata = get_string(pktin);
+                    unsigned screen_number = get_uint32(pktin);
+                    reply_success = chan_enable_x11_forwarding(
+                        c->chan, oneshot, authproto, authdata, screen_number);
+                } else if (ptrlen_eq_string(type,
+                                            "[email protected]")) {
+                    reply_success = chan_enable_agent_forwarding(c->chan);
+                } else if (ptrlen_eq_string(type, "pty-req")) {
+                    ptrlen termtype = get_string(pktin);
+                    unsigned width = get_uint32(pktin);
+                    unsigned height = get_uint32(pktin);
+                    unsigned pixwidth = get_uint32(pktin);
+                    unsigned pixheight = get_uint32(pktin);
+                    ptrlen encoded_modes = get_string(pktin);
+                    BinarySource bs_modes[1];
+                    struct ssh_ttymodes modes;
+
+                    BinarySource_BARE_INIT_PL(bs_modes, encoded_modes);
+                    modes = read_ttymodes_from_packet(bs_modes, 2);
+                    if (get_err(bs_modes) || get_avail(bs_modes) > 0) {
+                        ppl_logevent("Unable to decode terminal mode string");
+                        reply_success = false;
+                    } else {
+                        reply_success = chan_allocate_pty(
+                            c->chan, termtype, width, height,
+                            pixwidth, pixheight, modes);
+                    }
+                } else if (ptrlen_eq_string(type, "env")) {
+                    ptrlen var = get_string(pktin);
+                    ptrlen value = get_string(pktin);
+
+                    reply_success = chan_set_env(c->chan, var, value);
+                } else if (ptrlen_eq_string(type, "break")) {
+                    unsigned length = get_uint32(pktin);
+
+                    reply_success = chan_send_break(c->chan, length);
+                } else if (ptrlen_eq_string(type, "signal")) {
+                    ptrlen signame = get_string(pktin);
+
+                    reply_success = chan_send_signal(c->chan, signame);
+                } else if (ptrlen_eq_string(type, "window-change")) {
+                    unsigned width = get_uint32(pktin);
+                    unsigned height = get_uint32(pktin);
+                    unsigned pixwidth = get_uint32(pktin);
+                    unsigned pixheight = get_uint32(pktin);
+                    reply_success = chan_change_window_size(
+                        c->chan, width, height, pixwidth, pixheight);
+                }
+                if (want_reply) {
+                    int type = (reply_success ? SSH2_MSG_CHANNEL_SUCCESS :
+                                SSH2_MSG_CHANNEL_FAILURE);
+                    pktout = ssh_bpp_new_pktout(s->ppl.bpp, type);
+                    put_uint32(pktout, c->remoteid);
+                    pq_push(s->ppl.out_pq, pktout);
+                }
+                break;
+
+              case SSH2_MSG_CHANNEL_SUCCESS:
+              case SSH2_MSG_CHANNEL_FAILURE:
+                ocr = c->chanreq_head;
+                if (!ocr) {
+                    ssh_proto_error(
+                        s->ppl.ssh,
+                        "Received %s for channel %d with no outstanding "
+                        "channel request",
+                        ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                      s->ppl.bpp->pls->actx, pktin->type),
+                        c->localid);
+                    return true;
+                }
+                ocr->handler(c, pktin, ocr->ctx);
+                c->chanreq_head = ocr->next;
+                sfree(ocr);
+                /*
+                 * We may now initiate channel-closing procedures, if
+                 * that CHANNEL_REQUEST was the last thing outstanding
+                 * before we send CHANNEL_CLOSE.
+                 */
+                ssh2_channel_check_close(c);
+                break;
+
+              case SSH2_MSG_CHANNEL_EOF:
+                if (!(c->closes & CLOSES_RCVD_EOF)) {
+                    c->closes |= CLOSES_RCVD_EOF;
+                    chan_send_eof(c->chan);
+                    ssh2_channel_check_close(c);
+                }
+                break;
+
+              case SSH2_MSG_CHANNEL_CLOSE:
+                /*
+                 * When we receive CLOSE on a channel, we assume it
+                 * comes with an implied EOF if we haven't seen EOF
+                 * yet.
+                 */
+                if (!(c->closes & CLOSES_RCVD_EOF)) {
+                    c->closes |= CLOSES_RCVD_EOF;
+                    chan_send_eof(c->chan);
+                }
+
+                if (!(s->ppl.remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) {
+                    /*
+                     * It also means we stop expecting to see replies
+                     * to any outstanding channel requests, so clean
+                     * those up too. (ssh_chanreq_init will enforce by
+                     * assertion that we don't subsequently put
+                     * anything back on this list.)
+                     */
+                    while (c->chanreq_head) {
+                        struct outstanding_channel_request *ocr =
+                            c->chanreq_head;
+                        ocr->handler(c, NULL, ocr->ctx);
+                        c->chanreq_head = ocr->next;
+                        sfree(ocr);
+                    }
+                }
+
+                /*
+                 * And we also send an outgoing EOF, if we haven't
+                 * already, on the assumption that CLOSE is a pretty
+                 * forceful announcement that the remote side is doing
+                 * away with the entire channel. (If it had wanted to
+                 * send us EOF and continue receiving data from us, it
+                 * would have just sent CHANNEL_EOF.)
+                 */
+                if (!(c->closes & CLOSES_SENT_EOF)) {
+                    /*
+                     * Abandon any buffered data we still wanted to
+                     * send to this channel. Receiving a CHANNEL_CLOSE
+                     * is an indication that the server really wants
+                     * to get on and _destroy_ this channel, and it
+                     * isn't going to send us any further
+                     * WINDOW_ADJUSTs to permit us to send pending
+                     * stuff.
+                     */
+                    bufchain_clear(&c->outbuffer);
+                    bufchain_clear(&c->errbuffer);
+
+                    /*
+                     * Send outgoing EOF.
+                     */
+                    sshfwd_write_eof(&c->sc);
+
+                    /*
+                     * Make sure we don't read any more from whatever
+                     * our local data source is for this channel.
+                     * (This will pick up on the changes made by
+                     * sshfwd_write_eof.)
+                     */
+                    ssh2_channel_check_throttle(c);
+                }
+
+                /*
+                 * Now process the actual close.
+                 */
+                if (!(c->closes & CLOSES_RCVD_CLOSE)) {
+                    c->closes |= CLOSES_RCVD_CLOSE;
+                    ssh2_channel_check_close(c);
+                }
+
+                break;
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          default:
+            return false;
+        }
+    }
+}
+
+static void ssh2_handle_winadj_response(struct ssh2_channel *c,
+                                        PktIn *pktin, void *ctx)
+{
+    unsigned *sizep = ctx;
+
+    /*
+     * Winadj responses should always be failures. However, at least
+     * one server ("boks_sshd") is known to return SUCCESS for channel
+     * requests it's never heard of, such as "winadj@putty". Raised
+     * with foxt.com as bug 090916-090424, but for the sake of a quiet
+     * life, we don't worry about what kind of response we got.
+     */
+
+    c->remlocwin += *sizep;
+    sfree(sizep);
+    /*
+     * winadj messages are only sent when the window is fully open, so
+     * if we get an ack of one, we know any pending unthrottle is
+     * complete.
+     */
+    if (c->throttle_state == UNTHROTTLING)
+        c->throttle_state = UNTHROTTLED;
+}
+
+static void ssh2_set_window(struct ssh2_channel *c, int newwin)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+
+    /*
+     * Never send WINDOW_ADJUST for a channel that the remote side has
+     * already sent EOF on; there's no point, since it won't be
+     * sending any more data anyway. Ditto if _we've_ already sent
+     * CLOSE.
+     */
+    if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
+        return;
+
+    /*
+     * If the client-side Channel is in an initial setup phase with a
+     * fixed window size, e.g. for an X11 channel when we're still
+     * waiting to see its initial auth and may yet hand it off to a
+     * downstream, don't send any WINDOW_ADJUST either.
+     */
+    if (c->chan->initial_fixed_window_size)
+        return;
+
+    /*
+     * If the remote end has a habit of ignoring maxpkt, limit the
+     * window so that it has no choice (assuming it doesn't ignore the
+     * window as well).
+     */
+    if ((s->ppl.remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT)
+        newwin = OUR_V2_MAXPKT;
+
+    /*
+     * Only send a WINDOW_ADJUST if there's significantly more window
+     * available than the other end thinks there is.  This saves us
+     * sending a WINDOW_ADJUST for every character in a shell session.
+     *
+     * "Significant" is arbitrarily defined as half the window size.
+     */
+    if (newwin / 2 >= c->locwindow) {
+        PktOut *pktout;
+        unsigned *up;
+
+        /*
+         * In order to keep track of how much window the client
+         * actually has available, we'd like it to acknowledge each
+         * WINDOW_ADJUST.  We can't do that directly, so we accompany
+         * it with a CHANNEL_REQUEST that has to be acknowledged.
+         *
+         * This is only necessary if we're opening the window wide.
+         * If we're not, then throughput is being constrained by
+         * something other than the maximum window size anyway.
+         */
+        if (newwin == c->locmaxwin &&
+            !(s->ppl.remote_bugs & BUG_CHOKES_ON_WINADJ)) {
+            up = snew(unsigned);
+            *up = newwin - c->locwindow;
+            pktout = ssh2_chanreq_init(c, "[email protected]",
+                                       ssh2_handle_winadj_response, up);
+            pq_push(s->ppl.out_pq, pktout);
+
+            if (c->throttle_state != UNTHROTTLED)
+                c->throttle_state = UNTHROTTLING;
+        } else {
+            /* Pretend the WINDOW_ADJUST was acked immediately. */
+            c->remlocwin = newwin;
+            c->throttle_state = THROTTLED;
+        }
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+        put_uint32(pktout, c->remoteid);
+        put_uint32(pktout, newwin - c->locwindow);
+        pq_push(s->ppl.out_pq, pktout);
+        c->locwindow = newwin;
+    }
+}
+
+static PktIn *ssh2_connection_pop(struct ssh2_connection_state *s)
+{
+    ssh2_connection_filter_queue(s);
+    return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_connection_process_queue(PacketProtocolLayer *ppl)
+{
+    struct ssh2_connection_state *s =
+        container_of(ppl, struct ssh2_connection_state, ppl);
+    PktIn *pktin;
+
+    if (ssh2_connection_filter_queue(s)) /* no matter why we were called */
+        return;
+
+    crBegin(s->crState);
+
+    if (s->connshare)
+        share_activate(s->connshare, s->peer_verstring);
+
+    /*
+     * Signal the seat that authentication is done, so that it can
+     * deploy spoofing defences. If it doesn't have any, deploy our
+     * own fallback one.
+     *
+     * We do this here rather than at the end of userauth, because we
+     * might not have gone through userauth at all (if we're a
+     * connection-sharing downstream).
+     */
+    if (ssh2_connection_need_antispoof_prompt(s)) {
+        s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl);
+        s->antispoof_prompt->to_server = true;
+        s->antispoof_prompt->from_server = false;
+        s->antispoof_prompt->name = dupstr("Authentication successful");
+        add_prompt(
+            s->antispoof_prompt,
+            dupstr("Access granted. Press Return to begin session. "), false);
+        s->antispoof_ret = seat_get_userpass_input(
+            ppl_get_iseat(&s->ppl), s->antispoof_prompt);
+        while (s->antispoof_ret.kind == SPRK_INCOMPLETE) {
+            crReturnV;
+            s->antispoof_ret = seat_get_userpass_input(
+                ppl_get_iseat(&s->ppl), s->antispoof_prompt);
+        }
+        free_prompts(s->antispoof_prompt);
+        s->antispoof_prompt = NULL;
+    }
+
+    /*
+     * Enable port forwardings.
+     */
+    portfwdmgr_config(s->portfwdmgr, s->conf);
+    s->portfwdmgr_configured = true;
+
+    /*
+     * Create the main session channel, if any.
+     */
+    s->mainchan = mainchan_new(
+        &s->ppl, &s->cl, s->conf, s->term_width, s->term_height,
+        s->ssh_is_simple, &s->mainchan_sc);
+    s->started = true;
+    // WINSCP
+    if (!s->mainchan)
+    {
+        s->ready = true;
+    }
+
+    /*
+     * Transfer data!
+     */
+
+    while (1) {
+        if ((pktin = ssh2_connection_pop(s)) != NULL) {
+
+            /*
+             * _All_ the connection-layer packets we expect to
+             * receive are now handled by the dispatch table.
+             * Anything that reaches here must be bogus.
+             */
+
+            ssh_proto_error(s->ppl.ssh, "Received unexpected connection-layer "
+                            "packet, type %d (%s)", pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            return;
+        }
+        crReturnV;
+    }
+
+    crFinishV;
+}
+
+static void ssh2_channel_check_close(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+
+    if (c->halfopen) {
+        /*
+         * If we've sent out our own CHANNEL_OPEN but not yet seen
+         * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then
+         * it's too early to be sending close messages of any kind.
+         */
+        return;
+    }
+
+    if (chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF),
+                        (c->closes & CLOSES_RCVD_EOF)) &&
+        !c->chanreq_head &&
+        !(c->closes & CLOSES_SENT_CLOSE)) {
+        /*
+         * We have both sent and received EOF (or the channel is a
+         * zombie), and we have no outstanding channel requests, which
+         * means the channel is in final wind-up. But we haven't sent
+         * CLOSE, so let's do so now.
+         */
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_CLOSE);
+        put_uint32(pktout, c->remoteid);
+        pq_push(s->ppl.out_pq, pktout);
+        c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
+    }
+
+    if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+        assert(c->chanreq_head == NULL);
+        /*
+         * We have both sent and received CLOSE, which means we're
+         * completely done with the channel.
+         */
+        ssh2_channel_destroy(c);
+    }
+}
+
+static void ssh2_channel_try_eof(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+    assert(c->pending_eof);          /* precondition for calling us */
+    if (c->halfopen)
+        return;                 /* can't close: not even opened yet */
+    if (bufchain_size(&c->outbuffer) > 0 || bufchain_size(&c->errbuffer) > 0)
+        return;              /* can't send EOF: pending outgoing data */
+
+    c->pending_eof = false;            /* we're about to send it */
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_EOF);
+    put_uint32(pktout, c->remoteid);
+    pq_push(s->ppl.out_pq, pktout);
+    c->closes |= CLOSES_SENT_EOF;
+    ssh2_channel_check_close(c);
+}
+
+/*
+ * Attempt to send data on an SSH-2 channel.
+ */
+static size_t ssh2_try_send(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+    size_t bufsize;
+
+    if (!c->halfopen) {
+        while (c->remwindow > 0 &&
+               (bufchain_size(&c->outbuffer) > 0 ||
+                bufchain_size(&c->errbuffer) > 0)) {
+            bufchain *buf = (bufchain_size(&c->errbuffer) > 0 ?
+                             &c->errbuffer : &c->outbuffer);
+
+            ptrlen data = bufchain_prefix(buf);
+            if (data.len > c->remwindow)
+                data.len = c->remwindow;
+            if (data.len > c->remmaxpkt)
+                data.len = c->remmaxpkt;
+            if (buf == &c->errbuffer) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_CHANNEL_EXTENDED_DATA);
+                put_uint32(pktout, c->remoteid);
+                put_uint32(pktout, SSH2_EXTENDED_DATA_STDERR);
+            } else {
+                pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA);
+                put_uint32(pktout, c->remoteid);
+            }
+            put_stringpl(pktout, data);
+            pq_push(s->ppl.out_pq, pktout);
+            bufchain_consume(buf, data.len);
+            c->remwindow -= data.len;
+        }
+    }
+
+    /*
+     * After having sent as much data as we can, return the amount
+     * still buffered.
+     */
+    bufsize = bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer);
+
+    /*
+     * And if there's no data pending but we need to send an EOF, send
+     * it.
+     */
+    if (!bufsize && c->pending_eof)
+        ssh2_channel_try_eof(c);
+
+    ssh_sendbuffer_changed(s->ppl.ssh);
+    return bufsize;
+}
+
+static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c)
+{
+    int bufsize;
+    if (c->closes & CLOSES_SENT_EOF)
+        return;                   /* don't send on channels we've EOFed */
+    bufsize = ssh2_try_send(c);
+    if (bufsize == 0) {
+        c->throttled_by_backlog = false;
+        ssh2_channel_check_throttle(c);
+    }
+}
+
+static void ssh2_channel_check_throttle(struct ssh2_channel *c)
+{
+    /*
+     * We don't want this channel to read further input if this
+     * particular channel has a backed-up SSH window, or if the
+     * outgoing side of the whole SSH connection is currently
+     * throttled, or if this channel already has an outgoing EOF
+     * either sent or pending.
+     */
+    chan_set_input_wanted(c->chan,
+                          !c->throttled_by_backlog &&
+                          !c->connlayer->all_channels_throttled &&
+                          !c->pending_eof &&
+                          !(c->closes & CLOSES_SENT_EOF));
+}
+
+/*
+ * Close any local socket and free any local resources associated with
+ * a channel.  This converts the channel into a zombie.
+ */
+static void ssh2_channel_close_local(struct ssh2_channel *c,
+                                     const char *reason)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    char *msg = NULL;
+
+    if (c->sharectx)
+        return;
+
+    msg = chan_log_close_msg(c->chan);
+
+    if (msg)
+        ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : "");
+
+    sfree(msg);
+
+    chan_free(c->chan);
+    c->chan = zombiechan_new();
+}
+
+static void ssh2_check_termination_callback(void *vctx)
+{
+    struct ssh2_connection_state *s = (struct ssh2_connection_state *)vctx;
+    ssh2_check_termination(s);
+}
+
+static void ssh2_channel_destroy(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+
+    assert(c->chanreq_head == NULL);
+
+    ssh2_channel_close_local(c, NULL);
+    del234(s->channels, c);
+    ssh2_channel_free(c);
+
+    /*
+     * If that was the last channel left open, we might need to
+     * terminate. But we'll be a bit cautious, by doing that in a
+     * toplevel callback, just in case anything on the current call
+     * stack objects to this entire PPL being freed.
+     */
+    queue_toplevel_callback(ssh2_check_termination_callback, s);
+}
+
+static void ssh2_check_termination(struct ssh2_connection_state *s)
+{
+    /*
+     * Decide whether we should terminate the SSH connection now.
+     * Called after a channel or a downstream goes away. The general
+     * policy is that we terminate when none of either is left.
+     */
+
+    if (s->persistent)
+        return;     /* persistent mode: never proactively terminate */
+
+    if (!s->started) {
+        /* At startup, we don't have any channels open because we
+         * haven't got round to opening the main one yet. In that
+         * situation, we don't want to terminate, even if a sharing
+         * connection opens and closes and causes a call to this
+         * function. */
+        return;
+    }
+
+    if (count234(s->channels) == 0 &&
+        !(s->connshare && share_ndownstreams(s->connshare) > 0)) {
+        /*
+         * We used to send SSH_MSG_DISCONNECT here, because I'd
+         * believed that _every_ conforming SSH-2 connection had to
+         * end with a disconnect being sent by at least one side;
+         * apparently I was wrong and it's perfectly OK to
+         * unceremoniously slam the connection shut when you're done,
+         * and indeed OpenSSH feels this is more polite than sending a
+         * DISCONNECT. So now we don't.
+         */
+        ssh_user_close(s->ppl.ssh, "All channels closed");
+        return;
+    }
+}
+
+/*
+ * Set up most of a new ssh2_channel. Nulls out sharectx, but leaves
+ * chan untouched (since it will sometimes have been filled in before
+ * calling this).
+ */
+void ssh2_channel_init(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    c->closes = 0;
+    c->pending_eof = false;
+    c->throttling_conn = false;
+    c->throttled_by_backlog = false;
+    c->sharectx = NULL;
+    c->locwindow = c->locmaxwin = c->remlocwin =
+        s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+    c->chanreq_head = NULL;
+    c->throttle_state = UNTHROTTLED;
+    bufchain_init(&c->outbuffer);
+    bufchain_init(&c->errbuffer);
+    c->sc.vt = &ssh2channel_vtable;
+    c->sc.cl = &s->cl;
+    c->localid = alloc_channel_id(s->channels, struct ssh2_channel);
+    add234(s->channels, c);
+}
+
+/*
+ * Construct the common parts of a CHANNEL_OPEN.
+ */
+PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN);
+    put_stringz(pktout, type);
+    put_uint32(pktout, c->localid);
+    put_uint32(pktout, c->locwindow);     /* our window size */
+    put_uint32(pktout, OUR_V2_MAXPKT);    /* our max pkt size */
+    return pktout;
+}
+
+/*
+ * Construct the common parts of a CHANNEL_REQUEST.  If handler is not
+ * NULL then a reply will be requested and the handler will be called
+ * when it arrives.  The returned packet is ready to have any
+ * request-specific data added and be sent.  Note that if a handler is
+ * provided, it's essential that the request actually be sent.
+ *
+ * The handler will usually be passed the response packet in pktin. If
+ * pktin is NULL, this means that no reply will ever be forthcoming
+ * (e.g. because the entire connection is being destroyed, or because
+ * the server initiated channel closure before we saw the response)
+ * and the handler should free any storage it's holding.
+ */
+PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
+                          cr_handler_fn_t handler, void *ctx)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+
+    assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_REQUEST);
+    put_uint32(pktout, c->remoteid);
+    put_stringz(pktout, type);
+    put_bool(pktout, handler != NULL);
+    if (handler != NULL) {
+        struct outstanding_channel_request *ocr =
+            snew(struct outstanding_channel_request);
+
+        ocr->handler = handler;
+        ocr->ctx = ctx;
+        ocr->next = NULL;
+        if (!c->chanreq_head)
+            c->chanreq_head = ocr;
+        else
+            c->chanreq_tail->next = ocr;
+        c->chanreq_tail = ocr;
+    }
+    return pktout;
+}
+
+static Conf *ssh2channel_get_conf(SshChannel *sc)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+    return s->conf;
+}
+
+static void ssh2channel_write_eof(SshChannel *sc)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+
+    if (c->closes & CLOSES_SENT_EOF)
+        return;
+
+    c->pending_eof = true;
+    ssh2_channel_try_eof(c);
+}
+
+static void ssh2channel_initiate_close(SshChannel *sc, const char *err)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    char *reason;
+
+    reason = err ? dupprintf("due to local error: %s", err) : NULL;
+    ssh2_channel_close_local(c, reason);
+    sfree(reason);
+    c->pending_eof = false;   /* this will confuse a zombie channel */
+
+    ssh2_channel_check_close(c);
+}
+
+static void ssh2channel_unthrottle(SshChannel *sc, size_t bufsize)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+    size_t buflimit;
+
+    buflimit = s->ssh_is_simple ? 0 : c->locmaxwin;
+    if (bufsize < buflimit)
+        ssh2_set_window(c, buflimit - bufsize);
+
+    if (c->throttling_conn && bufsize <= buflimit) {
+        c->throttling_conn = false;
+        ssh_throttle_conn(s->ppl.ssh, -1);
+    }
+}
+
+static size_t ssh2channel_write(
+    SshChannel *sc, bool is_stderr, const void *buf, size_t len)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    assert(!(c->closes & CLOSES_SENT_EOF));
+    bufchain_add(is_stderr ? &c->errbuffer : &c->outbuffer, buf, len);
+    return ssh2_try_send(c);
+}
+
+static void ssh2channel_x11_sharing_handover(
+    SshChannel *sc, ssh_sharing_connstate *share_cs, share_channel *share_chan,
+    const char *peer_addr, int peer_port, int endian,
+    int protomajor, int protominor, const void *initial_data, int initial_len)
+{
+    #ifndef WINSCP
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    /*
+     * This function is called when we've just discovered that an X
+     * forwarding channel on which we'd been handling the initial auth
+     * ourselves turns out to be destined for a connection-sharing
+     * downstream. So we turn the channel into a sharing one, meaning
+     * that we completely stop tracking windows and buffering data and
+     * just pass more or less unmodified SSH messages back and forth.
+     */
+    c->sharectx = share_cs;
+    share_setup_x11_channel(share_cs, share_chan,
+                            c->localid, c->remoteid, c->remwindow,
+                            c->remmaxpkt, c->locwindow,
+                            peer_addr, peer_port, endian,
+                            protomajor, protominor,
+                            initial_data, initial_len);
+    chan_free(c->chan);
+    c->chan = NULL;
+    #else
+    assert(false);
+    #endif
+}
+
+static void ssh2channel_window_override_removed(SshChannel *sc)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    /*
+     * This function is called when a client-side Channel has just
+     * stopped requiring an initial fixed-size window.
+     */
+    assert(!c->chan->initial_fixed_window_size);
+    ssh2_set_window(c, s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE);
+}
+
+static void ssh2channel_hint_channel_is_simple(SshChannel *sc)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "[email protected]", NULL, NULL);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+static SshChannel *ssh2_lportfwd_open(
+    ConnectionLayer *cl, const char *hostname, int port,
+    const char *description, const SocketPeerInfo *pi, Channel *chan)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct ssh2_channel *c = snew(struct ssh2_channel);
+    PktOut *pktout;
+
+    c->connlayer = s;
+    ssh2_channel_init(c);
+    c->halfopen = true;
+    c->chan = chan;
+
+    pktout = ssh2_portfwd_chanopen(s, c, hostname, port, description, pi);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return &c->sc;
+}
+
+static void ssh2_sharing_globreq_response(
+    struct ssh2_connection_state *s, PktIn *pktin, void *ctx)
+{
+    ssh_sharing_connstate *cs = (ssh_sharing_connstate *)ctx;
+    share_got_pkt_from_server(cs, pktin->type,
+                              BinarySource_UPCAST(pktin)->data,
+                              BinarySource_UPCAST(pktin)->len);
+}
+
+static void ssh2_sharing_queue_global_request(
+    ConnectionLayer *cl, ssh_sharing_connstate *cs)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    ssh2_queue_global_request_handler(s, ssh2_sharing_globreq_response, cs);
+}
+
+static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    queue_toplevel_callback(ssh2_check_termination_callback, s);
+}
+
+static struct X11FakeAuth *ssh2_add_x11_display(
+    ConnectionLayer *cl, int authtype, struct X11Display *disp)
+{
+    #ifndef WINSCP
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype);
+    auth->disp = disp;
+    return auth;
+    #else
+    assert(false);
+    return NULL;
+    #endif
+}
+
+static struct X11FakeAuth *ssh2_add_sharing_x11_display(
+    ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs,
+    share_channel *share_chan)
+{
+    #ifndef WINSCP
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct X11FakeAuth *auth;
+
+    /*
+     * Make up a new set of fake X11 auth data, and add it to the tree
+     * of currently valid ones with an indication of the sharing
+     * context that it's relevant to.
+     */
+    auth = x11_invent_fake_auth(s->x11authtree, authtype);
+    auth->share_cs = share_cs;
+    auth->share_chan = share_chan;
+
+    return auth;
+    #else
+    assert(false);
+    return NULL;
+    #endif
+}
+
+static void ssh2_remove_sharing_x11_display(
+    ConnectionLayer *cl, struct X11FakeAuth *auth)
+{
+    #ifndef WINSCP
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    del234(s->x11authtree, auth);
+    x11_free_fake_auth(auth);
+    #else
+    assert(false);
+    #endif
+}
+
+static unsigned ssh2_alloc_sharing_channel(
+    ConnectionLayer *cl, ssh_sharing_connstate *connstate)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct ssh2_channel *c = snew(struct ssh2_channel);
+
+    c->connlayer = s;
+    ssh2_channel_init(c);
+    c->chan = NULL;
+    c->sharectx = connstate;
+    return c->localid;
+}
+
+static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct ssh2_channel *c = find234(s->channels, &localid, ssh2_channelfind);
+    if (c)
+        ssh2_channel_destroy(c);
+}
+
+static void ssh2_send_packet_from_downstream(
+        ConnectionLayer *cl, unsigned id, int type,
+        const void *data, int datalen, const char *additional_log_text)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    PktOut *pkt = ssh_bpp_new_pktout(s->ppl.bpp, type);
+    pkt->downstream_id = id;
+    pkt->additional_log_text = additional_log_text;
+    put_data(pkt, data, datalen);
+    pq_push(s->ppl.out_pq, pkt);
+}
+
+static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists();
+}
+
+static bool ssh2_connection_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+    struct ssh2_connection_state *s =
+        container_of(ppl, struct ssh2_connection_state, ppl);
+    bool toret = false;
+
+    if (s->mainchan) {
+        mainchan_get_specials(s->mainchan, add_special, ctx);
+        toret = true;
+    }
+
+    /*
+     * Don't bother offering IGNORE if we've decided the remote
+     * won't cope with it, since we wouldn't bother sending it if
+     * asked anyway.
+     */
+    if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+        if (toret)
+            add_special(ctx, NULL, SS_SEP, 0);
+
+        add_special(ctx, "IGNORE message", SS_NOP, 0);
+        toret = true;
+    }
+
+    return toret;
+}
+
+static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl,
+                                        SessionSpecialCode code, int arg)
+{
+    struct ssh2_connection_state *s =
+        container_of(ppl, struct ssh2_connection_state, ppl);
+    PktOut *pktout;
+
+    if (code == SS_PING || code == SS_NOP) {
+        if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_IGNORE);
+            put_stringz(pktout, "");
+            pq_push(s->ppl.out_pq, pktout);
+        }
+    } else if (s->mainchan) {
+        mainchan_special_cmd(s->mainchan, code, arg);
+    }
+}
+
+static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+
+    s->term_width = width;
+    s->term_height = height;
+    if (s->mainchan)
+        mainchan_terminal_size(s->mainchan, width, height);
+}
+
+static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+
+    if (s->mainchan)
+        sshfwd_unthrottle(s->mainchan_sc, bufsize);
+}
+
+static size_t ssh2_stdin_backlog(ConnectionLayer *cl)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct ssh2_channel *c;
+
+    if (!s->mainchan)
+        return 0;
+    c = container_of(s->mainchan_sc, struct ssh2_channel, sc);
+    return s->mainchan ?
+        bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer) : 0;
+}
+
+static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct ssh2_channel *c;
+    int i;
+
+    s->all_channels_throttled = throttled;
+
+    for (i = 0; NULL != (c = index234(s->channels, i)); i++)
+        if (!c->sharectx)
+            ssh2_channel_check_throttle(c);
+}
+
+static bool ssh2_ldisc_option(ConnectionLayer *cl, int option)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+
+    return s->ldisc_opts[option];
+}
+
+static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+
+    s->ldisc_opts[option] = value;
+}
+
+static void ssh2_enable_x_fwd(ConnectionLayer *cl)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+
+    s->X11_fwd_enabled = true;
+}
+
+static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+
+    s->want_user_input = wanted;
+    if (wanted)
+        ssh_check_sendok(s->ppl.ssh);
+    s->ready = true; // WINSCP
+}
+
+static bool ssh2_get_wants_user_input(ConnectionLayer *cl)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+    return s->want_user_input;
+}
+
+static void ssh2_got_user_input(ConnectionLayer *cl)
+{
+    struct ssh2_connection_state *s =
+        container_of(cl, struct ssh2_connection_state, cl);
+
+    while (s->mainchan && bufchain_size(s->user_input) > 0) {
+        /*
+         * Add user input to the main channel's buffer.
+         */
+        ptrlen data = bufchain_prefix(s->user_input);
+        sshfwd_write(s->mainchan_sc, data.ptr, data.len);
+        bufchain_consume(s->user_input, data.len);
+    }
+}
+
+static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+    struct ssh2_connection_state *s =
+        container_of(ppl, struct ssh2_connection_state, ppl);
+
+    conf_free(s->conf);
+    s->conf = conf_copy(conf);
+
+    if (s->portfwdmgr_configured)
+        portfwdmgr_config(s->portfwdmgr, s->conf);
+}
+
+#include <puttyexp.h>
+
+static unsigned int ssh2_connection_winscp_query(PacketProtocolLayer *ppl, int query)
+{
+    struct ssh2_connection_state *s =
+        container_of(ppl, struct ssh2_connection_state, ppl);
+
+    if (query == WINSCP_QUERY_REMMAXPKT)
+    {
+        if (!s->mainchan)
+        {
+            return 0;
+        }
+        else
+        {
+            struct ssh2_channel *c = container_of(s->mainchan_sc, struct ssh2_channel, sc);
+            return c->remmaxpkt;
+        }
+    }
+    else if (query == WINSCP_QUERY_MAIN_CHANNEL)
+    {
+        return s->ready;
+    }
+    else
+    {
+        assert(0);
+        return 0;
+    }
+}

+ 237 - 0
source/putty/ssh/connection2.h

@@ -0,0 +1,237 @@
+#ifndef PUTTY_SSH2CONNECTION_H
+#define PUTTY_SSH2CONNECTION_H
+
+struct outstanding_channel_request;
+struct outstanding_global_request;
+
+struct ssh2_connection_state {
+    int crState;
+
+    ssh_sharing_state *connshare;
+    char *peer_verstring;
+
+    mainchan *mainchan;
+    SshChannel *mainchan_sc;
+    bool ldisc_opts[LD_N_OPTIONS];
+    int session_attempt, session_status;
+    int term_width, term_height;
+    bool want_user_input;
+    bufchain *user_input;
+    bool ready; // WINSCP
+
+    bool ssh_is_simple;
+    bool persistent;
+    bool started;
+
+    Conf *conf;
+
+    tree234 *channels;                 /* indexed by local id */
+    bool all_channels_throttled;
+
+    bool X11_fwd_enabled;
+    // tree234 *x11authtree; WINSCP
+
+    bool got_pty;
+
+    tree234 *rportfwds;
+    PortFwdManager *portfwdmgr;
+    bool portfwdmgr_configured;
+
+    prompts_t *antispoof_prompt;
+    SeatPromptResult antispoof_ret;
+
+    const SftpServerVtable *sftpserver_vt;
+    const SshServerConfig *ssc;
+
+    /*
+     * These store the list of global requests that we're waiting for
+     * replies to. (REQUEST_FAILURE doesn't come with any indication
+     * of what message caused it, so we have to keep track of the
+     * queue ourselves.)
+     */
+    struct outstanding_global_request *globreq_head, *globreq_tail;
+
+    ConnectionLayer cl;
+    PacketProtocolLayer ppl;
+};
+
+typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s,
+                                PktIn *pktin, void *ctx);
+void ssh2_queue_global_request_handler(
+    struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx);
+
+struct ssh2_channel {
+    struct ssh2_connection_state *connlayer;
+
+    unsigned remoteid, localid;
+    int type;
+    /* True if we opened this channel but server hasn't confirmed. */
+    bool halfopen;
+
+    /* Bitmap of whether we've sent/received CHANNEL_EOF and
+     * CHANNEL_CLOSE. */
+#define CLOSES_SENT_EOF    1
+#define CLOSES_SENT_CLOSE  2
+#define CLOSES_RCVD_EOF    4
+#define CLOSES_RCVD_CLOSE  8
+    int closes;
+
+    /*
+     * This flag indicates that an EOF is pending on the outgoing side
+     * of the channel: that is, wherever we're getting the data for
+     * this channel has sent us some data followed by EOF. We can't
+     * actually send the EOF until we've finished sending the data, so
+     * we set this flag instead to remind us to do so once our buffer
+     * is clear.
+     */
+    bool pending_eof;
+
+    /*
+     * True if this channel is causing the underlying connection to be
+     * throttled.
+     */
+    bool throttling_conn;
+
+    /*
+     * True if we currently have backed-up data on the direction of
+     * this channel pointing out of the SSH connection, and therefore
+     * would prefer the 'Channel' implementation not to read further
+     * local input if possible.
+     */
+    bool throttled_by_backlog;
+
+    bufchain outbuffer, errbuffer;
+    unsigned remwindow, remmaxpkt;
+    /* locwindow is signed so we can cope with excess data. */
+    int locwindow, locmaxwin;
+    /*
+     * remlocwin is the amount of local window that we think
+     * the remote end had available to it after it sent the
+     * last data packet or window adjust ack.
+     */
+    int remlocwin;
+
+    /*
+     * These store the list of channel requests that we're waiting for
+     * replies to. (CHANNEL_FAILURE doesn't come with any indication
+     * of what message caused it, so we have to keep track of the
+     * queue ourselves.)
+     */
+    struct outstanding_channel_request *chanreq_head, *chanreq_tail;
+
+    enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
+
+    ssh_sharing_connstate *sharectx; /* sharing context, if this is a
+                                      * downstream channel */
+    Channel *chan;      /* handle the client side of this channel, if not */
+    SshChannel sc;      /* entry point for chan to talk back to */
+};
+
+typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *);
+
+void ssh2_channel_init(struct ssh2_channel *c);
+PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
+                          cr_handler_fn_t handler, void *ctx);
+
+typedef enum ChanopenOutcome {
+    CHANOPEN_RESULT_FAILURE,
+    CHANOPEN_RESULT_SUCCESS,
+    CHANOPEN_RESULT_DOWNSTREAM,
+} ChanopenOutcome;
+
+typedef struct ChanopenResult {
+    ChanopenOutcome outcome;
+    union {
+        struct {
+            char *wire_message;        /* must be freed by recipient */
+            unsigned reason_code;
+        } failure;
+        struct {
+            Channel *channel;
+        } success;
+        struct {
+            ssh_sharing_connstate *share_ctx;
+        } downstream;
+    } u;
+} ChanopenResult;
+
+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);
+
+struct ssh_rportfwd *ssh2_rportfwd_alloc(
+    ConnectionLayer *cl,
+    const char *shost, int sport, const char *dhost, int dport,
+    int addressfamily, const char *log_description, PortFwdRecord *pfr,
+    ssh_sharing_connstate *share_ctx);
+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);
+SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan);
+
+void ssh2channel_send_exit_status(SshChannel *c, int status);
+void ssh2channel_send_exit_signal(
+    SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
+void ssh2channel_send_exit_signal_numeric(
+    SshChannel *c, int signum, bool core_dumped, ptrlen msg);
+void ssh2channel_request_x11_forwarding(
+    SshChannel *c, bool want_reply, const char *authproto,
+    const char *authdata, int screen_number, bool oneshot);
+void ssh2channel_request_agent_forwarding(SshChannel *c, bool want_reply);
+void ssh2channel_request_pty(
+    SshChannel *c, bool want_reply, Conf *conf, int w, int h);
+bool ssh2channel_send_env_var(
+    SshChannel *c, bool want_reply, const char *var, const char *value);
+void ssh2channel_start_shell(SshChannel *c, bool want_reply);
+void ssh2channel_start_command(
+    SshChannel *c, bool want_reply, const char *command);
+bool ssh2channel_start_subsystem(
+    SshChannel *c, bool want_reply, const char *subsystem);
+bool ssh2channel_send_env_var(
+    SshChannel *c, bool want_reply, const char *var, const char *value);
+bool ssh2channel_send_serial_break(
+    SshChannel *c, bool want_reply, int length);
+bool ssh2channel_send_signal(
+    SshChannel *c, bool want_reply, const char *signame);
+void ssh2channel_send_terminal_size_change(SshChannel *c, int w, int h);
+
+#define CHANOPEN_RETURN_FAILURE(code, msgparams) do             \
+    {                                                           \
+        ChanopenResult toret;                                   \
+        toret.outcome = CHANOPEN_RESULT_FAILURE;                \
+        toret.u.failure.reason_code = code;                     \
+        toret.u.failure.wire_message = dupprintf msgparams;     \
+        return toret;                                           \
+    } while (0)
+
+#define CHANOPEN_RETURN_SUCCESS(chan) do                \
+    {                                                   \
+        ChanopenResult toret;                           \
+        toret.outcome = CHANOPEN_RESULT_SUCCESS;        \
+        toret.u.success.channel = chan;                 \
+        return toret;                                   \
+    } while (0)
+
+#define CHANOPEN_RETURN_DOWNSTREAM(shctx) do            \
+    {                                                   \
+        ChanopenResult toret;                           \
+        toret.outcome = CHANOPEN_RESULT_DOWNSTREAM;     \
+        toret.u.downstream.share_ctx = shctx;           \
+        return toret;                                   \
+    } while (0)
+
+ChanopenResult ssh2_connection_parse_channel_open(
+    struct ssh2_connection_state *s, ptrlen type,
+    PktIn *pktin, SshChannel *sc);
+
+bool ssh2_connection_parse_global_request(
+    struct ssh2_connection_state *s, ptrlen type, PktIn *pktin);
+
+bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s);
+
+#endif /* PUTTY_SSH2CONNECTION_H */

+ 217 - 0
source/putty/ssh/gss.h

@@ -0,0 +1,217 @@
+#ifndef PUTTY_SSHGSS_H
+#define PUTTY_SSHGSS_H
+#include "putty.h"
+#include "pgssapi.h"
+
+#ifndef NO_GSSAPI
+
+#define SSH2_GSS_OIDTYPE 0x06
+typedef void *Ssh_gss_ctx;
+
+typedef enum Ssh_gss_stat {
+    SSH_GSS_OK = 0,
+    SSH_GSS_S_CONTINUE_NEEDED,
+    SSH_GSS_NO_MEM,
+    SSH_GSS_BAD_HOST_NAME,
+    SSH_GSS_BAD_MIC,
+    SSH_GSS_NO_CREDS,
+    SSH_GSS_FAILURE
+} Ssh_gss_stat;
+
+#define SSH_GSS_S_COMPLETE SSH_GSS_OK
+
+#define SSH_GSS_CLEAR_BUF(buf) do {             \
+    (*buf).length = 0;                          \
+    (*buf).value = NULL;                                \
+} while (0)
+
+typedef gss_buffer_desc Ssh_gss_buf;
+typedef gss_name_t Ssh_gss_name;
+
+#define GSS_NO_EXPIRATION ((time_t)-1)
+
+#define GSS_DEF_REKEY_MINS 2    /* Default minutes between GSS cache checks */
+
+/* Functions, provided by either {windows,unix}/gss.c or gssc.c */
+
+struct ssh_gss_library;
+
+/*
+ * Prepare a collection of GSSAPI libraries for use in a single SSH
+ * connection. Returns a structure containing a list of libraries,
+ * with their ids (see struct ssh_gss_library below) filled in so
+ * that the client can go through them in the SSH user's preferred
+ * order.
+ *
+ * Must always return non-NULL. (Even if no libraries are available,
+ * it must return an empty structure.)
+ *
+ * The free function cleans up the structure, and its associated
+ * libraries (if any).
+ */
+struct ssh_gss_liblist {
+    struct ssh_gss_library *libraries;
+    int nlibraries;
+};
+struct ssh_gss_liblist *ssh_gss_setup(Conf *conf, LogContext *logctx);
+void ssh_gss_cleanup(struct ssh_gss_liblist *list);
+
+/*
+ * Fills in buf with a string describing the GSSAPI mechanism in
+ * use. buf->data is not dynamically allocated.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_indicate_mech)(struct ssh_gss_library *lib,
+                                                Ssh_gss_buf *buf);
+
+/*
+ * Converts a name such as a hostname into a GSSAPI internal form,
+ * which is placed in "out". The result should be freed by
+ * ssh_gss_release_name().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_import_name)(struct ssh_gss_library *lib,
+                                              char *in, Ssh_gss_name *out);
+
+/*
+ * Frees the contents of an Ssh_gss_name structure filled in by
+ * ssh_gss_import_name().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib,
+                                               Ssh_gss_name *name);
+
+/*
+ * The main GSSAPI security context setup function. The "out"
+ * parameter will need to be freed by ssh_gss_free_tok.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_init_sec_context)
+    (struct ssh_gss_library *lib,
+     Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate,
+     Ssh_gss_buf *in, Ssh_gss_buf *out, time_t *expiry,
+     unsigned long *lifetime);
+
+/*
+ * Frees the contents of an Ssh_gss_buf filled in by
+ * ssh_gss_init_sec_context(). Do not accidentally call this on
+ * something filled in by ssh_gss_get_mic() (which requires a
+ * different free function) or something filled in by any other
+ * way.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_free_tok)(struct ssh_gss_library *lib,
+                                           Ssh_gss_buf *);
+
+/*
+ * Acquires the credentials to perform authentication in the first
+ * place. Needs to be freed by ssh_gss_release_cred().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_acquire_cred)(struct ssh_gss_library *lib,
+                                               Ssh_gss_ctx *,
+                                               time_t *expiry);
+
+/*
+ * Frees the contents of an Ssh_gss_ctx filled in by
+ * ssh_gss_acquire_cred().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_release_cred)(struct ssh_gss_library *lib,
+                                               Ssh_gss_ctx *);
+
+/*
+ * Gets a MIC for some input data. "out" needs to be freed by
+ * ssh_gss_free_mic().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_get_mic)(struct ssh_gss_library *lib,
+                                          Ssh_gss_ctx ctx, Ssh_gss_buf *in,
+                                          Ssh_gss_buf *out);
+
+/*
+ * Validates an input MIC for some input data.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_verify_mic)(struct ssh_gss_library *lib,
+                                             Ssh_gss_ctx ctx,
+                                             Ssh_gss_buf *in_data,
+                                             Ssh_gss_buf *in_mic);
+
+/*
+ * Frees the contents of an Ssh_gss_buf filled in by
+ * ssh_gss_get_mic(). Do not accidentally call this on something
+ * filled in by ssh_gss_init_sec_context() (which requires a
+ * different free function) or something filled in by any other
+ * way.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_free_mic)(struct ssh_gss_library *lib,
+                                           Ssh_gss_buf *);
+
+/*
+ * Return an error message after authentication failed. The
+ * message string is returned in "buf", with buf->len giving the
+ * number of characters of printable message text and buf->data
+ * containing one more character which is a trailing NUL.
+ * buf->data should be manually freed by the caller.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_display_status)(struct ssh_gss_library *lib,
+                                                 Ssh_gss_ctx, Ssh_gss_buf *buf);
+
+struct ssh_gss_library {
+    /*
+     * Identifying number in the enumeration used by the
+     * configuration code to specify a preference order.
+     */
+    int id;
+
+    /*
+     * Filled in at initialisation time, if there's anything
+     * interesting to say about how GSSAPI was initialised (e.g.
+     * which of a number of alternative libraries was used).
+     */
+    const char *gsslogmsg;
+
+    /*
+     * Function pointers implementing the SSH wrapper layer on top
+     * of GSSAPI. (Defined in sshgssc, typically, though Windows
+     * provides an alternative layer to sit on top of the annoyingly
+     * different SSPI.)
+     */
+    t_ssh_gss_indicate_mech indicate_mech;
+    t_ssh_gss_import_name import_name;
+    t_ssh_gss_release_name release_name;
+    t_ssh_gss_init_sec_context init_sec_context;
+    t_ssh_gss_free_tok free_tok;
+    t_ssh_gss_acquire_cred acquire_cred;
+    t_ssh_gss_release_cred release_cred;
+    t_ssh_gss_get_mic get_mic;
+    t_ssh_gss_verify_mic verify_mic;
+    t_ssh_gss_free_mic free_mic;
+    t_ssh_gss_display_status display_status;
+
+    /*
+     * Additional data for the wrapper layers.
+     */
+    union {
+        struct gssapi_functions gssapi;
+        /*
+         * The SSPI wrappers don't need to store their Windows API
+         * function pointers in this structure, because there can't
+         * be more than one set of them available.
+         */
+    } u;
+
+    /*
+     * Wrapper layers will often also need to store a library handle
+     * of some sort for cleanup time.
+     */
+    void *handle;
+};
+
+/*
+ * State that has to be shared between all GSSAPI-using parts of the
+ * same SSH connection, in particular between GSS key exchange and the
+ * subsequent trivial userauth method that reuses its output.
+ */
+struct ssh_connection_shared_gss_state {
+    struct ssh_gss_liblist *libs;
+    struct ssh_gss_library *lib;
+    Ssh_gss_name srv_name;
+    Ssh_gss_ctx ctx;
+};
+
+#endif /* NO_GSSAPI */
+
+#endif /*PUTTY_SSHGSS_H*/

+ 297 - 0
source/putty/ssh/gssc.c

@@ -0,0 +1,297 @@
+#include "putty.h"
+
+#include <string.h>
+#include <limits.h>
+#include "gssc.h"
+#include "misc.h"
+
+#ifndef NO_GSSAPI
+
+static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib,
+                                             Ssh_gss_buf *mech)
+{
+    /* Copy constant into mech */
+    mech->length  = GSS_MECH_KRB5->length;
+    mech->value = GSS_MECH_KRB5->elements;
+    return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib,
+                                           char *host,
+                                           Ssh_gss_name *srv_name)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    OM_uint32 min_stat,maj_stat;
+    gss_buffer_desc host_buf;
+    char *pStr;
+
+    pStr = dupcat("host@", host);
+
+    host_buf.value = pStr;
+    host_buf.length = strlen(pStr);
+
+    maj_stat = gss->import_name(&min_stat, &host_buf,
+                                GSS_C_NT_HOSTBASED_SERVICE, srv_name);
+    /* Release buffer */
+    sfree(pStr);
+    if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+    return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib,
+                                            Ssh_gss_ctx *ctx,
+                                            time_t *expiry)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    #ifdef MPEXT
+    gss_OID_set_desc k5only;
+    #else
+    gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 };
+    #endif
+    gss_cred_id_t cred;
+    OM_uint32 dummy;
+    OM_uint32 time_rec;
+    gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx);
+
+    #ifdef MPEXT
+    k5only.count = 1;
+    k5only.elements = GSS_MECH_KRB5;
+    #endif
+
+    gssctx->ctx = GSS_C_NO_CONTEXT;
+    gssctx->expiry = 0;
+
+    gssctx->maj_stat =
+        gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+                          &k5only, GSS_C_INITIATE, &cred,
+                          (gss_OID_set *)0, &time_rec);
+
+    if (gssctx->maj_stat != GSS_S_COMPLETE) {
+        sfree(gssctx);
+        return SSH_GSS_FAILURE;
+    }
+
+    /*
+     * When the credential lifetime is not yet available due to deferred
+     * processing, gss_acquire_cred should return a 0 lifetime which is
+     * distinct from GSS_C_INDEFINITE which signals a crential that never
+     * expires.  However, not all implementations get this right, and with
+     * Kerberos, initiator credentials always expire at some point.  So when
+     * lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to
+     * complete deferred processing.
+     */
+    if (time_rec == GSS_C_INDEFINITE || time_rec == 0) {
+        gssctx->maj_stat =
+            gss->inquire_cred_by_mech(&gssctx->min_stat, cred,
+                                      (gss_OID) GSS_MECH_KRB5,
+                                      GSS_C_NO_NAME,
+                                      &time_rec,
+                                      NULL,
+                                      NULL);
+    }
+    (void) gss->release_cred(&dummy, &cred);
+
+    if (gssctx->maj_stat != GSS_S_COMPLETE) {
+        sfree(gssctx);
+        return SSH_GSS_FAILURE;
+    }
+
+    if (time_rec != GSS_C_INDEFINITE)
+        gssctx->expiry = time(NULL) + time_rec;
+    else
+        gssctx->expiry = GSS_NO_EXPIRATION;
+
+    if (expiry) {
+        *expiry = gssctx->expiry;
+    }
+
+    *ctx = (Ssh_gss_ctx) gssctx;
+    return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib,
+                                                Ssh_gss_ctx *ctx,
+                                                Ssh_gss_name srv_name,
+                                                int to_deleg,
+                                                Ssh_gss_buf *recv_tok,
+                                                Ssh_gss_buf *send_tok,
+                                                time_t *expiry,
+                                                unsigned long *lifetime)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx;
+    OM_uint32 ret_flags;
+    OM_uint32 lifetime_rec;
+
+    if (to_deleg) to_deleg = GSS_C_DELEG_FLAG;
+    gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat,
+                                             GSS_C_NO_CREDENTIAL,
+                                             &gssctx->ctx,
+                                             srv_name,
+                                             (gss_OID) GSS_MECH_KRB5,
+                                             GSS_C_MUTUAL_FLAG |
+                                             GSS_C_INTEG_FLAG | to_deleg,
+                                             0,
+                                             GSS_C_NO_CHANNEL_BINDINGS,
+                                             recv_tok,
+                                             NULL,   /* ignore mech type */
+                                             send_tok,
+                                             &ret_flags,
+                                             &lifetime_rec);
+
+    if (lifetime) {
+        if (lifetime_rec == GSS_C_INDEFINITE)
+            *lifetime = ULONG_MAX;
+        else
+            *lifetime = lifetime_rec;
+    }
+    if (expiry) {
+        if (lifetime_rec == GSS_C_INDEFINITE)
+            *expiry = GSS_NO_EXPIRATION;
+        else
+            *expiry = time(NULL) + lifetime_rec;
+    }
+
+    if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE;
+    if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED;
+    return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_display_status(struct ssh_gss_library *lib,
+                                              Ssh_gss_ctx ctx,
+                                              Ssh_gss_buf *buf)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
+    OM_uint32 lmin,lmax;
+    OM_uint32 ccc;
+    gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER;
+
+    /* Return empty buffer in case of failure */
+    SSH_GSS_CLEAR_BUF(buf);
+
+    /* get first mesg from GSS */
+    ccc=0;
+    lmax=gss->display_status(&lmin,gssctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_maj);
+
+    if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE;
+
+    /* get first mesg from Kerberos */
+    ccc=0;
+    lmax=gss->display_status(&lmin,gssctx->min_stat,GSS_C_MECH_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_min);
+
+    if (lmax != GSS_S_COMPLETE) {
+        gss->release_buffer(&lmin, &msg_maj);
+        return SSH_GSS_FAILURE;
+    }
+
+    /* copy data into buffer */
+    buf->length = msg_maj.length + msg_min.length + 1;
+    buf->value = snewn(buf->length + 1, char);
+
+    /* copy mem */
+    memcpy((char *)buf->value, msg_maj.value, msg_maj.length);
+    ((char *)buf->value)[msg_maj.length] = ' ';
+    memcpy((char *)buf->value + msg_maj.length + 1, msg_min.value, msg_min.length);
+    ((char *)buf->value)[buf->length] = 0;
+    /* free mem & exit */
+    gss->release_buffer(&lmin, &msg_maj);
+    gss->release_buffer(&lmin, &msg_min);
+    return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_free_tok(struct ssh_gss_library *lib,
+                                        Ssh_gss_buf *send_tok)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    OM_uint32 min_stat,maj_stat;
+    maj_stat = gss->release_buffer(&min_stat, send_tok);
+
+    if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+    return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib,
+                                            Ssh_gss_ctx *ctx)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) *ctx;
+    OM_uint32 min_stat;
+    OM_uint32 maj_stat=GSS_S_COMPLETE;
+
+    if (gssctx == NULL) return SSH_GSS_FAILURE;
+    if (gssctx->ctx != GSS_C_NO_CONTEXT)
+        maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER);
+    sfree(gssctx);
+    *ctx = NULL;
+
+    if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+    return SSH_GSS_FAILURE;
+}
+
+
+static Ssh_gss_stat ssh_gssapi_release_name(struct ssh_gss_library *lib,
+                                            Ssh_gss_name *srv_name)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    OM_uint32 min_stat,maj_stat;
+    maj_stat = gss->release_name(&min_stat, srv_name);
+
+    if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+    return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib,
+                                       Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+                                       Ssh_gss_buf *hash)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
+    if (gssctx == NULL) return SSH_GSS_FAILURE;
+    return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash);
+}
+
+static Ssh_gss_stat ssh_gssapi_verify_mic(struct ssh_gss_library *lib,
+                                          Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+                                          Ssh_gss_buf *hash)
+{
+    struct gssapi_functions *gss = &lib->u.gssapi;
+    gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
+    if (gssctx == NULL) return SSH_GSS_FAILURE;
+    return gss->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL);
+}
+
+static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib,
+                                        Ssh_gss_buf *hash)
+{
+    /* On Unix this is the same freeing process as ssh_gssapi_free_tok. */
+    return ssh_gssapi_free_tok(lib, hash);
+}
+
+void ssh_gssapi_bind_fns(struct ssh_gss_library *lib)
+{
+    lib->indicate_mech = ssh_gssapi_indicate_mech;
+    lib->import_name = ssh_gssapi_import_name;
+    lib->release_name = ssh_gssapi_release_name;
+    lib->init_sec_context = ssh_gssapi_init_sec_context;
+    lib->free_tok = ssh_gssapi_free_tok;
+    lib->acquire_cred = ssh_gssapi_acquire_cred;
+    lib->release_cred = ssh_gssapi_release_cred;
+    lib->get_mic = ssh_gssapi_get_mic;
+    lib->verify_mic = ssh_gssapi_verify_mic;
+    lib->free_mic = ssh_gssapi_free_mic;
+    lib->display_status = ssh_gssapi_display_status;
+}
+
+#else
+
+/* Dummy function so this source file defines something if NO_GSSAPI
+   is defined. */
+
+int ssh_gssapi_init(void)
+{
+    return 0;
+}
+
+#endif

+ 24 - 0
source/putty/ssh/gssc.h

@@ -0,0 +1,24 @@
+#ifndef PUTTY_SSHGSSC_H
+#define PUTTY_SSHGSSC_H
+#include "putty.h"
+#ifndef NO_GSSAPI
+
+#include "pgssapi.h"
+#include "gss.h"
+
+typedef struct gssapi_ssh_gss_ctx {
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    gss_ctx_id_t ctx;
+    time_t expiry;
+} gssapi_ssh_gss_ctx;
+
+void ssh_gssapi_bind_fns(struct ssh_gss_library *lib);
+
+#else
+
+int ssh_gssapi_init(void);
+
+#endif /*NO_GSSAPI*/
+
+#endif /*PUTTY_SSHGSSC_H*/

+ 935 - 0
source/putty/ssh/kex2-client.c

@@ -0,0 +1,935 @@
+/*
+ * Client side of key exchange for the SSH-2 transport protocol (RFC 4253).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+#include "storage.h"
+#include "transport2.h"
+#include "mpint.h"
+
+/*
+ * Another copy of the symbol defined in mpunsafe.c. See the comment
+ * there.
+ */
+const int deliberate_symbol_clash = 12345;
+
+void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    PktIn *pktin;
+    PktOut *pktout;
+
+    crBegin(s->crStateKex);
+
+    if (s->kex_alg->main_type == KEXTYPE_DH) {
+        /*
+         * Work out the number of bits of key we will need from the
+         * key exchange. We start with the maximum key length of
+         * either cipher...
+         */
+        {
+            int csbits, scbits;
+
+            csbits = s->out.cipher ? s->out.cipher->real_keybits : 0;
+            scbits = s->in.cipher ? s->in.cipher->real_keybits : 0;
+            s->nbits = (csbits > scbits ? csbits : scbits);
+        }
+        /* The keys only have hlen-bit entropy, since they're based on
+         * a hash. So cap the key size at hlen bits. */
+        if (s->nbits > s->kex_alg->hash->hlen * 8)
+            s->nbits = s->kex_alg->hash->hlen * 8;
+
+        /*
+         * If we're doing Diffie-Hellman group exchange, start by
+         * requesting a group.
+         */
+        if (dh_is_gex(s->kex_alg)) {
+            ppl_logevent("Doing Diffie-Hellman group exchange");
+            s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX;
+            /*
+             * Work out how big a DH group we will need to allow that
+             * much data.
+             */
+            s->pbits = 512 << ((s->nbits - 1) / 64);
+            if (s->pbits < DH_MIN_SIZE)
+                s->pbits = DH_MIN_SIZE;
+            if (s->pbits > DH_MAX_SIZE)
+                s->pbits = DH_MAX_SIZE;
+            if ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD);
+                put_uint32(pktout, s->pbits);
+            } else {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST);
+                put_uint32(pktout, DH_MIN_SIZE);
+                put_uint32(pktout, s->pbits);
+                put_uint32(pktout, DH_MAX_SIZE);
+            }
+            pq_push(s->ppl.out_pq, pktout);
+
+            crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+            if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                                "expecting Diffie-Hellman group, type %d (%s)",
+                                pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                *aborted = true;
+                return;
+            }
+            s->p = get_mp_ssh2(pktin);
+            s->g = get_mp_ssh2(pktin);
+            if (get_err(pktin)) {
+                ssh_proto_error(s->ppl.ssh,
+                                "Unable to parse Diffie-Hellman group packet");
+                *aborted = true;
+                return;
+            }
+            s->dh_ctx = dh_setup_gex(s->p, s->g);
+            s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
+            s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
+
+            ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit "
+                         "modulus and hash %s with a server-supplied group",
+                         dh_modulus_bit_size(s->dh_ctx),
+                         ssh_hash_alg(s->exhash)->text_name);
+        } else {
+            s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP;
+            s->dh_ctx = dh_setup_group(s->kex_alg);
+            s->kex_init_value = SSH2_MSG_KEXDH_INIT;
+            s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+
+            ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit "
+                         "modulus and hash %s with standard group \"%s\"",
+                         dh_modulus_bit_size(s->dh_ctx),
+                         ssh_hash_alg(s->exhash)->text_name,
+                         s->kex_alg->groupname);
+        }
+
+        /*
+         * Now generate and send e for Diffie-Hellman.
+         */
+        seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+        s->e = dh_create_e(s->dh_ctx);
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value);
+        put_mp_ssh2(pktout, s->e);
+        pq_push(s->ppl.out_pq, pktout);
+
+        seat_set_busy_status(s->ppl.seat, BUSY_WAITING);
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != s->kex_reply_value) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting Diffie-Hellman reply, type %d (%s)",
+                            pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            *aborted = true;
+            return;
+        }
+        seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+        s->hostkeydata = get_string(pktin);
+        s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+        s->f = get_mp_ssh2(pktin);
+        s->sigdata = get_string(pktin);
+        if (get_err(pktin)) {
+            ssh_proto_error(s->ppl.ssh,
+                            "Unable to parse Diffie-Hellman reply packet");
+            *aborted = true;
+            return;
+        }
+
+        {
+            const char *err = dh_validate_f(s->dh_ctx, s->f);
+            if (err) {
+                ssh_proto_error(s->ppl.ssh, "Diffie-Hellman reply failed "
+                                "validation: %s", err);
+                *aborted = true;
+                return;
+            }
+        }
+        s->K = dh_find_K(s->dh_ctx, s->f);
+
+        /* We assume everything from now on will be quick, and it might
+         * involve user interaction. */
+        seat_set_busy_status(s->ppl.seat, BUSY_NOT);
+
+        put_stringpl(s->exhash, s->hostkeydata);
+        if (dh_is_gex(s->kex_alg)) {
+            if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX))
+                put_uint32(s->exhash, DH_MIN_SIZE);
+            put_uint32(s->exhash, s->pbits);
+            if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX))
+                put_uint32(s->exhash, DH_MAX_SIZE);
+            put_mp_ssh2(s->exhash, s->p);
+            put_mp_ssh2(s->exhash, s->g);
+        }
+        put_mp_ssh2(s->exhash, s->e);
+        put_mp_ssh2(s->exhash, s->f);
+
+        dh_cleanup(s->dh_ctx);
+        s->dh_ctx = NULL;
+        mp_free(s->f); s->f = NULL;
+        if (dh_is_gex(s->kex_alg)) {
+            mp_free(s->g); s->g = NULL;
+            mp_free(s->p); s->p = NULL;
+        }
+    } else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
+
+        ppl_logevent("Doing ECDH key exchange with curve %s and hash %s",
+                     ssh_ecdhkex_curve_textname(s->kex_alg),
+                     ssh_hash_alg(s->exhash)->text_name);
+        s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX;
+
+        s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg);
+        if (!s->ecdh_key) {
+            ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH");
+            *aborted = true;
+            return;
+        }
+
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT);
+        {
+            strbuf *pubpoint = strbuf_new();
+            ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+            put_stringsb(pktout, pubpoint);
+        }
+
+        pq_push(s->ppl.out_pq, pktout);
+
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting ECDH reply, type %d (%s)", pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            *aborted = true;
+            return;
+        }
+
+        s->hostkeydata = get_string(pktin);
+        put_stringpl(s->exhash, s->hostkeydata);
+        s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+
+        {
+            strbuf *pubpoint = strbuf_new();
+            ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+            put_string(s->exhash, pubpoint->u, pubpoint->len);
+            strbuf_free(pubpoint);
+        }
+
+        {
+            ptrlen keydata = get_string(pktin);
+            put_stringpl(s->exhash, keydata);
+            s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata);
+            if (!get_err(pktin) && !s->K) {
+                ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
+                                "point in ECDH reply");
+                *aborted = true;
+                return;
+            }
+        }
+
+        s->sigdata = get_string(pktin);
+        if (get_err(pktin)) {
+            ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet");
+            *aborted = true;
+            return;
+        }
+
+        ssh_ecdhkex_freekey(s->ecdh_key);
+        s->ecdh_key = NULL;
+#ifndef NO_GSSAPI
+    } else if (s->kex_alg->main_type == KEXTYPE_GSS) {
+        ptrlen data;
+
+        s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX;
+        s->init_token_sent = false;
+        s->complete_rcvd = false;
+        s->hkey = NULL;
+        s->keystr = NULL;
+
+        /*
+         * Work out the number of bits of key we will need from the
+         * key exchange. We start with the maximum key length of
+         * either cipher...
+         *
+         * This is rote from the KEXTYPE_DH section above.
+         */
+        {
+            int csbits, scbits;
+
+            csbits = s->out.cipher->real_keybits;
+            scbits = s->in.cipher->real_keybits;
+            s->nbits = (csbits > scbits ? csbits : scbits);
+        }
+        /* The keys only have hlen-bit entropy, since they're based on
+         * a hash. So cap the key size at hlen bits. */
+        if (s->nbits > s->kex_alg->hash->hlen * 8)
+            s->nbits = s->kex_alg->hash->hlen * 8;
+
+        if (dh_is_gex(s->kex_alg)) {
+            /*
+             * Work out how big a DH group we will need to allow that
+             * much data.
+             */
+            s->pbits = 512 << ((s->nbits - 1) / 64);
+            ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman "
+                         "group exchange, with minimum %d bits", s->pbits);
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ);
+            put_uint32(pktout, s->pbits); /* min */
+            put_uint32(pktout, s->pbits); /* preferred */
+            put_uint32(pktout, s->pbits * 2); /* max */
+            pq_push(s->ppl.out_pq, pktout);
+
+            crMaybeWaitUntilV(
+                (pktin = ssh2_transport_pop(s)) != NULL);
+            if (pktin->type != SSH2_MSG_KEXGSS_GROUP) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                                "expecting Diffie-Hellman group, type %d (%s)",
+                                pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                *aborted = true;
+                return;
+            }
+            s->p = get_mp_ssh2(pktin);
+            s->g = get_mp_ssh2(pktin);
+            if (get_err(pktin)) {
+                ssh_proto_error(s->ppl.ssh,
+                                "Unable to parse Diffie-Hellman group packet");
+                *aborted = true;
+                return;
+            }
+            s->dh_ctx = dh_setup_gex(s->p, s->g);
+        } else {
+            s->dh_ctx = dh_setup_group(s->kex_alg);
+            ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with"
+                         " standard group \"%s\"", s->kex_alg->groupname);
+        }
+
+        ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key "
+                     "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name);
+        /* Now generate e for Diffie-Hellman. */
+        seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+        s->e = dh_create_e(s->dh_ctx);
+
+        if (s->shgss->lib->gsslogmsg)
+            ppl_logevent("%s", s->shgss->lib->gsslogmsg);
+
+        /* initial tokens are empty */
+        SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+        SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
+        SSH_GSS_CLEAR_BUF(&s->mic);
+        s->gss_stat = s->shgss->lib->acquire_cred(
+            s->shgss->lib, &s->shgss->ctx, &s->gss_cred_expiry);
+        if (s->gss_stat != SSH_GSS_OK) {
+            ssh_sw_abort(s->ppl.ssh,
+                         "GSSAPI key exchange failed to initialise");
+            *aborted = true;
+            return;
+        }
+
+        /* now enter the loop */
+        assert(s->shgss->srv_name);
+        do {
+            /*
+             * When acquire_cred yields no useful expiration, go with the
+             * service ticket expiration.
+             */
+            s->gss_stat = s->shgss->lib->init_sec_context(
+                s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name,
+                s->gss_delegate, &s->gss_rcvtok, &s->gss_sndtok,
+                (s->gss_cred_expiry == GSS_NO_EXPIRATION ?
+                 &s->gss_cred_expiry : NULL), NULL);
+            SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+
+            if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd)
+                break; /* MIC is verified after the loop */
+
+            if (s->gss_stat != SSH_GSS_S_COMPLETE &&
+                s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) {
+                if (s->shgss->lib->display_status(
+                        s->shgss->lib, s->shgss->ctx,
+                        &s->gss_buf) == SSH_GSS_OK) {
+                    char *err = s->gss_buf.value;
+                    ssh_sw_abort(s->ppl.ssh,
+                                 "GSSAPI key exchange failed to initialise "
+                                 "context: %s", err);
+                    sfree(err);
+                    *aborted = true;
+                    return;
+                }
+            }
+            assert(s->gss_stat == SSH_GSS_S_COMPLETE ||
+                   s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+            if (!s->init_token_sent) {
+                s->init_token_sent = true;
+                pktout = ssh_bpp_new_pktout(s->ppl.bpp,
+                                            SSH2_MSG_KEXGSS_INIT);
+                if (s->gss_sndtok.length == 0) {
+                    ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange failed: "
+                                 "no initial context token");
+                    *aborted = true;
+                    return;
+                }
+                put_string(pktout,
+                           s->gss_sndtok.value, s->gss_sndtok.length);
+                put_mp_ssh2(pktout, s->e);
+                pq_push(s->ppl.out_pq, pktout);
+                s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+                ppl_logevent("GSSAPI key exchange initialised");
+            } else if (s->gss_sndtok.length != 0) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_KEXGSS_CONTINUE);
+                put_string(pktout,
+                           s->gss_sndtok.value, s->gss_sndtok.length);
+                pq_push(s->ppl.out_pq, pktout);
+                s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+            }
+
+            if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd)
+                break;
+
+          wait_for_gss_token:
+            crMaybeWaitUntilV(
+                (pktin = ssh2_transport_pop(s)) != NULL);
+            switch (pktin->type) {
+              case SSH2_MSG_KEXGSS_CONTINUE:
+                data = get_string(pktin);
+                s->gss_rcvtok.value = (char *)data.ptr;
+                s->gss_rcvtok.length = data.len;
+                continue;
+              case SSH2_MSG_KEXGSS_COMPLETE:
+                s->complete_rcvd = true;
+                s->f = get_mp_ssh2(pktin);
+                data = get_string(pktin);
+                s->mic.value = (char *)data.ptr;
+                s->mic.length = data.len;
+                /* If there's a final token we loop to consume it */
+                if (get_bool(pktin)) {
+                    data = get_string(pktin);
+                    s->gss_rcvtok.value = (char *)data.ptr;
+                    s->gss_rcvtok.length = data.len;
+                    continue;
+                }
+                break;
+              case SSH2_MSG_KEXGSS_HOSTKEY:
+                s->hostkeydata = get_string(pktin);
+                if (s->hostkey_alg) {
+                    s->hkey = ssh_key_new_pub(s->hostkey_alg,
+                                              s->hostkeydata);
+                    put_stringpl(s->exhash, s->hostkeydata);
+                }
+                /*
+                 * Can't loop as we have no token to pass to
+                 * init_sec_context.
+                 */
+                goto wait_for_gss_token;
+              case SSH2_MSG_KEXGSS_ERROR:
+                /*
+                 * We have no use for the server's major and minor
+                 * status.  The minor status is really only
+                 * meaningful to the server, and with luck the major
+                 * status means something to us (but not really all
+                 * that much).  The string is more meaningful, and
+                 * hopefully the server sends any error tokens, as
+                 * that will produce the most useful information for
+                 * us.
+                 */
+                get_uint32(pktin); /* server's major status */
+                get_uint32(pktin); /* server's minor status */
+                data = get_string(pktin);
+                ppl_logevent("GSSAPI key exchange failed; "
+                             "server's message: %.*s", PTRLEN_PRINTF(data));
+                /* Language tag, but we have no use for it */
+                get_string(pktin);
+                /*
+                 * Wait for an error token, if there is one, or the
+                 * server's disconnect.  The error token, if there
+                 * is one, must follow the SSH2_MSG_KEXGSS_ERROR
+                 * message, per the RFC.
+                 */
+                goto wait_for_gss_token;
+              default:
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet "
+                                "during GSSAPI key exchange, type %d (%s)",
+                                pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                *aborted = true;
+                return;
+            }
+        } while (s->gss_rcvtok.length ||
+                 s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED ||
+                 !s->complete_rcvd);
+
+        {
+            const char *err = dh_validate_f(s->dh_ctx, s->f);
+            if (err) {
+                ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed "
+                                "validation: %s", err);
+                *aborted = true;
+                return;
+            }
+        }
+        s->K = dh_find_K(s->dh_ctx, s->f);
+
+        /* We assume everything from now on will be quick, and it might
+         * involve user interaction. */
+        seat_set_busy_status(s->ppl.seat, BUSY_NOT);
+
+        if (!s->hkey)
+            put_stringz(s->exhash, "");
+        if (dh_is_gex(s->kex_alg)) {
+            /* min,  preferred, max */
+            put_uint32(s->exhash, s->pbits);
+            put_uint32(s->exhash, s->pbits);
+            put_uint32(s->exhash, s->pbits * 2);
+
+            put_mp_ssh2(s->exhash, s->p);
+            put_mp_ssh2(s->exhash, s->g);
+        }
+        put_mp_ssh2(s->exhash, s->e);
+        put_mp_ssh2(s->exhash, s->f);
+
+        /*
+         * MIC verification is done below, after we compute the hash
+         * used as the MIC input.
+         */
+
+        dh_cleanup(s->dh_ctx);
+        s->dh_ctx = NULL;
+        mp_free(s->f); s->f = NULL;
+        if (dh_is_gex(s->kex_alg)) {
+            mp_free(s->g); s->g = NULL;
+            mp_free(s->p); s->p = NULL;
+        }
+#endif
+    } else {
+        ptrlen rsakeydata;
+
+        assert(s->kex_alg->main_type == KEXTYPE_RSA);
+        ppl_logevent("Doing RSA key exchange with hash %s",
+                     ssh_hash_alg(s->exhash)->text_name);
+        s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX;
+        /*
+         * RSA key exchange. First expect a KEXRSA_PUBKEY packet
+         * from the server.
+         */
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting RSA public key, type %d (%s)",
+                            pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            *aborted = true;
+            return;
+        }
+
+        s->hostkeydata = get_string(pktin);
+        put_stringpl(s->exhash, s->hostkeydata);
+        s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+
+        rsakeydata = get_string(pktin);
+
+        s->rsa_kex_key = ssh_rsakex_newkey(rsakeydata);
+        if (!s->rsa_kex_key) {
+            ssh_proto_error(s->ppl.ssh,
+                            "Unable to parse RSA public key packet");
+            *aborted = true;
+            return;
+        }
+        s->rsa_kex_key_needs_freeing = true;
+
+        put_stringpl(s->exhash, rsakeydata);
+
+        /*
+         * Next, set up a shared secret K, of precisely KLEN -
+         * 2*HLEN - 49 bits, where KLEN is the bit length of the
+         * RSA key modulus and HLEN is the bit length of the hash
+         * we're using.
+         */
+        {
+            int klen = ssh_rsakex_klen(s->rsa_kex_key);
+
+            const struct ssh_rsa_kex_extra *extra =
+                (const struct ssh_rsa_kex_extra *)s->kex_alg->extra;
+            if (klen < extra->minklen) {
+                ssh_proto_error(s->ppl.ssh, "Server sent %d-bit RSA key, "
+                                "less than the minimum size %d for %s "
+                                "key exchange", klen, extra->minklen,
+                                s->kex_alg->name);
+                *aborted = true;
+                return;
+            }
+
+            { // WINSCP
+            int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49);
+            assert(nbits > 0);
+
+            { // WINSCP
+            strbuf *buf, *outstr;
+
+            mp_int *tmp = mp_random_bits(nbits - 1);
+            s->K = mp_power_2(nbits - 1);
+            mp_add_into(s->K, s->K, tmp);
+            mp_free(tmp);
+
+            /*
+             * Encode this as an mpint.
+             */
+            buf = strbuf_new_nm();
+            put_mp_ssh2(buf, s->K);
+
+            /*
+             * Encrypt it with the given RSA key.
+             */
+            outstr = ssh_rsakex_encrypt(s->rsa_kex_key, s->kex_alg->hash,
+                                        ptrlen_from_strbuf(buf));
+
+            /*
+             * And send it off in a return packet.
+             */
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET);
+            put_stringpl(pktout, ptrlen_from_strbuf(outstr));
+            pq_push(s->ppl.out_pq, pktout);
+
+            put_stringsb(s->exhash, outstr); /* frees outstr */
+
+            strbuf_free(buf);
+            } // WINSCP
+            } // WINSCP
+        }
+
+        ssh_rsakex_freekey(s->rsa_kex_key);
+        s->rsa_kex_key = NULL;
+        s->rsa_kex_key_needs_freeing = false;
+
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting RSA kex signature, type %d (%s)",
+                            pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            *aborted = true;
+            return;
+        }
+
+        s->sigdata = get_string(pktin);
+        if (get_err(pktin)) {
+            ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature");
+            *aborted = true;
+            return;
+        }
+    }
+
+    ssh2transport_finalise_exhash(s);
+
+#ifndef NO_GSSAPI
+    if (s->kex_alg->main_type == KEXTYPE_GSS) {
+        Ssh_gss_buf gss_buf;
+        SSH_GSS_CLEAR_BUF(&s->gss_buf);
+
+        gss_buf.value = s->exchange_hash;
+        gss_buf.length = s->kex_alg->hash->hlen;
+        s->gss_stat = s->shgss->lib->verify_mic(
+            s->shgss->lib, s->shgss->ctx, &gss_buf, &s->mic);
+        if (s->gss_stat != SSH_GSS_OK) {
+            if (s->shgss->lib->display_status(
+                    s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) {
+                char *err = s->gss_buf.value;
+                ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was "
+                             "not valid: %s", err);
+                sfree(err);
+            } else {
+                ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was "
+                             "not valid");
+            }
+            *aborted = true;
+            return;
+        }
+
+        s->gss_kex_used = true;
+
+        /*-
+         * If this the first KEX, save the GSS context for "gssapi-keyex"
+         * authentication.
+         *
+         * http://tools.ietf.org/html/rfc4462#section-4
+         *
+         * This method may be used only if the initial key exchange was
+         * performed using a GSS-API-based key exchange method defined in
+         * accordance with Section 2.  The GSS-API context used with this
+         * method is always that established during an initial GSS-API-based
+         * key exchange.  Any context established during key exchange for the
+         * purpose of rekeying MUST NOT be used with this method.
+         */
+        if (s->got_session_id) {
+            s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+        }
+        ppl_logevent("GSSAPI Key Exchange complete!");
+    }
+#endif
+
+    s->dh_ctx = NULL;
+
+    /* In GSS keyex there's no hostkey signature to verify */
+    if (s->kex_alg->main_type != KEXTYPE_GSS) {
+        if (!s->hkey) {
+            ssh_proto_error(s->ppl.ssh, "Server's host key is invalid");
+            *aborted = true;
+            return;
+        }
+
+        if (!ssh_key_verify(
+                s->hkey, s->sigdata,
+                make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen))) {
+#ifndef FUZZING
+            ssh_proto_error(s->ppl.ssh, "Signature from server's host key "
+                            "is invalid");
+            *aborted = true;
+            return;
+#endif
+        }
+    }
+
+    s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL);
+#ifndef NO_GSSAPI
+    if (s->gss_kex_used) {
+        /*
+         * In a GSS-based session, check the host key (if any) against
+         * the transient host key cache.
+         */
+        if (s->kex_alg->main_type == KEXTYPE_GSS) {
+
+            /*
+             * We've just done a GSS key exchange. If it gave us a
+             * host key, store it.
+             */
+            if (s->hkey) {
+                char *fingerprint = ssh2_fingerprint(
+                    s->hkey, SSH_FPTYPE_DEFAULT);
+                ppl_logevent("GSS kex provided fallback host key:");
+                ppl_logevent("%s", fingerprint);
+                sfree(fingerprint);
+
+                ssh_transient_hostkey_cache_add(s->thc, s->hkey);
+            } else if (!ssh_transient_hostkey_cache_non_empty(s->thc)) {
+                /*
+                 * But if it didn't, then we currently have no
+                 * fallback host key to use in subsequent non-GSS
+                 * rekeys. So we should immediately trigger a non-GSS
+                 * rekey of our own, to set one up, before the session
+                 * keys have been used for anything else.
+                 *
+                 * This is similar to the cross-certification done at
+                 * user request in the permanent host key cache, but
+                 * here we do it automatically, once, at session
+                 * startup, and only add the key to the transient
+                 * cache.
+                 */
+                if (s->hostkey_alg) {
+                    s->need_gss_transient_hostkey = true;
+                } else {
+                    /*
+                     * If we negotiated the "null" host key algorithm
+                     * in the key exchange, that's an indication that
+                     * no host key at all is available from the server
+                     * (both because we listed "null" last, and
+                     * because RFC 4462 section 5 says that a server
+                     * MUST NOT offer "null" as a host key algorithm
+                     * unless that is the only algorithm it provides
+                     * at all).
+                     *
+                     * In that case we actually _can't_ perform a
+                     * non-GSSAPI key exchange, so it's pointless to
+                     * attempt one proactively. This is also likely to
+                     * cause trouble later if a rekey is required at a
+                     * moment whne GSS credentials are not available,
+                     * but someone setting up a server in this
+                     * configuration presumably accepts that as a
+                     * consequence.
+                     */
+                    if (!s->warned_about_no_gss_transient_hostkey) {
+                        ppl_logevent("No fallback host key available");
+                        s->warned_about_no_gss_transient_hostkey = true;
+                    }
+                }
+            }
+        } else {
+            /*
+             * We've just done a fallback key exchange, so make
+             * sure the host key it used is in the cache of keys
+             * we previously received in GSS kexes.
+             *
+             * An exception is if this was the non-GSS key exchange we
+             * triggered on purpose to populate the transient cache.
+             */
+            assert(s->hkey);  /* only KEXTYPE_GSS lets this be null */
+            { // WINSCP
+            char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
+
+            if (s->need_gss_transient_hostkey) {
+                ppl_logevent("Post-GSS rekey provided fallback host key:");
+                ppl_logevent("%s", fingerprint);
+                ssh_transient_hostkey_cache_add(s->thc, s->hkey);
+                s->need_gss_transient_hostkey = false;
+            } else if (!ssh_transient_hostkey_cache_verify(s->thc, s->hkey)) {
+                ppl_logevent("Non-GSS rekey after initial GSS kex "
+                             "used host key:");
+                ppl_logevent("%s", fingerprint);
+                sfree(fingerprint);
+                ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any "
+                             "used in previous GSS kex");
+                *aborted = true;
+                return;
+            }
+
+            sfree(fingerprint);
+            } // WINSCP
+        }
+    } else
+#endif /* NO_GSSAPI */
+        if (!s->got_session_id) {
+            /*
+             * Make a note of any other host key formats that are available.
+             */
+            {
+                int i, j, nkeys = 0;
+                char *list = NULL;
+                for (i = 0; i < lenof(ssh2_hostkey_algs); i++) {
+                    if (ssh2_hostkey_algs[i].alg == s->hostkey_alg)
+                        continue;
+
+                    for (j = 0; j < s->n_uncert_hostkeys; j++)
+                        if (s->uncert_hostkeys[j] == i)
+                            break;
+
+                    if (j < s->n_uncert_hostkeys) {
+                        char *newlist;
+                        if (list)
+                            newlist = dupprintf(
+                                "%s/%s", list,
+                                ssh2_hostkey_algs[i].alg->ssh_id);
+                        else
+                            newlist = dupprintf(
+                                "%s", ssh2_hostkey_algs[i].alg->ssh_id);
+                        sfree(list);
+                        list = newlist;
+                        nkeys++;
+                    }
+                }
+                if (list) {
+                    ppl_logevent("Server also has %s host key%s, but we "
+                                 "don't know %s", list,
+                                 nkeys > 1 ? "s" : "",
+                                 nkeys > 1 ? "any of them" : "it");
+                    sfree(list);
+                }
+            }
+
+            /*
+             * Authenticate remote host: verify host key. (We've already
+             * checked the signature of the exchange hash.)
+             */
+            {
+                ssh2_userkey uk; // WINSCP
+                uk.key = s->hkey; // WINSCP
+                uk.comment = NULL; // WINSCP
+                { // WINSCP
+                char *keydisp = ssh2_pubkey_openssh_str(&uk);
+                char **fingerprints = ssh2_all_fingerprints(s->hkey);
+
+                FingerprintType fptype_default =
+                    ssh2_pick_default_fingerprint(fingerprints);
+                ppl_logevent("Host key fingerprint is:");
+                ppl_logevent("%s", fingerprints[fptype_default]);
+
+                s->spr = verify_ssh_host_key(
+                    ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport,
+                    s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp,
+                    fingerprints, ssh2_transport_dialog_callback, s);
+
+                ssh2_free_all_fingerprints(fingerprints);
+                sfree(keydisp);
+                } // WINSCP
+            }
+#ifdef FUZZING
+            s->spr = SPR_OK;
+#endif
+            crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+            if (spr_is_abort(s->spr)) {
+                *aborted = true;
+                ssh_spr_close(s->ppl.ssh, s->spr, "host key verification");
+                return;
+            }
+
+            /*
+             * Save this host key, to check against the one presented in
+             * subsequent rekeys.
+             */
+            s->hostkey_str = s->keystr;
+            s->keystr = NULL;
+        } else if (s->cross_certifying) {
+            assert(s->hkey);
+            assert(ssh_key_alg(s->hkey) == s->cross_certifying);
+
+            { // WINSCP
+            char *fingerprint = ssh2_fingerprint(s->hkey, SSH_FPTYPE_DEFAULT);
+            ppl_logevent("Storing additional host key for this host:");
+            ppl_logevent("%s", fingerprint);
+            sfree(fingerprint);
+
+            store_host_key(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
+             * re-checking in future normal rekeys.
+             */
+            s->hostkey_str = s->keystr;
+            s->keystr = NULL;
+            } // WINSCP
+        } else {
+            /*
+             * In a rekey, we never present an interactive host key
+             * verification request to the user. Instead, we simply
+             * enforce that the key we're seeing this time is identical to
+             * the one we saw before.
+             */
+            assert(s->keystr);         /* filled in by prior key exchange */
+            if (strcmp(s->hostkey_str, s->keystr)) {
+#ifndef FUZZING
+                ssh_sw_abort(s->ppl.ssh,
+                             "Host key was different in repeat key exchange");
+                *aborted = true;
+                return;
+#endif
+            }
+        }
+
+    sfree(s->keystr);
+    s->keystr = NULL;
+    if (s->hkey) {
+        ssh_key_free(s->hkey);
+        s->hkey = NULL;
+    }
+
+    crFinishV;
+}

+ 548 - 0
source/putty/ssh/mainchan.c

@@ -0,0 +1,548 @@
+/*
+ * SSH main session channel handling.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "ppl.h"
+#include "channel.h"
+
+static void mainchan_free(Channel *chan);
+static void mainchan_open_confirmation(Channel *chan);
+static void mainchan_open_failure(Channel *chan, const char *errtext);
+static size_t mainchan_send(
+    Channel *chan, bool is_stderr, const void *, size_t);
+static void mainchan_send_eof(Channel *chan);
+static void mainchan_set_input_wanted(Channel *chan, bool wanted);
+static char *mainchan_log_close_msg(Channel *chan);
+static bool mainchan_rcvd_exit_status(Channel *chan, int status);
+static bool mainchan_rcvd_exit_signal(
+    Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg);
+static bool mainchan_rcvd_exit_signal_numeric(
+    Channel *chan, int signum, bool core_dumped, ptrlen msg);
+static void mainchan_request_response(Channel *chan, bool success);
+
+static const ChannelVtable mainchan_channelvt = {
+    // WINSCP
+    /*.free =*/ mainchan_free,
+    /*.open_confirmation =*/ mainchan_open_confirmation,
+    /*.open_failed =*/ mainchan_open_failure,
+    /*.send =*/ mainchan_send,
+    /*.send_eof =*/ mainchan_send_eof,
+    /*.set_input_wanted =*/ mainchan_set_input_wanted,
+    /*.log_close_msg =*/ mainchan_log_close_msg,
+    /*.want_close =*/ chan_default_want_close,
+    /*.rcvd_exit_status =*/ mainchan_rcvd_exit_status,
+    /*.rcvd_exit_signal =*/ mainchan_rcvd_exit_signal,
+    /*.rcvd_exit_signal_numeric =*/ mainchan_rcvd_exit_signal_numeric,
+    /*.run_shell =*/ chan_no_run_shell,
+    /*.run_command =*/ chan_no_run_command,
+    /*.run_subsystem =*/ chan_no_run_subsystem,
+    /*.enable_x11_forwarding =*/ chan_no_enable_x11_forwarding,
+    /*.enable_agent_forwarding =*/ chan_no_enable_agent_forwarding,
+    /*.allocate_pty =*/ chan_no_allocate_pty,
+    /*.set_env =*/ chan_no_set_env,
+    /*.send_break =*/ chan_no_send_break,
+    /*.send_signal =*/ chan_no_send_signal,
+    /*.change_window_size =*/ chan_no_change_window_size,
+    /*.request_response =*/ mainchan_request_response,
+};
+
+typedef enum MainChanType {
+    MAINCHAN_SESSION, MAINCHAN_DIRECT_TCPIP
+} MainChanType;
+
+struct mainchan {
+    SshChannel *sc;
+    Conf *conf;
+    PacketProtocolLayer *ppl;
+    ConnectionLayer *cl;
+
+    MainChanType type;
+    bool is_simple;
+
+    bool req_x11, req_agent, req_pty, req_cmd_primary, req_cmd_fallback;
+    int n_req_env, n_env_replies, n_env_fails;
+    bool eof_pending, eof_sent, got_pty, ready;
+
+    int term_width, term_height;
+
+    Channel chan;
+};
+
+mainchan *mainchan_new(
+    PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
+    int term_width, int term_height, bool is_simple, SshChannel **sc_out)
+{
+    mainchan *mc;
+
+    if (conf_get_bool(conf, CONF_ssh_no_shell))
+        return NULL;                   /* no main channel at all */
+
+    mc = snew(mainchan);
+    memset(mc, 0, sizeof(mainchan));
+    mc->ppl = ppl;
+    mc->cl = cl;
+    mc->conf = conf_copy(conf);
+    mc->term_width = term_width;
+    mc->term_height = term_height;
+    mc->is_simple = is_simple;
+
+    mc->sc = NULL;
+    mc->chan.vt = &mainchan_channelvt;
+    mc->chan.initial_fixed_window_size = 0;
+
+    if (*conf_get_str(mc->conf, CONF_ssh_nc_host)) {
+        const char *host = conf_get_str(mc->conf, CONF_ssh_nc_host);
+        int port = conf_get_int(mc->conf, CONF_ssh_nc_port);
+
+        mc->sc = ssh_lportfwd_open(cl, host, port, "main channel",
+                                   NULL, &mc->chan);
+        mc->type = MAINCHAN_DIRECT_TCPIP;
+    } else {
+        mc->sc = ssh_session_open(cl, &mc->chan);
+        mc->type = MAINCHAN_SESSION;
+    }
+
+    if (sc_out) *sc_out = mc->sc;
+    return mc;
+}
+
+static void mainchan_free(Channel *chan)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    conf_free(mc->conf);
+    sfree(mc);
+}
+
+static void mainchan_try_fallback_command(mainchan *mc);
+static void mainchan_ready(mainchan *mc);
+
+static void mainchan_open_confirmation(Channel *chan)
+{
+    mainchan *mc = container_of(chan, mainchan, chan);
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    seat_update_specials_menu(mc->ppl->seat);
+    ppl_logevent("Opened main channel");
+    seat_notify_session_started(mc->ppl->seat);
+
+    if (mc->is_simple)
+        sshfwd_hint_channel_is_simple(mc->sc);
+
+    if (mc->type == MAINCHAN_SESSION) {
+        /*
+         * Send the CHANNEL_REQUESTS for the main session channel.
+         */
+        char *key, *val, *cmd;
+        #ifndef WINSCP
+        struct X11Display *x11disp;
+        struct X11FakeAuth *x11auth;
+        #endif
+        bool retry_cmd_now = false;
+
+        #ifndef WINSCP
+        if (conf_get_bool(mc->conf, CONF_x11_forward)) {
+            char *x11_setup_err;
+            if ((x11disp = x11_setup_display(
+                     conf_get_str(mc->conf, CONF_x11_display),
+                     mc->conf, &x11_setup_err)) == NULL) {
+                ppl_logevent("X11 forwarding not enabled: unable to"
+                             " initialise X display: %s", x11_setup_err);
+                sfree(x11_setup_err);
+            } else {
+                x11auth = ssh_add_x11_display(
+                    mc->cl, conf_get_int(mc->conf, CONF_x11_auth), x11disp);
+
+                sshfwd_request_x11_forwarding(
+                    mc->sc, true, x11auth->protoname, x11auth->datastring,
+                    x11disp->screennum, false);
+                mc->req_x11 = true;
+            }
+        }
+        #endif
+
+        if (ssh_agent_forwarding_permitted(mc->cl)) {
+            sshfwd_request_agent_forwarding(mc->sc, true);
+            mc->req_agent = true;
+        }
+
+        if (!conf_get_bool(mc->conf, CONF_nopty)) {
+            sshfwd_request_pty(
+                mc->sc, true, mc->conf, mc->term_width, mc->term_height);
+            mc->req_pty = true;
+        }
+
+        for (val = conf_get_str_strs(mc->conf, CONF_environmt, NULL, &key);
+             val != NULL;
+             val = conf_get_str_strs(mc->conf, CONF_environmt, key, &key)) {
+            sshfwd_send_env_var(mc->sc, true, key, val);
+            mc->n_req_env++;
+        }
+        if (mc->n_req_env)
+            ppl_logevent("Sent %d environment variables", mc->n_req_env);
+
+        cmd = conf_get_str(mc->conf, CONF_remote_cmd);
+        if (conf_get_bool(mc->conf, CONF_ssh_subsys)) {
+            retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd);
+        } else if (*cmd) {
+            sshfwd_start_command(mc->sc, true, cmd);
+        } else {
+            sshfwd_start_shell(mc->sc, true);
+        }
+
+        if (retry_cmd_now)
+            mainchan_try_fallback_command(mc);
+        else
+            mc->req_cmd_primary = true;
+
+    } else {
+        ssh_set_ldisc_option(mc->cl, LD_ECHO, true);
+        ssh_set_ldisc_option(mc->cl, LD_EDIT, true);
+        mainchan_ready(mc);
+    }
+}
+
+static void mainchan_try_fallback_command(mainchan *mc)
+{
+    const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2);
+    if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) {
+        sshfwd_start_subsystem(mc->sc, true, cmd);
+    } else if (*cmd) {
+        sshfwd_start_command(mc->sc, true, cmd);
+    } else {
+        sshfwd_start_shell(mc->sc, true); // WINSCP
+    }
+    mc->req_cmd_fallback = true;
+}
+
+static void mainchan_request_response(Channel *chan, bool success)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    if (mc->req_x11) {
+        mc->req_x11 = false;
+
+        if (success) {
+            ppl_logevent("X11 forwarding enabled");
+            ssh_enable_x_fwd(mc->cl);
+        } else {
+            ppl_logevent("X11 forwarding refused");
+        }
+        return;
+    }
+
+    if (mc->req_agent) {
+        mc->req_agent = false;
+
+        if (success) {
+            ppl_logevent("Agent forwarding enabled");
+        } else {
+            ppl_logevent("Agent forwarding refused");
+        }
+        return;
+    }
+
+    if (mc->req_pty) {
+        mc->req_pty = false;
+
+        if (success) {
+            ppl_logevent("Allocated pty");
+            mc->got_pty = true;
+        } else {
+            ppl_logevent("Server refused to allocate pty");
+            ppl_printf("Server refused to allocate pty\r\n");
+            ssh_set_ldisc_option(mc->cl, LD_ECHO, true);
+            ssh_set_ldisc_option(mc->cl, LD_EDIT, true);
+        }
+        return;
+    }
+
+    if (mc->n_env_replies < mc->n_req_env) {
+        int j = mc->n_env_replies++;
+        if (!success) {
+            ppl_logevent("Server refused to set environment variable %s",
+                         conf_get_str_nthstrkey(mc->conf,
+                                                CONF_environmt, j));
+            mc->n_env_fails++;
+        }
+
+        if (mc->n_env_replies == mc->n_req_env) {
+            if (mc->n_env_fails == 0) {
+                ppl_logevent("All environment variables successfully set");
+            } else if (mc->n_env_fails == mc->n_req_env) {
+                ppl_logevent("All environment variables refused");
+                ppl_printf("Server refused to set environment "
+                           "variables\r\n");
+            } else {
+                ppl_printf("Server refused to set all environment "
+                           "variables\r\n");
+            }
+        }
+        return;
+    }
+
+    if (mc->req_cmd_primary) {
+        mc->req_cmd_primary = false;
+
+        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
+            ppl_logevent("Primary command failed; attempting fallback");
+            mainchan_try_fallback_command(mc);
+        } else {
+            /*
+             * If there's no remote_cmd2 configured, then we have no
+             * fallback command, so we've run out of options.
+             */
+            ssh_sw_abort(mc->ppl->ssh,
+                         "Server refused to start a shell/command");
+        }
+        return;
+    }
+
+    if (mc->req_cmd_fallback) {
+        mc->req_cmd_fallback = false;
+
+        if (success) {
+            ppl_logevent("Started a shell/command");
+            ssh_got_fallback_cmd(mc->ppl->ssh);
+            mainchan_ready(mc);
+        } else {
+            ssh_sw_abort(mc->ppl->ssh,
+                         "Server refused to start a shell/command");
+        }
+        return;
+    }
+}
+
+static void mainchan_ready(mainchan *mc)
+{
+    mc->ready = true;
+
+    ssh_set_wants_user_input(mc->cl, true);
+    ssh_got_user_input(mc->cl); /* in case any is already queued */
+
+    /* If an EOF arrived before we were ready, handle it now. */
+    if (mc->eof_pending) {
+        mc->eof_pending = false;
+        mainchan_special_cmd(mc, SS_EOF, 0);
+    }
+
+    ssh_ldisc_update(mc->ppl->ssh);
+    queue_idempotent_callback(&mc->ppl->ic_process_queue);
+}
+
+static void mainchan_open_failure(Channel *chan, const char *errtext)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+
+    ssh_sw_abort_deferred(mc->ppl->ssh,
+                          "Server refused to open main channel: %s", errtext);
+}
+
+static size_t mainchan_send(Channel *chan, bool is_stderr,
+                         const void *data, size_t length)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    return seat_output(mc->ppl->seat, is_stderr, data, length);
+}
+
+static void mainchan_send_eof(Channel *chan)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    if (!mc->eof_sent && (seat_eof(mc->ppl->seat) || mc->got_pty)) {
+        /*
+         * Either seat_eof told us that the front end wants us to
+         * close the outgoing side of the connection as soon as we see
+         * EOF from the far end, or else we've unilaterally decided to
+         * do that because we've allocated a remote pty and hence EOF
+         * isn't a particularly meaningful concept.
+         */
+        sshfwd_write_eof(mc->sc);
+        ppl_logevent("Sent EOF message");
+        mc->eof_sent = true;
+        ssh_set_wants_user_input(mc->cl, false); /* stop reading from stdin */
+    }
+}
+
+static void mainchan_set_input_wanted(Channel *chan, bool wanted)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+
+    /*
+     * This is the main channel of the SSH session, i.e. the one tied
+     * to the standard input (or GUI) of the primary SSH client user
+     * interface. So ssh->send_ok is how we control whether we're
+     * reading from that input.
+     */
+    ssh_set_wants_user_input(mc->cl, wanted);
+}
+
+static char *mainchan_log_close_msg(Channel *chan)
+{
+    return dupstr("Main session channel closed");
+}
+
+static bool mainchan_rcvd_exit_status(Channel *chan, int status)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    ssh_got_exitcode(mc->ppl->ssh, status);
+    ppl_logevent("Session sent command exit status %d", status);
+    return true;
+}
+
+static void mainchan_log_exit_signal_common(
+    mainchan *mc, const char *sigdesc,
+    bool core_dumped, ptrlen msg)
+{
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    const char *core_msg = core_dumped ? " (core dumped)" : "";
+    const char *msg_pre = (msg.len ? " (" : "");
+    const char *msg_post = (msg.len ? ")" : "");
+    ppl_logevent("Session exited on %s%s%s%.*s%s",
+                 sigdesc, core_msg, msg_pre, PTRLEN_PRINTF(msg), msg_post);
+}
+
+static bool mainchan_rcvd_exit_signal(
+    Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    int exitcode;
+    char *signame_str;
+
+    /*
+     * Translate the signal description back into a locally meaningful
+     * number, or 128 if the string didn't match any we recognise.
+     */
+    exitcode = 128;
+
+    #define SIGNAL_SUB(s) \
+        if (ptrlen_eq_string(signame, #s))      \
+            exitcode = 128 + SIG ## s;
+    #define SIGNAL_MAIN(s, text) SIGNAL_SUB(s)
+    #define SIGNALS_LOCAL_ONLY
+    #include "signal-list.h"
+    #undef SIGNAL_SUB
+    #undef SIGNAL_MAIN
+    #undef SIGNALS_LOCAL_ONLY
+
+    ssh_got_exitcode(mc->ppl->ssh, exitcode);
+    if (exitcode == 128)
+        signame_str = dupprintf("unrecognised signal \"%.*s\"",
+                                PTRLEN_PRINTF(signame));
+    else
+        signame_str = dupprintf("signal SIG%.*s", PTRLEN_PRINTF(signame));
+    mainchan_log_exit_signal_common(mc, signame_str, core_dumped, msg);
+    sfree(signame_str);
+    return true;
+}
+
+static bool mainchan_rcvd_exit_signal_numeric(
+    Channel *chan, int signum, bool core_dumped, ptrlen msg)
+{
+    pinitassert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    char *signum_str;
+
+    ssh_got_exitcode(mc->ppl->ssh, 128 + signum);
+    signum_str = dupprintf("signal %d", signum);
+    mainchan_log_exit_signal_common(mc, signum_str, core_dumped, msg);
+    sfree(signum_str);
+    return true;
+}
+
+void mainchan_get_specials(
+    mainchan *mc, add_special_fn_t add_special, void *ctx)
+{
+    /* FIXME: this _does_ depend on whether these services are supported */
+
+    /* WINSCP (causes linker warning and we do not use these)
+    add_special(ctx, "Break", SS_BRK, 0);
+
+    #define SIGNAL_MAIN(name, desc) \
+    add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0);
+    #define SIGNAL_SUB(name)
+    #include "signal-list.h"
+    #undef SIGNAL_MAIN
+    #undef SIGNAL_SUB
+
+    add_special(ctx, "More signals", SS_SUBMENU, 0);
+
+    #define SIGNAL_MAIN(name, desc)
+    #define SIGNAL_SUB(name) \
+    add_special(ctx, "SIG" #name, SS_SIG ## name, 0);
+    #include "signal-list.h"
+    #undef SIGNAL_MAIN
+    #undef SIGNAL_SUB
+
+    add_special(ctx, NULL, SS_EXITMENU, 0);
+    */
+}
+
+static const char *ssh_signal_lookup(SessionSpecialCode code)
+{
+    #define SIGNAL_SUB(name) \
+    if (code == SS_SIG ## name) return #name;
+    #define SIGNAL_MAIN(name, desc) SIGNAL_SUB(name)
+    #include "signal-list.h"
+    #undef SIGNAL_MAIN
+    #undef SIGNAL_SUB
+
+    /* If none of those clauses matched, fail lookup. */
+    return NULL;
+}
+
+void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg)
+{
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+    const char *signame;
+
+    if (code == SS_EOF) {
+        if (!mc->ready) {
+            /*
+             * Buffer the EOF to send as soon as the main channel is
+             * fully set up.
+             */
+            mc->eof_pending = true;
+        } else if (!mc->eof_sent) {
+            sshfwd_write_eof(mc->sc);
+            mc->eof_sent = true;
+        }
+    } else if (code == SS_BRK) {
+        sshfwd_send_serial_break(
+            mc->sc, false, 0 /* default break length */);
+    } else if ((signame = ssh_signal_lookup(code)) != NULL) {
+        /* It's a signal. */
+        sshfwd_send_signal(mc->sc, false, signame);
+        ppl_logevent("Sent signal SIG%s", signame);
+    }
+}
+
+void mainchan_terminal_size(mainchan *mc, int width, int height)
+{
+    mc->term_width = width;
+    mc->term_height = height;
+
+    if (mc->req_pty || mc->got_pty)
+        sshfwd_send_terminal_size_change(mc->sc, width, height);
+}

+ 25 - 0
source/putty/ssh/nosharing.c

@@ -0,0 +1,25 @@
+/*
+ * Stub implementation of SSH connection-sharing IPC, for any
+ * platform which can't support it at all.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "ssh.h"
+#include "network.h"
+
+int platform_ssh_share(const char *name, Conf *conf,
+                       Plug *downplug, Plug *upplug, Socket **sock,
+                       char **logtext, char **ds_err, char **us_err,
+                       bool can_upstream, bool can_downstream)
+{
+    return SHARE_NONE;
+}
+
+void platform_ssh_share_cleanup(const char *name)
+{
+}

+ 105 - 0
source/putty/ssh/pgssapi.c

@@ -0,0 +1,105 @@
+/* This file actually defines the GSSAPI function pointers for
+ * functions we plan to import from a GSSAPI library.
+ */
+#include "putty.h"
+
+#ifndef NO_GSSAPI
+
+#include "pgssapi.h"
+
+#ifndef NO_LIBDL
+
+/* Reserved static storage for GSS_oids.  Comments are quotes from RFC 2744. */
+static const gss_OID_desc oids[] = {
+    /* The implementation must reserve static storage for a
+     * gss_OID_desc object containing the value */
+    {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"},
+    /* corresponding to an object-identifier value of
+     * {iso(1) member-body(2) United States(840) mit(113554)
+     * infosys(1) gssapi(2) generic(1) user_name(1)}.  The constant
+     * GSS_C_NT_USER_NAME should be initialized to point
+     * to that gss_OID_desc.
+
+     * The implementation must reserve static storage for a
+     * gss_OID_desc object containing the value */
+    {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"},
+    /* corresponding to an object-identifier value of
+     * {iso(1) member-body(2) United States(840) mit(113554)
+     * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}.
+     * The constant GSS_C_NT_MACHINE_UID_NAME should be
+     * initialized to point to that gss_OID_desc.
+
+     * The implementation must reserve static storage for a
+     * gss_OID_desc object containing the value */
+    {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"},
+    /* corresponding to an object-identifier value of
+     * {iso(1) member-body(2) United States(840) mit(113554)
+     * infosys(1) gssapi(2) generic(1) string_uid_name(3)}.
+     * The constant GSS_C_NT_STRING_UID_NAME should be
+     * initialized to point to that gss_OID_desc.
+     *
+     * The implementation must reserve static storage for a
+     * gss_OID_desc object containing the value */
+    {6, (void *)"\x2b\x06\x01\x05\x06\x02"},
+    /* corresponding to an object-identifier value of
+     * {iso(1) org(3) dod(6) internet(1) security(5)
+     * nametypes(6) gss-host-based-services(2))}.  The constant
+     * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point
+     * to that gss_OID_desc.  This is a deprecated OID value, and
+     * implementations wishing to support hostbased-service names
+     * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID,
+     * defined below, to identify such names;
+     * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym
+     * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input
+     * parameter, but should not be emitted by GSS-API
+     * implementations
+     *
+     * The implementation must reserve static storage for a
+     * gss_OID_desc object containing the value */
+     {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"},
+    /* corresponding to an object-identifier value of {iso(1)
+     * member-body(2) Unites States(840) mit(113554) infosys(1)
+     * gssapi(2) generic(1) service_name(4)}.  The constant
+     * GSS_C_NT_HOSTBASED_SERVICE should be initialized
+     * to point to that gss_OID_desc.
+     *
+     * The implementation must reserve static storage for a
+     * gss_OID_desc object containing the value */
+    {6, (void *)"\x2b\x06\01\x05\x06\x03"},
+    /* corresponding to an object identifier value of
+     * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
+     * 6(nametypes), 3(gss-anonymous-name)}.  The constant
+     * and GSS_C_NT_ANONYMOUS should be initialized to point
+     * to that gss_OID_desc.
+     *
+     * The implementation must reserve static storage for a
+     * gss_OID_desc object containing the value */
+    {6, (void *)"\x2b\x06\x01\x05\x06\x04"},
+     /* corresponding to an object-identifier value of
+     * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
+     * 6(nametypes), 4(gss-api-exported-name)}.  The constant
+     * GSS_C_NT_EXPORT_NAME should be initialized to point
+     * to that gss_OID_desc.
+     */
+};
+
+/* Here are the constants which point to the static structure above.
+ *
+ * Constants of the form GSS_C_NT_* are specified by rfc 2744.
+ */
+const_gss_OID GSS_C_NT_USER_NAME           = oids+0;
+const_gss_OID GSS_C_NT_MACHINE_UID_NAME    = oids+1;
+const_gss_OID GSS_C_NT_STRING_UID_NAME     = oids+2;
+const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = oids+3;
+const_gss_OID GSS_C_NT_HOSTBASED_SERVICE   = oids+4;
+const_gss_OID GSS_C_NT_ANONYMOUS           = oids+5;
+const_gss_OID GSS_C_NT_EXPORT_NAME         = oids+6;
+
+#endif /* NO_LIBDL */
+
+static gss_OID_desc gss_mech_krb5_desc =
+{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+/* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/
+const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc;
+
+#endif /* NO_GSSAPI */

+ 333 - 0
source/putty/ssh/pgssapi.h

@@ -0,0 +1,333 @@
+#ifndef PUTTY_PGSSAPI_H
+#define PUTTY_PGSSAPI_H
+
+#include "putty.h"
+
+#ifndef NO_GSSAPI
+
+/*
+ * On Unix, if we're statically linking against GSSAPI, we leave the
+ * declaration of all this lot to the official header. If we're
+ * dynamically linking, we declare it ourselves, because that avoids
+ * us needing the official header at compile time.
+ *
+ * However, we still need the function pointer types, because even
+ * with statically linked GSSAPI we use the ssh_gss_library wrapper.
+ */
+#ifdef STATIC_GSSAPI
+#include <gssapi/gssapi.h>
+typedef gss_OID const_gss_OID;         /* for our prototypes below */
+#else /* STATIC_GSSAPI */
+
+/*******************************************************************************
+ *  GSSAPI Definitions, taken from RFC 2744
+ ******************************************************************************/
+
+/* GSSAPI Type Definitions */
+typedef uint32_t OM_uint32;
+
+typedef struct gss_OID_desc_struct {
+    OM_uint32 length;
+    void *elements;
+} gss_OID_desc;
+typedef const gss_OID_desc *const_gss_OID;
+typedef gss_OID_desc *gss_OID;
+
+typedef struct gss_OID_set_desc_struct  {
+    size_t  count;
+    gss_OID elements;
+} gss_OID_set_desc;
+typedef const gss_OID_set_desc *const_gss_OID_set;
+typedef gss_OID_set_desc *gss_OID_set;
+
+typedef struct gss_buffer_desc_struct {
+    size_t length;
+    void *value;
+} gss_buffer_desc, *gss_buffer_t;
+
+typedef struct gss_channel_bindings_struct {
+    OM_uint32 initiator_addrtype;
+    gss_buffer_desc initiator_address;
+    OM_uint32 acceptor_addrtype;
+    gss_buffer_desc acceptor_address;
+    gss_buffer_desc application_data;
+} *gss_channel_bindings_t;
+
+typedef void * gss_ctx_id_t;
+typedef void * gss_name_t;
+typedef void * gss_cred_id_t;
+
+typedef OM_uint32 gss_qop_t;
+typedef int gss_cred_usage_t;
+
+/* Flag bits for context-level services. */
+
+#define GSS_C_DELEG_FLAG      1
+#define GSS_C_MUTUAL_FLAG     2
+#define GSS_C_REPLAY_FLAG     4
+#define GSS_C_SEQUENCE_FLAG   8
+#define GSS_C_CONF_FLAG       16
+#define GSS_C_INTEG_FLAG      32
+#define GSS_C_ANON_FLAG       64
+#define GSS_C_PROT_READY_FLAG 128
+#define GSS_C_TRANS_FLAG      256
+
+/* Credential usage options */
+#define GSS_C_BOTH     0
+#define GSS_C_INITIATE 1
+#define GSS_C_ACCEPT   2
+
+/*-
+ * RFC 2744 Page 86
+ * Expiration time of 2^32-1 seconds means infinite lifetime for a
+ * credential or security context
+ */
+#define GSS_C_INDEFINITE 0xfffffffful
+
+/* Status code types for gss_display_status */
+#define GSS_C_GSS_CODE  1
+#define GSS_C_MECH_CODE 2
+
+/* The constant definitions for channel-bindings address families */
+#define GSS_C_AF_UNSPEC     0
+#define GSS_C_AF_LOCAL      1
+#define GSS_C_AF_INET       2
+#define GSS_C_AF_IMPLINK    3
+#define GSS_C_AF_PUP        4
+#define GSS_C_AF_CHAOS      5
+#define GSS_C_AF_NS         6
+#define GSS_C_AF_NBS        7
+#define GSS_C_AF_ECMA       8
+#define GSS_C_AF_DATAKIT    9
+#define GSS_C_AF_CCITT      10
+#define GSS_C_AF_SNA        11
+#define GSS_C_AF_DECnet     12
+#define GSS_C_AF_DLI        13
+#define GSS_C_AF_LAT        14
+#define GSS_C_AF_HYLINK     15
+#define GSS_C_AF_APPLETALK  16
+#define GSS_C_AF_BSC        17
+#define GSS_C_AF_DSS        18
+#define GSS_C_AF_OSI        19
+#define GSS_C_AF_X25        21
+
+#define GSS_C_AF_NULLADDR   255
+
+/* Various Null values */
+#define GSS_C_NO_NAME ((gss_name_t) 0)
+#define GSS_C_NO_BUFFER ((gss_buffer_t) 0)
+#define GSS_C_NO_OID ((gss_OID) 0)
+#define GSS_C_NO_OID_SET ((gss_OID_set) 0)
+#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) 0)
+#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0)
+#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0)
+#define GSS_C_EMPTY_BUFFER {0, NULL}
+
+/* Major status codes */
+#define GSS_S_COMPLETE 0
+
+/* Some "helper" definitions to make the status code macros obvious. */
+#define GSS_C_CALLING_ERROR_OFFSET 24
+#define GSS_C_ROUTINE_ERROR_OFFSET 16
+
+#define GSS_C_SUPPLEMENTARY_OFFSET 0
+#define GSS_C_CALLING_ERROR_MASK 0377ul
+#define GSS_C_ROUTINE_ERROR_MASK 0377ul
+#define GSS_C_SUPPLEMENTARY_MASK 0177777ul
+
+/*
+ * The macros that test status codes for error conditions.
+ * Note that the GSS_ERROR() macro has changed slightly from
+ * the V1 GSS-API so that it now evaluates its argument
+ * only once.
+ */
+#define GSS_CALLING_ERROR(x)                                            \
+    (x & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET))
+#define GSS_ROUTINE_ERROR(x)                                            \
+    (x & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))
+#define GSS_SUPPLEMENTARY_INFO(x)                                       \
+    (x & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET))
+#define GSS_ERROR(x)                                                    \
+    (x & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) |    \
+          (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)))
+
+/* Now the actual status code definitions */
+
+/* Calling errors: */
+#define GSS_S_CALL_INACCESSIBLE_READ            \
+    (1ul << GSS_C_CALLING_ERROR_OFFSET)
+#define GSS_S_CALL_INACCESSIBLE_WRITE           \
+    (2ul << GSS_C_CALLING_ERROR_OFFSET)
+#define GSS_S_CALL_BAD_STRUCTURE                \
+    (3ul << GSS_C_CALLING_ERROR_OFFSET)
+
+/* Routine errors: */
+#define GSS_S_BAD_MECH             (1ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_NAME             (2ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_NAMETYPE         (3ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_BINDINGS         (4ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_STATUS           (5ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_SIG              (6ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_MIC GSS_S_BAD_SIG
+#define GSS_S_NO_CRED              (7ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_NO_CONTEXT           (8ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DEFECTIVE_TOKEN      (9ul <<                      \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DEFECTIVE_CREDENTIAL (10ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_CREDENTIALS_EXPIRED  (11ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_CONTEXT_EXPIRED      (12ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_FAILURE              (13ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_QOP              (14ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_UNAUTHORIZED         (15ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_UNAVAILABLE          (16ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DUPLICATE_ELEMENT    (17ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_NAME_NOT_MN          (18ul <<                     \
+                                    GSS_C_ROUTINE_ERROR_OFFSET)
+
+/* Supplementary info bits: */
+#define GSS_S_CONTINUE_NEEDED                                           \
+                           (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 0))
+#define GSS_S_DUPLICATE_TOKEN                                           \
+                           (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 1))
+#define GSS_S_OLD_TOKEN                                                 \
+                           (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 2))
+#define GSS_S_UNSEQ_TOKEN                                               \
+                           (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 3))
+#define GSS_S_GAP_TOKEN                                                 \
+                           (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 4))
+
+extern const_gss_OID GSS_C_NT_USER_NAME;
+extern const_gss_OID GSS_C_NT_MACHINE_UID_NAME;
+extern const_gss_OID GSS_C_NT_STRING_UID_NAME;
+extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X;
+extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE;
+extern const_gss_OID GSS_C_NT_ANONYMOUS;
+extern const_gss_OID GSS_C_NT_EXPORT_NAME;
+
+#endif /* STATIC_GSSAPI */
+
+extern const gss_OID GSS_MECH_KRB5;
+
+/* GSSAPI functions we use.
+ * TODO: Replace with all GSSAPI functions from RFC?
+ */
+
+/* Calling convention, just in case we need one. */
+#ifndef GSS_CC
+#define GSS_CC
+#endif /*GSS_CC*/
+
+typedef OM_uint32 (GSS_CC *t_gss_release_cred)
+            (OM_uint32                    * /*minor_status*/,
+             gss_cred_id_t                * /*cred_handle*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_init_sec_context)
+            (OM_uint32                    * /*minor_status*/,
+             const gss_cred_id_t            /*initiator_cred_handle*/,
+             gss_ctx_id_t                 * /*context_handle*/,
+             const gss_name_t               /*target_name*/,
+             const gss_OID                  /*mech_type*/,
+             OM_uint32                      /*req_flags*/,
+             OM_uint32                      /*time_req*/,
+             const gss_channel_bindings_t   /*input_chan_bindings*/,
+             const gss_buffer_t             /*input_token*/,
+             gss_OID                      * /*actual_mech_type*/,
+             gss_buffer_t                   /*output_token*/,
+             OM_uint32                    * /*ret_flags*/,
+             OM_uint32                    * /*time_rec*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_delete_sec_context)
+            (OM_uint32                    * /*minor_status*/,
+             gss_ctx_id_t                 * /*context_handle*/,
+             gss_buffer_t                   /*output_token*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_get_mic)
+            (OM_uint32                    * /*minor_status*/,
+             const gss_ctx_id_t             /*context_handle*/,
+             gss_qop_t                      /*qop_req*/,
+             const gss_buffer_t             /*message_buffer*/,
+             gss_buffer_t                   /*msg_token*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_verify_mic)
+            (OM_uint32                    * /*minor_status*/,
+             const gss_ctx_id_t             /*context_handle*/,
+             const gss_buffer_t             /*message_buffer*/,
+             const gss_buffer_t             /*msg_token*/,
+             gss_qop_t                    * /*qop_state*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_display_status)
+            (OM_uint32                   * /*minor_status*/,
+             OM_uint32                     /*status_value*/,
+             int                           /*status_type*/,
+             const gss_OID                 /*mech_type*/,
+             OM_uint32                   * /*message_context*/,
+             gss_buffer_t                  /*status_string*/);
+
+
+typedef OM_uint32 (GSS_CC *t_gss_import_name)
+            (OM_uint32                   * /*minor_status*/,
+             const gss_buffer_t            /*input_name_buffer*/,
+             const_gss_OID                 /*input_name_type*/,
+             gss_name_t                  * /*output_name*/);
+
+
+typedef OM_uint32 (GSS_CC *t_gss_release_name)
+            (OM_uint32                   * /*minor_status*/,
+             gss_name_t                  * /*name*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_release_buffer)
+            (OM_uint32                   * /*minor_status*/,
+             gss_buffer_t                  /*buffer*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_acquire_cred)
+            (OM_uint32                    * /*minor_status*/,
+             const gss_name_t               /*desired_name*/,
+             OM_uint32                      /*time_req*/,
+             const gss_OID_set              /*desired_mechs*/,
+             gss_cred_usage_t               /*cred_usage*/,
+             gss_cred_id_t                * /*output_cred_handle*/,
+             gss_OID_set                  * /*actual_mechs*/,
+             OM_uint32                    * /*time_rec*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_inquire_cred_by_mech)
+            (OM_uint32                    * /*minor_status*/,
+             const gss_cred_id_t            /*cred_handle*/,
+             const gss_OID                  /*mech_type*/,
+             gss_name_t                   * /*name*/,
+             OM_uint32                    * /*initiator_lifetime*/,
+             OM_uint32                    * /*acceptor_lifetime*/,
+             gss_cred_usage_t             * /*cred_usage*/);
+
+struct gssapi_functions {
+    t_gss_delete_sec_context delete_sec_context;
+    t_gss_display_status display_status;
+    t_gss_get_mic get_mic;
+    t_gss_verify_mic verify_mic;
+    t_gss_import_name import_name;
+    t_gss_init_sec_context init_sec_context;
+    t_gss_release_buffer release_buffer;
+    t_gss_release_cred release_cred;
+    t_gss_release_name release_name;
+    t_gss_acquire_cred acquire_cred;
+    t_gss_inquire_cred_by_mech inquire_cred_by_mech;
+};
+
+#endif /* NO_GSSAPI */
+
+#endif /* PUTTY_PGSSAPI_H */

+ 1225 - 0
source/putty/ssh/portfwd.c

@@ -0,0 +1,1225 @@
+/*
+ * SSH port forwarding.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "channel.h"
+#include "proxy/socks.h"
+
+/*
+ * Enumeration of values that live in the 'socks_state' field of
+ * struct PortForwarding.
+ */
+typedef enum {
+    SOCKS_NONE, /* direct connection (no SOCKS, or SOCKS already done) */
+    SOCKS_INITIAL,       /* don't know if we're SOCKS 4 or 5 yet */
+    SOCKS_4,             /* expect a SOCKS 4 (or 4A) connection message */
+    SOCKS_5_INITIAL,     /* expect a SOCKS 5 preliminary message */
+    SOCKS_5_CONNECT      /* expect a SOCKS 5 connection message */
+} SocksState;
+
+typedef struct PortForwarding {
+    SshChannel *c;         /* channel structure held by SSH connection layer */
+    ConnectionLayer *cl;   /* the connection layer itself */
+    /* Note that ssh need not be filled in if c is non-NULL */
+    Socket *s;
+    bool input_wanted;
+    bool ready;
+    SocksState socks_state;
+    /*
+     * `hostname' and `port' are the real hostname and port, once
+     * we know what we're connecting to.
+     */
+    char *hostname;
+    int port;
+    /*
+     * `socksbuf' is the buffer we use to accumulate the initial SOCKS
+     * segment of the incoming data, plus anything after that that we
+     * receive before we're ready to send data to the SSH server.
+     */
+    strbuf *socksbuf;
+    size_t socksbuf_consumed;
+
+    Plug plug;
+    Channel chan;
+} PortForwarding;
+
+struct PortListener {
+    ConnectionLayer *cl;
+    Socket *s;
+    bool is_dynamic;
+    /*
+     * `hostname' and `port' are the real hostname and port, for
+     * ordinary forwardings.
+     */
+    char *hostname;
+    int port;
+
+    Plug plug;
+};
+
+static struct PortForwarding *new_portfwd_state(void)
+{
+    struct PortForwarding *pf = snew(struct PortForwarding);
+    pf->hostname = NULL;
+    pf->socksbuf = NULL;
+    return pf;
+}
+
+static void free_portfwd_state(struct PortForwarding *pf)
+{
+    if (!pf)
+        return;
+    sfree(pf->hostname);
+    if (pf->socksbuf)
+        strbuf_free(pf->socksbuf);
+    sfree(pf);
+}
+
+static struct PortListener *new_portlistener_state(void)
+{
+    struct PortListener *pl = snew(struct PortListener);
+    pl->hostname = NULL;
+    return pl;
+}
+
+static void free_portlistener_state(struct PortListener *pl)
+{
+    if (!pl)
+        return;
+    sfree(pl->hostname);
+    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)
+{
+    struct PortForwarding *pf =
+        container_of(plug, struct PortForwarding, plug);
+
+    if (type != PLUGCLOSE_NORMAL) {
+        /*
+         * Socket error. Slam the connection instantly shut.
+         */
+        if (pf->c) {
+            sshfwd_initiate_close(pf->c, error_msg);
+        } else {
+            /*
+             * We might not have an SSH channel, if a socket error
+             * occurred during SOCKS negotiation. If not, we must
+             * clean ourself up without sshfwd_initiate_close's call
+             * back to pfd_close.
+             */
+            pfd_close(pf);
+        }
+    } else {
+        /*
+         * Ordinary EOF received on socket. Send an EOF on the SSH
+         * channel.
+         */
+        if (pf->c)
+            sshfwd_write_eof(pf->c);
+    }
+}
+
+static void pfl_terminate(struct PortListener *pl);
+
+static void pfl_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{
+    struct PortListener *pl = (struct PortListener *) plug;
+    pfl_terminate(pl);
+}
+
+static SshChannel *wrap_lportfwd_open(
+    ConnectionLayer *cl, const char *hostname, int port,
+    Socket *s, Channel *chan)
+{
+    SocketPeerInfo *pi;
+    char *description;
+    SshChannel *toret;
+
+    pi = sk_peer_info(s);
+    if (pi && pi->log_text) {
+        description = dupprintf("forwarding from %s", pi->log_text);
+    } else {
+        description = dupstr("forwarding");
+    }
+    toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan);
+    sk_free_peer_info(pi);
+
+    sfree(description);
+    return toret;
+}
+
+static char *ipv4_to_string(unsigned ipv4)
+{
+    return dupprintf("%u.%u.%u.%u",
+                     (ipv4 >> 24) & 0xFF, (ipv4 >> 16) & 0xFF,
+                     (ipv4 >>  8) & 0xFF, (ipv4      ) & 0xFF);
+}
+
+static char *ipv6_to_string(ptrlen ipv6)
+{
+    const unsigned char *addr = ipv6.ptr;
+    assert(ipv6.len == 16);
+    return dupprintf("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
+                     (unsigned)GET_16BIT_MSB_FIRST(addr + 0),
+                     (unsigned)GET_16BIT_MSB_FIRST(addr + 2),
+                     (unsigned)GET_16BIT_MSB_FIRST(addr + 4),
+                     (unsigned)GET_16BIT_MSB_FIRST(addr + 6),
+                     (unsigned)GET_16BIT_MSB_FIRST(addr + 8),
+                     (unsigned)GET_16BIT_MSB_FIRST(addr + 10),
+                     (unsigned)GET_16BIT_MSB_FIRST(addr + 12),
+                     (unsigned)GET_16BIT_MSB_FIRST(addr + 14));
+}
+
+static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+    struct PortForwarding *pf =
+        container_of(plug, struct PortForwarding, plug);
+
+    if (len == 0)
+        return;
+
+    if (pf->socks_state != SOCKS_NONE) {
+        BinarySource src[1];
+
+        /*
+         * Store all the data we've got in socksbuf.
+         */
+        put_data(pf->socksbuf, data, len);
+
+        /*
+         * Check the start of socksbuf to see if it's a valid and
+         * complete message in the SOCKS exchange.
+         */
+
+        if (pf->socks_state == SOCKS_INITIAL) {
+            /* Preliminary: check the first byte of the data (which we
+             * _must_ have by now) to find out which SOCKS major
+             * version we're speaking. */
+            switch (pf->socksbuf->u[0]) {
+              case SOCKS4_REQUEST_VERSION:
+                pf->socks_state = SOCKS_4;
+                break;
+              case SOCKS5_REQUEST_VERSION:
+                pf->socks_state = SOCKS_5_INITIAL;
+                break;
+              default:
+                pfd_close(pf);         /* unrecognised version */
+                return;
+            }
+        }
+
+        BinarySource_BARE_INIT(src, pf->socksbuf->u, pf->socksbuf->len);
+        get_data(src, pf->socksbuf_consumed);
+
+        while (pf->socks_state != SOCKS_NONE) {
+            unsigned socks_version, message_type, reserved_byte;
+            unsigned reply_code, port, ipv4, method;
+            ptrlen methods;
+            const char *socks4_hostname;
+            strbuf *output;
+
+            switch (pf->socks_state) {
+              case SOCKS_INITIAL:
+              case SOCKS_NONE:
+                unreachable("These case values cannot appear");
+
+              case SOCKS_4:
+                /* SOCKS 4/4A connect message */
+                socks_version = get_byte(src);
+                message_type = get_byte(src);
+
+                if (get_err(src) == BSE_OUT_OF_DATA)
+                    return;
+                if (socks_version == SOCKS4_REQUEST_VERSION &&
+                    message_type == SOCKS_CMD_CONNECT) {
+                    /* CONNECT message */
+                    bool name_based = false;
+
+                    port = get_uint16(src);
+                    ipv4 = get_uint32(src);
+                    if (ipv4 >= SOCKS4A_NAME_FOLLOWS_BASE &&
+                        ipv4 < SOCKS4A_NAME_FOLLOWS_LIMIT) {
+                        /*
+                         * Addresses in this range indicate the SOCKS 4A
+                         * extension to specify a hostname, which comes
+                         * after the username.
+                         */
+                        name_based = true;
+                    }
+                    get_asciz(src);        /* skip username */
+                    socks4_hostname = name_based ? get_asciz(src) : NULL;
+
+                    if (get_err(src) == BSE_OUT_OF_DATA)
+                        return;
+                    if (get_err(src))
+                        goto socks4_reject;
+
+                    pf->port = port;
+                    if (name_based) {
+                        pf->hostname = dupstr(socks4_hostname);
+                    } else {
+                        pf->hostname = ipv4_to_string(ipv4);
+                    }
+
+                    output = strbuf_new();
+                    put_byte(output, SOCKS4_REPLY_VERSION);
+                    put_byte(output, SOCKS4_RESP_SUCCESS);
+                    put_uint16(output, 0);     /* null port field */
+                    put_uint32(output, 0);     /* null address field */
+                    sk_write(pf->s, output->u, output->len);
+                    strbuf_free(output);
+
+                    pf->socks_state = SOCKS_NONE;
+                    pf->socksbuf_consumed = src->pos;
+                    break;
+                }
+
+              socks4_reject:
+                output = strbuf_new();
+                put_byte(output, SOCKS4_REPLY_VERSION);
+                put_byte(output, SOCKS4_RESP_FAILURE);
+                put_uint16(output, 0);     /* null port field */
+                put_uint32(output, 0);     /* null address field */
+                sk_write(pf->s, output->u, output->len);
+                strbuf_free(output);
+                pfd_close(pf);
+                return;
+
+              case SOCKS_5_INITIAL:
+                /* SOCKS 5 initial method list */
+                socks_version = get_byte(src);
+                methods = get_pstring(src);
+
+                method = SOCKS5_AUTH_REJECTED;
+
+                /* Search the method list for AUTH_NONE, which is the
+                 * only one this client code can speak */
+                { // WINSCP
+                size_t i;
+                for (i = 0; i < methods.len; i++) {
+                    unsigned char this_method =
+                        ((const unsigned char *)methods.ptr)[i];
+                    if (this_method == SOCKS5_AUTH_NONE) {
+                        method = this_method;
+                        break;
+                    }
+                }
+
+                if (get_err(src) == BSE_OUT_OF_DATA)
+                    return;
+                if (get_err(src))
+                    method = SOCKS5_AUTH_REJECTED;
+
+                output = strbuf_new();
+                put_byte(output, SOCKS5_REPLY_VERSION);
+                put_byte(output, method);
+                sk_write(pf->s, output->u, output->len);
+                strbuf_free(output);
+
+                if (method == SOCKS5_AUTH_REJECTED) {
+                    pfd_close(pf);
+                    return;
+                }
+
+                pf->socks_state = SOCKS_5_CONNECT;
+                pf->socksbuf_consumed = src->pos;
+                break;
+                } // WINSCP
+
+              case SOCKS_5_CONNECT:
+                /* SOCKS 5 connect message */
+                socks_version = get_byte(src);
+                message_type = get_byte(src);
+                reserved_byte = get_byte(src);
+
+                if (socks_version == SOCKS5_REQUEST_VERSION &&
+                    message_type == SOCKS_CMD_CONNECT &&
+                    reserved_byte == 0) {
+
+                    reply_code = SOCKS5_RESP_SUCCESS;
+
+                    switch (get_byte(src)) {
+                      case SOCKS5_ADDR_IPV4:
+                        pf->hostname = ipv4_to_string(get_uint32(src));
+                        break;
+                      case SOCKS5_ADDR_IPV6:
+                        pf->hostname = ipv6_to_string(get_data(src, 16));
+                        break;
+                      case SOCKS5_ADDR_HOSTNAME:
+                        pf->hostname = mkstr(get_pstring(src));
+                        break;
+                      default:
+                        pf->hostname = NULL;
+                        reply_code = SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED;
+                        break;
+                    }
+
+                    pf->port = get_uint16(src);
+                } else {
+                    reply_code = SOCKS5_RESP_COMMAND_NOT_SUPPORTED;
+                }
+
+                if (get_err(src) == BSE_OUT_OF_DATA)
+                    return;
+                if (get_err(src))
+                    reply_code = SOCKS5_RESP_FAILURE;
+
+                output = strbuf_new();
+                put_byte(output, SOCKS5_REPLY_VERSION);
+                put_byte(output, reply_code);
+                put_byte(output, 0);       /* reserved */
+                put_byte(output, SOCKS5_ADDR_IPV4); /* IPv4 address follows */
+                put_uint32(output, 0);     /* bound IPv4 address (unused) */
+                put_uint16(output, 0);     /* bound port number (unused) */
+                sk_write(pf->s, output->u, output->len);
+                strbuf_free(output);
+
+                if (reply_code != SOCKS5_RESP_SUCCESS) {
+                    pfd_close(pf);
+                    return;
+                }
+
+                pf->socks_state = SOCKS_NONE;
+                pf->socksbuf_consumed = src->pos;
+                break;
+            }
+        }
+
+        /*
+         * We come here when we're ready to make an actual
+         * connection.
+         */
+
+        /*
+         * Freeze the socket until the SSH server confirms the
+         * connection.
+         */
+        sk_set_frozen(pf->s, true);
+
+        pf->c = wrap_lportfwd_open(pf->cl, pf->hostname, pf->port, pf->s,
+                                   &pf->chan);
+    }
+    if (pf->ready)
+        sshfwd_write(pf->c, data, len);
+}
+
+static void pfd_sent(Plug *plug, size_t bufsize)
+{
+    struct PortForwarding *pf =
+        container_of(plug, struct PortForwarding, plug);
+
+    if (pf->c)
+        sshfwd_unthrottle(pf->c, bufsize);
+}
+
+static const PlugVtable PortForwarding_plugvt = {
+    // WINSCP
+    /*.log =*/ pfd_log,
+    /*.closing =*/ pfd_closing,
+    /*.receive =*/ pfd_receive,
+    /*.sent =*/ pfd_sent,
+    NULL,
+};
+
+static void pfd_chan_free(Channel *chan);
+static void pfd_open_confirmation(Channel *chan);
+static void pfd_open_failure(Channel *chan, const char *errtext);
+static size_t pfd_send(
+    Channel *chan, bool is_stderr, const void *data, size_t len);
+static void pfd_send_eof(Channel *chan);
+static void pfd_set_input_wanted(Channel *chan, bool wanted);
+static char *pfd_log_close_msg(Channel *chan);
+
+static const ChannelVtable PortForwarding_channelvt = {
+    // WINSCP
+    /*.free =*/ pfd_chan_free,
+    /*.open_confirmation =*/ pfd_open_confirmation,
+    /*.open_failed =*/ pfd_open_failure,
+    /*.send =*/ pfd_send,
+    /*.send_eof =*/ pfd_send_eof,
+    /*.set_input_wanted =*/ pfd_set_input_wanted,
+    /*.log_close_msg =*/ pfd_log_close_msg,
+    /*.want_close =*/ chan_default_want_close,
+    /*.rcvd_exit_status =*/ chan_no_exit_status,
+    /*.rcvd_exit_signal =*/ chan_no_exit_signal,
+    /*.rcvd_exit_signal_numeric =*/ chan_no_exit_signal_numeric,
+    /*.run_shell =*/ chan_no_run_shell,
+    /*.run_command =*/ chan_no_run_command,
+    /*.run_subsystem =*/ chan_no_run_subsystem,
+    /*.enable_x11_forwarding =*/ chan_no_enable_x11_forwarding,
+    /*.enable_agent_forwarding =*/ chan_no_enable_agent_forwarding,
+    /*.allocate_pty =*/ chan_no_allocate_pty,
+    /*.set_env =*/ chan_no_set_env,
+    /*.send_break =*/ chan_no_send_break,
+    /*.send_signal =*/ chan_no_send_signal,
+    /*.change_window_size =*/ chan_no_change_window_size,
+    /*.request_response =*/ chan_no_request_response,
+};
+
+Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready)
+{
+    struct PortForwarding *pf;
+
+    pf = new_portfwd_state();
+    pf->plug.vt = &PortForwarding_plugvt;
+    pf->chan.initial_fixed_window_size = 0;
+    pf->chan.vt = &PortForwarding_channelvt;
+    pf->input_wanted = true;
+
+    pf->c = NULL;
+
+    pf->cl = cl;
+    pf->input_wanted = true;
+    pf->ready = start_ready;
+
+    pf->socks_state = SOCKS_NONE;
+    pf->hostname = NULL;
+    pf->port = 0;
+
+    *plug = &pf->plug;
+    return &pf->chan;
+}
+
+void portfwd_raw_free(Channel *pfchan)
+{
+    struct PortForwarding *pf;
+    assert(pfchan->vt == &PortForwarding_channelvt);
+    pf = container_of(pfchan, struct PortForwarding, chan);
+    free_portfwd_state(pf);
+}
+
+void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc)
+{
+    struct PortForwarding *pf;
+    assert(pfchan->vt == &PortForwarding_channelvt);
+    pf = container_of(pfchan, struct PortForwarding, chan);
+
+    pf->s = s;
+    pf->c = sc;
+}
+
+/*
+ called when someone connects to the local port
+ */
+
+static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+    struct PortListener *pl = container_of(p, struct PortListener, plug);
+    struct PortForwarding *pf;
+    Channel *chan;
+    Plug *plug;
+    Socket *s;
+    const char *err;
+
+    chan = portfwd_raw_new(pl->cl, &plug, false);
+    s = constructor(ctx, plug);
+    if ((err = sk_socket_error(s)) != NULL) {
+        portfwd_raw_free(chan);
+        return 1;
+    }
+
+    pf = container_of(chan, struct PortForwarding, chan);
+
+    if (pl->is_dynamic) {
+        pf->s = s;
+        pf->socks_state = SOCKS_INITIAL;
+        pf->socksbuf = strbuf_new();
+        pf->socksbuf_consumed = 0;
+        pf->port = 0;                  /* "hostname" buffer is so far empty */
+        sk_set_frozen(s, false);       /* we want to receive SOCKS _now_! */
+    } else {
+        pf->hostname = dupstr(pl->hostname);
+        pf->port = pl->port;
+        portfwd_raw_setup(
+            chan, s,
+            wrap_lportfwd_open(pl->cl, pf->hostname, pf->port, s, &pf->chan));
+    }
+
+    return 0;
+}
+
+static const PlugVtable PortListener_plugvt = {
+    // WINSCP
+    /*.log =*/ pfl_log,
+    /*.closing =*/ pfl_closing,
+    NULL,
+    NULL,
+    /*.accepting =*/ pfl_accepting,
+};
+
+/*
+ * Add a new port-forwarding listener from srcaddr:port -> desthost:destport.
+ *
+ * desthost == NULL indicates dynamic SOCKS port forwarding.
+ *
+ * On success, returns NULL and fills in *pl_ret. On error, returns a
+ * dynamically allocated error message string.
+ */
+static char *pfl_listen(const char *desthost, int destport,
+                        const char *srcaddr, int port,
+                        ConnectionLayer *cl, Conf *conf,
+                        struct PortListener **pl_ret, int address_family)
+{
+    const char *err;
+    struct PortListener *pl;
+
+    /*
+     * Open socket.
+     */
+    pl = *pl_ret = new_portlistener_state();
+    pl->plug.vt = &PortListener_plugvt;
+    if (desthost) {
+        pl->hostname = dupstr(desthost);
+        pl->port = destport;
+        pl->is_dynamic = false;
+    } else
+        pl->is_dynamic = true;
+    pl->cl = cl;
+
+    pl->s = new_listener(srcaddr, port, &pl->plug,
+                         !conf_get_bool(conf, CONF_lport_acceptall),
+                         conf, address_family);
+    if ((err = sk_socket_error(pl->s)) != NULL) {
+        char *err_ret = dupstr(err);
+        sk_close(pl->s);
+        free_portlistener_state(pl);
+        *pl_ret = NULL;
+        return err_ret;
+    }
+
+    return NULL;
+}
+
+static char *pfd_log_close_msg(Channel *chan)
+{
+    return dupstr("Forwarded port closed");
+}
+
+static void pfd_close(struct PortForwarding *pf)
+{
+    if (!pf)
+        return;
+
+    sk_close(pf->s);
+    free_portfwd_state(pf);
+}
+
+/*
+ * Terminate a listener.
+ */
+static void pfl_terminate(struct PortListener *pl)
+{
+    if (!pl)
+        return;
+
+    sk_close(pl->s);
+    free_portlistener_state(pl);
+}
+
+static void pfd_set_input_wanted(Channel *chan, bool wanted)
+{
+    pinitassert(chan->vt == &PortForwarding_channelvt);
+    PortForwarding *pf = container_of(chan, PortForwarding, chan);
+    pf->input_wanted = wanted;
+    sk_set_frozen(pf->s, !pf->input_wanted);
+}
+
+static void pfd_chan_free(Channel *chan)
+{
+    pinitassert(chan->vt == &PortForwarding_channelvt);
+    PortForwarding *pf = container_of(chan, PortForwarding, chan);
+    pfd_close(pf);
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+static size_t pfd_send(
+    Channel *chan, bool is_stderr, const void *data, size_t len)
+{
+    pinitassert(chan->vt == &PortForwarding_channelvt);
+    PortForwarding *pf = container_of(chan, PortForwarding, chan);
+    return sk_write(pf->s, data, len);
+}
+
+static void pfd_send_eof(Channel *chan)
+{
+    pinitassert(chan->vt == &PortForwarding_channelvt);
+    PortForwarding *pf = container_of(chan, PortForwarding, chan);
+    sk_write_eof(pf->s);
+}
+
+static void pfd_open_confirmation(Channel *chan)
+{
+    pinitassert(chan->vt == &PortForwarding_channelvt);
+    PortForwarding *pf = container_of(chan, PortForwarding, chan);
+
+    pf->ready = true;
+    sk_set_frozen(pf->s, false);
+    sk_write(pf->s, NULL, 0);
+    if (pf->socksbuf) {
+        sshfwd_write(pf->c, pf->socksbuf->u + pf->socksbuf_consumed,
+                     pf->socksbuf->len - pf->socksbuf_consumed);
+        strbuf_free(pf->socksbuf);
+        pf->socksbuf = NULL;
+    }
+}
+
+static void pfd_open_failure(Channel *chan, const char *errtext)
+{
+    pinitassert(chan->vt == &PortForwarding_channelvt);
+    PortForwarding *pf = container_of(chan, PortForwarding, chan);
+
+    logeventf(pf->cl->logctx,
+              "Forwarded connection refused by remote%s%s",
+              errtext ? ": " : "", errtext ? errtext : "");
+}
+
+/* ----------------------------------------------------------------------
+ * Code to manage the complete set of currently active port
+ * forwardings, and update it from Conf.
+ */
+
+struct PortFwdRecord {
+    enum { DESTROY, KEEP, CREATE } status;
+    int type;
+    unsigned sport, dport;
+    char *saddr, *daddr;
+    char *sserv, *dserv;
+    struct ssh_rportfwd *remote;
+    int addressfamily;
+    struct PortListener *local;
+};
+
+static int pfr_cmp(void *av, void *bv)
+{
+    PortFwdRecord *a = (PortFwdRecord *) av;
+    PortFwdRecord *b = (PortFwdRecord *) bv;
+    int i;
+    if (a->type > b->type)
+        return +1;
+    if (a->type < b->type)
+        return -1;
+    if (a->addressfamily > b->addressfamily)
+        return +1;
+    if (a->addressfamily < b->addressfamily)
+        return -1;
+    if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0)
+        return i < 0 ? -1 : +1;
+    if (a->sport > b->sport)
+        return +1;
+    if (a->sport < b->sport)
+        return -1;
+    if (a->type != 'D') {
+        if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0)
+            return i < 0 ? -1 : +1;
+        if (a->dport > b->dport)
+            return +1;
+        if (a->dport < b->dport)
+            return -1;
+    }
+    return 0;
+}
+
+static void pfr_free(PortFwdRecord *pfr)
+{
+    /* Dispose of any listening socket. */
+    if (pfr->local)
+        pfl_terminate(pfr->local);
+
+    sfree(pfr->saddr);
+    sfree(pfr->daddr);
+    sfree(pfr->sserv);
+    sfree(pfr->dserv);
+    sfree(pfr);
+}
+
+struct PortFwdManager {
+    ConnectionLayer *cl;
+    Conf *conf;
+    tree234 *forwardings;
+};
+
+PortFwdManager *portfwdmgr_new(ConnectionLayer *cl)
+{
+    PortFwdManager *mgr = snew(PortFwdManager);
+
+    mgr->cl = cl;
+    mgr->conf = NULL;
+    mgr->forwardings = newtree234(pfr_cmp);
+
+    return mgr;
+}
+
+void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr)
+{
+    PortFwdRecord *realpfr = del234(mgr->forwardings, pfr);
+    if (realpfr == pfr)
+        pfr_free(pfr);
+}
+
+void portfwdmgr_close_all(PortFwdManager *mgr)
+{
+    PortFwdRecord *pfr;
+
+    while ((pfr = delpos234(mgr->forwardings, 0)) != NULL)
+        pfr_free(pfr);
+}
+
+void portfwdmgr_free(PortFwdManager *mgr)
+{
+    portfwdmgr_close_all(mgr);
+    freetree234(mgr->forwardings);
+    if (mgr->conf)
+        conf_free(mgr->conf);
+    sfree(mgr);
+}
+
+void portfwdmgr_config(PortFwdManager *mgr, Conf *conf)
+{
+    PortFwdRecord *pfr;
+    int i;
+    char *key, *val;
+
+    if (mgr->conf)
+        conf_free(mgr->conf);
+    mgr->conf = conf_copy(conf);
+
+    /*
+     * Go through the existing port forwardings and tag them
+     * with status==DESTROY. Any that we want to keep will be
+     * re-enabled (status==KEEP) as we go through the
+     * configuration and find out which bits are the same as
+     * they were before.
+     */
+    for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++)
+        pfr->status = DESTROY;
+
+    for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key);
+         val != NULL;
+         val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) {
+        char *kp, *kp2, *vp, *vp2;
+        char address_family, type;
+        int sport, dport, sserv, dserv;
+        char *sports, *dports, *saddr, *host;
+
+        kp = key;
+
+        address_family = 'A';
+        type = 'L';
+        if (*kp == 'A' || *kp == '4' || *kp == '6')
+            address_family = *kp++;
+        if (*kp == 'L' || *kp == 'R')
+            type = *kp++;
+
+        if ((kp2 = host_strchr(kp, ':')) != NULL) {
+            /*
+             * There's a colon in the middle of the source port
+             * string, which means that the part before it is
+             * actually a source address.
+             */
+            char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp);
+            saddr = host_strduptrim(saddr_tmp);
+            sfree(saddr_tmp);
+            sports = kp2+1;
+        } else {
+            saddr = NULL;
+            sports = kp;
+        }
+        sport = atoi(sports);
+        sserv = 0;
+        if (sport == 0) {
+            sserv = 1;
+            sport = net_service_lookup(sports);
+            if (!sport) {
+                logeventf(mgr->cl->logctx, "Service lookup failed for source"
+                          " port \"%s\"", sports);
+            }
+        }
+
+        if (type == 'L' && !strcmp(val, "D")) {
+            /* dynamic forwarding */
+            host = NULL;
+            dports = NULL;
+            dport = -1;
+            dserv = 0;
+            type = 'D';
+        } else {
+            /* ordinary forwarding */
+            vp = val;
+            vp2 = vp + host_strcspn(vp, ":");
+            host = dupprintf("%.*s", (int)(vp2 - vp), vp);
+            if (*vp2)
+                vp2++;
+            dports = vp2;
+            dport = atoi(dports);
+            dserv = 0;
+            if (dport == 0) {
+                dserv = 1;
+                dport = net_service_lookup(dports);
+                if (!dport) {
+                    logeventf(mgr->cl->logctx,
+                              "Service lookup failed for destination"
+                              " port \"%s\"", dports);
+                }
+            }
+        }
+
+        if (sport && dport) {
+            /* Set up a description of the source port. */
+            pfr = snew(PortFwdRecord);
+            pfr->type = type;
+            pfr->saddr = saddr;
+            pfr->sserv = sserv ? dupstr(sports) : NULL;
+            pfr->sport = sport;
+            pfr->daddr = host;
+            pfr->dserv = dserv ? dupstr(dports) : NULL;
+            pfr->dport = dport;
+            pfr->local = NULL;
+            pfr->remote = NULL;
+            pfr->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 :
+                                  address_family == '6' ? ADDRTYPE_IPV6 :
+                                  ADDRTYPE_UNSPEC);
+
+            { // WINSCP
+            PortFwdRecord *existing = add234(mgr->forwardings, pfr);
+            if (existing != pfr) {
+                if (existing->status == DESTROY) {
+                    /*
+                     * We already have a port forwarding up and running
+                     * with precisely these parameters. Hence, no need
+                     * to do anything; simply re-tag the existing one
+                     * as KEEP.
+                     */
+                    existing->status = KEEP;
+                }
+                /*
+                 * Anything else indicates that there was a duplicate
+                 * in our input, which we'll silently ignore.
+                 */
+                pfr_free(pfr);
+            } else {
+                pfr->status = CREATE;
+            }
+            } // WINSCP
+        } else {
+            sfree(saddr);
+            sfree(host);
+        }
+    }
+
+    /*
+     * Now go through and destroy any port forwardings which were
+     * not re-enabled.
+     */
+    for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) {
+        if (pfr->status == DESTROY) {
+            char *message;
+
+            message = dupprintf("%s port forwarding from %s%s%d",
+                                pfr->type == 'L' ? "local" :
+                                pfr->type == 'R' ? "remote" : "dynamic",
+                                pfr->saddr ? pfr->saddr : "",
+                                pfr->saddr ? ":" : "",
+                                pfr->sport);
+
+            if (pfr->type != 'D') {
+                char *msg2 = dupprintf("%s to %s:%d", message,
+                                       pfr->daddr, pfr->dport);
+                sfree(message);
+                message = msg2;
+            }
+
+            logeventf(mgr->cl->logctx, "Cancelling %s", message);
+            sfree(message);
+
+            /* pfr->remote or pfr->local may be NULL if setting up a
+             * forwarding failed. */
+            if (pfr->remote) {
+                /*
+                 * Cancel the port forwarding at the server
+                 * end.
+                 *
+                 * Actually closing the listening port on the server
+                 * side may fail - because in SSH-1 there's no message
+                 * in the protocol to request it!
+                 *
+                 * Instead, we simply remove the record of the
+                 * forwarding from our local end, so that any
+                 * connections the server tries to make on it are
+                 * rejected.
+                 */
+                ssh_rportfwd_remove(mgr->cl, pfr->remote);
+                pfr->remote = NULL;
+            } else if (pfr->local) {
+                pfl_terminate(pfr->local);
+                pfr->local = NULL;
+            }
+
+            delpos234(mgr->forwardings, i);
+            pfr_free(pfr);
+            i--;                       /* so we don't skip one in the list */
+        }
+    }
+
+    /*
+     * And finally, set up any new port forwardings (status==CREATE).
+     */
+    for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) {
+        if (pfr->status == CREATE) {
+            char *sportdesc, *dportdesc;
+            sportdesc = dupprintf("%s%s%s%s%d%s",
+                                  pfr->saddr ? pfr->saddr : "",
+                                  pfr->saddr ? ":" : "",
+                                  pfr->sserv ? pfr->sserv : "",
+                                  pfr->sserv ? "(" : "",
+                                  pfr->sport,
+                                  pfr->sserv ? ")" : "");
+            if (pfr->type == 'D') {
+                dportdesc = NULL;
+            } else {
+                dportdesc = dupprintf("%s:%s%s%d%s",
+                                      pfr->daddr,
+                                      pfr->dserv ? pfr->dserv : "",
+                                      pfr->dserv ? "(" : "",
+                                      pfr->dport,
+                                      pfr->dserv ? ")" : "");
+            }
+
+            if (pfr->type == 'L') {
+                char *err = pfl_listen(pfr->daddr, pfr->dport,
+                                       pfr->saddr, pfr->sport,
+                                       mgr->cl, conf, &pfr->local,
+                                       pfr->addressfamily);
+
+                logeventf(mgr->cl->logctx,
+                          "Local %sport %s forwarding to %s%s%s",
+                          pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+                          pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+                          sportdesc, dportdesc,
+                          err ? " failed: " : "", err ? err : "");
+                if (err)
+                    sfree(err);
+            } else if (pfr->type == 'D') {
+                char *err = pfl_listen(NULL, -1, pfr->saddr, pfr->sport,
+                                       mgr->cl, conf, &pfr->local,
+                                       pfr->addressfamily);
+
+                logeventf(mgr->cl->logctx,
+                          "Local %sport %s SOCKS dynamic forwarding%s%s",
+                          pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+                          pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+                          sportdesc,
+                          err ? " failed: " : "", err ? err : "");
+
+                if (err)
+                    sfree(err);
+            } else {
+                const char *shost;
+
+                if (pfr->saddr) {
+                    shost = pfr->saddr;
+                } else if (conf_get_bool(conf, CONF_rport_acceptall)) {
+                    shost = "";
+                } else {
+                    shost = "localhost";
+                }
+
+                pfr->remote = ssh_rportfwd_alloc(
+                    mgr->cl, shost, pfr->sport, pfr->daddr, pfr->dport,
+                    pfr->addressfamily, sportdesc, pfr, NULL);
+
+                if (!pfr->remote) {
+                    logeventf(mgr->cl->logctx,
+                              "Duplicate remote port forwarding to %s:%d",
+                              pfr->daddr, pfr->dport);
+                    pfr_free(pfr);
+                } else {
+                    logeventf(mgr->cl->logctx, "Requesting remote port %s"
+                              " forward to %s", sportdesc, dportdesc);
+                }
+            }
+            sfree(sportdesc);
+            sfree(dportdesc);
+        }
+    }
+}
+
+bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port,
+                       const char *keyhost, int keyport, Conf *conf)
+{
+    PortFwdRecord *pfr;
+
+    pfr = snew(PortFwdRecord);
+    pfr->type = 'L';
+    pfr->saddr = host ? dupstr(host) : NULL;
+    pfr->daddr = keyhost ? dupstr(keyhost) : NULL;
+    pfr->sserv = pfr->dserv = NULL;
+    pfr->sport = port;
+    pfr->dport = keyport;
+    pfr->local = NULL;
+    pfr->remote = NULL;
+    pfr->addressfamily = ADDRTYPE_UNSPEC;
+
+    { // WINSCP
+    PortFwdRecord *existing = add234(mgr->forwardings, pfr);
+    if (existing != pfr) {
+        /*
+         * We had this record already. Return failure.
+         */
+        pfr_free(pfr);
+        return false;
+    }
+    } // WINSCP
+
+    { // WINSCP
+    char *err = pfl_listen(keyhost, keyport, host, port,
+                           mgr->cl, conf, &pfr->local, pfr->addressfamily);
+    logeventf(mgr->cl->logctx,
+              "%s on port %s:%d to forward to client%s%s",
+              err ? "Failed to listen" : "Listening", host, port,
+              err ? ": " : "", err ? err : "");
+    if (err) {
+        sfree(err);
+        del234(mgr->forwardings, pfr);
+        pfr_free(pfr);
+        return false;
+    }
+    } // WINSCP
+
+    return true;
+}
+
+bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port)
+{
+    PortFwdRecord pfr_key;
+
+    pfr_key.type = 'L';
+    /* Safe to cast the const away here, because it will only be used
+     * by pfr_cmp, which won't write to the string */
+    pfr_key.saddr = pfr_key.daddr = (char *)host;
+    pfr_key.sserv = pfr_key.dserv = NULL;
+    pfr_key.sport = pfr_key.dport = port;
+    pfr_key.local = NULL;
+    pfr_key.remote = NULL;
+    pfr_key.addressfamily = ADDRTYPE_UNSPEC;
+
+    { // WINSCP
+    PortFwdRecord *pfr = del234(mgr->forwardings, &pfr_key);
+
+    if (!pfr)
+        return false;
+
+    logeventf(mgr->cl->logctx, "Closing listening port %s:%d", host, port);
+
+    pfr_free(pfr);
+    } // WINSCP
+    return true;
+}
+
+/*
+ * Called when receiving a PORT OPEN from the server to make a
+ * connection to a destination host.
+ *
+ * On success, returns NULL and fills in *pf_ret. On error, returns a
+ * dynamically allocated error message string.
+ */
+char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret,
+                         char *hostname, int port, SshChannel *c,
+                         int addressfamily)
+{
+    SockAddr *addr;
+    const char *err;
+    char *dummy_realhost = NULL;
+    struct PortForwarding *pf;
+
+    /*
+     * Try to find host.
+     */
+    addr = name_lookup(hostname, port, &dummy_realhost, mgr->conf,
+                       addressfamily, NULL, NULL);
+    if ((err = sk_addr_error(addr)) != NULL) {
+        char *err_ret = dupstr(err);
+        sk_addr_free(addr);
+        sfree(dummy_realhost);
+        return err_ret;
+    }
+
+    /*
+     * Open socket.
+     */
+    pf = new_portfwd_state();
+    *chan_ret = &pf->chan;
+    pf->plug.vt = &PortForwarding_plugvt;
+    pf->chan.initial_fixed_window_size = 0;
+    pf->chan.vt = &PortForwarding_channelvt;
+    pf->input_wanted = true;
+    pf->ready = true;
+    pf->c = c;
+    pf->cl = mgr->cl;
+    pf->socks_state = SOCKS_NONE;
+
+    pf->s = new_connection(addr, dummy_realhost, port,
+                           false, true, false, false, &pf->plug, mgr->conf,
+                           NULL);
+    sfree(dummy_realhost);
+    if ((err = sk_socket_error(pf->s)) != NULL) {
+        char *err_ret = dupstr(err);
+        sk_close(pf->s);
+        free_portfwd_state(pf);
+        *chan_ret = NULL;
+        return err_ret;
+    }
+
+    return NULL;
+}
+
+#ifdef MPEXT
+
+#include "puttyexp.h"
+
+int is_pfwd(Plug * plug)
+{
+  return
+    (plug->vt->closing == pfd_closing) ||
+    (plug->vt->closing == pfl_closing);
+}
+
+Seat * get_pfwd_seat(Plug * plug)
+{
+  LogContext * logctx;
+  if (plug->vt->closing == pfl_closing)
+  {
+    struct PortListener *pl = container_of(plug, struct PortListener, plug);
+    logctx = pl->cl->logctx;
+  }
+  else if (plug->vt->closing == pfd_closing)
+  {
+    struct PortForwarding *pf = container_of(plug, struct PortForwarding, plug);
+    logctx = pf->cl->logctx;
+  }
+  return get_log_seat(logctx);
+}
+
+#endif

+ 179 - 0
source/putty/ssh/ppl.h

@@ -0,0 +1,179 @@
+/*
+ * Abstraction of the various layers of SSH packet-level protocol,
+ * general enough to take in all three of the main SSH-2 layers and
+ * both of the SSH-1 phases.
+ */
+
+#ifndef PUTTY_SSHPPL_H
+#define PUTTY_SSHPPL_H
+
+typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin);
+typedef struct PacketProtocolLayerVtable PacketProtocolLayerVtable;
+
+struct PacketProtocolLayerVtable {
+    void (*free)(PacketProtocolLayer *);
+    void (*process_queue)(PacketProtocolLayer *ppl);
+    bool (*get_specials)(
+        PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+    void (*special_cmd)(
+        PacketProtocolLayer *ppl, SessionSpecialCode code, int arg);
+    void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf);
+    size_t (*queued_data_size)(PacketProtocolLayer *ppl);
+
+    /* Protocol-level name of this layer. */
+    const char *name;
+
+    unsigned int (*winscp_query)(PacketProtocolLayer *ppl, int query);
+};
+
+struct PacketProtocolLayer {
+    const struct PacketProtocolLayerVtable *vt;
+
+    /* Link to the underlying SSH BPP. */
+    BinaryPacketProtocol *bpp;
+
+    /* Queue from which the layer receives its input packets, and one
+     * to put its output packets on. */
+    PktInQueue *in_pq;
+    PktOutQueue *out_pq;
+
+    /* Idempotent callback that in_pq will be linked to, causing a
+     * call to the process_queue method. in_pq points to this, so it
+     * will be automatically triggered by pushing things on the
+     * layer's input queue, but it can also be triggered on purpose. */
+    IdempotentCallback ic_process_queue;
+
+    /* Owner's pointer to this layer. Permits a layer to unilaterally
+     * abdicate in favour of a replacement, by overwriting this
+     * pointer and then freeing itself. */
+    PacketProtocolLayer **selfptr;
+
+    /* Logging and error-reporting facilities. */
+    LogContext *logctx;
+    Seat *seat;             /* for dialog boxes, session output etc */
+    Interactor *interactor; /* for ppl_get_iseat */
+    Ssh *ssh;   /* for session termination + assorted connection-layer ops */
+
+    /* Known bugs in the remote implementation. */
+    unsigned remote_bugs;
+};
+
+static inline void ssh_ppl_process_queue(PacketProtocolLayer *ppl)
+{ ppl->vt->process_queue(ppl); }
+static inline bool ssh_ppl_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{ return ppl->vt->get_specials(ppl, add_special, ctx); }
+static inline void ssh_ppl_special_cmd(
+    PacketProtocolLayer *ppl, SessionSpecialCode code, int arg)
+{ ppl->vt->special_cmd(ppl, code, arg); }
+static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{ ppl->vt->reconfigure(ppl, conf); }
+static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl)
+{ return ppl->vt->queued_data_size(ppl); }
+static inline unsigned int ssh_ppl_winscp_query(PacketProtocolLayer *ppl, int query)
+{ return ppl->vt->winscp_query(ppl, query); }
+
+static inline InteractionReadySeat ppl_get_iseat(PacketProtocolLayer *ppl)
+{ return interactor_announce(ppl->interactor); }
+
+/* ssh_ppl_free is more than just a macro wrapper on the vtable; it
+ * does centralised parts of the freeing too. */
+void ssh_ppl_free(PacketProtocolLayer *ppl);
+
+/* Helper routine to point a PPL at its input and output queues. Also
+ * sets up the IdempotentCallback on the input queue to trigger a call
+ * to process_queue whenever packets are added to it. */
+void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,
+                          PktInQueue *inq, PktOutQueue *outq);
+
+/* Routine a PPL can call to abdicate in favour of a replacement, by
+ * overwriting ppl->selfptr. Has the side effect of freeing 'old', so
+ * if 'old' actually called this (which is likely) then it should
+ * avoid dereferencing itself on return from this function! */
+void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new);
+
+/* Default implementation of queued_data_size, which just adds up the
+ * sizes of all the packets in pq_out. A layer can override this if it
+ * has other things to take into account as well. */
+size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl);
+
+PacketProtocolLayer *ssh1_login_new(
+    Conf *conf, const char *host, int port,
+    PacketProtocolLayer *successor_layer);
+PacketProtocolLayer *ssh1_connection_new(
+    Ssh *ssh, Conf *conf, bufchain *user_input, ConnectionLayer **cl_out);
+
+struct DataTransferStats;
+struct ssh_connection_shared_gss_state;
+PacketProtocolLayer *ssh2_transport_new(
+    Conf *conf, const char *host, int port, const char *fullhostname,
+    const char *client_greeting, const char *server_greeting,
+    struct ssh_connection_shared_gss_state *shgss,
+    struct DataTransferStats *stats, PacketProtocolLayer *higher_layer,
+    const SshServerConfig *ssc);
+PacketProtocolLayer *ssh2_userauth_new(
+    PacketProtocolLayer *successor_layer,
+    const char *hostname, const char *fullhostname,
+    Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth,
+    const char *default_username, bool change_username,
+    bool try_ki_auth,
+    bool try_gssapi_auth, bool try_gssapi_kex_auth,
+    bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss,
+    const char * loghost, bool change_password, Seat *seat); // WINSCP
+PacketProtocolLayer *ssh2_connection_new(
+    Ssh *ssh, ssh_sharing_state *connshare, bool is_simple,
+    Conf *conf, const char *peer_verstring, bufchain *user_input,
+    ConnectionLayer **cl_out);
+
+/* Can't put this in the userauth constructor without having a
+ * dependency loop at setup time (transport and userauth can't _both_
+ * be constructed second and given a pointer to the other). */
+void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth,
+                                       PacketProtocolLayer *transport);
+
+/* Convenience macro for protocol layers to send formatted strings to
+ * the Event Log. Assumes a function parameter called 'ppl' is in
+ * scope. */
+#define ppl_logevent(...) ( \
+        logevent_and_free((ppl)->logctx, dupprintf(__VA_ARGS__)))
+
+/* Convenience macro for protocol layers to send formatted strings to
+ * the terminal. Also expects 'ppl' to be in scope. */
+#define ppl_printf(...) \
+    ssh_ppl_user_output_string_and_free(ppl, dupprintf(__VA_ARGS__))
+void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text);
+
+/* Methods for userauth to communicate back to the transport layer */
+ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr);
+void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr);
+
+/* Shared method between ssh2 layers (defined in transport2.c) to
+ * handle the common packets between login and connection: DISCONNECT,
+ * DEBUG and IGNORE. Those messages are handled by the ssh2transport
+ * layer if we have one, but in bare ssh2-connection mode they have to
+ * be handled by ssh2connection. */
+bool ssh2_common_filter_queue(PacketProtocolLayer *ppl);
+
+/* Method for making a prompts_t in such a way that it will install a
+ * callback that causes this PPL's process_queue method to be called
+ * when asynchronous prompt input completes. */
+prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl);
+
+/* Methods for ssh1login to pass protocol flags to ssh1connection */
+void ssh1_connection_set_protoflags(
+    PacketProtocolLayer *ppl, int local, int remote);
+
+/* Shared get_specials method between the two ssh1 layers */
+bool ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *);
+
+/* Other shared functions between ssh1 layers  */
+bool ssh1_common_filter_queue(PacketProtocolLayer *ppl);
+void ssh1_compute_session_id(
+    unsigned char *session_id, const unsigned char *cookie,
+    RSAKey *hostkey, RSAKey *servkey);
+
+/* Method used by the SSH server */
+void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ssh2_transport_ptr,
+                                     ssh_key *const *hostkeys, int nhostkeys);
+
+#endif /* PUTTY_SSHPPL_H */

+ 2190 - 0
source/putty/ssh/sharing.c

@@ -0,0 +1,2190 @@
+/*
+ * Support for SSH connection sharing, i.e. permitting one PuTTY to
+ * open its own channels over the SSH session being run by another.
+ */
+
+/*
+ * Discussion and technical documentation
+ * ======================================
+ *
+ * The basic strategy for PuTTY's implementation of SSH connection
+ * sharing is to have a single 'upstream' PuTTY process, which manages
+ * the real SSH connection and all the cryptography, and then zero or
+ * more 'downstream' PuTTYs, which never talk to the real host but
+ * only talk to the upstream through local IPC (Unix-domain sockets or
+ * Windows named pipes).
+ *
+ * The downstreams communicate with the upstream using a protocol
+ * derived from SSH itself, which I'll document in detail below. In
+ * brief, though: the downstream->upstream protocol uses a trivial
+ * binary packet protocol (just length/type/data) to encapsulate
+ * unencrypted SSH messages, and downstreams talk to the upstream more
+ * or less as if it was an SSH server itself. (So downstreams can
+ * themselves open multiple SSH channels, for example, by sending
+ * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of
+ * their choice within each channel, and they handle their own
+ * WINDOW_ADJUST messages.)
+ *
+ * The upstream would ideally handle these downstreams by just putting
+ * their messages into the queue for proper SSH-2 encapsulation and
+ * encryption and sending them straight on to the server. However,
+ * that's not quite feasible as written, because client-side channel
+ * IDs could easily conflict (between multiple downstreams, or between
+ * a downstream and the upstream). To protect against that, the
+ * upstream rewrites the client-side channel IDs in messages it passes
+ * on to the server, so that it's performing what you might describe
+ * as 'channel-number NAT'. Then the upstream remembers which of its
+ * own channel IDs are channels it's managing itself, and which are
+ * placeholders associated with a particular downstream, so that when
+ * replies come in from the server they can be sent on to the relevant
+ * downstream (after un-NATting the channel number, of course).
+ *
+ * Global requests from downstreams are only accepted if the upstream
+ * knows what to do about them; currently the only such requests are
+ * the ones having to do with remote-to-local port forwarding (in
+ * which, again, the upstream remembers that some of the forwardings
+ * it's asked the server to set up were on behalf of particular
+ * downstreams, and sends the incoming CHANNEL_OPENs to those
+ * downstreams when connections come in).
+ *
+ * Other fiddly pieces of this mechanism are X forwarding and
+ * (OpenSSH-style) agent forwarding. Both of these have a fundamental
+ * problem arising from the protocol design: that the CHANNEL_OPEN
+ * from the server introducing a forwarded connection does not carry
+ * any indication of which session channel gave rise to it; so if
+ * session channels from multiple downstreams enable those forwarding
+ * methods, it's hard for the upstream to know which downstream to
+ * send the resulting connections back to.
+ *
+ * For X forwarding, we can work around this in a really painful way
+ * by using the fake X11 authorisation data sent to the server as part
+ * of the forwarding setup: upstream ensures that every X forwarding
+ * request carries distinguishable fake auth data, and then when X
+ * connections come in it waits to see the auth data in the X11 setup
+ * message before it decides which downstream to pass the connection
+ * on to.
+ *
+ * For agent forwarding, that workaround is unavailable. As a result,
+ * this system (and, as far as I can think of, any other system too)
+ * has the fundamental constraint that it can only forward one SSH
+ * agent - it can't forward two agents to different session channels.
+ * So downstreams can request agent forwarding if they like, but if
+ * they do, they'll get whatever SSH agent is known to the upstream
+ * (if any) forwarded to their sessions.
+ *
+ * Downstream-to-upstream protocol
+ * -------------------------------
+ *
+ * Here I document in detail the protocol spoken between PuTTY
+ * downstreams and upstreams over local IPC. The IPC mechanism can
+ * vary between host platforms, but the protocol is the same.
+ *
+ * The protocol commences with a version exchange which is exactly
+ * like the SSH-2 one, in that each side sends a single line of text
+ * of the form
+ *
+ *   <protocol>-<version>-<softwareversion> [comments] \r\n
+ *
+ * The only difference is that in real SSH-2, <protocol> is the string
+ * "SSH", whereas in this protocol the string is
+ * "[email protected]".
+ *
+ * (The SSH RFCs allow many protocol-level identifier namespaces to be
+ * extended by implementors without central standardisation as long as
+ * they suffix "@" and a domain name they control to their new ids.
+ * RFC 4253 does not define this particular name to be changeable at
+ * all, but I like to think this is obviously how it would have done
+ * so if the working group had foreseen the need :-)
+ *
+ * Thereafter, all data exchanged consists of a sequence of binary
+ * packets concatenated end-to-end, each of which is of the form
+ *
+ *     uint32     length of packet, N
+ *     byte[N]    N bytes of packet data
+ *
+ * and, since these are SSH-2 messages, the first data byte is taken
+ * to be the packet type code.
+ *
+ * These messages are interpreted as those of an SSH connection, after
+ * userauth completes, and without any repeat key exchange.
+ * Specifically, any message from the SSH Connection Protocol is
+ * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG,
+ * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport
+ * Protocol.
+ *
+ * This protocol imposes a few additional requirements, over and above
+ * those of the standard SSH Connection Protocol:
+ *
+ * Message sizes are not permitted to exceed 0x4010 (16400) bytes,
+ * including their length header.
+ *
+ * When the server (i.e. really the PuTTY upstream) sends
+ * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client
+ * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that
+ * confirmation message MUST include an initial window size of at
+ * least 256. (Rationale: this is a bit of a fudge which makes it
+ * easier, by eliminating the possibility of nasty edge cases, for an
+ * upstream to arrange not to pass the CHANNEL_OPEN on to downstream
+ * until after it's seen the X11 auth data to decide which downstream
+ * it needs to go to.)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "putty.h"
+#include "tree234.h"
+#include "ssh.h"
+#include "sshcr.h"
+
+struct ssh_sharing_state {
+    char *sockname;                  /* the socket name, kept for cleanup */
+    Socket *listensock;              /* the master listening Socket */
+    tree234 *connections;            /* holds ssh_sharing_connstates */
+    unsigned nextid;                 /* preferred id for next connstate */
+    ConnectionLayer *cl;             /* instance of the ssh connection layer */
+    char *server_verstring;          /* server version string after "SSH-" */
+
+    Plug plug;
+};
+
+struct share_globreq;
+
+struct ssh_sharing_connstate {
+    unsigned id;    /* used to identify this downstream in log messages */
+
+    Socket *sock;                     /* the Socket for this connection */
+    struct ssh_sharing_state *parent;
+
+    int crLine;                        /* coroutine state for share_receive */
+
+    bool sent_verstring, got_verstring;
+    int curr_packetlen;
+
+    unsigned char recvbuf[0x4010];
+    size_t recvlen;
+
+    /*
+     * Assorted state we have to remember about this downstream, so
+     * that we can clean it up appropriately when the downstream goes
+     * away.
+     */
+
+    /* Channels which don't have a downstream id, i.e. we've passed a
+     * CHANNEL_OPEN down from the server but not had an
+     * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes
+     * away, we respond to all of these with OPEN_FAILURE. */
+    tree234 *halfchannels;         /* stores 'struct share_halfchannel' */
+
+    /* Channels which do have a downstream id. We need to index these
+     * by both server id and upstream id, so we can find a channel
+     * when handling either an upward or a downward message referring
+     * to it. */
+    tree234 *channels_by_us;       /* stores 'struct share_channel' */
+    tree234 *channels_by_server;   /* stores 'struct share_channel' */
+
+    /* Another class of channel which doesn't have a downstream id.
+     * The difference between these and halfchannels is that xchannels
+     * do have an *upstream* id, because upstream has already accepted
+     * the channel request from the server. This arises in the case of
+     * X forwarding, where we have to accept the request and read the
+     * X authorisation data before we know whether the channel needs
+     * to be forwarded to a downstream. */
+    tree234 *xchannels_by_us;     /* stores 'struct share_xchannel' */
+    tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */
+
+    /* Remote port forwarding requests in force. */
+    tree234 *forwardings;          /* stores 'struct share_forwarding' */
+
+    /* Global requests we've sent on to the server, pending replies. */
+    struct share_globreq *globreq_head, *globreq_tail;
+
+    Plug plug;
+};
+
+struct share_halfchannel {
+    unsigned server_id;
+};
+
+/* States of a share_channel. */
+enum {
+    OPEN,
+    SENT_CLOSE,
+    RCVD_CLOSE,
+    /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet.
+     * If downstream goes away when a channel is in this state, we
+     * must wait for the server's response before starting to send
+     * CLOSE. Channels in this state are also not held in
+     * channels_by_server, because their server_id field is
+     * meaningless. */
+    UNACKNOWLEDGED
+};
+
+struct share_channel {
+    unsigned downstream_id, upstream_id, server_id;
+    int downstream_maxpkt;
+    int state;
+    /*
+     * Some channels (specifically, channels on which downstream has
+     * sent "x11-req") have the additional function of storing a set
+     * of downstream X authorisation data and a handle to an upstream
+     * fake set.
+     */
+    struct X11FakeAuth *x11_auth_upstream;
+    int x11_auth_proto;
+    char *x11_auth_data;
+    int x11_auth_datalen;
+    bool x11_one_shot;
+};
+
+struct share_forwarding {
+    char *host;
+    int port;
+    bool active;            /* has the server sent REQUEST_SUCCESS? */
+    struct ssh_rportfwd *rpf;
+};
+
+struct share_xchannel_message {
+    struct share_xchannel_message *next;
+    int type;
+    unsigned char *data;
+    int datalen;
+};
+
+struct share_xchannel {
+    unsigned upstream_id, server_id;
+
+    /*
+     * xchannels come in two flavours: live and dead. Live ones are
+     * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from
+     * downstream; dead ones have had an OPEN_FAILURE, so they only
+     * exist as a means of letting us conveniently respond to further
+     * channel messages from the server until such time as the server
+     * sends us CHANNEL_CLOSE.
+     */
+    bool live;
+
+    /*
+     * When we receive OPEN_CONFIRMATION, we will need to send a
+     * WINDOW_ADJUST to the server to synchronise the windows. For
+     * this purpose we need to know what window we have so far offered
+     * the server. We record this as exactly the value in the
+     * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount
+     * by which the two X greetings differed in length.
+     */
+    int window;
+
+    /*
+     * Linked list of SSH messages from the server relating to this
+     * channel, which we queue up until downstream sends us an
+     * OPEN_CONFIRMATION and we can belatedly send them all on.
+     */
+    struct share_xchannel_message *msghead, *msgtail;
+};
+
+enum {
+    GLOBREQ_TCPIP_FORWARD,
+    GLOBREQ_CANCEL_TCPIP_FORWARD
+};
+
+struct share_globreq {
+    struct share_globreq *next;
+    int type;
+    bool want_reply;
+    struct share_forwarding *fwd;
+};
+
+static int share_connstate_cmp(void *av, void *bv)
+{
+    const struct ssh_sharing_connstate *a =
+        (const struct ssh_sharing_connstate *)av;
+    const struct ssh_sharing_connstate *b =
+        (const struct ssh_sharing_connstate *)bv;
+
+    if (a->id < b->id)
+        return -1;
+    else if (a->id > b->id)
+        return +1;
+    else
+        return 0;
+}
+
+static unsigned share_find_unused_id
+(struct ssh_sharing_state *sharestate, unsigned first)
+{
+    int low_orig, low, mid, high, high_orig;
+    struct ssh_sharing_connstate *cs;
+    unsigned ret;
+
+    /*
+     * Find the lowest unused downstream ID greater or equal to
+     * 'first'.
+     *
+     * Begin by seeing if 'first' itself is available. If it is, we'll
+     * just return it; if it's already in the tree, we'll find the
+     * tree index where it appears and use that for the next stage.
+     */
+    {
+        struct ssh_sharing_connstate dummy;
+        dummy.id = first;
+        cs = findrelpos234(sharestate->connections, &dummy, NULL,
+                           REL234_GE, &low_orig);
+        if (!cs)
+            return first;
+    }
+
+    /*
+     * Now binary-search using the counted B-tree, to find the largest
+     * ID which is in a contiguous sequence from the beginning of that
+     * range.
+     */
+    low = low_orig;
+    high = high_orig = count234(sharestate->connections);
+    while (high - low > 1) {
+        mid = (high + low) / 2;
+        cs = index234(sharestate->connections, mid);
+        if (cs->id == first + (mid - low_orig))
+            low = mid;                 /* this one is still in the sequence */
+        else
+            high = mid;                /* this one is past the end */
+    }
+
+    /*
+     * Now low is the tree index of the largest ID in the initial
+     * sequence. So the return value is one more than low's id, and we
+     * know low's id is given by the formula in the binary search loop
+     * above.
+     *
+     * (If an SSH connection went on for _enormously_ long, we might
+     * reach a point where all ids from 'first' to UINT_MAX were in
+     * use. In that situation the formula below would wrap round by
+     * one and return zero, which is conveniently the right way to
+     * signal 'no id available' from this function.)
+     */
+    ret = first + (low - low_orig) + 1;
+    {
+        struct ssh_sharing_connstate dummy;
+        dummy.id = ret;
+        assert(NULL == find234(sharestate->connections, &dummy, NULL));
+    }
+    return ret;
+}
+
+static int share_halfchannel_cmp(void *av, void *bv)
+{
+    const struct share_halfchannel *a = (const struct share_halfchannel *)av;
+    const struct share_halfchannel *b = (const struct share_halfchannel *)bv;
+
+    if (a->server_id < b->server_id)
+        return -1;
+    else if (a->server_id > b->server_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_channel_us_cmp(void *av, void *bv)
+{
+    const struct share_channel *a = (const struct share_channel *)av;
+    const struct share_channel *b = (const struct share_channel *)bv;
+
+    if (a->upstream_id < b->upstream_id)
+        return -1;
+    else if (a->upstream_id > b->upstream_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_channel_server_cmp(void *av, void *bv)
+{
+    const struct share_channel *a = (const struct share_channel *)av;
+    const struct share_channel *b = (const struct share_channel *)bv;
+
+    if (a->server_id < b->server_id)
+        return -1;
+    else if (a->server_id > b->server_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_xchannel_us_cmp(void *av, void *bv)
+{
+    const struct share_xchannel *a = (const struct share_xchannel *)av;
+    const struct share_xchannel *b = (const struct share_xchannel *)bv;
+
+    if (a->upstream_id < b->upstream_id)
+        return -1;
+    else if (a->upstream_id > b->upstream_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_xchannel_server_cmp(void *av, void *bv)
+{
+    const struct share_xchannel *a = (const struct share_xchannel *)av;
+    const struct share_xchannel *b = (const struct share_xchannel *)bv;
+
+    if (a->server_id < b->server_id)
+        return -1;
+    else if (a->server_id > b->server_id)
+        return +1;
+    else
+        return 0;
+}
+
+static int share_forwarding_cmp(void *av, void *bv)
+{
+    const struct share_forwarding *a = (const struct share_forwarding *)av;
+    const struct share_forwarding *b = (const struct share_forwarding *)bv;
+    int i;
+
+    if ((i = strcmp(a->host, b->host)) != 0)
+        return i;
+    else if (a->port < b->port)
+        return -1;
+    else if (a->port > b->port)
+        return +1;
+    else
+        return 0;
+}
+
+static void share_xchannel_free(struct share_xchannel *xc)
+{
+    while (xc->msghead) {
+        struct share_xchannel_message *tmp = xc->msghead;
+        xc->msghead = tmp->next;
+        sfree(tmp);
+    }
+    sfree(xc);
+}
+
+static void share_connstate_free(struct ssh_sharing_connstate *cs)
+{
+    struct share_halfchannel *hc;
+    struct share_xchannel *xc;
+    struct share_channel *chan;
+    struct share_forwarding *fwd;
+
+    while ((hc = (struct share_halfchannel *)
+            delpos234(cs->halfchannels, 0)) != NULL)
+        sfree(hc);
+    freetree234(cs->halfchannels);
+
+    /* All channels live in 'channels_by_us' but only some in
+     * 'channels_by_server', so we use the former to find the list of
+     * ones to free */
+    freetree234(cs->channels_by_server);
+    while ((chan = (struct share_channel *)
+            delpos234(cs->channels_by_us, 0)) != NULL)
+        sfree(chan);
+    freetree234(cs->channels_by_us);
+
+    /* But every xchannel is in both trees, so it doesn't matter which
+     * we use to free them. */
+    while ((xc = (struct share_xchannel *)
+            delpos234(cs->xchannels_by_us, 0)) != NULL)
+        share_xchannel_free(xc);
+    freetree234(cs->xchannels_by_us);
+    freetree234(cs->xchannels_by_server);
+
+    while ((fwd = (struct share_forwarding *)
+            delpos234(cs->forwardings, 0)) != NULL)
+        sfree(fwd);
+    freetree234(cs->forwardings);
+
+    while (cs->globreq_head) {
+        struct share_globreq *globreq = cs->globreq_head;
+        cs->globreq_head = cs->globreq_head->next;
+        sfree(globreq);
+    }
+
+    if (cs->sock)
+        sk_close(cs->sock);
+
+    sfree(cs);
+}
+
+void sharestate_free(ssh_sharing_state *sharestate)
+{
+    struct ssh_sharing_connstate *cs;
+
+    platform_ssh_share_cleanup(sharestate->sockname);
+
+    while ((cs = (struct ssh_sharing_connstate *)
+            delpos234(sharestate->connections, 0)) != NULL) {
+        share_connstate_free(cs);
+    }
+    freetree234(sharestate->connections);
+    if (sharestate->listensock) {
+        sk_close(sharestate->listensock);
+        sharestate->listensock = NULL;
+    }
+    sfree(sharestate->server_verstring);
+    sfree(sharestate->sockname);
+    sfree(sharestate);
+}
+
+static struct share_halfchannel *share_add_halfchannel
+    (struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+    struct share_halfchannel *hc = snew(struct share_halfchannel);
+    hc->server_id = server_id;
+    if (add234(cs->halfchannels, hc) != hc) {
+        /* Duplicate?! */
+        sfree(hc);
+        return NULL;
+    } else {
+        return hc;
+    }
+}
+
+static struct share_halfchannel *share_find_halfchannel
+    (struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+    struct share_halfchannel dummyhc;
+    dummyhc.server_id = server_id;
+    return find234(cs->halfchannels, &dummyhc, NULL);
+}
+
+static void share_remove_halfchannel(struct ssh_sharing_connstate *cs,
+                                     struct share_halfchannel *hc)
+{
+    del234(cs->halfchannels, hc);
+    sfree(hc);
+}
+
+static struct share_channel *share_add_channel
+    (struct ssh_sharing_connstate *cs, unsigned downstream_id,
+     unsigned upstream_id, unsigned server_id, int state, int maxpkt)
+{
+    struct share_channel *chan = snew(struct share_channel);
+    chan->downstream_id = downstream_id;
+    chan->upstream_id = upstream_id;
+    chan->server_id = server_id;
+    chan->state = state;
+    chan->downstream_maxpkt = maxpkt;
+    chan->x11_auth_upstream = NULL;
+    chan->x11_auth_data = NULL;
+    chan->x11_auth_proto = -1;
+    chan->x11_auth_datalen = 0;
+    chan->x11_one_shot = false;
+    if (add234(cs->channels_by_us, chan) != chan) {
+        sfree(chan);
+        return NULL;
+    }
+    if (chan->state != UNACKNOWLEDGED) {
+        if (add234(cs->channels_by_server, chan) != chan) {
+            del234(cs->channels_by_us, chan);
+            sfree(chan);
+            return NULL;
+        }
+    }
+    return chan;
+}
+
+static void share_channel_set_server_id(struct ssh_sharing_connstate *cs,
+                                        struct share_channel *chan,
+                                        unsigned server_id, int newstate)
+{
+    chan->server_id = server_id;
+    chan->state = newstate;
+    assert(newstate != UNACKNOWLEDGED);
+    add234(cs->channels_by_server, chan);
+}
+
+static struct share_channel *share_find_channel_by_upstream
+    (struct ssh_sharing_connstate *cs, unsigned upstream_id)
+{
+    struct share_channel dummychan;
+    dummychan.upstream_id = upstream_id;
+    return find234(cs->channels_by_us, &dummychan, NULL);
+}
+
+static struct share_channel *share_find_channel_by_server
+    (struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+    struct share_channel dummychan;
+    dummychan.server_id = server_id;
+    return find234(cs->channels_by_server, &dummychan, NULL);
+}
+
+static void share_remove_channel(struct ssh_sharing_connstate *cs,
+                                 struct share_channel *chan)
+{
+    del234(cs->channels_by_us, chan);
+    del234(cs->channels_by_server, chan);
+    if (chan->x11_auth_upstream)
+        ssh_remove_sharing_x11_display(cs->parent->cl,
+                                       chan->x11_auth_upstream);
+    sfree(chan->x11_auth_data);
+    sfree(chan);
+}
+
+static struct share_xchannel *share_add_xchannel
+    (struct ssh_sharing_connstate *cs,
+     unsigned upstream_id, unsigned server_id)
+{
+    struct share_xchannel *xc = snew(struct share_xchannel);
+    xc->upstream_id = upstream_id;
+    xc->server_id = server_id;
+    xc->live = true;
+    xc->msghead = xc->msgtail = NULL;
+    if (add234(cs->xchannels_by_us, xc) != xc) {
+        sfree(xc);
+        return NULL;
+    }
+    if (add234(cs->xchannels_by_server, xc) != xc) {
+        del234(cs->xchannels_by_us, xc);
+        sfree(xc);
+        return NULL;
+    }
+    return xc;
+}
+
+static struct share_xchannel *share_find_xchannel_by_upstream
+    (struct ssh_sharing_connstate *cs, unsigned upstream_id)
+{
+    struct share_xchannel dummyxc;
+    dummyxc.upstream_id = upstream_id;
+    return find234(cs->xchannels_by_us, &dummyxc, NULL);
+}
+
+static struct share_xchannel *share_find_xchannel_by_server
+    (struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+    struct share_xchannel dummyxc;
+    dummyxc.server_id = server_id;
+    return find234(cs->xchannels_by_server, &dummyxc, NULL);
+}
+
+static void share_remove_xchannel(struct ssh_sharing_connstate *cs,
+                                 struct share_xchannel *xc)
+{
+    del234(cs->xchannels_by_us, xc);
+    del234(cs->xchannels_by_server, xc);
+    share_xchannel_free(xc);
+}
+
+static struct share_forwarding *share_add_forwarding
+    (struct ssh_sharing_connstate *cs,
+     const char *host, int port)
+{
+    struct share_forwarding *fwd = snew(struct share_forwarding);
+    fwd->host = dupstr(host);
+    fwd->port = port;
+    fwd->active = false;
+    if (add234(cs->forwardings, fwd) != fwd) {
+        /* Duplicate?! */
+        sfree(fwd);
+        return NULL;
+    }
+    return fwd;
+}
+
+static struct share_forwarding *share_find_forwarding
+    (struct ssh_sharing_connstate *cs, const char *host, int port)
+{
+    struct share_forwarding dummyfwd, *ret;
+    dummyfwd.host = dupstr(host);
+    dummyfwd.port = port;
+    ret = find234(cs->forwardings, &dummyfwd, NULL);
+    sfree(dummyfwd.host);
+    return ret;
+}
+
+static void share_remove_forwarding(struct ssh_sharing_connstate *cs,
+                                    struct share_forwarding *fwd)
+{
+    del234(cs->forwardings, fwd);
+    sfree(fwd);
+}
+
+static PRINTF_LIKE(2, 3) void log_downstream(struct ssh_sharing_connstate *cs,
+                                             const char *logfmt, ...)
+{
+    va_list ap;
+    char *buf;
+
+    va_start(ap, logfmt);
+    buf = dupvprintf(logfmt, ap);
+    va_end(ap);
+    logeventf(cs->parent->cl->logctx,
+              "Connection sharing downstream #%u: %s", cs->id, buf);
+    sfree(buf);
+}
+
+static PRINTF_LIKE(2, 3) void log_general(struct ssh_sharing_state *sharestate,
+                                          const char *logfmt, ...)
+{
+    va_list ap;
+    char *buf;
+
+    va_start(ap, logfmt);
+    buf = dupvprintf(logfmt, ap);
+    va_end(ap);
+    logeventf(sharestate->cl->logctx, "Connection sharing: %s", buf);
+    sfree(buf);
+}
+
+static void send_packet_to_downstream(struct ssh_sharing_connstate *cs,
+                                      int type, const void *pkt, int pktlen,
+                                      struct share_channel *chan)
+{
+    strbuf *packet;
+
+    if (!cs->sock) /* throw away all packets destined for a dead downstream */
+        return;
+
+    if (type == SSH2_MSG_CHANNEL_DATA) {
+        /*
+         * Special case which we take care of at a low level, so as to
+         * be sure to apply it in all cases. On rare occasions we
+         * might find that we have a channel for which the
+         * downstream's maximum packet size exceeds the max packet
+         * size we presented to the server on its behalf. (This can
+         * occur in X11 forwarding, where we have to send _our_
+         * CHANNEL_OPEN_CONFIRMATION before we discover which if any
+         * downstream the channel is destined for, so if that
+         * downstream turns out to present a smaller max packet size
+         * then we're in this situation.)
+         *
+         * If that happens, we just chop up the packet into pieces and
+         * send them as separate CHANNEL_DATA packets.
+         */
+        BinarySource src[1];
+        unsigned channel;
+        ptrlen data;
+
+        BinarySource_BARE_INIT(src, pkt, pktlen);
+        channel = get_uint32(src);
+        data = get_string(src);
+
+        do {
+            int this_len = (data.len > chan->downstream_maxpkt ?
+                            chan->downstream_maxpkt : data.len);
+
+            packet = strbuf_new_nm();
+            put_uint32(packet, 0);     /* placeholder for length field */
+            put_byte(packet, type);
+            put_uint32(packet, channel);
+            put_uint32(packet, this_len);
+            put_data(packet, data.ptr, this_len);
+            data.ptr = (const char *)data.ptr + this_len;
+            data.len -= this_len;
+            PUT_32BIT_MSB_FIRST(packet->s, packet->len-4);
+            sk_write(cs->sock, packet->s, packet->len);
+            strbuf_free(packet);
+        } while (data.len > 0);
+    } else {
+        /*
+         * Just do the obvious thing.
+         */
+        packet = strbuf_new_nm();
+        put_uint32(packet, 0);     /* placeholder for length field */
+        put_byte(packet, type);
+        put_data(packet, pkt, pktlen);
+        PUT_32BIT_MSB_FIRST(packet->s, packet->len-4);
+        sk_write(cs->sock, packet->s, packet->len);
+        strbuf_free(packet);
+    }
+}
+
+static void share_try_cleanup(struct ssh_sharing_connstate *cs)
+{
+    int i;
+    struct share_halfchannel *hc;
+    struct share_channel *chan;
+    struct share_forwarding *fwd;
+
+    /*
+     * Any half-open channels, i.e. those for which we'd received
+     * CHANNEL_OPEN from the server but not passed back a response
+     * from downstream, should be responded to with OPEN_FAILURE.
+     */
+    while ((hc = (struct share_halfchannel *)
+            index234(cs->halfchannels, 0)) != NULL) {
+        static const char reason[] = "PuTTY downstream no longer available";
+        static const char lang[] = "en";
+        strbuf *packet;
+
+        packet = strbuf_new();
+        put_uint32(packet, hc->server_id);
+        put_uint32(packet, SSH2_OPEN_CONNECT_FAILED);
+        put_stringz(packet, reason);
+        put_stringz(packet, lang);
+        ssh_send_packet_from_downstream(
+            cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_OPEN_FAILURE,
+            packet->s, packet->len,
+            "cleanup after downstream went away");
+        strbuf_free(packet);
+
+        share_remove_halfchannel(cs, hc);
+    }
+
+    /*
+     * Any actually open channels should have a CHANNEL_CLOSE sent for
+     * them, unless we've already done so. We won't be able to
+     * actually clean them up until CHANNEL_CLOSE comes back from the
+     * server, though (unless the server happens to have sent a CLOSE
+     * already).
+     *
+     * Another annoying exception is UNACKNOWLEDGED channels, i.e.
+     * we've _sent_ a CHANNEL_OPEN to the server but not received an
+     * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply
+     * before closing the channel, because until we see that reply we
+     * won't have the server's channel id to put in the close message.
+     */
+    for (i = 0; (chan = (struct share_channel *)
+                 index234(cs->channels_by_us, i)) != NULL; i++) {
+        strbuf *packet;
+
+        if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) {
+            packet = strbuf_new();
+            put_uint32(packet, chan->server_id);
+            ssh_send_packet_from_downstream(
+                cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE,
+                packet->s, packet->len,
+                "cleanup after downstream went away");
+            strbuf_free(packet);
+
+            if (chan->state != RCVD_CLOSE) {
+                chan->state = SENT_CLOSE;
+            } else {
+                /* In this case, we _can_ clear up the channel now. */
+                ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id);
+                share_remove_channel(cs, chan);
+                i--;    /* don't accidentally skip one as a result */
+            }
+        }
+    }
+
+    /*
+     * Any remote port forwardings we're managing on behalf of this
+     * downstream should be cancelled. Again, we must defer those for
+     * which we haven't yet seen REQUEST_SUCCESS/FAILURE.
+     *
+     * We take a fire-and-forget approach during cleanup, not
+     * bothering to set want_reply.
+     */
+    for (i = 0; (fwd = (struct share_forwarding *)
+                 index234(cs->forwardings, i)) != NULL; i++) {
+        if (fwd->active) {
+            strbuf *packet = strbuf_new();
+            put_stringz(packet, "cancel-tcpip-forward");
+            put_bool(packet, false);       /* !want_reply */
+            put_stringz(packet, fwd->host);
+            put_uint32(packet, fwd->port);
+            ssh_send_packet_from_downstream(
+                cs->parent->cl, cs->id, SSH2_MSG_GLOBAL_REQUEST,
+                packet->s, packet->len,
+                "cleanup after downstream went away");
+            strbuf_free(packet);
+
+            ssh_rportfwd_remove(cs->parent->cl, fwd->rpf);
+            share_remove_forwarding(cs, fwd);
+            i--;    /* don't accidentally skip one as a result */
+        }
+    }
+
+    if (count234(cs->halfchannels) == 0 &&
+        count234(cs->channels_by_us) == 0 &&
+        count234(cs->forwardings) == 0) {
+        struct ssh_sharing_state *sharestate = cs->parent;
+
+        /*
+         * Now we're _really_ done, so we can get rid of cs completely.
+         */
+        del234(sharestate->connections, cs);
+        log_downstream(cs, "disconnected");
+        share_connstate_free(cs);
+
+        /*
+         * And if this was the last downstream, notify the connection
+         * layer, because it might now be time to wind up the whole
+         * SSH connection.
+         */
+        if (count234(sharestate->connections) == 0 && sharestate->cl)
+            ssh_sharing_no_more_downstreams(sharestate->cl);
+    }
+}
+
+static void share_begin_cleanup(struct ssh_sharing_connstate *cs)
+{
+
+    sk_close(cs->sock);
+    cs->sock = NULL;
+
+    share_try_cleanup(cs);
+}
+
+static void share_disconnect(struct ssh_sharing_connstate *cs,
+                             const char *message)
+{
+    strbuf *packet = strbuf_new();
+    put_uint32(packet, SSH2_DISCONNECT_PROTOCOL_ERROR);
+    put_stringz(packet, message);
+    put_stringz(packet, "en");         /* language */
+    send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT,
+                              packet->s, packet->len, NULL);
+    strbuf_free(packet);
+
+    share_begin_cleanup(cs);
+}
+
+static void share_closing(Plug *plug, PlugCloseType type,
+                          const char *error_msg)
+{
+    struct ssh_sharing_connstate *cs = container_of(
+        plug, struct ssh_sharing_connstate, plug);
+
+    /*
+     * Most of the time, we log what went wrong when a downstream
+     * disappears with a socket error. One exception, though, is
+     * receiving EPIPE when we haven't received a protocol version
+     * string from the downstream, because that can happen as a result
+     * of plink -shareexists (opening the connection and instantly
+     * closing it again without bothering to read our version string).
+     * So that one case is not treated as a log-worthy error.
+     */
+    if (type == PLUGCLOSE_BROKEN_PIPE && !cs->got_verstring) {
+        /* do nothing */;
+    } else if (type != PLUGCLOSE_NORMAL) {
+        log_downstream(cs, "Socket error: %s", error_msg);
+    }
+    share_begin_cleanup(cs);
+}
+
+/*
+ * Append a message to the end of an xchannel's queue.
+ */
+static void share_xchannel_add_message(
+    struct share_xchannel *xc, int type, const void *data, int len)
+{
+    struct share_xchannel_message *msg;
+
+    /*
+     * Allocate the 'struct share_xchannel_message' and the actual
+     * data in one unit.
+     */
+    msg = snew_plus(struct share_xchannel_message, len);
+    msg->data = snew_plus_get_aux(msg);
+    msg->datalen = len;
+    msg->type = type;
+    memcpy(msg->data, data, len);
+
+    /*
+     * Queue it in the xchannel.
+     */
+    if (xc->msgtail)
+        xc->msgtail->next = msg;
+    else
+        xc->msghead = msg;
+    msg->next = NULL;
+    xc->msgtail = msg;
+}
+
+void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
+                                 struct share_xchannel *xc)
+{
+    /*
+     * Handle queued incoming messages from the server destined for an
+     * xchannel which is dead (i.e. downstream sent OPEN_FAILURE).
+     */
+    bool delete = false;
+    while (xc->msghead) {
+        struct share_xchannel_message *msg = xc->msghead;
+        xc->msghead = msg->next;
+
+        if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) {
+            /*
+             * A CHANNEL_REQUEST is responded to by sending
+             * CHANNEL_FAILURE, if it has want_reply set.
+             */
+            BinarySource src[1];
+            BinarySource_BARE_INIT(src, msg->data, msg->datalen);
+            get_uint32(src);           /* skip channel id */
+            get_string(src);           /* skip request type */
+            if (get_bool(src)) {
+                strbuf *packet = strbuf_new();
+                put_uint32(packet, xc->server_id);
+                ssh_send_packet_from_downstream
+                    (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE,
+                     packet->s, packet->len,
+                     "downstream refused X channel open");
+                strbuf_free(packet);
+            }
+        } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) {
+            /*
+             * On CHANNEL_CLOSE we can discard the channel completely.
+             */
+            delete = true;
+        }
+
+        sfree(msg);
+    }
+    xc->msgtail = NULL;
+    if (delete) {
+        ssh_delete_sharing_channel(cs->parent->cl, xc->upstream_id);
+        share_remove_xchannel(cs, xc);
+    }
+}
+
+void share_xchannel_confirmation(struct ssh_sharing_connstate *cs,
+                                 struct share_xchannel *xc,
+                                 struct share_channel *chan,
+                                 unsigned downstream_window)
+{
+    strbuf *packet;
+
+    /*
+     * Send all the queued messages downstream.
+     */
+    while (xc->msghead) {
+        struct share_xchannel_message *msg = xc->msghead;
+        xc->msghead = msg->next;
+
+        if (msg->datalen >= 4)
+            PUT_32BIT_MSB_FIRST(msg->data, chan->downstream_id);
+        send_packet_to_downstream(cs, msg->type,
+                                  msg->data, msg->datalen, chan);
+
+        sfree(msg);
+    }
+
+    /*
+     * Send a WINDOW_ADJUST back upstream, to synchronise the window
+     * size downstream thinks it's presented with the one we've
+     * actually presented.
+     */
+    packet = strbuf_new();
+    put_uint32(packet, xc->server_id);
+    put_uint32(packet, downstream_window - xc->window);
+    ssh_send_packet_from_downstream(
+        cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_WINDOW_ADJUST,
+        packet->s, packet->len,
+        "window adjustment after downstream accepted X channel");
+    strbuf_free(packet);
+}
+
+void share_xchannel_failure(struct ssh_sharing_connstate *cs,
+                            struct share_xchannel *xc)
+{
+    /*
+     * If downstream refuses to open our X channel at all for some
+     * reason, we must respond by sending an emergency CLOSE upstream.
+     */
+    strbuf *packet = strbuf_new();
+    put_uint32(packet, xc->server_id);
+    ssh_send_packet_from_downstream(
+        cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE,
+        packet->s, packet->len,
+        "downstream refused X channel open");
+    strbuf_free(packet);
+
+    /*
+     * Now mark the xchannel as dead, and respond to anything sent on
+     * it until we see CLOSE for it in turn.
+     */
+    xc->live = false;
+    share_dead_xchannel_respond(cs, xc);
+}
+
+#ifndef WINSCP
+void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
+                             unsigned upstream_id, unsigned server_id,
+                             unsigned server_currwin, unsigned server_maxpkt,
+                             unsigned client_adjusted_window,
+                             const char *peer_addr, int peer_port, int endian,
+                             int protomajor, int protominor,
+                             const void *initial_data, int initial_len)
+{
+    struct share_xchannel *xc;
+    void *greeting;
+    int greeting_len;
+    strbuf *packet;
+
+    /*
+     * Create an xchannel containing data we've already received from
+     * the X client, and preload it with a CHANNEL_DATA message
+     * containing our own made-up authorisation greeting and any
+     * additional data sent from the server so far.
+     */
+    xc = share_add_xchannel(cs, upstream_id, server_id);
+    greeting = x11_make_greeting(endian, protomajor, protominor,
+                                 chan->x11_auth_proto,
+                                 chan->x11_auth_data, chan->x11_auth_datalen,
+                                 peer_addr, peer_port, &greeting_len);
+    packet = strbuf_new_nm();
+    put_uint32(packet, 0); /* leave the channel id field unfilled - we
+                            * don't know the downstream id yet */
+    put_uint32(packet, greeting_len + initial_len);
+    put_data(packet, greeting, greeting_len);
+    put_data(packet, initial_data, initial_len);
+    sfree(greeting);
+    share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA,
+                               packet->s, packet->len);
+    strbuf_free(packet);
+
+    xc->window = client_adjusted_window + greeting_len;
+
+    /*
+     * Send on a CHANNEL_OPEN to downstream.
+     */
+    packet = strbuf_new();
+    put_stringz(packet, "x11");
+    put_uint32(packet, server_id);
+    put_uint32(packet, server_currwin);
+    put_uint32(packet, server_maxpkt);
+    put_stringz(packet, peer_addr);
+    put_uint32(packet, peer_port);
+    send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN,
+                              packet->s, packet->len, NULL);
+    strbuf_free(packet);
+
+    /*
+     * If this was a once-only X forwarding, clean it up now.
+     */
+    if (chan->x11_one_shot) {
+        ssh_remove_sharing_x11_display(cs->parent->cl,
+                                       chan->x11_auth_upstream);
+        chan->x11_auth_upstream = NULL;
+        sfree(chan->x11_auth_data);
+        chan->x11_auth_proto = -1;
+        chan->x11_auth_datalen = 0;
+        chan->x11_one_shot = false;
+    }
+}
+#endif
+
+void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type,
+                               const void *vpkt, int pktlen)
+{
+    const unsigned char *pkt = (const unsigned char *)vpkt;
+    struct share_globreq *globreq;
+    size_t id_pos;
+    unsigned upstream_id, server_id;
+    struct share_channel *chan;
+    struct share_xchannel *xc;
+    BinarySource src[1];
+
+    BinarySource_BARE_INIT(src, pkt, pktlen);
+
+    switch (type) {
+      case SSH2_MSG_REQUEST_SUCCESS:
+      case SSH2_MSG_REQUEST_FAILURE:
+        globreq = cs->globreq_head;
+        assert(globreq);         /* should match the queue in connection2.c */
+        if (globreq->type == GLOBREQ_TCPIP_FORWARD) {
+            if (type == SSH2_MSG_REQUEST_FAILURE) {
+                share_remove_forwarding(cs, globreq->fwd);
+            } else {
+                globreq->fwd->active = true;
+            }
+        } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) {
+            if (type == SSH2_MSG_REQUEST_SUCCESS) {
+                share_remove_forwarding(cs, globreq->fwd);
+            }
+        }
+        if (globreq->want_reply) {
+            send_packet_to_downstream(cs, type, pkt, pktlen, NULL);
+        }
+        cs->globreq_head = globreq->next;
+        sfree(globreq);
+        if (cs->globreq_head == NULL)
+            cs->globreq_tail = NULL;
+
+        if (!cs->sock) {
+            /* Retry cleaning up this connection, in case that reply
+             * was the last thing we were waiting for. */
+            share_try_cleanup(cs);
+        }
+
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN:
+        get_string(src);
+        server_id = get_uint32(src);
+        assert(!get_err(src));
+        share_add_halfchannel(cs, server_id);
+
+        send_packet_to_downstream(cs, type, pkt, pktlen, NULL);
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+      case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+      case SSH2_MSG_CHANNEL_CLOSE:
+      case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+      case SSH2_MSG_CHANNEL_DATA:
+      case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+      case SSH2_MSG_CHANNEL_EOF:
+      case SSH2_MSG_CHANNEL_REQUEST:
+      case SSH2_MSG_CHANNEL_SUCCESS:
+      case SSH2_MSG_CHANNEL_FAILURE:
+        /*
+         * All these messages have the recipient channel id as the
+         * first uint32 field in the packet. Substitute the downstream
+         * channel id for our one and pass the packet downstream.
+         */
+        id_pos = src->pos;
+        upstream_id = get_uint32(src);
+        if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) {
+            /*
+             * The normal case: this id refers to an open channel.
+             */
+            unsigned char *rewritten = snewn(pktlen, unsigned char);
+            memcpy(rewritten, pkt, pktlen);
+            PUT_32BIT_MSB_FIRST(rewritten + id_pos, chan->downstream_id);
+            send_packet_to_downstream(cs, type, rewritten, pktlen, chan);
+            sfree(rewritten);
+
+            /*
+             * Update the channel state, for messages that need it.
+             */
+            if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+                if (chan->state == UNACKNOWLEDGED && pktlen >= 8) {
+                    share_channel_set_server_id(
+                        cs, chan, GET_32BIT_MSB_FIRST(pkt+4), OPEN);
+                    if (!cs->sock) {
+                        /* Retry cleaning up this connection, so that we
+                         * can send an immediate CLOSE on this channel for
+                         * which we now know the server id. */
+                        share_try_cleanup(cs);
+                    }
+                }
+            } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
+                ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id);
+                share_remove_channel(cs, chan);
+            } else if (type == SSH2_MSG_CHANNEL_CLOSE) {
+                if (chan->state == SENT_CLOSE) {
+                    ssh_delete_sharing_channel(cs->parent->cl,
+                                               chan->upstream_id);
+                    share_remove_channel(cs, chan);
+                    if (!cs->sock) {
+                        /* Retry cleaning up this connection, in case this
+                         * channel closure was the last thing we were
+                         * waiting for. */
+                        share_try_cleanup(cs);
+                    }
+                } else {
+                    chan->state = RCVD_CLOSE;
+                }
+            }
+        } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id))
+                   != NULL) {
+            /*
+             * The unusual case: this id refers to an xchannel. Add it
+             * to the xchannel's queue.
+             */
+            share_xchannel_add_message(xc, type, pkt, pktlen);
+
+            /* If the xchannel is dead, then also respond to it (which
+             * may involve deleting the channel). */
+            if (!xc->live)
+                share_dead_xchannel_respond(cs, xc);
+        }
+        break;
+
+      default:
+        unreachable("This packet type should never have come from "
+                    "connection2.c");
+    }
+}
+
+static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
+                                          int type,
+                                          unsigned char *pkt, int pktlen)
+{
+    ptrlen request_name;
+    struct share_forwarding *fwd;
+    size_t id_pos;
+    unsigned maxpkt;
+    unsigned old_id, new_id, server_id;
+    struct share_globreq *globreq;
+    struct share_channel *chan;
+    struct share_halfchannel *hc;
+    struct share_xchannel *xc;
+    strbuf *packet;
+    char *err = NULL;
+    BinarySource src[1];
+    size_t wantreplypos;
+    bool orig_wantreply;
+
+    BinarySource_BARE_INIT(src, pkt, pktlen);
+
+    switch (type) {
+      case SSH2_MSG_DISCONNECT:
+        /*
+         * This message stops here: if downstream is disconnecting
+         * from us, that doesn't mean we want to disconnect from the
+         * SSH server. Close the downstream connection and start
+         * cleanup.
+         */
+        share_begin_cleanup(cs);
+        break;
+
+      case SSH2_MSG_GLOBAL_REQUEST:
+        /*
+         * The only global requests we understand are "tcpip-forward"
+         * and "cancel-tcpip-forward". Since those require us to
+         * maintain state, we must assume that other global requests
+         * will probably require that too, and so we don't forward on
+         * any request we don't understand.
+         */
+        request_name = get_string(src);
+        wantreplypos = src->pos;
+        orig_wantreply = get_bool(src);
+
+        if (ptrlen_eq_string(request_name, "tcpip-forward")) {
+            ptrlen hostpl;
+            char *host;
+            int port;
+            struct ssh_rportfwd *rpf;
+
+            /*
+             * Pick the packet apart to find the want_reply field and
+             * the host/port we're going to ask to listen on.
+             */
+            hostpl = get_string(src);
+            port = toint(get_uint32(src));
+            if (get_err(src)) {
+                err = dupprintf("Truncated GLOBAL_REQUEST packet");
+                goto confused;
+            }
+            host = mkstr(hostpl);
+
+            /*
+             * See if we can allocate space in the connection layer's
+             * tree of remote port forwardings. If we can't, it's
+             * because another client sharing this connection has
+             * already allocated the identical port forwarding, so we
+             * take it on ourselves to manufacture a failure packet
+             * and send it back to downstream.
+             */
+            rpf = ssh_rportfwd_alloc(
+                cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs);
+            if (!rpf) {
+                if (orig_wantreply) {
+                    send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+                                              "", 0, NULL);
+                }
+            } else {
+                /*
+                 * We've managed to make space for this forwarding
+                 * locally. Pass the request on to the SSH server, but
+                 * set want_reply even if it wasn't originally set, so
+                 * that we know whether this forwarding needs to be
+                 * cleaned up if downstream goes away.
+                 */
+                pkt[wantreplypos] = 1;
+                ssh_send_packet_from_downstream
+                    (cs->parent->cl, cs->id, type, pkt, pktlen,
+                     orig_wantreply ? NULL : "upstream added want_reply flag");
+                fwd = share_add_forwarding(cs, host, port);
+                ssh_sharing_queue_global_request(cs->parent->cl, cs);
+
+                if (fwd) {
+                    globreq = snew(struct share_globreq);
+                    globreq->next = NULL;
+                    if (cs->globreq_tail)
+                        cs->globreq_tail->next = globreq;
+                    else
+                        cs->globreq_head = globreq;
+                    globreq->fwd = fwd;
+                    globreq->want_reply = orig_wantreply;
+                    globreq->type = GLOBREQ_TCPIP_FORWARD;
+
+                    fwd->rpf = rpf;
+                }
+            }
+
+            sfree(host);
+        } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) {
+            ptrlen hostpl;
+            char *host;
+            int port;
+            struct share_forwarding *fwd;
+
+            /*
+             * Pick the packet apart to find the want_reply field and
+             * the host/port we're going to ask to listen on.
+             */
+            hostpl = get_string(src);
+            port = toint(get_uint32(src));
+            if (get_err(src)) {
+                err = dupprintf("Truncated GLOBAL_REQUEST packet");
+                goto confused;
+            }
+            host = mkstr(hostpl);
+
+            /*
+             * Look up the existing forwarding with these details.
+             */
+            fwd = share_find_forwarding(cs, host, port);
+            if (!fwd) {
+                if (orig_wantreply) {
+                    send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+                                              "", 0, NULL);
+                }
+            } else {
+                /*
+                 * Tell the connection layer to stop sending us
+                 * channel-opens for this forwarding.
+                 */
+                ssh_rportfwd_remove(cs->parent->cl, fwd->rpf);
+
+                /*
+                 * Pass the cancel request on to the SSH server, but
+                 * set want_reply even if it wasn't originally set, so
+                 * that _we_ know whether the forwarding has been
+                 * deleted even if downstream doesn't want to know.
+                 */
+                pkt[wantreplypos] = 1;
+                ssh_send_packet_from_downstream
+                    (cs->parent->cl, cs->id, type, pkt, pktlen,
+                     orig_wantreply ? NULL : "upstream added want_reply flag");
+                ssh_sharing_queue_global_request(cs->parent->cl, cs);
+
+                /*
+                 * And queue a globreq so that when the reply comes
+                 * back we know to cancel it.
+                 */
+                globreq = snew(struct share_globreq);
+                globreq->next = NULL;
+                if (cs->globreq_tail)
+                    cs->globreq_tail->next = globreq;
+                else
+                    cs->globreq_head = globreq;
+                globreq->fwd = fwd;
+                globreq->want_reply = orig_wantreply;
+                globreq->type = GLOBREQ_CANCEL_TCPIP_FORWARD;
+            }
+
+            sfree(host);
+        } else {
+            /*
+             * Request we don't understand. Manufacture a failure
+             * message if an answer was required.
+             */
+            if (orig_wantreply)
+                send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+                                          "", 0, NULL);
+        }
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN:
+        /* Sender channel id comes after the channel type string */
+        get_string(src);
+        id_pos = src->pos;
+        old_id = get_uint32(src);
+        new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs);
+        get_uint32(src);               /* skip initial window size */
+        maxpkt = get_uint32(src);
+        if (get_err(src)) {
+            err = dupprintf("Truncated CHANNEL_OPEN packet");
+            goto confused;
+        }
+        share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt);
+        PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id);
+        ssh_send_packet_from_downstream(cs->parent->cl, cs->id,
+                                        type, pkt, pktlen, NULL);
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+        if (pktlen < 16) {
+            err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet");
+            goto confused;
+        }
+
+        server_id = get_uint32(src);
+        id_pos = src->pos;
+        old_id = get_uint32(src);
+        get_uint32(src);               /* skip initial window size */
+        maxpkt = get_uint32(src);
+        if (get_err(src)) {
+            err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet");
+            goto confused;
+        }
+
+        /* This server id may refer to either a halfchannel or an xchannel. */
+        hc = NULL, xc = NULL;          /* placate optimiser */
+        if ((hc = share_find_halfchannel(cs, server_id)) != NULL) {
+            new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs);
+        } else if ((xc = share_find_xchannel_by_server(cs, server_id))
+                   != NULL) {
+            new_id = xc->upstream_id;
+        } else {
+            err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id);
+            goto confused;
+        }
+
+        PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id);
+
+        chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt);
+
+        if (hc) {
+            ssh_send_packet_from_downstream(cs->parent->cl, cs->id,
+                                            type, pkt, pktlen, NULL);
+            share_remove_halfchannel(cs, hc);
+        } else if (xc) {
+            unsigned downstream_window = GET_32BIT_MSB_FIRST(pkt + 8);
+            if (downstream_window < 256) {
+                err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window);
+                goto confused;
+            }
+            share_xchannel_confirmation(cs, xc, chan, downstream_window);
+            share_remove_xchannel(cs, xc);
+        }
+
+        break;
+
+      case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+        server_id = get_uint32(src);
+        if (get_err(src)) {
+            err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet");
+            goto confused;
+        }
+
+        /* This server id may refer to either a halfchannel or an xchannel. */
+        if ((hc = share_find_halfchannel(cs, server_id)) != NULL) {
+            ssh_send_packet_from_downstream(cs->parent->cl, cs->id,
+                                            type, pkt, pktlen, NULL);
+            share_remove_halfchannel(cs, hc);
+        } else if ((xc = share_find_xchannel_by_server(cs, server_id))
+                   != NULL) {
+            share_xchannel_failure(cs, xc);
+        } else {
+            err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id);
+            goto confused;
+        }
+
+        break;
+
+      case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+      case SSH2_MSG_CHANNEL_DATA:
+      case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+      case SSH2_MSG_CHANNEL_EOF:
+      case SSH2_MSG_CHANNEL_CLOSE:
+      case SSH2_MSG_CHANNEL_REQUEST:
+      case SSH2_MSG_CHANNEL_SUCCESS:
+      case SSH2_MSG_CHANNEL_FAILURE:
+      case SSH2_MSG_IGNORE:
+      case SSH2_MSG_DEBUG:
+        server_id = get_uint32(src);
+
+        if (type == SSH2_MSG_CHANNEL_REQUEST) {
+            request_name = get_string(src);
+
+            /*
+             * Agent forwarding requests from downstream are treated
+             * specially. Because OpenSSHD doesn't let us enable agent
+             * forwarding independently per session channel, and in
+             * particular because the OpenSSH-defined agent forwarding
+             * protocol does not mark agent-channel requests with the
+             * id of the session channel they originate from, the only
+             * way we can implement agent forwarding in a
+             * connection-shared PuTTY is to forward the _upstream_
+             * agent. Hence, we unilaterally deny agent forwarding
+             * requests from downstreams if we aren't prepared to
+             * forward an agent ourselves.
+             *
+             * (If we are, then we dutifully pass agent forwarding
+             * requests upstream. OpenSSHD has the curious behaviour
+             * that all but the first such request will be rejected,
+             * but all session channels opened after the first request
+             * get agent forwarding enabled whether they ask for it or
+             * not; but that's not our concern, since other SSH
+             * servers supporting the same piece of protocol might in
+             * principle at least manage to enable agent forwarding on
+             * precisely the channels that requested it, even if the
+             * subsequent CHANNEL_OPENs still can't be associated with
+             * a parent session channel.)
+             */
+            if (ptrlen_eq_string(request_name, "[email protected]") &&
+                !ssh_agent_forwarding_permitted(cs->parent->cl)) {
+
+                chan = share_find_channel_by_server(cs, server_id);
+                if (chan) {
+                    packet = strbuf_new();
+                    put_uint32(packet, chan->downstream_id);
+                    send_packet_to_downstream(
+                        cs, SSH2_MSG_CHANNEL_FAILURE,
+                        packet->s, packet->len, NULL);
+                    strbuf_free(packet);
+                } else {
+                    char *buf = dupprintf("Agent forwarding request for "
+                                          "unrecognised channel %u", server_id);
+                    share_disconnect(cs, buf);
+                    sfree(buf);
+                    return;
+                }
+                break;
+            }
+
+            #ifndef WINSCP
+            /*
+             * Another thing we treat specially is X11 forwarding
+             * requests. For these, we have to make up another set of
+             * X11 auth data, and enter it into our SSH connection's
+             * list of possible X11 authorisation credentials so that
+             * when we see an X11 channel open request we can know
+             * whether it's one to handle locally or one to pass on to
+             * a downstream, and if the latter, which one.
+             */
+            if (ptrlen_eq_string(request_name, "x11-req")) {
+                bool want_reply, single_connection;
+                int screen;
+                ptrlen auth_data;
+                int auth_proto;
+
+                chan = share_find_channel_by_server(cs, server_id);
+                if (!chan) {
+                    char *buf = dupprintf("X11 forwarding request for "
+                                          "unrecognised channel %u", server_id);
+                    share_disconnect(cs, buf);
+                    sfree(buf);
+                    return;
+                }
+
+                /*
+                 * Pick apart the whole message to find the downstream
+                 * auth details.
+                 */
+                want_reply = get_bool(src);
+                single_connection = get_bool(src);
+                auth_proto = x11_identify_auth_proto(get_string(src));
+                auth_data = get_string(src);
+                screen = toint(get_uint32(src));
+                if (get_err(src)) {
+                    err = dupprintf("Truncated CHANNEL_REQUEST(\"x11-req\")"
+                                    " packet");
+                    goto confused;
+                }
+
+                if (auth_proto < 0) {
+                    /* Reject due to not understanding downstream's
+                     * requested authorisation method. */
+                    packet = strbuf_new();
+                    put_uint32(packet, chan->downstream_id);
+                    send_packet_to_downstream(
+                        cs, SSH2_MSG_CHANNEL_FAILURE,
+                        packet->s, packet->len, NULL);
+                    strbuf_free(packet);
+                    break;
+                }
+
+                chan->x11_auth_proto = auth_proto;
+                chan->x11_auth_data = x11_dehexify(auth_data,
+                                                   &chan->x11_auth_datalen);
+                chan->x11_auth_upstream =
+                    ssh_add_sharing_x11_display(cs->parent->cl, auth_proto,
+                                                cs, chan);
+                chan->x11_one_shot = single_connection;
+
+                /*
+                 * Now construct a replacement X forwarding request,
+                 * containing our own auth data, and send that to the
+                 * server.
+                 */
+                packet = strbuf_new_nm();
+                put_uint32(packet, server_id);
+                put_stringz(packet, "x11-req");
+                put_bool(packet, want_reply);
+                put_bool(packet, single_connection);
+                put_stringz(packet, chan->x11_auth_upstream->protoname);
+                put_stringz(packet, chan->x11_auth_upstream->datastring);
+                put_uint32(packet, screen);
+                ssh_send_packet_from_downstream(
+                    cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_REQUEST,
+                    packet->s, packet->len, NULL);
+                strbuf_free(packet);
+
+                break;
+            }
+            #endif
+        }
+
+        ssh_send_packet_from_downstream(cs->parent->cl, cs->id,
+                                        type, pkt, pktlen, NULL);
+        if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) {
+            chan = share_find_channel_by_server(cs, server_id);
+            if (chan) {
+                if (chan->state == RCVD_CLOSE) {
+                    ssh_delete_sharing_channel(cs->parent->cl,
+                                               chan->upstream_id);
+                    share_remove_channel(cs, chan);
+                } else {
+                    chan->state = SENT_CLOSE;
+                }
+            }
+        }
+        break;
+
+      default:
+        err = dupprintf("Unexpected packet type %d\n", type);
+        goto confused;
+
+        /*
+         * Any other packet type is unexpected. In particular, we
+         * never pass GLOBAL_REQUESTs downstream, so we never expect
+         * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}.
+         */
+      confused:
+        assert(err != NULL);
+        share_disconnect(cs, err);
+        sfree(err);
+        break;
+    }
+}
+
+/*
+ * An extra coroutine macro, specific to this code which is consuming
+ * 'const char *data'.
+ */
+#define crGetChar(c) do                                         \
+    {                                                           \
+        while (len == 0) {                                      \
+            *crLine =__LINE__; return; case __LINE__:;          \
+        }                                                       \
+        len--;                                                  \
+        (c) = (unsigned char)*data++;                           \
+    } while (0)
+
+static void share_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+    ssh_sharing_connstate *cs = container_of(
+        plug, ssh_sharing_connstate, plug);
+    static const char expected_verstring_prefix[] =
+        "[email protected]";
+    unsigned char c;
+
+    crBegin(cs->crLine);
+
+    /*
+     * First read the version string from downstream.
+     */
+    cs->recvlen = 0;
+    while (1) {
+        crGetChar(c);
+        if (c == '\012')
+            break;
+        if (cs->recvlen >= sizeof(cs->recvbuf)) {
+            char *buf = dupprintf("Version string far too long\n");
+            share_disconnect(cs, buf);
+            sfree(buf);
+            goto dead;
+        }
+        cs->recvbuf[cs->recvlen++] = c;
+    }
+
+    /*
+     * Now parse the version string to make sure it's at least vaguely
+     * sensible, and log it.
+     */
+    if (cs->recvlen < sizeof(expected_verstring_prefix)-1 ||
+        memcmp(cs->recvbuf, expected_verstring_prefix,
+               sizeof(expected_verstring_prefix) - 1)) {
+        char *buf = dupprintf("Version string did not have expected prefix\n");
+        share_disconnect(cs, buf);
+        sfree(buf);
+        goto dead;
+    }
+    if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015')
+        cs->recvlen--;                 /* trim off \r before \n */
+    { // WINSCP
+    ptrlen verstring = make_ptrlen(cs->recvbuf, cs->recvlen);
+    log_downstream(cs, "Downstream version string: %.*s",
+                   PTRLEN_PRINTF(verstring));
+    } // WINSCP
+    cs->got_verstring = true;
+
+    /*
+     * Loop round reading packets.
+     */
+    while (1) {
+        cs->recvlen = 0;
+        while (cs->recvlen < 4) {
+            crGetChar(c);
+            cs->recvbuf[cs->recvlen++] = c;
+        }
+        cs->curr_packetlen = toint(GET_32BIT_MSB_FIRST(cs->recvbuf) + 4);
+        if (cs->curr_packetlen < 5 ||
+            cs->curr_packetlen > sizeof(cs->recvbuf)) {
+            char *buf = dupprintf("Bad packet length %u\n",
+                                  (unsigned)cs->curr_packetlen);
+            share_disconnect(cs, buf);
+            sfree(buf);
+            goto dead;
+        }
+        while (cs->recvlen < cs->curr_packetlen) {
+            crGetChar(c);
+            cs->recvbuf[cs->recvlen++] = c;
+        }
+
+        share_got_pkt_from_downstream(cs, cs->recvbuf[4],
+                                      cs->recvbuf + 5, cs->recvlen - 5);
+    }
+
+  dead:;
+    crFinishV;
+}
+
+static void share_sent(Plug *plug, size_t bufsize)
+{
+    /* ssh_sharing_connstate *cs = container_of(
+        plug, ssh_sharing_connstate, plug); */
+
+    /*
+     * We do nothing here, because we expect that there won't be a
+     * need to throttle and unthrottle the connection to a downstream.
+     * It should automatically throttle itself: if the SSH server
+     * sends huge amounts of data on all channels then it'll run out
+     * of window until our downstream sends it back some
+     * WINDOW_ADJUSTs.
+     */
+}
+
+static void share_listen_closing(Plug *plug, PlugCloseType type,
+                                 const char *error_msg)
+{
+    ssh_sharing_state *sharestate =
+        container_of(plug, ssh_sharing_state, plug);
+    if (type != PLUGCLOSE_NORMAL)
+        log_general(sharestate, "listening socket: %s", error_msg);
+    sk_close(sharestate->listensock);
+    sharestate->listensock = NULL;
+}
+
+static void share_send_verstring(ssh_sharing_connstate *cs)
+{
+    char *fullstring = dupcat("[email protected]",
+                              cs->parent->server_verstring, "\015\012");
+    sk_write(cs->sock, fullstring, strlen(fullstring));
+    sfree(fullstring);
+
+    cs->sent_verstring = true;
+}
+
+int share_ndownstreams(ssh_sharing_state *sharestate)
+{
+    return count234(sharestate->connections);
+}
+
+void share_activate(ssh_sharing_state *sharestate,
+                    const char *server_verstring)
+{
+    /*
+     * Indication from connection layer that we are now ready to begin
+     * serving any downstreams that have already connected to us.
+     */
+    struct ssh_sharing_connstate *cs;
+    int i;
+
+    /*
+     * Trim the server's version string down to just the software
+     * version component, removing "SSH-2.0-" or whatever at the
+     * front.
+     */
+    for (i = 0; i < 2; i++) {
+        server_verstring += strcspn(server_verstring, "-");
+        if (*server_verstring)
+            server_verstring++;
+    }
+
+    sharestate->server_verstring = dupstr(server_verstring);
+
+    for (i = 0; (cs = (struct ssh_sharing_connstate *)
+                 index234(sharestate->connections, i)) != NULL; i++) {
+        assert(!cs->sent_verstring);
+        share_send_verstring(cs);
+    }
+}
+
+static const PlugVtable ssh_sharing_conn_plugvt = {
+    // WINSCP
+    /*.log =*/ nullplug_log,
+    /*.closing =*/ share_closing,
+    /*.receive =*/ share_receive,
+    /*.sent =*/ share_sent,
+    NULL, // WINSCP
+};
+
+static int share_listen_accepting(Plug *plug,
+                                  accept_fn_t constructor, accept_ctx_t ctx)
+{
+    struct ssh_sharing_state *sharestate = container_of(
+        plug, struct ssh_sharing_state, plug);
+    struct ssh_sharing_connstate *cs;
+    const char *err;
+    SocketPeerInfo *peerinfo;
+
+    /*
+     * A new downstream has connected to us.
+     */
+    cs = snew(struct ssh_sharing_connstate);
+    cs->plug.vt = &ssh_sharing_conn_plugvt;
+    cs->parent = sharestate;
+
+    if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 &&
+        (cs->id = share_find_unused_id(sharestate, 1)) == 0) {
+        sfree(cs);
+        return 1;
+    }
+    sharestate->nextid = cs->id + 1;
+    if (sharestate->nextid == 0)
+        sharestate->nextid++; /* only happens in VERY long-running upstreams */
+
+    cs->sock = constructor(ctx, &cs->plug);
+    if ((err = sk_socket_error(cs->sock)) != NULL) {
+        sfree(cs);
+        return err != NULL;
+    }
+
+    sk_set_frozen(cs->sock, false);
+
+    add234(cs->parent->connections, cs);
+
+    cs->sent_verstring = false;
+    if (sharestate->server_verstring)
+        share_send_verstring(cs);
+
+    cs->got_verstring = false;
+    cs->recvlen = 0;
+    cs->crLine = 0;
+    cs->halfchannels = newtree234(share_halfchannel_cmp);
+    cs->channels_by_us = newtree234(share_channel_us_cmp);
+    cs->channels_by_server = newtree234(share_channel_server_cmp);
+    cs->xchannels_by_us = newtree234(share_xchannel_us_cmp);
+    cs->xchannels_by_server = newtree234(share_xchannel_server_cmp);
+    cs->forwardings = newtree234(share_forwarding_cmp);
+    cs->globreq_head = cs->globreq_tail = NULL;
+
+    peerinfo = sk_peer_info(cs->sock);
+    log_downstream(cs, "connected%s%s",
+                   (peerinfo && peerinfo->log_text ? " from " : ""),
+                   (peerinfo && peerinfo->log_text ? peerinfo->log_text : ""));
+    sk_free_peer_info(peerinfo);
+
+    return 0;
+}
+
+/*
+ * Decide on the string used to identify the connection point between
+ * upstream and downstream (be it a Windows named pipe or a
+ * Unix-domain socket or whatever else).
+ *
+ * I wondered about making this a SHA hash of all sorts of pieces of
+ * the PuTTY configuration - essentially everything PuTTY uses to know
+ * where and how to make a connection, including all the proxy details
+ * (or rather, all the _relevant_ ones - only including settings that
+ * other settings didn't prevent from having any effect), plus the
+ * username. However, I think it's better to keep it really simple:
+ * the connection point identifier is derived from the hostname and
+ * port used to index the host-key cache (not necessarily where we
+ * _physically_ connected to, in cases involving proxies or
+ * CONF_loghost), plus the username if one is specified.
+ *
+ * The per-platform code will quite likely hash or obfuscate this name
+ * in turn, for privacy from other users; failing that, it might
+ * transform it to avoid dangerous filename characters and so on. But
+ * that doesn't matter to us: for us, the point is that two session
+ * configurations which return the same string from this function will
+ * be treated as potentially shareable with each other.
+ */
+char *ssh_share_sockname(const char *host, int port, Conf *conf)
+{
+    char *username = NULL;
+    char *sockname;
+
+    /* Include the username we're logging in as in the hash, unless
+     * we're using a protocol for which it's completely irrelevant. */
+    if (conf_get_int(conf, CONF_protocol) != PROT_SSHCONN)
+        username = get_remote_username(conf);
+
+    if (port == 22) {
+        if (username)
+            sockname = dupprintf("%s@%s", username, host);
+        else
+            sockname = dupprintf("%s", host);
+    } else {
+        if (username)
+            sockname = dupprintf("%s@%s:%d", username, host, port);
+        else
+            sockname = dupprintf("%s:%d", host, port);
+    }
+
+    sfree(username);
+    return sockname;
+}
+
+bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf)
+{
+    char *sockname, *logtext, *ds_err, *us_err;
+    int result;
+    Socket *sock;
+
+    sockname = ssh_share_sockname(host, port, conf);
+
+    sock = NULL;
+    logtext = ds_err = us_err = NULL;
+    result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock,
+                                &logtext, &ds_err, &us_err, false, true);
+
+    sfree(logtext);
+    sfree(ds_err);
+    sfree(us_err);
+    sfree(sockname);
+
+    if (result == SHARE_NONE) {
+        assert(sock == NULL);
+        return false;
+    } else {
+        assert(result == SHARE_DOWNSTREAM);
+        sk_close(sock);
+        return true;
+    }
+}
+
+static const PlugVtable ssh_sharing_listen_plugvt = {
+    // WINSCP
+    /*.log =*/ nullplug_log,
+    /*.closing =*/ share_listen_closing,
+    NULL, NULL, // WINSCP
+    /*.accepting =*/ share_listen_accepting,
+};
+
+void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,
+                                     ConnectionLayer *cl)
+{
+    sharestate->cl = cl;
+}
+
+/*
+ * Init function for connection sharing. We either open a listening
+ * socket and become an upstream, or connect to an existing one and
+ * become a downstream, or do neither. We are responsible for deciding
+ * which of these to do (including checking the Conf to see if
+ * connection sharing is even enabled in the first place). If we
+ * become a downstream, we return the Socket with which we connected
+ * to the upstream; otherwise (whether or not we have established an
+ * upstream) we return NULL.
+ */
+Socket *ssh_connection_sharing_init(
+    const char *host, int port, Conf *conf, LogContext *logctx,
+    Plug *sshplug, ssh_sharing_state **state)
+{
+    int result;
+    bool can_upstream, can_downstream;
+    char *logtext, *ds_err, *us_err;
+    char *sockname;
+    Socket *sock, *toret = NULL;
+    struct ssh_sharing_state *sharestate;
+
+    if (!conf_get_bool(conf, CONF_ssh_connection_sharing))
+        return NULL;                   /* do not share anything */
+    can_upstream = share_can_be_upstream &&
+        conf_get_bool(conf, CONF_ssh_connection_sharing_upstream);
+    can_downstream = share_can_be_downstream &&
+        conf_get_bool(conf, CONF_ssh_connection_sharing_downstream);
+    if (!can_upstream && !can_downstream)
+        return NULL;
+
+    sockname = ssh_share_sockname(host, port, conf);
+
+    /*
+     * Create a data structure for the listening plug if we turn out
+     * to be an upstream.
+     */
+    sharestate = snew(struct ssh_sharing_state);
+    sharestate->plug.vt = &ssh_sharing_listen_plugvt;
+    sharestate->listensock = NULL;
+    sharestate->cl = NULL;
+
+    /*
+     * Now hand off to a per-platform routine that either connects to
+     * an existing upstream (using 'ssh' as the plug), establishes our
+     * own upstream (using 'sharestate' as the plug), or forks off a
+     * separate upstream and then connects to that. It will return a
+     * code telling us which kind of socket it put in 'sock'.
+     */
+    sock = NULL;
+    logtext = ds_err = us_err = NULL;
+    result = platform_ssh_share(
+        sockname, conf, sshplug, &sharestate->plug, &sock, &logtext,
+        &ds_err, &us_err, can_upstream, can_downstream);
+    switch (result) {
+      case SHARE_NONE:
+        /*
+         * We aren't sharing our connection at all (e.g. something
+         * went wrong setting the socket up). Free the upstream
+         * structure and return NULL.
+         */
+
+        if (logtext) {
+            /* For this result, if 'logtext' is not NULL then it is an
+             * error message indicating a reason why connection sharing
+             * couldn't be set up _at all_ */
+            logeventf(logctx,
+                      "Could not set up connection sharing: %s", logtext);
+        } else {
+            /* Failing that, ds_err and us_err indicate why we
+             * couldn't be a downstream and an upstream respectively */
+            if (ds_err)
+                logeventf(logctx, "Could not set up connection sharing"
+                          " as downstream: %s", ds_err);
+            if (us_err)
+                logeventf(logctx, "Could not set up connection sharing"
+                          " as upstream: %s", us_err);
+        }
+
+        assert(sock == NULL);
+        *state = NULL;
+        sfree(sharestate);
+        sfree(sockname);
+        break;
+
+      case SHARE_DOWNSTREAM:
+        /*
+         * We are downstream, so free sharestate which it turns out we
+         * don't need after all, and return the downstream socket as a
+         * replacement for an ordinary SSH connection.
+         */
+
+        /* 'logtext' is a local endpoint address */
+        logeventf(logctx, "Using existing shared connection at %s", logtext);
+
+        *state = NULL;
+        sfree(sharestate);
+        sfree(sockname);
+        toret = sock;
+        break;
+
+      case SHARE_UPSTREAM:
+        /*
+         * We are upstream. Set up sharestate properly and pass a copy
+         * to the caller; return NULL, to tell ssh.c that it has to
+         * make an ordinary connection after all.
+         */
+
+        /* 'logtext' is a local endpoint address */
+        logeventf(logctx, "Sharing this connection at %s", logtext);
+
+        *state = sharestate;
+        sharestate->listensock = sock;
+        sharestate->connections = newtree234(share_connstate_cmp);
+        sharestate->server_verstring = NULL;
+        sharestate->sockname = sockname;
+        sharestate->nextid = 1;
+        break;
+    }
+
+    sfree(logtext);
+    sfree(ds_err);
+    sfree(us_err);
+    return toret;
+}

+ 53 - 0
source/putty/ssh/signal-list.h

@@ -0,0 +1,53 @@
+/*
+ * List of signal names known to SSH, indicating whether PuTTY's UI
+ * for special session commands likes to put them in the main specials
+ * menu or in a submenu (and if the former, what title they have).
+ *
+ * This is a separate header file rather than my usual style of a
+ * parametric list macro, because in this case I need to be able to
+ * #ifdef out each mode in case it's not defined on a particular
+ * target system.
+ *
+ * If you want only the locally defined signals, #define
+ * SIGNALS_LOCAL_ONLY before including this header.
+ */
+
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGINT
+SIGNAL_MAIN(INT, "Interrupt")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGTERM
+SIGNAL_MAIN(TERM, "Terminate")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGKILL
+SIGNAL_MAIN(KILL, "Kill")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGQUIT
+SIGNAL_MAIN(QUIT, "Quit")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGHUP
+SIGNAL_MAIN(HUP, "Hangup")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGABRT
+SIGNAL_SUB(ABRT)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGALRM
+SIGNAL_SUB(ALRM)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGFPE
+SIGNAL_SUB(FPE)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGILL
+SIGNAL_SUB(ILL)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGPIPE
+SIGNAL_SUB(PIPE)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGSEGV
+SIGNAL_SUB(SEGV)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR1
+SIGNAL_SUB(USR1)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR2
+SIGNAL_SUB(USR2)
+#endif

+ 1391 - 0
source/putty/ssh/ssh.c

@@ -0,0 +1,1391 @@
+/*
+ * SSH backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <limits.h>
+#include <signal.h>
+
+#include "putty.h"
+#include "pageant.h" /* for AGENT_MAX_MSGLEN */
+#include "tree234.h"
+#include "storage.h"
+#include "marshal.h"
+#include "ssh.h"
+#include "sshcr.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#ifndef NO_GSSAPI
+#include "gssc.h"
+#include "gss.h"
+#define MIN_CTXT_LIFETIME 5     /* Avoid rekey with short lifetime (seconds) */
+#define GSS_KEX_CAPABLE (1<<0)  /* Can do GSS KEX */
+#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */
+#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */
+#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */
+#endif
+
+struct Ssh {
+    Socket *s;
+    Seat *seat;
+    Conf *conf;
+
+    struct ssh_version_receiver version_receiver;
+    int remote_bugs;
+
+    Plug plug;
+    Backend backend;
+    Interactor interactor;
+
+    Ldisc *ldisc;
+    LogContext *logctx;
+
+    /* The last list returned from get_specials. */
+    SessionSpecial *specials;
+
+    bool bare_connection;
+    ssh_sharing_state *connshare;
+    bool attempting_connshare;
+
+#ifndef NO_GSSAPI
+    struct ssh_connection_shared_gss_state gss_state;
+#endif
+
+    char *savedhost;
+    int savedport;
+    char *fullhostname;
+    char *description;
+
+    bool fallback_cmd;
+    int exitcode;
+
+    int version;
+    int conn_throttle_count;
+    size_t overall_bufsize;
+    bool throttled_all;
+
+    /*
+     * logically_frozen is true if we're not currently _processing_
+     * data from the SSH socket (e.g. because a higher layer has asked
+     * us not to due to ssh_throttle_conn). socket_frozen is true if
+     * we're not even _reading_ data from the socket (i.e. it should
+     * always match the value we last passed to sk_set_frozen).
+     *
+     * The two differ in that socket_frozen can also become
+     * temporarily true because of a large backlog in the in_raw
+     * bufchain, to force no further plug_receive events until the BPP
+     * input function has had a chance to run. (Some front ends, like
+     * GTK, can persistently call the network and never get round to
+     * the toplevel callbacks.) If we've stopped reading from the
+     * socket for that reason, we absolutely _do_ want to carry on
+     * processing our input bufchain, because that's the only way
+     * it'll ever get cleared!
+     *
+     * ssh_check_frozen() resets socket_frozen, and should be called
+     * whenever either of logically_frozen and the bufchain size
+     * changes.
+     */
+    bool logically_frozen, socket_frozen;
+
+    /* in case we find these out before we have a ConnectionLayer to tell */
+    int term_width, term_height;
+
+    bufchain in_raw, out_raw, user_input;
+    bool pending_close;
+    IdempotentCallback ic_out_raw;
+
+    PacketLogSettings pls;
+    struct DataTransferStats stats;
+
+    BinaryPacketProtocol *bpp;
+
+    /*
+     * base_layer identifies the bottommost packet protocol layer, the
+     * one connected directly to the BPP's packet queues. Any
+     * operation that needs to talk to all layers (e.g. free, or
+     * get_specials) will do it by talking to this, which will
+     * recursively propagate it if necessary.
+     */
+    PacketProtocolLayer *base_layer;
+
+    /*
+     * The ConnectionLayer vtable from our connection layer.
+     */
+    ConnectionLayer *cl;
+
+    /*
+     * A dummy ConnectionLayer that can be used for logging sharing
+     * downstreams that connect before the real one is ready.
+     */
+    ConnectionLayer cl_dummy;
+
+    /*
+     * session_started is false until we initialise the main protocol
+     * layers. So it distinguishes between base_layer==NULL meaning
+     * that the SSH protocol hasn't been set up _yet_, and
+     * base_layer==NULL meaning the SSH protocol has run and finished.
+     * It's also used to mark the point where we stop counting proxy
+     * command diagnostics as pre-session-startup.
+     */
+    bool session_started;
+
+    Pinger *pinger;
+
+    char *deferred_abort_message;
+
+    bool need_random_unref;
+};
+
+
+#define ssh_logevent(params) ( \
+        logevent_and_free((ssh)->logctx, dupprintf params))
+
+static void ssh_shutdown(Ssh *ssh);
+static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize);
+static void ssh_bpp_output_raw_data_callback(void *vctx);
+
+LogContext *ssh_get_logctx(Ssh *ssh)
+{
+    return ssh->logctx;
+}
+
+static void ssh_connect_bpp(Ssh *ssh)
+{
+    ssh->bpp->ssh = ssh;
+    ssh->bpp->in_raw = &ssh->in_raw;
+    ssh->bpp->out_raw = &ssh->out_raw;
+    bufchain_set_callback(ssh->bpp->out_raw, &ssh->ic_out_raw);
+    ssh->bpp->pls = &ssh->pls;
+    ssh->bpp->logctx = ssh->logctx;
+    ssh->bpp->remote_bugs = ssh->remote_bugs;
+}
+
+static void ssh_connect_ppl(Ssh *ssh, PacketProtocolLayer *ppl)
+{
+    ppl->bpp = ssh->bpp;
+    ppl->seat = ssh->seat;
+    ppl->interactor = &ssh->interactor;
+    ppl->ssh = ssh;
+    ppl->logctx = ssh->logctx;
+    ppl->remote_bugs = ssh->remote_bugs;
+}
+
+static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
+                                int major_version)
+{
+    Ssh *ssh = container_of(rcv, Ssh, version_receiver);
+    BinaryPacketProtocol *old_bpp;
+    PacketProtocolLayer *connection_layer;
+
+    ssh->session_started = true;
+
+    /*
+     * We don't support choosing a major protocol version dynamically,
+     * so this should always be the same value we set up in
+     * connect_to_host().
+     */
+    assert(ssh->version == major_version);
+    assert(ssh->version == 2);
+
+    old_bpp = ssh->bpp;
+    ssh->remote_bugs = ssh_verstring_get_bugs(old_bpp);
+
+    if (!ssh->bare_connection) {
+        /*WINSCP if (ssh->version == 2)*/ {
+            PacketProtocolLayer *userauth_layer, *transport_child_layer;
+
+            /*
+             * We use the 'simple' variant of the SSH protocol if
+             * we're asked to, except not if we're also doing
+             * connection-sharing (either tunnelling our packets over
+             * an upstream or expecting to be tunnelled over
+             * ourselves), since then the assumption that we have only
+             * one channel to worry about is not true after all.
+             */
+            bool is_simple =
+                (conf_get_bool(ssh->conf, CONF_ssh_simple) && !ssh->connshare);
+
+            ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats, false);
+            ssh_connect_bpp(ssh);
+
+#ifndef NO_GSSAPI
+            /* Load and pick the highest GSS library on the preference
+             * list. */
+            if (!ssh->gss_state.libs)
+                ssh->gss_state.libs = ssh_gss_setup(ssh->conf, ssh->logctx); // WINSCP
+            ssh->gss_state.lib = NULL;
+            if (ssh->gss_state.libs->nlibraries > 0) {
+                int i, j;
+                for (i = 0; i < ngsslibs; i++) {
+                    int want_id = conf_get_int_int(ssh->conf,
+                                                   CONF_ssh_gsslist, i);
+                    for (j = 0; j < ssh->gss_state.libs->nlibraries; j++)
+                        if (ssh->gss_state.libs->libraries[j].id == want_id) {
+                            ssh->gss_state.lib =
+                                &ssh->gss_state.libs->libraries[j];
+                            goto got_gsslib;   /* double break */
+                        }
+                }
+              got_gsslib:
+                /*
+                 * We always expect to have found something in
+                 * the above loop: we only came here if there
+                 * was at least one viable GSS library, and the
+                 * preference list should always mention
+                 * everything and only change the order.
+                 */
+                assert(ssh->gss_state.lib);
+            }
+#endif
+
+            connection_layer = ssh2_connection_new(
+                ssh, ssh->connshare, is_simple, ssh->conf,
+                ssh_verstring_get_remote(old_bpp), &ssh->user_input, &ssh->cl);
+            ssh_connect_ppl(ssh, connection_layer);
+
+            if (conf_get_bool(ssh->conf, CONF_ssh_no_userauth)) {
+                userauth_layer = NULL;
+                transport_child_layer = connection_layer;
+            } else {
+                char *username = get_remote_username(ssh->conf);
+
+                userauth_layer = ssh2_userauth_new(
+                    connection_layer, ssh->savedhost, ssh->fullhostname,
+                    conf_get_filename(ssh->conf, CONF_keyfile),
+                    conf_get_bool(ssh->conf, CONF_ssh_show_banner),
+                    conf_get_bool(ssh->conf, CONF_tryagent),
+                    conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth),
+                    username,
+                    conf_get_bool(ssh->conf, CONF_change_username),
+                    conf_get_bool(ssh->conf, CONF_try_ki_auth),
+#ifndef NO_GSSAPI
+                    conf_get_bool(ssh->conf, CONF_try_gssapi_auth),
+                    conf_get_bool(ssh->conf, CONF_try_gssapi_kex),
+                    conf_get_bool(ssh->conf, CONF_gssapifwd),
+                    &ssh->gss_state
+#else
+                    false,
+                    false,
+                    false,
+                    NULL
+#endif
+                    ,conf_get_str(ssh->conf, CONF_loghost),
+                    conf_get_bool(ssh->conf, CONF_change_password), // WINSCP
+                    ssh->seat
+                    );
+                ssh_connect_ppl(ssh, userauth_layer);
+                transport_child_layer = userauth_layer;
+
+                sfree(username);
+            }
+
+            ssh->base_layer = ssh2_transport_new(
+                ssh->conf, ssh->savedhost, ssh->savedport,
+                ssh->fullhostname,
+                ssh_verstring_get_local(old_bpp),
+                ssh_verstring_get_remote(old_bpp),
+#ifndef NO_GSSAPI
+                &ssh->gss_state,
+#else
+                NULL,
+#endif
+                &ssh->stats, transport_child_layer, NULL);
+            ssh_connect_ppl(ssh, ssh->base_layer);
+
+            if (userauth_layer)
+                ssh2_userauth_set_transport_layer(userauth_layer,
+                                                  ssh->base_layer);
+
+        } // WINSCP
+
+    } else {
+        ssh->bpp = ssh2_bare_bpp_new(ssh->logctx);
+        ssh_connect_bpp(ssh);
+
+        connection_layer = ssh2_connection_new(
+            ssh, ssh->connshare, false, ssh->conf,
+            ssh_verstring_get_remote(old_bpp), &ssh->user_input, &ssh->cl);
+        ssh_connect_ppl(ssh, connection_layer);
+        ssh->base_layer = connection_layer;
+    }
+
+    /* Connect the base layer - whichever it is - to the BPP, and set
+     * up its selfptr. */
+    ssh->base_layer->selfptr = &ssh->base_layer;
+    ssh_ppl_setup_queues(ssh->base_layer, &ssh->bpp->in_pq, &ssh->bpp->out_pq);
+
+    seat_update_specials_menu(ssh->seat);
+    ssh->pinger = pinger_new(ssh->conf, &ssh->backend);
+
+    queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+    ssh_ppl_process_queue(ssh->base_layer);
+
+    /* Pass in the initial terminal size, if we knew it already. */
+    ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height);
+
+    ssh_bpp_free(old_bpp);
+}
+
+void ssh_check_frozen(Ssh *ssh)
+{
+    if (!ssh->s)
+        return;
+
+    { // WINSCP
+    bool prev_frozen = ssh->socket_frozen;
+    ssh->socket_frozen = (ssh->logically_frozen ||
+                          bufchain_size(&ssh->in_raw) > SSH_MAX_BACKLOG);
+    sk_set_frozen(ssh->s, ssh->socket_frozen);
+    if (prev_frozen && !ssh->socket_frozen && ssh->bpp) {
+        /*
+         * If we've just unfrozen, process any SSH connection data
+         * that was stashed in our queue while we were frozen.
+         */
+        queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+    }
+    } // WINSCP
+}
+
+void ssh_conn_processed_data(Ssh *ssh)
+{
+    ssh_check_frozen(ssh);
+}
+
+static void ssh_bpp_output_raw_data_callback(void *vctx)
+{
+    Ssh *ssh = (Ssh *)vctx;
+
+    if (!ssh->s)
+        return;
+
+    while (bufchain_size(&ssh->out_raw) > 0) {
+        size_t backlog;
+
+        ptrlen data = bufchain_prefix(&ssh->out_raw);
+
+        if (ssh->logctx)
+            log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len,
+                       0, NULL, NULL, 0, NULL);
+        backlog = sk_write(ssh->s, data.ptr, data.len);
+
+        bufchain_consume(&ssh->out_raw, data.len);
+
+        if (backlog > SSH_MAX_BACKLOG) {
+            ssh_throttle_all(ssh, true, backlog);
+            return;
+        }
+    }
+
+    ssh_check_frozen(ssh);
+
+    if (ssh->pending_close) {
+        sk_close(ssh->s);
+        ssh->s = NULL;
+        seat_notify_remote_disconnect(ssh->seat);
+    }
+}
+
+static void ssh_shutdown_internal(Ssh *ssh)
+{
+    expire_timer_context(ssh);
+
+    if (ssh->connshare) {
+        sharestate_free(ssh->connshare);
+        ssh->connshare = NULL;
+    }
+
+    if (ssh->pinger) {
+        pinger_free(ssh->pinger);
+        ssh->pinger = NULL;
+    }
+
+    /*
+     * We only need to free the base PPL, which will free the others
+     * (if any) transitively.
+     */
+    if (ssh->base_layer) {
+        ssh_ppl_free(ssh->base_layer);
+        ssh->base_layer = NULL;
+    }
+
+    ssh->cl = NULL;
+}
+
+static void ssh_shutdown(Ssh *ssh)
+{
+    ssh_shutdown_internal(ssh);
+
+    if (ssh->bpp) {
+        ssh_bpp_free(ssh->bpp);
+        ssh->bpp = NULL;
+    }
+
+    if (ssh->s) {
+        sk_close(ssh->s);
+        ssh->s = NULL;
+        seat_notify_remote_disconnect(ssh->seat);
+    }
+
+    bufchain_clear(&ssh->in_raw);
+    bufchain_clear(&ssh->out_raw);
+    bufchain_clear(&ssh->user_input);
+}
+
+static void ssh_initiate_connection_close(Ssh *ssh)
+{
+    /* Wind up everything above the BPP. */
+    ssh_shutdown_internal(ssh);
+
+    /* Force any remaining queued SSH packets through the BPP, and
+     * schedule closing the network socket after they go out. */
+    ssh_bpp_handle_output(ssh->bpp);
+    ssh->pending_close = true;
+    queue_idempotent_callback(&ssh->ic_out_raw);
+
+    /* Now we expect the other end to close the connection too in
+     * response, so arrange that we'll receive notification of that
+     * via ssh_remote_eof. */
+    ssh->bpp->expect_close = true;
+}
+
+#define GET_FORMATTED_MSG                       \
+    char *msg;                                  \
+    va_list ap;                                 \
+    va_start(ap, fmt);                          \
+    msg = dupvprintf(fmt, ap);                  \
+    va_end(ap);                                 \
+    ((void)0) /* eat trailing semicolon */
+
+void ssh_remote_error(Ssh *ssh, const char *fmt, ...)
+{
+    if (ssh->base_layer || !ssh->session_started) {
+        GET_FORMATTED_MSG;
+
+        /* Error messages sent by the remote don't count as clean exits */
+        ssh->exitcode = 128;
+
+        /* Close the socket immediately, since the server has already
+         * closed its end (or is about to). */
+        ssh_shutdown(ssh);
+
+        logevent(ssh->logctx, msg);
+        seat_connection_fatal(ssh->seat, "%s", msg);
+        sfree(msg);
+    }
+}
+
+void ssh_remote_eof(Ssh *ssh, const char *fmt, ...)
+{
+    if (ssh->base_layer || !ssh->session_started) {
+        GET_FORMATTED_MSG;
+
+        /* EOF from the remote, if we were expecting it, does count as
+         * a clean exit */
+        ssh->exitcode = 0;
+
+        /* Close the socket immediately, since the server has already
+         * closed its end. */
+        ssh_shutdown(ssh);
+
+        logevent(ssh->logctx, msg);
+        sfree(msg);
+        seat_notify_remote_exit(ssh->seat);
+    } else {
+        /* This is responding to EOF after we've already seen some
+         * other reason for terminating the session. */
+        ssh_shutdown(ssh);
+    }
+}
+
+void ssh_proto_error(Ssh *ssh, const char *fmt, ...)
+{
+    if (ssh->base_layer || !ssh->session_started) {
+        GET_FORMATTED_MSG;
+
+        ssh->exitcode = 128;
+
+        ssh_bpp_queue_disconnect(ssh->bpp, msg,
+                                 SSH2_DISCONNECT_PROTOCOL_ERROR);
+        ssh_initiate_connection_close(ssh);
+
+        logevent(ssh->logctx, msg);
+        seat_connection_fatal(ssh->seat, "%s", msg);
+        sfree(msg);
+    }
+}
+
+void ssh_sw_abort(Ssh *ssh, const char *fmt, ...)
+{
+    if (ssh->base_layer || !ssh->session_started) {
+        GET_FORMATTED_MSG;
+
+        ssh->exitcode = 128;
+
+        ssh_initiate_connection_close(ssh);
+
+        logevent(ssh->logctx, msg);
+        seat_connection_fatal(ssh->seat, "%s", msg);
+        sfree(msg);
+
+        seat_notify_remote_exit(ssh->seat);
+    }
+}
+
+void ssh_user_close(Ssh *ssh, const char *fmt, ...)
+{
+    if (ssh->base_layer || !ssh->session_started) {
+        GET_FORMATTED_MSG;
+
+        /* Closing the connection due to user action, even if the
+         * action is the user aborting during authentication prompts,
+         * does count as a clean exit - except that this is also how
+         * we signal ordinary session termination, in which case we
+         * should use the exit status already sent from the main
+         * session (if any). */
+        if (ssh->exitcode < 0)
+            ssh->exitcode = 0;
+
+        ssh_initiate_connection_close(ssh);
+
+        logevent(ssh->logctx, msg);
+        sfree(msg);
+
+        seat_notify_remote_exit(ssh->seat);
+    }
+}
+
+static void ssh_deferred_abort_callback(void *vctx)
+{
+    Ssh *ssh = (Ssh *)vctx;
+    char *msg = ssh->deferred_abort_message;
+    ssh->deferred_abort_message = NULL;
+    ssh_sw_abort(ssh, "%s", msg);
+    sfree(msg);
+}
+
+void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...)
+{
+    if (!ssh->deferred_abort_message) {
+        GET_FORMATTED_MSG;
+        ssh->deferred_abort_message = msg;
+        queue_toplevel_callback(get_seat_callback_set(ssh->seat), ssh_deferred_abort_callback, ssh);
+    }
+}
+
+static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr,
+                           int port, const char *error_msg, int error_code)
+{
+    Ssh *ssh = container_of(plug, Ssh, plug);
+
+    /*
+     * While we're attempting connection sharing, don't loudly log
+     * everything that happens. Real TCP connections need to be logged
+     * when we _start_ trying to connect, because it might be ages
+     * before they respond if something goes wrong; but connection
+     * sharing is local and quick to respond, and it's sufficient to
+     * simply wait and see whether it worked afterwards.
+     */
+
+    if (!ssh->attempting_connshare)
+        backend_socket_log(ssh->seat, ssh->logctx, type, addr, port,
+                           error_msg, error_code, ssh->conf,
+                           ssh->session_started);
+}
+
+static void ssh_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{
+    Ssh *ssh = container_of(plug, Ssh, plug);
+    if (type == PLUGCLOSE_USER_ABORT) {
+        ssh_user_close(ssh, "%s", error_msg);
+    } else if (type != PLUGCLOSE_NORMAL) {
+        ssh_remote_error(ssh, "%s", error_msg);
+    } else if (ssh->bpp) {
+        ssh->bpp->input_eof = true;
+        queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+    }
+}
+
+static void ssh_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+    Ssh *ssh = container_of(plug, Ssh, plug);
+
+    /* Log raw data, if we're in that mode. */
+    if (ssh->logctx)
+        log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, len,
+                   0, NULL, NULL, 0, NULL);
+
+    bufchain_add(&ssh->in_raw, data, len);
+    if (!ssh->logically_frozen && ssh->bpp)
+        queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+
+    ssh_check_frozen(ssh);
+}
+
+static void ssh_sent(Plug *plug, size_t bufsize)
+{
+    Ssh *ssh = container_of(plug, Ssh, plug);
+    /*
+     * If the send backlog on the SSH socket itself clears, we should
+     * unthrottle the whole world if it was throttled. Also trigger an
+     * extra call to the consumer of the BPP's output, to try to send
+     * some more data off its bufchain.
+     */
+    if (bufsize < SSH_MAX_BACKLOG) {
+        ssh_throttle_all(ssh, false, bufsize);
+        queue_idempotent_callback(&ssh->ic_out_raw);
+        ssh_sendbuffer_changed(ssh);
+    }
+}
+
+static void ssh_hostport_setup(const char *host, int port, Conf *conf,
+                               char **savedhost, int *savedport,
+                               char **loghost_ret)
+{
+    char *loghost = conf_get_str(conf, CONF_loghost);
+    if (loghost_ret)
+        *loghost_ret = loghost;
+
+    if (*loghost) {
+        char *tmphost;
+        char *colon;
+
+        tmphost = dupstr(loghost);
+        *savedport = 22;               /* default ssh port */
+
+        /*
+         * A colon suffix on the hostname string also lets us affect
+         * savedport. (Unless there are multiple colons, in which case
+         * we assume this is an unbracketed IPv6 literal.)
+         */
+        colon = host_strrchr(tmphost, ':');
+        if (colon && colon == host_strchr(tmphost, ':')) {
+            *colon++ = '\0';
+            if (*colon)
+                *savedport = atoi(colon);
+        }
+
+        *savedhost = host_strduptrim(tmphost);
+        sfree(tmphost);
+    } else {
+        *savedhost = host_strduptrim(host);
+        if (port < 0)
+            port = 22;                 /* default ssh port */
+        *savedport = port;
+    }
+}
+
+static bool ssh_test_for_upstream(const char *host, int port, Conf *conf)
+{
+    char *savedhost;
+    int savedport;
+    bool ret;
+
+    random_ref(); /* platform may need this to determine share socket name */
+    ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL);
+    ret = ssh_share_test_for_upstream(savedhost, savedport, conf);
+    sfree(savedhost);
+    random_unref();
+
+    return ret;
+}
+
+static char *ssh_close_warn_text(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    if (!ssh->connshare)
+        return NULL;
+    { // WINSCP
+    int ndowns = share_ndownstreams(ssh->connshare);
+    if (ndowns == 0)
+        return NULL;
+    { // WINSCP
+    char *msg = dupprintf("This will also close %d downstream connection%s.",
+                          ndowns, ndowns==1 ? "" : "s");
+    return msg;
+    } // WINSCP
+    } // WINSCP
+}
+
+static const PlugVtable Ssh_plugvt = {
+    // WINSCP
+    /*.log =*/ ssh_socket_log,
+    /*.closing =*/ ssh_closing,
+    /*.receive =*/ ssh_receive,
+    /*.sent =*/ ssh_sent,
+    // NULL
+};
+
+static char *ssh_description(Interactor *itr)
+{
+    Ssh *ssh = container_of(itr, Ssh, interactor);
+    return dupstr(ssh->description);
+}
+
+static LogPolicy *ssh_logpolicy(Interactor *itr)
+{
+    Ssh *ssh = container_of(itr, Ssh, interactor);
+    return log_get_policy(ssh->logctx);
+}
+
+static Seat *ssh_get_seat(Interactor *itr)
+{
+    Ssh *ssh = container_of(itr, Ssh, interactor);
+    return ssh->seat;
+}
+
+static void ssh_set_seat(Interactor *itr, Seat *seat)
+{
+    Ssh *ssh = container_of(itr, Ssh, interactor);
+    ssh->seat = seat;
+}
+
+static const InteractorVtable Ssh_interactorvt = {
+    // WINSCP
+    /*.description =*/ ssh_description,
+    /*.logpolicy =*/ ssh_logpolicy,
+    /*.get_seat =*/ ssh_get_seat,
+    /*.set_seat =*/ ssh_set_seat,
+};
+
+/*
+ * Connect to specified host and port.
+ * Returns an error message, or NULL on success.
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *connect_to_host(
+    Ssh *ssh, const char *host, int port, char *loghost, char **realhost,
+    bool nodelay, bool keepalive)
+{
+    SockAddr *addr;
+    const char *err;
+    int addressfamily;
+
+    ssh->plug.vt = &Ssh_plugvt;
+    
+    #ifdef MPEXT
+    // make sure the field is initialized, in case lookup below fails
+    ssh->fullhostname = NULL;
+    #endif
+
+    /*
+     * Try connection-sharing, in case that means we don't open a
+     * socket after all. ssh_connection_sharing_init will connect to a
+     * previously established upstream if it can, and failing that,
+     * establish a listening socket for _us_ to be the upstream. In
+     * the latter case it will return NULL just as if it had done
+     * nothing, because here we only need to care if we're a
+     * downstream and need to do our connection setup differently.
+     */
+    ssh->connshare = NULL;
+    ssh->attempting_connshare = true;  /* affects socket logging behaviour */
+    ssh->s = ssh_connection_sharing_init(
+        ssh->savedhost, ssh->savedport, ssh->conf, ssh->logctx,
+        &ssh->plug, &ssh->connshare);
+    if (ssh->connshare)
+        ssh_connshare_provide_connlayer(ssh->connshare, &ssh->cl_dummy);
+    ssh->attempting_connshare = false;
+    if (ssh->s != NULL) {
+        /*
+         * We are a downstream.
+         */
+        ssh->bare_connection = true;
+        ssh->fullhostname = NULL;
+        *realhost = dupstr(host);      /* best we can do */
+
+        if (seat_verbose(ssh->seat) || seat_interactive(ssh->seat)) {
+            /* In an interactive session, or in verbose mode, announce
+             * in the console window that we're a sharing downstream,
+             * to avoid confusing users as to why this session doesn't
+             * behave in quite the usual way. */
+            const char *msg =
+                "Reusing a shared connection to this server.\r\n";
+            seat_stderr_pl(ssh->seat, ptrlen_from_asciz(msg));
+        }
+    } else {
+        /*
+         * We're not a downstream, so open a normal socket.
+         */
+
+        /*
+         * Try to find host.
+         */
+        addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
+        addr = name_lookup(host, port, realhost, ssh->conf, addressfamily,
+                           ssh->logctx, "SSH connection");
+        if ((err = sk_addr_error(addr)) != NULL) {
+            sk_addr_free(addr);
+            return dupstr(err);
+        }
+        ssh->fullhostname = dupstr(*realhost);   /* save in case of GSSAPI */
+
+        ssh->s = new_connection(addr, *realhost, port,
+                                false, true, nodelay, keepalive,
+                                &ssh->plug, ssh->conf, &ssh->interactor);
+        if ((err = sk_socket_error(ssh->s)) != NULL) {
+            ssh->s = NULL;
+            seat_notify_remote_exit(ssh->seat);
+            seat_notify_remote_disconnect(ssh->seat);
+            return dupstr(err);
+        }
+    }
+
+    /*
+     * The SSH version number is always fixed (since we no longer support
+     * fallback between versions), so set it now.
+     */
+        /* SSH-2 only */
+        ssh->version = 2; // WINSCP
+
+    /*
+     * Set up the initial BPP that will do the version string
+     * exchange, and get it started so that it can send the outgoing
+     * version string early if it wants to.
+     */
+    ssh->version_receiver.got_ssh_version = ssh_got_ssh_version;
+    ssh->bpp = ssh_verstring_new(
+        ssh->conf, ssh->logctx, ssh->bare_connection,
+        ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver,
+        false, ""); // WINSCP (impl_name provided in sshver already)
+    ssh_connect_bpp(ssh);
+    queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+
+    /*
+     * loghost, if configured, overrides realhost.
+     */
+    if (*loghost) {
+        sfree(*realhost);
+        *realhost = dupstr(loghost);
+    }
+
+    return NULL;
+}
+
+/*
+ * Throttle or unthrottle the SSH connection.
+ */
+void ssh_throttle_conn(Ssh *ssh, int adjust)
+{
+    int old_count = ssh->conn_throttle_count;
+    bool frozen;
+
+    ssh->conn_throttle_count += adjust;
+    assert(ssh->conn_throttle_count >= 0);
+
+    if (ssh->conn_throttle_count && !old_count) {
+        frozen = true;
+    } else if (!ssh->conn_throttle_count && old_count) {
+        frozen = false;
+    } else {
+        return;                /* don't change current frozen state */
+    }
+
+    ssh->logically_frozen = frozen;
+    ssh_check_frozen(ssh);
+}
+
+/*
+ * Throttle or unthrottle _all_ local data streams (for when sends
+ * on the SSH connection itself back up).
+ */
+static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize)
+{
+    if (enable == ssh->throttled_all)
+        return;
+    ssh->throttled_all = enable;
+    ssh->overall_bufsize = bufsize;
+
+    ssh_throttle_all_channels(ssh->cl, enable);
+}
+
+static void ssh_cache_conf_values(Ssh *ssh)
+{
+    ssh->pls.omit_passwords = conf_get_bool(ssh->conf, CONF_logomitpass);
+    ssh->pls.omit_data = conf_get_bool(ssh->conf, CONF_logomitdata);
+}
+
+bool ssh_is_bare(Ssh *ssh)
+{
+    return ssh->backend.vt->protocol == PROT_SSHCONN;
+}
+
+/* Dummy connlayer must provide ssh_sharing_no_more_downstreams,
+ * because it might be called early due to plink -shareexists */
+static void dummy_sharing_no_more_downstreams(ConnectionLayer *cl) {}
+static const ConnectionLayerVtable dummy_connlayer_vtable = {
+    // WINSCP
+    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+    /*.sharing_no_more_downstreams =*/ dummy_sharing_no_more_downstreams,
+    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+/*
+ * Called to set up the connection.
+ *
+ * Returns an error message, or NULL on success.
+ */
+static char *ssh_init(const BackendVtable *vt, Seat *seat,
+                      Backend **backend_handle, LogContext *logctx,
+                      Conf *conf, const char *host, int port,
+                      char **realhost, bool nodelay, bool keepalive)
+{
+    Ssh *ssh;
+
+    ssh = snew(Ssh);
+    memset(ssh, 0, sizeof(Ssh));
+
+    ssh->conf = conf_copy(conf);
+    ssh_cache_conf_values(ssh);
+    ssh->exitcode = -1;
+    ssh->pls.kctx = SSH2_PKTCTX_NOKEX;
+    ssh->pls.actx = SSH2_PKTCTX_NOAUTH;
+    bufchain_init(&ssh->in_raw);
+    bufchain_init(&ssh->out_raw);
+    bufchain_init(&ssh->user_input);
+    ssh->ic_out_raw.fn = ssh_bpp_output_raw_data_callback;
+    ssh->ic_out_raw.ctx = ssh;
+    ssh->ic_out_raw.set = get_seat_callback_set(seat);
+
+    ssh->term_width = conf_get_int(ssh->conf, CONF_width);
+    ssh->term_height = conf_get_int(ssh->conf, CONF_height);
+
+    ssh->backend.vt = vt;
+    ssh->interactor.vt = &Ssh_interactorvt;
+    ssh->backend.interactor = &ssh->interactor;
+    *backend_handle = &ssh->backend;
+
+    ssh->bare_connection = (vt->protocol == PROT_SSHCONN);
+
+    ssh->seat = seat;
+    ssh->cl_dummy.vt = &dummy_connlayer_vtable;
+    ssh->cl_dummy.logctx = ssh->logctx = logctx;
+
+    { // WINSCP
+    char *loghost;
+
+    ssh_hostport_setup(host, port, ssh->conf,
+                       &ssh->savedhost, &ssh->savedport, &loghost);
+    ssh->description = default_description(vt, ssh->savedhost, ssh->savedport);
+
+    random_ref(); /* do this now - may be needed by sharing setup code */
+    ssh->need_random_unref = true;
+
+    { // WINSCP
+    char *conn_err = connect_to_host(
+        ssh, host, port, loghost, realhost, nodelay, keepalive);
+    if (conn_err) {
+        /* Call random_unref now instead of waiting until the caller
+         * frees this useless Ssh object, in case the caller is
+         * impatient and just exits without bothering, in which case
+         * the random seed won't be re-saved. */
+        ssh->need_random_unref = false;
+        random_unref();
+        return conn_err;
+    }
+
+    return NULL;
+    } // WINSCP
+    } // WINSCP
+}
+
+static void ssh_free(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    bool need_random_unref;
+
+    ssh_shutdown(ssh);
+
+    if (is_tempseat(ssh->seat))
+        tempseat_free(ssh->seat);
+
+    conf_free(ssh->conf);
+    if (ssh->connshare)
+        sharestate_free(ssh->connshare);
+    sfree(ssh->savedhost);
+    sfree(ssh->fullhostname);
+    sfree(ssh->specials);
+
+#ifndef NO_GSSAPI
+    if (ssh->gss_state.srv_name)
+        ssh->gss_state.lib->release_name(
+            ssh->gss_state.lib, &ssh->gss_state.srv_name);
+    if (ssh->gss_state.ctx != NULL)
+        ssh->gss_state.lib->release_cred(
+            ssh->gss_state.lib, &ssh->gss_state.ctx);
+    if (ssh->gss_state.libs)
+        ssh_gss_cleanup(ssh->gss_state.libs);
+#endif
+
+    sfree(ssh->deferred_abort_message);
+    sfree(ssh->description);
+
+    delete_callbacks_for_context(get_seat_callback_set(ssh->seat), ssh); /* likely to catch ic_out_raw */ // WINSCP (seat)
+
+    need_random_unref = ssh->need_random_unref;
+    sfree(ssh);
+
+    if (need_random_unref)
+        random_unref();
+}
+
+/*
+ * Reconfigure the SSH backend.
+ */
+static void ssh_reconfig(Backend *be, Conf *conf)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+
+    if (ssh->pinger)
+        pinger_reconfig(ssh->pinger, ssh->conf, conf);
+
+    ssh_ppl_reconfigure(ssh->base_layer, conf);
+
+    conf_free(ssh->conf);
+    ssh->conf = conf_copy(conf);
+    ssh_cache_conf_values(ssh);
+}
+
+/*
+ * Called to send data down the SSH connection.
+ */
+static void ssh_send(Backend *be, const char *buf, size_t len)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+
+    if (ssh == NULL || ssh->s == NULL)
+        return;
+
+    bufchain_add(&ssh->user_input, buf, len);
+    if (ssh->cl)
+        ssh_got_user_input(ssh->cl);
+}
+
+/*
+ * Called to query the current amount of buffered stdin data.
+ */
+static size_t ssh_sendbuffer(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    size_t backlog;
+
+    if (!ssh || !ssh->s || !ssh->cl)
+        return 0;
+
+    backlog = ssh_stdin_backlog(ssh->cl);
+
+    #ifndef WINSCP
+    // This throttles WinSCP unnecessarily, as it uses its own throttling mechanism.
+    // This value is used only by WinSCP, never directly by PuTTY code.
+    if (ssh->base_layer)
+        backlog += ssh_ppl_queued_data_size(ssh->base_layer);
+    #endif
+
+    /*
+     * If the SSH socket itself has backed up, add the total backup
+     * size on that to any individual buffer on the stdin channel.
+     */
+    if (ssh->throttled_all)
+        backlog += ssh->overall_bufsize;
+
+    return backlog;
+}
+
+void ssh_sendbuffer_changed(Ssh *ssh)
+{
+    seat_sent(ssh->seat, ssh_sendbuffer(&ssh->backend));
+}
+
+/*
+ * Called to set the size of the window from SSH's POV.
+ */
+static void ssh_size(Backend *be, int width, int height)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+
+    ssh->term_width = width;
+    ssh->term_height = height;
+    if (ssh->cl)
+        ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height);
+}
+
+struct ssh_add_special_ctx {
+    SessionSpecial *specials;
+    size_t nspecials, specials_size;
+};
+
+static void ssh_add_special(void *vctx, const char *text,
+                            SessionSpecialCode code, int arg)
+{
+    struct ssh_add_special_ctx *ctx = (struct ssh_add_special_ctx *)vctx;
+    SessionSpecial *spec;
+
+    sgrowarray(ctx->specials, ctx->specials_size, ctx->nspecials);
+    spec = &ctx->specials[ctx->nspecials++];
+    spec->name = text;
+    spec->code = code;
+    spec->arg = arg;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const SessionSpecial *ssh_get_specials(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+
+    /*
+     * Ask all our active protocol layers what specials they've got,
+     * and amalgamate the list into one combined one.
+     */
+
+    struct ssh_add_special_ctx ctx[1];
+
+    ctx->specials = NULL;
+    ctx->nspecials = ctx->specials_size = 0;
+
+    if (ssh->base_layer)
+        ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, ctx);
+
+    if (ctx->specials) {
+        /* If the list is non-empty, terminate it with a SS_EXITMENU. */
+        ssh_add_special(ctx, NULL, SS_EXITMENU, 0);
+    }
+
+    sfree(ssh->specials);
+    ssh->specials = ctx->specials;
+    return ssh->specials;
+}
+
+/*
+ * Send special codes.
+ */
+static void ssh_special(Backend *be, SessionSpecialCode code, int arg)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+
+    if (ssh->base_layer)
+        ssh_ppl_special_cmd(ssh->base_layer, code, arg);
+}
+
+/*
+ * This is called when the seat's output channel manages to clear some
+ * backlog.
+ */
+static void ssh_unthrottle(Backend *be, size_t bufsize)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+
+    if (ssh->cl)
+        ssh_stdout_unthrottle(ssh->cl, bufsize);
+}
+
+static bool ssh_connected(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    return ssh->s != NULL;
+}
+
+static bool ssh_sendok(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    return ssh->cl && ssh_get_wants_user_input(ssh->cl);
+}
+
+void ssh_check_sendok(Ssh *ssh)
+{
+    /* Called when the connection layer might have caused ssh_sendok
+     * to start returning true */
+    if (ssh->ldisc)
+        ; // ldisc_check_sendok(ssh->ldisc); WINSCP
+}
+
+void ssh_ldisc_update(Ssh *ssh)
+{
+    /* Called when the connection layer wants to propagate an update
+     * to the line discipline options */
+    if (ssh->ldisc)
+        ldisc_echoedit_update(ssh->ldisc);
+}
+
+static bool ssh_ldisc(Backend *be, int option)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    return ssh->cl ? ssh_ldisc_option(ssh->cl, option) : false;
+}
+
+static void ssh_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    assert(false); // WINSCP
+    ssh->ldisc = ldisc;
+}
+
+void ssh_got_exitcode(Ssh *ssh, int exitcode)
+{
+    ssh->exitcode = exitcode;
+}
+
+static int ssh_return_exitcode(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    if (ssh->s && (!ssh->session_started || ssh->base_layer))
+        return -1;
+    else
+        return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX);
+}
+
+/*
+ * cfg_info for SSH is the protocol running in this session.
+ * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare
+ * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.)
+ */
+static int ssh_cfg_info(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    if (ssh->version == 0)
+        return 0; /* don't know yet */
+    else if (ssh->bare_connection)
+        return -1;
+    else
+        return ssh->version;
+}
+
+/*
+ * Gross hack: pscp will try to start SFTP but fall back to scp1 if
+ * that fails. This variable is the means by which pscp.c can reach
+ * into the SSH code and find out which one it got.
+ */
+extern bool ssh_fallback_cmd(Backend *be)
+{
+    Ssh *ssh = container_of(be, Ssh, backend);
+    return ssh->fallback_cmd;
+}
+
+void ssh_got_fallback_cmd(Ssh *ssh)
+{
+    ssh->fallback_cmd = true;
+}
+
+const BackendVtable ssh_backend = {
+    // WINSCP
+    /*.init =*/ ssh_init,
+    /*.free =*/ ssh_free,
+    /*.reconfig =*/ ssh_reconfig,
+    /*.send =*/ ssh_send,
+    /*.sendbuffer =*/ ssh_sendbuffer,
+    /*.size =*/ ssh_size,
+    /*.special =*/ ssh_special,
+    /*.get_specials =*/ ssh_get_specials,
+    /*.connected =*/ ssh_connected,
+    /*.exitcode =*/ ssh_return_exitcode,
+    /*.sendok =*/ ssh_sendok,
+    /*.ldisc_option_state =*/ ssh_ldisc,
+    /*.provide_ldisc =*/ ssh_provide_ldisc,
+    /*.unthrottle =*/ ssh_unthrottle,
+    /*.cfg_info =*/ ssh_cfg_info,
+    /*.test_for_upstream =*/ ssh_test_for_upstream,
+    /*.close_warn_text =*/ ssh_close_warn_text,
+    /*.id =*/ "ssh",
+    /*.displayname_tc =*/ "SSH",
+    /*.displayname_lc =*/ "SSH", /* proper name, so capitalise it anyway */
+    /*.protocol =*/ PROT_SSH,
+    /*.default_port =*/ 22,
+    /*.flags =*/ BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START,
+    0, 0, // WINSCP
+};
+
+const BackendVtable sshconn_backend = {
+    // WINSCP
+    /*.init =*/ ssh_init,
+    /*.free =*/ ssh_free,
+    /*.reconfig =*/ ssh_reconfig,
+    /*.send =*/ ssh_send,
+    /*.sendbuffer =*/ ssh_sendbuffer,
+    /*.size =*/ ssh_size,
+    /*.special =*/ ssh_special,
+    /*.get_specials =*/ ssh_get_specials,
+    /*.connected =*/ ssh_connected,
+    /*.exitcode =*/ ssh_return_exitcode,
+    /*.sendok =*/ ssh_sendok,
+    /*.ldisc_option_state =*/ ssh_ldisc,
+    /*.provide_ldisc =*/ ssh_provide_ldisc,
+    /*.unthrottle =*/ ssh_unthrottle,
+    /*.cfg_info =*/ ssh_cfg_info,
+    /*.test_for_upstream =*/ ssh_test_for_upstream,
+    /*.close_warn_text =*/ ssh_close_warn_text,
+    /*.id =*/ "ssh-connection",
+    /*.displayname_tc =*/ "Bare ssh-connection",
+    /*.displayname_lc =*/ "bare ssh-connection",
+    /*.protocol =*/ PROT_SSHCONN,
+    0, // WINSCP
+    /*.flags =*/ BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START,
+    0, 0, // WINSCP
+};
+
+#ifdef MPEXT
+
+#include "puttyexp.h"
+
+int is_ssh(Plug * plug)
+{
+  return plug->vt->closing == ssh_closing;
+}
+
+int get_ssh_version(Backend * be)
+{
+  Ssh * ssh = container_of(be, Ssh, backend);
+  return ssh->version;
+}
+
+Seat * get_ssh_seat(Plug * plug)
+{
+  return container_of(plug, Ssh, plug)->seat;
+}
+
+const ssh_cipher * get_cscipher(Backend * be)
+{
+  Ssh * ssh = container_of(be, Ssh, backend);
+  return ssh2_bpp_get_cscipher(ssh->bpp);
+}
+
+const ssh_cipher * get_sccipher(Backend * be)
+{
+  Ssh * ssh = container_of(be, Ssh, backend);
+  return ssh2_bpp_get_sccipher(ssh->bpp);
+}
+
+const struct ssh_compressor * get_cscomp(Backend * be)
+{
+  Ssh * ssh = container_of(be, Ssh, backend);
+  return ssh2_bpp_get_cscomp(ssh->bpp);
+}
+
+const struct ssh_decompressor * get_sccomp(Backend * be)
+{
+  Ssh * ssh = container_of(be, Ssh, backend);
+  return ssh2_bpp_get_sccomp(ssh->bpp);
+}
+
+unsigned int winscp_query(Backend * be, int query)
+{
+  Ssh * ssh = container_of(be, Ssh, backend);
+  if ((ssh->base_layer != NULL) && (ssh->base_layer->vt->winscp_query != NULL))
+  {
+    return ssh_ppl_winscp_query(ssh->base_layer, query);
+  }
+  else
+  {
+    return 0;
+  }
+}
+
+void md5checksum(const char * buffer, int len, unsigned char output[16])
+{
+  hash_simple(&ssh_md5, make_ptrlen(buffer, len), output);
+}
+#endif

+ 126 - 0
source/putty/ssh/transient-hostkey-cache.c

@@ -0,0 +1,126 @@
+/*
+ * Data structure managing host keys in sessions based on GSSAPI KEX.
+ *
+ * In a session we started with a GSSAPI key exchange, the concept of
+ * 'host key' has completely different lifetime and security semantics
+ * from the usual ones. Per RFC 4462 section 2.1, we assume that any
+ * host key delivered to us in the course of a GSSAPI key exchange is
+ * _solely_ there to use as a transient fallback within the same
+ * session, if at the time of a subsequent rekey the GSS credentials
+ * are temporarily invalid and so a non-GSS KEX method has to be used.
+ *
+ * In particular, in a GSS-based SSH deployment, host keys may not
+ * even _be_ persistent identities for the server; it would be
+ * legitimate for a server to generate a fresh one routinely if it
+ * wanted to, like SSH-1 server keys.
+ *
+ * So, in this mode, we never touch the persistent host key cache at
+ * all, either to check keys against it _or_ to store keys in it.
+ * Instead, we maintain an in-memory cache of host keys that have been
+ * mentioned in GSS key exchanges within this particular session, and
+ * we permit precisely those host keys in non-GSS rekeys.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+struct ssh_transient_hostkey_cache {
+    tree234 *cache;
+};
+
+struct ssh_transient_hostkey_cache_entry {
+    const ssh_keyalg *alg;
+    strbuf *pub_blob;
+};
+
+static int ssh_transient_hostkey_cache_cmp(void *av, void *bv)
+{
+    const struct ssh_transient_hostkey_cache_entry
+        *a = (const struct ssh_transient_hostkey_cache_entry *)av,
+        *b = (const struct ssh_transient_hostkey_cache_entry *)bv;
+    return strcmp(a->alg->ssh_id, b->alg->ssh_id);
+}
+
+static int ssh_transient_hostkey_cache_find(void *av, void *bv)
+{
+    const ssh_keyalg *aalg = (const ssh_keyalg *)av;
+    const struct ssh_transient_hostkey_cache_entry
+        *b = (const struct ssh_transient_hostkey_cache_entry *)bv;
+    return strcmp(aalg->ssh_id, b->alg->ssh_id);
+}
+
+ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void)
+{
+    ssh_transient_hostkey_cache *thc = snew(ssh_transient_hostkey_cache);
+    thc->cache = newtree234(ssh_transient_hostkey_cache_cmp);
+    return thc;
+}
+
+void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc)
+{
+    struct ssh_transient_hostkey_cache_entry *ent;
+    while ((ent = delpos234(thc->cache, 0)) != NULL) {
+        strbuf_free(ent->pub_blob);
+        sfree(ent);
+    }
+    freetree234(thc->cache);
+    sfree(thc);
+}
+
+void ssh_transient_hostkey_cache_add(
+    ssh_transient_hostkey_cache *thc, ssh_key *key)
+{
+    struct ssh_transient_hostkey_cache_entry *ent, *retd;
+
+    if ((ent = find234(thc->cache, (void *)ssh_key_alg(key),
+                       ssh_transient_hostkey_cache_find)) != NULL) {
+        del234(thc->cache, ent);
+        strbuf_free(ent->pub_blob);
+        sfree(ent);
+    }
+
+    ent = snew(struct ssh_transient_hostkey_cache_entry);
+    ent->alg = ssh_key_alg(key);
+    ent->pub_blob = strbuf_new();
+    ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob));
+    retd = add234(thc->cache, ent);
+    assert(retd == ent);
+}
+
+bool ssh_transient_hostkey_cache_verify(
+    ssh_transient_hostkey_cache *thc, ssh_key *key)
+{
+    struct ssh_transient_hostkey_cache_entry *ent;
+    bool toret = false;
+
+    if ((ent = find234(thc->cache, (void *)ssh_key_alg(key),
+                       ssh_transient_hostkey_cache_find)) != NULL) {
+        strbuf *this_blob = strbuf_new();
+        ssh_key_public_blob(key, BinarySink_UPCAST(this_blob));
+
+        if (this_blob->len == ent->pub_blob->len &&
+            !memcmp(this_blob->s, ent->pub_blob->s,
+                    this_blob->len))
+            toret = true;
+
+        strbuf_free(this_blob);
+    }
+
+    return toret;
+}
+
+bool ssh_transient_hostkey_cache_has(
+    ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg)
+{
+    struct ssh_transient_hostkey_cache_entry *ent =
+        find234(thc->cache, (void *)alg,
+                ssh_transient_hostkey_cache_find);
+    return ent != NULL;
+}
+
+bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc)
+{
+    return count234(thc->cache) > 0;
+}

+ 2240 - 0
source/putty/ssh/transport2.c

@@ -0,0 +1,2240 @@
+/*
+ * Packet protocol layer for the SSH-2 transport protocol (RFC 4253).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+#ifndef WINSCP
+#include "server.h"
+#endif
+#include "storage.h"
+#include "transport2.h"
+#include "mpint.h"
+
+const struct ssh_signkey_with_user_pref_id ssh2_hostkey_algs[] = {
+    #define ARRAYENT_HOSTKEY_ALGORITHM(type, alg) { &alg, type },
+    HOSTKEY_ALGORITHMS(ARRAYENT_HOSTKEY_ALGORITHM)
+};
+
+const static ssh2_macalg *const macs[] = {
+    &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
+};
+const static ssh2_macalg *const buggymacs[] = {
+    &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
+};
+
+static ssh_compressor *ssh_comp_none_init(void)
+{
+    return NULL;
+}
+static void ssh_comp_none_cleanup(ssh_compressor *handle)
+{
+}
+static ssh_decompressor *ssh_decomp_none_init(void)
+{
+    return NULL;
+}
+static void ssh_decomp_none_cleanup(ssh_decompressor *handle)
+{
+}
+static void ssh_comp_none_block(ssh_compressor *handle,
+                                const unsigned char *block, int len,
+                                unsigned char **outblock, int *outlen,
+                                int minlen)
+{
+}
+static bool ssh_decomp_none_block(ssh_decompressor *handle,
+                                  const unsigned char *block, int len,
+                                  unsigned char **outblock, int *outlen)
+{
+    return false;
+}
+static const ssh_compression_alg ssh_comp_none = {
+    // WINSCP
+    /*.name =*/ "none",
+    /*.delayed_name =*/ NULL,
+    /*.compress_new =*/ ssh_comp_none_init,
+    /*.compress_free =*/ ssh_comp_none_cleanup,
+    /*.compress =*/ ssh_comp_none_block,
+    /*.decompress_new =*/ ssh_decomp_none_init,
+    /*.decompress_free =*/ ssh_decomp_none_cleanup,
+    /*.decompress =*/ ssh_decomp_none_block,
+    /*.text_name =*/ NULL,
+};
+const static ssh_compression_alg *const compressions[] = {
+    &ssh_zlib, &ssh_comp_none
+};
+
+static void ssh2_transport_free(PacketProtocolLayer *);
+static void ssh2_transport_process_queue(PacketProtocolLayer *);
+static bool ssh2_transport_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl,
+                                       SessionSpecialCode code, int arg);
+static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl);
+
+static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s);
+static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def);
+static void ssh2_transport_higher_layer_packet_callback(void *context);
+static unsigned int ssh2_transport_winscp_query(PacketProtocolLayer *ppl, int query);
+
+static const PacketProtocolLayerVtable ssh2_transport_vtable = {
+    // WINSCP
+    /*.free =*/ ssh2_transport_free,
+    /*.process_queue =*/ ssh2_transport_process_queue,
+    /*.get_specials =*/ ssh2_transport_get_specials,
+    /*.special_cmd =*/ ssh2_transport_special_cmd,
+    /*.reconfigure =*/ ssh2_transport_reconfigure,
+    /*.queued_data_size =*/ ssh2_transport_queued_data_size,
+    /*.name =*/ NULL, /* no protocol name for this layer */
+    ssh2_transport_winscp_query,
+};
+
+#ifndef NO_GSSAPI
+static void ssh2_transport_gss_update(struct ssh2_transport_state *s,
+                                      bool definitely_rekeying);
+#endif
+
+static bool ssh2_transport_timer_update(struct ssh2_transport_state *s,
+                                        unsigned long rekey_time);
+static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive(
+    struct ssh2_transport_state *s, const char *type, const char *name,
+    const void *alg);
+
+static const char *const kexlist_descr[NKEXLIST] = {
+    "key exchange algorithm",
+    "host key algorithm",
+    "client-to-server cipher",
+    "server-to-client cipher",
+    "client-to-server MAC",
+    "server-to-client MAC",
+    "client-to-server compression method",
+    "server-to-client compression method"
+};
+
+static int weak_algorithm_compare(void *av, void *bv);
+
+PacketProtocolLayer *ssh2_transport_new(
+    Conf *conf, const char *host, int port, const char *fullhostname,
+    const char *client_greeting, const char *server_greeting,
+    struct ssh_connection_shared_gss_state *shgss,
+    struct DataTransferStats *stats, PacketProtocolLayer *higher_layer,
+    const SshServerConfig *ssc)
+{
+    struct ssh2_transport_state *s = snew(struct ssh2_transport_state);
+    memset(s, 0, sizeof(*s));
+    s->ppl.vt = &ssh2_transport_vtable;
+
+    s->conf = conf_copy(conf);
+    s->savedhost = dupstr(host);
+    s->savedport = port;
+    s->fullhostname = dupstr(fullhostname);
+    s->shgss = shgss;
+    s->client_greeting = dupstr(client_greeting);
+    s->server_greeting = dupstr(server_greeting);
+    s->stats = stats;
+    s->hostkeyblob = strbuf_new();
+
+    pq_in_init(&s->pq_in_higher, higher_layer->seat); // WINSCP
+    pq_out_init(&s->pq_out_higher, higher_layer->seat); // WINSCP
+    s->pq_out_higher.pqb.ic = &s->ic_pq_out_higher;
+    s->ic_pq_out_higher.fn = ssh2_transport_higher_layer_packet_callback;
+    s->ic_pq_out_higher.ctx = &s->ppl;
+    s->ic_pq_out_higher.set = get_seat_callback_set(higher_layer->seat);
+
+    s->higher_layer = higher_layer;
+    s->higher_layer->selfptr = &s->higher_layer;
+    ssh_ppl_setup_queues(s->higher_layer, &s->pq_in_higher, &s->pq_out_higher);
+
+#ifndef NO_GSSAPI
+    s->gss_cred_expiry = GSS_NO_EXPIRATION;
+    s->shgss->srv_name = GSS_C_NO_NAME;
+    s->shgss->ctx = NULL;
+#endif
+    s->thc = ssh_transient_hostkey_cache_new();
+    s->gss_kex_used = false;
+
+    s->outgoing_kexinit = strbuf_new();
+    s->incoming_kexinit = strbuf_new();
+    if (ssc) {
+        s->ssc = ssc;
+        s->client_kexinit = s->incoming_kexinit;
+        s->server_kexinit = s->outgoing_kexinit;
+        s->cstrans = &s->in;
+        s->sctrans = &s->out;
+        s->out.mkkey_adjust = 1;
+    } else {
+        s->client_kexinit = s->outgoing_kexinit;
+        s->server_kexinit = s->incoming_kexinit;
+        s->cstrans = &s->out;
+        s->sctrans = &s->in;
+        s->in.mkkey_adjust = 1;
+    }
+
+    s->weak_algorithms_consented_to = newtree234(weak_algorithm_compare);
+
+    ssh2_transport_set_max_data_size(s);
+
+    return &s->ppl;
+}
+
+static void ssh2_transport_free(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s =
+        container_of(ppl, struct ssh2_transport_state, ppl);
+
+    /*
+     * As our last act before being freed, move any outgoing packets
+     * off our higher layer's output queue on to our own output queue.
+     * We might be being freed while the SSH connection is still alive
+     * (because we're initiating shutdown from our end), in which case
+     * we don't want those last few packets to get lost.
+     *
+     * (If our owner were to have already destroyed our output pq
+     * before wanting to free us, then it would have to reset our
+     * publicly visible out_pq field to NULL to inhibit this attempt.
+     * But that's not how I expect the shutdown sequence to go in
+     * practice.)
+     */
+    if (s->ppl.out_pq)
+        pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+
+    conf_free(s->conf);
+
+    ssh_ppl_free(s->higher_layer);
+
+    pq_in_clear(&s->pq_in_higher);
+    pq_out_clear(&s->pq_out_higher);
+
+    sfree(s->savedhost);
+    sfree(s->fullhostname);
+    sfree(s->client_greeting);
+    sfree(s->server_greeting);
+    sfree(s->keystr);
+    sfree(s->hostkey_str);
+    strbuf_free(s->hostkeyblob);
+    if (s->hkey && !s->hostkeys) {
+        ssh_key_free(s->hkey);
+        s->hkey = NULL;
+    }
+    if (s->f) mp_free(s->f);
+    if (s->p) mp_free(s->p);
+    if (s->g) mp_free(s->g);
+    if (s->K) mp_free(s->K);
+    if (s->dh_ctx)
+        dh_cleanup(s->dh_ctx);
+    if (s->rsa_kex_key_needs_freeing) {
+        ssh_rsakex_freekey(s->rsa_kex_key);
+        sfree(s->rsa_kex_key);
+    }
+    if (s->ecdh_key)
+        ssh_ecdhkex_freekey(s->ecdh_key);
+    if (s->exhash)
+        ssh_hash_free(s->exhash);
+    strbuf_free(s->outgoing_kexinit);
+    strbuf_free(s->incoming_kexinit);
+    ssh_transient_hostkey_cache_free(s->thc);
+
+    freetree234(s->weak_algorithms_consented_to);
+
+    expire_timer_context(s);
+    sfree(s);
+}
+
+/*
+ * SSH-2 key derivation (RFC 4253 section 7.2).
+ */
+static void ssh2_mkkey(
+    struct ssh2_transport_state *s, strbuf *out,
+    mp_int *K, unsigned char *H, char chr, int keylen)
+{
+    int hlen = s->kex_alg->hash->hlen;
+    int keylen_padded;
+    unsigned char *key;
+    ssh_hash *h;
+
+    if (keylen == 0)
+        return;
+
+    /*
+     * Round the requested amount of key material up to a multiple of
+     * the length of the hash we're using to make it. This makes life
+     * simpler because then we can just write each hash output block
+     * straight into the output buffer without fiddling about
+     * truncating the last one. Since it's going into a strbuf, and
+     * strbufs are always smemclr()ed on free, there's no need to
+     * worry about leaving extra potentially-sensitive data in memory
+     * that the caller didn't ask for.
+     */
+    keylen_padded = ((keylen + hlen - 1) / hlen) * hlen;
+
+    strbuf_clear(out);
+    key = strbuf_append(out, keylen_padded);
+
+    /* First hlen bytes. */
+    h = ssh_hash_new(s->kex_alg->hash);
+    if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
+        put_mp_ssh2(h, K);
+    put_data(h, H, hlen);
+    put_byte(h, chr);
+    put_data(h, s->session_id, s->session_id_len);
+    ssh_hash_digest(h, key);
+
+    /* Subsequent blocks of hlen bytes. */
+    if (keylen_padded > hlen) {
+        int offset;
+
+        ssh_hash_reset(h);
+        if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
+            put_mp_ssh2(h, K);
+        put_data(h, H, hlen);
+
+        for (offset = hlen; offset < keylen_padded; offset += hlen) {
+            put_data(h, key + offset - hlen, hlen);
+            ssh_hash_digest_nondestructive(h, key + offset);
+        }
+
+    }
+
+    ssh_hash_free(h);
+}
+
+/*
+ * Find a slot in a KEXINIT algorithm list to use for a new algorithm.
+ * If the algorithm is already in the list, return a pointer to its
+ * entry, otherwise return an entry from the end of the list.
+ * This assumes that every time a particular name is passed in, it
+ * comes from the same string constant.  If this isn't true, this
+ * function may need to be rewritten to use strcmp() instead.
+ */
+static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm
+                                                     *list, const char *name)
+{
+    int i;
+
+    for (i = 0; i < MAXKEXLIST; i++)
+        if (list[i].name == NULL || list[i].name == name) {
+            list[i].name = name;
+            return &list[i];
+        }
+
+    unreachable("Should never run out of space in KEXINIT list");
+}
+
+bool ssh2_common_filter_queue(PacketProtocolLayer *ppl)
+{
+    static const char *const ssh2_disconnect_reasons[] = {
+        NULL,
+        "host not allowed to connect",
+        "protocol error",
+        "key exchange failed",
+        "host authentication failed",
+        "MAC error",
+        "compression error",
+        "service not available",
+        "protocol version not supported",
+        "host key not verifiable",
+        "connection lost",
+        "by application",
+        "too many connections",
+        "auth cancelled by user",
+        "no more auth methods available",
+        "illegal user name",
+    };
+
+    PktIn *pktin;
+    ptrlen msg;
+    int reason;
+
+    while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
+        switch (pktin->type) {
+          case SSH2_MSG_DISCONNECT:
+            reason = get_uint32(pktin);
+            msg = get_string(pktin);
+
+            ssh_remote_error(
+                ppl->ssh, "Remote side sent disconnect message\n"
+                "type %d (%s):\n\"%.*s\"", reason,
+                ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+                 ssh2_disconnect_reasons[reason] : "unknown"),
+                PTRLEN_PRINTF(msg));
+            /* don't try to pop the queue, because we've been freed! */
+            return true;               /* indicate that we've been freed */
+
+          case SSH2_MSG_DEBUG:
+            /* XXX maybe we should actually take notice of the return value */
+            get_bool(pktin);
+            msg = get_string(pktin);
+            ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg));
+            pq_pop(ppl->in_pq);
+            break;
+
+          case SSH2_MSG_IGNORE:
+            /* Do nothing, because we're ignoring it! Duhh. */
+            pq_pop(ppl->in_pq);
+            break;
+
+          case SSH2_MSG_EXT_INFO: {
+            /*
+             * The BPP enforces that these turn up only at legal
+             * points in the protocol. In particular, it will not pass
+             * an EXT_INFO on to us if it arrives before encryption is
+             * enabled (which is when a MITM could inject one
+             * maliciously).
+             *
+             * However, one of the criteria for legality is that a
+             * server is permitted to send this message immediately
+             * _before_ USERAUTH_SUCCESS. So we may receive this
+             * message not yet knowing whether it's legal to have sent
+             * it - we won't know until the BPP processes the next
+             * packet.
+             *
+             * But that should be OK, because firstly, an
+             * out-of-sequence EXT_INFO that's still within the
+             * encrypted session is only a _protocol_ violation, not
+             * an attack; secondly, any data we set in response to
+             * such an illegal EXT_INFO won't have a chance to affect
+             * the session before the BPP aborts it anyway.
+             */
+            uint32_t nexts = get_uint32(pktin);
+            uint32_t i; // WINSCP
+            for (i = 0; i < nexts && !get_err(pktin); i++) {
+                ptrlen extname = get_string(pktin);
+                ptrlen extvalue = get_string(pktin);
+                if (ptrlen_eq_string(extname, "server-sig-algs")) {
+                    /*
+                     * Server has sent a list of signature algorithms
+                     * it will potentially accept for user
+                     * authentication keys. Check in particular
+                     * whether the RFC 8332 improved versions of
+                     * ssh-rsa are in the list, and set flags in the
+                     * BPP if so.
+                     *
+                     * TODO: another thing we _could_ do here is to
+                     * record a full list of the algorithm identifiers
+                     * we've seen, whether we understand them
+                     * ourselves or not. Then we could use that as a
+                     * pre-filter during userauth, to skip keys in the
+                     * SSH agent if we already know the server can't
+                     * possibly accept them. (Even if the key
+                     * algorithm is one that the agent and the server
+                     * both understand but we do not.)
+                     */
+                    ptrlen algname;
+                    while (get_commasep_word(&extvalue, &algname)) {
+                        if (ptrlen_eq_string(algname, "rsa-sha2-256"))
+                            ppl->bpp->ext_info_rsa_sha256_ok = true;
+                        if (ptrlen_eq_string(algname, "rsa-sha2-512"))
+                            ppl->bpp->ext_info_rsa_sha512_ok = true;
+                    }
+                }
+            }
+            pq_pop(ppl->in_pq);
+            break;
+          }
+
+          default:
+            return false;
+        }
+    }
+
+    return false;
+}
+
+static bool ssh2_transport_filter_queue(struct ssh2_transport_state *s)
+{
+    PktIn *pktin;
+
+    while (1) {
+        if (ssh2_common_filter_queue(&s->ppl))
+            return true;
+        if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+            return false;
+
+        /* Pass on packets to the next layer if they're outside
+         * the range reserved for the transport protocol. */
+        if (pktin->type >= 50) {
+            /* ... except that we shouldn't tolerate higher-layer
+             * packets coming from the server before we've seen
+             * the first NEWKEYS. */
+            if (!s->higher_layer_ok) {
+                ssh_proto_error(s->ppl.ssh, "Received premature higher-"
+                                "layer packet, type %d (%s)", pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return true;
+            }
+
+            pq_pop(s->ppl.in_pq);
+            pq_push(&s->pq_in_higher, pktin);
+        } else {
+            /* Anything else is a transport-layer packet that the main
+             * process_queue coroutine should handle. */
+            return false;
+        }
+    }
+}
+
+PktIn *ssh2_transport_pop(struct ssh2_transport_state *s)
+{
+    if (ssh2_transport_filter_queue(s))
+        return NULL;   /* we've been freed */
+    return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_write_kexinit_lists(
+    /*WINSCP*/ Seat * seat, BinarySink *pktout,
+    struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST],
+    Conf *conf, const SshServerConfig *ssc, int remote_bugs,
+    const char *hk_host, int hk_port, const ssh_keyalg *hk_prev,
+    ssh_transient_hostkey_cache *thc,
+    ssh_key *const *our_hostkeys, int our_nhostkeys,
+    bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode)
+{
+    int i, j, k;
+    bool warn;
+
+    int n_preferred_kex;
+    const ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */
+    int n_preferred_hk;
+    int preferred_hk[HK_MAX];
+    int n_preferred_ciphers;
+    const ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+    const ssh_compression_alg *preferred_comp;
+    const ssh2_macalg *const *maclist;
+    int nmacs;
+
+    struct kexinit_algorithm *alg;
+
+    /*
+     * Set up the preferred key exchange. (NULL => warn below here)
+     */
+    n_preferred_kex = 0;
+    if (can_gssapi_keyex)
+        preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex;
+    for (i = 0; i < KEX_MAX; i++) {
+        switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) {
+          case KEX_DHGEX:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_diffiehellman_gex;
+            break;
+          case KEX_DHGROUP14:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_diffiehellman_group14;
+            break;
+          case KEX_DHGROUP1:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_diffiehellman_group1;
+            break;
+          case KEX_RSA:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_rsa_kex;
+            break;
+          case KEX_ECDH:
+            preferred_kex[n_preferred_kex++] =
+                &ssh_ecdh_kex;
+            break;
+          case KEX_WARN:
+            /* Flag for later. Don't bother if it's the last in
+             * the list. */
+            if (i < KEX_MAX - 1) {
+                preferred_kex[n_preferred_kex++] = NULL;
+            }
+            break;
+        }
+    }
+
+    /*
+     * Set up the preferred host key types. These are just the ids
+     * in the enum in putty.h, so 'warn below here' is indicated
+     * by HK_WARN.
+     */
+    n_preferred_hk = 0;
+    for (i = 0; i < HK_MAX; i++) {
+        int id = conf_get_int_int(conf, CONF_ssh_hklist, i);
+        /* As above, don't bother with HK_WARN if it's last in the
+         * list */
+        if (id != HK_WARN || i < HK_MAX - 1)
+            preferred_hk[n_preferred_hk++] = id;
+    }
+
+    /*
+     * Set up the preferred ciphers. (NULL => warn below here)
+     */
+    n_preferred_ciphers = 0;
+    for (i = 0; i < CIPHER_MAX; i++) {
+        switch (conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
+          case CIPHER_BLOWFISH:
+            preferred_ciphers[n_preferred_ciphers++] = &ssh2_blowfish;
+            break;
+          case CIPHER_DES:
+            if (conf_get_bool(conf, CONF_ssh2_des_cbc))
+                preferred_ciphers[n_preferred_ciphers++] = &ssh2_des;
+            break;
+          case CIPHER_3DES:
+            preferred_ciphers[n_preferred_ciphers++] = &ssh2_3des;
+            break;
+          case CIPHER_AES:
+            preferred_ciphers[n_preferred_ciphers++] = &ssh2_aes;
+            break;
+          case CIPHER_ARCFOUR:
+            preferred_ciphers[n_preferred_ciphers++] = &ssh2_arcfour;
+            break;
+          case CIPHER_CHACHA20:
+            preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp;
+            break;
+          case CIPHER_WARN:
+            /* Flag for later. Don't bother if it's the last in
+             * the list. */
+            if (i < CIPHER_MAX - 1) {
+                preferred_ciphers[n_preferred_ciphers++] = NULL;
+            }
+            break;
+        }
+    }
+
+    /*
+     * Set up preferred compression.
+     */
+    if (conf_get_bool(conf, CONF_compression))
+        preferred_comp = &ssh_zlib;
+    else
+        preferred_comp = &ssh_comp_none;
+
+    for (i = 0; i < NKEXLIST; i++)
+        for (j = 0; j < MAXKEXLIST; j++)
+            kexlists[i][j].name = NULL;
+    /* List key exchange algorithms. */
+    warn = false;
+    for (i = 0; i < n_preferred_kex; i++) {
+        const ssh_kexes *k = preferred_kex[i];
+        if (!k) warn = true;
+        else for (j = 0; j < k->nkexes; j++) {
+                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_KEX],
+                                          k->list[j]->name);
+                alg->u.kex.kex = k->list[j];
+                alg->u.kex.warn = warn;
+            }
+    }
+    /* List server host key algorithms. */
+    if (our_hostkeys) {
+        /*
+         * In server mode, we just list the algorithms that match the
+         * host keys we actually have.
+         */
+        for (i = 0; i < our_nhostkeys; i++) {
+            const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]);
+
+            alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                                      keyalg->ssh_id);
+            alg->u.hk.hostkey = keyalg;
+            alg->u.hk.hkflags = 0;
+            alg->u.hk.warn = false;
+
+            if (keyalg == &ssh_rsa) {
+                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                                          "rsa-sha2-256");
+                alg->u.hk.hostkey = keyalg;
+                alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256;
+                alg->u.hk.warn = false;
+
+                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                                          "rsa-sha2-512");
+                alg->u.hk.hostkey = keyalg;
+                alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512;
+                alg->u.hk.warn = false;
+            }
+        }
+    } else if (first_time) {
+        /*
+         * In the first key exchange, we list all the algorithms we're
+         * prepared to cope with, but (if configured to) we prefer
+         * those algorithms for which we have a host key for this
+         * host.
+         *
+         * If the host key algorithm is below the warning
+         * threshold, we warn even if we did already have a key
+         * for it, on the basis that if the user has just
+         * reconfigured that host key type to be warned about,
+         * they surely _do_ want to be alerted that a server
+         * they're actually connecting to is using it.
+         */
+        warn = false;
+        for (i = 0; i < n_preferred_hk; i++) {
+            if (preferred_hk[i] == HK_WARN)
+                warn = true;
+            for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+                if (ssh2_hostkey_algs[j].id != preferred_hk[i])
+                    continue;
+                if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) &&
+                    have_ssh_host_key(seat, hk_host, hk_port, // WINSCP
+                                      ssh2_hostkey_algs[j].alg->cache_id)) {
+                    alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                                              ssh2_hostkey_algs[j].alg->ssh_id);
+                    alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
+                    alg->u.hk.warn = warn;
+                }
+            }
+        }
+        warn = false;
+        for (i = 0; i < n_preferred_hk; i++) {
+            if (preferred_hk[i] == HK_WARN)
+                warn = true;
+            for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+                if (ssh2_hostkey_algs[j].id != preferred_hk[i])
+                    continue;
+                alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                                          ssh2_hostkey_algs[j].alg->ssh_id);
+                alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
+                alg->u.hk.warn = warn;
+            }
+        }
+#ifndef NO_GSSAPI
+    } else if (transient_hostkey_mode) {
+        /*
+         * If we've previously done a GSSAPI KEX, then we list
+         * precisely the algorithms for which a previous GSS key
+         * exchange has delivered us a host key, because we expect
+         * one of exactly those keys to be used in any subsequent
+         * non-GSS-based rekey.
+         *
+         * An exception is if this is the key exchange we
+         * triggered for the purposes of populating that cache -
+         * in which case the cache will currently be empty, which
+         * isn't helpful!
+         */
+        warn = false;
+        for (i = 0; i < n_preferred_hk; i++) {
+            if (preferred_hk[i] == HK_WARN)
+                warn = true;
+            for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+                if (ssh2_hostkey_algs[j].id != preferred_hk[i])
+                    continue;
+                if (ssh_transient_hostkey_cache_has(
+                        thc, ssh2_hostkey_algs[j].alg)) {
+                    alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
+                                              ssh2_hostkey_algs[j].alg->ssh_id);
+                    alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
+                    alg->u.hk.warn = warn;
+                }
+            }
+        }
+#endif
+    } else {
+        /*
+         * In subsequent key exchanges, we list only the host key
+         * algorithm that was selected in the first key exchange,
+         * so that we keep getting the same host key and hence
+         * don't have to interrupt the user's session to ask for
+         * reverification.
+         */
+        assert(hk_prev);
+        alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id);
+        alg->u.hk.hostkey = hk_prev;
+        alg->u.hk.warn = false;
+    }
+    if (can_gssapi_keyex) {
+        alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], "null");
+        alg->u.hk.hostkey = NULL;
+    }
+    /* List encryption algorithms (client->server then server->client). */
+    for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
+        warn = false;
+#ifdef FUZZING
+        alg = ssh2_kexinit_addalg(kexlists[k], "none");
+        alg->u.cipher.cipher = NULL;
+        alg->u.cipher.warn = warn;
+#endif /* FUZZING */
+        for (i = 0; i < n_preferred_ciphers; i++) {
+            const ssh2_ciphers *c = preferred_ciphers[i];
+            if (!c) warn = true;
+            else for (j = 0; j < c->nciphers; j++) {
+                    alg = ssh2_kexinit_addalg(kexlists[k],
+                                              c->list[j]->ssh2_id);
+                    alg->u.cipher.cipher = c->list[j];
+                    alg->u.cipher.warn = warn;
+                }
+        }
+    }
+
+    /*
+     * Be prepared to work around the buggy MAC problem.
+     */
+    if (remote_bugs & BUG_SSH2_HMAC) {
+        maclist = buggymacs;
+        nmacs = lenof(buggymacs);
+    } else {
+        maclist = macs;
+        nmacs = lenof(macs);
+    }
+
+    /* List MAC algorithms (client->server then server->client). */
+    for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
+#ifdef FUZZING
+        alg = ssh2_kexinit_addalg(kexlists[j], "none");
+        alg->u.mac.mac = NULL;
+        alg->u.mac.etm = false;
+#endif /* FUZZING */
+        for (i = 0; i < nmacs; i++) {
+            alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->name);
+            alg->u.mac.mac = maclist[i];
+            alg->u.mac.etm = false;
+        }
+        for (i = 0; i < nmacs; i++) {
+            /* For each MAC, there may also be an ETM version,
+             * which we list second. */
+            if (maclist[i]->etm_name) {
+                alg = ssh2_kexinit_addalg(kexlists[j], maclist[i]->etm_name);
+                alg->u.mac.mac = maclist[i];
+                alg->u.mac.etm = true;
+            }
+        }
+    }
+
+    /* List client->server compression algorithms,
+     * then server->client compression algorithms. (We use the
+     * same set twice.) */
+    for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
+        assert(lenof(compressions) > 1);
+        /* Prefer non-delayed versions */
+        alg = ssh2_kexinit_addalg(kexlists[j], preferred_comp->name);
+        alg->u.comp.comp = preferred_comp;
+        alg->u.comp.delayed = false;
+        if (preferred_comp->delayed_name) {
+            alg = ssh2_kexinit_addalg(kexlists[j],
+                                      preferred_comp->delayed_name);
+            alg->u.comp.comp = preferred_comp;
+            alg->u.comp.delayed = true;
+        }
+        for (i = 0; i < lenof(compressions); i++) {
+            const ssh_compression_alg *c = compressions[i];
+            alg = ssh2_kexinit_addalg(kexlists[j], c->name);
+            alg->u.comp.comp = c;
+            alg->u.comp.delayed = false;
+            if (c->delayed_name) {
+                alg = ssh2_kexinit_addalg(kexlists[j], c->delayed_name);
+                alg->u.comp.comp = c;
+                alg->u.comp.delayed = true;
+            }
+        }
+    }
+
+    /*
+     * Finally, format the lists into text and write them into the
+     * outgoing KEXINIT packet.
+     */
+    for (i = 0; i < NKEXLIST; i++) {
+        strbuf *list = strbuf_new();
+        #ifndef WINSCP
+        if (ssc && ssc->kex_override[i].ptr) {
+            put_datapl(list, ssc->kex_override[i]);
+        } else {
+        #endif
+            for (j = 0; j < MAXKEXLIST; j++) {
+                if (kexlists[i][j].name == NULL) break;
+                add_to_commasep(list, kexlists[i][j].name);
+            }
+        #ifndef WINSCP
+        }
+        #endif
+        if (i == KEXLIST_KEX && first_time) {
+            if (our_hostkeys)          /* we're the server */
+                add_to_commasep(list, "ext-info-s");
+            else                       /* we're the client */
+                add_to_commasep(list, "ext-info-c");
+        }
+        put_stringsb(pktout, list);
+    }
+    /* List client->server languages. Empty list. */
+    put_stringz(pktout, "");
+    /* List server->client languages. Empty list. */
+    put_stringz(pktout, "");
+}
+
+static bool ssh2_scan_kexinits(
+    ptrlen client_kexinit, ptrlen server_kexinit,
+    struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST],
+    const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg,
+    transport_direction *cs, transport_direction *sc,
+    bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher,
+    Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
+    int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags,
+    bool *can_send_ext_info)
+{
+    BinarySource client[1], server[1];
+    int i;
+    bool guess_correct;
+    ptrlen clists[NKEXLIST], slists[NKEXLIST];
+    const struct kexinit_algorithm *selected[NKEXLIST];
+
+    BinarySource_BARE_INIT_PL(client, client_kexinit);
+    BinarySource_BARE_INIT_PL(server, server_kexinit);
+
+    /* Skip packet type bytes and random cookies. */
+    get_data(client, 1 + 16);
+    get_data(server, 1 + 16);
+
+    guess_correct = true;
+
+    /* Find the matching string in each list, and map it to its
+     * kexinit_algorithm structure. */
+    for (i = 0; i < NKEXLIST; i++) {
+        ptrlen clist, slist, cword, sword, found;
+        bool cfirst, sfirst;
+        int j;
+
+        clists[i] = get_string(client);
+        slists[i] = get_string(server);
+        if (get_err(client) || get_err(server)) {
+            /* Report a better error than the spurious "Couldn't
+             * agree" that we'd generate if we pressed on regardless
+             * and treated the empty get_string() result as genuine */
+            ssh_proto_error(ssh, "KEXINIT packet was incomplete");
+            return false;
+        }
+
+        for (cfirst = true, clist = clists[i];
+             get_commasep_word(&clist, &cword); cfirst = false)
+            for (sfirst = true, slist = slists[i];
+                 get_commasep_word(&slist, &sword); sfirst = false)
+                if (ptrlen_eq_ptrlen(cword, sword)) {
+                    found = cword;
+                    goto found_match;
+                }
+
+        /* No matching string found in the two lists. Delay reporting
+         * a fatal error until below, because sometimes it turns out
+         * not to be fatal. */
+        selected[i] = NULL;
+
+        /*
+         * However, even if a failure to agree on any algorithm at all
+         * is not completely fatal (e.g. because it's the MAC
+         * negotiation for a cipher that comes with a built-in MAC),
+         * it still invalidates the guessed key exchange packet. (RFC
+         * 4253 section 7, not contradicted by OpenSSH's
+         * PROTOCOL.chacha20poly1305 or as far as I can see by their
+         * code.)
+         */
+        guess_correct = false;
+
+        continue;
+
+      found_match:
+
+        selected[i] = NULL;
+        for (j = 0; j < MAXKEXLIST; j++) {
+            if (kexlists[i][j].name &&
+                ptrlen_eq_string(found, kexlists[i][j].name)) {
+                selected[i] = &kexlists[i][j];
+                break;
+            }
+        }
+        if (!selected[i]) {
+            /*
+             * In the client, this should never happen! But in the
+             * server, where we allow manual override on the command
+             * line of the exact KEXINIT strings, it can happen
+             * because the command line contained a typo. So we
+             * produce a reasonably useful message instead of an
+             * assertion failure.
+             */
+            ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to "
+                         "any supported algorithm",
+                         kexlist_descr[i], PTRLEN_PRINTF(found));
+            return false;
+        }
+
+        /*
+         * If the kex or host key algorithm is not the first one in
+         * both sides' lists, that means the guessed key exchange
+         * packet (if any) is officially wrong.
+         */
+        if ((i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) && !(cfirst || sfirst))
+            guess_correct = false;
+    }
+
+    /*
+     * Skip language strings in both KEXINITs, and read the flags
+     * saying whether a guessed KEX packet follows.
+     */
+    get_string(client);
+    get_string(client);
+    get_string(server);
+    get_string(server);
+    if (ignore_guess_cs_packet)
+        *ignore_guess_cs_packet = get_bool(client) && !guess_correct;
+    if (ignore_guess_sc_packet)
+        *ignore_guess_sc_packet = get_bool(server) && !guess_correct;
+
+    /*
+     * Now transcribe the selected algorithm set into the output data.
+     */
+    for (i = 0; i < NKEXLIST; i++) {
+        const struct kexinit_algorithm *alg;
+
+        /*
+         * If we've already selected a cipher which requires a
+         * particular MAC, then just select that. This is the case in
+         * which it's not a fatal error if the actual MAC string lists
+         * didn't include any matching error.
+         */
+        if (i == KEXLIST_CSMAC && cs->cipher &&
+            cs->cipher->required_mac) {
+            cs->mac = cs->cipher->required_mac;
+            cs->etm_mode = !!(cs->mac->etm_name);
+            continue;
+        }
+        if (i == KEXLIST_SCMAC && sc->cipher &&
+            sc->cipher->required_mac) {
+            sc->mac = sc->cipher->required_mac;
+            sc->etm_mode = !!(sc->mac->etm_name);
+            continue;
+        }
+
+        alg = selected[i];
+        if (!alg) {
+            /*
+             * Otherwise, any match failure _is_ a fatal error.
+             */
+            ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)",
+                         kexlist_descr[i], PTRLEN_PRINTF(slists[i]));
+            return false;
+        }
+
+        switch (i) {
+          case KEXLIST_KEX:
+            *kex_alg = alg->u.kex.kex;
+            *warn_kex = alg->u.kex.warn;
+            break;
+
+          case KEXLIST_HOSTKEY:
+            /*
+             * Ignore an unexpected/inappropriate offer of "null",
+             * we offer "null" when we're willing to use GSS KEX,
+             * but it is only acceptable when GSSKEX is actually
+             * selected.
+             */
+            if (alg->u.hk.hostkey == NULL &&
+                (*kex_alg)->main_type != KEXTYPE_GSS)
+                continue;
+
+            *hostkey_alg = alg->u.hk.hostkey;
+            *hkflags = alg->u.hk.hkflags;
+            *warn_hk = alg->u.hk.warn;
+            break;
+
+          case KEXLIST_CSCIPHER:
+            cs->cipher = alg->u.cipher.cipher;
+            *warn_cscipher = alg->u.cipher.warn;
+            break;
+
+          case KEXLIST_SCCIPHER:
+            sc->cipher = alg->u.cipher.cipher;
+            *warn_sccipher = alg->u.cipher.warn;
+            break;
+
+          case KEXLIST_CSMAC:
+            cs->mac = alg->u.mac.mac;
+            cs->etm_mode = alg->u.mac.etm;
+            break;
+
+          case KEXLIST_SCMAC:
+            sc->mac = alg->u.mac.mac;
+            sc->etm_mode = alg->u.mac.etm;
+            break;
+
+          case KEXLIST_CSCOMP:
+            cs->comp = alg->u.comp.comp;
+            cs->comp_delayed = alg->u.comp.delayed;
+            break;
+
+          case KEXLIST_SCCOMP:
+            sc->comp = alg->u.comp.comp;
+            sc->comp_delayed = alg->u.comp.delayed;
+            break;
+
+          default:
+            unreachable("Bad list index in scan_kexinits");
+        }
+    }
+
+    /*
+     * Check whether the other side advertised support for EXT_INFO.
+     */
+    {
+        ptrlen extinfo_advert =
+            (server_hostkeys ? PTRLEN_LITERAL("ext-info-c") :
+             PTRLEN_LITERAL("ext-info-s"));
+        ptrlen list = (server_hostkeys ? clists[KEXLIST_KEX] :
+                       slists[KEXLIST_KEX]);
+        ptrlen word; // WINSCP
+        for (; get_commasep_word(&list, &word) ;)
+            if (ptrlen_eq_ptrlen(word, extinfo_advert))
+                *can_send_ext_info = true;
+    }
+
+    if (server_hostkeys) {
+        /*
+         * Finally, make an auxiliary pass over the server's host key
+         * list to find all the host key algorithms offered by the
+         * server which we know about at all, whether we selected each
+         * one or not. We return these as a list of indices into the
+         * constant ssh2_hostkey_algs[] array.
+         */
+        *n_server_hostkeys = 0;
+
+        { // WINSCP
+        ptrlen list = slists[KEXLIST_HOSTKEY];
+        ptrlen word; // WINSCP
+        for (; get_commasep_word(&list, &word) ;) {
+            for (i = 0; i < lenof(ssh2_hostkey_algs); i++)
+                if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) {
+                    server_hostkeys[(*n_server_hostkeys)++] = i;
+                    break;
+                }
+        }
+        } // WINSCP
+    }
+
+    return true;
+}
+
+void ssh2transport_finalise_exhash(struct ssh2_transport_state *s)
+{
+    put_mp_ssh2(s->exhash, s->K);
+    assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash));
+    ssh_hash_final(s->exhash, s->exchange_hash);
+    s->exhash = NULL;
+
+#if 0
+    debug("Exchange hash is:\n");
+    dmemdump(s->exchange_hash, s->kex_alg->hash->hlen);
+#endif
+}
+
+static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s =
+        container_of(ppl, struct ssh2_transport_state, ppl);
+    PktIn *pktin;
+    PktOut *pktout;
+
+    /* Filter centrally handled messages off the front of the queue on
+     * every entry to this coroutine, no matter where we're resuming
+     * from, even if we're _not_ looping on pq_pop. That way we can
+     * still proactively handle those messages even if we're waiting
+     * for a user response. */
+    if (ssh2_transport_filter_queue(s))
+        return;   /* we've been freed */
+
+    crBegin(s->crState);
+
+    s->in.cipher = s->out.cipher = NULL;
+    s->in.mac = s->out.mac = NULL;
+    s->in.comp = s->out.comp = NULL;
+
+    s->got_session_id = false;
+    s->need_gss_transient_hostkey = false;
+    s->warned_about_no_gss_transient_hostkey = false;
+
+  begin_key_exchange:
+
+#ifndef NO_GSSAPI
+    if (s->need_gss_transient_hostkey) {
+        /*
+         * This flag indicates a special case in which we must not do
+         * GSS key exchange even if we could. (See comments below,
+         * where the flag was set on the previous key exchange.)
+         */
+        s->can_gssapi_keyex = false;
+    } else if (conf_get_bool(s->conf, CONF_try_gssapi_kex)) {
+        /*
+         * We always check if we have GSS creds before we come up with
+         * the kex algorithm list, otherwise future rekeys will fail
+         * when creds expire. To make this so, this code section must
+         * follow the begin_key_exchange label above, otherwise this
+         * section would execute just once per-connection.
+         *
+         * Update GSS state unless the reason we're here is that a
+         * timer just checked the GSS state and decided that we should
+         * rekey to update delegated credentials. In that case, the
+         * state is "fresh".
+         */
+        if (s->rekey_class != RK_GSS_UPDATE)
+            ssh2_transport_gss_update(s, true);
+
+        /* Do GSSAPI KEX when capable */
+        s->can_gssapi_keyex = s->gss_status & GSS_KEX_CAPABLE;
+
+        /*
+         * But not when failure is likely. [ GSS implementations may
+         * attempt (and fail) to use a ticket that is almost expired
+         * when retrieved from the ccache that actually expires by the
+         * time the server receives it. ]
+         *
+         * Note: The first time always try KEXGSS if we can, failures
+         * will be very rare, and disabling the initial GSS KEX is
+         * worse. Some day GSS libraries will ignore cached tickets
+         * whose lifetime is critically short, and will instead use
+         * fresh ones.
+         */
+        if (!s->got_session_id && (s->gss_status & GSS_CTXT_MAYFAIL) != 0)
+            s->can_gssapi_keyex = false;
+        s->gss_delegate = conf_get_bool(s->conf, CONF_gssapifwd);
+    } else {
+        s->can_gssapi_keyex = false;
+    }
+#endif
+
+    s->ppl.bpp->pls->kctx = SSH2_PKTCTX_NOKEX;
+
+    /*
+     * Construct our KEXINIT packet, in a strbuf so we can refer to it
+     * later.
+     */
+    strbuf_clear(s->client_kexinit);
+    put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT);
+    random_read(strbuf_append(s->outgoing_kexinit, 16), 16);
+    ssh2_write_kexinit_lists(
+        /*WINSCP*/ s->ppl.seat, BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists,
+        s->conf, s->ssc, s->ppl.remote_bugs,
+        s->savedhost, s->savedport, s->hostkey_alg, s->thc,
+        s->hostkeys, s->nhostkeys,
+        !s->got_session_id, s->can_gssapi_keyex,
+        s->gss_kex_used && !s->need_gss_transient_hostkey);
+    /* First KEX packet does _not_ follow, because we're not that brave. */
+    put_bool(s->outgoing_kexinit, false);
+    put_uint32(s->outgoing_kexinit, 0);             /* reserved */
+
+    /*
+     * Send our KEXINIT.
+     */
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
+    put_data(pktout, s->outgoing_kexinit->u + 1,
+             s->outgoing_kexinit->len - 1); /* omit initial packet type byte */
+    pq_push(s->ppl.out_pq, pktout);
+
+    /*
+     * Flag that KEX is in progress.
+     */
+    s->kex_in_progress = true;
+
+    /*
+     * Wait for the other side's KEXINIT, and save it.
+     */
+    crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+    if (pktin->type != SSH2_MSG_KEXINIT) {
+        ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                        "expecting KEXINIT, type %d (%s)", pktin->type,
+                        ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                      s->ppl.bpp->pls->actx, pktin->type));
+        return;
+    }
+    strbuf_clear(s->incoming_kexinit);
+    put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT);
+    put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin));
+
+    /*
+     * Work through the two KEXINIT packets in parallel to find the
+     * selected algorithm identifiers.
+     */
+    {
+        int nhk, hks[MAXKEXLIST], i, j;
+
+        if (!ssh2_scan_kexinits(
+                ptrlen_from_strbuf(s->client_kexinit),
+                ptrlen_from_strbuf(s->server_kexinit),
+                s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans,
+                s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher,
+                &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks,
+                &s->hkflags, &s->can_send_ext_info))
+            return; /* false means a fatal error function was called */
+
+        /*
+         * In addition to deciding which host key we're actually going
+         * to use, we should make a list of the host keys offered by
+         * the server which we _don't_ have cached. These will be
+         * offered as cross-certification options by ssh_get_specials.
+         *
+         * We also count the key we're currently using for KEX as one
+         * we've already got, because by the time this menu becomes
+         * visible, it will be.
+         */
+        s->n_uncert_hostkeys = 0;
+
+        for (i = 0; i < nhk; i++) {
+            j = hks[i];
+            if (ssh2_hostkey_algs[j].alg != s->hostkey_alg &&
+                !have_ssh_host_key(s->ppl.seat, s->savedhost, s->savedport, // WINSCP
+                                   ssh2_hostkey_algs[j].alg->cache_id)) {
+                s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
+            }
+        }
+    }
+
+    if (s->warn_kex) {
+        s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+            s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg);
+        crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+        if (spr_is_abort(s->spr)) {
+            ssh_spr_close(s->ppl.ssh, s->spr, "kex warning");
+            return;
+        }
+    }
+
+    if (s->warn_hk) {
+        int j, k;
+        char *betteralgs;
+
+        /*
+         * Change warning box wording depending on why we chose a
+         * warning-level host key algorithm. If it's because
+         * that's all we have *cached*, list the host keys we
+         * could usefully cross-certify. Otherwise, use the same
+         * standard wording as any other weak crypto primitive.
+         */
+        betteralgs = NULL;
+        for (j = 0; j < s->n_uncert_hostkeys; j++) {
+            const struct ssh_signkey_with_user_pref_id *hktype =
+                &ssh2_hostkey_algs[s->uncert_hostkeys[j]];
+            bool better = false;
+            for (k = 0; k < HK_MAX; k++) {
+                int id = conf_get_int_int(s->conf, CONF_ssh_hklist, k);
+                if (id == HK_WARN) {
+                    break;
+                } else if (id == hktype->id) {
+                    better = true;
+                    break;
+                }
+            }
+            if (better) {
+                if (betteralgs) {
+                    char *old_ba = betteralgs;
+                    betteralgs = dupcat(betteralgs, ",", hktype->alg->ssh_id);
+                    sfree(old_ba);
+                } else {
+                    betteralgs = dupstr(hktype->alg->ssh_id);
+                }
+            }
+        }
+        if (betteralgs) {
+            /* Use the special warning prompt that lets us provide
+             * a list of better algorithms */
+            s->spr = seat_confirm_weak_cached_hostkey(
+                ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs,
+                ssh2_transport_dialog_callback, s);
+            sfree(betteralgs);
+        } else {
+            /* If none exist, use the more general 'weak crypto'
+             * warning prompt */
+            s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+                s, "host key type", s->hostkey_alg->ssh_id,
+                s->hostkey_alg);
+        }
+        crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+        if (spr_is_abort(s->spr)) {
+            ssh_spr_close(s->ppl.ssh, s->spr, "host key warning");
+            return;
+        }
+    }
+
+    if (s->warn_cscipher) {
+        s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+            s, "client-to-server cipher", s->out.cipher->ssh2_id,
+            s->out.cipher);
+        crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+        if (spr_is_abort(s->spr)) {
+            ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
+            return;
+        }
+    }
+
+    if (s->warn_sccipher) {
+        s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+            s, "server-to-client cipher", s->in.cipher->ssh2_id,
+            s->in.cipher);
+        crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+        if (spr_is_abort(s->spr)) {
+            ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
+            return;
+        }
+    }
+
+    /*
+     * If the other side has sent an initial key exchange packet that
+     * we must treat as a wrong guess, wait for it, and discard it.
+     */
+    if (s->ignorepkt)
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+
+    /*
+     * Actually perform the key exchange.
+     */
+    s->exhash = ssh_hash_new(s->kex_alg->hash);
+    put_stringz(s->exhash, s->client_greeting);
+    put_stringz(s->exhash, s->server_greeting);
+    put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len);
+    put_string(s->exhash, s->server_kexinit->u, s->server_kexinit->len);
+    s->crStateKex = 0;
+    while (1) {
+        bool aborted = false;
+        ssh2kex_coroutine(s, &aborted);
+        if (aborted)
+            return;    /* disaster: our entire state has been freed */
+        if (!s->crStateKex)
+            break;     /* kex phase has terminated normally */
+        crReturnV;
+    }
+
+    /*
+     * The exchange hash from the very first key exchange is also
+     * the session id, used in session key construction and
+     * authentication.
+     */
+    if (!s->got_session_id) {
+        assert(sizeof(s->exchange_hash) <= sizeof(s->session_id));
+        memcpy(s->session_id, s->exchange_hash, sizeof(s->exchange_hash));
+        s->session_id_len = s->kex_alg->hash->hlen;
+        assert(s->session_id_len <= sizeof(s->session_id));
+        s->got_session_id = true;
+    }
+
+    /*
+     * Send SSH2_MSG_NEWKEYS.
+     */
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_NEWKEYS);
+    pq_push(s->ppl.out_pq, pktout);
+    /* Start counting down the outgoing-data limit for these cipher keys. */
+    dts_reset(&s->stats->out, s->max_data_size);
+
+    /*
+     * Force the BPP to synchronously marshal all packets up to and
+     * including that NEWKEYS into wire format, before we switch over
+     * to new crypto.
+     */
+    ssh_bpp_handle_output(s->ppl.bpp);
+
+    /*
+     * We've sent outgoing NEWKEYS, so create and initialise outgoing
+     * session keys.
+     */
+    {
+        strbuf *cipher_key = strbuf_new_nm();
+        strbuf *cipher_iv = strbuf_new_nm();
+        strbuf *mac_key = strbuf_new_nm();
+
+        if (s->out.cipher) {
+            ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash,
+                       'A' + s->out.mkkey_adjust, s->out.cipher->blksize);
+            ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash,
+                       'C' + s->out.mkkey_adjust,
+                       s->out.cipher->padded_keybytes);
+        }
+        if (s->out.mac) {
+            ssh2_mkkey(s, mac_key, s->K, s->exchange_hash,
+                       'E' + s->out.mkkey_adjust, s->out.mac->keylen);
+        }
+
+        ssh2_bpp_new_outgoing_crypto(
+            s->ppl.bpp,
+            s->out.cipher, cipher_key->u, cipher_iv->u,
+            s->out.mac, s->out.etm_mode, mac_key->u,
+            s->out.comp, s->out.comp_delayed);
+
+        strbuf_free(cipher_key);
+        strbuf_free(cipher_iv);
+        strbuf_free(mac_key);
+    }
+
+    /*
+     * If that was our first key exchange, this is the moment to send
+     * our EXT_INFO, if we're sending one.
+     */
+    if (!s->post_newkeys_ext_info) {
+        s->post_newkeys_ext_info = true; /* never do this again */
+        if (s->can_send_ext_info) {
+            strbuf *extinfo = strbuf_new();
+            uint32_t n_exts = 0;
+
+            if (s->ssc) {
+                /* Server->client EXT_INFO lists our supported user
+                 * key algorithms. */
+                n_exts++;
+                put_stringz(extinfo, "server-sig-algs");
+                { // WINSCP
+                strbuf *list = strbuf_new();
+                size_t i; // WINSCP
+                for (i = 0; i < n_keyalgs; i++)
+                    add_to_commasep(list, all_keyalgs[i]->ssh_id);
+                put_stringsb(extinfo, list);
+                }  // WINSCP
+            } else {
+                /* Client->server EXT_INFO is currently not sent, but here's
+                 * where we should put things in it if we ever want to. */
+            }
+
+            /* Only send EXT_INFO if it's non-empty */
+            if (n_exts) {
+                pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_EXT_INFO);
+                put_uint32(pktout, n_exts);
+                put_datapl(pktout, ptrlen_from_strbuf(extinfo));
+                pq_push(s->ppl.out_pq, pktout);
+            }
+
+            strbuf_free(extinfo);
+        }
+    }
+
+    /*
+     * Now our end of the key exchange is complete, we can send all
+     * our queued higher-layer packets. Transfer the whole of the next
+     * layer's outgoing queue on to our own.
+     */
+    pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+    ssh_sendbuffer_changed(s->ppl.ssh);
+
+    /*
+     * Expect SSH2_MSG_NEWKEYS from server.
+     */
+    crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+    if (pktin->type != SSH2_MSG_NEWKEYS) {
+        ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                        "expecting SSH_MSG_NEWKEYS, type %d (%s)",
+                        pktin->type,
+                        ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                      s->ppl.bpp->pls->actx,
+                                      pktin->type));
+        return;
+    }
+    /* Start counting down the incoming-data limit for these cipher keys. */
+    dts_reset(&s->stats->in, s->max_data_size);
+
+    /*
+     * We've seen incoming NEWKEYS, so create and initialise
+     * incoming session keys.
+     */
+    {
+        strbuf *cipher_key = strbuf_new_nm();
+        strbuf *cipher_iv = strbuf_new_nm();
+        strbuf *mac_key = strbuf_new_nm();
+
+        if (s->in.cipher) {
+            ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash,
+                       'A' + s->in.mkkey_adjust, s->in.cipher->blksize);
+            ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash,
+                       'C' + s->in.mkkey_adjust,
+                       s->in.cipher->padded_keybytes);
+        }
+        if (s->in.mac) {
+            ssh2_mkkey(s, mac_key, s->K, s->exchange_hash,
+                       'E' + s->in.mkkey_adjust, s->in.mac->keylen);
+        }
+
+        ssh2_bpp_new_incoming_crypto(
+            s->ppl.bpp,
+            s->in.cipher, cipher_key->u, cipher_iv->u,
+            s->in.mac, s->in.etm_mode, mac_key->u,
+            s->in.comp, s->in.comp_delayed);
+
+        strbuf_free(cipher_key);
+        strbuf_free(cipher_iv);
+        strbuf_free(mac_key);
+    }
+
+    /*
+     * Free shared secret.
+     */
+    mp_free(s->K); s->K = NULL;
+
+    /*
+     * Update the specials menu to list the remaining uncertified host
+     * keys.
+     */
+    seat_update_specials_menu(s->ppl.seat);
+
+    /*
+     * Key exchange is over. Loop straight back round if we have a
+     * deferred rekey reason.
+     */
+    if (s->deferred_rekey_reason) {
+        ppl_logevent("%s", s->deferred_rekey_reason);
+        pktin = NULL;
+        s->deferred_rekey_reason = NULL;
+        goto begin_key_exchange;
+    }
+
+    /*
+     * Otherwise, schedule a timer for our next rekey.
+     */
+    s->kex_in_progress = false;
+    s->last_rekey = GETTICKCOUNT();
+    (void) ssh2_transport_timer_update(s, 0);
+
+    /*
+     * Now we're encrypting. Get the next-layer protocol started if it
+     * hasn't already, and then sit here waiting for reasons to go
+     * back to the start and do a repeat key exchange. One of those
+     * reasons is that we receive KEXINIT from the other end; the
+     * other is if we find rekey_reason is non-NULL, i.e. we've
+     * decided to initiate a rekey ourselves for some reason.
+     */
+    if (!s->higher_layer_ok) {
+        if (!s->hostkeys) {
+            /* We're the client, so send SERVICE_REQUEST. */
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST);
+            put_stringz(pktout, s->higher_layer->vt->name);
+            pq_push(s->ppl.out_pq, pktout);
+            crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+            if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) {
+                ssh_sw_abort(s->ppl.ssh, "Server refused request to start "
+                             "'%s' protocol", s->higher_layer->vt->name);
+                return;
+            }
+        } else {
+            ptrlen service_name;
+
+            /* We're the server, so expect SERVICE_REQUEST. */
+            crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+            if (pktin->type != SSH2_MSG_SERVICE_REQUEST) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                                "expecting SERVICE_REQUEST, type %d (%s)",
+                                pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return;
+            }
+            service_name = get_string(pktin);
+            if (!ptrlen_eq_string(service_name, s->higher_layer->vt->name)) {
+                ssh_proto_error(s->ppl.ssh, "Client requested service "
+                                "'%.*s' when we only support '%s'",
+                                PTRLEN_PRINTF(service_name),
+                                s->higher_layer->vt->name);
+                return;
+            }
+
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_ACCEPT);
+            put_stringz(pktout, s->higher_layer->vt->name);
+            pq_push(s->ppl.out_pq, pktout);
+        }
+
+        s->higher_layer_ok = true;
+        queue_idempotent_callback(&s->higher_layer->ic_process_queue);
+    }
+
+    s->rekey_class = RK_NONE;
+    do {
+        crReturnV;
+
+        /* Pass through outgoing packets from the higher layer. */
+        pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+        ssh_sendbuffer_changed(s->ppl.ssh);
+
+        /* Wait for either a KEXINIT, or something setting
+         * s->rekey_class. This call to ssh2_transport_pop also has
+         * the side effect of transferring incoming packets _to_ the
+         * higher layer (via filter_queue). */
+        if ((pktin = ssh2_transport_pop(s)) != NULL) {
+            if (pktin->type != SSH2_MSG_KEXINIT) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected transport-"
+                                "layer packet outside a key exchange, "
+                                "type %d (%s)", pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return;
+            }
+            pq_push_front(s->ppl.in_pq, pktin);
+            ppl_logevent("Remote side initiated key re-exchange");
+            s->rekey_class = RK_SERVER;
+        }
+
+        if (s->rekey_class == RK_POST_USERAUTH) {
+            /*
+             * userauth has seen a USERAUTH_SUCCESS. This may be the
+             * moment to do an immediate rekey with different
+             * parameters. But it may not; so here we turn that rekey
+             * class into either RK_NONE or RK_NORMAL.
+             *
+             * Currently the only reason for this is if we've done a
+             * GSS key exchange and don't have anything in our
+             * transient hostkey cache, in which case we should make
+             * an attempt to populate the cache now.
+             */
+            if (s->need_gss_transient_hostkey) {
+                s->rekey_reason = "populating transient host key cache";
+                s->rekey_class = RK_NORMAL;
+            } else {
+                /* No need to rekey at this time. */
+                s->rekey_class = RK_NONE;
+            }
+        }
+
+        if (!s->rekey_class) {
+            /* If we don't yet have any other reason to rekey, check
+             * if we've hit our data limit in either direction. */
+            if (s->stats->in.expired) {
+                s->rekey_reason = "too much data received";
+                s->rekey_class = RK_NORMAL;
+            } else if (s->stats->out.expired) {
+                s->rekey_reason = "too much data sent";
+                s->rekey_class = RK_NORMAL;
+            }
+        }
+
+        if (s->rekey_class != RK_NONE && s->rekey_class != RK_SERVER) {
+            /*
+             * Special case: if the server bug is set that doesn't
+             * allow rekeying, we give a different log message and
+             * continue waiting. (If such a server _initiates_ a
+             * rekey, we process it anyway!)
+             */
+            if ((s->ppl.remote_bugs & BUG_SSH2_REKEY)) {
+                ppl_logevent("Remote bug prevents key re-exchange (%s)",
+                             s->rekey_reason);
+                /* Reset the counters, so that at least this message doesn't
+                 * hit the event log _too_ often. */
+                dts_reset(&s->stats->in, s->max_data_size);
+                dts_reset(&s->stats->out, s->max_data_size);
+                (void) ssh2_transport_timer_update(s, 0);
+                s->rekey_class = RK_NONE;
+            } else {
+                ppl_logevent("Initiating key re-exchange (%s)",
+                             s->rekey_reason);
+            }
+        }
+    } while (s->rekey_class == RK_NONE);
+
+    /* Once we exit the above loop, we really are rekeying. */
+    goto begin_key_exchange;
+
+    crFinishV;
+}
+
+static void ssh2_transport_higher_layer_packet_callback(void *context)
+{
+    PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;
+    ssh_ppl_process_queue(ppl);
+}
+
+static void ssh2_transport_timer(void *ctx, unsigned long now)
+{
+    struct ssh2_transport_state *s = (struct ssh2_transport_state *)ctx;
+    unsigned long mins;
+    unsigned long ticks;
+
+    // WINSCP: our WINSCP_QUERY_TIMER implementation of schedule_timer
+    //  does not guarantee the `now` to be exactly as scheduled
+    if (s->kex_in_progress || now < s->next_rekey)
+        return;
+
+    mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60);
+    if (mins == 0)
+        return;
+
+    /* Rekey if enough time has elapsed */
+    ticks = mins * 60 * TICKSPERSEC;
+    if (now - s->last_rekey > ticks - 30*TICKSPERSEC) {
+        s->rekey_reason = "timeout";
+        s->rekey_class = RK_NORMAL;
+        queue_idempotent_callback(&s->ppl.ic_process_queue);
+        return;
+    }
+
+#ifndef NO_GSSAPI
+    /*
+     * Rekey now if we have a new cred or context expires this cycle,
+     * but not if this is unsafe.
+     */
+    if (conf_get_int(s->conf, CONF_gssapirekey)) {
+        ssh2_transport_gss_update(s, false);
+        if ((s->gss_status & GSS_KEX_CAPABLE) != 0 &&
+            (s->gss_status & GSS_CTXT_MAYFAIL) == 0 &&
+            (s->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) {
+            s->rekey_reason = "GSS credentials updated";
+            s->rekey_class = RK_GSS_UPDATE;
+            queue_idempotent_callback(&s->ppl.ic_process_queue);
+            return;
+        }
+    }
+#endif
+
+    /* Try again later. */
+    (void) ssh2_transport_timer_update(s, 0);
+}
+
+/*
+ * The rekey_time is zero except when re-configuring.
+ *
+ * We either schedule the next timer and return false, or return true
+ * to run the callback now, which will call us again to re-schedule on
+ * completion.
+ */
+static bool ssh2_transport_timer_update(struct ssh2_transport_state *s,
+                                        unsigned long rekey_time)
+{
+    unsigned long mins;
+    unsigned long ticks;
+
+    mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60);
+    ticks = mins * 60 * TICKSPERSEC;
+
+    /* Handle change from previous setting */
+    if (rekey_time != 0 && rekey_time != mins) {
+        unsigned long next;
+        unsigned long now = GETTICKCOUNT();
+
+        mins = rekey_time;
+        ticks = mins * 60 * TICKSPERSEC;
+        next = s->last_rekey + ticks;
+
+        /* If overdue, caller will rekey synchronously now */
+        if (now - s->last_rekey > ticks)
+            return true;
+        ticks = next - now;
+    }
+
+#ifndef NO_GSSAPI
+    if (s->gss_kex_used) {
+        /*
+         * If we've used GSSAPI key exchange, then we should
+         * periodically check whether we need to do another one to
+         * pass new credentials to the server.
+         */
+        unsigned long gssmins;
+
+        /* Check cascade conditions more frequently if configured */
+        gssmins = sanitise_rekey_time(
+            conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS);
+        if (gssmins > 0) {
+            if (gssmins < mins)
+                ticks = (mins = gssmins) * 60 * TICKSPERSEC;
+
+            if ((s->gss_status & GSS_KEX_CAPABLE) != 0) {
+                /*
+                 * Run next timer even sooner if it would otherwise be
+                 * too close to the context expiration time
+                 */
+                if ((s->gss_status & GSS_CTXT_EXPIRES) == 0 &&
+                    s->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME)
+                    ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC;
+            }
+        }
+    }
+#endif
+
+    /* Schedule the next timer */
+    s->next_rekey = schedule_timer(ticks, ssh2_transport_timer, s);
+    return false;
+}
+
+void ssh2_transport_dialog_callback(void *vctx, SeatPromptResult spr)
+{
+    struct ssh2_transport_state *s = (struct ssh2_transport_state *)vctx;
+    s->spr = spr;
+    ssh_ppl_process_queue(&s->ppl);
+}
+
+#ifndef NO_GSSAPI
+/*
+ * This is called at the beginning of each SSH rekey to determine
+ * whether we are GSS capable, and if we did GSS key exchange, and are
+ * delegating credentials, it is also called periodically to determine
+ * whether we should rekey in order to delegate (more) fresh
+ * credentials. This is called "credential cascading".
+ *
+ * On Windows, with SSPI, we may not get the credential expiration, as
+ * Windows automatically renews from cached passwords, so the
+ * credential effectively never expires. Since we still want to
+ * cascade when the local TGT is updated, we use the expiration of a
+ * newly obtained context as a proxy for the expiration of the TGT.
+ */
+static void ssh2_transport_gss_update(struct ssh2_transport_state *s,
+                                      bool definitely_rekeying)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    int gss_stat;
+    time_t gss_cred_expiry;
+    unsigned long mins;
+    Ssh_gss_buf gss_sndtok;
+    Ssh_gss_buf gss_rcvtok;
+    Ssh_gss_ctx gss_ctx;
+
+    s->gss_status = 0;
+
+    /*
+     * Nothing to do if no GSSAPI libraries are configured or GSSAPI
+     * auth is not enabled.
+     */
+    if (s->shgss->libs->nlibraries == 0)
+        return;
+    if (!conf_get_bool(s->conf, CONF_try_gssapi_auth) &&
+        !conf_get_bool(s->conf, CONF_try_gssapi_kex))
+        return;
+
+    /* Import server name and cache it */
+    if (s->shgss->srv_name == GSS_C_NO_NAME) {
+        gss_stat = s->shgss->lib->import_name(
+            s->shgss->lib, s->fullhostname, &s->shgss->srv_name);
+        if (gss_stat != SSH_GSS_OK) {
+            if (gss_stat == SSH_GSS_BAD_HOST_NAME)
+                ppl_logevent("GSSAPI import name failed - Bad service name;"
+                             " won't use GSS key exchange");
+            else
+                ppl_logevent("GSSAPI import name failed;"
+                             " won't use GSS key exchange");
+            return;
+        }
+    }
+
+    /*
+     * Do we (still) have credentials? Capture the credential
+     * expiration when available
+     */
+    gss_stat = s->shgss->lib->acquire_cred(
+        s->shgss->lib, &gss_ctx, &gss_cred_expiry);
+    if (gss_stat != SSH_GSS_OK)
+        return;
+
+    SSH_GSS_CLEAR_BUF(&gss_sndtok);
+    SSH_GSS_CLEAR_BUF(&gss_rcvtok);
+
+    /*
+     * When acquire_cred yields no useful expiration, get a proxy for
+     * the cred expiration from the context expiration.
+     */
+    gss_stat = s->shgss->lib->init_sec_context(
+        s->shgss->lib, &gss_ctx, s->shgss->srv_name,
+        0 /* don't delegate */, &gss_rcvtok, &gss_sndtok,
+        (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL),
+        &s->gss_ctxt_lifetime);
+
+    /* This context was for testing only. */
+    if (gss_ctx)
+        s->shgss->lib->release_cred(s->shgss->lib, &gss_ctx);
+
+    if (gss_stat != SSH_GSS_OK &&
+        gss_stat != SSH_GSS_S_CONTINUE_NEEDED) {
+        /*
+         * No point in verbosely interrupting the user to tell them we
+         * couldn't get GSS credentials, if this was only a check
+         * between key exchanges to see if fresh ones were available.
+         * When we do do a rekey, this message (if displayed) will
+         * appear among the standard rekey blurb, but when we're not,
+         * it shouldn't pop up all the time regardless.
+         */
+        if (definitely_rekeying)
+            ppl_logevent("No GSSAPI security context available");
+
+        return;
+    }
+
+    if (gss_sndtok.length)
+        s->shgss->lib->free_tok(s->shgss->lib, &gss_sndtok);
+
+    s->gss_status |= GSS_KEX_CAPABLE;
+
+    /*
+     * When rekeying to cascade, avoid doing this too close to the
+     * context expiration time, since the key exchange might fail.
+     */
+    if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME)
+        s->gss_status |= GSS_CTXT_MAYFAIL;
+
+    /*
+     * If we're not delegating credentials, rekeying is not used to
+     * refresh them. We must avoid setting GSS_CRED_UPDATED or
+     * GSS_CTXT_EXPIRES when credential delegation is disabled.
+     */
+    if (!conf_get_bool(s->conf, CONF_gssapifwd))
+        return;
+
+    if (s->gss_cred_expiry != GSS_NO_EXPIRATION &&
+        difftime(gss_cred_expiry, s->gss_cred_expiry) > 0)
+        s->gss_status |= GSS_CRED_UPDATED;
+
+    mins = sanitise_rekey_time(
+        conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS);
+    if (mins > 0 && s->gss_ctxt_lifetime <= mins * 60)
+        s->gss_status |= GSS_CTXT_EXPIRES;
+}
+#endif /* NO_GSSAPI */
+
+ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s;
+
+    assert(ppl->vt == &ssh2_transport_vtable);
+    s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+    assert(s->got_session_id);
+    return make_ptrlen(s->session_id, s->session_id_len);
+}
+
+void ssh2_transport_notify_auth_done(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s;
+
+    assert(ppl->vt == &ssh2_transport_vtable);
+    s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+    s->rekey_reason = NULL;            /* will be filled in later */
+    s->rekey_class = RK_POST_USERAUTH;
+    queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static bool ssh2_transport_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+    struct ssh2_transport_state *s =
+        container_of(ppl, struct ssh2_transport_state, ppl);
+    bool need_separator = false;
+    bool toret = false;
+
+    if (ssh_ppl_get_specials(s->higher_layer, add_special, ctx)) {
+        need_separator = true;
+        toret = true;
+    }
+
+    /*
+     * Don't bother offering rekey-based specials if we've decided the
+     * remote won't cope with it, since we wouldn't bother sending it
+     * if asked anyway.
+     */
+    if (!(s->ppl.remote_bugs & BUG_SSH2_REKEY)) {
+        if (need_separator) {
+            add_special(ctx, NULL, SS_SEP, 0);
+            need_separator = false;
+        }
+
+        add_special(ctx, "Repeat key exchange", SS_REKEY, 0);
+        toret = true;
+
+        if (s->n_uncert_hostkeys) {
+            int i;
+
+            add_special(ctx, NULL, SS_SEP, 0);
+            add_special(ctx, "Cache new host key type", SS_SUBMENU, 0);
+            for (i = 0; i < s->n_uncert_hostkeys; i++) {
+                const ssh_keyalg *alg =
+                    ssh2_hostkey_algs[s->uncert_hostkeys[i]].alg;
+
+                add_special(ctx, alg->ssh_id, SS_XCERT, s->uncert_hostkeys[i]);
+            }
+            add_special(ctx, NULL, SS_EXITMENU, 0);
+        }
+    }
+
+    return toret;
+}
+
+static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl,
+                                       SessionSpecialCode code, int arg)
+{
+    struct ssh2_transport_state *s =
+        container_of(ppl, struct ssh2_transport_state, ppl);
+
+    if (code == SS_REKEY) {
+        if (!s->kex_in_progress) {
+            s->rekey_reason = "at user request";
+            s->rekey_class = RK_NORMAL;
+            queue_idempotent_callback(&s->ppl.ic_process_queue);
+        }
+    } else if (code == SS_XCERT) {
+        if (!s->kex_in_progress) {
+            s->cross_certifying = s->hostkey_alg = ssh2_hostkey_algs[arg].alg;
+            s->rekey_reason = "cross-certifying new host key";
+            s->rekey_class = RK_NORMAL;
+            queue_idempotent_callback(&s->ppl.ic_process_queue);
+        }
+    } else {
+        /* Send everything else to the next layer up. This includes
+         * SS_PING/SS_NOP, which we _could_ handle here - but it's
+         * better to put them in the connection layer, so they'll
+         * still work in bare connection mode. */
+        ssh_ppl_special_cmd(s->higher_layer, code, arg);
+    }
+}
+
+/* Safely convert rekey_time to unsigned long minutes */
+static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def)
+{
+    if (rekey_time < 0 || rekey_time > MAX_TICK_MINS)
+        rekey_time = def;
+    return (unsigned long)rekey_time;
+}
+
+static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s)
+{
+    s->max_data_size = parse_blocksize(
+        conf_get_str(s->conf, CONF_ssh_rekey_data));
+}
+
+static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+    struct ssh2_transport_state *s;
+    const char *rekey_reason = NULL;
+    bool rekey_mandatory = false;
+    unsigned long old_max_data_size, rekey_time;
+    int i;
+
+    assert(ppl->vt == &ssh2_transport_vtable);
+    s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+    rekey_time = sanitise_rekey_time(
+        conf_get_int(conf, CONF_ssh_rekey_time), 60);
+    if (ssh2_transport_timer_update(s, rekey_time))
+        rekey_reason = "timeout shortened";
+
+    old_max_data_size = s->max_data_size;
+    ssh2_transport_set_max_data_size(s);
+    if (old_max_data_size != s->max_data_size &&
+        s->max_data_size != 0) {
+        if (s->max_data_size < old_max_data_size) {
+            unsigned long diff = old_max_data_size - s->max_data_size;
+
+            dts_consume(&s->stats->out, diff);
+            dts_consume(&s->stats->in, diff);
+            if (s->stats->out.expired || s->stats->in.expired)
+                rekey_reason = "data limit lowered";
+        } else {
+            unsigned long diff = s->max_data_size - old_max_data_size;
+            if (s->stats->out.running)
+                s->stats->out.remaining += diff;
+            if (s->stats->in.running)
+                s->stats->in.remaining += diff;
+        }
+    }
+
+    if (conf_get_bool(s->conf, CONF_compression) !=
+        conf_get_bool(conf, CONF_compression)) {
+        rekey_reason = "compression setting changed";
+        rekey_mandatory = true;
+    }
+
+    for (i = 0; i < CIPHER_MAX; i++)
+        if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) !=
+            conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
+        rekey_reason = "cipher settings changed";
+        rekey_mandatory = true;
+    }
+    if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) !=
+        conf_get_bool(conf, CONF_ssh2_des_cbc)) {
+        rekey_reason = "cipher settings changed";
+        rekey_mandatory = true;
+    }
+
+    conf_free(s->conf);
+    s->conf = conf_copy(conf);
+
+    if (rekey_reason) {
+        if (!s->kex_in_progress && !ssh2_bpp_rekey_inadvisable(s->ppl.bpp)) {
+            s->rekey_reason = rekey_reason;
+            s->rekey_class = RK_NORMAL;
+            queue_idempotent_callback(&s->ppl.ic_process_queue);
+        } else if (rekey_mandatory) {
+            s->deferred_rekey_reason = rekey_reason;
+        }
+    }
+
+    /* Also pass the configuration along to our higher layer */
+    ssh_ppl_reconfigure(s->higher_layer, conf);
+}
+
+static int weak_algorithm_compare(void *av, void *bv)
+{
+    uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv;
+    return a < b ? -1 : a > b ? +1 : 0;
+}
+
+/*
+ * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the
+ * tree234 s->weak_algorithms_consented_to to ensure we ask at most
+ * once about any given crypto primitive.
+ */
+static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive(
+    struct ssh2_transport_state *s, const char *type, const char *name,
+    const void *alg)
+{
+    if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL))
+        return SPR_OK;
+    add234(s->weak_algorithms_consented_to, (void *)alg);
+
+    return seat_confirm_weak_crypto_primitive(
+        ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback, s);
+}
+
+static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s =
+        container_of(ppl, struct ssh2_transport_state, ppl);
+
+    return (ssh_ppl_default_queued_data_size(ppl) +
+            ssh_ppl_queued_data_size(s->higher_layer));
+}
+
+#include "puttyexp.h"
+
+static unsigned int ssh2_transport_winscp_query(PacketProtocolLayer *ppl, int query)
+{
+    struct ssh2_transport_state *s =
+        container_of(ppl, struct ssh2_transport_state, ppl);
+    if (query == WINSCP_QUERY_TIMER)
+    {
+        ssh2_transport_timer(s, GETTICKCOUNT());
+        return 1;
+    }
+    else if (s->higher_layer->vt->winscp_query != NULL)
+    {
+        return ssh_ppl_winscp_query(s->higher_layer, query);
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+void call_ssh_timer(Backend * be)
+{
+    // TODO
+}
+
+// WINSCP
+void get_hostkey_algs(int * count, cp_ssh_keyalg * SignKeys)
+{
+    int i;
+    assert(lenof(ssh2_hostkey_algs) <= *count);
+    *count = lenof(ssh2_hostkey_algs);
+    for (i = 0; i < *count; i++)
+    {
+        *(SignKeys + i) = ssh2_hostkey_algs[i].alg;
+    }
+}
+
+// WINSCP
+void get_macs(int * count, const struct ssh2_macalg *** amacs)
+{
+    *amacs = macs;
+    *count = lenof(macs);
+}
+
+int have_any_ssh2_hostkey(Seat * seat, const char * host, int port)
+{
+    int j;
+    for (j = 0; j < lenof(ssh2_hostkey_algs); j++)
+    {
+        if (have_ssh_host_key(seat, host, port, ssh2_hostkey_algs[j].alg->cache_id))
+        {
+            return 1;
+        }
+    }
+    return 0;
+}

+ 246 - 0
source/putty/ssh/transport2.h

@@ -0,0 +1,246 @@
+/*
+ * Header connecting the pieces of the SSH-2 transport layer.
+ */
+
+#ifndef PUTTY_SSH2TRANSPORT_H
+#define PUTTY_SSH2TRANSPORT_H
+
+#ifndef NO_GSSAPI
+#include "gssc.h"
+#include "gss.h"
+#define MIN_CTXT_LIFETIME 5     /* Avoid rekey with short lifetime (seconds) */
+#define GSS_KEX_CAPABLE (1<<0)  /* Can do GSS KEX */
+#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */
+#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */
+#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */
+#endif
+
+#define DH_MIN_SIZE 1024
+#define DH_MAX_SIZE 8192
+
+#define MAXKEXLIST 16
+struct kexinit_algorithm {
+    const char *name;
+    union {
+        struct {
+            const ssh_kex *kex;
+            bool warn;
+        } kex;
+        struct {
+            const ssh_keyalg *hostkey;
+            unsigned hkflags;
+            bool warn;
+        } hk;
+        struct {
+            const ssh_cipheralg *cipher;
+            bool warn;
+        } cipher;
+        struct {
+            const ssh2_macalg *mac;
+            bool etm;
+        } mac;
+        struct {
+            const ssh_compression_alg *comp;
+            bool delayed;
+        } comp;
+    } u;
+};
+
+#define HOSTKEY_ALGORITHMS(X)                   \
+    X(HK_ED25519, ssh_ecdsa_ed25519)            \
+    X(HK_ED448, ssh_ecdsa_ed448)                \
+    X(HK_ECDSA, ssh_ecdsa_nistp256)             \
+    X(HK_ECDSA, ssh_ecdsa_nistp384)             \
+    X(HK_ECDSA, ssh_ecdsa_nistp521)             \
+    /* Changed order to match WinSCP default preference list for SshHostKeyList() */ \
+    X(HK_RSA, ssh_rsa_sha512)                   \
+    X(HK_RSA, ssh_rsa_sha256)                   \
+    X(HK_RSA, ssh_rsa)                          \
+    X(HK_DSA, ssh_dsa)                          \
+    /* end of list */
+#define COUNT_HOSTKEY_ALGORITHM(type, alg) +1
+#define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM))
+
+struct ssh_signkey_with_user_pref_id {
+    const ssh_keyalg *alg;
+    int id;
+};
+extern const struct ssh_signkey_with_user_pref_id
+    ssh2_hostkey_algs[N_HOSTKEY_ALGORITHMS];
+
+/*
+ * Enumeration of high-level classes of reason why we might need to do
+ * a repeat key exchange. A full detailed reason in human-readable
+ * string form for the Event Log is also provided, but this enum type
+ * is used to discriminate between classes of reason that the code
+ * needs to treat differently.
+ *
+ * RK_NONE == 0 is the value indicating that no rekey is currently
+ * needed at all. RK_INITIAL indicates that we haven't even done the
+ * _first_ key exchange yet. RK_SERVER indicates that we're rekeying
+ * because the server asked for it, not because we decided it
+ * ourselves. RK_NORMAL is the usual case. RK_GSS_UPDATE indicates
+ * that we're rekeying because we've just got new GSSAPI credentials
+ * (hence there's no point in doing a preliminary check for new GSS
+ * creds, because we already know the answer); RK_POST_USERAUTH
+ * indicates that _if_ we're going to need a post-userauth immediate
+ * rekey for any reason, this is the moment to do it.
+ *
+ * So RK_POST_USERAUTH only tells the transport layer to _consider_
+ * rekeying, not to definitely do it. Also, that one enum value is
+ * special in that the user-readable reason text is passed in to the
+ * transport layer as NULL, whereas fills in the reason text after it
+ * decides whether it needs a rekey at all. In the other cases,
+ * rekey_reason is passed in to the at the same time as rekey_class.
+ */
+typedef enum RekeyClass {
+    RK_NONE = 0,
+    RK_INITIAL,
+    RK_SERVER,
+    RK_NORMAL,
+    RK_POST_USERAUTH,
+    RK_GSS_UPDATE
+} RekeyClass;
+
+typedef struct transport_direction {
+    const ssh_cipheralg *cipher;
+    const ssh2_macalg *mac;
+    bool etm_mode;
+    const ssh_compression_alg *comp;
+    bool comp_delayed;
+    int mkkey_adjust;
+} transport_direction;
+
+struct ssh2_transport_state {
+    int crState, crStateKex;
+
+    PacketProtocolLayer *higher_layer;
+    PktInQueue pq_in_higher;
+    PktOutQueue pq_out_higher;
+    IdempotentCallback ic_pq_out_higher;
+
+    Conf *conf;
+    char *savedhost;
+    int savedport;
+    const char *rekey_reason;
+    enum RekeyClass rekey_class;
+
+    unsigned long max_data_size;
+
+    const ssh_kex *kex_alg;
+    const ssh_keyalg *hostkey_alg;
+    char *hostkey_str; /* string representation, for easy checking in rekeys */
+    unsigned char session_id[MAX_HASH_LEN];
+    int session_id_len;
+    int dh_min_size, dh_max_size;
+    bool dh_got_size_bounds;
+    dh_ctx *dh_ctx;
+    ssh_hash *exhash;
+
+    struct DataTransferStats *stats;
+
+    const SshServerConfig *ssc;
+
+    char *client_greeting, *server_greeting;
+
+    bool kex_in_progress;
+    unsigned long next_rekey, last_rekey;
+    const char *deferred_rekey_reason;
+    bool higher_layer_ok;
+
+    /*
+     * Fully qualified host name, which we need if doing GSSAPI.
+     */
+    char *fullhostname;
+
+    /* shgss is outside the ifdef on purpose to keep APIs simple. If
+     * NO_GSSAPI is not defined, then it's just an opaque structure
+     * tag and the pointer will be NULL. */
+    struct ssh_connection_shared_gss_state *shgss;
+#ifndef NO_GSSAPI
+    int gss_status;
+    time_t gss_cred_expiry;             /* Re-delegate if newer */
+    unsigned long gss_ctxt_lifetime;    /* Re-delegate when short */
+#endif
+    ssh_transient_hostkey_cache *thc;
+
+    bool gss_kex_used;
+
+    int nbits, pbits;
+    bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
+    mp_int *p, *g, *e, *f, *K;
+    strbuf *outgoing_kexinit, *incoming_kexinit;
+    strbuf *client_kexinit, *server_kexinit; /* aliases to the above */
+    int kex_init_value, kex_reply_value;
+    transport_direction in, out, *cstrans, *sctrans;
+    ptrlen hostkeydata, sigdata;
+    strbuf *hostkeyblob;
+    char *keystr;
+    ssh_key *hkey;                     /* actual host key */
+    unsigned hkflags;                  /* signing flags, used in server */
+    RSAKey *rsa_kex_key;             /* for RSA kex */
+    bool rsa_kex_key_needs_freeing;
+    ecdh_key *ecdh_key;                     /* for ECDH kex */
+    unsigned char exchange_hash[MAX_HASH_LEN];
+    bool can_gssapi_keyex;
+    bool need_gss_transient_hostkey;
+    bool warned_about_no_gss_transient_hostkey;
+    bool got_session_id;
+    bool can_send_ext_info, post_newkeys_ext_info;
+    SeatPromptResult spr;
+    bool guessok;
+    bool ignorepkt;
+    struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST];
+#ifndef NO_GSSAPI
+    Ssh_gss_buf gss_buf;
+    Ssh_gss_buf gss_rcvtok, gss_sndtok;
+    Ssh_gss_stat gss_stat;
+    Ssh_gss_buf mic;
+    bool init_token_sent;
+    bool complete_rcvd;
+    bool gss_delegate;
+#endif
+
+    /* List of crypto primitives below the warning threshold that the
+     * user has already clicked OK to, so that we don't keep asking
+     * about them again during rekeys. This directly stores pointers
+     * to the algorithm vtables, compared by pointer value (which is
+     * not a determinism hazard, because we're only using it as a
+     * set). */
+    tree234 *weak_algorithms_consented_to;
+
+    /*
+     * List of host key algorithms for which we _don't_ have a stored
+     * host key. These are indices into the main hostkey_algs[] array
+     */
+    int uncert_hostkeys[N_HOSTKEY_ALGORITHMS];
+    int n_uncert_hostkeys;
+
+    /*
+     * Indicate that the current rekey is intended to finish with a
+     * newly cross-certified host key. To double-check that we
+     * certified the right one, we set this to point to the host key
+     * algorithm we expect it to be.
+     */
+    const ssh_keyalg *cross_certifying;
+
+    ssh_key *const *hostkeys;
+    int nhostkeys;
+
+    PacketProtocolLayer ppl;
+};
+
+/* Helpers shared between transport and kex */
+PktIn *ssh2_transport_pop(struct ssh2_transport_state *s);
+void ssh2_transport_dialog_callback(void *, SeatPromptResult);
+
+/* Provided by transport for use in kex */
+void ssh2transport_finalise_exhash(struct ssh2_transport_state *s);
+
+/* Provided by kex for use in transport. Must set the 'aborted' flag
+ * if it throws a connection-terminating error, so that the caller
+ * won't have to check that by looking inside its state parameter
+ * which might already have been freed. */
+void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted);
+
+#endif /* PUTTY_SSH2TRANSPORT_H */

+ 179 - 0
source/putty/ssh/ttymode-list.h

@@ -0,0 +1,179 @@
+/*
+ * List of SSH terminal modes, indicating whether SSH types them as
+ * char or boolean, and if they're boolean, which POSIX flags field of
+ * a termios structure they appear in, and what bit mask removes them
+ * (e.g. CS7 and CS8 aren't single bits).
+ *
+ * Sources: RFC 4254, SSH-1 RFC-1.2.31, POSIX 2017, and the Linux
+ * termios manpage for flags not specified by POSIX.
+ *
+ * This is a separate header file rather than my usual style of a
+ * parametric list macro, because in this case I need to be able to
+ * #ifdef out each mode in case it's not defined on a particular
+ * target system.
+ *
+ * If you want only the locally defined modes, #define
+ * TTYMODES_LOCAL_ONLY before including this header.
+ */
+#if !defined TTYMODES_LOCAL_ONLY || defined VINTR
+TTYMODE_CHAR(INTR, 1, VINTR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VQUIT
+TTYMODE_CHAR(QUIT, 2, VQUIT)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VERASE
+TTYMODE_CHAR(ERASE, 3, VERASE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VKILL
+TTYMODE_CHAR(KILL, 4, VKILL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VEOF
+TTYMODE_CHAR(EOF, 5, VEOF)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VEOL
+TTYMODE_CHAR(EOL, 6, VEOL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VEOL2
+TTYMODE_CHAR(EOL2, 7, VEOL2)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSTART
+TTYMODE_CHAR(START, 8, VSTART)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSTOP
+TTYMODE_CHAR(STOP, 9, VSTOP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSUSP
+TTYMODE_CHAR(SUSP, 10, VSUSP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VDSUSP
+TTYMODE_CHAR(DSUSP, 11, VDSUSP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VREPRINT
+TTYMODE_CHAR(REPRINT, 12, VREPRINT)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VWERASE
+TTYMODE_CHAR(WERASE, 13, VWERASE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VLNEXT
+TTYMODE_CHAR(LNEXT, 14, VLNEXT)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VFLUSH
+TTYMODE_CHAR(FLUSH, 15, VFLUSH)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSWTCH
+TTYMODE_CHAR(SWTCH, 16, VSWTCH)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSTATUS
+TTYMODE_CHAR(STATUS, 17, VSTATUS)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VDISCARD
+TTYMODE_CHAR(DISCARD, 18, VDISCARD)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IGNPAR
+TTYMODE_FLAG(IGNPAR, 30, i, IGNPAR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined PARMRK
+TTYMODE_FLAG(PARMRK, 31, i, PARMRK)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined INPCK
+TTYMODE_FLAG(INPCK, 32, i, INPCK)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ISTRIP
+TTYMODE_FLAG(ISTRIP, 33, i, ISTRIP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined INLCR
+TTYMODE_FLAG(INLCR, 34, i, INLCR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IGNCR
+TTYMODE_FLAG(IGNCR, 35, i, IGNCR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ICRNL
+TTYMODE_FLAG(ICRNL, 36, i, ICRNL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IUCLC
+TTYMODE_FLAG(IUCLC, 37, i, IUCLC)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IXON
+TTYMODE_FLAG(IXON, 38, i, IXON)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IXANY
+TTYMODE_FLAG(IXANY, 39, i, IXANY)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IXOFF
+TTYMODE_FLAG(IXOFF, 40, i, IXOFF)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IMAXBEL
+TTYMODE_FLAG(IMAXBEL, 41, i, IMAXBEL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IUTF8
+TTYMODE_FLAG(IUTF8, 42, i, IUTF8)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ISIG
+TTYMODE_FLAG(ISIG, 50, l, ISIG)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ICANON
+TTYMODE_FLAG(ICANON, 51, l, ICANON)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined XCASE
+TTYMODE_FLAG(XCASE, 52, l, XCASE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHO
+TTYMODE_FLAG(ECHO, 53, l, ECHO)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHOE
+TTYMODE_FLAG(ECHOE, 54, l, ECHOE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHOK
+TTYMODE_FLAG(ECHOK, 55, l, ECHOK)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHONL
+TTYMODE_FLAG(ECHONL, 56, l, ECHONL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined NOFLSH
+TTYMODE_FLAG(NOFLSH, 57, l, NOFLSH)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined TOSTOP
+TTYMODE_FLAG(TOSTOP, 58, l, TOSTOP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IEXTEN
+TTYMODE_FLAG(IEXTEN, 59, l, IEXTEN)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHOCTL
+TTYMODE_FLAG(ECHOCTL, 60, l, ECHOCTL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHOKE
+TTYMODE_FLAG(ECHOKE, 61, l, ECHOKE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined PENDIN
+TTYMODE_FLAG(PENDIN, 62, l, PENDIN)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined OPOST
+TTYMODE_FLAG(OPOST, 70, o, OPOST)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined OLCUC
+TTYMODE_FLAG(OLCUC, 71, o, OLCUC)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ONLCR
+TTYMODE_FLAG(ONLCR, 72, o, ONLCR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined OCRNL
+TTYMODE_FLAG(OCRNL, 73, o, OCRNL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ONOCR
+TTYMODE_FLAG(ONOCR, 74, o, ONOCR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ONLRET
+TTYMODE_FLAG(ONLRET, 75, o, ONLRET)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined CS7
+TTYMODE_FLAG(CS7, 90, c, CSIZE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined CS8
+TTYMODE_FLAG(CS8, 91, c, CSIZE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined PARENB
+TTYMODE_FLAG(PARENB, 92, c, PARENB)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined PARODD
+TTYMODE_FLAG(PARODD, 93, c, PARODD)
+#endif

+ 1958 - 0
source/putty/ssh/userauth2-client.c

@@ -0,0 +1,1958 @@
+/*
+ * Packet protocol layer for the client side of the SSH-2 userauth
+ * protocol (RFC 4252).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+
+#ifndef NO_GSSAPI
+#include "gssc.h"
+#include "gss.h"
+#endif
+
+#define BANNER_LIMIT 131072
+
+typedef struct agent_key {
+    strbuf *blob, *comment;
+    ptrlen algorithm;
+} agent_key;
+
+struct ssh2_userauth_state {
+    int crState;
+
+    PacketProtocolLayer *transport_layer, *successor_layer;
+    Filename *keyfile;
+    bool show_banner, tryagent, notrivialauth, change_username;
+    char *hostname, *fullhostname;
+    char *default_username;
+    bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd;
+    char *loghost; // WINSCP
+    bool change_password; // WINSCP
+
+    ptrlen session_id;
+    enum {
+        AUTH_TYPE_NONE,
+        AUTH_TYPE_PUBLICKEY,
+        AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
+        AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
+        AUTH_TYPE_PASSWORD,
+        AUTH_TYPE_GSSAPI,      /* always QUIET */
+        AUTH_TYPE_KEYBOARD_INTERACTIVE,
+        AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
+    } type;
+    bool need_pw, can_pubkey, can_passwd, can_keyb_inter;
+    SeatPromptResult spr;
+    bool tried_pubkey_config, done_agent;
+    struct ssh_connection_shared_gss_state *shgss;
+#ifndef NO_GSSAPI
+    bool can_gssapi;
+    bool can_gssapi_keyex_auth;
+    bool tried_gssapi;
+    bool tried_gssapi_keyex_auth;
+    time_t gss_cred_expiry;
+    Ssh_gss_buf gss_buf;
+    Ssh_gss_buf gss_rcvtok, gss_sndtok;
+    Ssh_gss_stat gss_stat;
+#endif
+    bool suppress_wait_for_response_packet;
+    strbuf *last_methods_string;
+    bool kbd_inter_refused;
+    prompts_t *cur_prompt;
+    uint32_t num_prompts;
+    const char *username;
+    char *locally_allocated_username;
+    char *password;
+    bool got_username;
+    strbuf *publickey_blob;
+    bool privatekey_available, privatekey_encrypted;
+    char *publickey_algorithm;
+    char *publickey_comment;
+    void *agent_response_to_free;
+    ptrlen agent_response;
+    BinarySource asrc[1];          /* for reading SSH agent response */
+    size_t agent_keys_len;
+    agent_key *agent_keys;
+    size_t agent_key_index, agent_key_limit;
+    ptrlen agent_keyalg;
+    unsigned signflags;
+    int len;
+    PktOut *pktout;
+    bool is_trivial_auth;
+
+    agent_pending_query *auth_agent_query;
+    bufchain banner;
+    bufchain_sink banner_bs;
+    StripCtrlChars *banner_scc;
+    bool banner_scc_initialised;
+
+    StripCtrlChars *ki_scc;
+    bool ki_scc_initialised;
+    bool ki_printed_header;
+
+    Seat *seat; // WINSCP
+
+    PacketProtocolLayer ppl;
+};
+
+static void ssh2_userauth_free(PacketProtocolLayer *);
+static void ssh2_userauth_process_queue(PacketProtocolLayer *);
+static bool ssh2_userauth_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl,
+                                      SessionSpecialCode code, int arg);
+static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *);
+static void ssh2_userauth_agent_callback(void *, void *, int);
+static void ssh2_userauth_add_sigblob(
+    struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob);
+static void ssh2_userauth_add_session_id(
+    struct ssh2_userauth_state *s, strbuf *sigdata);
+#ifndef NO_GSSAPI
+static PktOut *ssh2_userauth_gss_packet(
+    struct ssh2_userauth_state *s, const char *authtype);
+#endif
+
+static const PacketProtocolLayerVtable ssh2_userauth_vtable = {
+    // WINSCP
+    /*.free =*/ ssh2_userauth_free,
+    /*.process_queue =*/ ssh2_userauth_process_queue,
+    /*.get_specials =*/ ssh2_userauth_get_specials,
+    /*.special_cmd =*/ ssh2_userauth_special_cmd,
+    /*.reconfigure =*/ ssh2_userauth_reconfigure,
+    /*.queued_data_size =*/ ssh_ppl_default_queued_data_size,
+    /*.name =*/ "ssh-userauth",
+    NULL, // WINSCP
+};
+
+PacketProtocolLayer *ssh2_userauth_new(
+    PacketProtocolLayer *successor_layer,
+    const char *hostname, const char *fullhostname,
+    Filename *keyfile, bool show_banner, bool tryagent, bool notrivialauth,
+    const char *default_username, bool change_username,
+    bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth,
+    bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss,
+    const char * loghost, bool change_password, Seat *seat) // WINSCP
+{
+    struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state);
+    memset(s, 0, sizeof(*s));
+    s->seat = seat;
+    s->ppl.vt = &ssh2_userauth_vtable;
+
+    s->successor_layer = successor_layer;
+    s->hostname = dupstr(hostname);
+    s->fullhostname = dupstr(fullhostname);
+    s->keyfile = filename_copy(keyfile);
+    s->show_banner = show_banner;
+    s->tryagent = tryagent;
+    s->notrivialauth = notrivialauth;
+    s->default_username = dupstr(default_username);
+    s->change_username = change_username;
+    s->try_ki_auth = try_ki_auth;
+    s->try_gssapi_auth = try_gssapi_auth;
+    s->try_gssapi_kex_auth = try_gssapi_kex_auth;
+    s->gssapi_fwd = gssapi_fwd;
+    s->shgss = shgss;
+    s->last_methods_string = strbuf_new();
+    s->is_trivial_auth = true;
+    s->loghost = dupstr(loghost); // WINSCP
+    s->change_password = change_password;
+    bufchain_init(&s->banner);
+    bufchain_sink_init(&s->banner_bs, &s->banner);
+
+    return &s->ppl;
+}
+
+void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth,
+                                       PacketProtocolLayer *transport)
+{
+    struct ssh2_userauth_state *s =
+        container_of(userauth, struct ssh2_userauth_state, ppl);
+    s->transport_layer = transport;
+}
+
+static void ssh2_userauth_free(PacketProtocolLayer *ppl)
+{
+    struct ssh2_userauth_state *s =
+        container_of(ppl, struct ssh2_userauth_state, ppl);
+    bufchain_clear(&s->banner);
+
+    if (s->successor_layer)
+        ssh_ppl_free(s->successor_layer);
+
+    if (s->agent_keys) {
+        size_t i; // WINSCP
+        for (i = 0; i < s->agent_keys_len; i++) {
+            strbuf_free(s->agent_keys[i].blob);
+            strbuf_free(s->agent_keys[i].comment);
+        }
+        sfree(s->agent_keys);
+    }
+    sfree(s->agent_response_to_free);
+    if (s->auth_agent_query)
+        agent_cancel_query(s->auth_agent_query);
+    filename_free(s->keyfile);
+    sfree(s->default_username);
+    sfree(s->locally_allocated_username);
+    sfree(s->hostname);
+    sfree(s->fullhostname);
+    if (s->cur_prompt)
+        free_prompts(s->cur_prompt);
+    sfree(s->publickey_comment);
+    sfree(s->publickey_algorithm);
+    if (s->publickey_blob)
+        strbuf_free(s->publickey_blob);
+    strbuf_free(s->last_methods_string);
+    sfree(s->loghost);
+    if (s->banner_scc)
+        stripctrl_free(s->banner_scc);
+    if (s->ki_scc)
+        stripctrl_free(s->ki_scc);
+    sfree(s);
+}
+
+static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s)
+{
+    PktIn *pktin;
+    ptrlen string;
+
+    while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) {
+        switch (pktin->type) {
+          case SSH2_MSG_USERAUTH_BANNER:
+            if (!s->show_banner) {
+                pq_pop(s->ppl.in_pq);
+                break;
+            }
+
+            string = get_string(pktin);
+            if (string.len > BANNER_LIMIT - bufchain_size(&s->banner))
+                string.len = BANNER_LIMIT - bufchain_size(&s->banner);
+            if (!s->banner_scc_initialised) {
+                s->banner_scc = seat_stripctrl_new(
+                    s->ppl.seat, BinarySink_UPCAST(&s->banner_bs), SIC_BANNER);
+                if (s->banner_scc)
+                    stripctrl_enable_line_limiting(s->banner_scc);
+                s->banner_scc_initialised = true;
+            }
+            if (s->banner_scc)
+                put_datapl(s->banner_scc, string);
+            else
+                put_datapl(&s->banner_bs, string);
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          default:
+            return;
+        }
+    }
+}
+
+static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s)
+{
+    ssh2_userauth_filter_queue(s);
+    return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
+{
+    struct ssh2_userauth_state *s =
+        container_of(ppl, struct ssh2_userauth_state, ppl);
+    PktIn *pktin;
+
+    ssh2_userauth_filter_queue(s);     /* no matter why we were called */
+
+    crBegin(s->crState);
+
+#ifndef NO_GSSAPI
+    s->tried_gssapi = false;
+    s->tried_gssapi_keyex_auth = false;
+#endif
+
+    /*
+     * Misc one-time setup for authentication.
+     */
+    s->publickey_blob = NULL;
+    s->session_id = ssh2_transport_get_session_id(s->transport_layer);
+
+    /*
+     * Load the public half of any configured public key file for
+     * later use.
+     */
+    if (!filename_is_null(s->keyfile)) {
+        int keytype;
+        ppl_logevent(WINSCP_BOM "Reading key file \"%s\"",
+                     filename_to_str(s->keyfile));
+        keytype = key_type(s->keyfile);
+        if (keytype == SSH_KEYTYPE_SSH2 ||
+            keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+            keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+            const char *error;
+            s->publickey_blob = strbuf_new();
+            if (ppk_loadpub_f(s->keyfile, &s->publickey_algorithm,
+                              BinarySink_UPCAST(s->publickey_blob),
+                              &s->publickey_comment, &error)) {
+                s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2);
+                if (!s->privatekey_available)
+                    ppl_logevent("Key file contains public key only");
+                s->privatekey_encrypted = ppk_encrypted_f(s->keyfile, NULL);
+            } else {
+                ppl_logevent("Unable to load key (%s)", error);
+                ppl_printf(WINSCP_BOM "Unable to load key file \"%s\" (%s)\r\n",
+                           filename_to_str(s->keyfile), error);
+                strbuf_free(s->publickey_blob);
+                s->publickey_blob = NULL;
+            }
+        } else {
+            ppl_logevent("Unable to use this key file (%s)",
+                         key_type_to_str(keytype));
+            ppl_printf(WINSCP_BOM "Unable to use key file \"%s\" (%s)\r\n",
+                       filename_to_str(s->keyfile),
+                       key_type_to_str(keytype));
+            s->publickey_blob = NULL;
+        }
+    }
+
+    /*
+     * Find out about any keys Pageant has (but if there's a public
+     * key configured, filter out all others).
+     */
+    if (s->tryagent && agent_exists()) {
+        ppl_logevent("Pageant is running. Requesting keys.");
+
+        /* Request the keys held by the agent. */
+        {
+            strbuf *request = strbuf_new_for_agent_query();
+            put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES);
+            ssh2_userauth_agent_query(s, request);
+            strbuf_free(request);
+            crWaitUntilV(!s->auth_agent_query);
+        }
+        BinarySource_BARE_INIT_PL(s->asrc, s->agent_response);
+
+        get_uint32(s->asrc); /* skip length field */
+        if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) {
+            size_t nkeys = get_uint32(s->asrc);
+            size_t origpos = s->asrc->pos;
+
+            /*
+             * Check that the agent response is well formed.
+             */
+            { // WINSCP
+            size_t i; // WINSCP
+            for (i = 0; i < nkeys; i++) {
+                get_string(s->asrc);   /* blob */
+                get_string(s->asrc);   /* comment */
+                if (get_err(s->asrc)) {
+                    ppl_logevent("Pageant's response was truncated");
+                    goto done_agent_query;
+                }
+            }
+            } // WINSCP
+
+            /*
+             * Copy the list of public-key blobs out of the Pageant
+             * response.
+             */
+            BinarySource_REWIND_TO(s->asrc, origpos);
+            s->agent_keys_len = nkeys;
+            s->agent_keys = snewn(s->agent_keys_len, agent_key);
+            { // WINSCP
+            size_t i; // WINSCP
+            for (i = 0; i < nkeys; i++) {
+                s->agent_keys[i].blob = strbuf_new();
+                put_datapl(s->agent_keys[i].blob, get_string(s->asrc));
+                s->agent_keys[i].comment = strbuf_new();
+                put_datapl(s->agent_keys[i].comment, get_string(s->asrc));
+
+                { // WINSCP
+                /* Also, extract the algorithm string from the start
+                 * of the public-key blob. */
+                BinarySource src[1];
+                BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
+                    s->agent_keys[i].blob));
+                s->agent_keys[i].algorithm = get_string(src);
+                } // WINSCP
+            }
+            } // WINSCP
+
+            ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys);
+
+            if (s->publickey_blob) {
+                /*
+                 * If we've been given a specific public key blob,
+                 * filter the list of keys to try from the agent down
+                 * to only that one, or none if it's not there.
+                 */
+                ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob);
+                size_t i;
+
+                for (i = 0; i < nkeys; i++) {
+                    if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf(
+                                             s->agent_keys[i].blob)))
+                        break;
+                }
+
+                if (i < nkeys) {
+                    ppl_logevent("Pageant key #%"SIZEu" matches "
+                                 "configured key file", i);
+                    s->agent_key_index = i;
+                    s->agent_key_limit = i+1;
+                } else {
+                    ppl_logevent("Configured key file not in Pageant");
+                    s->agent_key_index = 0;
+                    s->agent_key_limit = 0;
+                }
+            } else {
+                /*
+                 * Otherwise, try them all.
+                 */
+                s->agent_key_index = 0;
+                s->agent_key_limit = nkeys;
+            }
+        } else {
+            ppl_logevent("Failed to get reply from Pageant");
+        }
+      done_agent_query:;
+    }
+
+    /*
+     * We repeat this whole loop, including the username prompt,
+     * until we manage a successful authentication. If the user
+     * types the wrong _password_, they can be sent back to the
+     * beginning to try another username, if this is configured on.
+     * (If they specify a username in the config, they are never
+     * asked, even if they do give a wrong password.)
+     *
+     * I think this best serves the needs of
+     *
+     *  - the people who have no configuration, no keys, and just
+     *    want to try repeated (username,password) pairs until they
+     *    type both correctly
+     *
+     *  - people who have keys and configuration but occasionally
+     *    need to fall back to passwords
+     *
+     *  - people with a key held in Pageant, who might not have
+     *    logged in to a particular machine before; so they want to
+     *    type a username, and then _either_ their key will be
+     *    accepted, _or_ they will type a password. If they mistype
+     *    the username they will want to be able to get back and
+     *    retype it!
+     */
+    s->got_username = false;
+    while (1) {
+        /*
+         * Get a username.
+         */
+        if (s->got_username && !s->change_username) {
+            /*
+             * We got a username last time round this loop, and
+             * with change_username turned off we don't try to get
+             * it again.
+             */
+        } else if ((s->username = s->default_username) == NULL) {
+            s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+            s->cur_prompt->to_server = true;
+            s->cur_prompt->from_server = false;
+            s->cur_prompt->name = dupstr("SSH login name");
+            add_prompt(s->cur_prompt, dupstr("login as: "), true);
+            s->spr = seat_get_userpass_input(
+                ppl_get_iseat(&s->ppl), s->cur_prompt);
+            while (s->spr.kind == SPRK_INCOMPLETE) {
+                crReturnV;
+                s->spr = seat_get_userpass_input(
+                    ppl_get_iseat(&s->ppl), s->cur_prompt);
+            }
+            if (spr_is_abort(s->spr)) {
+                /*
+                 * seat_get_userpass_input() failed to get a username.
+                 * Terminate.
+                 */
+                free_prompts(s->cur_prompt);
+                s->cur_prompt = NULL;
+                ssh_spr_close(s->ppl.ssh, s->spr, "username prompt");
+                return;
+            }
+            sfree(s->locally_allocated_username); /* for change_username */
+            s->username = s->locally_allocated_username =
+                prompt_get_result(s->cur_prompt->prompts[0]);
+            free_prompts(s->cur_prompt);
+            s->cur_prompt = NULL;
+        } else {
+            if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))
+                ppl_printf(WINSCP_BOM "Using username \"%s\".\r\n", s->username);
+        }
+        s->got_username = true;
+
+        /*
+         * Send an authentication request using method "none": (a)
+         * just in case it succeeds, and (b) so that we know what
+         * authentication methods we can usefully try next.
+         */
+        s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;
+
+        s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+        put_stringz(s->pktout, s->username);
+        put_stringz(s->pktout, s->successor_layer->vt->name);
+        put_stringz(s->pktout, "none");    /* method */
+        pq_push(s->ppl.out_pq, s->pktout);
+        s->type = AUTH_TYPE_NONE;
+
+        s->tried_pubkey_config = false;
+        s->kbd_inter_refused = false;
+        s->done_agent = false;
+
+        while (1) {
+            /*
+             * Wait for the result of the last authentication request,
+             * unless the request terminated for some reason on our
+             * own side.
+             */
+            if (s->suppress_wait_for_response_packet) {
+                pktin = NULL;
+                s->suppress_wait_for_response_packet = false;
+            } else {
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+            }
+
+            /*
+             * Now is a convenient point to spew any banner material
+             * that we've accumulated. (This should ensure that when
+             * we exit the auth loop, we haven't any left to deal
+             * with.)
+             *
+             * Don't show the banner if we're operating in non-verbose
+             * non-interactive mode. (It's probably a script, which
+             * means nobody will read the banner _anyway_, and
+             * moreover the printing of the banner will screw up
+             * processing on the output of (say) plink.)
+             *
+             * The banner data has been sanitised already by this
+             * point, but we still need to precede and follow it with
+             * anti-spoofing header lines.
+             */
+            if (bufchain_size(&s->banner) &&
+                (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) {
+                if (s->banner_scc) {
+                    seat_antispoof_msg(
+                        ppl_get_iseat(&s->ppl),
+                        "Pre-authentication banner message from server:");
+                    seat_set_trust_status(s->ppl.seat, false);
+                }
+
+                { // WINSCP
+                bool mid_line = false;
+                while (bufchain_size(&s->banner) > 0) {
+                    ptrlen data = bufchain_prefix(&s->banner);
+                    seat_banner_pl(ppl_get_iseat(&s->ppl), data);
+                    mid_line =
+                        (((const char *)data.ptr)[data.len-1] != '\n');
+                    bufchain_consume(&s->banner, data.len);
+                }
+                bufchain_clear(&s->banner);
+
+                if (mid_line)
+                    seat_banner_pl(ppl_get_iseat(&s->ppl),
+                                   PTRLEN_LITERAL("\r\n"));
+
+                if (s->banner_scc) {
+                    seat_set_trust_status(s->ppl.seat, true);
+                    seat_antispoof_msg(ppl_get_iseat(&s->ppl),
+                                       "End of banner message from server");
+                }
+                } // WINSCP
+            }
+
+            if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
+                ppl_logevent("Access granted");
+                goto userauth_success;
+            }
+
+            if (pktin && pktin->type != SSH2_MSG_USERAUTH_FAILURE &&
+                s->type != AUTH_TYPE_GSSAPI) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet "
+                                "in response to authentication request, "
+                                "type %d (%s)", pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return;
+            }
+
+            /*
+             * OK, we're now sitting on a USERAUTH_FAILURE message, so
+             * we can look at the string in it and know what we can
+             * helpfully try next.
+             */
+            if (pktin && pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
+                ptrlen methods = get_string(pktin);
+                bool partial_success = get_bool(pktin);
+
+                if (!partial_success) {
+                    /*
+                     * We have received an unequivocal Access
+                     * Denied. This can translate to a variety of
+                     * messages, or no message at all.
+                     *
+                     * For forms of authentication which are attempted
+                     * implicitly, by which I mean without printing
+                     * anything in the window indicating that we're
+                     * trying them, we should never print 'Access
+                     * denied'.
+                     *
+                     * If we do print a message saying that we're
+                     * attempting some kind of authentication, it's OK
+                     * to print a followup message saying it failed -
+                     * but the message may sometimes be more specific
+                     * than simply 'Access denied'.
+                     *
+                     * Additionally, if we'd just tried password
+                     * authentication, we should break out of this
+                     * whole loop so as to go back to the username
+                     * prompt (iff we're configured to allow
+                     * username change attempts).
+                     */
+                    if (s->type == AUTH_TYPE_NONE) {
+                        /* do nothing */
+                    } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
+                               s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
+                        if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
+                            ppl_printf("Server refused our key\r\n");
+                        ppl_logevent("Server refused our key");
+                    } else if (s->type == AUTH_TYPE_PUBLICKEY) {
+                        /* This _shouldn't_ happen except by a
+                         * protocol bug causing client and server to
+                         * disagree on what is a correct signature. */
+                        ppl_printf("Server refused public-key signature"
+                                   " despite accepting key!\r\n");
+                        ppl_logevent("Server refused public-key signature"
+                                     " despite accepting key!");
+                    } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+                        /* quiet, so no ppl_printf */
+                        ppl_logevent("Server refused keyboard-interactive "
+                                     "authentication");
+                    } else if (s->type==AUTH_TYPE_GSSAPI) {
+                        /* always quiet, so no ppl_printf */
+                        /* also, the code down in the GSSAPI block has
+                         * already logged this in the Event Log */
+                    } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) {
+                        ppl_logevent("Keyboard-interactive authentication "
+                                     "failed");
+                        ppl_printf("Access denied\r\n");
+                    } else {
+                        assert(s->type == AUTH_TYPE_PASSWORD);
+                        ppl_logevent("Password authentication failed");
+                        ppl_printf("Access denied\r\n");
+
+                        if (s->change_username) {
+                            /* XXX perhaps we should allow
+                             * keyboard-interactive to do this too? */
+                            goto try_new_username;
+                        }
+                    }
+                } else {
+                    ppl_printf("Further authentication required\r\n");
+                    ppl_logevent("Further authentication required");
+                }
+
+                /*
+                 * Save the methods string for use in error messages.
+                 */
+                strbuf_clear(s->last_methods_string);
+                put_datapl(s->last_methods_string, methods);
+#ifdef WINSCP
+                ppl_logevent("Server offered these authentication methods: %s", s->last_methods_string->s);
+#endif
+
+                /*
+                 * Scan it for method identifiers we know about.
+                 */
+                { // WINSCP
+                bool srv_pubkey = false, srv_passwd = false;
+                bool srv_keyb_inter = false;
+#ifndef NO_GSSAPI
+                bool srv_gssapi = false, srv_gssapi_keyex_auth = false;
+#endif
+
+                ptrlen method; // WINSCP
+                for (; get_commasep_word(&methods, &method) ;) {
+                    if (ptrlen_eq_string(method, "publickey"))
+                        srv_pubkey = true;
+                    else if (ptrlen_eq_string(method, "password"))
+                        srv_passwd = true;
+                    else if (ptrlen_eq_string(method, "keyboard-interactive"))
+                        srv_keyb_inter = true;
+#ifndef NO_GSSAPI
+                    else if (ptrlen_eq_string(method, "gssapi-with-mic"))
+                        srv_gssapi = true;
+                    else if (ptrlen_eq_string(method, "gssapi-keyex"))
+                        srv_gssapi_keyex_auth = true;
+#endif
+                }
+
+                /*
+                 * And combine those flags with our own configuration
+                 * and context to set the main can_foo variables.
+                 */
+                s->can_pubkey = srv_pubkey;
+                s->can_passwd = srv_passwd;
+                s->can_keyb_inter = s->try_ki_auth && srv_keyb_inter;
+#ifndef NO_GSSAPI
+                s->can_gssapi = s->try_gssapi_auth && srv_gssapi &&
+                    s->shgss->libs->nlibraries > 0;
+                s->can_gssapi_keyex_auth = s->try_gssapi_kex_auth &&
+                    srv_gssapi_keyex_auth &&
+                    s->shgss->libs->nlibraries > 0 && s->shgss->ctx;
+#endif
+                } // WINSCP
+            }
+
+            s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;
+
+#ifndef NO_GSSAPI
+            if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) {
+
+                /* gssapi-keyex authentication */
+
+                s->type = AUTH_TYPE_GSSAPI;
+                s->tried_gssapi_keyex_auth = true;
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;
+
+                if (s->shgss->lib->gsslogmsg)
+                    ppl_logevent("%s", s->shgss->lib->gsslogmsg);
+
+                ppl_logevent("Trying gssapi-keyex...");
+                s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex");
+                pq_push(s->ppl.out_pq, s->pktout);
+                s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+                s->shgss->ctx = NULL;
+
+                continue;
+            } else
+#endif /* NO_GSSAPI */
+
+            if (s->can_pubkey && !s->done_agent &&
+                s->agent_key_index < s->agent_key_limit) {
+
+                /*
+                 * Attempt public-key authentication using a key from Pageant.
+                 */
+                s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm;
+                s->signflags = 0;
+                if (ptrlen_eq_string(s->agent_keyalg, "ssh-rsa")) {
+                    /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family,
+                     * if the server has announced support for them. */
+                    if (s->ppl.bpp->ext_info_rsa_sha512_ok) {
+                        s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-512");
+                        s->signflags = SSH_AGENT_RSA_SHA2_512;
+                    } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) {
+                        s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-256");
+                        s->signflags = SSH_AGENT_RSA_SHA2_256;
+                    }
+                }
+
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;
+
+                ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index);
+
+                /* See if server will accept it */
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, s->successor_layer->vt->name);
+                put_stringz(s->pktout, "publickey");
+                                                    /* method */
+                put_bool(s->pktout, false); /* no signature included */
+                put_stringpl(s->pktout, s->agent_keyalg);
+                put_stringpl(s->pktout, ptrlen_from_strbuf(
+                            s->agent_keys[s->agent_key_index].blob));
+                pq_push(s->ppl.out_pq, s->pktout);
+                s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
+
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+
+                    /* Offer of key refused, presumably via
+                     * USERAUTH_FAILURE. Requeue for the next iteration. */
+                    pq_push_front(s->ppl.in_pq, pktin);
+
+                } else {
+                    strbuf *agentreq, *sigdata;
+                    ptrlen comment = ptrlen_from_strbuf(
+                        s->agent_keys[s->agent_key_index].comment);
+
+                    if (seat_verbose(s->ppl.seat))
+                        ppl_printf("Authenticating with public key "
+                                   "\"%.*s\" from agent\r\n",
+                                   PTRLEN_PRINTF(comment));
+
+                    /*
+                     * Server is willing to accept the key.
+                     * Construct a SIGN_REQUEST.
+                     */
+                    s->pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                    put_stringz(s->pktout, s->username);
+                    put_stringz(s->pktout, s->successor_layer->vt->name);
+                    put_stringz(s->pktout, "publickey");
+                                                        /* method */
+                    put_bool(s->pktout, true);  /* signature included */
+                    put_stringpl(s->pktout, s->agent_keyalg);
+                    put_stringpl(s->pktout, ptrlen_from_strbuf(
+                            s->agent_keys[s->agent_key_index].blob));
+
+                    /* Ask agent for signature. */
+                    agentreq = strbuf_new_for_agent_query();
+                    put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST);
+                    put_stringpl(agentreq, ptrlen_from_strbuf(
+                            s->agent_keys[s->agent_key_index].blob));
+                    /* Now the data to be signed... */
+                    sigdata = strbuf_new();
+                    ssh2_userauth_add_session_id(s, sigdata);
+                    put_data(sigdata, s->pktout->data + 5,
+                             s->pktout->length - 5);
+                    put_stringsb(agentreq, sigdata);
+                    /* And finally the flags word. */
+                    put_uint32(agentreq, s->signflags);
+                    ssh2_userauth_agent_query(s, agentreq);
+                    strbuf_free(agentreq);
+                    crWaitUntilV(!s->auth_agent_query);
+
+                    if (s->agent_response.ptr) {
+                        ptrlen sigblob;
+                        BinarySource src[1];
+                        BinarySource_BARE_INIT(src, s->agent_response.ptr,
+                                               s->agent_response.len);
+                        get_uint32(src); /* skip length field */
+                        if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE &&
+                            (sigblob = get_string(src), !get_err(src))) {
+                            ppl_logevent("Sending Pageant's response");
+                            ssh2_userauth_add_sigblob(
+                                s, s->pktout,
+                                ptrlen_from_strbuf(
+                                    s->agent_keys[s->agent_key_index].blob),
+                                sigblob);
+                            pq_push(s->ppl.out_pq, s->pktout);
+                            s->type = AUTH_TYPE_PUBLICKEY;
+                            s->is_trivial_auth = false;
+                        } else {
+                            ppl_logevent("Pageant refused signing request");
+                            ppl_printf("Pageant failed to "
+                                       "provide a signature\r\n");
+                            s->suppress_wait_for_response_packet = true;
+                            ssh_free_pktout(s->pktout);
+                        }
+                    } else {
+                        ppl_logevent("Pageant failed to respond to "
+                                     "signing request");
+                        ppl_printf("Pageant failed to "
+                                   "respond to signing request\r\n");
+                        s->suppress_wait_for_response_packet = true;
+                        ssh_free_pktout(s->pktout);
+                    }
+                }
+
+                /* Do we have any keys left to try? */
+                if (++s->agent_key_index >= s->agent_key_limit)
+                    s->done_agent = true;
+
+            } else if (s->can_pubkey && s->publickey_blob &&
+                       s->privatekey_available && !s->tried_pubkey_config) {
+
+                ssh2_userkey *key;   /* not live over crReturn */
+                char *passphrase;           /* not live over crReturn */
+
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;
+
+                s->tried_pubkey_config = true;
+
+                /*
+                 * Try the public key supplied in the configuration.
+                 *
+                 * First, try to upgrade its algorithm.
+                 */
+                if (!strcmp(s->publickey_algorithm, "ssh-rsa")) {
+                    /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family,
+                     * if the server has announced support for them. */
+                    if (s->ppl.bpp->ext_info_rsa_sha512_ok) {
+                        sfree(s->publickey_algorithm);
+                        s->publickey_algorithm = dupstr("rsa-sha2-512");
+                        s->signflags = SSH_AGENT_RSA_SHA2_512;
+                    } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) {
+                        sfree(s->publickey_algorithm);
+                        s->publickey_algorithm = dupstr("rsa-sha2-256");
+                        s->signflags = SSH_AGENT_RSA_SHA2_256;
+                    }
+                }
+
+                /*
+                 * Offer the public blob to see if the server is willing to
+                 * accept it.
+                 */
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, s->successor_layer->vt->name);
+                put_stringz(s->pktout, "publickey");    /* method */
+                put_bool(s->pktout, false);
+                                                /* no signature included */
+                put_stringz(s->pktout, s->publickey_algorithm);
+                put_string(s->pktout, s->publickey_blob->s,
+                           s->publickey_blob->len);
+                pq_push(s->ppl.out_pq, s->pktout);
+                ppl_logevent("Offered public key");
+
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+                    /* Key refused. Give up. */
+                    pq_push_front(s->ppl.in_pq, pktin);
+                    s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+                    continue; /* process this new message */
+                }
+                ppl_logevent("Offer of public key accepted");
+
+                /*
+                 * Actually attempt a serious authentication using
+                 * the key.
+                 */
+                if (seat_verbose(s->ppl.seat))
+                    ppl_printf("Authenticating with public key \"%s\"\r\n",
+                               s->publickey_comment);
+
+                key = NULL;
+                while (!key) {
+                    const char *error;  /* not live over crReturn */
+                    if (s->privatekey_encrypted) {
+                        /*
+                         * Get a passphrase from the user.
+                         */
+                        s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+                        s->cur_prompt->to_server = false;
+                        s->cur_prompt->from_server = false;
+                        s->cur_prompt->name = dupstr("SSH key passphrase");
+                        add_prompt(s->cur_prompt,
+                                   dupprintf("Passphrase for key \"%s\": ",
+                                             s->publickey_comment),
+                                   false);
+                        s->spr = seat_get_userpass_input(
+                            ppl_get_iseat(&s->ppl), s->cur_prompt);
+                        while (s->spr.kind == SPRK_INCOMPLETE) {
+                            crReturnV;
+                            s->spr = seat_get_userpass_input(
+                                ppl_get_iseat(&s->ppl), s->cur_prompt);
+                        }
+                        if (spr_is_abort(s->spr)) {
+                            /* Failed to get a passphrase. Terminate. */
+                            free_prompts(s->cur_prompt);
+                            s->cur_prompt = NULL;
+                            ssh_bpp_queue_disconnect(
+                                s->ppl.bpp, "Unable to authenticate",
+                                SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+                            ssh_spr_close(s->ppl.ssh, s->spr,
+                                          "passphrase prompt");
+                            return;
+                        }
+                        passphrase =
+                            prompt_get_result(s->cur_prompt->prompts[0]);
+                        free_prompts(s->cur_prompt);
+                        s->cur_prompt = NULL;
+                    } else {
+                        passphrase = NULL; /* no passphrase needed */
+                    }
+
+                    /*
+                     * Try decrypting the key.
+                     */
+                    key = ppk_load_f(s->keyfile, passphrase, &error);
+                    if (passphrase) {
+                        /* burn the evidence */
+                        smemclr(passphrase, strlen(passphrase));
+                        sfree(passphrase);
+                    }
+                    if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
+                        if (passphrase &&
+                            (key == SSH2_WRONG_PASSPHRASE)) {
+                            ppl_printf("Wrong passphrase\r\n");
+                            key = NULL;
+                            /* and loop again */
+                        } else {
+                            ppl_printf("Unable to load private key (%s)\r\n",
+                                       error);
+                            key = NULL;
+                            s->suppress_wait_for_response_packet = true;
+                            break; /* try something else */
+                        }
+                    } else {
+                        /* FIXME: if we ever support variable signature
+                         * flags, this is somewhere they'll need to be
+                         * put */
+                        char *invalid = ssh_key_invalid(key->key, 0);
+                        if (invalid) {
+                            ppl_printf("Cannot use this private key (%s)\r\n",
+                                       invalid);
+                            ssh_key_free(key->key);
+                            sfree(key->comment);
+                            sfree(key);
+                            sfree(invalid);
+                            key = NULL;
+                            s->suppress_wait_for_response_packet = true;
+                            break; /* try something else */
+                        }
+                    }
+                }
+
+                if (key) {
+                    strbuf *pkblob, *sigdata, *sigblob;
+
+                    /*
+                     * We have loaded the private key and the server
+                     * has announced that it's willing to accept it.
+                     * Hallelujah. Generate a signature and send it.
+                     */
+                    s->pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                    put_stringz(s->pktout, s->username);
+                    put_stringz(s->pktout, s->successor_layer->vt->name);
+                    put_stringz(s->pktout, "publickey"); /* method */
+                    put_bool(s->pktout, true); /* signature follows */
+                    put_stringz(s->pktout, s->publickey_algorithm);
+                    pkblob = strbuf_new();
+                    ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob));
+                    put_string(s->pktout, pkblob->s, pkblob->len);
+
+                    /*
+                     * The data to be signed is:
+                     *
+                     *   string  session-id
+                     *
+                     * followed by everything so far placed in the
+                     * outgoing packet.
+                     */
+                    sigdata = strbuf_new();
+                    ssh2_userauth_add_session_id(s, sigdata);
+                    put_data(sigdata, s->pktout->data + 5,
+                             s->pktout->length - 5);
+                    sigblob = strbuf_new();
+                    ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata),
+                                 s->signflags, BinarySink_UPCAST(sigblob));
+                    strbuf_free(sigdata);
+                    ssh2_userauth_add_sigblob(
+                        s, s->pktout, ptrlen_from_strbuf(pkblob),
+                        ptrlen_from_strbuf(sigblob));
+                    strbuf_free(pkblob);
+                    strbuf_free(sigblob);
+
+                    pq_push(s->ppl.out_pq, s->pktout);
+                    ppl_logevent("Sent public key signature");
+                    s->type = AUTH_TYPE_PUBLICKEY;
+                    ssh_key_free(key->key);
+                    sfree(key->comment);
+                    sfree(key);
+                    s->is_trivial_auth = false;
+                }
+
+#ifndef NO_GSSAPI
+            } else if (s->can_gssapi && !s->tried_gssapi) {
+
+                /* gssapi-with-mic authentication */
+
+                ptrlen data;
+
+                s->type = AUTH_TYPE_GSSAPI;
+                s->tried_gssapi = true;
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;
+
+                if (s->shgss->lib->gsslogmsg)
+                    ppl_logevent("%s", s->shgss->lib->gsslogmsg);
+
+                /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
+                ppl_logevent("Trying gssapi-with-mic...");
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, s->successor_layer->vt->name);
+                put_stringz(s->pktout, "gssapi-with-mic");
+                ppl_logevent("Attempting GSSAPI authentication");
+
+                /* add mechanism info */
+                s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf);
+
+                /* number of GSSAPI mechanisms */
+                put_uint32(s->pktout, 1);
+
+                /* length of OID + 2 */
+                put_uint32(s->pktout, s->gss_buf.length + 2);
+                put_byte(s->pktout, SSH2_GSS_OIDTYPE);
+
+                /* length of OID */
+                put_byte(s->pktout, s->gss_buf.length);
+
+                put_data(s->pktout, s->gss_buf.value, s->gss_buf.length);
+                pq_push(s->ppl.out_pq, s->pktout);
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) {
+                    ppl_logevent("GSSAPI authentication request refused");
+                    pq_push_front(s->ppl.in_pq, pktin);
+                    continue;
+                }
+
+                /* check returned packet ... */
+
+                data = get_string(pktin);
+                s->gss_rcvtok.value = (char *)data.ptr;
+                s->gss_rcvtok.length = data.len;
+                if (s->gss_rcvtok.length != s->gss_buf.length + 2 ||
+                    ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE ||
+                    ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length ||
+                    memcmp((char *)s->gss_rcvtok.value + 2,
+                           s->gss_buf.value,s->gss_buf.length) ) {
+                    ppl_logevent("GSSAPI authentication - wrong response "
+                                 "from server");
+                    continue;
+                }
+
+                /* Import server name if not cached from KEX */
+                if (s->shgss->srv_name == GSS_C_NO_NAME) {
+                    // WINSCP
+                    const char * fullhostname = s->fullhostname;
+                    if (s->loghost[0] != '\0')
+                    {
+                        fullhostname = s->loghost;
+                    }
+                    // /WINSCP
+                    s->gss_stat = s->shgss->lib->import_name(
+                        s->shgss->lib, fullhostname, &s->shgss->srv_name); // WINSCP
+                    if (s->gss_stat != SSH_GSS_OK) {
+                        if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
+                            ppl_logevent("GSSAPI import name failed -"
+                                         " Bad service name");
+                        else
+                            ppl_logevent("GSSAPI import name failed");
+                        continue;
+                    }
+                }
+
+                /* Allocate our gss_ctx */
+                s->gss_stat = s->shgss->lib->acquire_cred(
+                    s->shgss->lib, &s->shgss->ctx, NULL);
+                if (s->gss_stat != SSH_GSS_OK) {
+                    ppl_logevent("GSSAPI authentication failed to get "
+                                 "credentials");
+                    /* The failure was on our side, so the server
+                     * won't be sending a response packet indicating
+                     * failure. Avoid waiting for it next time round
+                     * the loop. */
+                    s->suppress_wait_for_response_packet = true;
+                    continue;
+                }
+
+                /* initial tokens are empty */
+                SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+                SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
+
+                /* now enter the loop */
+                do {
+                    /*
+                     * When acquire_cred yields no useful expiration, go with
+                     * the service ticket expiration.
+                     */
+                    s->gss_stat = s->shgss->lib->init_sec_context
+                        (s->shgss->lib,
+                         &s->shgss->ctx,
+                         s->shgss->srv_name,
+                         s->gssapi_fwd,
+                         &s->gss_rcvtok,
+                         &s->gss_sndtok,
+                         NULL,
+                         NULL);
+
+                    if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
+                        s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
+                        ppl_logevent("GSSAPI authentication initialisation "
+                                     "failed");
+
+                        if (s->shgss->lib->display_status(s->shgss->lib,
+                                s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) {
+                            ppl_logevent("%s", (char *)s->gss_buf.value);
+                            sfree(s->gss_buf.value);
+                        }
+
+                        pq_push_front(s->ppl.in_pq, pktin);
+                        break;
+                    }
+                    ppl_logevent("GSSAPI authentication initialised");
+
+                    /*
+                     * Client and server now exchange tokens until GSSAPI
+                     * no longer says CONTINUE_NEEDED
+                     */
+                    if (s->gss_sndtok.length != 0) {
+                        s->is_trivial_auth = false;
+                        s->pktout =
+                            ssh_bpp_new_pktout(
+                                s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+                        put_string(s->pktout,
+                                   s->gss_sndtok.value, s->gss_sndtok.length);
+                        pq_push(s->ppl.out_pq, s->pktout);
+                        s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+                    }
+
+                    if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
+                        crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+
+                        if (pktin->type == SSH2_MSG_USERAUTH_GSSAPI_ERRTOK) {
+                            /*
+                             * Per RFC 4462 section 3.9, this packet
+                             * type MUST immediately precede an
+                             * ordinary USERAUTH_FAILURE.
+                             *
+                             * We currently don't know how to do
+                             * anything with the GSSAPI error token
+                             * contained in this packet, so we ignore
+                             * it and just wait for the following
+                             * FAILURE.
+                             */
+                            crMaybeWaitUntilV(
+                                (pktin = ssh2_userauth_pop(s)) != NULL);
+                            if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) {
+                                ssh_proto_error(
+                                    s->ppl.ssh, "Received unexpected packet "
+                                    "after SSH_MSG_USERAUTH_GSSAPI_ERRTOK "
+                                    "(expected SSH_MSG_USERAUTH_FAILURE): "
+                                    "type %d (%s)", pktin->type,
+                                    ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                                  s->ppl.bpp->pls->actx,
+                                                  pktin->type));
+                                return;
+                            }
+                        }
+
+                        if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
+                            ppl_logevent("GSSAPI authentication failed");
+                            s->gss_stat = SSH_GSS_FAILURE;
+                            pq_push_front(s->ppl.in_pq, pktin);
+                            break;
+                        } else if (pktin->type !=
+                                   SSH2_MSG_USERAUTH_GSSAPI_TOKEN) {
+                            ppl_logevent("GSSAPI authentication -"
+                                         " bad server response");
+                            s->gss_stat = SSH_GSS_FAILURE;
+                            break;
+                        }
+                        data = get_string(pktin);
+                        s->gss_rcvtok.value = (char *)data.ptr;
+                        s->gss_rcvtok.length = data.len;
+                    }
+                } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+                if (s->gss_stat != SSH_GSS_OK) {
+                    s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+                    continue;
+                }
+                ppl_logevent("GSSAPI authentication loop finished OK");
+
+                /* Now send the MIC */
+
+                s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic");
+                pq_push(s->ppl.out_pq, s->pktout);
+
+                s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+                continue;
+#endif
+            } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
+
+                /*
+                 * Keyboard-interactive authentication.
+                 */
+
+                s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER;
+
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, s->successor_layer->vt->name);
+                put_stringz(s->pktout, "keyboard-interactive");
+                                                        /* method */
+                put_stringz(s->pktout, "");     /* lang */
+                put_stringz(s->pktout, "");     /* submethods */
+                pq_push(s->ppl.out_pq, s->pktout);
+
+                ppl_logevent("Attempting keyboard-interactive authentication");
+
+                if (!s->ki_scc_initialised) {
+                    s->ki_scc = seat_stripctrl_new(
+                        s->ppl.seat, NULL, SIC_KI_PROMPTS);
+                    if (s->ki_scc)
+                        stripctrl_enable_line_limiting(s->ki_scc);
+                    s->ki_scc_initialised = true;
+                }
+
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+                    /* Server is not willing to do keyboard-interactive
+                     * at all (or, bizarrely but legally, accepts the
+                     * user without actually issuing any prompts).
+                     * Give up on it entirely. */
+                    pq_push_front(s->ppl.in_pq, pktin);
+                    s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+                    s->kbd_inter_refused = true; /* don't try it again */
+                    continue;
+                }
+
+                s->ki_printed_header = false;
+
+                /*
+                 * Loop while the server continues to send INFO_REQUESTs.
+                 */
+                while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+                    ptrlen name, inst;
+                    strbuf *sb;
+
+                    /*
+                     * We've got a fresh USERAUTH_INFO_REQUEST.
+                     * Get the preamble and start building a prompt.
+                     */
+                    name = get_string(pktin);
+                    inst = get_string(pktin);
+                    get_string(pktin); /* skip language tag */
+                    s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+                    s->cur_prompt->to_server = true;
+                    s->cur_prompt->from_server = true;
+
+                    /*
+                     * Get any prompt(s) from the packet.
+                     */
+                    s->num_prompts = get_uint32(pktin);
+                    { // WINSCP
+                    uint32_t i;
+                    for (i = 0; i < s->num_prompts; i++) {
+                        s->is_trivial_auth = false;
+                        { // WINSCP
+                        ptrlen prompt = get_string(pktin);
+                        bool echo = get_bool(pktin);
+
+                        if (get_err(pktin)) {
+                            ssh_proto_error(
+                                s->ppl.ssh, "Server sent truncated "
+                                "SSH_MSG_USERAUTH_INFO_REQUEST packet");
+                            return;
+                        }
+
+                        sb = strbuf_new();
+                        if (!prompt.len) {
+                            put_datapl(sb, PTRLEN_LITERAL(
+                                "<server failed to send prompt>: "));
+                        } else if (s->ki_scc) {
+                            stripctrl_retarget(
+                                s->ki_scc, BinarySink_UPCAST(sb));
+                            put_datapl(s->ki_scc, prompt);
+                            stripctrl_retarget(s->ki_scc, NULL);
+                        } else {
+                            put_datapl(sb, prompt);
+                        }
+                        add_prompt(s->cur_prompt, strbuf_to_str(sb), echo);
+                        } // WINSCP
+                    }
+                    } // WINSCP
+
+                    /*
+                     * Make the header strings. This includes the
+                     * 'name' (optional dialog-box title) and
+                     * 'instruction' from the server.
+                     *
+                     * First, display our disambiguating header line
+                     * if this is the first time round the loop -
+                     * _unless_ the server has sent a completely empty
+                     * k-i packet with no prompts _or_ text, which
+                     * apparently some do. In that situation there's
+                     * no need to alert the user that the following
+                     * text is server- supplied, because, well, _what_
+                     * text?
+                     *
+                     * We also only do this if we got a stripctrl,
+                     * because if we didn't, that suggests this is all
+                     * being done via dialog boxes anyway.
+                     */
+                    if (!s->ki_printed_header && s->ki_scc &&
+                        (s->num_prompts || name.len || inst.len)) {
+                        seat_antispoof_msg(
+                            ppl_get_iseat(&s->ppl), "Keyboard-interactive "
+                            "authentication prompts from server:");
+                        s->ki_printed_header = true;
+                        seat_set_trust_status(s->ppl.seat, false);
+                    }
+
+                    sb = strbuf_new();
+                    if (name.len) {
+                        put_datapl(sb, PTRLEN_LITERAL("SSH server: ")); // WINSCP
+                        if (s->ki_scc) {
+                            stripctrl_retarget(s->ki_scc,
+                                               BinarySink_UPCAST(sb));
+                            put_datapl(s->ki_scc, name);
+                            stripctrl_retarget(s->ki_scc, NULL);
+                        } else {
+                            put_datapl(sb, name);
+                        }
+                        s->cur_prompt->name_reqd = true;
+                    } else {
+                        put_datapl(sb, PTRLEN_LITERAL(
+                            "SSH server authentication"));
+                        s->cur_prompt->name_reqd = false;
+                    }
+                    s->cur_prompt->name = strbuf_to_str(sb);
+
+                    sb = strbuf_new();
+                    if (inst.len) {
+                        if (s->ki_scc) {
+                            stripctrl_retarget(s->ki_scc,
+                                               BinarySink_UPCAST(sb));
+                            put_datapl(s->ki_scc, inst);
+                            stripctrl_retarget(s->ki_scc, NULL);
+                        } else {
+                            put_datapl(sb, inst);
+                        }
+                        s->cur_prompt->instr_reqd = true;
+                    } else {
+                        s->cur_prompt->instr_reqd = false;
+                    }
+                    if (sb->len)
+                        s->cur_prompt->instruction = strbuf_to_str(sb);
+                    else
+                        strbuf_free(sb);
+
+                    /*
+                     * Our prompts_t is fully constructed now. Get the
+                     * user's response(s).
+                     */
+                    s->spr = seat_get_userpass_input(
+                        ppl_get_iseat(&s->ppl), s->cur_prompt);
+                    while (s->spr.kind == SPRK_INCOMPLETE) {
+                        crReturnV;
+                        s->spr = seat_get_userpass_input(
+                            ppl_get_iseat(&s->ppl), s->cur_prompt);
+                    }
+                    if (spr_is_abort(s->spr)) {
+                        /*
+                         * Failed to get responses. Terminate.
+                         */
+                        free_prompts(s->cur_prompt);
+                        s->cur_prompt = NULL;
+                        ssh_bpp_queue_disconnect(
+                            s->ppl.bpp, "Unable to authenticate",
+                            SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+                        ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-"
+                                      "interactive authentication prompt");
+                        return;
+                    }
+
+                    /*
+                     * Send the response(s) to the server.
+                     */
+                    s->pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
+                    put_uint32(s->pktout, s->num_prompts);
+                    { // WINSCP
+                    uint32_t i; // WINSCP
+                    for (i = 0; i < s->num_prompts; i++) {
+                        put_stringz(s->pktout, prompt_get_result_ref(
+                                        s->cur_prompt->prompts[i]));
+                    }
+                    } // WINSCP
+                    s->pktout->minlen = 256;
+                    pq_push(s->ppl.out_pq, s->pktout);
+
+                    /*
+                     * Free the prompts structure from this iteration.
+                     * If there's another, a new one will be allocated
+                     * when we return to the top of this while loop.
+                     */
+                    free_prompts(s->cur_prompt);
+                    s->cur_prompt = NULL;
+
+                    /*
+                     * Get the next packet in case it's another
+                     * INFO_REQUEST.
+                     */
+                    crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+
+                }
+
+                /*
+                 * Print our trailer line, if we printed a header.
+                 */
+                if (s->ki_printed_header) {
+                    seat_set_trust_status(s->ppl.seat, true);
+                    seat_antispoof_msg(
+                        ppl_get_iseat(&s->ppl),
+                        "End of keyboard-interactive prompts from server");
+                }
+
+                /*
+                 * We should have SUCCESS or FAILURE now.
+                 */
+                pq_push_front(s->ppl.in_pq, pktin);
+
+            } else if (s->can_passwd) {
+                s->is_trivial_auth = false;
+                { // WINSCP
+                /*
+                 * Plain old password authentication.
+                 */
+                bool changereq_first_time; /* not live over crReturn */
+
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD;
+
+                // WINSCP
+                if (s->change_password)
+                {
+                    s->password = dupstr("");
+                    s->type = AUTH_TYPE_PASSWORD;
+                }
+                else
+                {
+                // no indentation to ease merges
+                // /WINSCP
+                s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+                s->cur_prompt->to_server = true;
+                s->cur_prompt->from_server = false;
+                s->cur_prompt->name = dupstr("SSH password");
+                add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
+                                                    s->username, s->hostname),
+                           false);
+
+                s->spr = seat_get_userpass_input(
+                    ppl_get_iseat(&s->ppl), s->cur_prompt);
+                while (s->spr.kind == SPRK_INCOMPLETE) {
+                    crReturnV;
+                    s->spr = seat_get_userpass_input(
+                        ppl_get_iseat(&s->ppl), s->cur_prompt);
+                }
+                if (spr_is_abort(s->spr)) {
+                    /*
+                     * Failed to get responses. Terminate.
+                     */
+                    free_prompts(s->cur_prompt);
+                    s->cur_prompt = NULL;
+                    ssh_bpp_queue_disconnect(
+                        s->ppl.bpp, "Unable to authenticate",
+                        SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+                    ssh_spr_close(s->ppl.ssh, s->spr, "password prompt");
+                    return;
+                }
+                /*
+                 * Squirrel away the password. (We may need it later if
+                 * asked to change it.)
+                 */
+                s->password = prompt_get_result(s->cur_prompt->prompts[0]);
+                free_prompts(s->cur_prompt);
+                s->cur_prompt = NULL;
+
+                /*
+                 * Send the password packet.
+                 *
+                 * We pad out the password packet to 256 bytes to make
+                 * it harder for an attacker to find the length of the
+                 * user's password.
+                 *
+                 * Anyone using a password longer than 256 bytes
+                 * probably doesn't have much to worry about from
+                 * people who find out how long their password is!
+                 */
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, s->successor_layer->vt->name);
+                put_stringz(s->pktout, "password");
+                put_bool(s->pktout, false);
+                put_stringz(s->pktout, s->password);
+                s->pktout->minlen = 256;
+                pq_push(s->ppl.out_pq, s->pktout);
+                ppl_logevent("Sent password");
+                s->type = AUTH_TYPE_PASSWORD;
+
+                /*
+                 * Wait for next packet, in case it's a password change
+                 * request.
+                 */
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                } // WINSCP
+                changereq_first_time = true;
+
+                while ((pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) ||
+                       s->change_password) { // WINSCP
+
+                    /*
+                     * We're being asked for a new password
+                     * (perhaps not for the first time).
+                     * Loop until the server accepts it.
+                     */
+
+                    bool got_new = false; /* not live over crReturn */
+                    ptrlen prompt;  /* not live over crReturn */
+
+                    if (!s->change_password) // WINSCP
+                    {
+                        const char *msg;
+                        if (changereq_first_time)
+                            msg = "Server requested password change";
+                        else
+                            msg = "Server rejected new password";
+                        ppl_logevent("%s", msg);
+                        ppl_printf("%s\r\n", msg);
+                    }
+
+                    s->change_password = false; // WINSCP
+
+                    prompt = get_string(pktin);
+
+                    s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+                    s->cur_prompt->to_server = true;
+                    s->cur_prompt->from_server = false;
+                    s->cur_prompt->name = dupstr("New SSH password");
+                    s->cur_prompt->instruction = mkstr(prompt);
+                    s->cur_prompt->instr_reqd = true;
+                    /*
+                     * There's no explicit requirement in the protocol
+                     * for the "old" passwords in the original and
+                     * password-change messages to be the same, and
+                     * apparently some Cisco kit supports password change
+                     * by the user entering a blank password originally
+                     * and the real password subsequently, so,
+                     * reluctantly, we prompt for the old password again.
+                     *
+                     * (On the other hand, some servers don't even bother
+                     * to check this field.)
+                     */
+                    add_prompt(s->cur_prompt,
+                               dupstr("Current password (blank for previously entered password): "),
+                               false);
+                    add_prompt(s->cur_prompt, dupstr("Enter new password: "),
+                               false);
+                    add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
+                               false);
+
+                    /*
+                     * Loop until the user manages to enter the same
+                     * password twice.
+                     */
+                    while (!got_new) {
+                        s->spr = seat_get_userpass_input(
+                            ppl_get_iseat(&s->ppl), s->cur_prompt);
+                        while (s->spr.kind == SPRK_INCOMPLETE) {
+                            crReturnV;
+                            s->spr = seat_get_userpass_input(
+                                ppl_get_iseat(&s->ppl), s->cur_prompt);
+                        }
+                        if (spr_is_abort(s->spr)) {
+                            /*
+                             * Failed to get responses. Terminate.
+                             */
+                            /* burn the evidence */
+                            free_prompts(s->cur_prompt);
+                            s->cur_prompt = NULL;
+                            smemclr(s->password, strlen(s->password));
+                            sfree(s->password);
+                            ssh_bpp_queue_disconnect(
+                                s->ppl.bpp, "Unable to authenticate",
+                                SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+                            ssh_spr_close(s->ppl.ssh, s->spr,
+                                          "password-change prompt");
+                            return;
+                        }
+
+                        /*
+                         * If the user specified a new original password
+                         * (IYSWIM), overwrite any previously specified
+                         * one.
+                         * (A side effect is that the user doesn't have to
+                         * re-enter it if they louse up the new password.)
+                         */
+                        if (s->cur_prompt->prompts[0]->result->s[0]) {
+                            smemclr(s->password, strlen(s->password));
+                                /* burn the evidence */
+                            sfree(s->password);
+                            s->password = prompt_get_result(
+                                s->cur_prompt->prompts[0]);
+                        }
+
+                        /*
+                         * Check the two new passwords match.
+                         */
+                        got_new = !strcmp(
+                            prompt_get_result_ref(s->cur_prompt->prompts[1]),
+                            prompt_get_result_ref(s->cur_prompt->prompts[2]));
+                        if (!got_new)
+                            /* They don't. Silly user. */
+                            ppl_printf("Passwords do not match\r\n");
+
+                    }
+
+                    /*
+                     * Send the new password (along with the old one).
+                     * (see above for padding rationale)
+                     */
+                    s->pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                    put_stringz(s->pktout, s->username);
+                    put_stringz(s->pktout, s->successor_layer->vt->name);
+                    put_stringz(s->pktout, "password");
+                    put_bool(s->pktout, true);
+                    put_stringz(s->pktout, s->password);
+                    put_stringz(s->pktout, prompt_get_result_ref(
+                                    s->cur_prompt->prompts[1]));
+                    free_prompts(s->cur_prompt);
+                    s->cur_prompt = NULL;
+                    s->pktout->minlen = 256;
+                    pq_push(s->ppl.out_pq, s->pktout);
+                    ppl_logevent("Sent new password");
+
+                    /*
+                     * Now see what the server has to say about it.
+                     * (If it's CHANGEREQ again, it's not happy with the
+                     * new password.)
+                     */
+                    crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                    changereq_first_time = false;
+
+                }
+
+                /*
+                 * We need to reexamine the current pktin at the top
+                 * of the loop. Either:
+                 *  - we weren't asked to change password at all, in
+                 *    which case it's a SUCCESS or FAILURE with the
+                 *    usual meaning
+                 *  - we sent a new password, and the server was
+                 *    either OK with it (SUCCESS or FAILURE w/partial
+                 *    success) or unhappy with the _old_ password
+                 *    (FAILURE w/o partial success)
+                 * In any of these cases, we go back to the top of
+                 * the loop and start again.
+                 */
+                pq_push_front(s->ppl.in_pq, pktin);
+
+                /*
+                 * We don't need the old password any more, in any
+                 * case. Burn the evidence.
+                 */
+                smemclr(s->password, strlen(s->password));
+                sfree(s->password);
+                } // WINSCP
+
+            } else {
+                ssh_bpp_queue_disconnect(
+                    s->ppl.bpp,
+                    "No supported authentication methods available",
+                    SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE);
+                ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
+                             "available (server sent: %s)",
+                             s->last_methods_string->s);
+                return;
+            }
+
+        }
+      try_new_username:;
+    }
+
+  userauth_success:
+    if (s->notrivialauth && s->is_trivial_auth) {
+        ssh_proto_error(s->ppl.ssh, "Authentication was trivial! "
+                        "Abandoning session as specified in configuration.");
+        return;
+    }
+
+    /*
+     * We've just received USERAUTH_SUCCESS, and we haven't sent
+     * any packets since. Signal the transport layer to consider
+     * doing an immediate rekey, if it has any reason to want to.
+     */
+    ssh2_transport_notify_auth_done(s->transport_layer);
+
+    /*
+     * Finally, hand over to our successor layer, and return
+     * immediately without reaching the crFinishV: ssh_ppl_replace
+     * will have freed us, so crFinishV's zeroing-out of crState would
+     * be a use-after-free bug.
+     */
+    {
+        PacketProtocolLayer *successor = s->successor_layer;
+        s->successor_layer = NULL;     /* avoid freeing it ourself */
+        ssh_ppl_replace(&s->ppl, successor);
+        return;   /* we've just freed s, so avoid even touching s->crState */
+    }
+
+    crFinishV;
+}
+
+static void ssh2_userauth_add_session_id(
+    struct ssh2_userauth_state *s, strbuf *sigdata)
+{
+    if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) {
+        put_datapl(sigdata, s->session_id);
+    } else {
+        put_stringpl(sigdata, s->session_id);
+    }
+}
+
+static void ssh2_userauth_agent_query(
+    struct ssh2_userauth_state *s, strbuf *req)
+{
+    void *response;
+    int response_len;
+
+    sfree(s->agent_response_to_free);
+    s->agent_response_to_free = NULL;
+
+    s->auth_agent_query = agent_query(req, &response, &response_len,
+                                      ssh2_userauth_agent_callback, s, get_seat_callback_set(s->seat)); // WINSCP
+    if (!s->auth_agent_query)
+        ssh2_userauth_agent_callback(s, response, response_len);
+}
+
+static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen)
+{
+    struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav;
+
+    s->auth_agent_query = NULL;
+    s->agent_response_to_free = reply;
+    s->agent_response = make_ptrlen(reply, replylen);
+
+    queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+/*
+ * Helper function to add an SSH-2 signature blob to a packet. Expects
+ * to be shown the public key blob as well as the signature blob.
+ * Normally just appends the sig blob unmodified as a string, except
+ * that it optionally breaks it open and fiddle with it to work around
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_userauth_add_sigblob(
+    struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob)
+{
+    BinarySource pk[1], sig[1];
+    BinarySource_BARE_INIT_PL(pk, pkblob);
+    BinarySource_BARE_INIT_PL(sig, sigblob);
+
+    /* dmemdump(pkblob, pkblob_len); */
+    /* dmemdump(sigblob, sigblob_len); */
+
+    /*
+     * See if this is in fact an ssh-rsa signature and a buggy
+     * server; otherwise we can just do this the easy way.
+     */
+    if ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) &&
+        ptrlen_eq_string(get_string(pk), "ssh-rsa") &&
+        ptrlen_eq_string(get_string(sig), "ssh-rsa")) {
+        ptrlen mod_mp, sig_mp;
+        size_t sig_prefix_len;
+
+        /*
+         * Find the modulus and signature integers.
+         */
+        get_string(pk);                /* skip over exponent */
+        mod_mp = get_string(pk);       /* remember modulus */
+        sig_prefix_len = sig->pos;
+        sig_mp = get_string(sig);
+        if (get_err(pk) || get_err(sig))
+            goto give_up;
+
+        /*
+         * Find the byte length of the modulus, not counting leading
+         * zeroes.
+         */
+        while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) {
+            mod_mp.len--;
+            mod_mp.ptr = (const char *)mod_mp.ptr + 1;
+        }
+
+        /* debug("modulus length is %d\n", len); */
+        /* debug("signature length is %d\n", siglen); */
+
+        if (mod_mp.len > sig_mp.len) {
+            strbuf *substr = strbuf_new();
+            put_data(substr, sigblob.ptr, sig_prefix_len);
+            put_uint32(substr, mod_mp.len);
+            put_padding(substr, mod_mp.len - sig_mp.len, 0);
+            put_datapl(substr, sig_mp);
+            put_stringsb(pkt, substr);
+            return;
+        }
+
+        /* Otherwise fall through and do it the easy way. We also come
+         * here as a fallback if we discover above that the key blob
+         * is misformatted in some way. */
+      give_up:;
+    }
+
+    put_stringpl(pkt, sigblob);
+}
+
+#ifndef NO_GSSAPI
+static PktOut *ssh2_userauth_gss_packet(
+    struct ssh2_userauth_state *s, const char *authtype)
+{
+    strbuf *sb;
+    PktOut *p;
+    Ssh_gss_buf buf;
+    Ssh_gss_buf mic;
+
+    /*
+     * The mic is computed over the session id + intended
+     * USERAUTH_REQUEST packet.
+     */
+    sb = strbuf_new();
+    put_stringpl(sb, s->session_id);
+    put_byte(sb, SSH2_MSG_USERAUTH_REQUEST);
+    put_stringz(sb, s->username);
+    put_stringz(sb, s->successor_layer->vt->name);
+    put_stringz(sb, authtype);
+
+    /* Compute the mic */
+    buf.value = sb->s;
+    buf.length = sb->len;
+    s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic);
+    strbuf_free(sb);
+
+    /* Now we can build the real packet */
+    if (strcmp(authtype, "gssapi-with-mic") == 0) {
+        p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC);
+    } else {
+        p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+        put_stringz(p, s->username);
+        put_stringz(p, s->successor_layer->vt->name);
+        put_stringz(p, authtype);
+    }
+    put_string(p, mic.value, mic.length);
+
+    return p;
+}
+#endif
+
+static bool ssh2_userauth_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+    /* No specials provided by this layer. */
+    return false;
+}
+
+static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl,
+                                      SessionSpecialCode code, int arg)
+{
+    /* No specials provided by this layer. */
+}
+
+static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+    struct ssh2_userauth_state *s =
+        container_of(ppl, struct ssh2_userauth_state, ppl);
+    ssh_ppl_reconfigure(s->successor_layer, conf);
+}

+ 637 - 0
source/putty/ssh/verstring.c

@@ -0,0 +1,637 @@
+/*
+ * Code to handle the initial SSH version string exchange.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "sshcr.h"
+
+#define PREFIX_MAXLEN 64
+
+struct ssh_verstring_state {
+    int crState;
+
+    Conf *conf;
+    ptrlen prefix_wanted;
+    char *our_protoversion;
+    struct ssh_version_receiver *receiver;
+
+    bool send_early;
+
+    bool found_prefix;
+    int major_protoversion;
+    int remote_bugs;
+    char prefix[PREFIX_MAXLEN];
+    char *impl_name;
+    strbuf *vstring;
+    char *protoversion;
+    const char *softwareversion;
+
+    char *our_vstring;
+    int i;
+
+    BinaryPacketProtocol bpp;
+};
+
+static void ssh_verstring_free(BinaryPacketProtocol *bpp);
+static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp);
+static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp);
+static PktOut *ssh_verstring_new_pktout(int type);
+static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
+                                          const char *msg, int category);
+
+static const BinaryPacketProtocolVtable ssh_verstring_vtable = {
+    // WINSCP
+    /*.free =*/ ssh_verstring_free,
+    /*.handle_input =*/ ssh_verstring_handle_input,
+    /*.handle_output =*/ ssh_verstring_handle_output,
+    /*.new_pktout =*/ ssh_verstring_new_pktout,
+    /*.queue_disconnect =*/ ssh_verstring_queue_disconnect,
+    /*.packet_size_limit =*/ 0xFFFFFFFF, /* no special limit for this bpp */
+};
+
+static void ssh_detect_bugs(struct ssh_verstring_state *s);
+static bool ssh_version_includes_v1(const char *ver);
+static bool ssh_version_includes_v2(const char *ver);
+
+BinaryPacketProtocol *ssh_verstring_new(
+    Conf *conf, LogContext *logctx, bool bare_connection_mode,
+    const char *protoversion, struct ssh_version_receiver *rcv,
+    bool server_mode, const char *impl_name)
+{
+    struct ssh_verstring_state *s = snew(struct ssh_verstring_state);
+
+    memset(s, 0, sizeof(struct ssh_verstring_state));
+
+    if (!bare_connection_mode) {
+        s->prefix_wanted = PTRLEN_LITERAL("SSH-");
+    } else {
+        /*
+         * Ordinary SSH begins with the banner "SSH-x.y-...". Here,
+         * we're going to be speaking just the ssh-connection
+         * subprotocol, extracted and given a trivial binary packet
+         * protocol, so we need a new banner.
+         *
+         * The new banner is like the ordinary SSH banner, but
+         * replaces the prefix 'SSH-' at the start with a new name. In
+         * proper SSH style (though of course this part of the proper
+         * SSH protocol _isn't_ subject to this kind of
+         * DNS-domain-based extension), we define the new name in our
+         * extension space.
+         */
+        s->prefix_wanted = PTRLEN_LITERAL(
+            "[email protected]");
+    }
+    assert(s->prefix_wanted.len <= PREFIX_MAXLEN);
+
+    s->conf = conf_copy(conf);
+    s->bpp.logctx = logctx;
+    s->our_protoversion = dupstr(protoversion);
+    s->receiver = rcv;
+    s->impl_name = dupstr(impl_name);
+    s->vstring = strbuf_new();
+
+    /*
+     * We send our version string early if we can. But if it includes
+     * SSH-1, we can't, because we have to take the other end into
+     * account too (see below).
+     *
+     * In server mode, we do send early.
+     */
+    s->send_early = server_mode || !ssh_version_includes_v1(protoversion);
+
+    /*
+     * Override: we don't send our version string early if the server
+     * has a bug that will make it discard it. See for example
+     * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958
+     */
+    if (conf_get_int(s->conf, CONF_sshbug_dropstart) == FORCE_ON)
+        s->send_early = false;
+
+    s->bpp.vt = &ssh_verstring_vtable;
+    ssh_bpp_common_setup(&s->bpp);
+    return &s->bpp;
+}
+
+void ssh_verstring_free(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        container_of(bpp, struct ssh_verstring_state, bpp);
+    conf_free(s->conf);
+    sfree(s->impl_name);
+    strbuf_free(s->vstring);
+    sfree(s->protoversion);
+    sfree(s->our_vstring);
+    sfree(s->our_protoversion);
+    sfree(s);
+}
+
+static int ssh_versioncmp(const char *a, const char *b)
+{
+    char *ae, *be;
+    unsigned long av, bv;
+
+    av = strtoul(a, &ae, 10);
+    bv = strtoul(b, &be, 10);
+    if (av != bv)
+        return (av < bv ? -1 : +1);
+    if (*ae == '.')
+        ae++;
+    if (*be == '.')
+        be++;
+    av = strtoul(ae, &ae, 10);
+    bv = strtoul(be, &be, 10);
+    if (av != bv)
+        return (av < bv ? -1 : +1);
+    return 0;
+}
+
+static bool ssh_version_includes_v1(const char *ver)
+{
+    return ssh_versioncmp(ver, "2.0") < 0;
+}
+
+static bool ssh_version_includes_v2(const char *ver)
+{
+    return ssh_versioncmp(ver, "1.99") >= 0;
+}
+
+static void ssh_verstring_send(struct ssh_verstring_state *s)
+{
+    BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
+    char *p;
+    int sv_pos;
+
+    /*
+     * Construct our outgoing version string.
+     */
+    s->our_vstring = dupprintf(
+        "%.*s%s-%s%s",
+        (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr,
+        s->our_protoversion, s->impl_name, sshver);
+    sv_pos = s->prefix_wanted.len + strlen(s->our_protoversion) + 1;
+
+    /* Convert minus signs and spaces in the software version string
+     * into underscores. */
+    for (p = s->our_vstring + sv_pos; *p; p++) {
+        if (*p == '-' || *p == ' ')
+            *p = '_';
+    }
+
+#ifdef FUZZING
+    /*
+     * Replace the first character of the string with an "I" if we're
+     * compiling this code for fuzzing - i.e. the protocol prefix
+     * becomes "ISH-" instead of "SSH-".
+     *
+     * This is irrelevant to any real client software (the only thing
+     * reading the output of PuTTY built for fuzzing is the fuzzer,
+     * which can adapt to whatever it sees anyway). But it's a safety
+     * precaution making it difficult to accidentally run such a
+     * version of PuTTY (which would be hugely insecure) against a
+     * live peer implementation.
+     *
+     * (So the replacement prefix "ISH" notionally stands for
+     * 'Insecure Shell', of course.)
+     */
+    s->our_vstring[0] = 'I';
+#endif
+
+    /*
+     * Now send that version string, plus trailing \r\n or just \n
+     * (the latter in SSH-1 mode).
+     */
+    bufchain_add(s->bpp.out_raw, s->our_vstring, strlen(s->our_vstring));
+    if (ssh_version_includes_v2(s->our_protoversion))
+        bufchain_add(s->bpp.out_raw, "\015", 1);
+    bufchain_add(s->bpp.out_raw, "\012", 1);
+
+    bpp_logevent("We claim version: %s", s->our_vstring);
+}
+
+#define BPP_WAITFOR(minlen) do                          \
+    {                                                                   \
+        bool success;                                                   \
+        crMaybeWaitUntilV(                                              \
+            (success = (bufchain_size(s->bpp.in_raw) >= (minlen))) ||   \
+            s->bpp.input_eof);                                          \
+        if (!success)                                                   \
+            goto eof;                                                   \
+    } while (0)
+
+void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        container_of(bpp, struct ssh_verstring_state, bpp);
+
+    crBegin(s->crState);
+
+    /*
+     * If we're sending our version string up front before seeing the
+     * other side's, then do it now.
+     */
+    if (s->send_early)
+        ssh_verstring_send(s);
+
+    /*
+     * Search for a line beginning with the protocol name prefix in
+     * the input.
+     */
+    s->i = 0;
+    while (1) {
+        /*
+         * Every time round this loop, we're at the start of a new
+         * line, so look for the prefix.
+         */
+        BPP_WAITFOR(s->prefix_wanted.len);
+        bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len);
+        if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) {
+            bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len);
+            ssh_check_frozen(s->bpp.ssh);
+            break;
+        }
+
+        /*
+         * If we didn't find it, consume data until we see a newline.
+         */
+        while (1) {
+            ptrlen data;
+            char *nl;
+
+            /* Wait to receive at least 1 byte, but then consume more
+             * than that if it's there. */
+            BPP_WAITFOR(1);
+            data = bufchain_prefix(s->bpp.in_raw);
+            if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) {
+                bufchain_consume(s->bpp.in_raw, nl - (char *)data.ptr + 1);
+                ssh_check_frozen(s->bpp.ssh);
+                break;
+            } else {
+                bufchain_consume(s->bpp.in_raw, data.len);
+                ssh_check_frozen(s->bpp.ssh);
+            }
+        }
+    }
+
+    s->found_prefix = true;
+
+    /*
+     * Copy the greeting line so far into vstring.
+     */
+    put_data(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len);
+
+    /*
+     * Now read the rest of the greeting line.
+     */
+    s->i = 0;
+    do {
+        ptrlen data;
+        char *nl;
+
+        BPP_WAITFOR(1);
+        data = bufchain_prefix(s->bpp.in_raw);
+        if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) {
+            data.len = nl - (char *)data.ptr + 1;
+        }
+
+        put_datapl(s->vstring, data);
+        bufchain_consume(s->bpp.in_raw, data.len);
+        ssh_check_frozen(s->bpp.ssh);
+
+    } while (s->vstring->s[s->vstring->len-1] != '\012');
+
+    /*
+     * Trim \r and \n from the version string, and replace them with
+     * a NUL terminator.
+     */
+    while (s->vstring->len > 0 &&
+           (s->vstring->s[s->vstring->len-1] == '\r' ||
+            s->vstring->s[s->vstring->len-1] == '\n'))
+        strbuf_shrink_by(s->vstring, 1);
+
+    bpp_logevent("Remote version: %s", s->vstring->s);
+
+    /*
+     * Pick out the protocol version and software version. The former
+     * goes in a separately allocated string, so that s->vstring
+     * remains intact for later use in key exchange; the latter is the
+     * tail of s->vstring, so it doesn't need to be allocated.
+     */
+    {
+        const char *pv_start = s->vstring->s + s->prefix_wanted.len;
+        int pv_len = strcspn(pv_start, "-");
+        s->protoversion = dupprintf("%.*s", pv_len, pv_start);
+        s->softwareversion = pv_start + pv_len;
+        if (*s->softwareversion) {
+            assert(*s->softwareversion == '-');
+            s->softwareversion++;
+        }
+    }
+
+    ssh_detect_bugs(s);
+
+    /*
+     * Figure out what actual SSH protocol version we're speaking.
+     */
+    if (ssh_version_includes_v2(s->our_protoversion) &&
+        ssh_version_includes_v2(s->protoversion)) {
+        /*
+         * We're doing SSH-2.
+         */
+        s->major_protoversion = 2;
+    } else if (ssh_version_includes_v1(s->our_protoversion) &&
+               ssh_version_includes_v1(s->protoversion)) {
+        /*
+         * We're doing SSH-1.
+         */
+        s->major_protoversion = 1;
+
+        /*
+         * There are multiple minor versions of SSH-1, and the
+         * protocol does not specify that the minimum of client
+         * and server versions is used. So we must adjust our
+         * outgoing protocol version to be no higher than that of
+         * the other side.
+         */
+        if (!s->send_early &&
+            ssh_versioncmp(s->our_protoversion, s->protoversion) > 0) {
+            sfree(s->our_protoversion);
+            s->our_protoversion = dupstr(s->protoversion);
+        }
+    } else {
+        /*
+         * Unable to agree on a major protocol version at all.
+         */
+        if (!ssh_version_includes_v2(s->our_protoversion)) {
+            ssh_sw_abort(s->bpp.ssh,
+                         "SSH protocol version 1 required by our "
+                         "configuration but not provided by remote");
+        } else {
+            ssh_sw_abort(s->bpp.ssh,
+                         "SSH protocol version 2 required by our "
+                         "configuration but remote only provides "
+                         "(old, insecure) SSH-1");
+        }
+        crStopV;
+    }
+
+    bpp_logevent("Using SSH protocol version %d", s->major_protoversion);
+
+    if (!s->send_early) {
+        /*
+         * If we didn't send our version string early, construct and
+         * send it now, because now we know what it is.
+         */
+        ssh_verstring_send(s);
+    }
+
+    /*
+     * And we're done. Notify our receiver that we now know our
+     * protocol version. This will cause it to disconnect us from the
+     * input stream and ultimately free us, because our job is now
+     * done.
+     */
+    s->receiver->got_ssh_version(s->receiver, s->major_protoversion);
+    return;
+
+  eof:
+    ssh_remote_error(s->bpp.ssh,
+                     "Remote side unexpectedly closed network connection");
+    return;  /* avoid touching s now it's been freed */
+
+    crFinishV;
+}
+
+static PktOut *ssh_verstring_new_pktout(int type)
+{
+    unreachable("Should never try to send packets during SSH version "
+                "string exchange");
+}
+
+static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp)
+{
+    if (pq_peek(&bpp->out_pq)) {
+        unreachable("Should never try to send packets during SSH version "
+                    "string exchange");
+    }
+}
+
+/*
+ * Examine the remote side's version string, and compare it against a
+ * list of known buggy implementations.
+ */
+static void ssh_detect_bugs(struct ssh_verstring_state *s)
+{
+    BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
+    const char *imp = s->softwareversion;
+
+    s->remote_bugs = 0;
+
+    /*
+     * General notes on server version strings:
+     *  - Not all servers reporting "Cisco-1.25" have all the bugs listed
+     *    here -- in particular, we've heard of one that's perfectly happy
+     *    with SSH1_MSG_IGNOREs -- but this string never seems to change,
+     *    so we can't distinguish them.
+     */
+    if (conf_get_int(s->conf, CONF_sshbug_ignore1) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_ignore1) == AUTO &&
+         (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
+          !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
+          !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") ||
+          !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) {
+        /*
+         * These versions don't support SSH1_MSG_IGNORE, so we have
+         * to use a different defence against password length
+         * sniffing.
+         */
+        s->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
+        bpp_logevent("We believe remote version has SSH-1 ignore bug");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_plainpw1) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_plainpw1) == AUTO &&
+         (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) {
+        /*
+         * These versions need a plain password sent; they can't
+         * handle having a null and a random length of data after
+         * the password.
+         */
+        s->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
+        bpp_logevent("We believe remote version needs a "
+                     "plain SSH-1 password");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_rsa1) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_rsa1) == AUTO &&
+         (!strcmp(imp, "Cisco-1.25")))) {
+        /*
+         * These versions apparently have no clue whatever about
+         * RSA authentication and will panic and die if they see
+         * an AUTH_RSA message.
+         */
+        s->remote_bugs |= BUG_CHOKES_ON_RSA;
+        bpp_logevent("We believe remote version can't handle SSH-1 "
+                     "RSA authentication");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_hmac2) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_hmac2) == AUTO &&
+         !wc_match("* VShell", imp) &&
+         (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) ||
+          wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) ||
+          wc_match("2.1 *", imp)))) {
+        /*
+         * These versions have the HMAC bug.
+         */
+        s->remote_bugs |= BUG_SSH2_HMAC;
+        bpp_logevent("We believe remote version has SSH-2 HMAC bug");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_derivekey2) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_derivekey2) == AUTO &&
+         !wc_match("* VShell", imp) &&
+         (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) {
+        /*
+         * These versions have the key-derivation bug (failing to
+         * include the literal shared secret in the hashes that
+         * generate the keys).
+         */
+        s->remote_bugs |= BUG_SSH2_DERIVEKEY;
+        bpp_logevent("We believe remote version has SSH-2 "
+                     "key-derivation bug");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_rsapad2) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_rsapad2) == AUTO &&
+         (wc_match("OpenSSH_2.[5-9]*", imp) ||
+          wc_match("OpenSSH_3.[0-2]*", imp) ||
+          wc_match("mod_sftp/0.[0-8]*", imp) ||
+          wc_match("mod_sftp/0.9.[0-8]", imp)))) {
+        /*
+         * These versions have the SSH-2 RSA padding bug.
+         */
+        s->remote_bugs |= BUG_SSH2_RSA_PADDING;
+        bpp_logevent("We believe remote version has SSH-2 RSA padding bug");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_pksessid2) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_pksessid2) == AUTO &&
+         wc_match("OpenSSH_2.[0-2]*", imp))) {
+        /*
+         * These versions have the SSH-2 session-ID bug in
+         * public-key authentication.
+         */
+        s->remote_bugs |= BUG_SSH2_PK_SESSIONID;
+        bpp_logevent("We believe remote version has SSH-2 "
+                     "public-key-session-ID bug");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_rekey2) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_rekey2) == AUTO &&
+         (wc_match("DigiSSH_2.0", imp) ||
+          wc_match("OpenSSH_2.[0-4]*", imp) ||
+          wc_match("OpenSSH_2.5.[0-3]*", imp) ||
+          wc_match("Sun_SSH_1.0", imp) ||
+          wc_match("Sun_SSH_1.0.1", imp) ||
+          /* All versions <= 1.2.6 (they changed their format in 1.2.7) */
+          wc_match("WeOnlyDo-*", imp)))) {
+        /*
+         * These versions have the SSH-2 rekey bug.
+         */
+        s->remote_bugs |= BUG_SSH2_REKEY;
+        bpp_logevent("We believe remote version has SSH-2 rekey bug");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == AUTO &&
+         (wc_match("1.36_sshlib GlobalSCAPE", imp) ||
+          wc_match("1.36 sshlib: GlobalScape", imp)))) {
+        /*
+         * This version ignores our makpkt and needs to be throttled.
+         */
+        s->remote_bugs |= BUG_SSH2_MAXPKT;
+        bpp_logevent("We believe remote version ignores SSH-2 "
+                     "maximum packet size");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_ignore2) == FORCE_ON) {
+        /*
+         * Servers that don't support SSH2_MSG_IGNORE. Currently,
+         * none detected automatically.
+         */
+        s->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
+        bpp_logevent("We believe remote version has SSH-2 ignore bug");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_oldgex2) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_oldgex2) == AUTO &&
+         (wc_match("OpenSSH_2.[235]*", imp)))) {
+        /*
+         * These versions only support the original (pre-RFC4419)
+         * SSH-2 GEX request, and disconnect with a protocol error if
+         * we use the newer version.
+         */
+        s->remote_bugs |= BUG_SSH2_OLDGEX;
+        bpp_logevent("We believe remote version has outdated SSH-2 GEX");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_winadj) == FORCE_ON) {
+        /*
+         * Servers that don't support our winadj request for one
+         * reason or another. Currently, none detected automatically.
+         */
+        s->remote_bugs |= BUG_CHOKES_ON_WINADJ;
+        bpp_logevent("We believe remote version has winadj bug");
+    }
+
+    if (conf_get_int(s->conf, CONF_sshbug_chanreq) == FORCE_ON ||
+        (conf_get_int(s->conf, CONF_sshbug_chanreq) == AUTO &&
+         (wc_match("OpenSSH_[2-5].*", imp) ||
+          wc_match("OpenSSH_6.[0-6]*", imp) ||
+          wc_match("dropbear_0.[2-4][0-9]*", imp) ||
+          wc_match("dropbear_0.5[01]*", imp)))) {
+        /*
+         * These versions have the SSH-2 channel request bug.
+         * OpenSSH 6.7 and above do not:
+         * https://bugzilla.mindrot.org/show_bug.cgi?id=1818
+         * dropbear_0.52 and above do not:
+         * https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c
+         */
+        s->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY;
+        bpp_logevent("We believe remote version has SSH-2 "
+                     "channel request bug");
+    }
+}
+
+const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        container_of(bpp, struct ssh_verstring_state, bpp);
+    return s->vstring->s;
+}
+
+const char *ssh_verstring_get_local(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        container_of(bpp, struct ssh_verstring_state, bpp);
+    return s->our_vstring;
+}
+
+int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp)
+{
+    struct ssh_verstring_state *s =
+        container_of(bpp, struct ssh_verstring_state, bpp);
+    return s->remote_bugs;
+}
+
+static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
+                                           const char *msg, int category)
+{
+    /* No way to send disconnect messages at this stage of the protocol! */
+}

+ 1254 - 0
source/putty/ssh/zlib.c

@@ -0,0 +1,1254 @@
+/*
+ * Zlib (RFC1950 / RFC1951) compression for PuTTY.
+ *
+ * There will no doubt be criticism of my decision to reimplement
+ * Zlib compression from scratch instead of using the existing zlib
+ * code. People will cry `reinventing the wheel'; they'll claim
+ * that the `fundamental basis of OSS' is code reuse; they'll want
+ * to see a really good reason for me having chosen not to use the
+ * existing code.
+ *
+ * Well, here are my reasons. Firstly, I don't want to link the
+ * whole of zlib into the PuTTY binary; PuTTY is justifiably proud
+ * of its small size and I think zlib contains a lot of unnecessary
+ * baggage for the kind of compression that SSH requires.
+ *
+ * Secondly, I also don't like the alternative of using zlib.dll.
+ * Another thing PuTTY is justifiably proud of is its ease of
+ * installation, and the last thing I want to do is to start
+ * mandating DLLs. Not only that, but there are two _kinds_ of
+ * zlib.dll kicking around, one with C calling conventions on the
+ * exported functions and another with WINAPI conventions, and
+ * there would be a significant danger of getting the wrong one.
+ *
+ * Thirdly, there seems to be a difference of opinion on the IETF
+ * secsh mailing list about the correct way to round off a
+ * compressed packet and start the next. In particular, there's
+ * some talk of switching to a mechanism zlib isn't currently
+ * capable of supporting (see below for an explanation). Given that
+ * sort of uncertainty, I thought it might be better to have code
+ * that will support even the zlib-incompatible worst case.
+ *
+ * Fourthly, it's a _second implementation_. Second implementations
+ * are fundamentally a Good Thing in standardisation efforts. The
+ * difference of opinion mentioned above has arisen _precisely_
+ * because there has been only one zlib implementation and
+ * everybody has used it. I don't intend that this should happen
+ * again.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "defs.h"
+#include "ssh.h"
+
+/* ----------------------------------------------------------------------
+ * Basic LZ77 code. This bit is designed modularly, so it could be
+ * ripped out and used in a different LZ77 compressor. Go to it,
+ * and good luck :-)
+ */
+
+struct LZ77InternalContext;
+struct LZ77Context {
+    struct LZ77InternalContext *ictx;
+    void *userdata;
+    void (*literal) (struct LZ77Context * ctx, unsigned char c);
+    void (*match) (struct LZ77Context * ctx, int distance, int len);
+};
+
+/*
+ * Initialise the private fields of an LZ77Context. It's up to the
+ * user to initialise the public fields.
+ */
+static int lz77_init(struct LZ77Context *ctx);
+
+/*
+ * Supply data to be compressed. Will update the private fields of
+ * the LZ77Context, and will call literal() and match() to output.
+ * If `compress' is false, it will never emit a match, but will
+ * instead call literal() for everything.
+ */
+static void lz77_compress(struct LZ77Context *ctx,
+                          const unsigned char *data, int len);
+
+/*
+ * Modifiable parameters.
+ */
+#define WINSIZE 32768                  /* window size. Must be power of 2! */
+#define HASHMAX 2039                   /* one more than max hash value */
+#define MAXMATCH 32                    /* how many matches we track */
+#define HASHCHARS 3                    /* how many chars make a hash */
+
+/*
+ * This compressor takes a less slapdash approach than the
+ * gzip/zlib one. Rather than allowing our hash chains to fall into
+ * disuse near the far end, we keep them doubly linked so we can
+ * _find_ the far end, and then every time we add a new byte to the
+ * window (thus rolling round by one and removing the previous
+ * byte), we can carefully remove the hash chain entry.
+ */
+
+#define INVALID -1                     /* invalid hash _and_ invalid offset */
+struct WindowEntry {
+    short next, prev;                  /* array indices within the window */
+    short hashval;
+};
+
+struct HashEntry {
+    short first;                       /* window index of first in chain */
+};
+
+struct Match {
+    int distance, len;
+};
+
+struct LZ77InternalContext {
+    struct WindowEntry win[WINSIZE];
+    unsigned char data[WINSIZE];
+    int winpos;
+    struct HashEntry hashtab[HASHMAX];
+    unsigned char pending[HASHCHARS];
+    int npending;
+};
+
+static int lz77_hash(const unsigned char *data)
+{
+    return (257 * data[0] + 263 * data[1] + 269 * data[2]) % HASHMAX;
+}
+
+static int lz77_init(struct LZ77Context *ctx)
+{
+    struct LZ77InternalContext *st;
+    int i;
+
+    st = snew(struct LZ77InternalContext);
+    if (!st)
+        return 0;
+
+    ctx->ictx = st;
+
+    for (i = 0; i < WINSIZE; i++)
+        st->win[i].next = st->win[i].prev = st->win[i].hashval = INVALID;
+    for (i = 0; i < HASHMAX; i++)
+        st->hashtab[i].first = INVALID;
+    st->winpos = 0;
+
+    st->npending = 0;
+
+    return 1;
+}
+
+static void lz77_advance(struct LZ77InternalContext *st,
+                         unsigned char c, int hash)
+{
+    int off;
+
+    /*
+     * Remove the hash entry at winpos from the tail of its chain,
+     * or empty the chain if it's the only thing on the chain.
+     */
+    if (st->win[st->winpos].prev != INVALID) {
+        st->win[st->win[st->winpos].prev].next = INVALID;
+    } else if (st->win[st->winpos].hashval != INVALID) {
+        st->hashtab[st->win[st->winpos].hashval].first = INVALID;
+    }
+
+    /*
+     * Create a new entry at winpos and add it to the head of its
+     * hash chain.
+     */
+    st->win[st->winpos].hashval = hash;
+    st->win[st->winpos].prev = INVALID;
+    off = st->win[st->winpos].next = st->hashtab[hash].first;
+    st->hashtab[hash].first = st->winpos;
+    if (off != INVALID)
+        st->win[off].prev = st->winpos;
+    st->data[st->winpos] = c;
+
+    /*
+     * Advance the window pointer.
+     */
+    st->winpos = (st->winpos + 1) & (WINSIZE - 1);
+}
+
+#define CHARAT(k) ( (k)<0 ? st->data[(st->winpos+k)&(WINSIZE-1)] : data[k] )
+
+static void lz77_compress(struct LZ77Context *ctx,
+                          const unsigned char *data, int len)
+{
+    struct LZ77InternalContext *st = ctx->ictx;
+    int i, distance, off, nmatch, matchlen, advance;
+    struct Match defermatch, matches[MAXMATCH];
+    int deferchr;
+
+    assert(st->npending <= HASHCHARS);
+
+    /*
+     * Add any pending characters from last time to the window. (We
+     * might not be able to.)
+     *
+     * This leaves st->pending empty in the usual case (when len >=
+     * HASHCHARS); otherwise it leaves st->pending empty enough that
+     * adding all the remaining 'len' characters will not push it past
+     * HASHCHARS in size.
+     */
+    for (i = 0; i < st->npending; i++) {
+        unsigned char foo[HASHCHARS];
+        int j;
+        if (len + st->npending - i < HASHCHARS) {
+            /* Update the pending array. */
+            for (j = i; j < st->npending; j++)
+                st->pending[j - i] = st->pending[j];
+            break;
+        }
+        for (j = 0; j < HASHCHARS; j++)
+            foo[j] = (i + j < st->npending ? st->pending[i + j] :
+                      data[i + j - st->npending]);
+        lz77_advance(st, foo[0], lz77_hash(foo));
+    }
+    st->npending -= i;
+
+    defermatch.distance = 0; /* appease compiler */
+    defermatch.len = 0;
+    deferchr = '\0';
+    while (len > 0) {
+
+        if (len >= HASHCHARS) {
+            /*
+             * Hash the next few characters.
+             */
+            int hash = lz77_hash(data);
+
+            /*
+             * Look the hash up in the corresponding hash chain and see
+             * what we can find.
+             */
+            nmatch = 0;
+            for (off = st->hashtab[hash].first;
+                 off != INVALID; off = st->win[off].next) {
+                /* distance = 1       if off == st->winpos-1 */
+                /* distance = WINSIZE if off == st->winpos   */
+                distance =
+                    WINSIZE - (off + WINSIZE - st->winpos) % WINSIZE;
+                for (i = 0; i < HASHCHARS; i++)
+                    if (CHARAT(i) != CHARAT(i - distance))
+                        break;
+                if (i == HASHCHARS) {
+                    matches[nmatch].distance = distance;
+                    matches[nmatch].len = 3;
+                    if (++nmatch >= MAXMATCH)
+                        break;
+                }
+            }
+        } else {
+            nmatch = 0;
+        }
+
+        if (nmatch > 0) {
+            /*
+             * We've now filled up matches[] with nmatch potential
+             * matches. Follow them down to find the longest. (We
+             * assume here that it's always worth favouring a
+             * longer match over a shorter one.)
+             */
+            matchlen = HASHCHARS;
+            while (matchlen < len) {
+                int j;
+                for (i = j = 0; i < nmatch; i++) {
+                    if (CHARAT(matchlen) ==
+                        CHARAT(matchlen - matches[i].distance)) {
+                        matches[j++] = matches[i];
+                    }
+                }
+                if (j == 0)
+                    break;
+                matchlen++;
+                nmatch = j;
+            }
+
+            /*
+             * We've now got all the longest matches. We favour the
+             * shorter distances, which means we go with matches[0].
+             * So see if we want to defer it or throw it away.
+             */
+            matches[0].len = matchlen;
+            if (defermatch.len > 0) {
+                if (matches[0].len > defermatch.len + 1) {
+                    /* We have a better match. Emit the deferred char,
+                     * and defer this match. */
+                    ctx->literal(ctx, (unsigned char) deferchr);
+                    defermatch = matches[0];
+                    deferchr = data[0];
+                    advance = 1;
+                } else {
+                    /* We don't have a better match. Do the deferred one. */
+                    ctx->match(ctx, defermatch.distance, defermatch.len);
+                    advance = defermatch.len - 1;
+                    defermatch.len = 0;
+                }
+            } else {
+                /* There was no deferred match. Defer this one. */
+                defermatch = matches[0];
+                deferchr = data[0];
+                advance = 1;
+            }
+        } else {
+            /*
+             * We found no matches. Emit the deferred match, if
+             * any; otherwise emit a literal.
+             */
+            if (defermatch.len > 0) {
+                ctx->match(ctx, defermatch.distance, defermatch.len);
+                advance = defermatch.len - 1;
+                defermatch.len = 0;
+            } else {
+                ctx->literal(ctx, data[0]);
+                advance = 1;
+            }
+        }
+
+        /*
+         * Now advance the position by `advance' characters,
+         * keeping the window and hash chains consistent.
+         */
+        while (advance > 0) {
+            if (len >= HASHCHARS) {
+                lz77_advance(st, *data, lz77_hash(data));
+            } else {
+                assert(st->npending < HASHCHARS);
+                st->pending[st->npending++] = *data;
+            }
+            data++;
+            len--;
+            advance--;
+        }
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Zlib compression. We always use the static Huffman tree option.
+ * Mostly this is because it's hard to scan a block in advance to
+ * work out better trees; dynamic trees are great when you're
+ * compressing a large file under no significant time constraint,
+ * but when you're compressing little bits in real time, things get
+ * hairier.
+ *
+ * I suppose it's possible that I could compute Huffman trees based
+ * on the frequencies in the _previous_ block, as a sort of
+ * heuristic, but I'm not confident that the gain would balance out
+ * having to transmit the trees.
+ */
+
+struct Outbuf {
+    strbuf *outbuf;
+    unsigned long outbits;
+    int noutbits;
+    bool firstblock;
+};
+
+static void outbits(struct Outbuf *out, unsigned long bits, int nbits)
+{
+    assert(out->noutbits + nbits <= 32);
+    out->outbits |= bits << out->noutbits;
+    out->noutbits += nbits;
+    while (out->noutbits >= 8) {
+        put_byte(out->outbuf, out->outbits & 0xFF);
+        out->outbits >>= 8;
+        out->noutbits -= 8;
+    }
+}
+
+static const unsigned char mirrorbytes[256] = {
+    0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+    0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+    0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+    0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+    0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+    0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+    0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+    0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+    0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+    0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+    0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+    0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+    0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+    0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+    0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+    0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+    0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+    0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+    0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+    0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+    0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+    0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+    0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+    0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+    0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+    0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+    0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+    0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+    0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+    0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+    0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+    0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+typedef struct {
+    short code, extrabits;
+    int min, max;
+} coderecord;
+
+static const coderecord lencodes[] = {
+    {257, 0, 3, 3},
+    {258, 0, 4, 4},
+    {259, 0, 5, 5},
+    {260, 0, 6, 6},
+    {261, 0, 7, 7},
+    {262, 0, 8, 8},
+    {263, 0, 9, 9},
+    {264, 0, 10, 10},
+    {265, 1, 11, 12},
+    {266, 1, 13, 14},
+    {267, 1, 15, 16},
+    {268, 1, 17, 18},
+    {269, 2, 19, 22},
+    {270, 2, 23, 26},
+    {271, 2, 27, 30},
+    {272, 2, 31, 34},
+    {273, 3, 35, 42},
+    {274, 3, 43, 50},
+    {275, 3, 51, 58},
+    {276, 3, 59, 66},
+    {277, 4, 67, 82},
+    {278, 4, 83, 98},
+    {279, 4, 99, 114},
+    {280, 4, 115, 130},
+    {281, 5, 131, 162},
+    {282, 5, 163, 194},
+    {283, 5, 195, 226},
+    {284, 5, 227, 257},
+    {285, 0, 258, 258},
+};
+
+static const coderecord distcodes[] = {
+    {0, 0, 1, 1},
+    {1, 0, 2, 2},
+    {2, 0, 3, 3},
+    {3, 0, 4, 4},
+    {4, 1, 5, 6},
+    {5, 1, 7, 8},
+    {6, 2, 9, 12},
+    {7, 2, 13, 16},
+    {8, 3, 17, 24},
+    {9, 3, 25, 32},
+    {10, 4, 33, 48},
+    {11, 4, 49, 64},
+    {12, 5, 65, 96},
+    {13, 5, 97, 128},
+    {14, 6, 129, 192},
+    {15, 6, 193, 256},
+    {16, 7, 257, 384},
+    {17, 7, 385, 512},
+    {18, 8, 513, 768},
+    {19, 8, 769, 1024},
+    {20, 9, 1025, 1536},
+    {21, 9, 1537, 2048},
+    {22, 10, 2049, 3072},
+    {23, 10, 3073, 4096},
+    {24, 11, 4097, 6144},
+    {25, 11, 6145, 8192},
+    {26, 12, 8193, 12288},
+    {27, 12, 12289, 16384},
+    {28, 13, 16385, 24576},
+    {29, 13, 24577, 32768},
+};
+
+static void zlib_literal(struct LZ77Context *ectx, unsigned char c)
+{
+    struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+
+    if (c <= 143) {
+        /* 0 through 143 are 8 bits long starting at 00110000. */
+        outbits(out, mirrorbytes[0x30 + c], 8);
+    } else {
+        /* 144 through 255 are 9 bits long starting at 110010000. */
+        outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9);
+    }
+}
+
+static void zlib_match(struct LZ77Context *ectx, int distance, int len)
+{
+    const coderecord *d, *l;
+    int i, j, k;
+    struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+
+    while (len > 0) {
+        int thislen;
+
+        /*
+         * We can transmit matches of lengths 3 through 258
+         * inclusive. So if len exceeds 258, we must transmit in
+         * several steps, with 258 or less in each step.
+         *
+         * Specifically: if len >= 261, we can transmit 258 and be
+         * sure of having at least 3 left for the next step. And if
+         * len <= 258, we can just transmit len. But if len == 259
+         * or 260, we must transmit len-3.
+         */
+        thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3);
+        len -= thislen;
+
+        /*
+         * Binary-search to find which length code we're
+         * transmitting.
+         */
+        i = -1;
+        j = lenof(lencodes);
+        while (1) {
+            assert(j - i >= 2);
+            k = (j + i) / 2;
+            if (thislen < lencodes[k].min)
+                j = k;
+            else if (thislen > lencodes[k].max)
+                i = k;
+            else {
+                l = &lencodes[k];
+                break;                 /* found it! */
+            }
+        }
+
+        /*
+         * Transmit the length code. 256-279 are seven bits
+         * starting at 0000000; 280-287 are eight bits starting at
+         * 11000000.
+         */
+        if (l->code <= 279) {
+            outbits(out, mirrorbytes[(l->code - 256) * 2], 7);
+        } else {
+            outbits(out, mirrorbytes[0xc0 - 280 + l->code], 8);
+        }
+
+        /*
+         * Transmit the extra bits.
+         */
+        if (l->extrabits)
+            outbits(out, thislen - l->min, l->extrabits);
+
+        /*
+         * Binary-search to find which distance code we're
+         * transmitting.
+         */
+        i = -1;
+        j = lenof(distcodes);
+        while (1) {
+            assert(j - i >= 2);
+            k = (j + i) / 2;
+            if (distance < distcodes[k].min)
+                j = k;
+            else if (distance > distcodes[k].max)
+                i = k;
+            else {
+                d = &distcodes[k];
+                break;                 /* found it! */
+            }
+        }
+
+        /*
+         * Transmit the distance code. Five bits starting at 00000.
+         */
+        outbits(out, mirrorbytes[d->code * 8], 5);
+
+        /*
+         * Transmit the extra bits.
+         */
+        if (d->extrabits)
+            outbits(out, distance - d->min, d->extrabits);
+    }
+}
+
+struct ssh_zlib_compressor {
+    struct LZ77Context ectx;
+    ssh_compressor sc;
+};
+
+ssh_compressor *zlib_compress_init(void)
+{
+    struct Outbuf *out;
+    struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor);
+
+    lz77_init(&comp->ectx);
+    comp->sc.vt = &ssh_zlib;
+    comp->ectx.literal = zlib_literal;
+    comp->ectx.match = zlib_match;
+
+    out = snew(struct Outbuf);
+    out->outbuf = NULL;
+    out->outbits = out->noutbits = 0;
+    out->firstblock = true;
+    comp->ectx.userdata = out;
+
+    return &comp->sc;
+}
+
+void zlib_compress_cleanup(ssh_compressor *sc)
+{
+    struct ssh_zlib_compressor *comp =
+        container_of(sc, struct ssh_zlib_compressor, sc);
+    struct Outbuf *out = (struct Outbuf *)comp->ectx.userdata;
+    if (out->outbuf)
+        strbuf_free(out->outbuf);
+    sfree(out);
+    sfree(comp->ectx.ictx);
+    sfree(comp);
+}
+
+void zlib_compress_block(ssh_compressor *sc,
+                         const unsigned char *block, int len,
+                         unsigned char **outblock, int *outlen,
+                         int minlen)
+{
+    struct ssh_zlib_compressor *comp =
+        container_of(sc, struct ssh_zlib_compressor, sc);
+    struct Outbuf *out = (struct Outbuf *) comp->ectx.userdata;
+    bool in_block;
+
+    assert(!out->outbuf);
+    out->outbuf = strbuf_new_nm();
+
+    /*
+     * If this is the first block, output the Zlib (RFC1950) header
+     * bytes 78 9C. (Deflate compression, 32K window size, default
+     * algorithm.)
+     */
+    if (out->firstblock) {
+        outbits(out, 0x9C78, 16);
+        out->firstblock = false;
+
+        in_block = false;
+    } else
+        in_block = true;
+
+    if (!in_block) {
+        /*
+         * Start a Deflate (RFC1951) fixed-trees block. We
+         * transmit a zero bit (BFINAL=0), followed by a zero
+         * bit and a one bit (BTYPE=01). Of course these are in
+         * the wrong order (01 0).
+         */
+        outbits(out, 2, 3);
+    }
+
+    /*
+     * Do the compression.
+     */
+    lz77_compress(&comp->ectx, block, len);
+
+    /*
+     * End the block (by transmitting code 256, which is
+     * 0000000 in fixed-tree mode), and transmit some empty
+     * blocks to ensure we have emitted the byte containing the
+     * last piece of genuine data. There are three ways we can
+     * do this:
+     *
+     *  - Minimal flush. Output end-of-block and then open a
+     *    new static block. This takes 9 bits, which is
+     *    guaranteed to flush out the last genuine code in the
+     *    closed block; but allegedly zlib can't handle it.
+     *
+     *  - Zlib partial flush. Output EOB, open and close an
+     *    empty static block, and _then_ open the new block.
+     *    This is the best zlib can handle.
+     *
+     *  - Zlib sync flush. Output EOB, then an empty
+     *    _uncompressed_ block (000, then sync to byte
+     *    boundary, then send bytes 00 00 FF FF). Then open the
+     *    new block.
+     *
+     * For the moment, we will use Zlib partial flush.
+     */
+    outbits(out, 0, 7);        /* close block */
+    outbits(out, 2, 3 + 7);    /* empty static block */
+    outbits(out, 2, 3);        /* open new block */
+
+    /*
+     * If we've been asked to pad out the compressed data until it's
+     * at least a given length, do so by emitting further empty static
+     * blocks.
+     */
+    while (out->outbuf->len < minlen) {
+        outbits(out, 0, 7);            /* close block */
+        outbits(out, 2, 3);            /* open new static block */
+    }
+
+    *outlen = out->outbuf->len;
+    *outblock = (unsigned char *)strbuf_to_str(out->outbuf);
+    out->outbuf = NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Zlib decompression. Of course, even though our compressor always
+ * uses static trees, our _decompressor_ has to be capable of
+ * handling dynamic trees if it sees them.
+ */
+
+/*
+ * The way we work the Huffman decode is to have a table lookup on
+ * the first N bits of the input stream (in the order they arrive,
+ * of course, i.e. the first bit of the Huffman code is in bit 0).
+ * Each table entry lists the number of bits to consume, plus
+ * either an output code or a pointer to a secondary table.
+ */
+struct zlib_table;
+struct zlib_tableentry;
+
+struct zlib_tableentry {
+    unsigned char nbits;
+    short code;
+    struct zlib_table *nexttable;
+};
+
+struct zlib_table {
+    int mask;                          /* mask applied to input bit stream */
+    struct zlib_tableentry *table;
+};
+
+#define MAXCODELEN 16
+#define MAXSYMS 288
+
+/*
+ * Build a single-level decode table for elements
+ * [minlength,maxlength) of the provided code/length tables, and
+ * recurse to build subtables.
+ */
+static struct zlib_table *zlib_mkonetab(int *codes, unsigned char *lengths,
+                                        int nsyms,
+                                        int pfx, int pfxbits, int bits)
+{
+    struct zlib_table *tab = snew(struct zlib_table);
+    int pfxmask = (1 << pfxbits) - 1;
+    int nbits, i, j, code;
+
+    tab->table = snewn((size_t)1 << bits, struct zlib_tableentry);
+    tab->mask = (1 << bits) - 1;
+
+    for (code = 0; code <= tab->mask; code++) {
+        tab->table[code].code = -1;
+        tab->table[code].nbits = 0;
+        tab->table[code].nexttable = NULL;
+    }
+
+    for (i = 0; i < nsyms; i++) {
+        if (lengths[i] <= pfxbits || (codes[i] & pfxmask) != pfx)
+            continue;
+        code = (codes[i] >> pfxbits) & tab->mask;
+        for (j = code; j <= tab->mask; j += 1 << (lengths[i] - pfxbits)) {
+            tab->table[j].code = i;
+            nbits = lengths[i] - pfxbits;
+            if (tab->table[j].nbits < nbits)
+                tab->table[j].nbits = nbits;
+        }
+    }
+    for (code = 0; code <= tab->mask; code++) {
+        if (tab->table[code].nbits <= bits)
+            continue;
+        /* Generate a subtable. */
+        tab->table[code].code = -1;
+        nbits = tab->table[code].nbits - bits;
+        if (nbits > 7)
+            nbits = 7;
+        tab->table[code].nbits = bits;
+        tab->table[code].nexttable = zlib_mkonetab(codes, lengths, nsyms,
+                                                   pfx | (code << pfxbits),
+                                                   pfxbits + bits, nbits);
+    }
+
+    return tab;
+}
+
+/*
+ * Build a decode table, given a set of Huffman tree lengths.
+ */
+static struct zlib_table *zlib_mktable(unsigned char *lengths,
+                                       int nlengths)
+{
+    int count[MAXCODELEN], startcode[MAXCODELEN], codes[MAXSYMS];
+    int code, maxlen;
+    int i, j;
+
+    /* Count the codes of each length. */
+    maxlen = 0;
+    for (i = 1; i < MAXCODELEN; i++)
+        count[i] = 0;
+    for (i = 0; i < nlengths; i++) {
+        count[lengths[i]]++;
+        if (maxlen < lengths[i])
+            maxlen = lengths[i];
+    }
+    /* Determine the starting code for each length block. */
+    code = 0;
+    for (i = 1; i < MAXCODELEN; i++) {
+        startcode[i] = code;
+        code += count[i];
+        code <<= 1;
+    }
+    /* Determine the code for each symbol. Mirrored, of course. */
+    for (i = 0; i < nlengths; i++) {
+        code = startcode[lengths[i]]++;
+        codes[i] = 0;
+        for (j = 0; j < lengths[i]; j++) {
+            codes[i] = (codes[i] << 1) | (code & 1);
+            code >>= 1;
+        }
+    }
+
+    /*
+     * Now we have the complete list of Huffman codes. Build a
+     * table.
+     */
+    return zlib_mkonetab(codes, lengths, nlengths, 0, 0,
+                         maxlen < 9 ? maxlen : 9);
+}
+
+static int zlib_freetable(struct zlib_table **ztab)
+{
+    struct zlib_table *tab;
+    int code;
+
+    if (ztab == NULL)
+        return -1;
+
+    if (*ztab == NULL)
+        return 0;
+
+    tab = *ztab;
+
+    for (code = 0; code <= tab->mask; code++)
+        if (tab->table[code].nexttable != NULL)
+            zlib_freetable(&tab->table[code].nexttable);
+
+    sfree(tab->table);
+    tab->table = NULL;
+
+    sfree(tab);
+    *ztab = NULL;
+
+    return (0);
+}
+
+struct zlib_decompress_ctx {
+    struct zlib_table *staticlentable, *staticdisttable;
+    struct zlib_table *currlentable, *currdisttable, *lenlentable;
+    enum {
+        START, OUTSIDEBLK,
+        TREES_HDR, TREES_LENLEN, TREES_LEN, TREES_LENREP,
+        INBLK, GOTLENSYM, GOTLEN, GOTDISTSYM,
+        UNCOMP_LEN, UNCOMP_NLEN, UNCOMP_DATA
+    } state;
+    int sym, hlit, hdist, hclen, lenptr, lenextrabits, lenaddon, len,
+        lenrep;
+    int uncomplen;
+    unsigned char lenlen[19];
+
+    /*
+     * Array that accumulates the code lengths sent in the header of a
+     * dynamic-Huffman-tree block.
+     *
+     * There are 286 actual symbols in the literal/length alphabet
+     * (256 literals plus 20 length categories), and 30 symbols in the
+     * distance alphabet. However, the block header transmits the
+     * number of code lengths for the former alphabet as a 5-bit value
+     * HLIT to be added to 257, and the latter as a 5-bit value HDIST
+     * to be added to 1. This means that the number of _code lengths_
+     * can go as high as 288 for the symbol alphabet and 32 for the
+     * distance alphabet - each of those values being 2 more than the
+     * maximum number of actual symbols.
+     *
+     * It's tempting to rule that sending out-of-range HLIT or HDIST
+     * is therefore just illegal, and to fault it when we initially
+     * receive that header. But instead I've chosen to permit the
+     * Huffman-code definition to include code length entries for
+     * those unused symbols; if a header of that form is transmitted,
+     * then the effect will be that in the main body of the block,
+     * some bit sequence(s) will generate an illegal symbol number,
+     * and _that_ will be faulted as a decoding error.
+     *
+     * Rationale: this can already happen! The standard Huffman code
+     * used in a _static_ block for the literal/length alphabet is
+     * defined in such a way that it includes codes for symbols 287
+     * and 288, which are then never actually sent in the body of the
+     * block. And I think that if the standard static tree definition
+     * is willing to include Huffman codes that don't correspond to a
+     * symbol, then it's an excessive restriction on dynamic tables
+     * not to permit them to do the same. In particular, it would be
+     * strange for a dynamic block not to be able to exactly mimic
+     * either or both of the Huffman codes used by a static block for
+     * the corresponding alphabet.
+     *
+     * So we place no constraint on HLIT or HDIST during code
+     * construction, and we make this array large enough to include
+     * the maximum number of code lengths that can possibly arise as a
+     * result. It's only trying to _use_ the junk Huffman codes after
+     * table construction is completed that will provoke a decode
+     * error.
+     */
+    unsigned char lengths[288 + 32];
+
+    unsigned long bits;
+    int nbits;
+    unsigned char window[WINSIZE];
+    int winpos;
+    strbuf *outblk;
+
+    ssh_decompressor dc;
+};
+
+ssh_decompressor *zlib_decompress_init(void)
+{
+    struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx);
+    unsigned char lengths[288];
+
+    memset(lengths, 8, 144);
+    memset(lengths + 144, 9, 256 - 144);
+    memset(lengths + 256, 7, 280 - 256);
+    memset(lengths + 280, 8, 288 - 280);
+    dctx->staticlentable = zlib_mktable(lengths, 288);
+    memset(lengths, 5, 32);
+    dctx->staticdisttable = zlib_mktable(lengths, 32);
+    dctx->state = START;                       /* even before header */
+    dctx->currlentable = dctx->currdisttable = dctx->lenlentable = NULL;
+    dctx->bits = 0;
+    dctx->nbits = 0;
+    dctx->winpos = 0;
+    dctx->outblk = NULL;
+
+    dctx->dc.vt = &ssh_zlib;
+    return &dctx->dc;
+}
+
+void zlib_decompress_cleanup(ssh_decompressor *dc)
+{
+    struct zlib_decompress_ctx *dctx =
+        container_of(dc, struct zlib_decompress_ctx, dc);
+
+    if (dctx->currlentable && dctx->currlentable != dctx->staticlentable)
+        zlib_freetable(&dctx->currlentable);
+    if (dctx->currdisttable && dctx->currdisttable != dctx->staticdisttable)
+        zlib_freetable(&dctx->currdisttable);
+    if (dctx->lenlentable)
+        zlib_freetable(&dctx->lenlentable);
+    zlib_freetable(&dctx->staticlentable);
+    zlib_freetable(&dctx->staticdisttable);
+    if (dctx->outblk)
+        strbuf_free(dctx->outblk);
+    sfree(dctx);
+}
+
+static int zlib_huflookup(unsigned long *bitsp, int *nbitsp,
+                   struct zlib_table *tab)
+{
+    unsigned long bits = *bitsp;
+    int nbits = *nbitsp;
+    while (1) {
+        struct zlib_tableentry *ent;
+        ent = &tab->table[bits & tab->mask];
+        if (ent->nbits > nbits)
+            return -1;                 /* not enough data */
+        bits >>= ent->nbits;
+        nbits -= ent->nbits;
+        if (ent->code == -1)
+            tab = ent->nexttable;
+        else {
+            *bitsp = bits;
+            *nbitsp = nbits;
+            return ent->code;
+        }
+
+        if (!tab) {
+            /*
+             * There was a missing entry in the table, presumably
+             * due to an invalid Huffman table description, and the
+             * subsequent data has attempted to use the missing
+             * entry. Return a decoding failure.
+             */
+            return -2;
+        }
+    }
+}
+
+static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c)
+{
+    dctx->window[dctx->winpos] = c;
+    dctx->winpos = (dctx->winpos + 1) & (WINSIZE - 1);
+    put_byte(dctx->outblk, c);
+}
+
+#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) )
+
+bool zlib_decompress_block(ssh_decompressor *dc,
+                           const unsigned char *block, int len,
+                           unsigned char **outblock, int *outlen)
+{
+    struct zlib_decompress_ctx *dctx =
+        container_of(dc, struct zlib_decompress_ctx, dc);
+    const coderecord *rec;
+    int code, blktype, rep, dist, nlen, header;
+    static const unsigned char lenlenmap[] = {
+        16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+    };
+
+    assert(!dctx->outblk);
+    dctx->outblk = strbuf_new_nm();
+
+    while (len > 0 || dctx->nbits > 0) {
+        while (dctx->nbits < 24 && len > 0) {
+            dctx->bits |= (*block++) << dctx->nbits;
+            dctx->nbits += 8;
+            len--;
+        }
+        switch (dctx->state) {
+          case START:
+            /* Expect 16-bit zlib header. */
+            if (dctx->nbits < 16)
+                goto finished;         /* done all we can */
+
+            /*
+             * The header is stored as a big-endian 16-bit integer,
+             * in contrast to the general little-endian policy in
+             * the rest of the format :-(
+             */
+            header = (((dctx->bits & 0xFF00) >> 8) |
+                      ((dctx->bits & 0x00FF) << 8));
+            EATBITS(16);
+
+            /*
+             * Check the header:
+             *
+             *  - bits 8-11 should be 1000 (Deflate/RFC1951)
+             *  - bits 12-15 should be at most 0111 (window size)
+             *  - bit 5 should be zero (no dictionary present)
+             *  - we don't care about bits 6-7 (compression rate)
+             *  - bits 0-4 should be set up to make the whole thing
+             *    a multiple of 31 (checksum).
+             */
+            if ((header & 0x0F00) != 0x0800 ||
+                (header & 0xF000) >  0x7000 ||
+                (header & 0x0020) != 0x0000 ||
+                (header % 31) != 0)
+                goto decode_error;
+
+            dctx->state = OUTSIDEBLK;
+            break;
+          case OUTSIDEBLK:
+            /* Expect 3-bit block header. */
+            if (dctx->nbits < 3)
+                goto finished;         /* done all we can */
+            EATBITS(1);
+            blktype = dctx->bits & 3;
+            EATBITS(2);
+            if (blktype == 0) {
+                int to_eat = dctx->nbits & 7;
+                dctx->state = UNCOMP_LEN;
+                EATBITS(to_eat);       /* align to byte boundary */
+            } else if (blktype == 1) {
+                dctx->currlentable = dctx->staticlentable;
+                dctx->currdisttable = dctx->staticdisttable;
+                dctx->state = INBLK;
+            } else if (blktype == 2) {
+                dctx->state = TREES_HDR;
+            }
+            break;
+          case TREES_HDR:
+            /*
+             * Dynamic block header. Five bits of HLIT, five of
+             * HDIST, four of HCLEN.
+             */
+            if (dctx->nbits < 5 + 5 + 4)
+                goto finished;         /* done all we can */
+            dctx->hlit = 257 + (dctx->bits & 31);
+            EATBITS(5);
+            dctx->hdist = 1 + (dctx->bits & 31);
+            EATBITS(5);
+            dctx->hclen = 4 + (dctx->bits & 15);
+            EATBITS(4);
+            dctx->lenptr = 0;
+            dctx->state = TREES_LENLEN;
+            memset(dctx->lenlen, 0, sizeof(dctx->lenlen));
+            break;
+          case TREES_LENLEN:
+            if (dctx->nbits < 3)
+                goto finished;
+            while (dctx->lenptr < dctx->hclen && dctx->nbits >= 3) {
+                dctx->lenlen[lenlenmap[dctx->lenptr++]] =
+                    (unsigned char) (dctx->bits & 7);
+                EATBITS(3);
+            }
+            if (dctx->lenptr == dctx->hclen) {
+                dctx->lenlentable = zlib_mktable(dctx->lenlen, 19);
+                dctx->state = TREES_LEN;
+                dctx->lenptr = 0;
+            }
+            break;
+          case TREES_LEN:
+            if (dctx->lenptr >= dctx->hlit + dctx->hdist) {
+                dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit);
+                dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit,
+                                                  dctx->hdist);
+                zlib_freetable(&dctx->lenlentable);
+                dctx->lenlentable = NULL;
+                dctx->state = INBLK;
+                break;
+            }
+            code =
+                zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->lenlentable);
+            if (code == -1)
+                goto finished;
+            if (code == -2)
+                goto decode_error;
+            if (code < 16)
+                dctx->lengths[dctx->lenptr++] = code;
+            else {
+                dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7);
+                dctx->lenaddon = (code == 18 ? 11 : 3);
+                dctx->lenrep = (code == 16 && dctx->lenptr > 0 ?
+                               dctx->lengths[dctx->lenptr - 1] : 0);
+                dctx->state = TREES_LENREP;
+            }
+            break;
+          case TREES_LENREP:
+            if (dctx->nbits < dctx->lenextrabits)
+                goto finished;
+            rep =
+                dctx->lenaddon +
+                (dctx->bits & ((1 << dctx->lenextrabits) - 1));
+            EATBITS(dctx->lenextrabits);
+            while (rep > 0 && dctx->lenptr < dctx->hlit + dctx->hdist) {
+                dctx->lengths[dctx->lenptr] = dctx->lenrep;
+                dctx->lenptr++;
+                rep--;
+            }
+            dctx->state = TREES_LEN;
+            break;
+          case INBLK:
+            code =
+                zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->currlentable);
+            if (code == -1)
+                goto finished;
+            if (code == -2)
+                goto decode_error;
+            if (code < 256)
+                zlib_emit_char(dctx, code);
+            else if (code == 256) {
+                dctx->state = OUTSIDEBLK;
+                if (dctx->currlentable != dctx->staticlentable) {
+                    zlib_freetable(&dctx->currlentable);
+                    dctx->currlentable = NULL;
+                }
+                if (dctx->currdisttable != dctx->staticdisttable) {
+                    zlib_freetable(&dctx->currdisttable);
+                    dctx->currdisttable = NULL;
+                }
+            } else if (code < 286) {
+                dctx->state = GOTLENSYM;
+                dctx->sym = code;
+            } else {
+                /* literal/length symbols 286 and 287 are invalid */
+                goto decode_error;
+            }
+            break;
+          case GOTLENSYM:
+            rec = &lencodes[dctx->sym - 257];
+            if (dctx->nbits < rec->extrabits)
+                goto finished;
+            dctx->len =
+                rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
+            EATBITS(rec->extrabits);
+            dctx->state = GOTLEN;
+            break;
+          case GOTLEN:
+            code =
+                zlib_huflookup(&dctx->bits, &dctx->nbits,
+                               dctx->currdisttable);
+            if (code == -1)
+                goto finished;
+            if (code == -2)
+                goto decode_error;
+            if (code >= 30)            /* dist symbols 30 and 31 are invalid */
+                goto decode_error;
+            dctx->state = GOTDISTSYM;
+            dctx->sym = code;
+            break;
+          case GOTDISTSYM:
+            rec = &distcodes[dctx->sym];
+            if (dctx->nbits < rec->extrabits)
+                goto finished;
+            dist = rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
+            EATBITS(rec->extrabits);
+            dctx->state = INBLK;
+            while (dctx->len--)
+                zlib_emit_char(dctx, dctx->window[(dctx->winpos - dist) &
+                                                  (WINSIZE - 1)]);
+            break;
+          case UNCOMP_LEN:
+            /*
+             * Uncompressed block. We expect to see a 16-bit LEN.
+             */
+            if (dctx->nbits < 16)
+                goto finished;
+            dctx->uncomplen = dctx->bits & 0xFFFF;
+            EATBITS(16);
+            dctx->state = UNCOMP_NLEN;
+            break;
+          case UNCOMP_NLEN:
+            /*
+             * Uncompressed block. We expect to see a 16-bit NLEN,
+             * which should be the one's complement of the previous
+             * LEN.
+             */
+            if (dctx->nbits < 16)
+                goto finished;
+            nlen = dctx->bits & 0xFFFF;
+            EATBITS(16);
+            if (dctx->uncomplen != (nlen ^ 0xFFFF))
+                goto decode_error;
+            if (dctx->uncomplen == 0)
+                dctx->state = OUTSIDEBLK;       /* block is empty */
+            else
+                dctx->state = UNCOMP_DATA;
+            break;
+          case UNCOMP_DATA:
+            if (dctx->nbits < 8)
+                goto finished;
+            zlib_emit_char(dctx, dctx->bits & 0xFF);
+            EATBITS(8);
+            if (--dctx->uncomplen == 0)
+                dctx->state = OUTSIDEBLK;       /* end of uncompressed block */
+            break;
+        }
+    }
+
+  finished:
+    *outlen = dctx->outblk->len;
+    *outblock = (unsigned char *)strbuf_to_str(dctx->outblk);
+    dctx->outblk = NULL;
+    return true;
+
+  decode_error:
+    *outblock = NULL;
+    *outlen = 0;
+    return false;
+}
+
+const ssh_compression_alg ssh_zlib = {
+    // WINSCP
+    /*.name =*/ "zlib",
+    /*.delayed_name =*/ "[email protected]", /* delayed version */
+    /*.compress_new =*/ zlib_compress_init,
+    /*.compress_free =*/ zlib_compress_cleanup,
+    /*.compress =*/ zlib_compress_block,
+    /*.decompress_new =*/ zlib_decompress_init,
+    /*.decompress_free =*/ zlib_decompress_cleanup,
+    /*.decompress =*/ zlib_decompress_block,
+    /*.text_name =*/ "zlib (RFC1950)",
+};

+ 4 - 0
source/putty/sshpubk.c

@@ -181,6 +181,8 @@ static bool expect_signature(BinarySource *src, ptrlen realsig)
     return !get_err(src) && ptrlen_eq_ptrlen(realsig, thissig);
 }
 
+#ifndef WINSCP
+
 static int rsa1_load_s_internal(BinarySource *src, RSAKey *key, bool pub_only,
                                 char **commentptr, const char *passphrase,
                                 const char **error)
@@ -512,6 +514,8 @@ bool rsa1_save_f(const Filename *filename, RSAKey *key, const char *passphrase)
     } // WINSCP
 }
 
+#endif
+
 /* ----------------------------------------------------------------------
  * SSH-2 private key load/store functions.
  *

+ 42 - 0
source/putty/stubs/nullplug.c

@@ -0,0 +1,42 @@
+/*
+ * nullplug.c: provide a null implementation of the Plug vtable which
+ * ignores all calls. Occasionally useful in cases where we want to
+ * make a network connection just to see if it works, but not do
+ * anything with it afterwards except close it again.
+ */
+
+#include "putty.h"
+
+void nullplug_log(Plug *plug, 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)
+{
+}
+
+static const PlugVtable nullplug_plugvt = {
+    // WINSCP
+    /*.log =*/ nullplug_log,
+    /*.closing =*/ nullplug_closing,
+    /*.receive =*/ nullplug_receive,
+    /*.sent =*/ nullplug_sent,
+    NULL, // WINSCP
+};
+
+static Plug nullplug_plug = { &nullplug_plugvt };
+
+/*
+ * There's a singleton instance of nullplug, because it's not
+ * interesting enough to worry about making more than one of them.
+ */
+Plug *const nullplug = &nullplug_plug;

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

@@ -0,0 +1,28 @@
+#include "putty.h"
+#include "misc.h"
+
+void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg)
+{
+    strbuf *sb = strbuf_new();
+    seat_set_trust_status(iseat.seat, true);
+    if (seat_can_set_trust_status(iseat.seat)) {
+        /*
+         * If the seat can directly indicate that this message is
+         * generated by the client, then we can just use the message
+         * unmodified as an unspoofable header.
+         */
+        put_dataz(sb, msg);
+    } else if (*msg) {
+        /*
+         * Otherwise, add enough padding around it that the server
+         * wouldn't be able to mimic it within our line-length
+         * constraint.
+         */
+        put_fmt(sb, "-- %s ", msg);
+        while (sb->len < 78)
+            put_byte(sb, '-');
+    }
+    put_datapl(sb, PTRLEN_LITERAL("\r\n"));
+    seat_banner_pl(iseat, ptrlen_from_strbuf(sb));
+    strbuf_free(sb);
+}

+ 62 - 0
source/putty/utils/backend_socket_log.c

@@ -0,0 +1,62 @@
+#include <assert.h>
+#include <string.h>
+
+#include "putty.h"
+#include "network.h"
+
+void backend_socket_log(Seat *seat, LogContext *logctx,
+                        PlugLogType type, SockAddr *addr, int port,
+                        const char *error_msg, int error_code, Conf *conf,
+                        bool session_started)
+{
+    char addrbuf[256], *msg;
+
+    switch (type) {
+      case PLUGLOG_CONNECT_TRYING:
+        sk_getaddr(addr, addrbuf, lenof(addrbuf));
+        if (sk_addr_needs_port(addr)) {
+            msg = dupprintf("Connecting to %s port %d", addrbuf, port);
+        } else {
+            msg = dupprintf("Connecting to %s", addrbuf);
+        }
+        break;
+      case PLUGLOG_CONNECT_FAILED:
+        sk_getaddr(addr, addrbuf, lenof(addrbuf));
+        msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
+        break;
+      case PLUGLOG_CONNECT_SUCCESS:
+        if (addr)
+            sk_getaddr(addr, addrbuf, lenof(addrbuf));
+        else /* fallback if address unavailable */
+            sprintf(addrbuf, "remote host");
+        msg = dupprintf("Connected to %s", addrbuf);
+        break;
+      case PLUGLOG_PROXY_MSG: {
+        /* Proxy-related log messages have their own identifying
+         * prefix already, put on by our caller. */
+        int len, log_to_term;
+
+        /* Suffix \r\n temporarily, so we can log to the terminal. */
+        msg = dupprintf("%s\r\n", error_msg);
+        len = strlen(msg);
+        assert(len >= 2);
+
+        log_to_term = conf_get_int(conf, CONF_proxy_log_to_term);
+        if (log_to_term == AUTO)
+            log_to_term = session_started ? FORCE_OFF : FORCE_ON;
+        if (log_to_term == FORCE_ON)
+            seat_stderr(seat, msg, len);
+
+        msg[len-2] = '\0';         /* remove the \r\n again */
+        break;
+      }
+      default:
+        msg = NULL;  /* shouldn't happen, but placate optimiser */
+        break;
+    }
+
+    if (msg) {
+        logevent(logctx, msg);
+        sfree(msg);
+    }
+}

+ 54 - 0
source/putty/utils/base64_decode_atom.c

@@ -0,0 +1,54 @@
+/*
+ * Core routine to decode a single atomic base64 chunk.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+int base64_decode_atom(const char *atom, unsigned char *out)
+{
+    int vals[4];
+    int i, v, len;
+    unsigned word;
+    char c;
+
+    for (i = 0; i < 4; i++) {
+        c = atom[i];
+        if (c >= 'A' && c <= 'Z')
+            v = c - 'A';
+        else if (c >= 'a' && c <= 'z')
+            v = c - 'a' + 26;
+        else if (c >= '0' && c <= '9')
+            v = c - '0' + 52;
+        else if (c == '+')
+            v = 62;
+        else if (c == '/')
+            v = 63;
+        else if (c == '=')
+            v = -1;
+        else
+            return 0;                  /* invalid atom */
+        vals[i] = v;
+    }
+
+    if (vals[0] == -1 || vals[1] == -1)
+        return 0;
+    if (vals[2] == -1 && vals[3] != -1)
+        return 0;
+
+    if (vals[3] != -1)
+        len = 3;
+    else if (vals[2] != -1)
+        len = 2;
+    else
+        len = 1;
+
+    word = ((vals[0] << 18) |
+            (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F));
+    out[0] = (word >> 16) & 0xFF;
+    if (len > 1)
+        out[1] = (word >> 8) & 0xFF;
+    if (len > 2)
+        out[2] = word & 0xFF;
+    return len;
+}

+ 30 - 0
source/putty/utils/base64_encode_atom.c

@@ -0,0 +1,30 @@
+/*
+ * Core routine to encode a single atomic base64 chunk.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+void base64_encode_atom(const unsigned char *data, int n, char *out)
+{
+    static const char base64_chars[] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    unsigned word;
+
+    word = data[0] << 16;
+    if (n > 1)
+        word |= data[1] << 8;
+    if (n > 2)
+        word |= data[2];
+    out[0] = base64_chars[(word >> 18) & 0x3F];
+    out[1] = base64_chars[(word >> 12) & 0x3F];
+    if (n > 1)
+        out[2] = base64_chars[(word >> 6) & 0x3F];
+    else
+        out[2] = '=';
+    if (n > 2)
+        out[3] = base64_chars[word & 0x3F];
+    else
+        out[3] = '=';
+}

部分文件因文件數量過多而無法顯示